From e9b070ff35ea5f597ef22dff577ac319550e2d40 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 27 Aug 2025 02:42:13 +0800 Subject: [PATCH] :sparkles: Building duartion --- Data/Buildings.json | 19 +++++- Scenes/Tiles/MinerTile.tscn | 6 ++ Scenes/Tiles/WallTile.tscn | 6 ++ Scripts/System/BuildingRegistry.cs | 90 ++++++++++++++++++++++++---- Scripts/System/PlacementManager.cs | 9 ++- Scripts/Tiles/BaseTile.cs | 96 ++++++++++++++++++++---------- 6 files changed, 178 insertions(+), 48 deletions(-) diff --git a/Data/Buildings.json b/Data/Buildings.json index eaf2d99..8ed95a3 100644 --- a/Data/Buildings.json +++ b/Data/Buildings.json @@ -1,4 +1,19 @@ { - "wall": "res://Scenes/Tiles/WallTile.tscn", - "miner": "res://Scenes/Tiles/MinerTile.tscn" + "wall": { + "scene": "res://Scenes/Tiles/WallTile.tscn", + "cost": { + "stone": 5 + }, + "durability": 100, + "buildTime": 1.5 + }, + "miner": { + "scene": "res://Scenes/Tiles/MinerTile.tscn", + "cost": { + "wood": 10, + "stone": 3 + }, + "durability": 200, + "buildTime": 3.0 + } } diff --git a/Scenes/Tiles/MinerTile.tscn b/Scenes/Tiles/MinerTile.tscn index f848936..c40d1b1 100644 --- a/Scenes/Tiles/MinerTile.tscn +++ b/Scenes/Tiles/MinerTile.tscn @@ -15,3 +15,9 @@ texture = ExtResource("2_mecoy") [node name="CollisionShape2D" type="CollisionShape2D" parent="."] shape = SubResource("RectangleShape2D_8o613") + +[node name="ProgressOverlay" type="ColorRect" parent="."] +offset_left = -27.0 +offset_top = -27.0 +offset_right = 27.0 +offset_bottom = 27.0 diff --git a/Scenes/Tiles/WallTile.tscn b/Scenes/Tiles/WallTile.tscn index 6e96293..d9a4ae7 100644 --- a/Scenes/Tiles/WallTile.tscn +++ b/Scenes/Tiles/WallTile.tscn @@ -15,3 +15,9 @@ texture = ExtResource("1_8o613") [node name="CollisionShape2D" type="CollisionShape2D" parent="."] shape = SubResource("RectangleShape2D_8o613") + +[node name="ProgressOverlay" type="ColorRect" parent="."] +offset_left = -27.0 +offset_top = -27.0 +offset_right = 27.0 +offset_bottom = 27.0 diff --git a/Scripts/System/BuildingRegistry.cs b/Scripts/System/BuildingRegistry.cs index 2ba7f61..1e5c429 100644 --- a/Scripts/System/BuildingRegistry.cs +++ b/Scripts/System/BuildingRegistry.cs @@ -5,7 +5,14 @@ namespace AceFieldNewHorizon.Scripts.System; public partial class BuildingRegistry : Node { - private Dictionary _registry = new(); + public record BuildingData( + PackedScene Scene, + Dictionary Cost, + int Durability, + float BuildTime + ); + + private Dictionary _registry = new(); [Export] public string JsonPath { get; set; } = "res://Data/Buildings.json"; @@ -38,26 +45,87 @@ public partial class BuildingRegistry : Node foreach (string key in dict.Keys) { - var scenePath = dict[key].AsString(); - var scene = GD.Load(scenePath); + // Each entry is a Dictionary with keys: "scene", "cost", "durability", "buildTime" + var buildingDict = dict[key].AsGodotDictionary(); - if (scene != null) + // Parse scene + var scenePath = buildingDict.ContainsKey("scene") ? buildingDict["scene"].AsString() : null; + if (string.IsNullOrEmpty(scenePath)) { - _registry[key] = scene; - GD.Print($"[BuildingRegistry] Loaded building '{key}' from {scenePath}"); + GD.PrintErr($"[BuildingRegistry] No scene path for '{key}'"); + continue; } - else + + var scene = GD.Load(scenePath); + if (scene == null) { GD.PrintErr($"[BuildingRegistry] Failed to load scene for '{key}' at {scenePath}"); + continue; } + + // Parse cost + Dictionary cost = new(); + if (buildingDict.TryGetValue("cost", out var value)) + { + var costDict = value.AsGodotDictionary(); + foreach (string mat in costDict.Keys) + { + int val; + var obj = costDict[mat]; + if (obj.VariantType == Variant.Type.PackedInt64Array) + val = (int)obj.AsInt64(); + else if (obj.VariantType == Variant.Type.PackedInt32Array) + val = obj.AsInt32(); + else + int.TryParse(obj.ToString(), out val); + cost[mat] = val; + } + } + + // Parse durability + var durability = 0; + if (buildingDict.TryGetValue("durability", out var dObj)) + { + if (dObj.VariantType == Variant.Type.PackedInt64Array) + durability = (int)dObj.AsInt64(); + else if (dObj.VariantType == Variant.Type.PackedInt32Array) + durability = dObj.AsInt32(); + else + int.TryParse(dObj.ToString(), out durability); + } + + // Parse buildTime + var buildTime = 0f; + if (buildingDict.TryGetValue("buildTime", out var bObj)) + { + switch (bObj.VariantType) + { + case Variant.Type.PackedFloat32Array or Variant.Type.PackedFloat64Array: + buildTime = (float)bObj.AsDouble(); + break; + case Variant.Type.PackedInt64Array: + buildTime = bObj.AsInt64(); + break; + case Variant.Type.PackedInt32Array: + buildTime = bObj.AsInt32(); + break; + default: + float.TryParse(bObj.ToString(), out buildTime); + break; + } + } + + var buildingData = new BuildingData(scene, cost, durability, buildTime); + _registry[key] = buildingData; + GD.Print($"[BuildingRegistry] Loaded building '{key}' from {scenePath}"); } - + GD.Print($"[BuildingRegistry] Loaded {_registry.Count} buildings"); } - public PackedScene GetScene(string id) + public BuildingData GetBuilding(string id) { - _registry.TryGetValue(id, out var scene); - return scene; + _registry.TryGetValue(id, out var data); + return data; } } diff --git a/Scripts/System/PlacementManager.cs b/Scripts/System/PlacementManager.cs index 2cfd773..a8be86b 100644 --- a/Scripts/System/PlacementManager.cs +++ b/Scripts/System/PlacementManager.cs @@ -31,7 +31,7 @@ public partial class PlacementManager : Node2D if (_ghostBuilding == null) { - var scene = Registry.GetScene(_currentBuildingId); + var scene = Registry.GetBuilding(_currentBuildingId)?.Scene; if (scene == null) return; _ghostBuilding = (BaseTile)scene.Instantiate(); @@ -61,12 +61,17 @@ public partial class PlacementManager : Node2D // Left click to place if (Input.IsActionPressed("build_tile") && canPlace) { - var scene = Registry.GetScene(_currentBuildingId); + var scene = Registry.GetBuilding(_currentBuildingId)?.Scene; if (scene == null) return; _ghostBuilding.FinalizePlacement(); + + var buildingData = Registry.GetBuilding(_currentBuildingId); Grid.OccupyCell(_hoveredCell, _ghostBuilding); + if (buildingData is { BuildTime: > 0f }) + _ghostBuilding.StartConstruction(buildingData.BuildTime); + _ghostBuilding = (BaseTile)scene.Instantiate(); _ghostBuilding.SetGhostMode(true); AddChild(_ghostBuilding); diff --git a/Scripts/Tiles/BaseTile.cs b/Scripts/Tiles/BaseTile.cs index 06df515..6a54ca3 100644 --- a/Scripts/Tiles/BaseTile.cs +++ b/Scripts/Tiles/BaseTile.cs @@ -1,45 +1,75 @@ +using System.Numerics; +using AceFieldNewHorizon.Scripts.System; using Godot; +using Vector2 = Godot.Vector2; -namespace AceFieldNewHorizon.Scripts.Tiles +namespace AceFieldNewHorizon.Scripts.Tiles; + +public partial class BaseTile : Node2D { - public partial class BaseTile : Node2D + private CollisionShape2D _collisionShape; + private Sprite2D _sprite; + private ColorRect _progressOverlay; + + public override void _Ready() { - private CollisionShape2D _collisionShape; - private Sprite2D _sprite; + _collisionShape = GetNodeOrNull("CollisionShape2D"); + _sprite = GetNodeOrNull("Sprite2D"); + _progressOverlay = GetNodeOrNull("ProgressOverlay"); + if (_progressOverlay != null) + _progressOverlay.Visible = false; + } - public override void _Ready() + public void SetGhostMode(bool canPlace) + { + if (_collisionShape != null) + _collisionShape.Disabled = true; + + if (_sprite != null) + _sprite.Modulate = canPlace + ? new Color(0, 1, 0, 0.5f) + : new Color(1, 0, 0, 0.5f); + } + + public void FinalizePlacement() + { + if (_collisionShape != null) + _collisionShape.Disabled = false; + if (_sprite != null) + _sprite.Modulate = Colors.White; + } + + // Building progress visualization + public void StartConstruction(float buildTime) + { + if (_progressOverlay == null || _sprite?.Texture == null) return; + + var texSize = new Vector2(GridUtils.TileSize, GridUtils.TileSize); + + _progressOverlay.Visible = true; + _progressOverlay.Color = new Color(0, 0, 1, 0.4f); // semi-transparent blue + + async void RunProgress() { - _collisionShape = GetNodeOrNull("CollisionShape2D"); - _sprite = GetNodeOrNull("Sprite2D"); - } - - /// - /// Switch between ghost and placed mode. - /// - /// If in ghost mode, true = green, false = red. If placed, always white. - public void SetGhostMode(bool canPlace) - { - if (_collisionShape != null) - _collisionShape.Disabled = true; // always disabled in ghost mode - - if (_sprite != null) + var elapsed = 0f; + while (elapsed < buildTime) { - // Ghost preview coloring - _sprite.Modulate = canPlace ? new Color(1, 1, 1, 0.2f) : // white semi-transparent - new Color(1, 0, 0, 0.2f); // red semi-transparent + await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); + elapsed += (float)GetProcessDeltaTime(); + + var ratio = Mathf.Clamp(elapsed / buildTime, 0, 1); + + // Fill from bottom to top, aligned with sprite center pivot + _progressOverlay.Size = new Vector2(texSize.X, texSize.Y * ratio); + _progressOverlay.Position = new Vector2( + -texSize.X / 2, // align left edge + texSize.Y / 2 - _progressOverlay.Size.Y // move upward + ); } + + _progressOverlay.Visible = false; } - /// - /// Call when placement is finalized. - /// - public void FinalizePlacement() - { - if (_collisionShape != null) - _collisionShape.Disabled = false; - - if (_sprite != null) - _sprite.Modulate = Colors.White; // reset to normal - } + RunProgress(); } } \ No newline at end of file