diff --git a/res/barrier/electric_door.png b/res/barrier/electric_door.png new file mode 100644 index 0000000..598853a Binary files /dev/null and b/res/barrier/electric_door.png differ diff --git a/res/barrier/electric_door_emit.png b/res/barrier/electric_door_emit.png new file mode 100644 index 0000000..cef3d31 Binary files /dev/null and b/res/barrier/electric_door_emit.png differ diff --git a/res/barrier/gate.mp3 b/res/barrier/gate.mp3 new file mode 100644 index 0000000..2651567 Binary files /dev/null and b/res/barrier/gate.mp3 differ diff --git a/res/barrier/gate.yaml b/res/barrier/gate.yaml new file mode 100644 index 0000000..9fb2abb --- /dev/null +++ b/res/barrier/gate.yaml @@ -0,0 +1,9 @@ +sprite: res/barrier/electric_door.png +fx_sprite: res/barrier/electric_door_emit.png +scale_origin: [0.0, 0.0] +animations: +- name: "idle" + loop: true + frames: + - clip: [0, 0, 20, 20] + duration: 1.0 \ No newline at end of file diff --git a/res/clip1/back.png b/res/clip1/back.png new file mode 100644 index 0000000..7e8b7c3 Binary files /dev/null and b/res/clip1/back.png differ diff --git a/res/clip1/eldritch.png b/res/clip1/eldritch.png new file mode 100644 index 0000000..f40bb83 Binary files /dev/null and b/res/clip1/eldritch.png differ diff --git a/res/clip1/music.mp3 b/res/clip1/music.mp3 new file mode 100644 index 0000000..8afef85 Binary files /dev/null and b/res/clip1/music.mp3 differ diff --git a/res/clip1/text0.png b/res/clip1/text0.png new file mode 100644 index 0000000..50a7dfc Binary files /dev/null and b/res/clip1/text0.png differ diff --git a/res/clip1/text1.png b/res/clip1/text1.png new file mode 100644 index 0000000..49bdd7c Binary files /dev/null and b/res/clip1/text1.png differ diff --git a/res/clip1/text2.png b/res/clip1/text2.png new file mode 100644 index 0000000..806777f Binary files /dev/null and b/res/clip1/text2.png differ diff --git a/res/deco/light1.png b/res/deco/light1.png new file mode 100644 index 0000000..202a777 Binary files /dev/null and b/res/deco/light1.png differ diff --git a/res/deco/light1.yaml b/res/deco/light1.yaml new file mode 100644 index 0000000..b65a061 --- /dev/null +++ b/res/deco/light1.yaml @@ -0,0 +1,9 @@ +sprite: res/deco/light1.png +fx_sprite: res/deco/light1_emit.png +scale_origin: [0.0, 0.0] +animations: +- name: "idle" + loop: true + frames: + - clip: [0, 0, 60, 100] + duration: 1.0 \ No newline at end of file diff --git a/res/deco/light1_emit.png b/res/deco/light1_emit.png new file mode 100644 index 0000000..355faea Binary files /dev/null and b/res/deco/light1_emit.png differ diff --git a/res/level1/backdrop.png b/res/level1/backdrop.png new file mode 100644 index 0000000..6019bf5 Binary files /dev/null and b/res/level1/backdrop.png differ diff --git a/res/level2/backdrop.png b/res/level2/backdrop.png new file mode 100644 index 0000000..32a761c Binary files /dev/null and b/res/level2/backdrop.png differ diff --git a/res/level2/backdrop_fx.png b/res/level2/backdrop_fx.png new file mode 100644 index 0000000..44bb1cc Binary files /dev/null and b/res/level2/backdrop_fx.png differ diff --git a/res/level2/map.png b/res/level2/map.png new file mode 100644 index 0000000..192b3e6 Binary files /dev/null and b/res/level2/map.png differ diff --git a/res/level2/map.yaml b/res/level2/map.yaml new file mode 100644 index 0000000..c3f6542 --- /dev/null +++ b/res/level2/map.yaml @@ -0,0 +1,66 @@ +seed: 1234 + +images: +- res/level2/map.png +- res/level2/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: 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] + +areas: +- name: water + color: [0, 0, 255] +- name: break_wood + color: [0, 255, 150] +- name: gate + color: [100, 255, 150] +- 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] diff --git a/res/level2/map_ent.png b/res/level2/map_ent.png new file mode 100644 index 0000000..173d39c Binary files /dev/null and b/res/level2/map_ent.png differ diff --git a/res/level2/tutorial00.png b/res/level2/tutorial00.png new file mode 100644 index 0000000..844f793 Binary files /dev/null and b/res/level2/tutorial00.png differ diff --git a/res/objects/box.png b/res/objects/box.png new file mode 100644 index 0000000..6d7f328 Binary files /dev/null and b/res/objects/box.png differ diff --git a/res/objects/box.yaml b/res/objects/box.yaml new file mode 100644 index 0000000..0dca161 --- /dev/null +++ b/res/objects/box.yaml @@ -0,0 +1,9 @@ +sprite: res/objects/box.png +fx_sprite: none +scale_origin: [30.0, 30.0] +animations: +- name: "idle" + loop: true + frames: + - clip: [0, 0, 60, 60] + duration: 1.0 \ No newline at end of file diff --git a/res/objects/button.png b/res/objects/button.png new file mode 100644 index 0000000..5bded56 Binary files /dev/null and b/res/objects/button.png differ diff --git a/res/objects/button.yaml b/res/objects/button.yaml new file mode 100644 index 0000000..bf021cd --- /dev/null +++ b/res/objects/button.yaml @@ -0,0 +1,14 @@ +sprite: res/objects/button.png +fx_sprite: res/objects/button_emit.png +scale_origin: [20.0, 20.0] +animations: +- name: "off" + loop: true + frames: + - clip: [0, 0, 40, 40] + duration: 1.0 +- name: "on" + loop: true + frames: + - clip: [40, 0, 40, 40] + duration: 1.0 \ No newline at end of file diff --git a/res/objects/button_emit.png b/res/objects/button_emit.png new file mode 100644 index 0000000..bf059a7 Binary files /dev/null and b/res/objects/button_emit.png differ diff --git a/res/perish.mp3 b/res/perish.mp3 new file mode 100644 index 0000000..4ce9859 Binary files /dev/null and b/res/perish.mp3 differ diff --git a/res/perish.png b/res/perish.png new file mode 100644 index 0000000..8484af7 Binary files /dev/null and b/res/perish.png differ diff --git a/res/platform/emit_base.png b/res/platform/emit_base.png new file mode 100644 index 0000000..c313061 Binary files /dev/null and b/res/platform/emit_base.png differ diff --git a/res/platform/emit_cabin.png b/res/platform/emit_cabin.png new file mode 100644 index 0000000..4b6251c Binary files /dev/null and b/res/platform/emit_cabin.png differ diff --git a/res/platform/platform.png b/res/platform/platform.png new file mode 100644 index 0000000..6a8d85c Binary files /dev/null and b/res/platform/platform.png differ diff --git a/res/shader/line.fs b/res/shader/line.fs new file mode 100644 index 0000000..50486ce --- /dev/null +++ b/res/shader/line.fs @@ -0,0 +1,12 @@ +#version 330 +layout(location = 0) out vec4 color; +layout(location = 1) out vec4 effect; + +uniform vec4 tint; +uniform vec4 fx_tint; + +void main() +{ + color = tint; + effect = fx_tint; +} diff --git a/res/shader/line.vs b/res/shader/line.vs new file mode 100644 index 0000000..e53722e --- /dev/null +++ b/res/shader/line.vs @@ -0,0 +1,10 @@ +#version 330 +layout (location = 0) in vec3 aPos; + +uniform mat4 tform; +uniform mat4 subtform; + +void main() +{ + gl_Position = tform * subtform * vec4(aPos.x, aPos.y, aPos.z, 1.0); +} diff --git a/res/shader/sprite.fs b/res/shader/sprite.fs index 9a07fc1..8beb513 100644 --- a/res/shader/sprite.fs +++ b/res/shader/sprite.fs @@ -8,6 +8,7 @@ uniform sampler2D tex; uniform int has_fx; uniform vec4 tint; +uniform int clear_fx; // Contains (x, y) = lower bound (z, w) = size uniform vec4 clip; @@ -25,6 +26,13 @@ void main() else { color = texture(tex, coord) * tint; - effect = vec4(0, 0, 0, 0); + if(clear_fx == 1 && color.a != 0) + { + effect = vec4(0, 0, 0, 1); + } + else + { + effect = vec4(0, 0, 0, 0); + } } } diff --git a/src/engine/base.nim b/src/engine/base.nim index 324bab8..33b9afc 100644 --- a/src/engine/base.nim +++ b/src/engine/base.nim @@ -3,6 +3,7 @@ import launcher import base/scene_manager import graphics/sprite +import graphics/line import graphics/camera import globals import base/renderer as rnd diff --git a/src/engine/graphics/line.nim b/src/engine/graphics/line.nim new file mode 100644 index 0000000..da03054 --- /dev/null +++ b/src/engine/graphics/line.nim @@ -0,0 +1,54 @@ +import nimgl/opengl +import glm +import shader + +# Draws a connected line, with width +type Line* = ref object + vbo: GLuint + vao: GLuint + width*: float + shader*: Shader + position*: Vec2f + rotation*: float + scale*: Vec2f + points: int + layer*: int + + color*: Vec4f + fx_color*: Vec4f + +proc create_line*(points: seq[Vec2f], width: float = 1.0): Line = + let shader = load_shader("res/shader/line") + var verts: seq[float32] + for point in points: + verts.add(point.x) + verts.add(point.y) + verts.add(0.0) + + var vao: GLuint + var vbo: GLuint + glGenVertexArrays(1, addr vao) + glGenBuffers(1, addr vbo) + + glBindVertexArray(vao) + + glBindBuffer(GL_ARRAY_BUFFER, vbo) + glBufferData(GL_ARRAY_BUFFER, verts.len * sizeof(float32), unsafeAddr verts[0], GL_STATIC_DRAW) + + # position attribute + glVertexAttribPointer(0'u32, 3'i32, EGL_FLOAT, false, 3 * float32.sizeof, cast[pointer](0)) + glEnableVertexAttribArray(0) + + return Line(width: width, shader: shader, vbo: vbo, points: points.len, + fx_color: vec4f(0, 0, 0, 0), color: vec4f(1, 1, 1, 1), vao: vao, scale: vec2f(1.0, 1.0)) + +proc do_draw*(line: Line) = + var subtform = mat4f(1.0) + subtform = subtform.rotate(line.rotation, 0, 0, 1).scale(line.scale.x, line.scale.y, 1.0) + line.shader.set_mat4("subtform", subtform) + line.shader.set_vec4("tint", line.color) + line.shader.set_vec4("fx_tint", line.fx_color) + + glLineWidth(1.0) + glBindVertexArray(line.vao) + glDrawArrays(GL_LINE_STRIP, 0.GLint, line.points.GLsizei) \ No newline at end of file diff --git a/src/engine/graphics/sprite.nim b/src/engine/graphics/sprite.nim index c88f27b..29b80ea 100644 --- a/src/engine/graphics/sprite.nim +++ b/src/engine/graphics/sprite.nim @@ -16,6 +16,7 @@ type Sprite* = ref object texture_width*, texture_height*: int # In UV coordinates clip*: Vec4f + clear_fx*: bool # Tint color tint*: Vec4f flip_h*, flip_v*: bool @@ -31,7 +32,8 @@ type Sprite* = ref object proc create_sprite*(image: GLuint, fx_image: GLuint, width: int, height: int): Sprite = return Sprite(texture_id: image, fx_texture_id: fx_image, texture_width: width, texture_height: height, clip: vec4f(0, 0, 1, 1), tint: vec4f(1.0, 1.0, 1.0, 1.0), - shader: load_shader("res/shader/sprite"), scale: vec2f(1.0, 1.0)) + shader: load_shader("res/shader/sprite"), scale: vec2f(1.0, 1.0), + clear_fx: true) proc create_sprite*(image: string): Sprite = var width, height, nCh : int @@ -73,8 +75,8 @@ proc create_fx_sprite*(image: string): Sprite = proc create_sprite*(image: string, fx_image: string): Sprite = var width, height, nCh, fxwidth, fxheight : int - var data = stbi.load(image, width, height, nCh, 0) - var fxdata = stbi.load(fx_image, fxwidth, fxheight, nCh, 0) + var data = stbi.load(image, width, height, nCh, 4) + var fxdata = stbi.load(fx_image, fxwidth, fxheight, nCh, 4) assert fxwidth == width and fxheight == height var tex, fxtex: Gluint @@ -124,13 +126,8 @@ proc do_draw*(sprite: Sprite) = .rotate(sprite.rotation, 0, 0, 1) .translate(-sprite.scale_origin.x, -sprite.scale_origin.y, 0.0) sprite.shader.set_mat4("sprite_tform", stform) - - if sprite.fx_texture_id != 0: - glActiveTexture(GL_TEXTURE0) - glBindTexture(GL_TEXTURE_2D, sprite.fx_texture_id) - sprite.shader.set_int("has_fx", 1) - glDepthMask(false) - draw_rectangle() + sprite.shader.set_int("clear_fx", + if (sprite.texture_id != 0) and sprite.clear_fx: 1 else: 0) if sprite.texture_id != 0: glActiveTexture(GL_TEXTURE0) @@ -138,6 +135,13 @@ proc do_draw*(sprite: Sprite) = sprite.shader.set_int("has_fx", 0) glDepthMask(true) draw_rectangle() + + if sprite.fx_texture_id != 0: + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_2D, sprite.fx_texture_id) + sprite.shader.set_int("has_fx", 1) + glDepthMask(false) + draw_rectangle() proc `center_position=`*(sprite: Sprite, pos: Vec2f) = diff --git a/src/engine/map/map_loader.nim b/src/engine/map/map_loader.nim index 7cb44fb..f816b15 100644 --- a/src/engine/map/map_loader.nim +++ b/src/engine/map/map_loader.nim @@ -86,6 +86,7 @@ type Map* = ref object points*: Table[string, seq[Vec2f]] # Only bounding boxes areas*: Table[string, seq[Vec4f]] + size*: Vec2f proc hash(x: Vec3i): Hash = return x.x.hash !& x.y.hash !& x.z.hash @@ -592,7 +593,8 @@ proc load_map*(map: string, scale: int, space: Space): Map = let drawer = create_map_drawer(ground_tiles) - return Map(drawer: drawer, segments: segments, points: points, areas: areas) + let size = vec2f((images[0].width * scale).toFloat, (images[0].height * scale).toFloat) + return Map(drawer: drawer, segments: segments, points: points, areas: areas, size: size) diff --git a/src/game/entities/barrier.nim b/src/game/entities/barrier.nim index 3e7d09a..7fda3a8 100644 --- a/src/game/entities/barrier.nim +++ b/src/game/entities/barrier.nim @@ -12,7 +12,7 @@ import ../userdata const BARRIER_COLL = 42 type Barrier* = ref object - health: float + health*: float hurt_wav: WavHandle break_wav: WavHandle sprites*: seq[AnimatedSprite] @@ -100,10 +100,19 @@ 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) + 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/gate.mp3") + proc update*(this: var Barrier) = for sprite in mitems(this.sprites): sprite.animate(dt) - if this.health < 0.0: + if this.health <= 0.0: discard this.break_wav.play_sound() this.phys_space.removeShape(this.phys_shape) this.broken = true diff --git a/src/game/entities/physical_object.nim b/src/game/entities/physical_object.nim index 137d4c6..8ddb1eb 100644 --- a/src/game/entities/physical_object.nim +++ b/src/game/entities/physical_object.nim @@ -3,31 +3,70 @@ include ../../engine/base import ../userdata - + +const OBJECT_COLL = 42 * 2 type ObjectKind = enum okRock, okMagmaRock, - okDeadRockman + okDeadRockman, + okBox, + okButton PhysicalObject* = ref object - kind: ObjectKind + case kind: ObjectKind + of okButton: + active*: bool + active_timer: float + hold_active*: bool + else: discard sprite*: AnimatedSprite phys_body*: Body phys_shape*: Shape user_data: UserData +# A button will remain +proc on_collide(this: PhysicalObject, other: BodyKind, dir: Vect) = + if this.kind == okButton: + this.active = true + this.active_timer = 0.0 + +# Physical objects may be activated by collisions with other stuff +proc phys_object_handler(arb: Arbiter, sp: Space, data: pointer) {.cdecl.} = + var shape_other, shape_us: Shape + # In the order defined by the handler + shapes(arb, addr shape_other, addr shape_us) + let objects = cast[ptr seq[PhysicalObject]](data) + let oudata = cast[ptr UserData](shape_us.userData) + if shape_other.userData != nil: + let udata = cast[ptr UserData](shape_other.userData) + let cset = contactPointSet(arb) + objects[oudata.point].on_collide(udata.kind, cset.normal) + proc update*(this: var PhysicalObject) = this.sprite.center_position = vec2f(this.phys_body.position.x, this.phys_body.position.y) this.sprite.rotation = this.phys_body.angle + + if this.kind == okButton: + if this.active: + this.sprite.start_anim("on") + this.active_timer += dt + if this.active_timer >= 0.5: + this.active = false + else: + this.sprite.start_anim("off") + this.sprite.animate(dt) proc draw*(this: var PhysicalObject) = renderer.draw(this.sprite) +proc base_create(kind: ObjectKind): PhysicalObject = + return PhysicalObject(kind: kind) + proc create_rock*(pos: Vec2f, space: Space, id: int): PhysicalObject = - result = new(PhysicalObject) + result = base_create(okRock) result.sprite = create_animated_sprite("res/objects/rock.yaml") let mass = 40.0 let moment = momentForCircle(mass, 0.0, 58.0, vzero) @@ -39,10 +78,9 @@ proc create_rock*(pos: Vec2f, space: Space, id: int): PhysicalObject = result.phys_body.position = v(pos.x, pos.y) result.phys_shape.friction = 0.1 - result.kind = okRock proc create_magmarock*(pos: Vec2f, space: Space, id: int): PhysicalObject = - result = new(PhysicalObject) + result = base_create(okMagmaRock) result.sprite = create_animated_sprite("res/objects/magmarock.yaml") let mass = 40.0 let moment = momentForCircle(mass, 0.0, 58.0, vzero) @@ -54,10 +92,9 @@ proc create_magmarock*(pos: Vec2f, space: Space, id: int): PhysicalObject = result.phys_body.position = v(pos.x, pos.y) result.phys_shape.friction = 0.1 - result.kind = okMagmaRock proc create_deadrockman*(pos: Vec2f, space: Space, id: int): PhysicalObject = - result = new(PhysicalObject) + result = base_create(okDeadRockman) result.sprite = create_animated_sprite("res/enemies/deadrockman.yaml") let mass = 12.0 let moment = momentForBox(mass, 12.0, 12.0) @@ -69,7 +106,46 @@ proc create_deadrockman*(pos: Vec2f, space: Space, id: int): PhysicalObject = result.phys_body.position = v(pos.x, pos.y) result.phys_shape.friction = 0.7 - result.kind = okDeadRockman + + +proc create_box*(pos: Vec2f, space: Space, id: int): PhysicalObject = + result = base_create(okBox) + + result.sprite = create_animated_sprite("res/objects/box.yaml") + let mass = 80.0 + let moment = momentForBox(mass, 60.0, 60.0) + + result.phys_body = space.addBody(newBody(mass, moment)) + result.phys_shape = space.addShape(newBoxShape(result.phys_body, 60.0, 60.0, 0.0)) + result.user_data = make_object_userdata(id) + result.phys_shape.userData = addr result.user_data + result.phys_body.position = v(pos.x, pos.y) + result.phys_shape.friction = 0.4 + + +proc create_button*(pos: Vec2f, space: Space, id: int, obj: ptr seq[PhysicalObject]): PhysicalObject = + result = base_create(okButton) + result.sprite = create_animated_sprite("res/objects/button.yaml") + let mass = 40.0 + let moment = momentForBox(mass, 27.0, 35.0) + + result.phys_body = space.addBody(newBody(mass, moment)) + result.phys_shape = space.addShape(newBoxShape(result.phys_body, 27.0, 35.0, 0.0)) + result.user_data = make_object_userdata(id) + result.phys_shape.userData = addr result.user_data + result.phys_body.position = v(pos.x + 30.0, pos.y - 10.0) + result.phys_shape.friction = 0.4 + + # We sign up for collision callbacks between everything and barriers + result.phys_shape.collisionType = cast[pointer](OBJECT_COLL) + let all_coll = cast[pointer](0) + var handler = space.addCollisionHandler(all_coll, cast[pointer](OBJECT_COLL)) + # TODO: Assign it only for the very first time + handler.postSolveFunc = phys_object_handler + handler.userData = obj proc is_tossable*(this: PhysicalObject): bool = - return this.kind == okDeadRockman \ No newline at end of file + return this.kind == okDeadRockman + +proc is_draggable*(this: PhysicalObject): bool = + return this.kind == okBox or this.kind == okButton \ No newline at end of file diff --git a/src/game/entities/platform.nim b/src/game/entities/platform.nim new file mode 100644 index 0000000..1ecf9dc --- /dev/null +++ b/src/game/entities/platform.nim @@ -0,0 +1,19 @@ +# the control of the player is taken as long as he is near and presses E +include ../../engine/base +import nimgl/glfw +import ../userdata + +type Platform* = ref object + sprite*: Sprit + lights*: Sprite + lights_cabin*: Sprite + + phys_body*: Body + phys_shape*: Shape + phys_space: Space + + in_control*: bool + + +proc make_platform(p0: Vect, p1: Vect, space: Space): Platform = + result = new(Platform) \ No newline at end of file diff --git a/src/game/entities/player.nim b/src/game/entities/player.nim index 046030e..1743ed0 100644 --- a/src/game/entities/player.nim +++ b/src/game/entities/player.nim @@ -7,6 +7,7 @@ import random type Player* = ref object sprite*: AnimatedSprite lantern*: Sprite + ind_line: Line phys_body*: Body phys_shape*: Shape @@ -23,6 +24,8 @@ type Player* = ref object in_toss: bool release_toss: bool + in_drag: bool + step_wav: WavHandle fall_wav: WavHandle jump_wav: WavHandle @@ -65,6 +68,9 @@ proc create_player*(pos: Vec2f, space: Space): Player = result.step_sound = create_sound(result.step_wav, true) result.fall_sound = create_sound(result.fall_wav, true) + let points = @[vec2f(0.0, 0.0), vec2f(20.0, 0.0), vec2f(15.0, 5.0), vec2f(15.0, -5.0)] + result.ind_line = create_line(points, 4.0) + proc ground_query_foot(sh: Shape, p: Vect, n: Vect, a: Float, data: pointer) {.cdecl.} = @@ -145,6 +151,33 @@ proc query_toss(sh: Shape, p: Vect, n: Vect, a: Float, data: pointer) {.cdecl.} fp = v(fp.x + rand(10.0), fp.y + rand(10.0)) body.applyImpulseAtWorldPoint(datac[].force, fp) +proc get_toss_prog(this: Player): float = + # Toss timer goes from 0.0 to 1.8 + # from 0.0 to 0.4 we do nothing + # from 0.4 to 1.2 linear rampup (0.8 size) + # from 1.2 to 1.8 capped + var toss_prog = 0.0 + if this.attack_timer > 0.4: + toss_prog = (this.attack_timer - 0.4) / 0.8 + toss_prog = min(toss_prog, 1.0) + return toss_prog + +proc get_toss_vec(this: Player, toss_prog: float): Vect = + var force = v(0.0, 0.0) + var ver = 0.9 * (sin(PI * toss_prog) + 0.3) + if this.sprite.scale.x < 0.0: + force = v(-1.0, -ver) + else: + force = v(1.0, -ver) + let len = force.vlength + force = v(force.x / len, force.y / len) + + return force + +proc get_toss_factor(this: Player, toss_prog: float): float = + return (cos(PI * 0.5 * toss_prog - PI * 0.5) + 0.7) + + proc toss(this: Player, enemies: seq[Enemy], objects: seq[PhysicalObject]) = let rays = this.phys_body.position + v(0.0, 0.0) var raye = this.phys_body.position + v(48.0, 23.0) @@ -161,28 +194,15 @@ proc toss(this: Player, enemies: seq[Enemy], objects: seq[PhysicalObject]) = query_data.enemies = enemies query_data.objects = objects - # Toss timer goes from 0.0 to 1.8 - # from 0.0 to 0.4 we do nothing - # from 0.4 to 1.2 linear rampup (0.8 size) - # from 1.2 to 1.8 capped - var toss_prog = 0.0 - if this.attack_timer > 0.4: - toss_prog = (this.attack_timer - 0.4) / 0.8 - toss_prog = min(toss_prog, 1.0) + let toss_prog = this.get_toss_prog() if toss_prog == 0.0: return # Direction - var ver = 1.0 * (sin(PI * toss_prog) + 0.2) - if this.sprite.scale.x < 0.0: - query_data.force = v(-1.0, -ver) - else: - query_data.force = v(1.0, -ver) - let len = query_data.force.vlength - query_data.force = v(query_data.force.x / len, query_data.force.y / len) + query_data.force = this.get_toss_vec(toss_prog) - var force = 7000.0 * (cos(PI * 0.5 * toss_prog - PI * 0.5) + 0.3) + let force = 5000.0 * this.get_toss_factor(toss_prog) query_data.force = v(query_data.force.x * force, query_data.force.y * force) # We do two querys @@ -240,6 +260,18 @@ proc update*(this: var Player, enemies: seq[Enemy], objects: seq[PhysicalObject] if this.attack_timer > 0.8: this.in_attack = false elif this.in_toss: + let prog = this.get_toss_prog() + let fac = this.get_toss_factor(prog) + if this.sprite.scale.x > 0: + this.ind_line.position = this.sprite.position + vec2f(61.0, 73.0) + this.ind_line.scale = vec2f(fac, 1.0) + else: + this.ind_line.position = this.sprite.position + vec2f(-28.0, 73.0) + this.ind_line.scale = vec2f(-fac, 1.0) + + let fvec = this.get_toss_vec(prog) + this.ind_line.rotation = arctan(fvec.y / fvec.x) + if glfw_window.getKey(GLFWKey.C) != GLFW_PRESS or this.attack_timer > 1.8: this.in_toss = false this.toss(enemies, objects) @@ -249,13 +281,20 @@ proc update*(this: var Player, enemies: seq[Enemy], objects: seq[PhysicalObject] else: var lateral = false + if glfw_window.getKey(GLFWKey.B) == GLFW_PRESS: + this.in_drag = true + else: + this.in_drag = false + if glfw_window.getKey(GLFWKey.A) == GLFW_PRESS: this.phys_body.position = this.phys_body.position + v(-dt * 120.0, 0.0) - this.sprite.scale = vec2f(-1.0, 1.0) + if not this.in_drag: + this.sprite.scale = vec2f(-1.0, 1.0) lateral = true if glfw_window.getKey(GLFWKey.D) == GLFW_PRESS: this.phys_body.position = this.phys_body.position + v(dt * 120.0, 0.0) - this.sprite.scale = vec2f(1.0, 1.0) + if not this.in_drag: + this.sprite.scale = vec2f(1.0, 1.0) lateral = true if glfw_window.getKey(GLFWKey.V) == GLFW_PRESS: this.attack_timer = 0.0 @@ -272,13 +311,13 @@ proc update*(this: var Player, enemies: seq[Enemy], objects: seq[PhysicalObject] else: this.release_toss = true if glfw_window.getKey(GLFWKey.Space) == GLFW_PRESS: - if not this.last_jump and this.grounded: + if not this.last_jump and (this.grounded or (this.time_in_air < 0.4 and this.sliding)): this.phys_body.applyImpulseAtWorldPoint(v(0, -15000.0), v(0, 0)) discard this.jump_wav.play_sound() this.last_jump = true else: this.last_jump = false - + if this.time_in_air > 0.5: this.sprite.start_anim("fall") this.step_sound.pause() @@ -287,7 +326,11 @@ proc update*(this: var Player, enemies: seq[Enemy], objects: seq[PhysicalObject] else: this.fall_sound.pause() if lateral: - this.sprite.start_anim("walk") + if this.in_drag: + this.sprite.start_anim("push") + else: + this.sprite.start_anim("walk") + this.step_sound.resume() if not lateral: @@ -309,3 +352,6 @@ proc update*(this: var Player, enemies: seq[Enemy], objects: seq[PhysicalObject] proc draw*(this: var Player) = renderer.draw(this.sprite) renderer.draw(this.lantern) + + if this.in_toss: + renderer.draw(this.ind_line) diff --git a/src/game/scenes/cutsc1.nim b/src/game/scenes/cutsc1.nim new file mode 100644 index 0000000..88ffc6d --- /dev/null +++ b/src/game/scenes/cutsc1.nim @@ -0,0 +1,71 @@ +include ../../engine/base +import ../../engine/graphics/shader +import level2 + + +type CutScene1* = ref object of Scene + back: Sprite + eldritch: Sprite + text: seq[Sprite] + text_prg: int + cur_time: float + text_time: float + text_times: seq[float] + music: WavHandle + time: float + +method init(this: CutScene1) = + 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/clip1/back.png") + this.eldritch = create_sprite("res/clip1/eldritch.png") + this.text.add(create_sprite("res/clip1/text0.png")) + this.text.add(create_sprite("res/clip1/text1.png")) + this.text.add(create_sprite("res/clip1/text2.png")) + this.text[2].position = vec2f(0.0, 100.0) + this.text_times.add(10.0) + this.text_times.add(7.0) + this.text_times.add(4.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: CutScene1) = + this.time += dt + this.text_time -= dt + this.cur_time -= dt + + renderer.camera.center = vec2f(320, 150 + this.time * 4.0) + if this.time > 20.0: + this.eldritch.position = vec2f(0, 500 - (this.time - 20.0) * 160.0) + else: + this.eldritch.position = vec2f(0, 500.0) + + if this.time > 28.0: + goto_scene(Level2Scene()) + + 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: CutScene1) = + renderer.draw(this.eldritch) + renderer.draw(this.back) + 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 0f63dd7..0baff48 100644 --- a/src/game/scenes/level.nim +++ b/src/game/scenes/level.nim @@ -7,8 +7,10 @@ import ../entities/door import ../../engine/map/map_loader import ../entities/physical_object import ../../engine/globals +import ../../engine/audio/audio_engine import ../../engine/graphics/sprite import ../../engine/graphics/shader +import ../../engine/base/renderer as rnd import ../userdata import nimgl/glfw @@ -26,12 +28,24 @@ type Level* = ref object physical_objects*: seq[PhysicalObject] barriers*: seq[Barrier] enemies: seq[Enemy] + # Must be manually created + platforms*: seq[Platform] doors: seq[Door] + backdrop: Sprite + deco: seq[AnimatedSprite] + kill: seq[Vec4f] + + die_timer: float + die_sprite: Sprite + die_sound: WavHandle + # Buttons have a preset ammount of buttons + buttons_idx*: array[4, int] proc init_no_map(this: var Level, scale: int) = this.player = create_player(this.map.points["player"][0], this.physics_space) + this.die_timer = -1.0 # Create all types of enemies / objects if this.map.points.hasKey("rockman"): @@ -45,17 +59,47 @@ proc init_no_map(this: var Level, scale: int) = if this.map.points.hasKey("magmarock"): for point in this.map.points["magmarock"]: this.physical_objects.add(create_magmarock(point, this.physics_space, this.physical_objects.len)) + + if this.map.points.hasKey("box"): + for point in this.map.points["box"]: + this.physical_objects.add(create_box(point, this.physics_space, this.physical_objects.len)) + + for i in countup(0, this.buttons_idx.len - 1): + this.buttons_idx[i] = -1 + let str = "button" & $i + if this.map.points.hasKey(str) and this.map.points[str].len == 1: + let point = this.map.points[str][0] + this.physical_objects.add(create_button(point, this.physics_space, this.physical_objects.len, + addr this.physical_objects)) + this.buttons_idx[0] = this.physical_objects.len - 1 + # 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)) + 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)) + + # Kill zones + if this.map.areas.hasKey("kill"): + for area in this.map.areas["kill"]: + this.kill.add(area) + # Load doors if this.map.points.hasKey("door"): for point in this.map.points["door"]: this.doors.add(create_door(point)) + # Load deco + if this.map.points.hasKey("light1l"): + for point in this.map.points["light1l"]: + var sprite = create_animated_sprite("res/deco/light1.yaml") + sprite.position = vec2f(point.x - 10.0, point.y) + this.deco.add(sprite) + # Removes all physical objects EXCEPT the world, and reinits proc restart(this: var Level) = for obj in this.physical_objects: @@ -79,7 +123,12 @@ proc restart(this: var Level) = this.init_no_map(this.scale) -proc init*(this: var Level, map: string, scale: int) = +proc die(this: var Level) = + if this.die_timer < 0.0: + discard this.die_sound.play_sound() + this.die_timer = 2.0 + +proc init*(this: var Level, map: string, backdrop: string, backdrop_fx: string, scale: int) = this = new(Level) this.scale = scale renderer.fullscreen_shader = load_shader("res/shader/fullscreen") @@ -95,9 +144,16 @@ proc init*(this: var Level, map: string, scale: int) = ##segment.userData = addr this.terr_udata segment.friction = 0.5 + if backdrop_fx != "none": + this.backdrop = create_sprite(backdrop, backdrop_fx) + else: + this.backdrop = create_sprite(backdrop) + this.die_sprite = create_sprite("res/perish.png", "res/perish.png") + this.die_sound = load_sound("res/perish.mp3") + init_no_map(this, this.scale) -proc update*(this: var Level) = +proc update*(this: var Level): bool = # we do multiple substeps to prevent pass-through # TODO: Tune this const steps = 4 @@ -119,29 +175,70 @@ proc update*(this: var Level) = exit = exit or door.update(this.player) if exit: - quit(1) + return true renderer.camera.center = this.player.sprite.position renderer.camera.scale = 1.0 if glfw_window.getKey(GLFWKey.R) == GLFW_PRESS: this.restart() + + let screens = vec2f( + this.backdrop.texture_width.toFloat / 640, + this.backdrop.texture_height.toFloat / 384 + ) -proc draw*(this: var Level) = - this.map.drawer.draw_tiles() + let screenp = vec2f( + ((renderer.camera.center.x / this.map.size.x) * screens.x - 0.5) * 2.0, + ((renderer.camera.center.y / this.map.size.y) * screens.y - 0.5) * 2.0 + ) - for enemy in mitems(this.enemies): - if not enemy.dead: - enemy.draw() - for phys_obj in mitems(this.physical_objects): - phys_obj.draw() + let offset = vec2f( + renderer.camera.center.x - 640 * 0.25 * screenp.x, + renderer.camera.center.y - 384 * 0.25 * screenp.y + ) - for barrier in mitems(this.barriers): - if not barrier.broken: - barrier.draw() - this.player.draw() - for door in this.doors: - door.draw() + this.backdrop.center_position = offset + + # Check die areas + for area in this.kill: + let pos = this.player.sprite.position + if pos.x > area.x and pos.y > area.y and pos.x < area.x + area.z and pos.y < area.y + area.w: + this.die() + + if this.die_timer > 0.0: + this.die_timer -= dt + if this.die_timer < 0.0: + this.restart() + + return false + +proc draw*(this: var Level) = + if this.die_timer > 0.0: + this.die_sprite.center_position = renderer.camera.center + renderer.draw(this.die_sprite) + else: + + renderer.draw(this.backdrop) + + 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() + + for barrier in mitems(this.barriers): + if not barrier.broken: + barrier.draw() + this.player.draw() + + for door in this.doors: + door.draw() diff --git a/src/game/scenes/level1.nim b/src/game/scenes/level1.nim index b0f3a63..86cf8b4 100644 --- a/src/game/scenes/level1.nim +++ b/src/game/scenes/level1.nim @@ -3,6 +3,7 @@ import ../../engine/map/map_loader import ../../engine/graphics/sprite import ../entities/player import level +import cutsc1 type Level1Scene* = ref object of Scene music: WavHandle @@ -14,7 +15,7 @@ method init(this: Level1Scene) = this.music = load_sound("res/level1/music.mp3") discard play_sound(this.music, true) - this.level.init("res/level1/map.yaml", 20) + this.level.init("res/level1/map.yaml", "res/level1/backdrop.png", "none", 20) this.tut.add(create_sprite("res/tutorial/tut000.png")) this.tut.add(create_sprite("res/tutorial/tut0.png")) this.tut.add(create_sprite("res/tutorial/tut00.png")) @@ -29,10 +30,13 @@ method init(this: Level1Scene) = this.tut[4].center_position = vec2f(176 * 20.0, 29 * 20.0) this.tut[5].center_position = vec2f(207 * 20.0, 13 * 20.0) + for tut in mitems(this.tut): + tut.clear_fx = false + method update(this: Level1Scene) = - this.level.update() - return + if this.level.update(): + goto_scene(CutScene1) method render(this: Level1Scene) = this.level.draw() for tut in this.tut: diff --git a/src/game/scenes/level2.nim b/src/game/scenes/level2.nim index e69de29..f646d32 100644 --- a/src/game/scenes/level2.nim +++ b/src/game/scenes/level2.nim @@ -0,0 +1,46 @@ +include ../../engine/base +import ../../engine/map/map_loader +import ../../engine/graphics/sprite +import ../entities/player +import ../entities/physical_object +import ../../engine/base/renderer as rnd +import level + +type Level2Scene* = ref object of Scene + music: WavHandle + level: Level + tut: seq[Sprite] + open: bool + cable: Line + +method init(this: Level2Scene) = + this.music = load_sound("res/level1/music.mp3") + discard play_sound(this.music, true) + this.level.init("res/level2/map.yaml", "res/level2/backdrop.png", "res/level2/backdrop_fx.png", 20) + let points = @[vec2f(92.0 * 20.0 + 5.0, 75.0 * 20.0 + 15.0), vec2f(98.0 * 20.0, 75.0 * 20.0 + 15.0), + vec2f(98.0 * 20.0, 59.0 * 20.0)] + this.cable = create_line(points, 8.0) + this.cable.color = vec4f(0.1, 0.1, 0.1, 0.7) + + this.tut.add(create_sprite("res/level2/tutorial00.png")) + this.tut[0].center_position = vec2f(99 * 20.0, 54 * 20.0) + for tut in mitems(this.tut): + tut.clear_fx = false + + + +method update(this: Level2Scene) = + discard this.level.update() + 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[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 diff --git a/src/game/userdata.nim b/src/game/userdata.nim index 90ff8b0..9ac1876 100644 --- a/src/game/userdata.nim +++ b/src/game/userdata.nim @@ -4,7 +4,8 @@ type bkPlayer, bkEnemy, bkObject, - bkBarrier + bkBarrier, + bkPlatform UserData* = ref object kind*: BodyKind @@ -33,4 +34,9 @@ proc make_object_userdata*(obj: int): UserData = proc make_barrier_userdata*(obj: int): UserData = result = new(UserData) result.kind = bkBarrier + result.point = obj + +proc make_platform_userdata*(obj: int): UserData = + result = new(UserData) + result.kind = bkPlatform result.point = obj \ No newline at end of file diff --git a/src/main.nim b/src/main.nim index f6e1c54..da73500 100644 --- a/src/main.nim +++ b/src/main.nim @@ -1,10 +1,12 @@ import nimgl/[glfw] #import game/scenes/intro -import game/scenes/level1 +#import game/scenes/level1 +#import game/scenes/cutsc1 +import game/scenes/level2 include engine/base -goto_scene(Level1Scene()) +goto_scene(Level2Scene()) proc update() =