eee39fa7f4a25900ac0db750704c03d1285b421a
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 # Where the player is going
26 var going_target
= new GoingTarget
28 # Activate the `going_target`
29 fun goes_to
(x
,y
: Int, speed
: Int)
33 going_target
.active
= true
34 var angle
= angle_to
(going_target
)
35 set_velocity
(angle
, speed
)
38 # Current forture of the player
39 var money
: Int writable = 0
41 # Number of basic bullets fired together
42 var nbshoots
: Int writable = 1
44 # Time bebore the player shoot again a basic bullet (cooldown)
49 var nbmissiles
: Int writable = 0
51 # Time bebore the player shoot again a missile (cooldown)
55 # Remainind time when the player is protected from impacts
58 # The associated play scene
59 # (mainly used to registed shoots)
62 init(scene
: PlayScene) do
76 else if self.y
> 60000 then
83 else if self.x
> 80000 then
88 # Update of the player protection if any
89 if protected_ttl
> 0 then protected_ttl
-= 1
91 # Need to shoot basic bullets?
96 for i
in [0..nbshoots
[ do
101 shoot
.vx
= (i
- nbshoots
/ 2) * 100
102 scene
.player_shoots
.add
(shoot
)
106 # Need to shoot missiles?
107 if missile_ttl
> 0 then
109 else if nbmissiles
> 0 then
110 missile_ttl
= 500 / nbmissiles
111 var shoot
= new Missile
116 scene
.player_shoots
.add
(shoot
)
121 # Time before the player is respawned by the scene
122 var respawn_ttl
: Int = 0
126 if self.protected_ttl
> 0 then return
127 self.scene
.explosion
(self.x
, self.y
, 10)
130 # Reset the position for respawn
135 self.respawn_ttl
= 50
140 # Sprites that may be hit by the player.
141 # Eq. enemies, bullets, loots, etc.
145 # What do do when self is hit by the player.
146 # By defaut, do nothing
147 fun hit_by_player
(player
: Player) do end
150 # Destination for the player (pointer position)
154 # true in on move, false if player is at rest
155 var active
writable = false
162 redef fun hit_by_player
(player
)
164 if not active
then return
171 # A bullet shooted by a ship
175 # Was the shoot fired by an enemy.
176 # Since there is no frendly fire, it is important to distinguish ownership
177 var enemy
: Bool = false
189 if self.y
< -100 * 100 or self.y
> 700 * 100 or self.x
< -100 * 100 or self.x
> 900 * 100 then
194 redef fun hit_by_player
(player
)
201 # A advanced bullet that aims a target (player or enemy)
205 # The target aquired by the missile
206 var target
: nullable Sprite
208 # When ttl is 0 then the angle stay fixed
209 # The angle is updated toward the target if ttl>0
216 # Do we still update the angle ?
217 if ttl
<= 0 then return
220 # Do we have a target?
221 var target
= self.target
222 if target
== null or not target
.exists
then return
224 # Just update the angle
225 var angle
= self.angle_to
(target
)
226 self.set_velocity
(angle
, 300)
231 # Various enemies exists, each kind has its own subclass
235 # The scene of the ship
236 # Is used to store created bullets or to get info about the player
239 # Time bebore the enemy shoot again (cooldown)
241 # The default value is used as a grace period to avoid a first shoot on
245 init(scene
: PlayScene)
250 scene
.enemies
.add
(self)
258 if self.y
> 700 * 100 or self.x
< -100 * 100 or self.x
> 900 * 100 then
259 # Note: no control on the top to let ennemies appear
264 if shoot_ttl
> 0 then
271 # Each enemy has its own kind of shoot strategy
272 # Note: is automatically called by update when shoot_ttl is expired
275 # Money given when the enemy is destroyed
276 fun loot
: Int is abstract
278 # What to do when the enemy is hit by a player shoot (or by the player himself)?
279 # By default it kill the enemy in an explosion and generate a loot
283 scene
.explosion
(self.x
, self.y
, 5)
285 var upmissile
= new UpMissile
290 scene
.loots
.add
(upmissile
)
291 scene
.hitables
.add
(new LootArea(upmissile
, 2000))
293 for i
in [0..self.loot
[ do
294 var money
= new Money
297 money
.set_velocity
(100.rand
.to_f
*pi
/50.0, (500+self.loot
).rand
)
298 scene
.loots
.add
(money
)
299 scene
.hitables
.add
(new LootArea(money
, 2000))
304 redef fun hit_by_player
(player
)
311 # Basic enemy, do not shoot
315 redef fun loot
do return 3
318 # Simple shooter of paris of basic bullets
327 # two bullets shoot each time
328 for dx
in [-11, 11] do
329 var shoot
= new Shoot
331 shoot
.x
= self.x
+ dx
* 100
332 shoot
.y
= self.bottom
334 scene
.enemy_shoots
.add
(shoot
)
338 redef fun loot
do return 5
341 # Enemy that shoot missiles
350 # The missile targets the player
351 var shoot
= new Missile
354 shoot
.y
= self.bottom
356 shoot
.target
= scene
.player
357 scene
.enemy_shoots
.add
(shoot
)
360 redef fun loot
do return 10
363 # Enem that shoot rings of basic bullets
373 var shoot
= new Shoot
376 shoot
.y
= self.bottom
377 shoot
.set_velocity
(pi
/5.0*i
.to_f
, 500)
378 scene
.enemy_shoots
.add
(shoot
)
382 redef fun loot
do return 20
385 # Enemy with a turret that shoot burst of bullets toward the player
389 # The angle of the turret
390 var angle
: Float = 0.0
396 # Rotate the turret toward the player
397 var target
= scene
.player
398 if target
.exists
then
399 angle
= self.angle_to
(target
)
403 # Shoots come in burst
404 var nbshoots
: Int = 0
408 # Next shoot: is there still bullets in the burst?
409 if self.nbshoots
< 10 then
418 # Shoot with the turret angle
419 var shoot
= new Shoot
423 shoot
.set_velocity
(angle
, 500)
424 scene
.enemy_shoots
.add
(shoot
)
427 redef fun loot
do return 20
430 # Enemy that rush directly on the player
438 # Try to target the player
439 var target
= scene
.player
440 if not target
.exists
then return
442 var angle
= self.angle_to
(target
)
443 self.set_velocity
(angle
, 600)
446 redef fun loot
do return 5
449 # The boss has two semi-independent arms
454 var left_part
: BossPart
457 var right_part
: BossPart
462 self.width
= 128 * 100
463 self.height
= 100 * 100
466 self.left_part
= new BossPart(self, -48*100)
467 self.right_part
= new BossPart(self, 48*100)
470 var flick_ttl
: Int = 0
474 if flick_ttl
> 0 then flick_ttl
-= 1
476 # Path of the boss (down then left<->right)
477 if self.y
< 20000 then
480 else if self.vx
== 0 then
483 else if self.x
> 700 * 100 and self.vx
> 0 then
485 else if self.x
< 100 * 100 and self.vx
< 0 then
494 # Do not shoot if not ready
495 if self.vy
!= 0 then return
497 # Try to target the player
498 var target
= scene
.player
499 if not target
.exists
then return
501 # Next shoot: burst if no arms remains
502 if left_part
.exists
or right_part
.exists
then
508 # Shoot the player with a basic bullet
509 var shoot
= new Shoot
512 shoot
.y
= self.bottom
513 var angle
= shoot
.angle_to
(target
)
514 shoot
.set_velocity
(angle
, 500)
515 scene
.enemy_shoots
.add
(shoot
)
518 redef fun loot
do return 100
524 # Protected while an arm remains
525 if left_part
.exists
or right_part
.exists
then return
532 scene
.explosion
(self.x
, self.y
, 30)
541 # The associated boss
544 # Relative x coordonate (center to center) of the arm
547 # Relative y coordonate (center to center) of the arm
548 var rely
: Int = 36 * 100
552 init(boss
: Boss, relx
: Int)
557 self.width
= 32 * 100
558 self.height
= 60 * 100
560 # Alternate the shoots of the arms
564 self.x
= boss
.x
+ relx
565 self.y
= boss
.y
+ rely
570 self.x
= boss
.x
+ relx
571 self.y
= boss
.y
+ rely
575 if flick_ttl
> 0 then flick_ttl
-= 1
580 # Do not shoot if not ready
581 if self.boss
.vy
!= 0 then return
586 # Shoot a missile that targets the player
587 var shoot
= new Missile
590 shoot
.y
= self.bottom
592 shoot
.target
= scene
.player
593 scene
.enemy_shoots
.add
(shoot
)
596 var flick_ttl
: Int = 0
608 redef fun loot
do return 10
611 # Whatever reward or bonus that can be picked by the player
621 # Magnet effect: The loot will move to the target if set
622 # See LootArea for details
623 var target
: nullable Sprite = null
630 if self.y
> 700 * 100 then
634 var target
= self.target
635 if target
== null then
636 # Not magneted: deploy
638 # Heavy fuild friction to stops the explosion
639 # Loots are placed with a explosion, see `Enemy::hit'
640 self.vx
= self.vx
*7/8
641 self.vy
= self.vy
*7/8
643 # Background scroling
646 else if target
.exists
then
647 # Magneted: rush toward the target
648 var angle
= self.angle_to
(target
)
649 self.set_velocity
(angle
, 800)
652 # Magneted but dead target: reset the loot
664 redef fun hit_by_player
(player
)
668 if player
.money
> 100 then
675 # Increase the number of missiles
679 redef fun hit_by_player
(player
)
682 player
.nbmissiles
+= 1
686 # A loot area is an invisible field used to implement the magnet effets of loots
688 # * the loot is an invisible sprite with a hitbox larger than the loot hitbox
689 # * the lootbox remains centered on the loot
690 # * when the player hit the lootarea, then the loot is set to target the player
691 # * when the player hit the loot, then the player gains effectively the loot
695 # The associated loot
698 init(loot
: Loot, radius
: Int)
701 self.width
= radius
* 2 + loot
.width
702 self.height
= radius
* 2 + loot
.height
707 # position remains centered on the loot
712 if not loot
.exists
then self.exists
= false
714 # the super is useless but it is a good practice to call it
718 redef fun hit_by_player
(player
)
723 # The loot now targets the player
728 # A non interactive element of an explosion
729 # A real explosion is made of many Explosion object
730 # Use the `PlayScene::explosion` method to generate a full explosion
734 # Time before the sprite vanishes
739 # Heavy fuild friction to stops the explosion
740 self.vx
= self.vx
*7/8
741 self.vy
= self.vy
*7/8
743 # Background scrolling
757 # A star is a non-interactive background element
758 # Stars are used to simulate a continuous global scroling
764 # Randomely places stars on the plane
765 self.x
= 800.rand
* 100
766 self.y
= 600.rand
* 100
767 self.vy
= 40.rand
+ 11
774 # Replace the star on the top
775 if self.y
> 600 * 100 then
776 self.y
= 200.rand
* -100
777 self.x
= 800.rand
* 100
778 self.vy
= 40.rand
+ 11
784 # When a scene need to be replaced, just assign the next_scene to a non null value
785 var next_scene
: nullable Scene writable = null
788 # The main play state
795 # Shoots of the player
796 var player_shoots
= new LiveGroup[Shoot]
799 var enemies
= new LiveGroup[Enemy]
802 var enemy_shoots
= new LiveGroup[Shoot]
805 var loots
= new LiveGroup[Loot]
807 # Non active stuff like explosions
808 var pasive_stuff
= new LiveGroup[LiveObject]
810 # Background stuff like stars
811 var background
= new LiveGroup[LiveObject]
813 # All other hitable sprites
814 var hitables
= new LiveGroup[Hitable]
817 var sprites
= new LiveGroup[LiveObject]
821 self.player
= new Player(self)
824 self.sprites
.add
(background
)
825 self.sprites
.add
(pasive_stuff
)
826 self.sprites
.add
(loots
)
827 self.sprites
.add
(player_shoots
)
828 self.sprites
.add
(enemy_shoots
)
829 self.sprites
.add
(enemies
)
830 self.sprites
.add
(self.player
)
831 self.sprites
.add
(hitables
)
834 background
.add
(new Star)
837 hitables
.add
(player
.going_target
)
840 # Generate an explosion
841 fun explosion
(x
, y
: Int, radius
: Int)
843 # Project explosion parts from the given position
844 # The strong friction and the short ttl of each part will achieve the effect
845 for i
in [0..radius
[ do
846 var ex
= new Explosion
849 ex
.set_velocity
(100.rand
.to_f
*pi
/50.0, (50*radius
).rand
)
850 ex
.ttl
+= radius
.rand
855 var enemy_remains
: Int = 15
856 var boss_wait_ttl
: Int = 0
857 var boss
: nullable Boss
864 if enemy_remains
== 0 then
865 if boss_wait_ttl
> 0 then
867 else if boss
== null then
868 boss
= new Boss(self)
870 else if not boss
.exists
then
873 else if 100.rand
< 1 then
875 if enemy_remains
== 0 then
881 enemy
= new Enemy0(self)
882 else if rnd
< 60 then
883 enemy
= new Enemy1(self)
884 else if rnd
< 70 then
885 enemy
= new EnemyKamikaze(self)
886 else if rnd
< 90 then
887 enemy
= new Enemy2(self)
888 else if rnd
< 95 then
889 enemy
= new Enemy3(self)
891 enemy
= new Enemy4(self)
893 enemy
.x
= 600.rand
* 100 + 10000
894 enemy
.vy
= 200.rand
+ 100
896 enemy
.vx
= 200.rand
- 100
900 for ps
in player_shoots
do
901 if not ps
.exists
then continue
902 var target
: nullable Enemy = null
903 var td
= 100000 # big int
905 if not e
.exists
then continue
906 if ps
.overlaps
(e
) then
910 var d
= (e
.x
- ps
.x
).abs
+ (e
.y
- ps
.y
).abs
916 if ps
isa Missile and (ps
.target
== null or not ps
.target
.exists
) then
922 if not e
.exists
then continue
923 if player
.exists
and player
.overlaps
(e
) then
924 e
.hit_by_player
(player
)
927 for s
in enemy_shoots
do
928 if not s
.exists
then continue
929 if player
.exists
and player
.overlaps
(s
) then
930 s
.hit_by_player
(player
)
934 if not l
.exists
then continue
935 if player
.exists
and player
.overlaps
(l
) then
936 l
.hit_by_player
(player
)
940 if not l
.exists
then continue
941 if player
.exists
and player
.overlaps
(l
) then
942 l
.hit_by_player
(player
)
945 if not player
.exists
then
946 if player
.respawn_ttl
> 0 then
947 player
.respawn_ttl
-= 1
950 player
.protected_ttl
= 100
951 self.sprites
.add
(self.player
)
962 var sprites
= new LiveGroup[LiveObject]
967 sprites
.add
(new Star)
971 var play
: Bool writable = false
978 if not play
then return
983 next_scene
= new PlayScene
990 # Only run the playscene
991 var scene
= new PlayScene
993 scene
.player
.nbshoots
= 5
994 scene
.player
.nbmissiles
= 5
998 if args
.length
> 0 then
999 turns
= args
.first
.to_i
1001 for i
in [0..turns
[ do
1002 for j
in [0..10000[ do
1005 print
"{i}: money={scene.player.money} enemies={scene.enemies.length} shoots={scene.player_shoots.length}"