diff --git a/res/barrier/enemy_killer.png b/res/barrier/enemy_killer.png new file mode 100644 index 0000000..7179e7d Binary files /dev/null and b/res/barrier/enemy_killer.png differ diff --git a/res/barrier/enemy_killer.yaml b/res/barrier/enemy_killer.yaml new file mode 100644 index 0000000..e2b1997 --- /dev/null +++ b/res/barrier/enemy_killer.yaml @@ -0,0 +1,13 @@ +sprite: res/barrier/enemy_killer.png +fx_sprite: res/barrier/enemy_killer.png +scale_origin: [0.0, 0.0] +animations: +- name: "idle" + loop: true + frames: + - clip: [0, 0, 20, 20] + duration: 0.1 + - clip: [20, 0, 20, 20] + duration: 0.1 + - clip: [40, 0, 20, 20] + duration: 0.1 \ No newline at end of file diff --git a/res/barrier/fry.mp3 b/res/barrier/fry.mp3 new file mode 100644 index 0000000..eb90e36 Binary files /dev/null and b/res/barrier/fry.mp3 differ diff --git a/res/deco/light.png b/res/deco/light.png new file mode 100644 index 0000000..7868096 Binary files /dev/null and b/res/deco/light.png differ diff --git a/res/deco/light.yaml b/res/deco/light.yaml new file mode 100644 index 0000000..f3a36d3 --- /dev/null +++ b/res/deco/light.yaml @@ -0,0 +1,9 @@ +sprite: res/deco/light.png +fx_sprite: res/deco/light_emit.png +scale_origin: [0.0, 0.0] +animations: +- name: "idle" + loop: true + frames: + - clip: [0, 0, 256, 256] + duration: 1.0 \ No newline at end of file diff --git a/res/deco/light_emit.png b/res/deco/light_emit.png new file mode 100644 index 0000000..cbef93b Binary files /dev/null and b/res/deco/light_emit.png differ diff --git a/res/enemies/spawner.png b/res/enemies/spawner.png new file mode 100644 index 0000000..c30d160 Binary files /dev/null and b/res/enemies/spawner.png differ diff --git a/res/enemies/spawner.yaml b/res/enemies/spawner.yaml new file mode 100644 index 0000000..e025bed --- /dev/null +++ b/res/enemies/spawner.yaml @@ -0,0 +1,24 @@ +sprite: res/enemies/spawner.png +fx_sprite: none +scale_origin: [16.0, 16.0] +animations: +- name: "idle" + loop: true + frames: + - clip: [0, 0, 40, 80] + duration: 1.0 +- name: "spawn" + loop: false + frames: + - clip: [40, 0, 40, 80] + duration: 0.4 + - clip: [80, 0, 40, 80] + duration: 0.4 + - clip: [120, 0, 40, 80] + duration: 0.4 + - clip: [80, 0, 40, 80] + duration: 0.4 + - clip: [40, 0, 40, 80] + duration: 0.4 + - clip: [0, 0, 40, 80] + duration: 0.4 \ No newline at end of file diff --git a/res/level2/map.png b/res/level2/map.png index 192b3e6..ba9e6c9 100644 Binary files a/res/level2/map.png and b/res/level2/map.png differ diff --git a/res/level2/map_ent.png b/res/level2/map_ent.png index 173d39c..161e5de 100644 Binary files a/res/level2/map_ent.png and b/res/level2/map_ent.png differ diff --git a/res/level2/tutorial01.png b/res/level2/tutorial01.png new file mode 100644 index 0000000..f2d0f56 Binary files /dev/null and b/res/level2/tutorial01.png differ diff --git a/res/level3/back_metal.png b/res/level3/back_metal.png new file mode 100644 index 0000000..aefb57f Binary files /dev/null and b/res/level3/back_metal.png differ diff --git a/res/level3/back_metal1.png b/res/level3/back_metal1.png new file mode 100644 index 0000000..8066971 Binary files /dev/null and b/res/level3/back_metal1.png differ diff --git a/res/level3/back_rock.png b/res/level3/back_rock.png new file mode 100644 index 0000000..e0fbf33 Binary files /dev/null and b/res/level3/back_rock.png differ diff --git a/res/level3/map.png b/res/level3/map.png new file mode 100644 index 0000000..4e9acad Binary files /dev/null and b/res/level3/map.png differ diff --git a/res/level3/map.yaml b/res/level3/map.yaml new file mode 100644 index 0000000..5eee8a7 --- /dev/null +++ b/res/level3/map.yaml @@ -0,0 +1,94 @@ +seed: 1234 + +images: +- res/level3/map.png +- res/level3/map_ent.png + +tiles: +- class: ground + texture: res/level1/ground.png + noise: 1.0 + color: [0, 0, 0] + +- class: ground + texture: res/level1/wood.png + noise: 0.0 + color: [101, 101, 101] + +- class: ground + texture: res/level1/sand.png + noise: 3.0 + color: [200, 200, 200] + +- class: ground + texture: res/level3/rock.png + noise: 0.7 + color: [75, 50, 50] + +- class: B@back + texture: res/level1/back.png + noise: 1.0 + color: [240, 240, 240] + +- class: B@backwood + texture: res/level1/wood_back.png + noise: 0.0 + color: [160, 160, 160] + +- class: B@back + texture: res/level3/back_metal.png + noise: 0.0 + color: [210, 140, 140] + +- class: B@back + texture: res/level3/back_metal1.png + noise: 0.0 + color: [210, 145, 145] + +- class: B@back + texture: res/level3/back_rock.png + noise: 1.5 + color: [220, 220, 220] + +areas: +- name: water + color: [0, 0, 255] +- name: break_wood + color: [0, 255, 150] +- name: gate + color: [100, 255, 150] +- name: enemy_killer + color: [255, 240, 0] +- name: kill + color: [255, 0, 0] + +points: +- name: rock + color: [140, 140, 200] + +- name: magmarock + color: [140, 175, 200] + +- name: box + color: [255, 140, 200] + +- name: player + color: [100, 255, 50] + +- name: rockman + color: [255, 100, 50] + +- name: door + color: [0, 51, 255] + +- name: button0 + color: [255, 0, 255] + +- name: light1l + color: [255, 190, 0] + +- name: light + color: [100, 0, 0] + +- name: rockman_spawner + color: [255, 0, 110] diff --git a/res/level3/map_ent.png b/res/level3/map_ent.png new file mode 100644 index 0000000..d36eca8 Binary files /dev/null and b/res/level3/map_ent.png differ diff --git a/res/level3/metal.png b/res/level3/metal.png new file mode 100644 index 0000000..dfb001f Binary files /dev/null and b/res/level3/metal.png differ diff --git a/res/level3/music.mp3 b/res/level3/music.mp3 new file mode 100644 index 0000000..be94935 Binary files /dev/null and b/res/level3/music.mp3 differ diff --git a/res/level3/rock.png b/res/level3/rock.png new file mode 100644 index 0000000..799cdd2 Binary files /dev/null and b/res/level3/rock.png differ diff --git a/res/shader/fullscreen.fs b/res/shader/fullscreen.fs index 6df9360..1d3b12f 100644 --- a/res/shader/fullscreen.fs +++ b/res/shader/fullscreen.fs @@ -94,6 +94,8 @@ void main() vec2 off = (vec2(n, n) * 2.0 - 1.0) * 0.015 * fac; vec2 fxcoord = coord + off; + fxcoord.x = max(min(fxcoord.x, 1.0), 0.0); + fxcoord.y = max(min(fxcoord.y, 1.0), 0.0); // Effects, we do lighting vec4 fx = texture(fxtex, fxcoord); diff --git a/src/engine/base/renderer.nim b/src/engine/base/renderer.nim index ee27d37..ecee2b2 100644 --- a/src/engine/base/renderer.nim +++ b/src/engine/base/renderer.nim @@ -60,6 +60,8 @@ proc resize(renderer: var Renderer, width: int, height: int) = glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST.GLint) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST.GLint) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP.GLint) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP.GLint) glGenRenderbuffers(1, addr renderer.depth_buffer) glBindRenderbuffer(GL_RENDERBUFFER, renderer.depth_buffer) diff --git a/src/engine/graphics/sprite.nim b/src/engine/graphics/sprite.nim index 29b80ea..51ea09b 100644 --- a/src/engine/graphics/sprite.nim +++ b/src/engine/graphics/sprite.nim @@ -23,7 +23,7 @@ type Sprite* = ref object layer*: int # Position, to be used with the renderer position*: Vec2f - rotation: float + rotation*: float scale*: Vec2f # Movement in pixels respect to the sprite top-left corner # Also used for rotation diff --git a/src/game/entities/barrier.nim b/src/game/entities/barrier.nim index 7fda3a8..15bcfe2 100644 --- a/src/game/entities/barrier.nim +++ b/src/game/entities/barrier.nim @@ -11,19 +11,33 @@ import ../userdata # Default is 0, so 42 is the answer to everything const BARRIER_COLL = 42 -type Barrier* = ref object - health*: float - hurt_wav: WavHandle - break_wav: WavHandle - sprites*: seq[AnimatedSprite] - broken*: bool - phys_shape*: Shape - phys_space: Space - user_data*: UserData - # via physical impact at speed - enemies_damage: bool - objects_damage: bool - min_energy: float + +type + Barrier* = ref object + health*: float + hurt_wav: WavHandle + break_wav: WavHandle + sprites*: seq[AnimatedSprite] + broken*: bool + phys_shape*: Shape + phys_space: Space + user_data*: UserData + # via physical impact at speed + enemies_damage: bool + objects_damage: bool + sensor_data: SensorData + + sensor: bool + + # only for sensors + destroy_object: bool + destroy_enemy: bool + + min_energy: float + SensorData = ref object + barriers: ptr seq[Barrier] + objects: ptr seq[PhysicalObject] + enemies: ptr seq[Enemy] proc hurt(this: var Barrier, energy: float) = discard this.hurt_wav.play_sound() @@ -37,17 +51,37 @@ proc on_collide*(this: var Barrier, other: BodyKind, energy: float) = this.hurt(energy) + proc barrier_handler(arb: Arbiter, sp: Space, data: pointer) {.cdecl.} = var shape_other, shape_barrier: Shape # In the order defined by the handler shapes(arb, addr shape_other, addr shape_barrier) - let barriers = cast[ptr seq[Barrier]](data) + let barriers = cast[ptr SensorData](data)[].barriers let budata = cast[ptr UserData](shape_barrier.userData) let eng = arb.totalKE if shape_other.userData != nil: let udata = cast[ptr UserData](shape_other.userData) barriers[budata.point].on_collide(udata.kind, eng) +proc sensor_barrier_handler(arb: Arbiter, sp: Space, data: pointer): bool {.cdecl.} = + var shape_other, shape_barrier: Shape + # In the order defined by the handler + shapes(arb, addr shape_other, addr shape_barrier) + let sdata = cast[ptr SensorData](data) + let budata = cast[ptr UserData](shape_barrier.userData) + if shape_other.userData != nil: + let udata = cast[ptr UserData](shape_other.userData) + let barrier = sdata.barriers[budata[].point] + if barrier.sensor: + if barrier.destroy_object and udata.kind == bkObject: + sdata.objects[udata.point].indirect_die() + discard barrier.break_wav.play_sound() + if barrier.destroy_enemy and udata.kind == bkEnemy: + sdata.enemies[udata.point].indirect_die() + discard barrier.break_wav.play_sound() + + return true + proc create_barrier_segment(this: Barrier, sprite: string, pos: Vec2f, size: int, hor: bool, space: Space) = var sprite = create_animated_sprite(sprite) @@ -59,8 +93,10 @@ proc create_barrier_segment(this: Barrier, sprite: string, pos: Vec2f, size: int this.sprites.add(sprite) -proc create_barrier(sprite: string, area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier]): Barrier = +proc create_barrier(sprite: string, area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier], + objects: ptr seq[PhysicalObject], enemies: ptr seq[Enemy], sensor: bool = false): Barrier = result = new(Barrier) + result.sensor = sensor if area.x == area.z: # Vertical barrier @@ -77,22 +113,30 @@ proc create_barrier(sprite: string, area: Vec4f, size: int, space: Space, id: in # The collider is a simple static segment let hs = size.toFloat * 0.5 result.phys_shape = newSegmentShape(space.staticBody, v(area.x + hs, area.y + hs), v(area.z + hs, area.w + hs), 8) + if sensor: + result.phys_shape.sensor = true result.user_data = make_barrier_userdata(id) result.phys_shape.userData = addr result.user_data # We sign up for collision callbacks between everything and barriers result.phys_shape.collisionType = cast[pointer](BARRIER_COLL) let all_coll = cast[pointer](0) var handler = space.addCollisionHandler(all_coll, cast[pointer](BARRIER_COLL)) + result.sensor_data = new(SensorData) + result.sensor_data.barriers = barriers + result.sensor_data.objects = objects + result.sensor_data.enemies = enemies # TODO: Assign it only for the very first time handler.postSolveFunc = barrier_handler - handler.userData = barriers + handler.preSolveFunc = sensor_barrier_handler + handler.userData = addr result.sensor_data result.phys_space = space discard space.addShape(result.phys_shape) -proc create_wooden_barrier*(area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier]): Barrier = - result = create_barrier("res/level1/break_wood.yaml", area, size, space, id, barriers) +proc create_wooden_barrier*(area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier], + objects: ptr seq[PhysicalObject], enemies: ptr seq[Enemy]): Barrier = + result = create_barrier("res/level1/break_wood.yaml", area, size, space, id, barriers, objects, enemies) result.health = 10.0 result.min_energy = 1000000.0 result.enemies_damage = true @@ -100,8 +144,9 @@ proc create_wooden_barrier*(area: Vec4f, size: int, space: Space, id: int, barri result.hurt_wav = load_sound("res/barrier/wood_hurt.mp3") result.break_wav = load_sound("res/barrier/wood_break.mp3") -proc create_gate*(area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier]): Barrier = - result = create_barrier("res/barrier/gate.yaml", area, size, space, id, barriers) +proc create_gate*(area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier], + objects: ptr seq[PhysicalObject], enemies: ptr seq[Enemy]): Barrier = + result = create_barrier("res/barrier/gate.yaml", area, size, space, id, barriers, objects, enemies) result.health = 10.0 result.min_energy = 1000000000.0 result.enemies_damage = false @@ -109,6 +154,18 @@ proc create_gate*(area: Vec4f, size: int, space: Space, id: int, barriers: ptr s result.hurt_wav = load_sound("res/barrier/wood_hurt.mp3") result.break_wav = load_sound("res/barrier/gate.mp3") +proc create_enemy_killer*(area: Vec4f, size: int, space: Space, id: int, barriers: ptr seq[Barrier], + objects: ptr seq[PhysicalObject], enemies: ptr seq[Enemy]): Barrier = + result = create_barrier("res/barrier/enemy_killer.yaml", area, size, space, id, barriers, objects, enemies, true) + result.health = 10.0 + result.min_energy = 1000000000.0 + result.enemies_damage = false + result.objects_damage = false + result.hurt_wav = load_sound("res/barrier/wood_hurt.mp3") + result.break_wav = load_sound("res/barrier/fry.mp3") + result.destroy_enemy = true + result.destroy_object = true + proc update*(this: var Barrier) = for sprite in mitems(this.sprites): sprite.animate(dt) diff --git a/src/game/entities/enemy.nim b/src/game/entities/enemy.nim index 89da48e..04f146a 100644 --- a/src/game/entities/enemy.nim +++ b/src/game/entities/enemy.nim @@ -3,12 +3,15 @@ include ../../engine/base import player import physical_object import random +import options +import sequtils import ../userdata type EnemyKind = enum - ekRockman + ekRockman, + ekRockmanSpawner Enemy* = ref object case kind: EnemyKind @@ -16,9 +19,16 @@ type retreat_timer: float retreat_goal: float retreat: bool + # -1 go left 0 not dumb 1 go right + dumb: int + of ekRockmanSpawner: + spawn_timer: float + children: seq[Enemy] + max_children: int dead*: bool health: float + no_loot: bool hurt_wav: WavHandle sprite*: AnimatedSprite phys_body*: Body @@ -26,36 +36,63 @@ type user_data*: UserData toss_timer: float +proc base_create(kind: EnemyKind): Enemy = + return Enemy(kind: kind) + proc create_rockman*(pos: Vec2f, space: Space, id: int): Enemy = - result = new(Enemy) + result = base_create(ekRockman) result.sprite = create_animated_sprite("res/enemies/rockman.yaml") let mass = 50.0 let moment = momentforBox(mass, 31.0, 31.0) result.phys_body = space.addBody(newBody(mass, moment)) result.phys_shape = space.addShape(newBoxShape(result.phys_body, 31.0, 31.0, 0.0)) - result.phys_shape.friction = 0.8 - + result.phys_shape.friction = 0.2 result.phys_body.position = v(pos.x, pos.y) + 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.kind = ekRockman result.health = 2.0 +proc create_rockman_spawner*(pos: Vec2f, space: Space, id: int): Enemy = + result = base_create(ekRockmanSpawner) + result.sprite = create_animated_sprite("res/enemies/spawner.yaml") + let mass = 500.0 + let moment = momentForBox(mass, 40.0, 80.0) -proc die(this: var Enemy, objects: var seq[PhysicalObject], space: Space) = - let pos = this.phys_body.position - let vpos = vec2f(pos.x, pos.y) - space.removeShape(this.phys_shape) - space.removeBody(this.phys_body) - this.dead = true - if this.kind == ekRockman: - # Spawn a dead rockman - objects.add(create_deadrockman(vpos, space, objects.len)) + result.phys_body = space.addBody(newBody(mass, moment)) + result.phys_shape = space.addShape(newBoxShape(result.phys_body, 40.0, 80.0, 0.0)) + result.phys_shape.friction = 1.0 + result.phys_body.position = v(pos.x, pos.y - 40.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 = 6.0 + result.spawn_timer = 0.0 + result.max_children = 5 + + +proc indirect_die*(this: var Enemy) = + this.health = -1.0 + this.no_loot = true -proc update*(this: var Enemy, player: Player, objects: var seq[PhysicalObject], space: Space) = +proc die*(this: var Enemy, objects: var seq[PhysicalObject], space: Space) = + if not this.dead: + let pos = this.phys_body.position + let vpos = vec2f(pos.x, pos.y) + space.removeShape(this.phys_shape) + space.removeBody(this.phys_body) + this.dead = true + if this.kind == ekRockman and not this.no_loot: + # Spawn a dead rockman + objects.add(create_deadrockman(vpos, space, objects.len)) + + +proc update*(this: var Enemy, player: Player, objects: var seq[PhysicalObject], enemy_count: var int, space: Space): Option[Enemy] = this.sprite.center_position = vec2f(this.phys_body.position.x, this.phys_body.position.y) this.sprite.animate(dt) @@ -65,25 +102,48 @@ proc update*(this: var Enemy, player: Player, objects: var seq[PhysicalObject], if this.toss_timer < 0.0: this.phys_body.angle = 0.0 if this.kind == ekRockman: - # Simple moving towards player behaviour, with random retreats - if this.retreat: - this.retreat_timer -= dt * 4.0 - if this.retreat_timer < 0.0: - this.retreat = false - this.retreat_goal = rand(10.0) + if this.dumb != 0: + this.phys_body.velocity = v(50.0 * this.dumb.toFloat, this.phys_body.velocity.y) else: - this.retreat_timer += dt - if this.retreat_timer > this.retreat_goal: - this.retreat = true - - var player_dir = this.sprite.position - player.sprite.position - let player_dist = length(player_dir) - player_dir /= player_dist - if player_dist < 300.0: + # Simple moving towards player behaviour, with random retreats if this.retreat: - this.phys_body.velocity = v(player_dir.x * 70.0, this.phys_body.velocity.y) + this.retreat_timer -= dt * 4.0 + if this.retreat_timer < 0.0: + this.retreat = false + this.retreat_goal = rand(10.0) else: - this.phys_body.velocity = v(-player_dir.x * 60.0, this.phys_body.velocity.y) + this.retreat_timer += dt + if this.retreat_timer > this.retreat_goal: + this.retreat = true + + var player_dir = this.sprite.position - player.sprite.position + let player_dist = length(player_dir) + player_dir /= player_dist + if player_dist < 300.0: + if this.retreat: + this.phys_body.velocity = v(player_dir.x * 70.0, this.phys_body.velocity.y) + else: + this.phys_body.velocity = v(-player_dir.x * 60.0, this.phys_body.velocity.y) + elif this.kind == ekRockmanSpawner: + this.spawn_timer -= dt + # remove dead children + this.children = this.children.filter do (x: Enemy) -> bool: x.dead + if this.spawn_timer < 1.2: + this.sprite.start_anim("spawn") + elif this.spawn_timer < 5.0: + this.sprite.start_anim("idle") + if this.spawn_timer < 0.0 and this.children.len < this.max_children: + this.spawn_timer = 15.0 + var pos = vec2f(this.sprite.center_position.x - 40.0, this.sprite.center_position.y) + var dumb = -1 + if this.sprite.scale.x < 0.0: + dumb = 1 + pos = vec2f(this.sprite.center_position.x + 40.0, this.sprite.center_position.y) + result = some(create_rockman(pos, space, enemy_count)) + inc enemy_count + result.get().dumb = dumb + this.children.add(result.get()) + else: this.toss_timer -= dt @@ -92,7 +152,8 @@ proc update*(this: var Enemy, player: Player, objects: var seq[PhysicalObject], proc hurt*(this: var Enemy, point: Vect) = this.health -= 1.0 let p = this.phys_body.position - this.phys_body.applyImpulseAtWorldPoint(v(0, -5000.0), p) + if this.kind == ekRockman: + this.phys_body.applyImpulseAtWorldPoint(v(0, -5000.0), p) discard this.hurt_wav.play_sound() # Enemies are knocked by default @@ -100,7 +161,7 @@ proc toss*(this: var Enemy) = this.toss_timer = 2.0 proc is_tossable*(this: Enemy): bool = - return true + return this.kind == ekRockman proc draw*(this: var Enemy) = renderer.draw(this.sprite) diff --git a/src/game/entities/physical_object.nim b/src/game/entities/physical_object.nim index 8ddb1eb..b9300fb 100644 --- a/src/game/entities/physical_object.nim +++ b/src/game/entities/physical_object.nim @@ -25,6 +25,8 @@ type phys_body*: Body phys_shape*: Shape user_data: UserData + dead*: bool + next_die: bool # A button will remain proc on_collide(this: PhysicalObject, other: BodyKind, dir: Vect) = @@ -44,10 +46,23 @@ proc phys_object_handler(arb: Arbiter, sp: Space, data: pointer) {.cdecl.} = let cset = contactPointSet(arb) objects[oudata.point].on_collide(udata.kind, cset.normal) -proc update*(this: var PhysicalObject) = +proc indirect_die*(this: var PhysicalObject) = + this.next_die = true + +proc die*(this: var PhysicalObject, space: Space) = + let pos = this.phys_body.position + let vpos = vec2f(pos.x, pos.y) + space.removeShape(this.phys_shape) + space.removeBody(this.phys_body) + this.dead = true + +proc update*(this: var PhysicalObject, space: Space) = this.sprite.center_position = vec2f(this.phys_body.position.x, this.phys_body.position.y) this.sprite.rotation = this.phys_body.angle + if this.next_die: + this.die(space) + if this.kind == okButton: if this.active: this.sprite.start_anim("on") diff --git a/src/game/entities/platform.nim b/src/game/entities/platform.nim index 1ecf9dc..4a1be4e 100644 --- a/src/game/entities/platform.nim +++ b/src/game/entities/platform.nim @@ -1,19 +1,135 @@ # the control of the player is taken as long as he is near and presses E include ../../engine/base import nimgl/glfw +import player import ../userdata +import options type Platform* = ref object - sprite*: Sprit + sprite*: Sprite lights*: Sprite lights_cabin*: Sprite + line: Line phys_body*: Body phys_shape*: Shape + track_body*: Body + track_shape*: Shape + spring*: Constraint + speed: float + + progress: float + p0: Vect + p1: Vect + phys_space: Space in_control*: bool + just_hopped: bool + + +proc create_platform*(p0: Vect, p1: Vect, lanchor0: Vec2f, lanchor1: Vec2f, space: Space): Platform = + result = new(Platform) + result.sprite = create_sprite("res/platform/platform.png") + result.lights = create_fx_sprite("res/platform/emit_base.png") + result.lights_cabin = create_fx_sprite("res/platform/emit_cabin.png") + + result.sprite.scale_origin = vec2f(160.0 / result.sprite.texture_width.toFloat, 94.0 / result.sprite.texture_height.toFloat) + result.lights.scale_origin = result.sprite.scale_origin + result.lights_cabin.scale_origin = result.sprite.scale_origin + + # Create the tracker body + let tracker_mass = 100.0 + let tracker_moment = momentForCircle(tracker_mass, 0.0, 10.0, vzero) + result.track_body = space.addBody(newBody(tracker_mass, tracker_moment)) + result.track_body.bodyType = BODY_TYPE_KINEMATIC + result.track_shape = space.addShape(newCircleShape(result.track_body, 10.0, vzero)) + + # Create the platform body + let platform_mass = 800.0 + let platform_moment = momentForBox(platform_mass, 118.0, 137.0) + result.phys_body = space.addBody(newBody(platform_mass, platform_moment)) + result.phys_shape = space.addShape(newBoxShape(result.phys_body, 118.0, 137.0, 0.0)) + + # Constraint them + let spring = newDampedSpring(result.track_body, result.phys_body, + vzero, v(0.0, -126.0 / 2.0), 0.0, 15000.0, 5000.0) + spring.collideBodies = false + result.spring = space.addConstraint(cast[Constraint](spring)) + + result.phys_body.position = v(p0.x, p0.y + 126.0 / 2.0) + result.track_body.position = p0 + + result.p0 = p0 + result.p1 = p1 + + result.speed = 50.0 / vdist(result.p0, result.p1) + + var points: seq[Vec2f] + points.add(lanchor0) + points.add(vec2f(p0.x, p0.y)) + points.add(vec2f(p1.x, p1.y)) + points.add(lanchor1) + result.line = create_line(points, 1.0) + result.line.color = vec4f(0.4, 0.4, 0.4, 1.0) + + +proc update*(this: var Platform, player: Player, control_platform: Option[Platform]) = + + let interp_pos = v( + this.p0.x * (1.0 - this.progress) + this.p1.x * this.progress, + this.p0.y * (1.0 - this.progress) + this.p1.y * this.progress) + this.track_body.position = interp_pos + + this.sprite.center_position = vec2f(this.phys_body.position.x, this.phys_body.position.y + 65.0) + this.lights.center_position = this.sprite.center_position + this.lights.rotation = this.sprite.rotation + this.lights_cabin.center_position = this.sprite.center_position + this.lights_cabin.rotation = this.sprite.rotation + this.sprite.rotation = this.phys_body.angle + + if glfw_window.getKey(GLFWKey.E) == GLFW_RELEASE: + this.just_hopped = false + + if control_platform.isNone: + if glfw_window.getKey(GLFWKey.E) == GLFW_PRESS and not this.just_hopped: + let dist = vdist(player.phys_body.position, this.phys_body.position) + if dist < 130.0: + this.in_control = true + this.just_hopped = true + elif control_platform.get == this: + # We may control the platform or leave + renderer.camera.center = vec2f(this.phys_body.position.x, this.phys_body.position.y) + # Set the player physics off-world, but move its sprite + let pos = this.sprite.center_position + player.sprite.center_position = vec2f(pos.x, pos.y - 60.0) + player.lantern.center_position = vec2f(pos.x, pos.y - 60.0) + player.phys_body.position = v(-100.0, -100.0) + player.fall_sound.pause() + player.step_sound.pause() + if glfw_window.getKey(GLFWKey.A) == GLFW_PRESS: + this.progress -= dt * this.speed + elif glfw_window.getKey(GLFWKey.D) == GLFW_PRESS: + this.progress += dt * this.speed + + # Stop controlling + if glfw_window.getKey(GLFWKey.E) == GLFW_PRESS and not this.just_hopped: + this.in_control = false + # throw the player + var exit_pos = this.phys_body.position + if this.progress > 0.5: + exit_pos.x += 130.0 + else: + exit_pos.x -= 130.0 + exit_pos.y -= 50.0 + player.phys_body.position = exit_pos + player.phys_body.velocity = vzero + this.just_hopped = true + this.progress = max(min(this.progress, 1.0), 0.0) -proc make_platform(p0: Vect, p1: Vect, space: Space): Platform = - result = new(Platform) \ No newline at end of file +proc draw*(this: Platform) = + renderer.draw(this.line) + renderer.draw(this.sprite) + renderer.draw(this.lights) + renderer.draw(this.lights_cabin) \ No newline at end of file diff --git a/src/game/entities/player.nim b/src/game/entities/player.nim index 1743ed0..413c4b2 100644 --- a/src/game/entities/player.nim +++ b/src/game/entities/player.nim @@ -24,6 +24,8 @@ type Player* = ref object in_toss: bool release_toss: bool + control_lock: bool + in_drag: bool step_wav: WavHandle @@ -33,8 +35,8 @@ type Player* = ref object miss_wav: WavHandle hit_wav: WavHandle attack_wav: WavHandle - step_sound: AudioHandle - fall_sound: AudioHandle + step_sound*: AudioHandle + fall_sound*: AudioHandle # This must be here to avoid circular dependency hell @@ -345,13 +347,17 @@ proc update*(this: var Player, enemies: seq[Enemy], objects: seq[PhysicalObject] let off = if this.sprite.scale.x > 0.0: 32.0 else: 7.0 this.lantern.center_position = this.sprite.position + vec2f(off, 9.0) this.sprite.animate(dt) + + renderer.camera.center = this.sprite.position + renderer.camera.scale = 1.0 proc draw*(this: var Player) = renderer.draw(this.sprite) - renderer.draw(this.lantern) +proc draw_fx*(this: var Player) = + renderer.draw(this.lantern) if this.in_toss: renderer.draw(this.ind_line) diff --git a/src/game/scenes/level.nim b/src/game/scenes/level.nim index 0baff48..3be50c1 100644 --- a/src/game/scenes/level.nim +++ b/src/game/scenes/level.nim @@ -3,6 +3,7 @@ import ../entities/player import ../entities/enemy import ../entities/barrier import ../entities/door +import ../entities/platform import ../../engine/map/map_loader import ../entities/physical_object @@ -12,6 +13,7 @@ import ../../engine/graphics/sprite import ../../engine/graphics/shader import ../../engine/base/renderer as rnd import ../userdata +import options import nimgl/glfw @@ -51,6 +53,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("rockman_spawner"): + for point in this.map.points["rockman_spawner"]: + this.enemies.add(create_rockman_spawner(point, this.physics_space, this.enemies.len)) if this.map.points.hasKey("rock"): for point in this.map.points["rock"]: @@ -77,12 +83,18 @@ proc init_no_map(this: var Level, scale: int) = # Load barriers if this.map.areas.hasKey("break_wood"): for area in this.map.areas["break_wood"]: - this.barriers.add(create_wooden_barrier(area, scale, this.physics_space, this.barriers.len, addr this.barriers)) + this.barriers.add(create_wooden_barrier(area, scale, this.physics_space, this.barriers.len, + addr this.barriers, addr this.physical_objects, addr this.enemies)) if this.map.areas.hasKey("gate"): for area in this.map.areas["gate"]: - this.barriers.add(create_gate(area, scale, this.physics_space, this.barriers.len, addr this.barriers)) - + this.barriers.add(create_gate(area, scale, this.physics_space, this.barriers.len, + addr this.barriers, addr this.physical_objects, addr this.enemies)) + + if this.map.areas.hasKey("enemy_killer"): + for area in this.map.areas["enemy_killer"]: + this.barriers.add(create_enemy_killer(area, scale, this.physics_space, this.barriers.len, + addr this.barriers, addr this.physical_objects, addr this.enemies)) # Kill zones if this.map.areas.hasKey("kill"): for area in this.map.areas["kill"]: @@ -99,6 +111,12 @@ proc init_no_map(this: var Level, scale: int) = var sprite = create_animated_sprite("res/deco/light1.yaml") sprite.position = vec2f(point.x - 10.0, point.y) this.deco.add(sprite) + + if this.map.points.hasKey("light"): + for point in this.map.points["light"]: + var sprite = create_animated_sprite("res/deco/light.yaml") + sprite.center_position = vec2f(point.x, point.y) + this.deco.add(sprite) # Removes all physical objects EXCEPT the world, and reinits proc restart(this: var Level) = @@ -159,16 +177,40 @@ proc update*(this: var Level): bool = const steps = 4 for i in countup(0, steps - 1): this.physics_space.step(dt / steps) + + var nenemies: seq[Enemy] + var enemy_count = this.enemies.len for enemy in mitems(this.enemies): if not enemy.dead: - enemy.update(this.player, this.physical_objects, this.physics_space) + let nenemy = enemy.update(this.player, this.physical_objects, enemy_count, this.physics_space) + if nenemy.isSome: + nenemies.add(nenemy.get()) + + for nenemy in nenemies: + this.enemies.add(nenemy) + for phys_obj in mitems(this.physical_objects): - phys_obj.update() + if not phys_obj.dead: + phys_obj.update(this.physics_space) for barrier in mitems(this.barriers): if not barrier.broken: barrier.update() - this.player.update(this.enemies, this.physical_objects) + + var in_platform: bool = false + var control_platform: Option[Platform] + for platform in this.platforms: + if platform.in_control: + control_platform = some(platform) + in_platform = true + break + + for platform in mitems(this.platforms): + platform.update(this.player, control_platform) + + if not in_platform: + this.player.update(this.enemies, this.physical_objects) + var exit = false for door in this.doors: @@ -177,8 +219,6 @@ proc update*(this: var Level): bool = if exit: return true - renderer.camera.center = this.player.sprite.position - renderer.camera.scale = 1.0 if glfw_window.getKey(GLFWKey.R) == GLFW_PRESS: this.restart() @@ -224,21 +264,28 @@ proc draw*(this: var Level) = this.map.drawer.draw_tiles() - for deco in this.deco: - renderer.draw(deco) for enemy in mitems(this.enemies): if not enemy.dead: enemy.draw() for phys_obj in mitems(this.physical_objects): - phys_obj.draw() + if not phys_obj.dead: + phys_obj.draw() for barrier in mitems(this.barriers): if not barrier.broken: barrier.draw() this.player.draw() - + for door in this.doors: door.draw() + + for deco in this.deco: + renderer.draw(deco) + + for platform in this.platforms: + platform.draw() + + this.player.draw_fx() diff --git a/src/game/scenes/level2.nim b/src/game/scenes/level2.nim index f646d32..c4089ae 100644 --- a/src/game/scenes/level2.nim +++ b/src/game/scenes/level2.nim @@ -2,9 +2,11 @@ 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 level3 type Level2Scene* = ref object of Scene music: WavHandle @@ -24,13 +26,25 @@ method init(this: Level2Scene) = this.tut.add(create_sprite("res/level2/tutorial00.png")) this.tut[0].center_position = vec2f(99 * 20.0, 54 * 20.0) + this.tut.add(create_sprite("res/level2/tutorial01.png")) + this.tut[1].center_position = vec2f(104 * 20.0, 61 * 20.0) for tut in mitems(this.tut): tut.clear_fx = false + this.level.platforms.add(create_platform( + v(111.0 * 20.0 + 30.0, 55.0 * 20.0), + v(145 * 20.0, 23.0 * 20.0), + vec2f(103.0 * 20.0, 55.0 * 20.0), + vec2f(156 * 20.0, 23.0 * 20.0), + this.level.physics_space + )) + method update(this: Level2Scene) = - discard this.level.update() + if this.level.update(): + echo "Scene change" + goto_scene(Level3Scene()) 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) @@ -38,9 +52,10 @@ method update(this: Level2Scene) = this.level.barriers[0].health = 0.0 else: this.cable.fx_color = vec4f(0, 0, 0, 0) + method render(this: Level2Scene) = this.level.draw() renderer.draw(this.cable) if not this.open: - for tut in this.tut: - renderer.draw(tut) \ No newline at end of file + renderer.draw(this.tut[0]) + renderer.draw(this.tut[1]) \ No newline at end of file diff --git a/src/game/scenes/level3.nim b/src/game/scenes/level3.nim new file mode 100644 index 0000000..919e86b --- /dev/null +++ b/src/game/scenes/level3.nim @@ -0,0 +1,41 @@ +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 + +type Level3Scene* = ref object of Scene + music: WavHandle + level: Level + open: bool + cable: Line + +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, 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)] + this.cable = create_line(points, 8.0) + this.cable.color = vec4f(0.1, 0.1, 0.1, 0.7) + + + +method update(this: Level3Scene) = + if this.level.update(): + goto_scene(Level3Scene()) + + 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) + +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 da73500..f3220e1 100644 --- a/src/main.nim +++ b/src/main.nim @@ -2,11 +2,12 @@ import nimgl/[glfw] #import game/scenes/intro #import game/scenes/level1 #import game/scenes/cutsc1 -import game/scenes/level2 +#import game/scenes/level2 +import game/scenes/level3 include engine/base -goto_scene(Level2Scene()) +goto_scene(Level3Scene()) proc update() =