From ad76ef17031735a515277632c7cbac133730049c Mon Sep 17 00:00:00 2001 From: copygirl Date: Mon, 29 Sep 2025 22:14:27 +0200 Subject: [PATCH] Add BlockRegion helper class --- world/block_region.gd | 45 ++++++++++++++++++++++ world/block_region.gd.uid | 1 + world/chunk.gd | 5 +-- world/generation/generator_simple.gd | 32 ++++++++-------- world/layers/matter.gd | 57 ++++++++++++++-------------- world/layers/shape.gd | 37 +++++++++--------- 6 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 world/block_region.gd create mode 100644 world/block_region.gd.uid diff --git a/world/block_region.gd b/world/block_region.gd new file mode 100644 index 0000000..7b987cf --- /dev/null +++ b/world/block_region.gd @@ -0,0 +1,45 @@ +class_name BlockRegion +extends RefCounted + +static var LOCAL_CHUNK := BlockRegion.new(Vector2i.ZERO, Vector2i.ONE * (Chunk.SIZE - 1)) + +var min: Vector2i +var max: Vector2i + +func _init(a: Vector2i, b: Vector2i) -> void: + min = a.min(b) + max = a.max(b) + +func size() -> Vector2i: + return max - min + Vector2i.ONE + +func contains(pos: Vector2i) -> bool: + return (pos.x >= min.x) && (pos.x <= max.x) and (pos.y >= min.y) && (pos.y <= max.y) + + +func offset(value: Vector2i) -> BlockRegion: + return BlockRegion.new(min + value, max + value) + +func extend(amount: int) -> BlockRegion: + var v := Vector2i.ONE * amount + return BlockRegion.new(min - v, max + v) + + +func array_index(pos: Vector2i) -> int: + assert(contains(pos)) + var size := size() + var index := (pos.x - min.x) + (pos.y - min.y) * size.x + return clampi(index, 0, size.x * size.y) + + +# Custom iterators in GDScript are WEIRD. +func _iter_init(state: Array) -> bool: + state[0] = min + return true +func _iter_next(state: Array) -> bool: + var cur: Vector2i = state[0] + if cur.x < max.x: state[0].x += 1; return true + elif cur.y < max.y: state[0] = Vector2i(min.x, cur.y + 1); return true + else: return false +func _iter_get(state: Variant) -> Vector2i: + return state diff --git a/world/block_region.gd.uid b/world/block_region.gd.uid new file mode 100644 index 0000000..9beff81 --- /dev/null +++ b/world/block_region.gd.uid @@ -0,0 +1 @@ +uid://w5fuyrkwf40c diff --git a/world/chunk.gd b/world/chunk.gd index 7529af8..0835eb4 100644 --- a/world/chunk.gd +++ b/world/chunk.gd @@ -76,7 +76,4 @@ static func pos_to_center(chunk_pos: Vector2i) -> Vector2: ## Creates an integer index from a chunk-local block position that can be used to index into an array. static func array_index(local_pos: Vector2i) -> int: - assert((local_pos.x >= 0) and (local_pos.x < Chunk.SIZE)) - assert((local_pos.y >= 0) and (local_pos.y < Chunk.SIZE)) - # Using `posmod` ensures `local_pos` is valid, and this returns a valid index. - return posmod(local_pos.x, Chunk.SIZE) + posmod(local_pos.y, Chunk.SIZE) * Chunk.SIZE + return BlockRegion.LOCAL_CHUNK.array_index(local_pos) diff --git a/world/generation/generator_simple.gd b/world/generation/generator_simple.gd index d286397..091f44f 100644 --- a/world/generation/generator_simple.gd +++ b/world/generation/generator_simple.gd @@ -31,23 +31,21 @@ func generate(world: World, chunk: Chunk) -> void: var matter: Matter.Layer = chunk.get_or_create_layer(Matter) var shapes: Shape.Layer = chunk.get_or_create_layer(Shape) - for x in Chunk.SIZE: - for y in Chunk.SIZE: - var local_pos := Vector2i(x, y) - var shape: Shape - match _values(local_pos): - [ true, true, true, true ]: shape = Shape.FULL - [ false, true, true, true ]: shape = Shape.Base.SLOPE.variants[0] - [ true, false, true, true ]: shape = Shape.Base.SLOPE.variants[2] - [ true, true, false, true ]: shape = Shape.Base.SLOPE.variants[3] - [ true, true, true, false ]: shape = Shape.Base.SLOPE.variants[1] - [ false, false, true, true ]: shape = Shape.Base.HALF.variants[0] - [ true, false, false, true ]: shape = Shape.Base.HALF.variants[2] - [ true, true, false, false ]: shape = Shape.Base.HALF.variants[3] - [ false, true, true, false ]: shape = Shape.Base.HALF.variants[1] - if shape: - matter.set_at(local_pos, Matter.PLASTIC) - shapes.set_at(local_pos, shape) + for local_pos in BlockRegion.LOCAL_CHUNK: + var shape: Shape + match _values(local_pos): + [ true, true, true, true ]: shape = Shape.FULL + [ false, true, true, true ]: shape = Shape.Base.SLOPE.variants[0] + [ true, false, true, true ]: shape = Shape.Base.SLOPE.variants[2] + [ true, true, false, true ]: shape = Shape.Base.SLOPE.variants[3] + [ true, true, true, false ]: shape = Shape.Base.SLOPE.variants[1] + [ false, false, true, true ]: shape = Shape.Base.HALF.variants[0] + [ true, false, false, true ]: shape = Shape.Base.HALF.variants[2] + [ true, true, false, false ]: shape = Shape.Base.HALF.variants[3] + [ false, true, true, false ]: shape = Shape.Base.HALF.variants[1] + if shape: + matter.set_at(local_pos, Matter.PLASTIC) + shapes.set_at(local_pos, shape) func _values(pos: Vector2i) -> Array[bool]: return [ diff --git a/world/layers/matter.gd b/world/layers/matter.gd index 9a9f8ee..dd05d74 100644 --- a/world/layers/matter.gd +++ b/world/layers/matter.gd @@ -41,16 +41,17 @@ class Layer extends MeshInstance2D: @rpc("reliable") func set_id_at(pos: Vector2i, id: int) -> void: var index := Chunk.array_index(pos) - if data[index] != id: - # TODO: Keep track of how many non-air blocks are present, to allow for cleaning empty chunks. - data[index] = id - chunk.dirty = true + if data[index] == id: return + # TODO: Keep track of how many non-air blocks are present, to allow for cleaning empty chunks. + data[index] = id + chunk.dirty = true + + if multiplayer.is_server(): # Send change to every player tracking this chunk. - if multiplayer.is_server(): - for player in chunk.get_players_tracking(): - if player.network.is_local: continue # skip server player - set_id_at.rpc_id(player.network.peer_id, pos, id) + for player in chunk.get_players_tracking(): + if player.network.is_local: continue # skip server player + set_id_at.rpc_id(player.network.peer_id, pos, id) func _ready() -> void: @@ -66,27 +67,25 @@ class Layer extends MeshInstance2D: var shapes: Shape.Layer = chunk.get_layer_or_null(Shape) if shapes: # Should exist, but let's sanity check. - for x in Chunk.SIZE: - for y in Chunk.SIZE: - var pos := Vector2i(x, y) - var offset := (Vector2.ONE / 2) + Vector2(pos) - - # TODO: Support different matter. - # var matter := get_at(pos) - var shape := shapes.get_at(pos) - if shape.points.is_empty(): continue - any_blocks = true - - for i in range(2, shape.points.size()): - var uv0 := shape.uvs[0 ] - var uv1 := shape.uvs[i-1] - var uv2 := shape.uvs[i ] - var p0 := (offset + shape.points[0 ]) * Block.SIZE - CHUNK_HALF_SIZE - var p1 := (offset + shape.points[i-1]) * Block.SIZE - CHUNK_HALF_SIZE - var p2 := (offset + shape.points[i ]) * Block.SIZE - CHUNK_HALF_SIZE - m.surface_set_uv(uv0); m.surface_add_vertex_2d(p0) - m.surface_set_uv(uv1); m.surface_add_vertex_2d(p1) - m.surface_set_uv(uv2); m.surface_add_vertex_2d(p2) + for pos in BlockRegion.LOCAL_CHUNK: + var offset := (Vector2.ONE / 2) + Vector2(pos) + + # TODO: Support different matter. + # var matter := get_at(pos) + var shape := shapes.get_at(pos) + if shape.points.is_empty(): continue + any_blocks = true + + for i in range(2, shape.points.size()): + var uv0 := shape.uvs[0 ] + var uv1 := shape.uvs[i-1] + var uv2 := shape.uvs[i ] + var p0 := (offset + shape.points[0 ]) * Block.SIZE - CHUNK_HALF_SIZE + var p1 := (offset + shape.points[i-1]) * Block.SIZE - CHUNK_HALF_SIZE + var p2 := (offset + shape.points[i ]) * Block.SIZE - CHUNK_HALF_SIZE + m.surface_set_uv(uv0); m.surface_add_vertex_2d(p0) + m.surface_set_uv(uv1); m.surface_add_vertex_2d(p1) + m.surface_set_uv(uv2); m.surface_add_vertex_2d(p2) if any_blocks: m.surface_end() diff --git a/world/layers/shape.gd b/world/layers/shape.gd index e30c7fd..ee79702 100644 --- a/world/layers/shape.gd +++ b/world/layers/shape.gd @@ -130,16 +130,17 @@ class Layer extends StaticBody2D: @rpc("reliable") func set_id_at(pos: Vector2i, id: int) -> void: var index := Chunk.array_index(pos) - if data[index] != id: - # TODO: Keep track of how many non-air blocks are present, to allow for cleaning empty chunks. - data[index] = id - chunk.dirty = true + if data[index] == id: return + # TODO: Keep track of how many non-air blocks are present, to allow for cleaning empty chunks. + data[index] = id + chunk.dirty = true + + if multiplayer.is_server(): # Send change to every player tracking this chunk. - if multiplayer.is_server(): - for player in chunk.get_players_tracking(): - if player.network.is_local: continue # skip server player - set_id_at.rpc_id(player.network.peer_id, pos, id) + for player in chunk.get_players_tracking(): + if player.network.is_local: continue # skip server player + set_id_at.rpc_id(player.network.peer_id, pos, id) func _ready() -> void: @@ -155,14 +156,12 @@ class Layer extends StaticBody2D: if child is CollisionShape2D: child.queue_free() - for x in Chunk.SIZE: - for y in Chunk.SIZE: - var pos := Vector2i(x, y) - var shape := get_at(pos) - if shape.shape == null: continue - - var shape2d := CollisionShape2D.new() - shape2d.name = "Block %s" % pos - shape2d.position = (Vector2(pos) + (Vector2.ONE / 2)) * Block.SIZE - CHUNK_HALF_SIZE - shape2d.shape = shape.shape - add_child(shape2d) + for pos in BlockRegion.LOCAL_CHUNK: + var shape := get_at(pos) + if shape.shape == null: continue + + var shape2d := CollisionShape2D.new() + shape2d.name = "Block %s" % pos + shape2d.position = (Vector2(pos) + (Vector2.ONE / 2)) * Block.SIZE - CHUNK_HALF_SIZE + shape2d.shape = shape.shape + add_child(shape2d)