Add BlockRegion helper class

main
copygirl 1 week ago
parent 3dc9a3a3da
commit ad76ef1703
  1. 45
      world/block_region.gd
  2. 1
      world/block_region.gd.uid
  3. 5
      world/chunk.gd
  4. 32
      world/generation/generator_simple.gd
  5. 57
      world/layers/matter.gd
  6. 37
      world/layers/shape.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

@ -0,0 +1 @@
uid://w5fuyrkwf40c

@ -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. ## 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: static func array_index(local_pos: Vector2i) -> int:
assert((local_pos.x >= 0) and (local_pos.x < Chunk.SIZE)) return BlockRegion.LOCAL_CHUNK.array_index(local_pos)
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

@ -31,23 +31,21 @@ func generate(world: World, chunk: Chunk) -> void:
var matter: Matter.Layer = chunk.get_or_create_layer(Matter) var matter: Matter.Layer = chunk.get_or_create_layer(Matter)
var shapes: Shape.Layer = chunk.get_or_create_layer(Shape) var shapes: Shape.Layer = chunk.get_or_create_layer(Shape)
for x in Chunk.SIZE: for local_pos in BlockRegion.LOCAL_CHUNK:
for y in Chunk.SIZE: var shape: Shape
var local_pos := Vector2i(x, y) match _values(local_pos):
var shape: Shape [ true, true, true, true ]: shape = Shape.FULL
match _values(local_pos): [ false, true, true, true ]: shape = Shape.Base.SLOPE.variants[0]
[ true, true, true, true ]: shape = Shape.FULL [ true, false, true, true ]: shape = Shape.Base.SLOPE.variants[2]
[ false, true, true, true ]: shape = Shape.Base.SLOPE.variants[0] [ true, true, false, true ]: shape = Shape.Base.SLOPE.variants[3]
[ true, false, true, true ]: shape = Shape.Base.SLOPE.variants[2] [ true, true, true, false ]: shape = Shape.Base.SLOPE.variants[1]
[ true, true, false, true ]: shape = Shape.Base.SLOPE.variants[3] [ false, false, true, true ]: shape = Shape.Base.HALF.variants[0]
[ true, true, true, false ]: shape = Shape.Base.SLOPE.variants[1] [ true, false, false, true ]: shape = Shape.Base.HALF.variants[2]
[ false, false, true, true ]: shape = Shape.Base.HALF.variants[0] [ true, true, false, false ]: shape = Shape.Base.HALF.variants[3]
[ true, false, false, true ]: shape = Shape.Base.HALF.variants[2] [ false, true, true, false ]: shape = Shape.Base.HALF.variants[1]
[ true, true, false, false ]: shape = Shape.Base.HALF.variants[3] if shape:
[ false, true, true, false ]: shape = Shape.Base.HALF.variants[1] matter.set_at(local_pos, Matter.PLASTIC)
if shape: shapes.set_at(local_pos, shape)
matter.set_at(local_pos, Matter.PLASTIC)
shapes.set_at(local_pos, shape)
func _values(pos: Vector2i) -> Array[bool]: func _values(pos: Vector2i) -> Array[bool]:
return [ return [

@ -41,16 +41,17 @@ class Layer extends MeshInstance2D:
@rpc("reliable") @rpc("reliable")
func set_id_at(pos: Vector2i, id: int) -> void: func set_id_at(pos: Vector2i, id: int) -> void:
var index := Chunk.array_index(pos) var index := Chunk.array_index(pos)
if data[index] != id: 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
# 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. # Send change to every player tracking this chunk.
if multiplayer.is_server(): for player in chunk.get_players_tracking():
for player in chunk.get_players_tracking(): if player.network.is_local: continue # skip server player
if player.network.is_local: continue # skip server player set_id_at.rpc_id(player.network.peer_id, pos, id)
set_id_at.rpc_id(player.network.peer_id, pos, id)
func _ready() -> void: func _ready() -> void:
@ -66,27 +67,25 @@ class Layer extends MeshInstance2D:
var shapes: Shape.Layer = chunk.get_layer_or_null(Shape) var shapes: Shape.Layer = chunk.get_layer_or_null(Shape)
if shapes: # Should exist, but let's sanity check. if shapes: # Should exist, but let's sanity check.
for x in Chunk.SIZE: for pos in BlockRegion.LOCAL_CHUNK:
for y in Chunk.SIZE: var offset := (Vector2.ONE / 2) + Vector2(pos)
var pos := Vector2i(x, y)
var offset := (Vector2.ONE / 2) + Vector2(pos) # TODO: Support different matter.
# var matter := get_at(pos)
# TODO: Support different matter. var shape := shapes.get_at(pos)
# var matter := get_at(pos) if shape.points.is_empty(): continue
var shape := shapes.get_at(pos) any_blocks = true
if shape.points.is_empty(): continue
any_blocks = true for i in range(2, shape.points.size()):
var uv0 := shape.uvs[0 ]
for i in range(2, shape.points.size()): var uv1 := shape.uvs[i-1]
var uv0 := shape.uvs[0 ] var uv2 := shape.uvs[i ]
var uv1 := shape.uvs[i-1] var p0 := (offset + shape.points[0 ]) * Block.SIZE - CHUNK_HALF_SIZE
var uv2 := shape.uvs[i ] var p1 := (offset + shape.points[i-1]) * Block.SIZE - CHUNK_HALF_SIZE
var p0 := (offset + shape.points[0 ]) * Block.SIZE - CHUNK_HALF_SIZE var p2 := (offset + shape.points[i ]) * Block.SIZE - CHUNK_HALF_SIZE
var p1 := (offset + shape.points[i-1]) * Block.SIZE - CHUNK_HALF_SIZE m.surface_set_uv(uv0); m.surface_add_vertex_2d(p0)
var p2 := (offset + shape.points[i ]) * Block.SIZE - CHUNK_HALF_SIZE m.surface_set_uv(uv1); m.surface_add_vertex_2d(p1)
m.surface_set_uv(uv0); m.surface_add_vertex_2d(p0) m.surface_set_uv(uv2); m.surface_add_vertex_2d(p2)
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: if any_blocks:
m.surface_end() m.surface_end()

@ -130,16 +130,17 @@ class Layer extends StaticBody2D:
@rpc("reliable") @rpc("reliable")
func set_id_at(pos: Vector2i, id: int) -> void: func set_id_at(pos: Vector2i, id: int) -> void:
var index := Chunk.array_index(pos) var index := Chunk.array_index(pos)
if data[index] != id: 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
# 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. # Send change to every player tracking this chunk.
if multiplayer.is_server(): for player in chunk.get_players_tracking():
for player in chunk.get_players_tracking(): if player.network.is_local: continue # skip server player
if player.network.is_local: continue # skip server player set_id_at.rpc_id(player.network.peer_id, pos, id)
set_id_at.rpc_id(player.network.peer_id, pos, id)
func _ready() -> void: func _ready() -> void:
@ -155,14 +156,12 @@ class Layer extends StaticBody2D:
if child is CollisionShape2D: if child is CollisionShape2D:
child.queue_free() child.queue_free()
for x in Chunk.SIZE: for pos in BlockRegion.LOCAL_CHUNK:
for y in Chunk.SIZE: var shape := get_at(pos)
var pos := Vector2i(x, y) if shape.shape == null: continue
var shape := get_at(pos)
if shape.shape == null: continue var shape2d := CollisionShape2D.new()
shape2d.name = "Block %s" % pos
var shape2d := CollisionShape2D.new() shape2d.position = (Vector2(pos) + (Vector2.ONE / 2)) * Block.SIZE - CHUNK_HALF_SIZE
shape2d.name = "Block %s" % pos shape2d.shape = shape.shape
shape2d.position = (Vector2(pos) + (Vector2.ONE / 2)) * Block.SIZE - CHUNK_HALF_SIZE add_child(shape2d)
shape2d.shape = shape.shape
add_child(shape2d)

Loading…
Cancel
Save