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
)
20 android_manifest_activity
"""android:screenOrientation="sensorLandscape""""
26 import gamnit::limit_fps
36 var world: World = new World is lazy
41 # Textures of the biplane, jet, helicopter, parachute and powerups
42 var planes_sheet = new PlanesImages
44 # Animation for the player movement
45 private var player_textures: Array[Texture] =
46 [for f in [1..12] do new Texture("textures
/player
/frame_
{f.pad(2)}.png
")]
49 private var iss_model = new Model("models
/iss
.obj
")
54 private var ground_texture = new Texture("textures
/fastgras01
.jpg
")
55 private var tree_texture = new Texture("textures
/Tree03.png
")
60 private var splatter_texture = new Texture("textures
/blood_splatter
.png
")
61 private var splatter_material: TexturedMaterial do
62 var mat = new TexturedMaterial([1.0]*4, [0.0]*4, [0.0]*4)
63 mat.ambient_texture = splatter_texture
66 private var splatter_model = new LeafModel(new Plane, splatter_material)
71 private var city_texture = new TextureAsset("textures
/city_background_clean
.png
")
73 private var stars_texture = new Texture("textures
/stars
.jpg
")
74 private var stars = new Sprite(stars_texture, new Point3d[Float](0.0, 1100.0, -600.0)) is lazy
80 var explosions = new ParticleSystem(20, explosion_program,
81 new Texture("particles
/explosion00
.png
"))
83 # Blood explosion particles
84 var blood = new ParticleSystem(20, explosion_program,
85 new Texture("particles
/blood07
.png
"))
87 # Smoke for the background
88 var smoke = new ParticleSystem(500, smoke_program,
89 new Texture("particles
/blackSmoke12
.png
"))
91 # Static clouds particles
92 var clouds = new ParticleSystem(1600, static_program,
93 new Texture("particles
/whitePuff12
.png
"))
99 #private var fx_fire = new Sound("sounds
/fire
.mp3
")
103 private var texts_sheet = new TextsImages
105 private var tutorial_wasd = new Sprite(app.texts_sheet.tutorial_wasd,
106 app.ui_camera.center.offset(0.0, -250.0, 0.0)) is lazy
108 private var tutorial_arrows = new Sprite(app.texts_sheet.tutorial_arrows,
109 app.ui_camera.center.offset(0.0, -350.0, 0.0)) is lazy
111 private var tutorial_chute = new Sprite(app.texts_sheet.tutorial_chute,
112 app.ui_camera.center.offset(0.0, -450.0, 0.0)) is lazy
114 private var tutorial_goal = new Sprite(app.texts_sheet.goal,
115 app.ui_camera.center.offset(0.0, 0.0, 0.0)) is lazy
117 private var outro_directed = new Sprite(app.texts_sheet.directed,
118 app.ui_camera.center.offset(0.0, 400.0, 0.0)) is lazy
120 private var outro_created = new Sprite(app.texts_sheet.created,
121 app.ui_camera.center.offset(0.0, -200.0, 0.0)) is lazy
124 # Counters for the UI
126 private var score_counter = new CounterSprites(texts_sheet.n,
127 new Point3d[Float](32.0, -64.0, 0.0))
129 private var altitude_counter = new CounterSprites(texts_sheet.n,
130 new Point3d[Float](1400.0, -64.0, 0.0))
132 # Did the player asked to skip the intro animation?
133 private var skip_intro = false
139 show_splash_screen new Texture("textures
/splash
.jpg
")
145 world_camera.reset_height 60.0
146 ui_camera.reset_height 1080.0
148 # Register particle systems
149 particle_systems.add explosions
150 particle_systems.add blood
151 particle_systems.add smoke
152 particle_systems.add clouds
159 city_texture.pixelated = true
160 var city_sprite = new Sprite(city_texture, new Point3d[Float](0.0, 370.0, -600.0))
161 city_sprite.scale = 0.8
162 sprites.add city_sprite
165 var ground_mesh = new Plane
166 ground_mesh.repeat_x = 100.0
167 ground_mesh.repeat_y = 100.0
169 var ground_material = new TexturedMaterial(
170 [0.0, 0.1, 0.0, 1.0], [0.4, 0.4, 0.4, 1.0], [0.0]*4)
171 ground_material.diffuse_texture = ground_texture
173 var ground_model = new LeafModel(ground_mesh, ground_material)
174 var ground = new Actor(ground_model, new Point3d[Float](0.0, 0.0, 0.0))
175 ground.scale = 5000.0
179 for i in 2000.times do
180 var s = 0.1 + 0.1.rand
181 var h = tree_texture.height * s
182 var sprite = new Sprite(tree_texture,
183 new Point3d[Float](0.0 & 1500.0, h/2.0 - 10.0*s, 10.0 - 609.0.rand))
190 sprite.tint = [c, 1.0, c, 1.0]
194 var no_clouds_layer = 200.0
195 for i in [0 .. 32[ do
197 var x = 0.0 & 1000.0 * zp
198 var y = no_clouds_layer + (world.boss_altitude - no_clouds_layer*2.0).rand
199 var z = -500.0*zp - 10.0
205 clouds.add(new Point3d[Float](x+2.0*a.cos*rj, y+a.sin*rj, z & 1.0),
206 48000.0 & 16000.0, inf)
210 # Move the sun to best light the ISS
211 light.position.x = 2000.0
212 light.position.z = 4000.0
214 # Prepare for intro animation
215 ui_sprites.add tutorial_goal
216 world_camera.far = 1024.0
224 # Update background color
225 var player = world.player
226 var player_pos = if player != null then player.center else new Point3d[Float](0.0, 200.0, 0.0)
227 var altitude = player_pos.y
228 var p = altitude / world.boss_altitude
230 glClearColor(0.3*ip, 0.3*ip, ip, 1.0)
231 stars.alpha = (1.4*p-0.4).clamp(0.0, 1.0)
235 new Point3d[Float](291.0, 338.0, -601.0),
236 new Point3d[Float](-356.0, 422.0, -601.0)]
242 new Point3d[Float](pos.x & r, pos.y & r, pos.z & r),
243 96000.0 & 16000.0, 10.0)
247 world_camera.position.x = player_pos.x
248 world_camera.position.y = player_pos.y + 5.0
252 var intro_duration = 8.0
253 if t < intro_duration and not skip_intro then
254 var pitch = t/intro_duration
255 pitch = (pitch*pi).sin
256 world_camera.pitch = pitch
260 if world.player == null then
261 world_camera.pitch = 0.0
262 world_camera.far = 700.0
268 score_counter.value = world.score
270 if world.player != null then alt = world.player.altitude.to_i
271 altitude_counter.value = alt
273 # General movement on the X axis
274 if player != null then
276 if pressed_keys.has("left
") then player.moving -= 1.0
277 if pressed_keys.has("right
") then player.moving += 1.0
278 player.sprite.as(PlayerSprite).update
281 # Try to fire as long as a key is pressed
282 if pressed_keys.not_empty then
284 if pressed_keys.has("a
") then
285 if pressed_keys.has("w
") then
287 else if pressed_keys.has("s
") then
292 else if pressed_keys.has("d
") then
293 if pressed_keys.has("w
") then
295 else if pressed_keys.has("s
") then
300 else if pressed_keys.has("w
") then
302 else if pressed_keys.has("s
") then
306 if a != inf and player != null then
307 player.shoot(a, world)
312 # Low-gravity controls
313 if player != null and player.is_alive and player.altitude >= world.boss_altitude then
315 for key in pressed_keys do
317 player.inertia.y += d
318 else if key == "down
" then
319 player.inertia.y -= d
320 else if key == "left
" then
321 player.inertia.x -= d
322 else if key == "right
" then
323 player.inertia.x += d
330 if won_at == null then
331 var boss = world.boss
332 if boss != null and not boss.is_alive then
333 self.won_at = world.t
337 var t_since_won = world.t - won_at
338 if t_since_won > 1.0 and not ui_sprites.has(outro_directed) then ui_sprites.add outro_directed
339 if t_since_won > 2.0 and not ui_sprites.has(outro_created) then ui_sprites.add outro_created
343 # Begin playing, after intro if `initial`, otherwise after death
344 fun begin_play(initial: Bool)
349 world.planes.add new Airplane(new Point3d[Float](0.0, world.player.center.y - 10.0, 0.0), 16.0, 4.0)
353 ui_sprites.add_all([tutorial_wasd, tutorial_arrows, tutorial_chute])
357 # Seconds at which the game was won, using `world.t` as reference
358 private var won_at: nullable Float = null
360 # Remove the tutorial sprite about WASD from `ui_sprites`
361 private fun hide_tutorial_wasd do if ui_sprites.has(tutorial_wasd) then ui_sprites.remove(tutorial_wasd)
363 # Remove the tutorial sprite about arrows from `ui_sprites`
364 private fun hide_tutorial_arrows do if ui_sprites.has(tutorial_arrows) then ui_sprites.remove(tutorial_arrows)
366 # Remove the tutorial sprite about the parachute from `ui_sprites`
367 private fun hide_tutorial_chute do if ui_sprites.has(tutorial_chute) then ui_sprites.remove(tutorial_chute)
369 redef fun accept_event(event)
371 if super then return true
373 if event isa QuitEvent then
376 else if event isa KeyEvent then
377 if event.name == "escape
" and event.is_down then
382 var player = world.player
383 if player != null and player.is_alive then
385 # Hide tutorial about arrows once they are used
386 var arrows = once ["left
", "right
"]
387 if arrows.has(event.name) then hide_tutorial_arrows
389 if player.altitude < world.boss_altitude then
390 if event.name == "space
" and event.is_down and not player.parachute_deployed and player.plane == null then
392 if player.parachute_deployed then
393 var pc = player.center
394 world.parachute = new Parachute(new Point3d[Float](pc.x, pc.y + 5.0, pc.z-0.1), 8.0, 5.0)
399 if (event.name == "space
" or event.name == "up
") and event.is_down then
403 if event.name == "left
" then
404 var mod = if event.is_down then -1.0 else 1.0
408 if event.name == "right
" then
409 var mod = if event.is_down then 1.0 else -1.0
413 if player.moving == 0.0 then
414 player.sprite.as(PlayerSprite).stop_running
415 else player.sprite.as(PlayerSprite).start_running
420 # When player is dead, respawn on spacebar or pointer depressed
421 if (event isa KeyEvent and event.name == "space
") or
422 (event isa PointerEvent and not event.is_move and event.depressed) then
423 var player = world.player
424 if player == null then
426 else if not player.is_alive then
436 # Sprite representing this entity if there is no `actor`
437 fun sprite: Sprite is abstract
440 fun actor: nullable Actor do return null
445 if actor != null then
447 else app.sprites.add sprite
450 redef fun destroy(world)
455 if actor != null then
456 app.actors.remove actor
457 else app.sprites.remove sprite
469 # Show death animation (explosion)
476 new Point3d[Float](center.x & force, center.y & force, center.z & force),
477 (2048.0 & 4096.0) * force, 0.3 & 0.1)
483 init do sprite.scale = width/sprite.texture.width
485 redef fun update(dt, world)
489 if inertia.x < 0.0 then
490 sprite.invert_x = false
491 else if inertia.x > 0.0 then
492 sprite.invert_x = true
498 private fun texture: Texture do return if center.y < 600.0 then app.planes_sheet.biplane else app.planes_sheet.jet
500 redef var sprite = new Sprite(texture, center) is lazy
503 redef class Helicopter
504 redef var sprite = new Sprite(app.planes_sheet.helicopter, center) is lazy
508 redef var actor is lazy do
509 var actor = new Actor(app.iss_model, center)
514 redef fun death_animation
517 app.explosions.add(center, 4096.0 * force, 0.3)
518 for i in (8.0*force).to_i.times do
520 new Point3d[Float](center.x & force, center.y & force/8.0, center.z & force),
521 (2048.0 & 1024.0) * force, 0.3 + 5.0.rand, 5.0.rand)
527 redef var sprite = new Sprite(app.player_textures.rand, center) is lazy
528 init do sprite.scale = width/sprite.texture.width * 2.0
531 redef class Parachute
532 redef var sprite = new Sprite(app.planes_sheet.parachute, center) is lazy
533 init do sprite.scale = width / sprite.texture.width
537 redef var sprite = new PlayerSprite(app.player_textures[1], center, app.player_textures, 0.08) is lazy
538 init do sprite.scale = width/sprite.texture.width * 2.0
540 redef fun update(dt, world)
544 sprite.invert_x = false
545 else if moving < 0.0 then
546 sprite.invert_x = true
554 if center.y < 10.0 then
555 # Blood splatter on the ground
556 var splatter = new Actor(app.splatter_model,
557 new Point3d[Float](center.x, 0.05 & 0.04, center.y))
558 splatter.scale = 32.0
559 splatter.yaw = 2.0*pi.rand
560 app.actors.add splatter
563 # Display respawn instructions
564 app.ui_sprites.add new Sprite(app.texts_sheet.respawn, app.ui_camera.center)
569 redef var sprite = new Sprite(weapon.bullet_texture, center) is lazy
573 sprite.rotation = angle
578 fun bullet_texture: Texture do return app.planes_sheet.bullet_ak
582 redef fun bullet_texture do return app.planes_sheet.bullet_pistol
585 redef class RocketLauncher
586 redef fun bullet_texture do return app.planes_sheet.bullet_rocket
590 # Scale so it looks like 5 world units wide, not matter the size of the texture
591 init do sprite.scale = 5.0/sprite.texture.width
595 redef var sprite = new Sprite(app.planes_sheet.ak, center) is lazy
598 redef class RocketLauncherPU
599 redef var sprite = new Sprite(app.planes_sheet.rocket, center) is lazy
603 redef var sprite = new Sprite(app.planes_sheet.health, center) is lazy
604 init do sprite.scale = 3.0/sprite.texture.height
609 redef fun explode(center, force)
614 app.explosions.add(center, 8192.0 * force, 0.3)
615 for i in (4.0*force).to_i.times do
617 new Point3d[Float](center.x & force, center.y & force/2.0, center.z & force),
618 (4096.0 & 2048.0) * force, 0.3 & 0.3, 0.5.rand)
624 # Pad a number with `0`s on the left side to reach `size` digits
625 private fun pad(size: Int): String
628 var d = size - s.length
629 if d > 0 then s = "0"*d + s
634 # Special `Sprite` for the player character which is animated
638 # Animation of the running character
639 var running_animation: Array[Texture]
641 # Seconds per frame of the animations
642 var time_per_frame: Float
644 # Currently playing animation
645 private var current_animation: nullable Array[Texture] = null
647 # Second at witch `current_animation` started
648 private var anim_ot = 0.0
650 # Start the running animation
653 anim_ot = app.world.t
654 current_animation = running_animation
657 # Stop the running animation
658 fun stop_running do current_animation = null
660 # Update `texture` from `current_animation`
663 var anim = current_animation
665 var dt = app.world.t - anim_ot
666 var i = (dt / time_per_frame).to_i+2
667 texture = anim.modulo(i)
672 # Manager to display numbers in sprite
675 # TODO clean up and move up to lib
677 # Number textures, from 0 to 9
679 # Require: `textures.length == 10`
680 var textures: Array[Texture]
682 # Center of the first digit in UI coordinates
683 var anchor: Point3d[Float]
685 # Last set of sprites generated to display the `value=`
686 private var sprites = new Array[Sprite]
688 # Update the value displayed by the counter by inserting new sprites into `app.ui_sprites`
689 fun value=(value: Int)
691 # Clean up last used sprites
692 for s in sprites do if app.ui_sprites.has(s) then app.ui_sprites.remove s
696 var s = value.to_s # TODO manipulate ints directly
700 var tex = textures[i]
703 sprites.add new Sprite(tex, new Point3d[Float](anchor.x + x, anchor.y, anchor.z))
707 # Register sprites to be drawn by `app.ui_camera`
708 app.ui_sprites.add_all sprites
712 redef class SmokeProgram
714 # Redef source to get particles that move up faster
715 redef fun vertex_shader_core do return """
718 c.x += dt * dt * 2.0;
720 gl_Position = c * mvp;
721 gl_PointSize = scale / gl_Position.z * (pt+0.1);
724 v_color.a = pt / 0.1;
726 v_color.a = 1.0 - pt*0.9;