why are the tiles still 24bytes...

This commit is contained in:
Chris Bell 2025-02-26 18:06:51 -06:00
parent 22f9af5417
commit f7a409f565
31 changed files with 484 additions and 95 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
game/game

Binary file not shown.

View File

@ -11,30 +11,32 @@ import "core:strings"
player : Player
world : World
camera : rl.Camera2D
main :: proc() {
rl.InitWindow(1280, 720, "Odin game")
flags : rl.ConfigFlags = {.VSYNC_HINT}
rl.SetConfigFlags(flags)
rl.SetTargetFPS(60)
player.position.x = CELL_SIZE * 5
player.position.y = CELL_SIZE * 5
player.mode = .INTERACT
camera.target = {player.position.x + (CELL_SIZE / 2), player.position.y + (CELL_SIZE / 2)}
camera.zoom = 2
camera.offset = {f32(rl.GetScreenWidth()) / 2, f32(rl.GetScreenHeight()) / 2}
player = {
position = {CELL_SIZE * 5, CELL_SIZE * 5},
camera = {
zoom = 2,
target = {player.position.x + (CELL_SIZE / 2), player.position.y + (CELL_SIZE / 2)},
offset = {f32(rl.GetScreenWidth()) / 2, f32(rl.GetScreenHeight()) / 2},
},
mode = .INTERACT,
}
load_tilemap()
defer unload_tilemap()
fill_world_grid_with_nothing(&world)
world = create_world("test_world")
load_nearby_chunks(&world, player.position)
save_world(&world)
place_random_trees(&world)
game_loop()
}
@ -50,7 +52,7 @@ game_loop :: proc() {
rl.BeginDrawing()
rl.ClearBackground(rl.BLACK)
rl.BeginMode2D(camera)
rl.BeginMode2D(player.camera)
draw()
@ -59,7 +61,7 @@ game_loop :: proc() {
rl.DrawFPS(5,5)
player_grid_pos := get_player_grid_position(&player)
player_grid_pos_tile := get_grid_tile(&world, vec2_to_vec2i(player_grid_pos))
player_grid_pos_tile := get_world_tile(&world, vec2_to_vec2i(player_grid_pos))
status_string := rl.TextFormat("POS: %v : %v | MODE: %v", player_grid_pos, player_grid_pos_tile.type, player.mode)
rl.DrawText(status_string, 5, 25, 20, rl.RED)
@ -73,11 +75,7 @@ game_loop :: proc() {
}
update :: proc() {
handle_player_input(&player, &world)
handle_window_resize()
camera.target = {player.position.x + (CELL_SIZE / 2), player.position.y + (CELL_SIZE / 2)}
player_update(&player, &world)
}
draw :: proc() {
@ -85,16 +83,3 @@ draw :: proc() {
draw_player(&player)
}
handle_window_resize :: proc() {
if rl.IsWindowResized() {
camera.offset = {f32(rl.GetScreenWidth()) / 2, f32(rl.GetScreenHeight()) / 2}
}
}
print_grid :: proc() {
for x in 0..< len(world.grid) {
for y in 0..< len(world.grid) {
fmt.printfln("[%d, %d] %v", x, y, world.grid[x][y].type)
}
}
}

View File

@ -1,8 +1,8 @@
package game
Vec2i :: struct {
x: u32,
y:u32,
x: int,
y: int,
}
vec2i_to_vec2 :: proc(v2i:Vec2i) -> [2]f32 {
@ -10,5 +10,43 @@ vec2i_to_vec2 :: proc(v2i:Vec2i) -> [2]f32 {
}
vec2_to_vec2i :: proc(v2:[2]f32) -> Vec2i {
return {u32(v2.x), u32(v2.y)}
return {int(v2.x), int(v2.y)}
}
serialize_vec2i :: proc(v:Vec2i) -> [8]byte {
data: [8]byte
x := serialize_int(v.x)
y := serialize_int(v.y)
for i in 0..<4 {
data[i] = x[i]
}
for i in 0..<4 {
data[4 + i] = y[i]
}
return data
}
deserialize_vec2i :: proc(data:[]byte) -> Vec2i {
x := deserialize_int(data[0:4])
y := deserialize_int(data[4:8])
return Vec2i{x,y}
}
serialize_int :: proc(v:int) -> [4]byte {
data : [4]byte
data[0] = byte(v)
data[1] = byte(v >> 8)
data[2] = byte(v >> 16)
data[3] = byte(v >> 24)
return data
}
deserialize_int :: proc(data:[]byte) -> int {
return int(data[0]) | int(data[1]) << 8 | int(data[2]) << 16 | int(data[3]) << 24
}

View File

@ -3,10 +3,13 @@ package game
import rl "vendor:raylib"
import "core:fmt"
CHUNK_UNLOAD_DISTANCE :: 3
Player :: struct {
position : rl.Vector2,
move_timer: f32,
mode: InteractMode
mode: InteractMode,
camera: rl.Camera2D
}
InteractMode :: enum {
@ -14,8 +17,46 @@ InteractMode :: enum {
ATTACK,
}
handle_player_input :: proc(p : ^Player, w: ^World) {
handle_player_camera :: proc(p:^Player) {
p.camera.target = {p.position.x + (CELL_SIZE / 2), p.position.y + (CELL_SIZE / 2)}
if rl.IsWindowResized() {
p.camera.offset = {f32(rl.GetScreenWidth()) / 2, f32(rl.GetScreenHeight()) / 2}
}
}
player_update :: proc(p : ^Player, w: ^World) {
handle_player_input(p,w)
handle_player_camera(p)
}
load_nearby_chunks :: proc(w:^World, player_pos:rl.Vector2) {
player_chunk_pos := world_pos_to_chunk_pos(player_pos)
chunk_radius := 2 // Adjust based on the camera size
for x := -chunk_radius; x <= chunk_radius; x += 1 {
for y := -chunk_radius; y <= chunk_radius; y += 1 {
chunk_pos := Vec2i{player_chunk_pos.x + x, player_chunk_pos.y + y}
get_chunk(w, chunk_pos) // Ensures chunk is loaded or generated
}
}
}
unload_far_chunks :: proc(w: ^World, player_pos: Vec2i) {
for chunk_pos in w.chunks {
dist_x := abs(chunk_pos.x - player_pos.x)
dist_y := abs(chunk_pos.y - player_pos.y)
if dist_x > CHUNK_UNLOAD_DISTANCE || dist_y > CHUNK_UNLOAD_DISTANCE {
unload_chunk(chunk_pos, w)
}
}
}
handle_player_input :: proc(p:^Player, w:^World) {
target_pos := get_player_grid_position(p)
dt := rl.GetFrameTime()
@ -31,33 +72,41 @@ handle_player_input :: proc(p : ^Player, w: ^World) {
if p.move_timer <= 0 {
if rl.IsKeyDown(.D) {
target_pos.x += 1
if !will_collide(target_pos, w) {
if !will_collide(w, target_pos) {
player.position.x += CELL_SIZE
p.move_timer = move_delay
load_nearby_chunks(w, p.position)
unload_far_chunks(w, vec2_to_vec2i(p.position))
}
}
if rl.IsKeyDown(.A) {
target_pos.x -= 1
if !will_collide(target_pos, w) {
if !will_collide(w, target_pos) {
player.position.x -= CELL_SIZE
p.move_timer = move_delay
load_nearby_chunks(w, p.position)
unload_far_chunks(w, vec2_to_vec2i(p.position))
}
}
if rl.IsKeyDown(.W) {
target_pos.y -= 1
if !will_collide(target_pos, w) {
if !will_collide(w, target_pos) {
player.position.y -= CELL_SIZE
p.move_timer = move_delay
load_nearby_chunks(w, p.position)
unload_far_chunks(w, vec2_to_vec2i(p.position))
}
}
if rl.IsKeyDown(.S) {
target_pos.y += 1
if !will_collide(target_pos, w) {
if !will_collide(w, target_pos) {
p.move_timer = move_delay
player.position.y += CELL_SIZE
load_nearby_chunks(w, p.position)
unload_far_chunks(w, vec2_to_vec2i(p.position))
}
}
}
@ -74,12 +123,13 @@ draw_player :: proc(player:^Player) {
draw_tile({27,0}, player.position, rl.DARKBLUE)
}
will_collide :: proc(pos:rl.Vector2, w:^World) -> bool {
if pos.y > WORLD_SIZE * CELL_SIZE || pos.x > WORLD_SIZE * CELL_SIZE {
return false
}
will_collide :: proc(w:^World, pos:rl.Vector2) -> bool {
world_grid_pos := vec2_to_vec2i(pos)
chunk_pos := world_pos_to_chunk_pos(pos)
local_pos := get_local_chunk_pos(world_grid_pos)
tile := get_grid_tile(w, vec2_to_vec2i(pos))
chunk := get_chunk(w, chunk_pos)
tile := get_chunk_tile(chunk, local_pos)
#partial switch tile.type {
case .WALL:

View File

@ -3,21 +3,132 @@ package game
import rl "vendor:raylib"
import "core:math/rand"
Tile :: struct #packed {
tilemap_pos: Vec2i,
color: rl.Color,
type: TileType,
interaction: InteractionType,
resource: ResourceType,
}
TileType :: enum {
NOTHING,
WALL,
FOLIAGE,
}
ResourceType :: enum {
NOTHING,
TREE,
}
InteractionType :: enum {
NOTHING,
RESOURCE,
ENEMY,
}
serialize_tile :: proc(t: Tile) -> []byte {
data: [dynamic]byte
// Tilemap Pos (16 bytes)
tilemap_pos_bytes := serialize_vec2i(t.tilemap_pos)
append(&data, ..tilemap_pos_bytes[:])
// Color (4 bytes)
append(&data, byte(t.color.r), byte(t.color.g), byte(t.color.b), byte(t.color.a))
// TileType (8 bytes)
tile_type_bytes := serialize_int(int(t.type))
append(&data, ..tile_type_bytes[:])
// Interaction Type (8 bytes)
interaction_bytes := serialize_int(int(t.interaction))
append(&data, ..interaction_bytes[:])
// Resource Type (8 bytes)
resource_bytes := serialize_int(int(t.resource))
append(&data, ..resource_bytes[:])
return data[:]
}
deserialize_tile :: proc(data: []byte) -> Tile {
t: Tile
// Tilemap Pos (16 bytes)
t.tilemap_pos = deserialize_vec2i(data[0:16])
// Color (4 bytes)
t.color.r = u8(data[16])
t.color.g = u8(data[17])
t.color.b = u8(data[18])
t.color.a = u8(data[19])
// TileType (8 bytes)
t.type = TileType(deserialize_int(data[20:28]))
// Interaction Type (8 bytes)
t.interaction = InteractionType(deserialize_int(data[28:36]))
// Resource Type (8 bytes)
t.resource = ResourceType(deserialize_int(data[36:44]))
return t
}
// serialize_tile :: proc(t: Tile) -> []byte {
// data: [dynamic]byte
// // TileType
// tile_type_bytes := serialize_int(int(t.type))
// append(&data, ..tile_type_bytes[:])
// // Tilemap Pos
// tilemap_pos_bytes := serialize_vec2i(t.tilemap_pos)
// append(&data, ..tilemap_pos_bytes[:])
// // Color
// append(&data, byte(t.color.r), byte(t.color.g), byte(t.color.b), byte(t.color.a))
// // Interaction Type
// interaction_bytes := serialize_int(int(t.interaction))
// append(&data, ..interaction_bytes[:])
// // Resource Type
// resource_bytes := serialize_int(int(t.resource))
// append(&data, ..resource_bytes[:])
// return data[:]
// }
// deserialize_tile :: proc(data:[]byte) -> Tile {
// t: Tile
// // TileType
// t.type = TileType(deserialize_int(data[0:4]))
// // TilemapPos
// t.tilemap_pos = deserialize_vec2i(data[4:12])
// // Color
// t.color.r = u8(data[12])
// t.color.g = u8(data[13])
// t.color.b = u8(data[14])
// t.color.a = u8(data[15])
// // ResourceType
// t.resource = ResourceType(deserialize_int(data[16:20]))
// // InteractionType
// t.interaction = InteractionType(deserialize_int(data[20:24]))
// return t
// }
tree_tile := Tile {
type = .WALL,
tilemap_pos = {0,1},
color = rl.DARKGREEN
}
place_random_trees :: proc(w:^World) {
for x in 0..< len(w.grid) {
for y in 0..< len(w.grid) {
chance := rand.int_max(100)
if chance <= 5 {
w.grid[x][y] = tree_tile
}
}
}
color = rl.DARKGREEN,
resource = .TREE,
interaction = .RESOURCE,
}

View File

@ -2,61 +2,266 @@ package game
import rl "vendor:raylib"
import "core:fmt"
import "core:os"
import "core:path/filepath"
CELL_SIZE :: 16
WORLD_SIZE :: 100
CHUNK_SIZE :: 32
WORLD_DATA_PATH :: "data/worlds"
World :: struct {
grid: [WORLD_SIZE][WORLD_SIZE]Tile
data_dir: string,
chunks: map[Vec2i]Chunk
}
Tile :: struct {
type: TileType,
tilemap_pos:rl.Vector2,
color:rl.Color,
Chunk :: struct #packed {
position: Vec2i,
tiles: [CHUNK_SIZE][CHUNK_SIZE]Tile,
}
TileType :: enum {
NOTHING,
WALL,
DOOR,
FLOOR,
}
set_grid_tile :: proc(w:^World, pos:Vec2i, t:Tile) {
w.grid[pos.x][pos.y] = t
}
get_grid_tile :: proc(w: ^World, pos: Vec2i) -> Tile {
if pos.x < 0 || pos.x >= len(w.grid) || pos.y < 0 || pos.y >= len(w.grid[0]) {
// fmt.printfln("Target [%v] outside of world bounds", pos)
return w.grid[0][0] // Default or error tile
create_world :: proc(name:string) -> World {
data_dir := fmt.tprintf("%v/%v", WORLD_DATA_PATH, name)
if !os.is_dir(data_dir) {
fmt.printfln("Data dir: %v does not exist", data_dir)
os.make_directory(data_dir)
}
chunk_dir := fmt.tprintf("%v/%v", data_dir, "chunks")
if !os.is_dir(chunk_dir) {
os.make_directory(chunk_dir)
}
return World {
data_dir = data_dir,
chunks = make(map[Vec2i]Chunk),
}
return w.grid[pos.x][pos.y]
}
fill_world_grid_with_nothing :: proc(w:^World) {
for x in 0..< len(w.grid) {
for y in 0..<len(w.grid) {
w.grid[x][y] = Tile {
type = .NOTHING,
tilemap_pos = {0,0}
load_world :: proc(name:string) -> World {
dir := fmt.tprintf("%v/%v", WORLD_DATA_PATH, name)
if !os.is_dir(dir) {
panic("Couldnt load world")
}
return World {
data_dir = dir,
chunks = make(map[Vec2i]Chunk),
}
}
save_world :: proc(w:^World) {
if !os.is_dir(w.data_dir) {
panic("World has invalid data_path")
}
fmt.printfln("Saving world %v", w.data_dir)
for chunk in w.chunks {
save_chunk(get_chunk(w, chunk), w)
}
}
serialize_chunk :: proc(c:^Chunk) -> []byte {
data : [dynamic]byte
// Position
pos_bytes := serialize_vec2i(c.position)
append(&data, ..pos_bytes[:])
// Tiles
for y in 0..<CHUNK_SIZE {
for x in 0..<CHUNK_SIZE {
tile_bytes := serialize_tile(c.tiles[x][y])
fmt.printfln("Serialized tile size: %v", len(tile_bytes))
append(&data, ..tile_bytes[:])
}
}
fmt.printfln("Serialized chunk size: %v", len(data)) // Print final serialized size
fmt.printfln("Before serialization, Chunk position: %v", c.position)
return data[:]
}
deserialize_chunk :: proc(data: []byte) -> Chunk {
chunk: Chunk
// Position
chunk.position = deserialize_vec2i(data[0:8])
// Tiles
offset := 8
tile_size := size_of(Tile)
// Check total expected size of data
expected_size := offset + (CHUNK_SIZE * CHUNK_SIZE * tile_size)
if len(data) != expected_size {
fmt.printfln("Error: Data size mismatch. Expected size: %v, Actual size: %v", expected_size, len(data))
fmt.printfln("Chunk position: %v", chunk.position)
return chunk // Return empty or invalid chunk to handle the error.
}
for y in 0..<CHUNK_SIZE {
for x in 0..<CHUNK_SIZE {
start := offset + (y * CHUNK_SIZE + x) * tile_size
end := start + tile_size
fmt.printfln("Start:%v, End:%v", start, end)
if end > len(data) {
fmt.printfln("Out of bounds access. Start:%v, End:%v | Data Length: %v", start, end, len(data))
break
}
chunk.tiles[x][y] = deserialize_tile(data[start:end])
}
}
fmt.printfln("Expected chunk size: %v | Serialized chunk data size: %v", size_of(Chunk), len(data))
return chunk
}
save_chunk :: proc(c:^Chunk, w:^World) {
chunk_dir := fmt.tprintf("%v/%v", w.data_dir, "chunks")
if !os.is_dir(chunk_dir) {
os.make_directory(chunk_dir)
}
filename := fmt.tprintf("%v/%v_%v.chunk", chunk_dir, c.position.x, c.position.y)
data := serialize_chunk(c)
fmt.printfln("Writing chunk data: %v bytes", len(data))
err := os.write_entire_file_or_err(filename, data)
if err != nil {
fmt.printfln("Failed to save chunk: %v", err)
}
}
load_chunk :: proc(pos:Vec2i, w:^World) -> Chunk {
chunk_dir := fmt.tprintf("%v/%v", w.data_dir, "chunks")
if !os.is_dir(chunk_dir) {
}
filename := fmt.tprintf("%v/%v_%v.chunk", chunk_dir, pos.x, pos.y)
data, err := os.read_entire_file_from_filename_or_err(filename)
if err != nil {
fmt.printfln("Chunk %v file does not exist, creating new chunk", pos)
return generate_chunk(pos)
}
return deserialize_chunk(data)
}
unload_chunk :: proc(pos:Vec2i, w:^World) {
_, exists := w.chunks[pos]
if exists {
save_chunk(get_chunk(w, pos), w)
delete_key(&w.chunks, pos)
}
}
generate_chunk :: proc(pos:Vec2i) -> Chunk {
chunk := Chunk {position = pos}
for x in 0..<CHUNK_SIZE {
for y in 0..<CHUNK_SIZE {
chunk.tiles[x][y] = Tile {
type = .FOLIAGE,
tilemap_pos = {0,2},
interaction = .NOTHING,
resource = .NOTHING,
color = rl.GREEN
}
}
}
center_pos := Vec2i{CHUNK_SIZE/2, CHUNK_SIZE/2}
// set_chunk_tile(&chunk, tree_tile, center_pos)
return chunk
}
get_chunk :: proc(w:^World, chunk_pos:Vec2i) -> ^Chunk {
chunk, exists := w.chunks[chunk_pos]
if !exists {
w.chunks[chunk_pos] = load_chunk(chunk_pos, w)
}
return &w.chunks[chunk_pos]
}
get_chunk_from_world_pos :: proc(w:^World, pos:rl.Vector2) -> ^Chunk {
chunk_pos := world_pos_to_chunk_pos(pos)
return get_chunk(w, chunk_pos)
}
world_pos_to_chunk_pos :: proc(pos:rl.Vector2) -> Vec2i {
chunk_pos := vec2_to_vec2i({pos.x / CHUNK_SIZE, pos.y / CHUNK_SIZE})
return chunk_pos
}
get_local_chunk_pos :: proc(pos:Vec2i) -> Vec2i {
return Vec2i {
(pos.x % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE,
(pos.y % CHUNK_SIZE + CHUNK_SIZE) % CHUNK_SIZE,
}
}
get_world_tile :: proc(w:^World, pos:Vec2i) -> ^Tile {
chunk_pos := world_pos_to_chunk_pos(vec2i_to_vec2(pos))
local_pos := get_local_chunk_pos(pos)
chunk := get_chunk(w, chunk_pos)
return get_chunk_tile(chunk, local_pos)
}
get_chunk_tile :: proc(c:^Chunk, pos:Vec2i) -> ^Tile {
return &c.tiles[pos.x][pos.y]
}
set_chunk_tile :: proc(c:^Chunk, t:Tile, pos:Vec2i) {
c.tiles[pos.x][pos.y] = t
}
draw_world :: proc(w:^World) {
for x in 0..< len(w.grid) {
for y in 0..< len(w.grid) {
tile := w.grid[x][y]
posX := x * TILE_SIZE
posY := y * TILE_SIZE
for chunk_pos, chunk in w.chunks {
for x in 0..<CHUNK_SIZE {
for y in 0..<CHUNK_SIZE {
tile := chunk.tiles[x][y]
world_x := chunk_pos.x * CHUNK_SIZE + x
world_y := chunk_pos.y * CHUNK_SIZE + y
pos := rl.Vector2{f32(world_x * CELL_SIZE), f32(world_y * CELL_SIZE)}
if tile.type != .NOTHING {
draw_tile(tile.tilemap_pos, {f32(posX), f32(posY)}, tile.color)
if tile.type != .NOTHING {
draw_tile(vec2i_to_vec2(tile.tilemap_pos), pos, tile.color)
}
}
}
}
}
// draw_world :: proc(w:^World) {
// for x in 0..< len(w.grid) {
// for y in 0..< len(w.grid) {
// tile := w.grid[x][y]
// posX := x * TILE_SIZE
// posY := y * TILE_SIZE
// if tile.type != .NOTHING {
// draw_tile(tile.tilemap_pos, {f32(posX), f32(posY)}, tile.color)
// }
// }
// }
// }