aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/aabb.odin13
-rw-r--r--src/main.odin50
-rw-r--r--src/phys/body.odin65
-rw-r--r--src/phys/world.odin205
-rw-r--r--src/platform.odin9
-rw-r--r--src/player.odin91
6 files changed, 361 insertions, 72 deletions
diff --git a/src/aabb.odin b/src/aabb.odin
deleted file mode 100644
index 9637ec9..0000000
--- a/src/aabb.odin
+++ /dev/null
@@ -1,13 +0,0 @@
-package demonchime
-
-aabb_hori :: proc(a: Rect, b: Rect) -> bool {
- return a.start.x < b.start.x + b.size.x && b.start.x < a.start.x + a.size.x
-}
-
-aabb_vert :: proc(a: Rect, b: Rect) -> bool {
- return a.start.y < b.start.y + b.size.y && b.start.y < a.start.y + a.size.y
-}
-
-aabb :: proc(a: Rect, b: Rect) -> bool {
- return aabb_hori(a, b) && aabb_vert(a, b)
-}
diff --git a/src/main.odin b/src/main.odin
index 9a9c91c..48bf454 100644
--- a/src/main.odin
+++ b/src/main.odin
@@ -15,6 +15,7 @@ import stime "shared:sokol/time"
import slog "shared:sokol/log"
import "draw"
+import "phys"
Vec2 :: [2]f32
@@ -29,6 +30,7 @@ state: struct {
platform_list: Entity_List(Platform),
renderer: draw.Renderer,
+ physics_world: phys.World,
input: Input,
}
@@ -59,23 +61,61 @@ init :: proc "c" () {
make_platform(Rect{
start = {50, 50},
- size = {100, 20}
+ size = {64, 20}
+ })
+
+ i := f32(20)
+ for i < draw.SCREEN_WIDTH - 20 - 64 {
+ make_platform(Rect{
+ start = {i, 340},
+ size = {64, 20}
+ })
+ i += 65
+ }
+ // make_platform(Rect{
+ // start = {i, 340},
+ // size = {20, 20}
+ // })
+
+ make_platform(Rect{
+ start = {160, 320},
+ size = {20, 20}
})
make_platform(Rect{
- start = {20, 340},
- size = {440, 20}
+ start = {240, 280},
+ size = {40, 20}
})
make_platform(Rect{
- start = {140, 320},
+ start = {260, 260},
size = {20, 20}
})
make_platform(Rect{
- start = {240, 280},
+ start = {300, 220},
+ size = {40, 20}
+ })
+
+ make_platform(Rect{
+ start = {240, 180},
+ size = {40, 20}
+ })
+
+ make_platform(Rect{
+ start = {200, 140},
+ size = {40, 20}
+ })
+
+ make_platform(Rect{
+ start = {180, 100},
size = {20, 20}
})
+
+ make_platform(Rect{
+ start = {140, 100},
+ size = {16, 16}
+ })
}
frame :: proc "c" () {
diff --git a/src/phys/body.odin b/src/phys/body.odin
new file mode 100644
index 0000000..fcae697
--- /dev/null
+++ b/src/phys/body.odin
@@ -0,0 +1,65 @@
+package phys
+
+Vec2 :: [2]f32
+
+Rect :: struct {
+ start: Vec2,
+ size: Vec2,
+}
+
+Layer :: enum(u16) {
+ DEFAULT,
+ HARD, // hard collisions; don't let bodies intersect at all
+ SOFT, // soft collisions; push away other bodies with a force
+ ENEMY, // enemy hitboxes
+ PLAYER, // player hitboxes
+}
+
+Collision_Type :: enum(u16) {
+ UP,
+ DOWN,
+ RIGHT,
+ LEFT,
+ HORIZONTAL,
+ VERTICAL,
+}
+
+Body :: struct {
+ handle: Body_Handle,
+ bin_idx: i32,
+ rect: Rect,
+ active: bool,
+ pos: Vec2,
+ vel: Vec2,
+ collisions: bit_set[Collision_Type; u16],
+ layers: bit_set[Layer; u16],
+ mask: bit_set[Layer; u16],
+}
+
+make_body :: proc(
+ w: ^World,
+ rect: Rect,
+ layers := bit_set[Layer; u16]{.DEFAULT},
+ mask := bit_set[Layer; u16]{.DEFAULT},
+) -> (Body_Handle, ^Body) {
+ b := Body {
+ rect = rect,
+ layers = layers,
+ mask = mask,
+ active = true,
+ }
+ return add_body(w, b)
+}
+
+aabb_hori :: proc(a: Rect, b: Rect) -> bool {
+ return a.start.x < b.start.x + b.size.x && b.start.x < a.start.x + a.size.x
+}
+
+aabb_vert :: proc(a: Rect, b: Rect) -> bool {
+ return a.start.y < b.start.y + b.size.y && b.start.y < a.start.y + a.size.y
+}
+
+aabb :: proc(a: Rect, b: Rect) -> bool {
+ return aabb_hori(a, b) && aabb_vert(a, b)
+}
+
diff --git a/src/phys/world.odin b/src/phys/world.odin
new file mode 100644
index 0000000..eac3d5c
--- /dev/null
+++ b/src/phys/world.odin
@@ -0,0 +1,205 @@
+package phys
+
+import "core:log"
+import "core:math"
+
+import sapp "shared:sokol/app"
+
+BIN_COUNT :: 2056
+BIN_SIZE :: 64
+
+Body_Handle :: u32
+
+World :: struct {
+ handles: [dynamic]u32,
+ unused_handles: [dynamic]Body_Handle,
+ bodies: [dynamic]Body,
+ bins: [BIN_COUNT][dynamic]Body_Handle,
+}
+
+destroy_world :: proc(w: World) {
+ for bin in w.bins {
+ delete(bin)
+ }
+}
+
+@(private="file")
+hash_bin :: proc(x: i32, y: i32) -> u32 {
+ return transmute(u32)((x * 73856093) ~ (y * 19349663))
+}
+
+@(private="file")
+world_to_bin :: proc(point: Vec2) -> (i32, i32) {
+ return \
+ i32(math.floor(point.x / BIN_SIZE)),
+ i32(math.floor(point.y / BIN_SIZE))
+}
+
+@(private="file")
+get_surrounding_bins :: proc(
+ w: ^World,
+ pos: Vec2,
+ allocator := context.temp_allocator,
+) -> []^[dynamic]Body_Handle {
+ neighbors := make([]^[dynamic]Body_Handle, 9, allocator)
+
+ center_x, center_y := world_to_bin(pos)
+
+ idx := 0
+
+ for offset_x in -1..=1 {
+ for offset_y in -1..=1 {
+ bin_idx := hash_bin(center_x + i32(offset_x), center_y + i32(offset_y))
+ bin := &w.bins[bin_idx % BIN_COUNT]
+ neighbors[idx] = bin
+ idx += 1
+ }
+ }
+
+ return neighbors
+}
+
+@(private="file")
+find_bin :: proc(w: ^World, b: Body) -> ^[dynamic]Body_Handle {
+ bin_x, bin_y := world_to_bin(b.pos + b.rect.start)
+ return &w.bins[hash_bin(bin_x, bin_y) % BIN_COUNT]
+}
+
+@(private="file")
+add_to_bins :: proc(w: ^World, b: Body) {
+ bin := find_bin(w, b)
+ idx := i32(len(bin))
+ append(bin, b.handle)
+ w.bodies[bin[idx]].bin_idx = idx
+}
+
+@(private="file")
+remove_from_bins :: proc(w: ^World, b: Body) {
+ bin := find_bin(w, b)
+
+ assert(bin[b.bin_idx] == b.handle)
+
+ last := pop(bin)
+ if last != b.handle {
+ bin[b.bin_idx] = last
+ w.bodies[last].bin_idx = b.bin_idx
+ }
+}
+
+get_body :: proc(w: World, h: Body_Handle) -> ^Body {
+ return &w.bodies[w.handles[h]]
+}
+
+add_body :: proc(w: ^World, b: Body) -> (Body_Handle, ^Body) {
+ handle: Body_Handle
+
+ if b.rect.size.x > BIN_SIZE || b.rect.size.y > BIN_SIZE {
+ log.warnf("Body size is too big (%vx%v)", b.rect.size.x, b.rect.size.y)
+ }
+
+ if len(w.unused_handles) > 0 {
+ handle = pop(&w.unused_handles)
+ } else {
+ handle = cast(Body_Handle)len(w.handles)
+ append(&w.handles, 0)
+ }
+
+ w.handles[handle] = u32(len(w.bodies))
+ append(&w.bodies, b)
+
+ body := &w.bodies[w.handles[handle]]
+ body.handle = handle
+
+ add_to_bins(w, body^)
+
+ return handle, body
+}
+
+remove_body :: proc(w: ^World, h: Body_Handle) {
+ b := get_body(w^, h)
+
+ remove_from_bins(w, b^)
+
+ last := pop(&w.bodies)
+ if last.handle != h {
+ w.bodies[h] = last
+ w.handles[last.handle] = w.handles[h]
+ }
+
+ append(&w.unused_handles, b.handle)
+}
+
+update_body :: proc(w: ^World, h: Body_Handle) {
+ dt := f32(sapp.frame_duration())
+
+ b := get_body(w^, h)
+
+ res_pos := b.pos + b.vel * dt
+
+ rect := b.rect
+ rect.start += b.pos
+
+ res_rect := b.rect
+ res_rect.start += res_pos
+
+ bin_list := get_surrounding_bins(w, res_rect.start)
+
+ b.collisions = {}
+
+ total_len := 0
+
+ for bin in bin_list {
+ total_len += len(bin)
+
+ for c_h in bin {
+ if c_h == h {
+ continue
+ }
+
+ c := get_body(w^, c_h)
+
+ c_rect := c.rect
+ c_rect.start += c.pos
+
+ if aabb(res_rect, c_rect) {
+ if aabb_hori(rect, c_rect) {
+ if b.vel.y > 0 {
+ res_pos.y = c_rect.start.y - b.rect.size.y - b.rect.start.y
+ b.collisions += {.DOWN}
+ } else {
+ res_pos.y = c_rect.start.y + c_rect.size.y - b.rect.start.y
+ b.collisions += {.UP}
+ }
+ b.collisions += {.VERTICAL}
+ } else if aabb_vert(rect, c.rect) {
+ if b.vel.x > 0 {
+ res_pos.x = c_rect.start.x - b.rect.size.x - b.rect.start.x
+ b.collisions += {.LEFT}
+ } else {
+ res_pos.x = c_rect.start.x + c_rect.size.x - b.rect.start.x
+ b.collisions += {.RIGHT}
+ }
+ b.collisions += {.HORIZONTAL}
+ }
+ }
+ }
+ }
+
+ if .HORIZONTAL in b.collisions {
+ b.vel.x = 0
+ }
+ if .VERTICAL in b.collisions {
+ b.vel.y = 0
+ }
+
+ b.pos = res_pos
+
+ prev_bin := hash_bin(world_to_bin(b.pos + b.rect.start))
+ res_bin := hash_bin(world_to_bin(res_pos + b.rect.start))
+
+ if prev_bin != res_bin {
+ remove_from_bins(w, b^)
+ add_to_bins(w, b^)
+ }
+}
+
diff --git a/src/platform.odin b/src/platform.odin
index b837355..150d96c 100644
--- a/src/platform.odin
+++ b/src/platform.odin
@@ -1,21 +1,24 @@
package demonchime
import "draw"
+import "phys"
Platform :: struct {
handle: Entity_Handle,
- rect: Rect,
+ body: phys.Body_Handle,
}
make_platform :: proc(rect: Rect) -> (Entity_Handle, ^Platform) {
+ handle, body := phys.make_body(&state.physics_world, transmute(phys.Rect)rect)
return make_entity(&state.platform_list, Platform {
- rect = rect,
+ body = handle,
})
}
draw_platforms :: proc() {
iter := iter_entity_list(state.platform_list)
for p in entity_list_iter(&iter) {
- draw.rect(&state.renderer, cast(draw.Rect)p.rect)
+ body := phys.get_body(state.physics_world, p.body)
+ draw.rect(&state.renderer, cast(draw.Rect)body.rect)
}
}
diff --git a/src/player.odin b/src/player.odin
index 1dd79ae..f204126 100644
--- a/src/player.odin
+++ b/src/player.odin
@@ -6,22 +6,25 @@ import "core:math"
import sapp "shared:sokol/app"
import "draw"
+import "phys"
Player :: struct {
- pos: Vec2,
- vel: Vec2,
+ body_handle: phys.Body_Handle,
anim: draw.Animation,
sprite: draw.Sprite,
- on_floor: bool
+
+ jump_buffer: f32,
+ cyote_time: f32,
}
PLAYER_SPEED :: 100
PLAYER_ACCEL :: 10
PLAYER_JUMP_FORCE :: 350
-init_player :: proc(p: ^Player) {
- p.pos = Vec2{50, 50}
+JUMP_BUFFERING :: 0.07
+CYOTE_TIME :: 0.05
+init_player :: proc(p: ^Player) {
anim_ok := draw.init_anim_data(&p.anim, "res/robot2.json")
if !anim_ok {
fmt.println("coult not load animation")
@@ -29,6 +32,14 @@ init_player :: proc(p: ^Player) {
return
}
+ handle, body := phys.make_body(
+ &state.physics_world,
+ phys.Rect{{-8, -16}, {16, 16}},
+ )
+ p.body_handle = handle
+
+ body.pos = Vec2{50, 50}
+
draw.init_sprite(&p.sprite, p.anim.image_path, p.anim)
p.sprite.offset = Vec2{
@@ -38,6 +49,8 @@ init_player :: proc(p: ^Player) {
}
deinit_player :: proc(p: ^Player) {
+ phys.remove_body(&state.physics_world, p.body_handle)
+
draw.delete_anim_data(p.anim)
}
@@ -51,6 +64,10 @@ update_player :: proc(p: ^Player, dt: f32) {
input += 1
}
+ if is_keybind_down(state.input, state.input.jump) {
+ p.jump_buffer = JUMP_BUFFERING
+ }
+
if input != 0 {
draw.set_sprite_active_tag(&p.sprite, "down_run")
p.sprite.scale.x = math.sign(input)
@@ -58,60 +75,32 @@ update_player :: proc(p: ^Player, dt: f32) {
draw.set_sprite_active_tag(&p.sprite, "down_idle")
}
- if is_keybind_down(state.input, state.input.jump) && p.on_floor {
- p.vel.y = -PLAYER_JUMP_FORCE
+ body := phys.get_body(state.physics_world, p.body_handle)
+
+ if .DOWN in body.collisions {
+ p.cyote_time = CYOTE_TIME
+ }
+
+ if p.jump_buffer > 0 && p.cyote_time > 0 {
+ p.jump_buffer = 0
+ p.cyote_time = 0
+ body.vel.y = -PLAYER_JUMP_FORCE
}
- p.vel.x = math.lerp(
- p.vel.x,
+ body.vel.x = math.lerp(
+ body.vel.x,
input * PLAYER_SPEED,
math.pow(0.5, dt * PLAYER_ACCEL),
)
- p.vel.y = math.min(p.vel.y + GRAVITY * dt, TERMINAL_VELOCITY)
-
- res_pos := p.pos + p.vel * dt
-
- rect_size := Vec2{f32(p.sprite.width), f32(p.sprite.height)}
- rect_offset := Vec2{-rect_size.x / 2, -rect_size.y}
-
- rect := Rect{
- res_pos + rect_offset,
- rect_size,
- }
+ body.vel.y = math.min(body.vel.y + GRAVITY * dt, TERMINAL_VELOCITY)
- p.on_floor = false
-
- iter := iter_entity_list(state.platform_list)
- for plat in entity_list_iter(&iter) {
- if aabb(rect, plat.rect) {
- prev_rect := Rect{
- p.pos + rect_offset,
- rect_size,
- }
- if aabb_hori(prev_rect, plat.rect) {
- if p.vel.y > 0 {
- res_pos.y = plat.rect.start.y - rect.size.y - rect_offset.y
- p.on_floor = true
- } else {
- res_pos.y = plat.rect.start.y + plat.rect.size.y - rect_offset.y
- }
- p.vel.y = 0
- } else if aabb_vert(prev_rect, plat.rect) {
- if p.vel.x > 0 {
- res_pos.x = plat.rect.start.x - rect.size.x - rect_offset.x
- } else {
- res_pos.x = plat.rect.start.x + plat.rect.size.x - rect_offset.x
- }
- p.vel.x = 0
- }
- }
- }
-
- p.pos = res_pos
-
- p.sprite.pos = p.pos
+ phys.update_body(&state.physics_world, p.body_handle)
+ p.sprite.pos = body.pos
draw.update_sprite(&p.sprite, dt)
+
+ p.jump_buffer -= dt
+ p.cyote_time -= dt
}
draw_player :: proc(p: Player) {