diff --git a/constants.odin b/constants.odin index a780aad..8d2da81 100644 --- a/constants.odin +++ b/constants.odin @@ -13,3 +13,7 @@ INVALID_ENTITY :: Entity(0) MOVE_SPEED :: 4.0 +MAX_PATH :: 512 + +NEIGHBOR_OFFSET :: [4]Tile_Coord{{0, -1}, {0, 1}, {1, 0}, {-1, 0}} + diff --git a/entity.odin b/entity.odin index 2f4f741..545125e 100644 --- a/entity.odin +++ b/entity.odin @@ -14,6 +14,8 @@ Entity_World :: struct { move_state: [MAX_ENTITIES]Move_State, move_target: [MAX_ENTITIES]Tile_Coord, visual_position: [MAX_ENTITIES]rl.Vector2, + path: [MAX_ENTITIES]Path, + path_index: [MAX_ENTITIES]int, } Move_State :: enum { @@ -68,17 +70,29 @@ tile_center_world :: proc(t: Tile_Coord) -> rl.Vector2 { } } -entity_set_move_target :: proc(ew: ^Entity_World, e: Entity, target: Tile_Coord) -> bool { +entity_set_move_target :: proc( + ew: ^Entity_World, + e: Entity, + tm: ^Tilemap, + target: Tile_Coord, +) -> bool { i := entity_index(e) - if i < 0 || !ew.active[i] {return false} - - if ew.position[i] == target { - ew.move_state[i] = .Idle - return true + if i < 0 || !ew.active[i] { + return false + } + + start := ew.position[i] + if !pathfind_bfs(tm, start, target, &ew.path[i]) { + return false + } + ew.path_index[i] = 1 + if ew.path[i].count <= 1 { + ew.move_state[i] = .Idle + } else { + ew.move_state[i] = .Moving + ew.move_target[i] = target } - ew.move_target[i] = target - ew.move_state[i] = .Moving return true } @@ -97,13 +111,15 @@ entity_update_movement :: proc(ew: ^Entity_World, dt: f32) { continue } - next := pos - if pos.x != target.x { - next.x += pos.x < target.x ? 1 : -1 - } else if pos.y != target.y { - next.y += pos.y < target.y ? 1 : -1 + p := &ew.path[i] + + if ew.path_index[i] >= p.count { + ew.move_state[i] = .Idle + continue } + next := p.tiles[ew.path_index[i]] + goal := tile_center_world(next) vp := ew.visual_position[i] @@ -114,11 +130,22 @@ entity_update_movement :: proc(ew: ^Entity_World, dt: f32) { if dist < 1.0 { ew.position[i] = next ew.visual_position[i] = goal + ew.path_index[i] += 1 + + if ew.path_index[i] >= p.count { + ew.move_state[i] = .Idle + } } else { step := speed * dt if step >= dist { ew.visual_position[i] = goal ew.position[i] = next + ew.path_index[i] += 1 + + if ew.path_index[i] >= p.count { + ew.move_state[i] = .Idle + } + } else { ew.visual_position[i].x += dx / dist * step ew.visual_position[i].y += dy / dist * step diff --git a/game.odin b/game.odin index be8f502..936f631 100644 --- a/game.odin +++ b/game.odin @@ -107,7 +107,7 @@ game_handle_input :: proc(g: ^Game) { wx, wy := screen_to_world(&g.camera, mx, my) tx, ty := world_to_tile(wx, wy) if tile_in_bounds(&g.tilemap, tx, ty) { - entity_set_move_target(&g.world.entities, g.player_colonist, {tx, ty}) + entity_set_move_target(&g.world.entities, g.player_colonist, &g.tilemap, {tx, ty}) g.selection.active = true g.selection.tile = {tx, ty} } diff --git a/pathfinding.odin b/pathfinding.odin new file mode 100644 index 0000000..a013d9b --- /dev/null +++ b/pathfinding.odin @@ -0,0 +1,93 @@ +package main + +Path :: struct { + tiles: []Tile_Coord, + count: int, +} + +path_clear :: proc(path: ^Path) { + delete(path.tiles) + path.tiles = nil + path.count = 0 +} + +pathfind_bfs :: proc(tilemap: ^Tilemap, start, goal: Tile_Coord, path: ^Path) -> bool { + path_clear(path) + + if start == goal { + path.tiles[0] = start + path.count = 1 + return true + } + + w, h := tilemap.width, tilemap.height + + parents: [MAP_HEIGHT][MAP_WIDTH]Tile_Coord + visited: [MAP_HEIGHT][MAP_WIDTH]bool + + found := false + + for y in 0 ..< h { + for x in 0 ..< w { + parents[y][x] = {-1, -1} + } + } + + queue: [MAP_WIDTH * MAP_HEIGHT]Tile_Coord + head, tail: int = 0, 0 + + queue[tail] = start; tail += 1 + visited[start.y][start.x] = true + parents[start.y][start.x] = start + + for head < tail { + current := queue[head]; head += 1 + + if current == goal { + found = true + break + } + + for offset in NEIGHBOR_OFFSET { + nx := current.x + offset.x + ny := current.y + offset.y + if !tile_in_bounds(tilemap, nx, ny) || + visited[ny][nx] || + !tile_walkable(tilemap, nx, ny) { + continue + } + + visited[ny][nx] = true + parents[ny][nx] = current + queue[tail] = {nx, ny} + tail += 1 + } + } + + if !found { + return false + } + + rev: [MAX_PATH]Tile_Coord + rev_count := 0 + cur := goal + for { + rev[rev_count] = cur + rev_count += 1 + if cur == start { + break + } + cur = parents[cur.y][cur.x] + if rev_count >= MAX_PATH { + return false + } + } + + path.tiles = make([]Tile_Coord, rev_count) + path.count = rev_count + for i in 0 ..< rev_count { + path.tiles[i] = rev[rev_count - 1 - i] + } + return true +} + diff --git a/tilemap.odin b/tilemap.odin index e7494e8..62a999b 100644 --- a/tilemap.odin +++ b/tilemap.odin @@ -9,11 +9,16 @@ Tilemap :: struct { } tilemap_init :: proc() -> Tilemap { - return Tilemap { - width = MAP_WIDTH, + tm := Tilemap { + width = MAP_WIDTH, height = MAP_HEIGHT, - tiles = make([]u8, MAP_WIDTH * MAP_HEIGHT), + tiles = make([]u8, MAP_WIDTH * MAP_HEIGHT), } + // test wall + for x in 15 ..< 25 { + tm.tiles[tile_index(&tm, x, 12)] = 1 + } + return tm } tilemap_destroy :: proc(tilemap: ^Tilemap) { @@ -74,3 +79,11 @@ tilemap_draw :: proc(tilemap: ^Tilemap, camera: ^Camera2D) { } } + +tile_walkable :: proc(tilemap: ^Tilemap, x, y: int) -> bool { + if !tile_in_bounds(tilemap, x, y) {return false} + idx := tile_index(tilemap, x, y) + + return tilemap.tiles[idx] != 1 +} +