New sfx and vfx and features in building

This commit is contained in:
2025-08-29 14:36:05 +08:00
parent 885d2c0075
commit 7720e74a3d
12 changed files with 219 additions and 34 deletions

View File

@@ -19,6 +19,11 @@ public partial class PlacementManager : Node2D
private static readonly List<string> BuildableTiles = ["wall", "miner"]; private static readonly List<string> BuildableTiles = ["wall", "miner"];
private readonly Dictionary<Node2D, BuildTask> _buildTasks = new(); private readonly Dictionary<Node2D, BuildTask> _buildTasks = new();
private AudioStreamPlayer _completionSound; private AudioStreamPlayer _completionSound;
private AudioStreamPlayer _buildingSound;
private AudioStreamPlayer _canceledSound;
private AudioStreamPlayer _cannotDeploySound;
private AudioStreamPlayer _notReadySound;
private AudioStreamPlayer _insufficientFundsSound;
private Node2D _currentGhost; // Keep track of the current ghost building private Node2D _currentGhost; // Keep track of the current ghost building
public override void _Ready() public override void _Ready()
@@ -26,13 +31,25 @@ public partial class PlacementManager : Node2D
base._Ready(); base._Ready();
// Setup completion sound // Setup completion sound
_completionSound = new AudioStreamPlayer(); _completionSound = CreateAudioPlayer("res://Sounds/Events/ConstructionComplete.wav");
AddChild(_completionSound); _buildingSound = CreateAudioPlayer("res://Sounds/Events/Building.wav");
var sound = GD.Load<AudioStream>("res://Sounds/Events/ConstructionComplete.wav"); _canceledSound = CreateAudioPlayer("res://Sounds/Events/Canceled.wav");
_cannotDeploySound = CreateAudioPlayer("res://Sounds/Events/CannotDeployHere.wav");
_notReadySound = CreateAudioPlayer("res://Sounds/Events/NotReady.wav");
_insufficientFundsSound = CreateAudioPlayer("res://Sounds/Events/InsufficientFunds.wav");
}
private AudioStreamPlayer CreateAudioPlayer(string path)
{
var player = new AudioStreamPlayer();
AddChild(player);
var sound = GD.Load<AudioStream>(path);
if (sound != null) if (sound != null)
{ {
_completionSound.Stream = sound; player.Stream = sound;
} }
return player;
} }
private void OnBuildCompleted() private void OnBuildCompleted()
@@ -194,24 +211,25 @@ public partial class PlacementManager : Node2D
_ghostBuilding.SetGhostMode(canPlace); _ghostBuilding.SetGhostMode(canPlace);
// Left click to place // Left click to place
if (Input.IsActionPressed("build_tile") && canPlace) if (Input.IsActionPressed("build_tile"))
{ {
var building = Registry.GetBuilding(_currentBuildingId); var building = Registry.GetBuilding(_currentBuildingId);
if (building == null) return; if (building == null) return;
if (!CanStartNewBuild()) if (!CanStartNewBuild())
{ {
// Optionally show feedback to player that build queue is full _notReadySound.Play();
return; return;
} }
// Consume resources first // Consume resources first
if (!ConsumeBuildingResources(_currentBuildingId)) if (!ConsumeBuildingResources(_currentBuildingId))
{ {
// Optionally show feedback to player that they can't afford this building _insufficientFundsSound.Play();
return; return;
} }
// Create the building instance first
var scene = building.Scene; var scene = building.Scene;
var buildingInstance = (BaseTile)scene.Instantiate(); var buildingInstance = (BaseTile)scene.Instantiate();
buildingInstance.RotationDegrees = _currentRotation; buildingInstance.RotationDegrees = _currentRotation;
@@ -219,23 +237,30 @@ public partial class PlacementManager : Node2D
buildingInstance.Position = _ghostBuilding.Position; buildingInstance.Position = _ghostBuilding.Position;
AddChild(buildingInstance); AddChild(buildingInstance);
// Check if area is free before placing // First check if area is free
if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer)) if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer))
{ {
_cannotDeploySound.Play();
RefundBuildingResources(_currentBuildingId); RefundBuildingResources(_currentBuildingId);
buildingInstance.QueueFree(); buildingInstance.QueueFree();
return; return;
} }
// Occupy the area // If we get here, area is free, so we can safely occupy it
Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer); Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer);
if (building.BuildTime > 0f) if (building.BuildTime > 0f)
{ {
var wasQueueEmpty = _buildTasks.Count == 0;
var buildTask = new BuildTask(OnBuildCompleted); var buildTask = new BuildTask(OnBuildCompleted);
_buildTasks[buildingInstance] = buildTask; _buildTasks[buildingInstance] = buildTask;
buildingInstance.StartConstruction(building.BuildTime, () => { // Play building sound only when adding to an empty queue
if (wasQueueEmpty)
_buildingSound.Play();
buildingInstance.StartConstruction(building.BuildTime, () =>
{
// On construction complete // On construction complete
if (_buildTasks.TryGetValue(buildingInstance, out var task)) if (_buildTasks.TryGetValue(buildingInstance, out var task))
{ {
@@ -245,6 +270,7 @@ public partial class PlacementManager : Node2D
Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer); Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer);
buildingInstance.QueueFree(); buildingInstance.QueueFree();
} }
task.Complete(); task.Complete();
_buildTasks.Remove(buildingInstance); _buildTasks.Remove(buildingInstance);
} }
@@ -258,9 +284,28 @@ public partial class PlacementManager : Node2D
// Right click to destroy from current layer // Right click to destroy from current layer
var building = Grid.GetBuildingAtCell(_hoveredCell); var building = Grid.GetBuildingAtCell(_hoveredCell);
if (building == null) return; if (building == null) return;
// Find all cells occupied by this building // Find all cells occupied by this building
var buildingInfo = Grid.GetBuildingInfoAtCell(_hoveredCell, GridLayer.Building); var buildingInfo = Grid.GetBuildingInfoAtCell(_hoveredCell, GridLayer.Building);
if (buildingInfo == null) return; if (buildingInfo == null) return;
// Check if this building is in the build tasks (under construction)
if (_buildTasks.TryGetValue(building, out var buildTask))
{
var buildingTile = building as BaseTile;
// Cancel the build task
buildTask.Complete(true); // Mark as cancelled
_buildTasks.Remove(building);
_canceledSound.Play();
if (buildingTile == null) return;
// Refund resources for canceled build
var buildingData = Registry.GetBuilding(buildingTile.TileId);
if (buildingData != null)
RefundBuildingResources(buildingTile.TileId);
}
// Clean up the building and grid
building.QueueFree(); building.QueueFree();
Grid.FreeArea(buildingInfo.Value.Position, buildingInfo.Value.Size, buildingInfo.Value.Rotation); Grid.FreeArea(buildingInfo.Value.Position, buildingInfo.Value.Size, buildingInfo.Value.Rotation);
} }

