diff options
author | dusoleil <howcansocksbereal@gmail.com> | 2022-09-18 07:45:13 -0400 |
---|---|---|
committer | dusoleil <howcansocksbereal@gmail.com> | 2022-09-18 07:45:13 -0400 |
commit | 3ac2fb62b67b6446ab31b2455a4cb74069f1c54f (patch) | |
tree | 4764fa39466d7fd0d4ac9133e5a237dd1576a487 | |
parent | 5923cd97dfafd7fee2784a91e81d7d55ed28448e (diff) | |
download | game_jam49-3ac2fb62b67b6446ab31b2455a4cb74069f1c54f.tar.gz game_jam49-3ac2fb62b67b6446ab31b2455a4cb74069f1c54f.zip |
Add ChunkLoader to World Generation
Break up world gen into chunk and offload generating them to a Chunk Loader.
The World figures out which chunks should be loaded and gives this to the Chunk Loader.
The Chunk Loader frees any chunks out of render distance.
It also picks an unloaded chunk and gives it to a worker thread to generate it.
-rw-r--r-- | Chunk.tscn | 19 | ||||
-rw-r--r-- | ChunkGen.gd | 35 | ||||
-rw-r--r-- | ChunkLoader.gd | 66 | ||||
-rw-r--r-- | ChunkedCSGMesh.gd | 6 | ||||
-rw-r--r-- | ChunkedMeshInstance.gd | 6 | ||||
-rw-r--r-- | ChunkedSprite3D.gd | 6 | ||||
-rw-r--r-- | World.gd | 60 | ||||
-rw-r--r-- | World.tscn | 22 | ||||
-rw-r--r-- | project.godot | 4 |
9 files changed, 169 insertions, 55 deletions
@@ -1,33 +1,34 @@ -[gd_scene load_steps=6 format=2] +[gd_scene load_steps=7 format=2] -[ext_resource path="res://Chunk.gd" type="Script" id=1] +[ext_resource path="res://ChunkedCSGMesh.gd" type="Script" id=3] [sub_resource type="SphereShape" id=1] radius = 50.0 -[sub_resource type="PlaneMesh" id=2] -size = Vector2( 100, 100 ) - [sub_resource type="SpatialMaterial" id=3] flags_unshaded = true params_cull_mode = 2 albedo_color = Color( 0.937255, 0, 1, 1 ) +[sub_resource type="PlaneMesh" id=2] +size = Vector2( 100, 100 ) + [sub_resource type="PlaneMesh" id=4] size = Vector2( 98, 98 ) [node name="Chunk" type="Area"] collision_layer = 32 collision_mask = 0 -script = ExtResource( 1 ) [node name="Size" type="CollisionShape" parent="."] shape = SubResource( 1 ) [node name="Border" type="CSGMesh" parent="."] -mesh = SubResource( 2 ) -material = SubResource( 3 ) +material_override = SubResource( 3 ) +script = ExtResource( 3 ) +_mesh = SubResource( 2 ) [node name="CSGMesh" type="CSGMesh" parent="Border"] operation = 2 -mesh = SubResource( 4 ) +script = ExtResource( 3 ) +_mesh = SubResource( 4 ) diff --git a/ChunkGen.gd b/ChunkGen.gd new file mode 100644 index 0000000..6e1f0eb --- /dev/null +++ b/ChunkGen.gd @@ -0,0 +1,35 @@ +extends Node + +var chunk_half_size setget _set_chunk_size +var chunk_size + +func _set_chunk_size(val): + chunk_half_size = val + chunk_size = val * 2.0 + +onready var noise = OpenSimplexNoise.new() + +func _ready(): + randomize() + noise.seed = randi() + noise.octaves = 4 + noise.period = 64 + noise.persistence = 0.001 + noise.lacunarity = 2.0 + +func gen_chunk(chunk): + var gen_tree = Spatial.new() + gen_tree.name = "gen_tree" + chunk.add_child(gen_tree) + +func v2_coords(coords:Vector3): + return Vector2(coords.x,coords.z) + +func v3_coords(coords:Vector2): + return Vector3(coords.x,0.0,coords.y) + +func iterate_chunk(chunk,step:Vector2,cb:FuncRef): + var chunk_size_rounded = Vector2(chunk_half_size,chunk_half_size).snapped(step) + for x in range(-chunk_size_rounded.x,chunk_size_rounded.x,step.x): + for y in range(-chunk_size_rounded.y,chunk_size_rounded.y,step.y): + cb.call_func(chunk,Vector2(x,y)) diff --git a/ChunkLoader.gd b/ChunkLoader.gd new file mode 100644 index 0000000..90cac4f --- /dev/null +++ b/ChunkLoader.gd @@ -0,0 +1,66 @@ +extends Node + +var world = null + +var Chunk = preload("res://Chunk.tscn") +var chunks = {} +var loaded_chunks = {} +var chunk_thread = Thread.new() +var mtx = Mutex.new() +var sgnl = Semaphore.new() +var exit = false +var chunk_to_load = null + +func _ready(): + chunk_thread.start(self, "chunk_loader") + +func _exit_tree(): + mtx.lock() + exit = true + sgnl.post() + mtx.unlock() + chunk_thread.wait_to_finish() + +func add_chunk(coords:Vector2): + chunks[coords] = coords.length() + +func chunk_update(): + var closest_unloaded_chunk = null + var closest_len = 0.0 + for chunk in chunks.keys(): + if !loaded_chunks.has(chunk): + if closest_unloaded_chunk == null || closest_len > chunks[chunk]: + closest_unloaded_chunk = chunk + closest_len = chunks[chunk] + if closest_unloaded_chunk != null: + mtx.lock() + if chunk_to_load == null: + chunk_to_load = closest_unloaded_chunk + sgnl.post() + mtx.unlock() + for chunk in loaded_chunks.keys(): + if !chunks.has(chunk): + var c = loaded_chunks[chunk].get_ref() + if c != null: + c.queue_free() + loaded_chunks.erase(chunk) + chunks = {} + +func chunk_loader(): + while true: + sgnl.wait() + mtx.lock() + var x = exit + var coords = chunk_to_load + chunk_to_load = null + mtx.unlock() + if x: + break + var chunk = Chunk.instance() + chunk.transform.origin = Vector3(coords.x,0.0,coords.y) + ChunkGen.gen_chunk(chunk) + self.call_deferred("finish_chunk", chunk) + +func finish_chunk(chunk): + world.get_node("Chunks").add_child(chunk) + loaded_chunks[Vector2(chunk.transform.origin.x,chunk.transform.origin.z)] = weakref(chunk) diff --git a/ChunkedCSGMesh.gd b/ChunkedCSGMesh.gd new file mode 100644 index 0000000..2b8c6db --- /dev/null +++ b/ChunkedCSGMesh.gd @@ -0,0 +1,6 @@ +extends CSGMesh + +export(Mesh) var _mesh + +func _ready(): + self.mesh = _mesh diff --git a/ChunkedMeshInstance.gd b/ChunkedMeshInstance.gd new file mode 100644 index 0000000..dde7eda --- /dev/null +++ b/ChunkedMeshInstance.gd @@ -0,0 +1,6 @@ +extends MeshInstance + +export(Mesh) var _mesh + +func _ready(): + self.mesh = _mesh diff --git a/ChunkedSprite3D.gd b/ChunkedSprite3D.gd new file mode 100644 index 0000000..c9e0be9 --- /dev/null +++ b/ChunkedSprite3D.gd @@ -0,0 +1,6 @@ +extends Sprite3D + +export(Texture) var _tex + +func _ready(): + self.texture = _tex @@ -4,45 +4,17 @@ export(NodePath) var traveler = null onready var _traveler = get_node(traveler) export var acceleration = 10.0 -onready var Chunk = preload("res://Chunk.tscn") -onready var chunk_size = $"Chunks/Chunk/Size".shape.radius*2.0 -var chunks = {} +onready var chunk_render_distance = $"ChunkRenderDistance/Radius".shape.radius -func v2_coords(coords:Vector3): - return Vector2(coords.x,coords.z) - -func v3_coords(coords:Vector2): - return Vector3(coords.x,0.0,coords.y) +onready var chunk_half_size = $"Chunks/Chunk/Size".shape.radius +onready var chunk_size = chunk_half_size * 2.0 -func chunk_coords(coords:Vector2): - return coords.snapped(Vector2(chunk_size,chunk_size)) - -func gen_chunk(coords:Vector2): - var chunk = Chunk.instance() - chunk.transform.origin = v3_coords(coords) - $"Chunks".add_child(chunk) - chunks[coords] = chunk - -func gen_chunks(): - var gen_center = -v2_coords($"Chunks".transform.origin) - var gen_radius = $"ChunkRenderDistance/Radius".shape.radius - var gen_radius_rounded = stepify(gen_radius, chunk_size) - for x in range(-gen_radius_rounded, gen_radius_rounded+chunk_size, chunk_size): - for y in range(-gen_radius_rounded, gen_radius_rounded+chunk_size, chunk_size): - var gen_coords = Vector2(x,y) - if gen_coords.length() > gen_radius: - continue - var coords = chunk_coords(gen_coords + gen_center) - if !chunks.has(coords): - gen_chunk(coords) +func _ready(): + ChunkLoader.world = self + ChunkGen.chunk_half_size = self.chunk_half_size func _on_ChunkRenderDistance_area_exited(area:Area): - gen_chunks() area.queue_free() - chunks.erase(v2_coords(area.transform.origin)) - -func _ready(): - gen_chunks() func travel(direction:Vector3): var heading = _traveler.global_transform.basis @@ -53,6 +25,26 @@ func travel(direction:Vector3): $"Chunks".add_central_force(direction*acceleration) func _physics_process(_state): + chunk_process() var stick = Input.get_axis("ship_down","ship_up") if stick != 0.0: self.travel(Vector3.FORWARD*stick) + +func v2_coords(coords:Vector3): + return Vector2(coords.x,coords.z) + +func chunk_coords(coords:Vector2): + return coords.snapped(Vector2(chunk_size,chunk_size)) + +func chunk_process(): + var gen_center = -v2_coords($"Chunks".transform.origin) + var gen_radius = chunk_render_distance + var gen_radius_rounded = stepify(gen_radius, chunk_size) + for x in range(-gen_radius_rounded, gen_radius_rounded+chunk_size, chunk_size): + for y in range(-gen_radius_rounded, gen_radius_rounded+chunk_size, chunk_size): + var gen_coords = Vector2(x,y) + if gen_coords.length() > gen_radius: + continue + var coords = chunk_coords(gen_coords + gen_center) + ChunkLoader.add_chunk(coords) + ChunkLoader.chunk_update() @@ -1,17 +1,24 @@ -[gd_scene load_steps=5 format=2] +[gd_scene load_steps=9 format=2] [ext_resource path="res://World.gd" type="Script" id=1] [ext_resource path="res://Chunk.tscn" type="PackedScene" id=3] -[sub_resource type="BoxShape" id=23] - [sub_resource type="SphereShape" id=24] -radius = 300.0 +radius = 500.0 + +[sub_resource type="BoxShape" id=23] [node name="World" type="Spatial"] script = ExtResource( 1 ) acceleration = 50.0 +[node name="ChunkRenderDistance" type="Area" parent="."] +collision_layer = 0 +collision_mask = 32 + +[node name="Radius" type="CollisionShape" parent="ChunkRenderDistance"] +shape = SubResource( 24 ) + [node name="Chunks" type="RigidBody" parent="."] collision_layer = 0 collision_mask = 0 @@ -29,11 +36,4 @@ shape = SubResource( 23 ) visible = false collision_layer = 0 -[node name="ChunkRenderDistance" type="Area" parent="."] -collision_layer = 0 -collision_mask = 32 - -[node name="Radius" type="CollisionShape" parent="ChunkRenderDistance"] -shape = SubResource( 24 ) - [connection signal="area_exited" from="ChunkRenderDistance" to="." method="_on_ChunkRenderDistance_area_exited"] diff --git a/project.godot b/project.godot index 34e666b..a22cfe0 100644 --- a/project.godot +++ b/project.godot @@ -27,7 +27,8 @@ config/icon="res://icon.png" [autoload] Settings="*res://singletons/settings.gd" -WorldGenRNG="*res://WorldGenRNG.gd" +ChunkLoader="*res://ChunkLoader.gd" +ChunkGen="*res://ChunkGen.gd" [display] @@ -121,5 +122,6 @@ common/enable_pause_aware_picking=true [rendering] +threads/thread_model=2 quality/filters/msaa=3 environment/default_environment="res://default_env.tres" |