diff options
| author | Xander Swan <[hidden email]> | 2026-01-01 18:32:20 -0500 |
|---|---|---|
| committer | Xander Swan <[hidden email]> | 2026-01-01 18:32:20 -0500 |
| commit | 65a2ceda55198dab3bab75c06c0d50adeb0a1101 (patch) | |
| tree | 2a08400520f60fcd86b381bc840bb5c739ff998a /src | |
| parent | dc373b507ca68ada2cbf4c8e0d7949d6bc46ed9b (diff) | |
lots of changes + player dash
Diffstat (limited to 'src')
| -rw-r--r-- | src/draw/draw.odin | 4 | ||||
| -rw-r--r-- | src/input.odin | 2 | ||||
| -rw-r--r-- | src/main.odin | 86 | ||||
| -rw-r--r-- | src/phys/world.odin | 18 | ||||
| -rw-r--r-- | src/platform.odin | 33 | ||||
| -rw-r--r-- | src/player.odin | 180 | ||||
| -rw-r--r-- | src/tiled/tiled.odin | 59 | ||||
| -rw-r--r-- | src/tiled/world.odin | 84 |
8 files changed, 343 insertions, 123 deletions
diff --git a/src/draw/draw.odin b/src/draw/draw.odin index 3e80d42..122e6fa 100644 --- a/src/draw/draw.odin +++ b/src/draw/draw.odin @@ -73,9 +73,7 @@ end_frame :: proc() { rl.EndDrawing() } -rect :: proc( - rect: Rect, -) { +rect :: proc(rect: Rect) { assert(rect.size.x > 0 && rect.size.y > 0) rl.DrawRectangle( diff --git a/src/input.odin b/src/input.odin index de2af7e..475be92 100644 --- a/src/input.odin +++ b/src/input.odin @@ -17,12 +17,14 @@ actions: struct { move_left: Keybind, move_right: Keybind, jump: Keybind, + dash: Keybind, } init_keybinds :: proc() { actions.move_left.input = .A actions.move_right.input = .D actions.jump.input = .SPACE + actions.dash.input = .LEFT_SHIFT } is_keybind_down :: proc(keybind: Keybind) -> bool { diff --git a/src/main.odin b/src/main.odin index ff794f6..3a90917 100644 --- a/src/main.odin +++ b/src/main.odin @@ -24,55 +24,44 @@ Rect :: struct { } state: struct { - player: Player, + camera: rl.Camera2D, platform_list: Entity_List(Platform), - - room: tiled.Map, } init :: proc() { + state.camera.zoom = 1 + // state.camera.offset = {draw.SCREEN_WIDTH/2, draw.SCREEN_HEIGHT/2} + init_keybinds() draw.init() - init_player(&state.player) + init_player() - room, err := tiled.load_map("res/room_begin.tmj") - if err != .NONE { - log.errorf("Error loading map (%v)", err) - } - state.room = room + // room, err := tiled.load_map("res/room_begin.tmj") + // if err != .NONE { + // log.errorf("Error loading map (%v)", err) + // } + // state.room = room - make_map_collisions :: proc( - x: i32, - y: i32, - tile: tiled.Tile, - tile_set: tiled.Tile_Set, - ) { - objs, is_object_layer := tile.object_group.layer.(tiled.Object_Layer) - if !is_object_layer { - return - } + tiled.load_world("res/map.world") + tiled.open_new_room_at({0, 0}) - for obj in objs { - make_platform({ - Vec2{f32(x), f32(y)} + obj.position, - obj.size, - }) - } - } - - tiled.iterate_map_tiles(state.room, make_map_collisions) + setup_map_collisions() } frame :: proc() { - update_player(&state.player, rl.GetFrameTime()) + update_player(rl.GetFrameTime()) draw.new_frame() + + rl.BeginMode2D(state.camera) + + draw_player() + tiled.draw_map(tiled.current_room.tmap) + draw_platforms() - draw_player(state.player) - tiled.draw_map(state.room) - // draw_platforms() + rl.EndMode2D() fps_text := fmt.caprintf( "FPS: %v", rl.GetFPS(), @@ -84,27 +73,27 @@ frame :: proc() { } cleanup :: proc() { - delete_player(state.player) + delete_player() delete_entity_list(state.platform_list) - tiled.delete_map(state.room) + tiled.delete_world() phys.destroy_world() } main :: proc() { - when ODIN_DEBUG { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) + when ODIN_DEBUG { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) - defer { - if len(track.allocation_map) > 0 { - fmt.eprintf( + defer { + if len(track.allocation_map) > 0 { + fmt.eprintf( "=== %v allocations not freed: ===\n", len(track.allocation_map), ) total := 0 - for _, entry in track.allocation_map { - fmt.eprintf( + for _, entry in track.allocation_map { + fmt.eprintf( "- %v bytes @ %v (%v, mode %v)\n", entry.size, entry.location, @@ -112,12 +101,12 @@ main :: proc() { entry.mode, ) total += entry.size - } + } fmt.eprintf("=== a total of %v bytes unfreed ===\n", total) - } - mem.tracking_allocator_destroy(&track) - } + } + mem.tracking_allocator_destroy(&track) + } } logger := log.create_console_logger() @@ -131,6 +120,9 @@ main :: proc() { init() for !rl.WindowShouldClose() { + // pbody := phys.get_body(player.body_handle) + // state.camera.target = {pbody.pos.x, pbody.pos.y} + frame() free_all(context.temp_allocator) diff --git a/src/phys/world.odin b/src/phys/world.odin index 8470872..51a85a5 100644 --- a/src/phys/world.odin +++ b/src/phys/world.odin @@ -68,11 +68,11 @@ find_bin :: proc(b: Body) -> ^[dynamic]Body_Handle { } @(private="file") -add_to_bins :: proc(b: Body) { - bin := find_bin(b) +add_to_bins :: proc(b: ^Body) { + bin := find_bin(b^) idx := i32(len(bin)) append(bin, b.handle) - world.bodies[b.handle].bin_idx = idx + b.bin_idx = idx } @(private="file") @@ -84,8 +84,10 @@ remove_from_bins :: proc(b: Body) { last := pop(bin) if last != b.handle { + // log.debug(last, b.handle) + last_body := get_body(last) bin[b.bin_idx] = last - world.bodies[last].bin_idx = b.bin_idx + last_body.bin_idx = b.bin_idx } } @@ -113,7 +115,7 @@ add_body :: proc(b: Body) -> (Body_Handle, ^Body) { body := &world.bodies[world.handles[handle]] body.handle = handle - add_to_bins(body^) + add_to_bins(body) return handle, body } @@ -125,11 +127,11 @@ remove_body :: proc(h: Body_Handle) { last := pop(&world.bodies) if last.handle != h { - world.bodies[h] = last + world.bodies[world.handles[h]] = last world.handles[last.handle] = world.handles[h] } - append(&world.unused_handles, b.handle) + append(&world.unused_handles, h) } update_body :: proc(h: Body_Handle) { @@ -207,7 +209,7 @@ set_body_position :: proc(h: Body_Handle, new_pos: Vec2) { if prev_bin != res_bin { remove_from_bins(b^) b.pos = new_pos - add_to_bins(b^) + add_to_bins(b) } else { b.pos = new_pos } diff --git a/src/platform.odin b/src/platform.odin index 306df3b..bc0e103 100644 --- a/src/platform.odin +++ b/src/platform.odin @@ -2,6 +2,7 @@ package demonchime import "draw" import "phys" +import "tiled" Platform :: struct { handle: Entity_Handle, @@ -24,3 +25,35 @@ draw_platforms :: proc() { } draw.renderer.tint = {1, 1, 1, 1} } + +clear_platforms :: proc() { + iter := iter_entity_list(state.platform_list) + for p in entity_list_iter(&iter) { + phys.remove_body(p.body) + delete_entity(&state.platform_list, p.handle) + } +} + +setup_map_collisions :: proc() { + clear_platforms() + + make_map_collisions :: proc( + x: i32, + y: i32, + tile: tiled.Tile, + tile_set: tiled.Tile_Set, + ) { + objs, is_object_layer := tile.object_group.layer.(tiled.Object_Layer) + if !is_object_layer { + return + } + + for obj in objs { + make_platform({ + Vec2{f32(x), f32(y)} + obj.position, + obj.size, + }) + } + } + tiled.iterate_map_tiles(tiled.current_room.tmap, make_map_collisions) +} diff --git a/src/player.odin b/src/player.odin index 5d7ffb4..5c001da 100644 --- a/src/player.odin +++ b/src/player.odin @@ -7,25 +7,42 @@ import rl "vendor:raylib" import "draw" import "phys" +import "tiled" PLAYER_SPEED :: 100 PLAYER_ACCEL :: 10 PLAYER_JUMP_FORCE :: 350 -JUMP_BUFFERING :: 0.07 -COYOTE_TIME :: 0.05 -JUMP_RELEASE_CUT :: -100 +PLAYER_JUMP_BUFFERING :: 0.07 +PLAYER_COYOTE_TIME :: 0.05 +PLAYER_JUMP_RELEASE_CUT :: -100 -Player :: struct { +PLAYER_DASH_SPEED :: 500 +PLAYER_DASH_TIME :: 0.15 +PLAYER_DASH_COOLDOWN :: 0.3 + +Player_State :: enum { + DEFAULT, + DASH, +} + +// there will only ever be one player, so just make it a global :) +player: struct { body_handle: phys.Body_Handle, anim: draw.Animation, sprite: draw.Sprite, jump_buffer: f32, coyote_time: f32, + + dash_cooldown: f32, + dash_timer: f32, + + state: Player_State, + outside_room: bool, } -init_player :: proc(p: ^Player) { - anim_ok := draw.init_anim_data(&p.anim, "res/player.json") +init_player :: proc() { + anim_ok := draw.init_anim_data(&player.anim, "res/player.json") if !anim_ok { log.warn("could not load animation") rl.CloseWindow() @@ -33,28 +50,29 @@ init_player :: proc(p: ^Player) { } handle, body := phys.make_body( - phys.Rect{{8, 17}, {16, 16}}, + phys.Rect{{-4, 17}, {8, 16}}, ) - p.body_handle = handle + player.body_handle = handle phys.set_body_position(handle, Vec2{50, 100}) - draw.init_sprite(&p.sprite, p.anim.image_path, p.anim) + draw.init_sprite(&player.sprite, player.anim.image_path, player.anim) - p.sprite.offset = Vec2{ - -math.floor(f32(p.sprite.width / 2)), - -f32(p.sprite.height) + player.sprite.offset = Vec2{ + math.floor(f32(player.sprite.width / 2)), + -f32(player.sprite.height) } } -delete_player :: proc(p: Player) { - phys.remove_body(p.body_handle) +delete_player :: proc() { + phys.remove_body(player.body_handle) - draw.destroy_sprite(p.sprite) - draw.delete_anim_data(p.anim) + draw.destroy_sprite(player.sprite) + draw.delete_anim_data(player.anim) } -update_player :: proc(p: ^Player, dt: f32) { +@(private="file") +get_input_dir :: proc() -> f32 { input: f32 if is_keybind_down(actions.move_left) { @@ -64,33 +82,45 @@ update_player :: proc(p: ^Player, dt: f32) { input += 1 } - if is_keybind_just_down(actions.jump) { - p.jump_buffer = JUMP_BUFFERING - } + return input +} + +@(private="file") +default_state :: proc(dt: f32) { + input := get_input_dir() if input != 0 { - draw.set_sprite_active_tag(&p.sprite, "run") - p.sprite.scale.x = math.sign(input) + draw.set_sprite_active_tag(&player.sprite, "run") + player.sprite.scale.x = math.sign(input) } else { - draw.set_sprite_active_tag(&p.sprite, "idle") + draw.set_sprite_active_tag(&player.sprite, "idle") + } + + if is_keybind_just_down(actions.jump) { + player.jump_buffer = PLAYER_JUMP_BUFFERING + } + + if is_keybind_just_down(actions.dash) && player.dash_cooldown <= 0 { + enter_dash() + return } - body := phys.get_body(p.body_handle) - + body := phys.get_body(player.body_handle) + if .DOWN in body.collisions { - p.coyote_time = COYOTE_TIME + player.coyote_time = PLAYER_COYOTE_TIME } - if p.jump_buffer > 0 && p.coyote_time > 0 { - p.jump_buffer = 0 - p.coyote_time = 0 + if player.jump_buffer > 0 && player.coyote_time > 0 { + player.jump_buffer = 0 + player.coyote_time = 0 body.vel.y = -PLAYER_JUMP_FORCE } if .DOWN not_in body.collisions \ && !is_keybind_down(actions.jump) \ - && body.vel.y < JUMP_RELEASE_CUT { - body.vel.y = JUMP_RELEASE_CUT + && body.vel.y < PLAYER_JUMP_RELEASE_CUT { + body.vel.y = PLAYER_JUMP_RELEASE_CUT } body.vel.x = math.lerp( @@ -100,16 +130,90 @@ update_player :: proc(p: ^Player, dt: f32) { ) body.vel.y = math.min(body.vel.y + GRAVITY * dt, TERMINAL_VELOCITY) - phys.update_body(p.body_handle) + phys.update_body(player.body_handle) +} + +@(private="file") +enter_dash :: proc() { + body := phys.get_body(player.body_handle) + // the sprite x scale is the direction the player is facing :) + body.vel = {math.sign(player.sprite.scale.x) * PLAYER_DASH_SPEED, 0} + + player.dash_timer = PLAYER_DASH_TIME + player.state = .DASH +} + +@(private="file") +dash_state :: proc(dt: f32) { + phys.update_body(player.body_handle) + + player.dash_timer -= dt + body := phys.get_body(player.body_handle) + + if player.dash_timer <= 0 || body.collisions != {} { + exit_dash() + } +} + +exit_dash :: proc() { + player.state = .DEFAULT + + player.dash_cooldown = PLAYER_DASH_COOLDOWN + + body := phys.get_body(player.body_handle) + body.vel /= 2 +} + +@(private="file") +change_rooms :: proc() { + body := phys.get_body(player.body_handle) - p.sprite.pos = body.pos - draw.update_sprite(&p.sprite, dt) + width := f32(tiled.current_room.room.width) + height := f32(tiled.current_room.room.height) - p.jump_buffer -= dt - p.coyote_time -= dt + if body.pos.x < 0 || body.pos.x > width \ + || body.pos.y < 0 || body.pos.y > height { + prev_room_pos := [2]f32{ + f32(tiled.current_room.room.x), + f32(tiled.current_room.room.y), + } + changed := tiled.open_new_room_at({i32(body.pos.x), i32(body.pos.y)}) + + if changed { + new_room_pos := [2]f32{ + f32(tiled.current_room.room.x), + f32(tiled.current_room.room.y), + } + + diff := prev_room_pos - new_room_pos + new_pos := body.pos + diff - {0, 0} + phys.set_body_position(player.body_handle, new_pos) + + setup_map_collisions() + } + } } -draw_player :: proc(p: Player) { - draw.sprite(p.sprite) +update_player :: proc(dt: f32) { + switch player.state { + case .DEFAULT: + default_state(dt) + case .DASH: + dash_state(dt) + } + + body := phys.get_body(player.body_handle) + + player.sprite.pos = body.pos + draw.update_sprite(&player.sprite, dt) + + player.dash_cooldown -= dt + player.jump_buffer -= dt + player.coyote_time -= dt + + change_rooms() } +draw_player :: proc() { + draw.sprite(player.sprite) +} diff --git a/src/tiled/tiled.odin b/src/tiled/tiled.odin index 373715a..d84c296 100644 --- a/src/tiled/tiled.odin +++ b/src/tiled/tiled.odin @@ -183,7 +183,7 @@ load_map :: proc(path: string) -> (Map, Error) { return {}, .COULD_NOT_LOAD } - log.info(jmap) + // log.debug(jmap) tmap: Map err := convert_json_map(jmap, &tmap, path) @@ -356,8 +356,8 @@ iterate_map_tiles :: proc(tmap: Map, callback: Map_Iterate_Callback) { x: i32 = 0 y: i32 = 0 - for cell in tile_layer.data { - if cell != 0 { + for cell, i in tile_layer.data { + if cell > 0 { tile := tmap.tiles[cell - 1] tile_set := tmap.tile_sets[tile.tile_set] callback(x, y, tile, tile_set) @@ -372,7 +372,7 @@ iterate_map_tiles :: proc(tmap: Map, callback: Map_Iterate_Callback) { } } -draw_map :: proc(tmap: Map) { +test_cells :: proc(tmap: Map) { for layer in tmap.layers { tile_layer, ok := layer.layer.(Tile_Layer) if !ok { @@ -382,29 +382,9 @@ draw_map :: proc(tmap: Map) { x: i32 = 0 y: i32 = 0 - for cell in tile_layer.data { - if cell != 0 { - tile := tmap.tiles[cell - 1] - tile_set := tmap.tile_sets[tile.tile_set] - - rl.DrawTexturePro( - tile_set.texture, - rl.Rectangle{ - x = f32(tile.src_start.x), - y = f32(tile.src_start.y), - width = f32(tile.src_size.x), - height = f32(tile.src_size.y), - }, - rl.Rectangle{ - x = f32(x), - y = f32(y), - width = f32(tile.src_size.x), - height = f32(tile.src_size.y), - }, - {0, 0}, - 0, - rl.WHITE, - ) + for cell, i in tile_layer.data { + if cell > 0 { + log.debug(i, [2]i32{x, y}, cell) } x += tmap.tile_width @@ -416,6 +396,31 @@ draw_map :: proc(tmap: Map) { } } +draw_map :: proc(tmap: Map) { + draw_tile :: proc(x: i32, y: i32, tile: Tile, tile_set: Tile_Set) { + rl.DrawTexturePro( + tile_set.texture, + rl.Rectangle{ + x = f32(tile.src_start.x), + y = f32(tile.src_start.y), + width = f32(tile.src_size.x), + height = f32(tile.src_size.y), + }, + rl.Rectangle{ + x = f32(x), + y = f32(y), + width = f32(tile.src_size.x), + height = f32(tile.src_size.y), + }, + {0, 0}, + 0, + rl.WHITE, + ) + } + + iterate_map_tiles(tmap, draw_tile) +} + @(private) load_image :: proc(filename: string, res_dir: string) -> rl.Texture2D { path := strings.concatenate({res_dir, "/", filename}) diff --git a/src/tiled/world.odin b/src/tiled/world.odin new file mode 100644 index 0000000..c55097a --- /dev/null +++ b/src/tiled/world.odin @@ -0,0 +1,84 @@ +package tiled + +import os "core:os/os2" +import "core:log" +import "core:encoding/json" +import "core:path/filepath" +import "core:strings" + +World_Room :: struct { + file_name: string `json:"fileName"`, + x: i32, + y: i32, + width: i32, + height: i32, +} + +res_dir: string +world: []World_Room +current_room: struct { + tmap: Map, + using room: World_Room, +} + +load_world :: proc(path: string) -> bool { + world_text, read_err := os.read_entire_file(path, context.temp_allocator) + if read_err != nil { + log.errorf("Failed to read file %v (%v)", path, read_err) + return false + } + + jworld: struct { + maps: []World_Room, + } + unmarshal_err := json.unmarshal( + world_text, + &jworld, + ) + if unmarshal_err != nil { + log.errorf("Failed to unmarshal file %v (%v)", path, unmarshal_err) + return false + } + + world = jworld.maps + res_dir = filepath.dir(path) + + return true +} + +delete_world :: proc() { + delete_map(current_room.tmap) + delete(world) + delete(res_dir) +} + +open_new_room_at :: proc(pos: [2]i32) -> bool { + pos := pos + pos += {current_room.room.x, current_room.room.y} + + log.debug("trying to change room...", pos) + for room in world { + if strings.compare(room.file_name, current_room.room.file_name) == 0 { + continue + } + + if pos.x >= room.x && pos.x <= room.x + room.width \ + && pos.y >= room.y && pos.y <= room.y + room.height { + delete_map(current_room.tmap) + + path := strings.concatenate( + {res_dir, "/", room.file_name}, + allocator = context.temp_allocator, + ) + new_map, err := load_map(path) + if err != .NONE { + log.error("could not load new room") + return false + } + current_room.tmap = new_map + current_room.room = room + return true + } + } + return false +} |
