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) } } }