197 lines
6.0 KiB
Odin
197 lines
6.0 KiB
Odin
package game
|
|
|
|
import "core:math"
|
|
import rl "vendor:raylib"
|
|
|
|
Tile_Type :: enum {
|
|
// TEMP light gray
|
|
FLOOR,
|
|
// TEMP DARK gray
|
|
WALL,
|
|
// TEMP gray
|
|
LOW_WALL,
|
|
// TREMP GREEN
|
|
PICKUP,
|
|
}
|
|
|
|
Tile_Def :: struct {
|
|
solid: bool,
|
|
collider_offset_x, collider_offset_y: f32,
|
|
collider_w, collider_h: f32
|
|
}
|
|
|
|
// awesome odin sets the fixed size to the ammount of enum options
|
|
tile_defs: [Tile_Type]Tile_Def = {
|
|
.FLOOR = { solid = false },
|
|
.PICKUP = { solid = false },
|
|
.WALL = {
|
|
solid = true,
|
|
collider_offset_x = 0, collider_offset_y = 0,
|
|
collider_w = TILE_SIZE, collider_h = TILE_SIZE
|
|
},
|
|
.LOW_WALL = {
|
|
solid = true,
|
|
collider_offset_x = 0, collider_offset_y = TILE_SIZE/2,
|
|
collider_w = TILE_SIZE, collider_h = TILE_SIZE/2
|
|
},
|
|
}
|
|
|
|
Game_Map :: struct {
|
|
tile_size: int,
|
|
width: int,
|
|
height: int,
|
|
width_px: f32,
|
|
height_px: f32,
|
|
data: [400]int
|
|
}
|
|
|
|
Grid_Pos :: [2]int
|
|
|
|
map_init :: proc() -> Game_Map {
|
|
// @TODO: store maps and load them later
|
|
// NOTE: flat, row-major — data[row * width + col]
|
|
map_data := [400]int{
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 1,
|
|
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
|
|
1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
|
|
1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
}
|
|
|
|
game := Game_Map{
|
|
width = 20,
|
|
height = 20,
|
|
tile_size = TILE_SIZE,
|
|
data = map_data,
|
|
}
|
|
|
|
game.width_px = f32(game.width * game.tile_size)
|
|
game.height_px = f32(game.height * game.tile_size)
|
|
|
|
return game
|
|
}
|
|
|
|
// requires raw window-space
|
|
// i.e mouse position clicked in window get that space in screen
|
|
// returns screen-space pos, scaled to game resolution.
|
|
window_to_screen :: proc(window_pos: rl.Vector2) -> rl.Vector2 {
|
|
// window_width / game_width will give the scale back
|
|
// invert it to scale down back to the render size
|
|
screen_x := window_pos.x / (WINDOW_WIDTH / GAME_WIDTH)
|
|
screen_y := window_pos.y / (WINDOW_HEIGHT / GAME_HEIGHT)
|
|
return rl.Vector2{screen_x, screen_y}
|
|
}
|
|
|
|
// requires screen-space pos i.e mouse pos, scaled from window -> render size
|
|
// returns world-space position (accounts for camera and its zoom)
|
|
// remember uesd with raw input coordinates, not something already in world space (like player.pos).
|
|
screen_to_world :: proc(screen_pos: rl.Vector2, camera: rl.Camera2D) -> rl.Vector2 {
|
|
return rl.GetScreenToWorld2D(screen_pos, camera)
|
|
}
|
|
|
|
// @TODO: Do I need this?? Maybe for debug view??
|
|
world_to_screen :: proc(world_pos: rl.Vector2, camera: rl.Camera2D) -> rl.Vector2 {
|
|
return rl.GetWorldToScreen2D(world_pos, camera)
|
|
}
|
|
|
|
// requires world-space position i.e player.pos, or output of screen_to_world
|
|
// returns grid col/row not considering camera
|
|
world_to_grid :: proc(world_pos: rl.Vector2) -> Grid_Pos {
|
|
col := int(math.floor(world_pos.x / TILE_SIZE))
|
|
row := int(math.floor(world_pos.y / TILE_SIZE))
|
|
return Grid_Pos{col, row}
|
|
}
|
|
|
|
// returns world-space position grid pos top-left corner opposite of world_to_grid.
|
|
grid_to_world :: proc(col: int, row: int) -> rl.Vector2 {
|
|
world_x := f32(col * TILE_SIZE)
|
|
world_y := f32(row * TILE_SIZE)
|
|
return rl.Vector2{world_x, world_y}
|
|
}
|
|
|
|
// requires world x/y and then gives the rectangle in pixels that matches that
|
|
get_world_tile_rect :: proc(gx, gy, offset_x, offset_y, w, h: f32) -> rl.Rectangle {
|
|
return rl.Rectangle {
|
|
(gx * TILE_SIZE) + offset_x,
|
|
(gy * TILE_SIZE) + offset_y,
|
|
w, h
|
|
}
|
|
}
|
|
|
|
// Simple check for now
|
|
is_tile_solid :: proc(tile_type: Tile_Type) -> bool {
|
|
return tile_type == .WALL
|
|
}
|
|
|
|
// takes in the next position/rect for check compares it
|
|
check_map_collision :: proc(rect: rl.Rectangle, game_map: Game_Map) -> bool {
|
|
|
|
// collider top left point
|
|
min_grid := world_to_grid(rl.Vector2{rect.x, rect.y})
|
|
// collider bot right point
|
|
max_grid := world_to_grid(rl.Vector2{rect.x + rect.width, rect.y + rect.height})
|
|
|
|
// check all points along the x axis per y axis 'row'
|
|
for gy := min_grid[1]; gy <= max_grid[1]; gy += 1 {
|
|
for gx := min_grid[0]; gx <= max_grid[0]; gx += 1 {
|
|
idx := (gy * game_map.width) + gx
|
|
tile := Tile_Type(game_map.data[idx])
|
|
def := tile_defs[tile]
|
|
|
|
if def.solid {
|
|
tile_rect := get_world_tile_rect(
|
|
f32(gx), f32(gy),
|
|
def.collider_offset_x, def.collider_offset_y,
|
|
def.collider_w, def.collider_h
|
|
);
|
|
|
|
if rl.CheckCollisionRecs(rect, tile_rect) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
draw_map :: proc(current_map: Game_Map) {
|
|
for value, index in current_map.data {
|
|
row := index / current_map.width
|
|
column := index % current_map.width
|
|
|
|
tile := Tile_Type(value)
|
|
color: rl.Color
|
|
rect := grid_to_world(column, row)
|
|
|
|
// base floor color, draw full tile first to avoid background bleeding
|
|
rl.DrawRectangle(i32(rect.x), i32(rect.y), TILE_SIZE, TILE_SIZE, rl.LIGHTGRAY)
|
|
|
|
// anything that isn't plain floor draws on top of the base
|
|
#partial switch tile {
|
|
case .WALL:
|
|
rl.DrawRectangle(i32(rect.x), i32(rect.y), TILE_SIZE, TILE_SIZE, rl.DARKGRAY)
|
|
case .LOW_WALL:
|
|
rl.DrawRectangle(i32(rect.x), i32(rect.y + TILE_SIZE / 2), TILE_SIZE, TILE_SIZE / 2, rl.GRAY)
|
|
case .PICKUP:
|
|
rl.DrawRectangle(i32(rect.x), i32(rect.y), TILE_SIZE, TILE_SIZE, rl.GREEN)
|
|
}
|
|
}
|
|
}
|