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.
19 import gamnit
::limit_fps
29 var world
: World = new World is lazy
34 # Textures of the biplane, jet, helicopter, parachute and powerups
35 private var planes_sheet
= new PlanesImages
37 # Animation for the player movement
38 private var player_textures
: Array[Texture] =
39 [for f
in [1..12] do new Texture("textures/player/frame_{f.pad(2)}.png")]
42 private var iss_model
= new Model("models/iss.obj")
47 private var ground_texture
= new Texture("textures/fastgras01.jpg")
48 private var tree_texture
= new Texture("textures/Tree03.png")
53 private var splatter_texture
= new Texture("textures/blood_splatter.png")
54 private var splatter_material
: TexturedMaterial do
55 var mat
= new TexturedMaterial([1.0]*4, [0.0]*4, [0.0]*4)
56 mat
.ambient_texture
= splatter_texture
59 private var splatter_model
= new LeafModel(new Plane, splatter_material
)
64 private var city_texture
= new Texture("textures/city_background_clean.png")
66 private var stars_texture
= new Texture("textures/stars.jpg")
67 private var stars
= new Sprite(stars_texture
, new Point3d[Float](0.0, 1100.0, -600.0)) is lazy
73 var explosions
= new ParticleSystem(20, explosion_program
,
74 new Texture("particles/explosion00.png"))
76 # Blood explosion particles
77 var blood
= new ParticleSystem(20, explosion_program
,
78 new Texture("particles/blood07.png"))
80 # Smoke for the background
81 var smoke
= new ParticleSystem(500, smoke_program
,
82 new Texture("particles/blackSmoke12.png"))
84 # Static clouds particles
85 var clouds
= new ParticleSystem(1600, static_program
,
86 new Texture("particles/whitePuff12.png"))
92 #private var fx_fire = new Sound("sounds/fire.mp3")
96 private var texts_sheet
= new TextsImages
98 private var tutorial_wasd
= new Sprite(app
.texts_sheet
.tutorial_wasd
,
99 app
.ui_camera
.center
.offset
(0.0, -250.0, 0.0)) is lazy
101 private var tutorial_arrows
= new Sprite(app
.texts_sheet
.tutorial_arrows
,
102 app
.ui_camera
.center
.offset
(0.0, -350.0, 0.0)) is lazy
104 private var tutorial_chute
= new Sprite(app
.texts_sheet
.tutorial_chute
,
105 app
.ui_camera
.center
.offset
(0.0, -450.0, 0.0)) is lazy
107 private var tutorial_goal
= new Sprite(app
.texts_sheet
.goal
,
108 app
.ui_camera
.center
.offset
(0.0, 0.0, 0.0)) is lazy
110 private var outro_directed
= new Sprite(app
.texts_sheet
.directed
,
111 app
.ui_camera
.center
.offset
(0.0, 400.0, 0.0)) is lazy
113 private var outro_created
= new Sprite(app
.texts_sheet
.created
,
114 app
.ui_camera
.center
.offset
(0.0, -200.0, 0.0)) is lazy
117 # Counters for the UI
119 private var score_counter
= new CounterSprites(texts_sheet
.n
,
120 new Point3d[Float](32.0, -64.0, 0.0))
122 private var altitude_counter
= new CounterSprites(texts_sheet
.n
,
123 new Point3d[Float](1400.0, -64.0, 0.0))
129 show_splash_screen
new Texture("textures/splash.jpg")
135 world_camera
.reset_height
40.0
136 ui_camera
.reset_height
1080.0
138 # Register particle systems
139 particle_systems
.add explosions
140 particle_systems
.add blood
141 particle_systems
.add smoke
142 particle_systems
.add clouds
149 var city_sprite
= new Sprite(city_texture
, new Point3d[Float](0.0, 370.0, -600.0))
150 city_sprite
.scale
= 0.8
151 sprites
.add city_sprite
154 var ground_mesh
= new Plane
155 ground_mesh
.repeat_x
= 100.0
156 ground_mesh
.repeat_y
= 100.0
158 var ground_material
= new TexturedMaterial(
159 [0.0, 0.1, 0.0, 1.0], [0.4, 0.4, 0.4, 1.0], [0.0]*4)
160 ground_material
.diffuse_texture
= ground_texture
162 var ground_model
= new LeafModel(ground_mesh
, ground_material
)
163 var ground
= new Actor(ground_model
, new Point3d[Float](0.0, 0.0, 0.0))
164 ground
.scale
= 5000.0
168 for i
in 1000.times
do
169 var s
= 0.1 + 0.1.rand
170 var h
= tree_texture
.height
* s
171 var sprite
= new Sprite(tree_texture
,
172 new Point3d[Float](0.0 & 1500.0, h
/2.0 - 10.0*s
, 10.0 - 609.0.rand
))
178 sprite
.tint
= [c
, 1.0, c
, 1.0]
182 var no_clouds_layer
= 200.0
183 for i
in [0 .. 32[ do
185 var x
= 0.0 & 1000.0 * zp
186 var y
= no_clouds_layer
+ (world
.boss_altitude
- no_clouds_layer
*2.0).rand
187 var z
= -500.0*zp
- 10.0
193 clouds
.add
(new Point3d[Float](x
+2.0*a
.cos
*rj
, y
+a
.sin
*rj
, z
& 1.0),
194 48000.0 & 16000.0, inf
)
198 # Move the sun to best light the ISS
199 light
.position
.x
= 2000.0
200 light
.position
.z
= 4000.0
202 # Prepare for intro animation
203 ui_sprites
.add tutorial_goal
204 world_camera
.far
= 700.0
212 # Update background color
213 var player
= world
.player
214 var player_pos
= if player
!= null then player
.center
else new Point3d[Float](0.0, 200.0, 0.0)
215 var altitude
= player_pos
.y
216 var p
= altitude
/ world
.boss_altitude
218 glClearColor
(0.3*ip
, 0.3*ip
, ip
, 1.0)
219 stars
.alpha
= (1.4*p-0
.4
).min
(1.0).max
(0.0)
223 new Point3d[Float](291.0, 338.0, -601.0),
224 new Point3d[Float](-356.0, 422.0, -601.0)]
230 new Point3d[Float](pos
.x
& r
, pos
.y
& r
, pos
.z
& r
),
231 96000.0 & 16000.0, 10.0)
235 world_camera
.position
.x
= player_pos
.x
236 world_camera
.position
.y
= player_pos
.y
+ 5.0
240 var intro_duration
= 8.0
241 if t
< intro_duration
then
242 var pitch
= t
/intro_duration
243 pitch
= (pitch
*pi
).sin
244 world_camera
.pitch
= pitch
248 if world
.player
== null then
251 world
.planes
.add
new Airplane(new Point3d[Float](0.0, world
.player
.center
.y
- 10.0, 0.0), 16.0, 4.0)
255 ui_sprites
.add_all
([tutorial_wasd
, tutorial_arrows
, tutorial_chute
])
257 world_camera
.pitch
= 0.0
258 world_camera
.far
= 700.0
262 score_counter
.value
= world
.score
264 if world
.player
!= null then alt
= world
.player
.altitude
.to_i
265 altitude_counter
.value
= alt
267 # General movement on the X axis
268 if player
!= null then
270 if pressed_keys
.has
("left") then player
.moving
-= 1.0
271 if pressed_keys
.has
("right") then player
.moving
+= 1.0
274 # Try to fire as long as a key is pressed
275 if pressed_keys
.not_empty
then
277 if pressed_keys
.has
("a") then
278 if pressed_keys
.has
("w") then
280 else if pressed_keys
.has
("s") then
285 else if pressed_keys
.has
("d") then
286 if pressed_keys
.has
("w") then
288 else if pressed_keys
.has
("s") then
293 else if pressed_keys
.has
("w") then
295 else if pressed_keys
.has
("s") then
299 if a
!= inf
and player
!= null then
300 player
.shoot
(a
, world
)
305 # Low-gravity controls
306 if player
!= null and player
.is_alive
and player
.altitude
>= world
.boss_altitude
then
308 for key
in pressed_keys
do
310 player
.inertia
.y
+= d
311 else if key
== "down" then
312 player
.inertia
.y
-= d
313 else if key
== "left" then
314 player
.inertia
.x
-= d
315 else if key
== "right" then
316 player
.inertia
.x
+= d
323 if won_at
== null then
324 var boss
= world
.boss
325 if boss
!= null and not boss
.is_alive
then
326 self.won_at
= world
.t
330 var t_since_won
= world
.t
- won_at
331 if t_since_won
> 1.0 and not ui_sprites
.has
(outro_directed
) then ui_sprites
.add outro_directed
332 if t_since_won
> 2.0 and not ui_sprites
.has
(outro_created
) then ui_sprites
.add outro_created
336 # Seconds at which the game was won, using `world.t` as reference
337 private var won_at
: nullable Float = null
339 # Remove the tutorial sprite about WASD from `ui_sprites`
340 private fun hide_tutorial_wasd
do if ui_sprites
.has
(tutorial_wasd
) then ui_sprites
.remove
(tutorial_wasd
)
342 # Remove the tutorial sprite about arrows from `ui_sprites`
343 private fun hide_tutorial_arrows
do if ui_sprites
.has
(tutorial_arrows
) then ui_sprites
.remove
(tutorial_arrows
)
345 # Remove the tutorial sprite about the parachute from `ui_sprites`
346 private fun hide_tutorial_chute
do if ui_sprites
.has
(tutorial_chute
) then ui_sprites
.remove
(tutorial_chute
)
348 redef fun accept_event
(event
)
352 if event
isa QuitEvent then
354 else if event
isa KeyEvent then
355 if event
.name
== "escape" and event
.is_down
then
359 var player
= world
.player
360 if player
!= null and player
.is_alive
then
362 # Hide tutorial about arrows once they are used
363 var arrows
= once
["left", "right"]
364 if arrows
.has
(event
.name
) then hide_tutorial_arrows
366 if player
.altitude
< world
.boss_altitude
then
367 if event
.name
== "space" and event
.is_down
and not player
.parachute_deployed
and player
.plane
== null then
369 if player
.parachute_deployed
then
370 var pc
= player
.center
371 world
.parachute
= new Parachute(new Point3d[Float](pc
.x
, pc
.y
+ 5.0, pc
.z-0
.1
), 8.0, 5.0)
376 if (event
.name
== "space" or event
.name
== "up") and event
.is_down
then
380 if event
.name
== "left" then
381 var mod
= if event
.is_down
then -1.0 else 1.0
385 if event
.name
== "right" then
386 var mod
= if event
.is_down
then 1.0 else -1.0
390 if player
.moving
== 0.0 then
391 player
.sprite
.as(PlayerSprite).stop_running
392 else player
.sprite
.as(PlayerSprite).start_running
396 # When player is dead, respawn on spacebar
397 if player
!= null and not player
.is_alive
then
398 if event
.name
== "space" then
410 # Sprite representing this entity if there is no `actor`
411 fun sprite
: Sprite is abstract
414 fun actor
: nullable Actor do return null
419 if actor
!= null then
421 else app
.sprites
.add sprite
424 redef fun destroy
(world
)
429 if actor
!= null then
430 app
.actors
.remove actor
431 else app
.sprites
.remove sprite
443 # Show death animation (explosion)
450 new Point3d[Float](center
.x
& force
, center
.y
& force
, center
.z
& force
),
451 (2048.0 & 4096.0) * force
, 0.3 & 0.1)
457 init do sprite
.scale
= width
/sprite
.texture
.width
459 redef fun update
(dt
, world
)
463 if inertia
.x
< 0.0 then
464 sprite
.invert_x
= false
465 else if inertia
.x
> 0.0 then
466 sprite
.invert_x
= true
472 private fun texture
: Texture do return if center
.y
< 600.0 then app
.planes_sheet
.biplane
else app
.planes_sheet
.jet
474 redef var sprite
= new Sprite(texture
, center
) is lazy
477 redef class Helicopter
478 redef var sprite
= new Sprite(app
.planes_sheet
.helicopter
, center
) is lazy
482 redef var actor
is lazy
do
483 var actor
= new Actor(app
.iss_model
, center
)
484 actor
.rotation
= pi
/2.0
488 redef fun death_animation
491 app
.explosions
.add
(center
, 4096.0 * force
, 0.3)
492 for i
in (8.0*force
).to_i
.times
do
494 new Point3d[Float](center
.x
& force
, center
.y
& force
/8.0, center
.z
& force
),
495 (2048.0 & 1024.0) * force
, 0.3 + 5.0.rand
, 5.0.rand
)
501 redef var sprite
= new Sprite(app
.player_textures
.rand
, center
) is lazy
502 init do sprite
.scale
= width
/sprite
.texture
.width
* 2.0
505 redef class Parachute
506 redef var sprite
= new Sprite(app
.planes_sheet
.parachute
, center
) is lazy
507 init do sprite
.scale
= width
/ sprite
.texture
.width
511 redef var sprite
= new PlayerSprite(app
.player_textures
[1], center
, app
.player_textures
, 0.08) is lazy
512 init do sprite
.scale
= width
/sprite
.texture
.width
* 2.0
514 redef fun update
(dt
, world
)
518 sprite
.invert_x
= false
519 else if moving
< 0.0 then
520 sprite
.invert_x
= true
528 if center
.y
< 10.0 then
529 # Blood splatter on the ground
530 var splatter
= new Actor(app
.splatter_model
,
531 new Point3d[Float](center
.x
, 0.05 & 0.04, center
.y
))
532 splatter
.scale
= 32.0
533 splatter
.rotation
= 2.0 * pi
.rand
534 app
.actors
.add splatter
537 # Display respawn instructions
538 app
.ui_sprites
.add
new Sprite(app
.texts_sheet
.respawn
, app
.ui_camera
.center
)
543 redef var sprite
= new Sprite(weapon
.bullet_texture
, center
) is lazy
547 sprite
.rotation
= angle
552 fun bullet_texture
: Texture do return app
.planes_sheet
.bullet_ak
556 redef fun bullet_texture
do return app
.planes_sheet
.bullet_pistol
559 redef class RocketLauncher
560 redef fun bullet_texture
do return app
.planes_sheet
.bullet_rocket
564 # Scale so it looks like 5 world units wide, not matter the size of the texture
565 init do sprite
.scale
= 5.0/sprite
.texture
.width
569 redef var sprite
= new Sprite(app
.planes_sheet
.ak
, center
) is lazy
572 redef class RocketLauncherPU
573 redef var sprite
= new Sprite(app
.planes_sheet
.rocket
, center
) is lazy
577 redef var sprite
= new Sprite(app
.planes_sheet
.health
, center
) is lazy
578 init do sprite
.scale
= 3.0/sprite
.texture
.height
583 redef fun explode
(center
, force
)
588 app
.explosions
.add
(center
, 8192.0 * force
, 0.3)
589 for i
in (4.0*force
).to_i
.times
do
591 new Point3d[Float](center
.x
& force
, center
.y
& force
/2.0, center
.z
& force
),
592 (4096.0 & 2048.0) * force
, 0.3 & 0.3, 0.5.rand
)
598 # Pad a number with `0`s on the left side to reach `size` digits
599 private fun pad
(size
: Int): String
602 var d
= size
- s
.length
603 if d
> 0 then s
= "0"*d
+ s
608 # Special `Sprite` for the player character which is animated
612 # Animation of the running character
613 var running_animation
: Array[Texture]
615 # Seconds per frame of the animations
616 var time_per_frame
: Float
618 # Currently playing animation
619 private var current_animation
: nullable Array[Texture] = null
621 # Second at witch `current_animation` started
622 private var anim_ot
= 0.0
624 # Start the running animation
627 anim_ot
= app
.world
.t
628 current_animation
= running_animation
631 # Stop the running animation
632 fun stop_running
do current_animation
= null
636 var anim
= current_animation
638 var dt
= app
.world
.t
- anim_ot
639 var i
= (dt
/ time_per_frame
).to_i
+2
640 return anim
.modulo
(i
)
647 # Manager to display numbers in sprite
650 # TODO clean up and move up to lib
652 # Number textures, from 0 to 9
654 # Require: `textures.length == 10`
655 var textures
: Array[Texture]
657 # Center of the first digit in UI coordinates
658 var anchor
: Point3d[Float]
660 # Last set of sprites generated to display the `value=`
661 private var sprites
= new Array[Sprite]
663 # Update the value displayed by the counter by inserting new sprites into `app.ui_sprites`
664 fun value
=(value
: Int)
666 # Clean up last used sprites
667 for s
in sprites
do if app
.ui_sprites
.has
(s
) then app
.ui_sprites
.remove s
671 var s
= value
.to_s
# TODO manipulate ints directly
675 var tex
= textures
[i
]
678 sprites
.add
new Sprite(tex
, new Point3d[Float](anchor
.x
+ x
, anchor
.y
, anchor
.z
))
682 # Register sprites to be drawn by `app.ui_camera`
683 app
.ui_sprites
.add_all sprites
687 redef class SmokeProgram
689 # Redef source to get particles that move up faster
690 redef fun vertex_shader_core
do return """
693 c.x += dt * dt * 2.0;
695 gl_Position = c * mvp;
696 gl_PointSize = scale / gl_Position.z * (pt+0.1);
699 v_color.a = pt / 0.1;
701 v_color.a = 1.0 - pt*0.9;