c49f6ec8bb175c1490aee6068141671846aa3875
1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 # This program is a fun game but also a good example of the scene2d module
21 # The ship of the player
25 # Current forture of the player
26 var money
: Int writable = 0
28 # Number of basic bullets fired together
29 var nbshoots
: Int writable = 1
31 # Time bebore the player shoot again a basic bullet (cooldown)
36 var nbmissiles
: Int writable = 0
38 # Time bebore the player shoot again a missile (cooldown)
42 # Remainind time when the player is protected from impacts
45 # The associated play scene
46 # (mainly used to registed shoots)
49 init(scene
: PlayScene) do
63 else if self.y
> 60000 then
70 else if self.x
> 80000 then
75 # Update of the player protection if any
76 if protected_ttl
> 0 then protected_ttl
-= 1
78 # Need to shoot basic bullets?
83 for i
in [0..nbshoots
[ do
88 shoot
.vx
= (i
- nbshoots
/ 2) * 100
89 scene
.player_shoots
.add
(shoot
)
93 # Need to shoot missiles?
94 if missile_ttl
> 0 then
96 else if nbmissiles
> 0 then
97 missile_ttl
= 500 / nbmissiles
98 var shoot
= new Missile
103 scene
.player_shoots
.add
(shoot
)
108 # Time before the player is respawned by the scene
109 var respawn_ttl
: Int = 0
113 if self.protected_ttl
> 0 then return
114 self.scene
.explosion
(self.x
, self.y
, 10)
117 # Reset the position for respawn
122 self.respawn_ttl
= 50
127 # Sprites that may be hit by the player.
128 # Eq. enemies, bullets, loots, etc.
132 # What do do when self is hit by the player.
133 # By defaut, do nothing
134 fun hit_by_player
(player
: Player) do end
137 # A bullet shooted by a ship
141 # Was the shoot fired by an enemy.
142 # Since there is no frendly fire, it is important to distinguish ownership
143 var enemy
: Bool = false
155 if self.y
< -100 * 100 or self.y
> 700 * 100 or self.x
< -100 * 100 or self.x
> 900 * 100 then
160 redef fun hit_by_player
(player
)
167 # A advanced bullet that aims a target (player or enemy)
171 # The target aquired by the missile
172 var target
: nullable Sprite
174 # When ttl is 0 then the angle stay fixed
175 # The angle is updated toward the target if ttl>0
182 # Do we still update the angle ?
183 if ttl
<= 0 then return
186 # Do we have a target?
187 var target
= self.target
188 if target
== null or not target
.exists
then return
190 # Just update the angle
191 var angle
= self.angle_to
(target
)
192 self.set_velocity
(angle
, 300)
197 # Various enemies exists, each kind has its own subclass
201 # The scene of the ship
202 # Is used to store created bullets or to get info about the player
205 # Time bebore the enemy shoot again (cooldown)
207 # The default value is used as a grace period to avoid a first shoot on
211 init(scene
: PlayScene)
216 scene
.enemies
.add
(self)
224 if self.y
> 700 * 100 or self.x
< -100 * 100 or self.x
> 900 * 100 then
225 # Note: no control on the top to let ennemies appear
230 if shoot_ttl
> 0 then
237 # Each enemy has its own kind of shoot strategy
238 # Note: is automatically called by update when shoot_ttl is expired
241 # Money given when the enemy is destroyed
242 fun loot
: Int is abstract
244 # What to do when the enemy is hit by a player shoot (or by the player himself)?
245 # By default it kill the enemy in an explosion and generate a loot
249 scene
.explosion
(self.x
, self.y
, 5)
251 var upmissile
= new UpMissile
256 scene
.loots
.add
(upmissile
)
257 scene
.hitables
.add
(new LootArea(upmissile
, 2000))
259 for i
in [0..self.loot
[ do
260 var money
= new Money
263 money
.set_velocity
(100.rand
.to_f
*pi
/50.0, (500+self.loot
).rand
)
264 scene
.loots
.add
(money
)
265 scene
.hitables
.add
(new LootArea(money
, 2000))
270 redef fun hit_by_player
(player
)
277 # Basic enemy, do not shoot
281 redef fun loot
do return 3
284 # Simple shooter of paris of basic bullets
293 # two bullets shoot each time
294 for dx
in [-11, 11] do
295 var shoot
= new Shoot
297 shoot
.x
= self.x
+ dx
* 100
298 shoot
.y
= self.bottom
300 scene
.enemy_shoots
.add
(shoot
)
304 redef fun loot
do return 5
307 # Enemy that shoot missiles
316 # The missile targets the player
317 var shoot
= new Missile
320 shoot
.y
= self.bottom
322 shoot
.target
= scene
.player
323 scene
.enemy_shoots
.add
(shoot
)
326 redef fun loot
do return 10
329 # Enem that shoot rings of basic bullets
339 var shoot
= new Shoot
342 shoot
.y
= self.bottom
343 shoot
.set_velocity
(pi
/5.0*i
.to_f
, 500)
344 scene
.enemy_shoots
.add
(shoot
)
348 redef fun loot
do return 20
351 # Enemy with a turret that shoot burst of bullets toward the player
355 # The angle of the turret
356 var angle
: Float = 0.0
362 # Rotate the turret toward the player
363 var target
= scene
.player
364 if target
.exists
then
365 angle
= self.angle_to
(target
)
369 # Shoots come in burst
370 var nbshoots
: Int = 0
374 # Next shoot: is there still bullets in the burst?
375 if self.nbshoots
< 10 then
384 # Shoot with the turret angle
385 var shoot
= new Shoot
389 shoot
.set_velocity
(angle
, 500)
390 scene
.enemy_shoots
.add
(shoot
)
393 redef fun loot
do return 20
396 # Enemy that rush directly on the player
404 # Try to target the player
405 var target
= scene
.player
406 if not target
.exists
then return
408 var angle
= self.angle_to
(target
)
409 self.set_velocity
(angle
, 600)
412 redef fun loot
do return 5
415 # The boss has two semi-independent arms
420 var left_part
: BossPart
423 var right_part
: BossPart
428 self.width
= 128 * 100
429 self.height
= 100 * 100
432 self.left_part
= new BossPart(self, -48*100)
433 self.right_part
= new BossPart(self, 48*100)
436 var flick_ttl
: Int = 0
440 if flick_ttl
> 0 then flick_ttl
-= 1
442 # Path of the boss (down then left<->right)
443 if self.y
< 20000 then
446 else if self.vx
== 0 then
449 else if self.x
> 700 * 100 and self.vx
> 0 then
451 else if self.x
< 100 * 100 and self.vx
< 0 then
460 # Do not shoot if not ready
461 if self.vy
!= 0 then return
463 # Try to target the player
464 var target
= scene
.player
465 if not target
.exists
then return
467 # Next shoot: burst if no arms remains
468 if left_part
.exists
or right_part
.exists
then
474 # Shoot the player with a basic bullet
475 var shoot
= new Shoot
478 shoot
.y
= self.bottom
479 var angle
= shoot
.angle_to
(target
)
480 shoot
.set_velocity
(angle
, 500)
481 scene
.enemy_shoots
.add
(shoot
)
484 redef fun loot
do return 100
490 # Protected while an arm remains
491 if left_part
.exists
or right_part
.exists
then return
498 scene
.explosion
(self.x
, self.y
, 30)
507 # The associated boss
510 # Relative x coordonate (center to center) of the arm
513 # Relative y coordonate (center to center) of the arm
514 var rely
: Int = 36 * 100
518 init(boss
: Boss, relx
: Int)
523 self.width
= 32 * 100
524 self.height
= 60 * 100
526 # Alternate the shoots of the arms
530 self.x
= boss
.x
+ relx
531 self.y
= boss
.y
+ rely
536 self.x
= boss
.x
+ relx
537 self.y
= boss
.y
+ rely
541 if flick_ttl
> 0 then flick_ttl
-= 1
546 # Do not shoot if not ready
547 if self.boss
.vy
!= 0 then return
552 # Shoot a missile that targets the player
553 var shoot
= new Missile
556 shoot
.y
= self.bottom
558 shoot
.target
= scene
.player
559 scene
.enemy_shoots
.add
(shoot
)
562 var flick_ttl
: Int = 0
574 redef fun loot
do return 10
577 # Whatever reward or bonus that can be picked by the player
587 # Magnet effect: The loot will move to the target if set
588 # See LootArea for details
589 var target
: nullable Sprite = null
596 if self.y
> 700 * 100 then
600 var target
= self.target
601 if target
== null then
602 # Not magneted: deploy
604 # Heavy fuild friction to stops the explosion
605 # Loots are placed with a explosion, see `Enemy::hit'
606 self.vx
= self.vx
*7/8
607 self.vy
= self.vy
*7/8
609 # Background scroling
612 else if target
.exists
then
613 # Magneted: rush toward the target
614 var angle
= self.angle_to
(target
)
615 self.set_velocity
(angle
, 800)
618 # Magneted but dead target: reset the loot
630 redef fun hit_by_player
(player
)
634 if player
.money
> 100 then
641 # Increase the number of missiles
645 redef fun hit_by_player
(player
)
648 player
.nbmissiles
+= 1
652 # A loot area is an invisible field used to implement the magnet effets of loots
654 # * the loot is an invisible sprite with a hitbox larger than the loot hitbox
655 # * the lootbox remains centered on the loot
656 # * when the player hit the lootarea, then the loot is set to target the player
657 # * when the player hit the loot, then the player gains effectively the loot
661 # The associated loot
664 init(loot
: Loot, radius
: Int)
667 self.width
= radius
* 2 + loot
.width
668 self.height
= radius
* 2 + loot
.height
673 # position remains centered on the loot
678 if not loot
.exists
then self.exists
= false
680 # the super is useless but it is a good practice to call it
684 redef fun hit_by_player
(player
)
689 # The loot now targets the player
694 # A non interactive element of an explosion
695 # A real explosion is made of many Explosion object
696 # Use the `PlayScene::explosion` method to generate a full explosion
700 # Time before the sprite vanishes
705 # Heavy fuild friction to stops the explosion
706 self.vx
= self.vx
*7/8
707 self.vy
= self.vy
*7/8
709 # Background scrolling
723 # A star is a non-interactive background element
724 # Stars are used to simulate a continuous global scroling
730 # Randomely places stars on the plane
731 self.x
= 800.rand
* 100
732 self.y
= 600.rand
* 100
733 self.vy
= 40.rand
+ 11
740 # Replace the star on the top
741 if self.y
> 600 * 100 then
742 self.y
= 200.rand
* -100
743 self.x
= 800.rand
* 100
744 self.vy
= 40.rand
+ 11
750 # When a scene need to be replaced, just assign the next_scene to a non null value
751 var next_scene
: nullable Scene writable = null
754 # The main play state
761 # Shoots of the player
762 var player_shoots
= new LiveGroup[Shoot]
765 var enemies
= new LiveGroup[Enemy]
768 var enemy_shoots
= new LiveGroup[Shoot]
771 var loots
= new LiveGroup[Loot]
773 # Non active stuff like explosions
774 var pasive_stuff
= new LiveGroup[LiveObject]
776 # Background stuff like stars
777 var background
= new LiveGroup[LiveObject]
779 # All other hitable sprites
780 var hitables
= new LiveGroup[Hitable]
783 var sprites
= new LiveGroup[LiveObject]
787 self.player
= new Player(self)
790 self.sprites
.add
(background
)
791 self.sprites
.add
(pasive_stuff
)
792 self.sprites
.add
(loots
)
793 self.sprites
.add
(player_shoots
)
794 self.sprites
.add
(enemy_shoots
)
795 self.sprites
.add
(enemies
)
796 self.sprites
.add
(self.player
)
797 self.sprites
.add
(hitables
)
800 background
.add
(new Star)
804 # Generate an explosion
805 fun explosion
(x
, y
: Int, radius
: Int)
807 # Project explosion parts from the given position
808 # The strong friction and the short ttl of each part will achieve the effect
809 for i
in [0..radius
[ do
810 var ex
= new Explosion
813 ex
.set_velocity
(100.rand
.to_f
*pi
/50.0, (50*radius
).rand
)
814 ex
.ttl
+= radius
.rand
819 var enemy_remains
: Int = 15
820 var boss_wait_ttl
: Int = 0
821 var boss
: nullable Boss
828 if enemy_remains
== 0 then
829 if boss_wait_ttl
> 0 then
831 else if boss
== null then
832 boss
= new Boss(self)
834 else if not boss
.exists
then
837 else if 100.rand
< 1 then
839 if enemy_remains
== 0 then
845 enemy
= new Enemy0(self)
846 else if rnd
< 60 then
847 enemy
= new Enemy1(self)
848 else if rnd
< 70 then
849 enemy
= new EnemyKamikaze(self)
850 else if rnd
< 90 then
851 enemy
= new Enemy2(self)
852 else if rnd
< 95 then
853 enemy
= new Enemy3(self)
855 enemy
= new Enemy4(self)
857 enemy
.x
= 600.rand
* 100 + 10000
858 enemy
.vy
= 200.rand
+ 100
860 enemy
.vx
= 200.rand
- 100
864 for ps
in player_shoots
do
865 if not ps
.exists
then continue
866 var target
: nullable Enemy = null
867 var td
= 100000 # big int
869 if not e
.exists
then continue
870 if ps
.overlaps
(e
) then
874 var d
= (e
.x
- ps
.x
).abs
+ (e
.y
- ps
.y
).abs
880 if ps
isa Missile and (ps
.target
== null or not ps
.target
.exists
) then
886 if not e
.exists
then continue
887 if player
.exists
and player
.overlaps
(e
) then
888 e
.hit_by_player
(player
)
891 for s
in enemy_shoots
do
892 if not s
.exists
then continue
893 if player
.exists
and player
.overlaps
(s
) then
894 s
.hit_by_player
(player
)
898 if not l
.exists
then continue
899 if player
.exists
and player
.overlaps
(l
) then
900 l
.hit_by_player
(player
)
904 if not l
.exists
then continue
905 if player
.exists
and player
.overlaps
(l
) then
906 l
.hit_by_player
(player
)
909 if not player
.exists
then
910 if player
.respawn_ttl
> 0 then
911 player
.respawn_ttl
-= 1
914 player
.protected_ttl
= 100
915 self.sprites
.add
(self.player
)
926 var sprites
= new LiveGroup[LiveObject]
931 sprites
.add
(new Star)
935 var play
: Bool writable = false
942 if not play
then return
947 next_scene
= new PlayScene
954 # Only run the playscene
955 var scene
= new PlayScene
957 scene
.player
.nbshoots
= 5
958 scene
.player
.nbmissiles
= 5
962 if args
.length
> 0 then
963 turns
= args
.first
.to_i
965 for i
in [0..turns
[ do
966 for j
in [0..10000[ do
969 print
"{i}: money={scene.player.money} enemies={scene.enemies.length} shoots={scene.player_shoots.length}"