diff --git a/res/clip2/back.png b/res/clip2/back.png new file mode 100644 index 0000000..607c161 Binary files /dev/null and b/res/clip2/back.png differ diff --git a/res/clip2/eldritch.png b/res/clip2/eldritch.png new file mode 100644 index 0000000..3f9702f Binary files /dev/null and b/res/clip2/eldritch.png differ diff --git a/res/clip2/factory.png b/res/clip2/factory.png new file mode 100644 index 0000000..358916c Binary files /dev/null and b/res/clip2/factory.png differ diff --git a/res/clip2/fore.png b/res/clip2/fore.png new file mode 100644 index 0000000..237995e Binary files /dev/null and b/res/clip2/fore.png differ diff --git a/res/clip2/text0.png b/res/clip2/text0.png new file mode 100644 index 0000000..0f40701 Binary files /dev/null and b/res/clip2/text0.png differ diff --git a/res/clip2/text1.png b/res/clip2/text1.png new file mode 100644 index 0000000..eae6a0e Binary files /dev/null and b/res/clip2/text1.png differ diff --git a/res/clip2/text3.png b/res/clip2/text3.png new file mode 100644 index 0000000..3ae4969 Binary files /dev/null and b/res/clip2/text3.png differ diff --git a/res/clip2/wall.png b/res/clip2/wall.png new file mode 100644 index 0000000..7c62841 Binary files /dev/null and b/res/clip2/wall.png differ diff --git a/res/enemies/bird.png b/res/enemies/bird.png new file mode 100644 index 0000000..93bbb52 Binary files /dev/null and b/res/enemies/bird.png differ diff --git a/res/enemies/bird.yaml b/res/enemies/bird.yaml new file mode 100644 index 0000000..a7dc638 --- /dev/null +++ b/res/enemies/bird.yaml @@ -0,0 +1,26 @@ +sprite: res/enemies/bird.png +fx_sprite: none +scale_origin: [19.0, 23.0] +animations: +- name: "normal" + loop: true + frames: + - clip: [0, 0, 50, 36] + duration: 0.2 + - clip: [50, 0, 50, 36] + duration: 0.2 + - clip: [100, 0, 50, 36] + duration: 0.2 + - clip: [150, 0, 50, 36] + duration: 0.2 +- name: "bloody" + loop: true + frames: + - clip: [0, 36, 50, 36] + duration: 0.2 + - clip: [50, 36, 50, 36] + duration: 0.2 + - clip: [100, 36, 50, 36] + duration: 0.2 + - clip: [150, 36, 50, 36] + duration: 0.2 \ No newline at end of file diff --git a/res/level3/map.png b/res/level3/map.png index 4e9acad..152a0e2 100644 Binary files a/res/level3/map.png and b/res/level3/map.png differ diff --git a/res/level3/map.yaml b/res/level3/map.yaml index 5eee8a7..f8139e5 100644 --- a/res/level3/map.yaml +++ b/res/level3/map.yaml @@ -78,6 +78,9 @@ points: - name: rockman color: [255, 100, 50] +- name: bird + color: [60, 160, 60] + - name: door color: [0, 51, 255] diff --git a/res/level3/map_ent.png b/res/level3/map_ent.png index d36eca8..91e016a 100644 Binary files a/res/level3/map_ent.png and b/res/level3/map_ent.png differ diff --git a/res/level3/run.mp3 b/res/level3/run.mp3 new file mode 100644 index 0000000..94511df Binary files /dev/null and b/res/level3/run.mp3 differ diff --git a/res/objects/roll_lava.mp3 b/res/objects/roll_lava.mp3 new file mode 100644 index 0000000..a2aa7bc Binary files /dev/null and b/res/objects/roll_lava.mp3 differ diff --git a/src/game/entities/enemy.nim b/src/game/entities/enemy.nim index 04f146a..24f944d 100644 --- a/src/game/entities/enemy.nim +++ b/src/game/entities/enemy.nim @@ -11,7 +11,8 @@ import ../userdata type EnemyKind = enum ekRockman, - ekRockmanSpawner + ekRockmanSpawner, + ekBird Enemy* = ref object case kind: EnemyKind @@ -25,6 +26,9 @@ type spawn_timer: float children: seq[Enemy] max_children: int + of ekBird: + spawn_point: Vec2f + turn_timer: float dead*: bool health: float @@ -75,6 +79,25 @@ proc create_rockman_spawner*(pos: Vec2f, space: Space, id: int): Enemy = result.spawn_timer = 0.0 result.max_children = 5 +proc create_bird*(pos: Vec2f, space: Space, id: int): Enemy = + result = base_create(ekBird) + result.sprite = create_animated_sprite("res/enemies/bird.yaml") + let mass = 200.0 + let moment = momentForBox(mass, 40.0, 36.0) + + result.phys_body = space.addBody(newBody(mass, moment)) + result.phys_shape = space.addShape(newBoxShape(result.phys_body, 40.0, 36.0, 0.0)) + result.phys_shape.friction = 0.3 + result.phys_body.position = v(pos.x, pos.y - 10.0) + + result.hurt_wav = load_sound("res/enemies/rockman_hurt.mp3") + result.user_data = make_enemy_userdata(id) + result.phys_shape.userData = addr result.user_data + + result.health = 10.0 + result.spawn_point = pos + result.turn_timer = 0.0 + proc indirect_die*(this: var Enemy) = this.health = -1.0 @@ -143,6 +166,28 @@ proc update*(this: var Enemy, player: Player, objects: var seq[PhysicalObject], inc enemy_count result.get().dumb = dumb this.children.add(result.get()) + elif this.kind == ekBird: + var player_dir = player.sprite.position - this.sprite.position + let player_dist = length(player_dir) + player_dir /= player_dist + let scale = this.sprite.scale + if this.turn_timer > 0.0: + this.turn_timer -= dt + if this.turn_timer < 0.0: + # Turn + this.sprite.scale = vec2f(-this.sprite.scale.x, 1.0) + if (scale.x < 0.0 and player_dir.x < 0.0) or + (scale.x > 0.0 and player_dir.x > 0.0): + # Player walked into our attack! + this.turn_timer = 0.0 + elif player_dist < 150.0: + # Move quickly towards the player, but with turn-around inertia + if (scale.x < 0.0 and player_dir.x > 0.0) or + (scale.x > 0.0 and player_dir.x < 0.0): + # we are looking one way, but must go the other, start timer + this.turn_timer = 3.0 + else: + this.phys_body.velocity = v(player_dir.x * 120.0, this.phys_body.velocity.y) else: this.toss_timer -= dt diff --git a/src/game/entities/physical_object.nim b/src/game/entities/physical_object.nim index b9300fb..5961a4a 100644 --- a/src/game/entities/physical_object.nim +++ b/src/game/entities/physical_object.nim @@ -1,13 +1,15 @@ # An object is a physical thing you can interact with by some means include ../../engine/base +import ../../engine/audio/audio_engine import ../userdata +import options const OBJECT_COLL = 42 * 2 type - ObjectKind = enum + ObjectKind* = enum okRock, okMagmaRock, okDeadRockman, @@ -15,7 +17,7 @@ type okButton PhysicalObject* = ref object - case kind: ObjectKind + case kind*: ObjectKind of okButton: active*: bool active_timer: float @@ -25,6 +27,11 @@ type phys_body*: Body phys_shape*: Shape user_data: UserData + + move_wav: Option[WavHandle] + move_sound: AudioHandle + vel_timer: float + dead*: bool next_die: bool @@ -62,6 +69,16 @@ proc update*(this: var PhysicalObject, space: Space) = if this.next_die: this.die(space) + this.vel_timer -= dt + if this.move_wav.isSome: + var vel = this.phys_body.velocity.vlength + vel = min(vel, 40.0) + vel /= 40.0 + if vel < 0.1: + vel = 0.0 + else: + this.vel_timer = 1.0 + this.move_sound.set_volume(max(vel, this.vel_timer)) if this.kind == okButton: if this.active: @@ -92,6 +109,8 @@ proc create_rock*(pos: Vec2f, space: Space, id: int): PhysicalObject = result.phys_shape.userData = addr result.user_data result.phys_body.position = v(pos.x, pos.y) result.phys_shape.friction = 0.1 + result.move_wav = some(load_sound("res/objects/roll.mp3")) + result.move_sound = result.move_wav.get().play_sound(true) proc create_magmarock*(pos: Vec2f, space: Space, id: int): PhysicalObject = @@ -106,6 +125,9 @@ proc create_magmarock*(pos: Vec2f, space: Space, id: int): PhysicalObject = result.phys_shape.userData = addr result.user_data result.phys_body.position = v(pos.x, pos.y) result.phys_shape.friction = 0.1 + + result.move_wav = some(load_sound("res/objects/roll_lava.mp3")) + result.move_sound = result.move_wav.get().play_sound(true) proc create_deadrockman*(pos: Vec2f, space: Space, id: int): PhysicalObject = diff --git a/src/game/scenes/cutsc2.nim b/src/game/scenes/cutsc2.nim new file mode 100644 index 0000000..5f25b9d --- /dev/null +++ b/src/game/scenes/cutsc2.nim @@ -0,0 +1,86 @@ +include ../../engine/base +import ../../engine/graphics/shader + +import level4 + + +type CutScene2* = ref object of Scene + back: Sprite + eldritch: Sprite + factory: Sprite + wall: Sprite + fore: Sprite + text: seq[Sprite] + text_prg: int + cur_time: float + text_time: float + text_times: seq[float] + music: WavHandle + time: float + +method init(this: CutScene2) = + renderer.camera.center = vec2f(320, 150) + renderer.camera.scale = 1.0 + renderer.fullscreen_shader = load_shader("res/shader/fullscreen_intro") + this.back = create_sprite("res/clip2/back.png") + this.eldritch = create_sprite("res/clip2/eldritch.png") + this.factory = create_sprite("res/clip2/factory.png") + this.wall = create_sprite("res/clip2/wall.png") + this.fore = create_sprite("res/clip2/fore.png") + + this.text.add(create_sprite("res/clip2/text0.png")) + this.text.add(create_sprite("res/clip2/text1.png")) + this.text.add(create_sprite("res/clip2/text3.png")) + this.text_times.add(10.0) + this.text_times.add(5.0) + this.text_times.add(7.0) + this.text_prg = -1 + this.text_time = 0.0 + + this.music = load_sound("res/clip1/music.mp3") + + discard play_sound(this.music) + +method update(this: CutScene2) = + this.time += dt + this.text_time -= dt + this.cur_time -= dt + + renderer.camera.center = vec2f(320, 192) + if this.time > 18.0: + this.eldritch.position = vec2f(0, 500 - (this.time - 18.0) * 100.0) + else: + this.eldritch.position = vec2f(0, 500.0) + + this.fore.position = vec2f( + (sin(this.time) + 1.0) * 10.0, + (cos(this.time) + 1.0) * 20.0 + ) + + if this.time > 28.0: + goto_scene(Level4Scene()) + + if this.text_time < 0.0: + inc this.text_prg + if this.text_prg < this.text.len: + this.text_time = this.text_times[this.text_prg] + this.cur_time = 1.0 + + for i in countup(0, this.text.len - 1): + if i == this.text_prg: + this.text[i].tint = vec4f(1.0, 1.0, 1.0, min(1.0 - this.cur_time, 1.0)) + elif i == this.text_prg - 1: + this.text[i].tint = vec4f(1.0, 1.0, 1.0, max(this.cur_time, 0.0)) + else: + this.text[i].tint = vec4f(1.0, 1.0, 1.0, 0.0) + + + +method render(this: CutScene2) = + renderer.draw(this.back) + renderer.draw(this.eldritch) + renderer.draw(this.factory) + renderer.draw(this.wall) + renderer.draw(this.fore) + for text in this.text: + renderer.draw(text) \ No newline at end of file diff --git a/src/game/scenes/level.nim b/src/game/scenes/level.nim index 3be50c1..9e325d1 100644 --- a/src/game/scenes/level.nim +++ b/src/game/scenes/level.nim @@ -37,6 +37,8 @@ type Level* = ref object deco: seq[AnimatedSprite] kill: seq[Vec4f] + reinit*: bool + die_timer: float die_sprite: Sprite die_sound: WavHandle @@ -46,6 +48,7 @@ type Level* = ref object proc init_no_map(this: var Level, scale: int) = + this.reinit = true this.player = create_player(this.map.points["player"][0], this.physics_space) this.die_timer = -1.0 @@ -53,6 +56,10 @@ proc init_no_map(this: var Level, scale: int) = if this.map.points.hasKey("rockman"): for point in this.map.points["rockman"]: this.enemies.add(create_rockman(point, this.physics_space, this.enemies.len)) + + if this.map.points.hasKey("bird"): + for point in this.map.points["bird"]: + this.enemies.add(create_bird(point, this.physics_space, this.enemies.len)) if this.map.points.hasKey("rockman_spawner"): for point in this.map.points["rockman_spawner"]: @@ -172,6 +179,7 @@ proc init*(this: var Level, map: string, backdrop: string, backdrop_fx: string, init_no_map(this, this.scale) proc update*(this: var Level): bool = + this.reinit = false # we do multiple substeps to prevent pass-through # TODO: Tune this const steps = 4 diff --git a/src/game/scenes/level3.nim b/src/game/scenes/level3.nim index 919e86b..452c795 100644 --- a/src/game/scenes/level3.nim +++ b/src/game/scenes/level3.nim @@ -6,27 +6,34 @@ import ../entities/platform import ../entities/physical_object import ../../engine/base/renderer as rnd import level +import cutsc2 type Level3Scene* = ref object of Scene music: WavHandle level: Level open: bool + event_played: bool cable: Line + run_sound: WavHandle method init(this: Level3Scene) = this.music = load_sound("res/level3/music.mp3") - #discard play_sound(this.music, true) + discard play_sound(this.music, true) this.level.init("res/level3/map.yaml", "res/level1/backdrop.png", "none", 20) - let points = @[vec2f(71.0 * 20.0 + 5.0, 67.0 * 20.0 + 15.0), vec2f(75.0 * 20.0, 67.0 * 20.0 + 15.0), - vec2f(75.0 * 20.0, 63.0 * 20.0)] + let points = @[vec2f(71.0 * 20.0 + 5.0, 87.0 * 20.0 + 15.0), vec2f(75.0 * 20.0, 87.0 * 20.0 + 15.0), + vec2f(75.0 * 20.0, 83.0 * 20.0)] this.cable = create_line(points, 8.0) this.cable.color = vec4f(0.1, 0.1, 0.1, 0.7) + this.run_sound = load_sound("res/level3/run.mp3") method update(this: Level3Scene) = + if this.level.reinit: + this.event_played = false + if this.level.update(): - goto_scene(Level3Scene()) + goto_scene(CutScene2()) let button_obj = this.level.physical_objects[this.level.buttons_idx[0]] if button_obj.active: @@ -36,6 +43,15 @@ method update(this: Level3Scene) = else: this.cable.fx_color = vec4f(0, 0, 0, 0) + if not this.event_played: + if this.level.player.sprite.center_position.y < 40.0 * 20.0: + discard this.run_sound.play_sound() + for obj in this.level.physical_objects: + if obj.kind == okMagmaRock: + let p = obj.phys_body.position + obj.phys_body.applyImpulseAtWorldPoint(v(10000.0, 0.0), p) + this.event_played = true + method render(this: Level3Scene) = this.level.draw() renderer.draw(this.cable) \ No newline at end of file diff --git a/src/game/scenes/level4.nim b/src/game/scenes/level4.nim new file mode 100644 index 0000000..452c795 --- /dev/null +++ b/src/game/scenes/level4.nim @@ -0,0 +1,57 @@ +include ../../engine/base +import ../../engine/map/map_loader +import ../../engine/graphics/sprite +import ../entities/player +import ../entities/platform +import ../entities/physical_object +import ../../engine/base/renderer as rnd +import level +import cutsc2 + +type Level3Scene* = ref object of Scene + music: WavHandle + level: Level + open: bool + event_played: bool + cable: Line + run_sound: WavHandle + +method init(this: Level3Scene) = + this.music = load_sound("res/level3/music.mp3") + discard play_sound(this.music, true) + this.level.init("res/level3/map.yaml", "res/level1/backdrop.png", "none", 20) + let points = @[vec2f(71.0 * 20.0 + 5.0, 87.0 * 20.0 + 15.0), vec2f(75.0 * 20.0, 87.0 * 20.0 + 15.0), + vec2f(75.0 * 20.0, 83.0 * 20.0)] + this.cable = create_line(points, 8.0) + this.cable.color = vec4f(0.1, 0.1, 0.1, 0.7) + this.run_sound = load_sound("res/level3/run.mp3") + + + +method update(this: Level3Scene) = + if this.level.reinit: + this.event_played = false + + if this.level.update(): + goto_scene(CutScene2()) + + let button_obj = this.level.physical_objects[this.level.buttons_idx[0]] + if button_obj.active: + this.cable.fx_color = vec4f(1.0, 0.4, 0.4, 1.0) + this.open = true + this.level.barriers[1].health = 0.0 + else: + this.cable.fx_color = vec4f(0, 0, 0, 0) + + if not this.event_played: + if this.level.player.sprite.center_position.y < 40.0 * 20.0: + discard this.run_sound.play_sound() + for obj in this.level.physical_objects: + if obj.kind == okMagmaRock: + let p = obj.phys_body.position + obj.phys_body.applyImpulseAtWorldPoint(v(10000.0, 0.0), p) + this.event_played = true + +method render(this: Level3Scene) = + this.level.draw() + renderer.draw(this.cable) \ No newline at end of file diff --git a/src/main.nim b/src/main.nim index f3220e1..5780685 100644 --- a/src/main.nim +++ b/src/main.nim @@ -3,11 +3,12 @@ import nimgl/[glfw] #import game/scenes/level1 #import game/scenes/cutsc1 #import game/scenes/level2 -import game/scenes/level3 +#import game/scenes/level3 +import game/scenes/cutsc2 include engine/base -goto_scene(Level3Scene()) +goto_scene(CutScene2()) proc update() =