Chunking system, save/load chunks #1

Merged
chrisbell merged 8 commits from chunking into master 2025-02-28 14:40:37 +00:00
31 changed files with 115 additions and 208 deletions
Showing only changes of commit cddba06c25 - Show all commits

File diff suppressed because one or more lines are too long

BIN
game/game

Binary file not shown.

View File

@ -1,8 +1,8 @@
package game package game
Vec2i :: struct { Vec2i :: struct {
x: i32, x: int,
y: i32, y: int,
} }
vec2i_to_vec2 :: proc(v2i:Vec2i) -> [2]f32 { vec2i_to_vec2 :: proc(v2i:Vec2i) -> [2]f32 {
@ -10,7 +10,7 @@ vec2i_to_vec2 :: proc(v2i:Vec2i) -> [2]f32 {
} }
vec2_to_vec2i :: proc(v2:[2]f32) -> Vec2i { vec2_to_vec2i :: proc(v2:[2]f32) -> Vec2i {
return {i32(v2.x), i32(v2.y)} return {int(v2.x), int(v2.y)}
} }
to_bytes :: proc(v: $T) -> [size_of(T)]u8 { to_bytes :: proc(v: $T) -> [size_of(T)]u8 {

View File

@ -29,31 +29,34 @@ player_update :: proc(p : ^Player, w: ^World) {
handle_player_input(p,w) handle_player_input(p,w)
handle_player_camera(p) handle_player_camera(p)
if rl.IsKeyPressed(.SPACE) {
set_tile(w, tree_tile, vec2_to_vec2i(get_player_grid_position(p)))
}
} }
load_nearby_chunks :: proc(w:^World, player_pos:rl.Vector2) { load_nearby_chunks :: proc(w:^World, player_pos:rl.Vector2) {
player_chunk_pos := world_pos_to_chunk_pos(player_pos) // player_chunk_pos := world_pos_to_chunk_pos(player_pos)
chunk_radius := 2 // Adjust based on the camera size // chunk_radius := 2 // Adjust based on the camera size
for x := -chunk_radius; x <= chunk_radius; x += 1 { // for x := -chunk_radius; x <= chunk_radius; x += 1 {
for y := -chunk_radius; y <= chunk_radius; y += 1 { // for y := -chunk_radius; y <= chunk_radius; y += 1 {
chunk_pos := Vec2i{player_chunk_pos.x + x, player_chunk_pos.y + y} // chunk_pos := Vec2i{player_chunk_pos.x + x, player_chunk_pos.y + y}
get_chunk(w, chunk_pos) // Ensures chunk is loaded or generated // get_chunk(w, chunk_pos) // Ensures chunk is loaded or generated
} // }
} // }
} }
unload_far_chunks :: proc(w: ^World, player_pos: Vec2i) { unload_far_chunks :: proc(w: ^World, player_pos: Vec2i) {
for chunk_pos in w.chunks { // for chunk_pos in w.chunks {
dist_x := abs(chunk_pos.x - player_pos.x) // dist_x := abs(chunk_pos.x - player_pos.x)
dist_y := abs(chunk_pos.y - player_pos.y) // dist_y := abs(chunk_pos.y - player_pos.y)
if dist_x > CHUNK_UNLOAD_DISTANCE || dist_y > CHUNK_UNLOAD_DISTANCE { // if dist_x > CHUNK_UNLOAD_DISTANCE || dist_y > CHUNK_UNLOAD_DISTANCE {
unload_chunk(chunk_pos, w) // unload_chunk(chunk_pos, w)
} // }
} // }
} }
handle_player_input :: proc(p:^Player, w:^World) { handle_player_input :: proc(p:^Player, w:^World) {

View File

@ -5,130 +5,33 @@ import "core:math/rand"
Tile :: struct #packed { Tile :: struct #packed {
tilemap_pos: Vec2i, tilemap_pos: Vec2i,
color: rl.Color, color: [4]u8,
type: TileType, type: TileType,
interaction: InteractionType, interaction: InteractionType,
resource: ResourceType, resource: ResourceType,
} }
TileType :: enum { TileType :: enum u8 {
NOTHING, NOTHING,
WALL, WALL,
FOLIAGE, FOLIAGE,
} }
ResourceType :: enum { ResourceType :: enum u8 {
NOTHING, NOTHING,
TREE, TREE,
} }
InteractionType :: enum { InteractionType :: enum u8 {
NOTHING, NOTHING,
RESOURCE, RESOURCE,
ENEMY, 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 { tree_tile := Tile {
type = .WALL, type = .WALL,
tilemap_pos = {0,1}, tilemap_pos = {0,1},
color = rl.DARKGREEN, color = {17,87,30,255},
resource = .TREE, resource = .TREE,
interaction = .RESOURCE, interaction = .RESOURCE,
} }

View File

@ -4,9 +4,10 @@ import rl "vendor:raylib"
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
import "core:path/filepath" import "core:path/filepath"
import "core:mem"
CELL_SIZE :: 16 CELL_SIZE :: 16
CHUNK_SIZE :: 32 CHUNK_SIZE :: 5
WORLD_DATA_PATH :: "data/worlds" WORLD_DATA_PATH :: "data/worlds"
World :: struct { World :: struct {
@ -61,105 +62,97 @@ save_world :: proc(w:^World) {
} }
} }
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) { save_chunk :: proc(c:^Chunk, w:^World) {
chunk_dir := fmt.tprintf("%v/%v", w.data_dir, "chunks") 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) filename := fmt.tprintf("%v/%v_%v.chunk", chunk_dir, c.position.x, c.position.y)
data := serialize_chunk(c) fmt.printfln("Saving chunk: %v", filename)
fmt.printfln("Writing chunk data: %v bytes", len(data)) data := make([dynamic]u8)
// Append Position
for byte in transmute([size_of(int)]u8)c.position.x {append(&data, byte)}
for byte in transmute([size_of(int)]u8)c.position.y {append(&data, byte)}
err := os.write_entire_file_or_err(filename, data) // Append Tiles
for row in &c.tiles {
if err != nil { for tile in row {
fmt.printfln("Failed to save chunk: %v", err) for byte in transmute([size_of(int)]u8)tile.tilemap_pos.x {append(&data, byte)}
for byte in transmute([size_of(int)]u8)tile.tilemap_pos.y {append(&data, byte)}
for byte in transmute([4]u8)tile.color {append(&data, byte)}
for byte in transmute([size_of(TileType)]u8)tile.type {append(&data, byte)}
for byte in transmute([size_of(InteractionType)]u8)tile.interaction {append(&data, byte)}
for byte in transmute([size_of(ResourceType)]u8)tile.resource {append(&data, byte)}
}
} }
err := os.write_entire_file_or_err(filename, data[:])
} }
load_chunk :: proc(pos:Vec2i, w:^World) -> Chunk { load_chunk :: proc(pos:Vec2i, w:^World) -> Chunk {
chunk_dir := fmt.tprintf("%v/%v", w.data_dir, "chunks") 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) filename := fmt.tprintf("%v/%v_%v.chunk", chunk_dir, pos.x, pos.y)
data, err := os.read_entire_file_from_filename_or_err(filename) data, err := os.read_entire_file_from_filename_or_err(filename)
if err != nil { if err != nil {
fmt.printfln("Chunk %v file does not exist, creating new chunk", pos) fmt.printfln("No chunk %v found, generating new chunk", pos)
return generate_chunk(pos) chunk := generate_chunk(pos)
save_chunk(&chunk, w)
return chunk
} }
return deserialize_chunk(data) chunk: Chunk
offset := 0
// Load Position
mem.copy(transmute([^]u8)&chunk.position.x, &data[offset], size_of(int))
offset += size_of(int)
mem.copy(transmute([^]u8)&chunk.position.y, &data[offset], size_of(int))
offset += size_of(int)
// // Load Tiles
// for row in &chunk.tiles {
// for &tile in row {
// mem.copy(transmute([^]u8)tile.tilemap_pos.x, &data[offset], size_of(int))
// offset += size_of(int)
// mem.copy(transmute([^]u8)tile.tilemap_pos.y, &data[offset], size_of(int))
// offset += size_of(int)
// //Color
// tile.color = {}
//
// }
// }
// Load tiles
for row_index := 0; row_index < len(chunk.tiles); row_index += 1 {
for tile_index := 0; tile_index < len(chunk.tiles[row_index]); tile_index += 1 {
tile := &chunk.tiles[row_index][tile_index] // Get address of tile
mem.copy(&tile.tilemap_pos.x, &data[offset], size_of(int))
offset += size_of(int)
mem.copy(&tile.tilemap_pos.y, &data[offset], size_of(int))
offset += size_of(int)
// Load color
color_temp: [4]u8
mem.copy(&color_temp, &data[offset], 4)
tile.color = color_temp
offset += 4
mem.copy(&tile.type, &data[offset], size_of(TileType))
offset += size_of(TileType)
mem.copy(&tile.interaction, &data[offset], size_of(InteractionType))
offset += size_of(InteractionType)
mem.copy(&tile.resource, &data[offset], size_of(ResourceType))
offset += size_of(ResourceType)
}
}
return chunk
} }
unload_chunk :: proc(pos:Vec2i, w:^World) { unload_chunk :: proc(pos:Vec2i, w:^World) {
@ -170,7 +163,6 @@ unload_chunk :: proc(pos:Vec2i, w:^World) {
} }
} }
generate_chunk :: proc(pos:Vec2i) -> Chunk { generate_chunk :: proc(pos:Vec2i) -> Chunk {
chunk := Chunk {position = pos} chunk := Chunk {position = pos}
@ -181,7 +173,7 @@ generate_chunk :: proc(pos:Vec2i) -> Chunk {
tilemap_pos = {0,2}, tilemap_pos = {0,2},
interaction = .NOTHING, interaction = .NOTHING,
resource = .NOTHING, resource = .NOTHING,
color = rl.GREEN color = {0,255,0,255}
} }
} }
} }
@ -234,6 +226,14 @@ set_chunk_tile :: proc(c:^Chunk, t:Tile, pos:Vec2i) {
c.tiles[pos.x][pos.y] = t c.tiles[pos.x][pos.y] = t
} }
set_tile :: proc(w:^World, t:Tile, p:Vec2i) {
chunk := get_chunk_from_world_pos(w, vec2i_to_vec2(p))
set_chunk_tile(chunk, t, get_local_chunk_pos(p))
save_chunk(chunk, w)
}
draw_world :: proc(w:^World) { draw_world :: proc(w:^World) {
for chunk_pos, chunk in w.chunks { for chunk_pos, chunk in w.chunks {
for x in 0..<CHUNK_SIZE { for x in 0..<CHUNK_SIZE {
@ -244,7 +244,7 @@ draw_world :: proc(w:^World) {
pos := rl.Vector2{f32(world_x * CELL_SIZE), f32(world_y * CELL_SIZE)} pos := rl.Vector2{f32(world_x * CELL_SIZE), f32(world_y * CELL_SIZE)}
if tile.type != .NOTHING { if tile.type != .NOTHING {
draw_tile(vec2i_to_vec2(tile.tilemap_pos), pos, tile.color) draw_tile(vec2i_to_vec2(tile.tilemap_pos), pos, rl.Color(tile.color))
} }
} }
} }