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.
15 module action_nitro
is
16 app_name
"Action Nitro"
17 app_namespace
"net.xymus.action_nitro"
18 app_version
(1, 0, git_revision
)
22 import gamnit
::landscape
32 var world
: World = new World is lazy
37 # Textures of the biplane, jet, helicopter, parachute and powerups
38 var planes_sheet
= new PlanesImages
40 # Animation when opening the parachute
41 var parachute_animation
= new Animation(planes_sheet
.parachute
, 16.0)
43 # Animation for the player movement
44 private var running_texture
= new Texture("textures/player.png")
45 private var running_animation
: Animation = running_texture
.to_animation
(10.0, 12, 0)
48 private var iss_model
= new Model("models/iss.obj")
53 private var ground_texture
= new Texture("textures/fastgras01.jpg")
54 private var tree_texture
= new Texture("textures/Tree03.png")
59 private var splatter_texture
= new Texture("textures/blood_splatter.png")
60 private var splatter_material
: TexturedMaterial do
61 var mat
= new TexturedMaterial([1.0]*4, [0.0]*4, [0.0]*4)
62 mat
.ambient_texture
= splatter_texture
65 private var splatter_model
= new LeafModel(new Plane, splatter_material
)
70 private var city_texture
= new TextureAsset("textures/city_background_clean.png")
72 private var stars_texture
= new Texture("textures/stars.jpg")
73 private var stars
= new Sprite(stars_texture
, new Point3d[Float](0.0, 1100.0, -600.0)) is lazy
79 var explosions
= new ParticleSystem(100, explosion_program
,
80 new Texture("particles/explosion00.png"))
82 # Blood explosion particles
83 var blood
= new ParticleSystem(100, explosion_program
,
84 new Texture("particles/blood07.png"))
86 # Smoke for the background
87 var smoke
= new ParticleSystem(500, smoke_program
,
88 new Texture("particles/blackSmoke12.png"))
90 # Static clouds particles
91 var clouds
= new ParticleSystem(1600, static_program
,
92 new Texture("particles/whitePuff12.png"))
98 #private var fx_fire = new Sound("sounds/fire.mp3")
102 private var texts_sheet
= new TextsImages
104 private var tutorial_wasd
= new Sprite(app
.texts_sheet
.tutorial_wasd
,
105 app
.ui_camera
.center
.offset
(0.0, -250.0, 0.0)) is lazy
107 private var tutorial_arrows
= new Sprite(app
.texts_sheet
.tutorial_arrows
,
108 app
.ui_camera
.center
.offset
(0.0, -350.0, 0.0)) is lazy
110 private var tutorial_chute
= new Sprite(app
.texts_sheet
.tutorial_chute
,
111 app
.ui_camera
.center
.offset
(0.0, -450.0, 0.0)) is lazy
113 private var tutorial_goal
= new Sprite(app
.texts_sheet
.goal
,
114 app
.ui_camera
.center
.offset
(0.0, 0.0, 0.0)) is lazy
116 private var outro_directed
= new Sprite(app
.texts_sheet
.directed
,
117 app
.ui_camera
.center
.offset
(0.0, 400.0, 0.0)) is lazy
119 private var outro_created
= new Sprite(app
.texts_sheet
.created
,
120 app
.ui_camera
.center
.offset
(0.0, -200.0, 0.0)) is lazy
123 # Counters for the UI
125 private var score_counter
= new CounterSprites(texts_sheet
.n
,
126 new Point3d[Float](32.0, -64.0, 0.0))
128 private var altitude_counter
= new CounterSprites(texts_sheet
.n
,
129 new Point3d[Float](1400.0, -64.0, 0.0))
131 # Did the player asked to skip the intro animation?
132 private var skip_intro
= false
136 blood
.texture
.as(RootTexture).premultiply_alpha
= false
137 explosions
.texture
.as(RootTexture).premultiply_alpha
= false
141 show_splash_screen
new Texture("textures/splash.jpg")
145 if iss_model
.errors
.not_empty
then print_error iss_model
.errors
.join
("\n")
148 world_camera
.reset_height
60.0
149 ui_camera
.reset_height
1080.0
151 # Register particle systems
152 particle_systems
.add smoke
153 particle_systems
.add clouds
154 particle_systems
.add blood
155 particle_systems
.add explosions
162 city_texture
.pixelated
= true
163 var city_sprite
= new Sprite(city_texture
, new Point3d[Float](0.0, 370.0, -600.0))
164 city_sprite
.scale
= 0.8
165 sprites
.add city_sprite
168 var ground_mesh
= new Plane
169 ground_mesh
.repeat_x
= 100.0
170 ground_mesh
.repeat_y
= 100.0
172 var ground_material
= new TexturedMaterial(
173 [0.0, 0.1, 0.0, 1.0], [0.4, 0.4, 0.4, 1.0], [0.0]*4)
174 ground_material
.diffuse_texture
= ground_texture
176 var ground_model
= new LeafModel(ground_mesh
, ground_material
)
177 var ground
= new Actor(ground_model
, new Point3d[Float](0.0, 0.0, 0.0))
178 ground
.scale
= 5000.0
182 for i
in 2000.times
do
183 var s
= 0.1 + 0.1.rand
184 var h
= tree_texture
.height
* s
185 var sprite
= new Sprite(tree_texture
,
186 new Point3d[Float](0.0 & 1500.0, h
/2.0 - 10.0*s
, 10.0 - 609.0.rand
))
193 sprite
.tint
= [c
, 1.0, c
, 1.0]
197 var no_clouds_layer
= 200.0
198 for i
in [0 .. 32[ do
200 var x
= 0.0 & 1000.0 * zp
201 var y
= no_clouds_layer
+ (world
.boss_altitude
- no_clouds_layer
*2.0).rand
202 var z
= -500.0*zp
- 10.0
208 clouds
.add
(new Point3d[Float](x
+2.0*a
.cos
*rj
, y
+a
.sin
*rj
, z
& 1.0),
209 48000.0 & 16000.0, inf
)
213 # Move the sun to best light the ISS
214 light
.position
.x
= 2000.0
215 light
.position
.z
= 4000.0
217 # Prepare for intro animation
218 ui_sprites
.add tutorial_goal
219 world_camera
.far
= 1024.0
227 # Update background color
228 var player
= world
.player
229 var player_pos
= if player
!= null then player
.center
else new Point3d[Float](0.0, 200.0, 0.0)
230 var altitude
= player_pos
.y
231 var p
= altitude
/ world
.boss_altitude
233 glClearColor
(0.3*ip
, 0.3*ip
, ip
, 1.0)
234 stars
.alpha
= (1.4*p-0
.4
).clamp
(0.0, 1.0)
238 new Point3d[Float](291.0, 338.0, -601.0),
239 new Point3d[Float](-356.0, 422.0, -601.0)]
245 new Point3d[Float](pos
.x
& r
, pos
.y
& r
, pos
.z
& r
),
246 96000.0 & 16000.0, 10.0)
250 world_camera
.position
.x
= player_pos
.x
251 world_camera
.position
.y
= player_pos
.y
+ 5.0
255 var intro_duration
= 8.0
256 if t
< intro_duration
and not skip_intro
then
257 var pitch
= t
/intro_duration
258 pitch
= (pitch
*pi
).sin
259 world_camera
.pitch
= pitch
263 if world
.player
== null then
264 world_camera
.pitch
= 0.0
265 world_camera
.far
= 700.0
271 score_counter
.value
= world
.score
273 if world
.player
!= null then alt
= world
.player
.altitude
.to_i
274 altitude_counter
.value
= alt
276 # General movement on the X axis
277 if player
!= null then
279 if pressed_keys
.has
("left") then player
.moving
-= 1.0
280 if pressed_keys
.has
("right") then player
.moving
+= 1.0
283 # Try to fire as long as a key is pressed
284 if pressed_keys
.not_empty
then
286 if pressed_keys
.has
("a") then
287 if pressed_keys
.has
("w") then
289 else if pressed_keys
.has
("s") then
294 else if pressed_keys
.has
("d") then
295 if pressed_keys
.has
("w") then
297 else if pressed_keys
.has
("s") then
302 else if pressed_keys
.has
("w") then
304 else if pressed_keys
.has
("s") then
308 if a
!= inf
and player
!= null then
309 player
.shoot
(a
, world
)
314 # Low-gravity controls
315 if player
!= null and player
.is_alive
and player
.altitude
>= world
.boss_altitude
then
317 for key
in pressed_keys
do
319 player
.inertia
.y
+= d
320 else if key
== "down" then
321 player
.inertia
.y
-= d
322 else if key
== "left" then
323 player
.inertia
.x
-= d
324 else if key
== "right" then
325 player
.inertia
.x
+= d
332 if won_at
== null then
333 var boss
= world
.boss
334 if boss
!= null and not boss
.is_alive
then
335 self.won_at
= world
.t
339 var t_since_won
= world
.t
- won_at
340 if t_since_won
> 1.0 and not ui_sprites
.has
(outro_directed
) then ui_sprites
.add outro_directed
341 if t_since_won
> 2.0 and not ui_sprites
.has
(outro_created
) then ui_sprites
.add outro_created
345 # Begin playing, after intro if `initial`, otherwise after death
346 fun begin_play
(initial
: Bool)
351 world
.planes
.add
new Airplane(new Point3d[Float](0.0, world
.player
.center
.y
- 10.0, 0.0), 16.0, 4.0)
355 ui_sprites
.add_all
([tutorial_wasd
, tutorial_arrows
, tutorial_chute
])
359 # Seconds at which the game was won, using `world.t` as reference
360 private var won_at
: nullable Float = null
362 # Remove the tutorial sprite about WASD from `ui_sprites`
363 private fun hide_tutorial_wasd
do if ui_sprites
.has
(tutorial_wasd
) then ui_sprites
.remove
(tutorial_wasd
)
365 # Remove the tutorial sprite about arrows from `ui_sprites`
366 private fun hide_tutorial_arrows
do if ui_sprites
.has
(tutorial_arrows
) then ui_sprites
.remove
(tutorial_arrows
)
368 # Remove the tutorial sprite about the parachute from `ui_sprites`
369 private fun hide_tutorial_chute
do if ui_sprites
.has
(tutorial_chute
) then ui_sprites
.remove
(tutorial_chute
)
371 redef fun accept_event
(event
)
373 if super then return true
375 if event
isa QuitEvent then
378 else if event
isa KeyEvent then
379 if event
.name
== "escape" and event
.is_down
then
384 var player
= world
.player
385 if player
!= null and player
.is_alive
then
387 # Hide tutorial about arrows once they are used
388 var arrows
= once
["left", "right"]
389 if arrows
.has
(event
.name
) then hide_tutorial_arrows
391 if player
.altitude
< world
.boss_altitude
then
392 if event
.name
== "space" and event
.is_down
and not player
.parachute_deployed
and player
.plane
== null then
394 if player
.parachute_deployed
then
395 var pc
= player
.center
396 world
.parachute
= new Parachute(new Point3d[Float](pc
.x
, pc
.y
+ 5.0, pc
.z-0
.1
), 8.0, 5.0)
401 if (event
.name
== "space" or event
.name
== "up") and event
.is_down
then
405 if event
.name
== "left" then
406 var mod
= if event
.is_down
then -1.0 else 1.0
409 else if event
.name
== "right" then
410 var mod
= if event
.is_down
then 1.0 else -1.0
418 # When player is dead, respawn on spacebar or pointer depressed
419 if (event
isa KeyEvent and event
.name
== "space") or
420 (event
isa PointerEvent and not event
.is_move
and event
.depressed
) then
421 var player
= world
.player
422 if player
== null then
424 else if not player
.is_alive
then
434 # Sprite representing this entity if there is no `actor`
435 fun sprite
: Sprite is abstract
438 fun actor
: nullable Actor do return null
443 if actor
!= null then
445 else app
.sprites
.add sprite
448 redef fun destroy
(world
)
453 if actor
!= null then
454 app
.actors
.remove actor
455 else app
.sprites
.remove sprite
467 # Show death animation (explosion)
474 new Point3d[Float](center
.x
& force
, center
.y
& force
, center
.z
& force
),
475 (4096.0 & 2048.0) * force
, 0.3 & 0.1)
481 init do sprite
.scale
= width
/sprite
.texture
.width
483 redef fun update
(dt
, world
)
487 if inertia
.x
< 0.0 then
488 sprite
.invert_x
= false
489 else if inertia
.x
> 0.0 then
490 sprite
.invert_x
= true
496 private fun texture
: Texture do return if center
.y
< 600.0 then app
.planes_sheet
.biplane
else app
.planes_sheet
.jet
498 redef var sprite
= new Sprite(texture
, center
) is lazy
501 redef class Helicopter
502 redef var sprite
= new Sprite(app
.planes_sheet
.helicopter
, center
) is lazy
506 redef var actor
is lazy
do
507 var actor
= new Actor(app
.iss_model
, center
)
512 redef fun death_animation
515 app
.explosions
.add
(center
, 4096.0 * force
, 0.3)
516 for i
in (8.0*force
).to_i
.times
do
518 new Point3d[Float](center
.x
& force
, center
.y
& force
/8.0, center
.z
& force
),
519 (2048.0 & 1024.0) * force
, 0.3 + 5.0.rand
, 5.0.rand
)
525 redef var sprite
= new Sprite(app
.running_animation
.frames
.rand
, center
) is lazy
526 init do sprite
.scale
= width
/sprite
.texture
.width
* 2.0
529 redef class Parachute
530 redef var sprite
= new Sprite(app
.planes_sheet
.parachute_open
, center
) is lazy
533 sprite
.scale
= width
/ sprite
.texture
.width
534 sprite
.animate app
.parachute_animation
539 redef var sprite
= new Sprite(app
.running_animation
.frames
.last
, center
) is lazy
540 init do sprite
.scale
= width
/sprite
.texture
.width
* 2.0
542 # Update current animation
545 if moving
== 0.0 then
547 else sprite
.animate
(app
.running_animation
, -1.0)
550 redef fun update
(dt
, world
)
554 sprite
.invert_x
= false
555 else if moving
< 0.0 then
556 sprite
.invert_x
= true
564 if center
.y
< 10.0 then
565 # Blood splatter on the ground
566 var splatter
= new Actor(app
.splatter_model
,
567 new Point3d[Float](center
.x
, 0.05 & 0.04, center
.y
))
568 splatter
.scale
= 32.0
569 splatter
.yaw
= 2.0*pi
.rand
570 app
.actors
.add splatter
573 # Display respawn instructions
574 app
.ui_sprites
.add
new Sprite(app
.texts_sheet
.respawn
, app
.ui_camera
.center
.offset
(0.0, 0.0, 0.0))
579 redef var sprite
= new Sprite(weapon
.bullet_texture
, center
) is lazy
583 sprite
.rotation
= angle
588 fun bullet_texture
: Texture do return app
.planes_sheet
.bullet_ak
592 redef fun bullet_texture
do return app
.planes_sheet
.bullet_pistol
595 redef class RocketLauncher
596 redef fun bullet_texture
do return app
.planes_sheet
.bullet_rocket
600 # Scale so it looks like 5 world units wide, not matter the size of the texture
601 init do sprite
.scale
= 5.0/sprite
.texture
.width
605 redef var sprite
= new Sprite(app
.planes_sheet
.ak
, center
) is lazy
608 redef class RocketLauncherPU
609 redef var sprite
= new Sprite(app
.planes_sheet
.rocket
, center
) is lazy
613 redef var sprite
= new Sprite(app
.planes_sheet
.health
, center
) is lazy
614 init do sprite
.scale
= 3.0/sprite
.texture
.height
619 redef fun explode
(center
, force
)
624 var range
= 0.5 * force
625 app
.explosions
.add
(center
, 4096.0 * force
, 0.3)
626 for i
in (2.0*force
).to_i
.times
do
628 new Point3d[Float](center
.x
& range
, center
.y
& range
, center
.z
& range
),
629 (2048.0 & 1024.0) * force
, 0.3 & 0.3, 0.5.rand
)
634 # Manager to display numbers in sprite
637 # TODO clean up and move up to lib
639 # Number textures, from 0 to 9
641 # Require: `textures.length == 10`
642 var textures
: Array[Texture]
644 # Center of the first digit in UI coordinates
645 var anchor
: Point3d[Float]
647 # Last set of sprites generated to display the `value=`
648 private var sprites
= new Array[Sprite]
650 # Update the value displayed by the counter by inserting new sprites into `app.ui_sprites`
651 fun value
=(value
: Int)
653 # Clean up last used sprites
654 for s
in sprites
do if app
.ui_sprites
.has
(s
) then app
.ui_sprites
.remove s
658 var s
= value
.to_s
# TODO manipulate ints directly
662 var tex
= textures
[i
]
665 sprites
.add
new Sprite(tex
, new Point3d[Float](anchor
.x
+ x
, anchor
.y
, anchor
.z
))
669 # Register sprites to be drawn by `app.ui_camera`
670 app
.ui_sprites
.add_all sprites
674 redef class SmokeProgram
676 # Redef source to get particles that move up faster
677 redef fun vertex_shader_core
do return """
680 c.x += dt * dt * 2.0;
682 gl_Position = c * mvp;
683 gl_PointSize = scale / gl_Position.z * (pt+0.1);
688 v_color *= 1.0 - pt*0.9;