View File

@@ -14,6 +14,7 @@ public partial class BaseTile : Node2D
private Sprite2D _sprite; private Sprite2D _sprite;
private ColorRect _progressOverlay; private ColorRect _progressOverlay;
private Action _onConstructionComplete; private Action _onConstructionComplete;
private bool _isConstructing = false;
public override void _Ready() public override void _Ready()
{ {
@@ -26,6 +27,9 @@ public partial class BaseTile : Node2D
public void SetGhostMode(bool canPlace) public void SetGhostMode(bool canPlace)
{ {
// Don't modify collision for constructing buildings
if (_isConstructing) return;
if (_collisionShape != null) if (_collisionShape != null)
_collisionShape.Disabled = true; _collisionShape.Disabled = true;
@@ -46,8 +50,13 @@ public partial class BaseTile : Node2D
// Building progress visualization // Building progress visualization
public void StartConstruction(float buildTime, Action onComplete = null) public void StartConstruction(float buildTime, Action onComplete = null)
{ {
_isConstructing = true;
if (_collisionShape != null)
_collisionShape.Disabled = true;
if (_progressOverlay == null || _sprite?.Texture == null) if (_progressOverlay == null || _sprite?.Texture == null)
{ {
_isConstructing = false;
onComplete?.Invoke(); onComplete?.Invoke();
return; return;
} }
@@ -55,6 +64,10 @@ public partial class BaseTile : Node2D
_onConstructionComplete = onComplete; _onConstructionComplete = onComplete;
var texSize = new Vector2(GridUtils.TileSize, GridUtils.TileSize); var texSize = new Vector2(GridUtils.TileSize, GridUtils.TileSize);
// Set initial transparency for construction
if (_sprite != null)
_sprite.Modulate = new Color(1, 1, 1, 0.8f);
_progressOverlay.Visible = true; _progressOverlay.Visible = true;
_progressOverlay.Modulate = Colors.White; _progressOverlay.Modulate = Colors.White;
_progressOverlay.Color = new Color(0, 0, 1, 0.4f); // semi-transparent blue _progressOverlay.Color = new Color(0, 0, 1, 0.4f); // semi-transparent blue
@@ -80,7 +93,14 @@ public partial class BaseTile : Node2D
// Fade out the overlay // Fade out the overlay
await FadeOutOverlay(0.5f); await FadeOutOverlay(0.5f);
// Notify completion // Construction complete - restore full opacity and enable collision
if (_sprite != null)
_sprite.Modulate = Colors.White;
_isConstructing = false;
if (_collisionShape != null)
_collisionShape.Disabled = false;
_onConstructionComplete?.Invoke(); _onConstructionComplete?.Invoke();
} }

BIN
Sounds/Events/Building.wav Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://d1trbqrntmuij"
path="res://.godot/imported/Building.wav-b8766581fd25a206c63f47b13bd2e2f5.sample"
[deps]
source_file="res://Sounds/Events/Building.wav"
dest_files=["res://.godot/imported/Building.wav-b8766581fd25a206c63f47b13bd2e2f5.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

BIN
Sounds/Events/Canceled.wav Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://chn8ux4two1kd"
path="res://.godot/imported/Canceled.wav-6d441d9a898b5cd9be4c0664a1f489ed.sample"
[deps]
source_file="res://Sounds/Events/Canceled.wav"
dest_files=["res://.godot/imported/Canceled.wav-6d441d9a898b5cd9be4c0664a1f489ed.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

Binary file not shown.

View File

@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://7u1gw7lt5xd1"
path="res://.godot/imported/CannotDeployHere.wav-a9ec27508654f74d03ac7b8c8037059c.sample"
[deps]
source_file="res://Sounds/Events/CannotDeployHere.wav"
dest_files=["res://.godot/imported/CannotDeployHere.wav-a9ec27508654f74d03ac7b8c8037059c.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

Binary file not shown.

View File

@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bemxvqcettqgp"
path="res://.godot/imported/InsufficientFunds.wav-7aba215cb1cd04a5285a5e9908999906.sample"
[deps]
source_file="res://Sounds/Events/InsufficientFunds.wav"
dest_files=["res://.godot/imported/InsufficientFunds.wav-7aba215cb1cd04a5285a5e9908999906.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

BIN
Sounds/Events/NotReady.wav Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dogbl6tfealwg"
path="res://.godot/imported/NotReady.wav-2cfa5f22feed110ca411d6478060fdea.sample"
[deps]
source_file="res://Sounds/Events/NotReady.wav"
dest_files=["res://.godot/imported/NotReady.wav-2cfa5f22feed110ca411d6478060fdea.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2