feat: Add EnhancedGridMap for dynamic grid generation, pathfinding, and network synchronization, and introduce core game scripts for Tekton entities and managers.

This commit is contained in:
Yogi Wiguna
2026-02-13 13:14:37 +08:00
parent 0ee5051ebd
commit ad16f5942b
9 changed files with 354 additions and 75 deletions
+88 -41
View File
@@ -55,6 +55,18 @@ func _ready():
if em:
em.cell_size = Vector3(1, 0.05, 1)
# Setup MultiplayerSpawner for Static Tekton Stands
# Create a container node for strict pathing
var stands_container = Node3D.new()
stands_container.name = "Stands"
add_child(stands_container)
var stand_spawner = MultiplayerSpawner.new()
stand_spawner.name = "StandSpawner"
stand_spawner.spawn_path = NodePath("../Stands") # Relative to Spawner, finding sibling
stand_spawner.add_spawnable_scene("res://scenes/static_tekton_stand.tscn")
add_child(stand_spawner)
func _on_goal_count_updated(peer_id: int, count: int):
# Only update for local player
if peer_id == multiplayer.get_unique_id():
@@ -699,61 +711,96 @@ func spawn_static_tektons():
# ID: 99000 + i (Consistent IDs for Static Tektons)
var id = 99000 + i
# Pick Shape on Server (0:Cyl, 1:Box, 2:Prism, 3:Sphere)
var shape_idx = randi() % 4
# Spawn on Server
_create_static_setup(pos, id)
_create_static_setup(pos, id, shape_idx)
# Sync to Clients
rpc("sync_spawn_static_setup", pos, id)
rpc("sync_spawn_static_setup", pos, id, shape_idx)
@rpc("call_remote", "reliable")
func sync_spawn_static_setup(pos: Vector2i, tekton_id: int):
_create_static_setup(pos, tekton_id)
@rpc("call_local", "reliable")
func sync_spawn_static_setup(pos: Vector2i, tekton_id: int, shape_idx: int):
# Call local creation logic on all peers.
# Server: Spawns Stand + Void + Tekton
# Client: Avoids Stand (Spawner) + Void + Tekton
_create_static_setup(pos, tekton_id, shape_idx)
func _create_static_setup(pos: Vector2i, tekton_id: int):
func _create_static_setup(pos: Vector2i, tekton_id: int, shape_idx: int):
"""Creates both the Stand and the Static Tekton at the position."""
var enhanced_gridmap = $EnhancedGridMap
# 1. Create Stand
var stand_name = "StaticStand_%d" % tekton_id
if not has_node(stand_name):
var stand_scene = load("res://scenes/static_tekton_stand.tscn")
if stand_scene:
var stand = stand_scene.instantiate()
stand.name = stand_name
add_child(stand)
# 1. Create Stand (Server Only - Synced via Spawner)
# IMPORTANT: Clients receive the Stand via MultiplayerSpawner.
# They MUST NOT spawn it manually here or we get duplicates.
if multiplayer.is_server():
var stands_container = get_node_or_null("Stands")
if stands_container:
var stand_name = "StaticStand_%d" % tekton_id
if not stands_container.has_node(stand_name):
var stand_scene = load("res://scenes/static_tekton_stand.tscn")
if stand_scene:
var stand = stand_scene.instantiate()
stand.name = stand_name
# Set Shape Index BEFORE adding to tree (so _ready picks it up/syncs)
if "shape_index" in stand:
stand.shape_index = shape_idx
stands_container.add_child(stand)
# Position Stand
if enhanced_gridmap:
# Convert grid to world
var world_pos = Vector3(pos.x + 0.5, 0, pos.y + 0.5)
if "cell_size" in enhanced_gridmap:
world_pos = Vector3(
pos.x * enhanced_gridmap.cell_size.x + enhanced_gridmap.cell_size.x/2,
0,
pos.y * enhanced_gridmap.cell_size.z + enhanced_gridmap.cell_size.z/2
)
stand.global_position = world_pos
# 2. Modify Base (Void) - Runs on ALL peers to update local GridMap visual/collision
if enhanced_gridmap:
var floor_count = 3
if "floors" in enhanced_gridmap:
floor_count = enhanced_gridmap.floors
# Position Stand
if enhanced_gridmap:
# Convert grid to world
var world_pos = Vector3(pos.x + 0.5, 0, pos.y + 0.5)
if "cell_size" in enhanced_gridmap:
world_pos = Vector3(
pos.x * enhanced_gridmap.cell_size.x + enhanced_gridmap.cell_size.x/2,
0,
pos.y * enhanced_gridmap.cell_size.z + enhanced_gridmap.cell_size.z/2
)
stand.global_position = world_pos
for dx in range(-1, 2):
for dy in range(-1, 2):
var tile_pos_x = pos.x + dx
var tile_pos_z = pos.y + dy
# Update GridMap to block pathfinding (Item 4 = Wall)
# Mark entire 3x3 area as immutable obstacles on FLOOR 0 (Ground Level)
# This overwrites the ground tile to ensure PlayerMovementManager sees it as blocked.
for dx in range(-1, 2):
for dy in range(-1, 2):
var tile_pos = Vector3i(pos.x + dx, 0, pos.y + dy)
enhanced_gridmap.set_cell_item(tile_pos, 4)
# CRITICAL: Force AStar update so Bots and Pathfinding know about the new walls
if enhanced_gridmap.has_method("update_astar_costs"):
enhanced_gridmap.update_astar_costs()
# Clear ALL vertical layers (Ground, Items, etc.)
for f in range(floor_count):
var tile_pos = Vector3i(tile_pos_x, f, tile_pos_z)
enhanced_gridmap.set_cell_item(tile_pos, -1) # -1 = Empty/Void
# CRITICAL: Force AStar update so Bots and Pathfinding know about the new walls
if enhanced_gridmap.has_method("update_astar_costs"):
enhanced_gridmap.update_astar_costs()
# 2. Create Tekton
# Reuse _create_tekton logic but force params
_create_tekton(pos, tekton_id, true)
# 3. Create Tekton Visual - Runs on ALL peers
# NOTE: Tekton NPC is currently not managed by a specialized Spawner for static setup?
# Or it is? If _create_tekton adds it to a path watched by a spawner, we should duplicate check.
# _create_tekton instantiates 'tekton.tscn' and adds to 'Main'.
# Main usually has a MultiplayerSpawner for 'Players' etc., but let's check.
# The original logic spawned it everywhere, so we keep that behavior to be safe.
# But we add a check to avoid duplicates if it already came in via sync.
if not has_node("Tekton_%d" % tekton_id):
_create_tekton(pos, tekton_id, true)
# Force Tekton height UP to sit on stand
# Force Tekton height UP to sit on stand on ALL peers
var tekton = get_node_or_null("Tekton_%d" % tekton_id)
if tekton:
tekton.position.y += 0.6 # Stand Height
var height_offset = 0.6
# If Sphere (Index 3), it is taller (Dome)
if shape_idx == 3:
height_offset = 1.3
tekton.position.y += height_offset
+18 -5
View File
@@ -1,4 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://static_tekton_stand_001"]
[gd_scene load_steps=6 format=3 uid="uid://static_tekton_stand_001"]
[ext_resource type="Script" path="res://scripts/static_tekton_stand.gd" id="1_script"]
[sub_resource type="CylinderMesh" id="CylinderMesh_stand"]
top_radius = 1.4
@@ -10,12 +12,23 @@ albedo_color = Color(0.15, 0.15, 0.2, 1)
metallic = 0.6
roughness = 0.4
[sub_resource type="CylinderShape3D" id="CylinderShape3D_stand"]
height = 0.6
radius = 1.4
[sub_resource type="BoxShape3D" id="BoxShape3D_stand"]
size = Vector3(3.2, 0.6, 3.2)
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_stand"]
properties/0/path = NodePath(":shape_index")
properties/0/spawn = true
properties/0/replication_mode = 2
properties/1/path = NodePath(":global_position")
properties/1/spawn = true
properties/1/replication_mode = 2
[node name="StaticTektonStand" type="StaticBody3D"]
collision_mask = 0
script = ExtResource("1_script")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_stand")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
@@ -24,4 +37,4 @@ surface_material_override/0 = SubResource("StandardMaterial3D_stand")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
shape = SubResource("CylinderShape3D_stand")
shape = SubResource("BoxShape3D_stand")