Compare commits

...

4 Commits

Author SHA1 Message Date
2c5e0459ad 💄 Optimize stuff 2025-08-31 19:30:16 +08:00
f2c243ecf6 🍱 Retexture the enemies 2025-08-31 18:54:30 +08:00
b424aafeab ♻️ Optimizations of the various system
🍱 Retexture of the enemy portal
2025-08-31 18:26:45 +08:00
09511b37c9 Reactor, and enemies attacking the tiles 2025-08-31 14:30:18 +08:00
29 changed files with 759 additions and 370 deletions

View File

@@ -33,14 +33,23 @@
"layer": 1, "layer": 1,
"size": [1, 1] "size": [1, 1]
}, },
"enemy_nest": { "reactor": {
"scene": "res://Scenes/Tiles/EnemyNest.tscn", "scene": "res://Scenes/Tiles/ReactorTile.tscn",
"cost": {},
"durability": 1000,
"buildTime": 5.0,
"allowedRotations": [0],
"layer": 1,
"size": [3, 3]
},
"enemy_portal": {
"scene": "res://Scenes/Tiles/EnemyPortalTile.tscn",
"cost": {}, "cost": {},
"durability": 200, "durability": 200,
"buildTime": 0.0, "buildTime": 0.0,
"allowedRotations": [0], "allowedRotations": [0],
"layer": 1, "layer": 1,
"size": [1, 1] "size": [1, 2]
}, },
"ground": { "ground": {
"scene": "res://Scenes/Tiles/GroundTile.tscn", "scene": "res://Scenes/Tiles/GroundTile.tscn",

BIN
Scenes/Entities/Enemy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

View File

@@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dlhpiyxtmp707" uid="uid://x4u6oatvsm8y"
path="res://.godot/imported/Enemy.jpg-862b79047d7834ee48aa6bbd7e126824.ctex" path="res://.godot/imported/Enemy.png-7a121c0bc2e7a40a7fe012e488d00452.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://Scenes/Entities/Enemy.jpg" source_file="res://Scenes/Entities/Enemy.png"
dest_files=["res://.godot/imported/Enemy.jpg-862b79047d7834ee48aa6bbd7e126824.ctex"] dest_files=["res://.godot/imported/Enemy.png-7a121c0bc2e7a40a7fe012e488d00452.ctex"]
[params] [params]

View File

@@ -1,19 +1,21 @@
[gd_scene load_steps=4 format=3 uid="uid://b3ffcucytwmk"] [gd_scene load_steps=3 format=3 uid="uid://b3ffcucytwmk"]
[ext_resource type="Texture2D" uid="uid://dlhpiyxtmp707" path="res://Scenes/Entities/Enemy.jpg" id="1_8q37v"]
[ext_resource type="Script" uid="uid://cvsmy820b8dwl" path="res://Scripts/Entities/Enemy.cs" id="1_jajit"] [ext_resource type="Script" uid="uid://cvsmy820b8dwl" path="res://Scripts/Entities/Enemy.cs" id="1_jajit"]
[ext_resource type="Texture2D" uid="uid://x4u6oatvsm8y" path="res://Scenes/Entities/Enemy.png" id="2_jajit"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jajit"]
size = Vector2(60, 74)
[node name="Enemy" type="CharacterBody2D"] [node name="Enemy" type="CharacterBody2D"]
collision_layer = 2 collision_layer = 2
collision_mask = 3 collision_mask = 3
script = ExtResource("1_jajit") script = ExtResource("1_jajit")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionPolygon2D" parent="."]
shape = SubResource("RectangleShape2D_jajit") polygon = PackedVector2Array(-2, -21, 3, -21, 24, 6, 24, 10, 20, 14, -21, 14, -24, 11, -24, 7)
[node name="Sprite2D" type="Sprite2D" parent="."] [node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.1, 0.1) scale = Vector2(0.05, 0.05)
texture = ExtResource("1_8q37v") texture = ExtResource("2_jajit")
[node name="AttackArea" type="Area2D" parent="."]
[node name="CollisionShape2D" type="CollisionPolygon2D" parent="AttackArea"]
polygon = PackedVector2Array(-3, -24, 4, -24, 27, 5, 27, 14, 20, 19, -21, 19, -27, 14, -27, 5)

View File

@@ -14,9 +14,8 @@ script = ExtResource("2_oss8w")
[node name="PlacementSystem" type="Node2D" parent="."] [node name="PlacementSystem" type="Node2D" parent="."]
script = ExtResource("2_sxhdm") script = ExtResource("2_sxhdm")
[node name="Player" parent="." node_paths=PackedStringArray("Inventory") instance=ExtResource("3_oss8w")] [node name="Player" parent="." instance=ExtResource("3_oss8w")]
scale = Vector2(0.35, 0.35) scale = Vector2(0.35, 0.35)
Inventory = NodePath("")
[node name="HUD" parent="." instance=ExtResource("8_hud_scene")] [node name="HUD" parent="." instance=ExtResource("8_hud_scene")]

View File

@@ -1,20 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://dup2su0s3ybcy"]
[ext_resource type="Script" uid="uid://26hl5mk4mqur" path="res://Scripts/Tiles/EnemyNestTile.cs" id="1_4g0ff"]
[ext_resource type="Texture2D" uid="uid://vwfs68ftvjr4" path="res://Scenes/Tiles/EnemyNest.jpg" id="1_id484"]
[ext_resource type="PackedScene" uid="uid://b3ffcucytwmk" path="res://Scenes/Entities/Enemy.tscn" id="2_pka71"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_id484"]
size = Vector2(54, 54)
[node name="EnemyNest" type="StaticBody2D"]
script = ExtResource("1_4g0ff")
EnemyScene = ExtResource("2_pka71")
TileId = "enemy_nest"
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.056, 0.056)
texture = ExtResource("1_id484")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_id484")

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dv2xwfyshxdtp"
path="res://.godot/imported/EnemyPortalTile.png-3904776a211e67c58254b1bdc9aba071.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scenes/Tiles/EnemyPortalTile.png"
dest_files=["res://.godot/imported/EnemyPortalTile.png-3904776a211e67c58254b1bdc9aba071.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,23 @@
[gd_scene load_steps=5 format=3 uid="uid://dup2su0s3ybcy"]
[ext_resource type="Script" uid="uid://26hl5mk4mqur" path="res://Scripts/Tiles/EnemyPortalTile.cs" id="1_o543x"]
[ext_resource type="PackedScene" uid="uid://b3ffcucytwmk" path="res://Scenes/Entities/Enemy.tscn" id="2_nh7ff"]
[ext_resource type="Texture2D" uid="uid://dv2xwfyshxdtp" path="res://Scenes/Tiles/EnemyPortalTile.png" id="3_i4us4"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_id484"]
size = Vector2(54, 79)
[node name="EnemyPortal" type="StaticBody2D"]
collision_layer = 0
collision_mask = 0
script = ExtResource("1_o543x")
EnemyScene = ExtResource("2_nh7ff")
TileId = "enemy_portal"
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.1, 0.1)
texture = ExtResource("3_i4us4")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -0.5)
shape = SubResource("RectangleShape2D_id484")

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

View File

@@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://vwfs68ftvjr4" uid="uid://fg03qxqphp7n"
path="res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex" path="res://.godot/imported/ReactorTile.png-f6f5bfaa813b044011d6b0a5736b9bc6.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://Scenes/Tiles/EnemyNest.jpg" source_file="res://Scenes/Tiles/ReactorTile.png"
dest_files=["res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex"] dest_files=["res://.godot/imported/ReactorTile.png-f6f5bfaa813b044011d6b0a5736b9bc6.ctex"]
[params] [params]

View File

@@ -0,0 +1,26 @@
[gd_scene load_steps=4 format=3 uid="uid://w6ni678js7cu"]
[ext_resource type="Script" uid="uid://c4k3ottt7j3b1" path="res://Scripts/Tiles/ReactorTile.cs" id="1_yldg2"]
[ext_resource type="Texture2D" uid="uid://fg03qxqphp7n" path="res://Scenes/Tiles/ReactorTile.png" id="3_fk1vt"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8o613"]
size = Vector2(54, 54)
[node name="ReactorTile" type="StaticBody2D"]
script = ExtResource("1_yldg2")
TileId = "reactor"
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.3, 0.3)
texture = ExtResource("3_fk1vt")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
scale = Vector2(3, 3)
shape = SubResource("RectangleShape2D_8o613")
[node name="ProgressOverlay" type="ColorRect" parent="."]
offset_left = -81.0
offset_top = -81.0
offset_right = -27.0
offset_bottom = -27.0
scale = Vector2(3, 3)

View File

@@ -1,193 +1,13 @@
using System.Collections.Generic;
using AceFieldNewHorizon.Scripts.Tiles;
using Godot; using Godot;
using AceFieldNewHorizon.Scripts.System;
namespace AceFieldNewHorizon.Scripts.Entities; namespace AceFieldNewHorizon.Scripts.Entities;
public partial class Enemy : CharacterBody2D public partial class Enemy : BaseEnemy
{ {
public const string EnemyGroupName = "Enemy"; // All the base functionality is now in BaseEnemy
// This class is kept for backward compatibility and can be used to add
[Export] public float MoveSpeed = 150.0f; // specific behaviors for the basic enemy type if needed
[Export] public float DetectionRadius = 300.0f;
[Export] public float AttackRange = 50.0f;
[Export] public int Damage = 10;
[Export] public float AttackCooldown = 1.0f;
[Export] public int MaxHealth = 100;
[Export] public bool ShowDamageNumbers = true;
private Player _player;
private float _attackTimer = 0;
private bool _isPlayerInRange = false;
private Area2D _detectionArea;
private int _currentHealth;
private ProgressBar _healthBar;
public int CurrentHealth
{
get => _currentHealth;
private set
{
_currentHealth = Mathf.Clamp(value, 0, MaxHealth);
UpdateHealthBar();
if (_currentHealth <= 0)
{
Die();
}
}
}
public bool IsDead => _currentHealth <= 0;
public override void _Ready()
{
_currentHealth = MaxHealth;
// Create health bar
_healthBar = new ProgressBar
{
MaxValue = MaxHealth,
Value = _currentHealth,
Size = new Vector2(40, 4),
ShowPercentage = false,
Visible = false
};
var healthBarContainer = new Control();
healthBarContainer.AddChild(_healthBar);
AddChild(healthBarContainer);
healthBarContainer.Position = new Vector2(-20, -20); // Adjust position as needed
// Create detection area
_detectionArea = new Area2D();
var collisionShape = new CollisionShape2D();
var shape = new CircleShape2D();
shape.Radius = DetectionRadius;
collisionShape.Shape = shape;
_detectionArea.AddChild(collisionShape);
AddChild(_detectionArea);
// Connect signals
_detectionArea.BodyEntered += OnBodyEnteredDetection;
_detectionArea.BodyExited += OnBodyExitedDetection;
AddToGroup(EnemyGroupName);
}
public override void _Process(double delta)
{
if (IsDead) return;
if (_player != null && _isPlayerInRange)
{
// Face the player
LookAt(_player.GlobalPosition);
// Move towards player if not in attack range
var direction = GlobalPosition.DirectionTo(_player.GlobalPosition);
var distance = GlobalPosition.DistanceTo(_player.GlobalPosition);
if (distance > AttackRange)
{
Velocity = direction * MoveSpeed;
}
else
{
Velocity = Vector2.Zero;
TryAttackPlayer(delta);
}
MoveAndSlide();
}
}
public void TakeDamage(int damage, Vector2? hitPosition = null)
{
if (IsDead) return;
CurrentHealth -= damage;
// Show damage number (optional)
if (ShowDamageNumbers)
{
var damageLabel = new Label
{
Text = $"-{damage}",
Position = hitPosition ?? GlobalPosition,
ZIndex = 1000
};
GetTree().CurrentScene.AddChild(damageLabel);
// Animate and remove damage number
var tween = CreateTween();
tween.TweenProperty(damageLabel, "position:y", damageLabel.Position.Y - 30, 0.5f);
tween.TweenCallback(Callable.From(() => damageLabel.QueueFree())).SetDelay(0.5f);
}
// Visual feedback
var originalModulate = Modulate;
Modulate = new Color(1, 0.5f, 0.5f); // Flash red
var tweenFlash = CreateTween();
tweenFlash.TweenProperty(this, "modulate", originalModulate, 0.2f);
}
private void UpdateHealthBar()
{
if (_healthBar != null)
{
_healthBar.Value = _currentHealth;
_healthBar.Visible = _currentHealth < MaxHealth; // Only show when damaged
}
}
private void Die()
{
// Play death animation/sound
// You can add a death animation here
// Disable collisions and hide
SetProcess(false);
SetPhysicsProcess(false);
Hide();
// Queue free after a delay (for any death animation/sound to play)
var timer = new Timer();
AddChild(timer);
timer.Timeout += () => QueueFree();
timer.Start(0.5f);
}
private void TryAttackPlayer(double delta)
{
if (IsDead) return;
_attackTimer += (float)delta;
if (_attackTimer >= AttackCooldown)
{
_attackTimer = 0;
// Here you can implement the attack logic
// For example: _player.TakeDamage(Damage);
GD.Print("Enemy attacks player!");
}
}
private void OnBodyEnteredDetection(Node2D body)
{
if (body is Player player)
{
_player = player;
_isPlayerInRange = true;
}
}
private void OnBodyExitedDetection(Node2D body)
{
if (body == _player)
{
_isPlayerInRange = false;
Velocity = Vector2.Zero;
}
}
} }

View File

@@ -0,0 +1,283 @@
using System.Collections.Generic;
using AceFieldNewHorizon.Scripts.System;
using AceFieldNewHorizon.Scripts.Tiles;
using Godot;
namespace AceFieldNewHorizon.Scripts.Entities;
public abstract partial class BaseEnemy : CharacterBody2D
{
public const string EnemyGroupName = "Enemy";
[Export] public float MoveSpeed = 150.0f;
[Export] public float DetectionRadius = 300.0f;
[Export] public float AttackRadius = 80.0f;
[Export] public int Damage = 10;
[Export] public float AttackCooldown = 3.0f;
[Export] public int MaxHealth = 100;
[Export] public bool ShowDamageNumbers = true;
protected BaseTile TargetTile;
protected ReactorTile Reactor;
protected float AttackTimer = 0;
protected Area2D DetectionArea;
protected Area2D AttackArea;
protected int CurrentHealthValue;
protected ProgressBar HealthBar;
protected GridManager Grid;
// Track collisions with potential targets
protected readonly HashSet<BaseTile> CollidingTiles = [];
public int CurrentHealth
{
get => CurrentHealthValue;
protected set
{
CurrentHealthValue = Mathf.Clamp(value, 0, MaxHealth);
UpdateHealthBar();
if (CurrentHealthValue <= 0)
{
Die();
}
}
}
public bool IsDead => CurrentHealthValue <= 0;
public override void _Ready()
{
CurrentHealthValue = MaxHealth;
Grid = DependencyInjection.Container.GetInstance<GridManager>();
Reactor = GetTree().GetFirstNodeInGroup(ReactorTile.ReactorGroupName) as ReactorTile;
InitializeHealthBar();
InitializeDetectionArea();
InitializeAttackArea();
AddToGroup(EnemyGroupName);
}
protected virtual void InitializeHealthBar()
{
HealthBar = new ProgressBar
{
MaxValue = MaxHealth,
Value = CurrentHealthValue,
Size = new Vector2(40, 4),
ShowPercentage = false,
Visible = false
};
var healthBarContainer = new Control();
healthBarContainer.AddChild(HealthBar);
AddChild(healthBarContainer);
healthBarContainer.Position = new Vector2(-20, -20);
}
protected virtual void InitializeDetectionArea()
{
DetectionArea = new Area2D();
var collisionShape = new CollisionShape2D();
var shape = new CircleShape2D();
shape.Radius = DetectionRadius;
collisionShape.Shape = shape;
DetectionArea.AddChild(collisionShape);
AddChild(DetectionArea);
DetectionArea.BodyEntered += OnBodyEnteredDetection;
DetectionArea.BodyExited += OnBodyExitedDetection;
}
protected virtual void InitializeAttackArea()
{
AttackArea = GetNodeOrNull<Area2D>("AttackArea");
if (AttackArea != null)
{
AttackArea.BodyEntered += OnBodyEntered;
AttackArea.BodyExited += OnBodyExited;
}
}
public override void _Process(double delta)
{
if (IsDead) return;
if (TargetTile == null || TargetTile.IsDestroyed || !TargetTile.IsInsideTree())
{
UpdateTarget();
}
MoveTowardsTarget();
HandleAttacks(delta);
}
protected virtual void MoveTowardsTarget()
{
if (TargetTile != null)
{
var direction = GlobalPosition.DirectionTo(TargetTile.GlobalPosition);
Velocity = direction * MoveSpeed;
LookAt(TargetTile.GlobalPosition);
MoveAndSlide();
}
}
protected virtual void HandleAttacks(double delta)
{
if (AttackTimer > 0)
{
AttackTimer -= (float)delta;
}
else if (CollidingTiles.Count > 0)
{
foreach (var tile in CollidingTiles)
{
if (tile != null && !tile.IsDestroyed && tile.IsInsideTree())
{
TryAttackTile(tile);
break;
}
}
}
}
protected virtual void UpdateTarget()
{
// If we have a valid target in collision, use that
foreach (var tile in CollidingTiles)
{
if (tile != null && !tile.IsDestroyed && tile.IsInsideTree())
{
TargetTile = tile;
return;
}
}
// Otherwise find the reactor
if (Reactor != null && !Reactor.IsDestroyed && Reactor.IsInsideTree())
{
TargetTile = Reactor;
}
else
{
TargetTile = null;
}
}
protected virtual void TryAttackTile(BaseTile tile)
{
if (IsDead || tile == null || tile.IsDestroyed || !tile.IsInsideTree())
return;
AttackTimer = AttackCooldown;
bool wasDestroyed = tile.TakeDamage(Damage);
GD.Print($"Attacking {tile.Name} for {Damage} damage. Was destroyed: {wasDestroyed}");
if (wasDestroyed)
{
CollidingTiles.Remove(tile);
if (TargetTile == tile)
{
TargetTile = null;
}
}
}
protected virtual void OnBodyEntered(Node2D body)
{
if (body is BaseTile { IsDestroyed: false } tile && !body.IsInGroup("Hostile"))
{
GD.Print($"[Enemy] {body.Name} Entered attack range");
CollidingTiles.Add(tile);
if (TargetTile == null || TargetTile.IsDestroyed || !TargetTile.IsInsideTree())
{
TargetTile = tile;
}
// Attack immediately on collision
TryAttackTile(tile);
}
}
protected virtual void OnBodyExited(Node2D body)
{
if (body.GetParent() is BaseTile tile)
{
CollidingTiles.Remove(tile);
if (TargetTile == tile)
{
TargetTile = null;
}
}
}
protected virtual void OnBodyEnteredDetection(Node2D body)
{
// Can be overridden by derived classes
}
protected virtual void OnBodyExitedDetection(Node2D body)
{
// Can be overridden by derived classes
}
public virtual void TakeDamage(int damage, Vector2? hitPosition = null)
{
if (IsDead) return;
CurrentHealth -= damage;
// Show damage number (optional)
if (ShowDamageNumbers)
{
var damageLabel = new Label
{
Text = $"-{damage}",
Position = hitPosition ?? GlobalPosition,
ZIndex = 1000
};
GetTree().CurrentScene.AddChild(damageLabel);
// Animate and remove damage number
var tween = CreateTween();
tween.TweenProperty(damageLabel, "position:y", damageLabel.Position.Y - 30, 0.5f);
tween.TweenCallback(Callable.From(() => damageLabel.QueueFree())).SetDelay(0.5f);
}
// Visual feedback
var originalModulate = Modulate;
Modulate = new Color(1, 0.5f, 0.5f); // Flash red
var tweenFlash = CreateTween();
tweenFlash.TweenProperty(this, "modulate", originalModulate, 0.2f);
}
protected virtual void UpdateHealthBar()
{
if (HealthBar != null)
{
HealthBar.Value = CurrentHealthValue;
HealthBar.Visible = CurrentHealthValue < MaxHealth; // Only show when damaged
}
}
protected virtual void Die()
{
// Play death animation/sound
// You can add a death animation here
// Disable collisions and hide
SetProcess(false);
SetPhysicsProcess(false);
Hide();
// Queue free after a delay (for any death animation/sound to play)
var timer = new Timer();
AddChild(timer);
timer.Timeout += () => QueueFree();
timer.Start(0.5f);
}
}

View File

@@ -0,0 +1 @@
uid://6oduws4kbdlf

View File

@@ -1,4 +1,3 @@
using System;
using AceFieldNewHorizon.Scripts.System; using AceFieldNewHorizon.Scripts.System;
using Godot; using Godot;

View File

@@ -43,11 +43,29 @@ public partial class GridManager : Node
public void FreeArea(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer = GridLayer.Building) public void FreeArea(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer = GridLayer.Building)
{ {
// Get all cells that should be occupied by this building
var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation); var occupiedCells = GridUtils.GetOccupiedCells(topLeft, size, rotation);
foreach (var cell in occupiedCells)
// Create a list to store cells that should be removed
var cellsToRemove = new List<Vector2I>();
// First, find all cells that match this building's position and size
foreach (var cell in _layers[layer].Keys.ToList())
{ {
if (_layers[layer].ContainsKey(cell)) var (building, buildingSize, buildingRotation) = _layers[layer][cell];
_layers[layer].Remove(cell); var buildingCells = GridUtils.GetOccupiedCells(cell, buildingSize, buildingRotation);
// If any of the building's cells match our target area, mark all of its cells for removal
if (buildingCells.Any(c => occupiedCells.Contains(c)))
{
cellsToRemove.AddRange(buildingCells);
}
}
// Remove all marked cells
foreach (var cell in cellsToRemove.Distinct())
{
_layers[layer].Remove(cell);
} }
} }

View File

@@ -8,11 +8,20 @@ public partial class Hud : CanvasLayer
private ResourceManager _resourceManager; private ResourceManager _resourceManager;
private VBoxContainer _resourceDisplay; private VBoxContainer _resourceDisplay;
private readonly Dictionary<string, Label> _resourceLabels = new(); private readonly Dictionary<string, Label> _resourceLabels = new();
private VBoxContainer _pickupLogContainer;
public override void _Ready() public override void _Ready()
{ {
_resourceDisplay = GetNode<VBoxContainer>("ResourceDisplay"); _resourceDisplay = GetNode<VBoxContainer>("ResourceDisplay");
_pickupLogContainer = new VBoxContainer();
_pickupLogContainer.Name = "PickupLogContainer";
_pickupLogContainer.SetAnchorsPreset(Control.LayoutPreset.BottomLeft);
_pickupLogContainer.OffsetLeft = 10;
_pickupLogContainer.OffsetBottom = -10; // Negative offset from bottom anchor
_pickupLogContainer.GrowVertical = Control.GrowDirection.Begin; // Make it grow upwards
AddChild(_pickupLogContainer);
_resourceManager = DependencyInjection.Container.GetInstance<ResourceManager>(); _resourceManager = DependencyInjection.Container.GetInstance<ResourceManager>();
if (_resourceManager == null) if (_resourceManager == null)
{ {
@@ -21,6 +30,7 @@ public partial class Hud : CanvasLayer
} }
_resourceManager.OnResourceChanged += UpdateResourceDisplay; _resourceManager.OnResourceChanged += UpdateResourceDisplay;
_resourceManager.OnResourcePickedUp += OnResourcePickedUp;
// Initialize display with current resources // Initialize display with current resources
foreach (var entry in _resourceManager.GetAllResources()) foreach (var entry in _resourceManager.GetAllResources())
@@ -54,11 +64,39 @@ public partial class Hud : CanvasLayer
} }
} }
private void OnResourcePickedUp(string resourceId, int amount)
{
AddPickupLogEntry($"Picked up {amount} {resourceId}!");
}
private void AddPickupLogEntry(string message)
{
var logLabel = new Label();
logLabel.Text = message;
_pickupLogContainer.AddChild(logLabel);
var timer = new Timer();
timer.WaitTime = 3.0f;
timer.OneShot = true;
timer.Autostart = true;
logLabel.AddChild(timer); // Add timer as child of the label
timer.Timeout += () => OnLogEntryTimeout(logLabel);
}
private void OnLogEntryTimeout(Label logLabel)
{
// Start fading out the label
var tween = logLabel.CreateTween();
tween.TweenProperty(logLabel, "modulate", new Color(1, 1, 1, 0), 0.5f); // Fade out over 0.5 seconds
tween.TweenCallback(Callable.From(() => logLabel.QueueFree())); // Free after fading
}
public override void _ExitTree() public override void _ExitTree()
{ {
if (_resourceManager != null) if (_resourceManager != null)
{ {
_resourceManager.OnResourceChanged -= UpdateResourceDisplay; _resourceManager.OnResourceChanged -= UpdateResourceDisplay;
_resourceManager.OnResourcePickedUp -= OnResourcePickedUp;
} }
} }
} }

View File

@@ -55,19 +55,22 @@ public partial class NaturalResourceGenerator : Node2D
return; return;
} }
// Test if enemy_nest is in the registry // Test if enemy_portal is in the registry
var testBuilding = Registry.GetBuilding("enemy_nest"); var testBuilding = Registry.GetBuilding("enemy_portal");
if (testBuilding == null) if (testBuilding == null)
{ {
GD.PrintErr($"{LogPrefix} 'enemy_nest' is not found in BuildingRegistry!"); GD.PrintErr($"{LogPrefix} 'enemy_portal' is not found in BuildingRegistry!");
} }
else else
{ {
GD.Print($"{LogPrefix} Found enemy_nest in registry!"); GD.Print($"{LogPrefix} Found enemy_portal in registry!");
} }
GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}"); GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
GD.Print($"{LogPrefix} Spawning the core reactor...");
SpawnCoreReactor();
if (SpawnEnemyNest) if (SpawnEnemyNest)
{ {
GD.Print($"{LogPrefix} Attempting to spawn enemy nest..."); GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
@@ -284,9 +287,9 @@ public partial class NaturalResourceGenerator : Node2D
// Remove all tiles in this chunk // Remove all tiles in this chunk
var chunkWorldPos = ChunkToWorldCoords(chunkPos); var chunkWorldPos = ChunkToWorldCoords(chunkPos);
for (int x = 0; x < ChunkSize; x++) for (var x = 0; x < ChunkSize; x++)
{ {
for (int y = 0; y < ChunkSize; y++) for (var y = 0; y < ChunkSize; y++)
{ {
var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y); var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
// Free a 1x1 area for each cell // Free a 1x1 area for each cell
@@ -393,6 +396,12 @@ public partial class NaturalResourceGenerator : Node2D
GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles"); GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles");
} }
private void SpawnCoreReactor()
{
// Place the reactor tile at the center of the map
PlaceTile("reactor", new Vector2I(0, 0));
}
private void SpawnRandomEnemyNest() private void SpawnRandomEnemyNest()
{ {
// Generate a random position within the specified distance from origin // Generate a random position within the specified distance from origin
@@ -402,12 +411,12 @@ public partial class NaturalResourceGenerator : Node2D
var nestPosition = new Vector2I((int)offset.X, (int)offset.Y); var nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
// Try to find a valid position for the nest // Try to find a valid position for the nest
int attempts = 0; var attempts = 0;
const int maxAttempts = 10; const int maxAttempts = 10;
while (attempts < maxAttempts) while (attempts < maxAttempts)
{ {
if (PlaceTile("enemy_nest", nestPosition)) if (PlaceTile("enemy_portal", nestPosition))
{ {
GD.Print($"{LogPrefix} Placed enemy nest at {nestPosition}"); GD.Print($"{LogPrefix} Placed enemy nest at {nestPosition}");
return; return;
@@ -428,25 +437,18 @@ public partial class NaturalResourceGenerator : Node2D
{ {
try try
{ {
// For enemy nest, we want to place it on top of existing ground
if (tileType != "enemy_nest")
{
// Original behavior for other tile types
Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
}
else
{
// For enemy nest, check if there's ground below (skip for now)
GD.Print($"{LogPrefix} Attempting placing nest at {cell}.");
}
var building = Registry.GetBuilding(tileType); var building = Registry.GetBuilding(tileType);
if (building == null) if (building == null)
{ {
GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}"); GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
return false; return false;
} }
// GD.Print($"{LogPrefix} Found building in registry: {tileType}");
// Free area for ground layer if needed
if (building.Layer == GridLayer.Ground)
Grid.FreeArea(cell, building.Size, 0f, GridLayer.Ground);
else
GD.Print($"{LogPrefix} Attempting placing building tile {tileType} at {cell}.");
var scene = building.Scene; var scene = building.Scene;
if (scene == null) if (scene == null)
@@ -454,29 +456,24 @@ public partial class NaturalResourceGenerator : Node2D
GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}"); GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
return false; return false;
} }
// GD.Print($"{LogPrefix} Scene loaded for {tileType}");
if (scene.Instantiate() is not BaseTile instance) if (scene.Instantiate() is not BaseTile instance)
{ {
GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}"); GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
return false; return false;
} }
// GD.Print($"{LogPrefix} Successfully instantiated {tileType}");
// Use the same positioning logic as PlacementManager // Calculate position with proper offset based on building size
var rotatedSize = building.GetRotatedSize(0f); var rotatedSize = building.GetRotatedSize(0f);
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f); var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
instance.ZIndex = (int)building.Layer; instance.ZIndex = (int)building.Layer;
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset; instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
instance.Grid = Grid;
AddChild(instance); AddChild(instance);
// For enemy nest, use Building layer // Occupy the appropriate area based on building size
var layer = tileType == "enemy_nest" ? GridLayer.Building : building.Layer; Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer);
Grid.OccupyArea(cell, instance, building.Size, 0f, layer);
// GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} on layer {layer}");
return true; return true;
} }
catch (Exception e) catch (Exception e)

View File

@@ -203,7 +203,6 @@ public partial class PlacementManager : Node2D
var scene = building.Scene; var scene = building.Scene;
_ghostBuilding = (BaseTile)scene.Instantiate(); _ghostBuilding = (BaseTile)scene.Instantiate();
_ghostBuilding.Grid = Grid;
_ghostBuilding.SetGhostMode(true); _ghostBuilding.SetGhostMode(true);
_ghostBuilding.RotationDegrees = _currentRotation; _ghostBuilding.RotationDegrees = _currentRotation;
_ghostBuilding.ZAsRelative = false; _ghostBuilding.ZAsRelative = false;
@@ -251,7 +250,6 @@ public partial class PlacementManager : Node2D
// Create the building instance // Create the building instance
var scene = building.Scene; var scene = building.Scene;
var buildingInstance = (BaseTile)scene.Instantiate(); var buildingInstance = (BaseTile)scene.Instantiate();
buildingInstance.Grid = Grid;
buildingInstance.RotationDegrees = _currentRotation; buildingInstance.RotationDegrees = _currentRotation;
buildingInstance.ZIndex = (int)building.Layer; buildingInstance.ZIndex = (int)building.Layer;
buildingInstance.Position = _ghostBuilding.Position; buildingInstance.Position = _ghostBuilding.Position;
@@ -430,32 +428,28 @@ public static class GridManagerExtensions
public static (Vector2I Position, Vector2I Size, float Rotation)? GetBuildingInfoAtCell(this GridManager grid, public static (Vector2I Position, Vector2I Size, float Rotation)? GetBuildingInfoAtCell(this GridManager grid,
Vector2I cell, GridLayer layer) Vector2I cell, GridLayer layer)
{ {
if (grid.GetTileAtCell(cell, layer) is { } building) if (grid.GetTileAtCell(cell, layer) is not { } building) return null;
// Find the top-left position of the building
for (var x = 0; x < 100; x++) // Arbitrary max size
{ {
// Find the top-left position of the building for (var y = 0; y < 100; y++)
for (int x = 0; x < 100; x++) // Arbitrary max size
{ {
for (int y = 0; y < 100; y++) var checkCell = new Vector2I(cell.X - x, cell.Y - y);
{ if (grid.GetTileAtCell(checkCell, layer) != building) continue;
var checkCell = new Vector2I(cell.X - x, cell.Y - y); // Found the top-left corner, now find the size
if (grid.GetTileAtCell(checkCell, layer) == building) var size = Vector2I.One;
{ // Search right
// Found the top-left corner, now find the size while (grid.GetTileAtCell(new Vector2I(checkCell.X + size.X, checkCell.Y), layer) ==
var size = Vector2I.One; building)
// Search right size.X++;
while (grid.GetTileAtCell(new Vector2I(checkCell.X + size.X, checkCell.Y), layer) == // Search down
building) while (grid.GetTileAtCell(new Vector2I(checkCell.X, checkCell.Y + size.Y), layer) ==
size.X++; building)
// Search down size.Y++;
while (grid.GetTileAtCell(new Vector2I(checkCell.X, checkCell.Y + size.Y), layer) ==
building)
size.Y++;
// Get rotation from the first cell // Get rotation from the first cell
var rotation = 0f; // You'll need to store rotation in GridManager to make this work var rotation = 0f; // You'll need to store rotation in Grid to make this work
return (checkCell, size, rotation); return (checkCell, size, rotation);
}
}
} }
} }

View File

@@ -14,6 +14,10 @@ public partial class ResourceManager : Node
[Signal] [Signal]
public delegate void OnResourceChangedEventHandler(string resourceId, int newAmount); public delegate void OnResourceChangedEventHandler(string resourceId, int newAmount);
// Event for when resources are picked up (added)
[Signal]
public delegate void OnResourcePickedUpEventHandler(string resourceId, int amount);
public override void _Ready() public override void _Ready()
{ {
base._Ready(); base._Ready();
@@ -43,6 +47,7 @@ public partial class ResourceManager : Node
} }
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]); EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
EmitSignal(nameof(OnResourcePickedUp), resourceId, amount);
} }
// Remove resources of a specific type // Remove resources of a specific type

View File

@@ -9,23 +9,39 @@ namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class BaseTile : Node2D public partial class BaseTile : Node2D
{ {
[Export] public string TileId { get; set; } [Export] public string TileId { get; set; }
[Export] public GridManager Grid { get; set; }
protected GridManager Grid { get; set; }
protected BuildingRegistry Registry { get; set; }
public int MaxDurability { get; private set; }
public int CurrentDurability { get; private set; }
public bool IsDestroyed { get; private set; }
private CollisionShape2D _collisionShape; private CollisionShape2D _collisionShape;
private Sprite2D _sprite; private Sprite2D _sprite;
private ColorRect _progressOverlay; private ColorRect _progressOverlay;
private Action _onConstructionComplete; private Action _onConstructionComplete;
private Tween _damageTween;
public bool IsConstructing; public bool IsConstructing;
public bool IsConstructed; public bool IsConstructed;
public override void _Ready() public override void _Ready()
{ {
Grid = DependencyInjection.Container.GetInstance<GridManager>();
Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
_collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D"); _collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D");
_sprite = GetNodeOrNull<Sprite2D>("Sprite2D"); _sprite = GetNodeOrNull<Sprite2D>("Sprite2D");
_progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay"); _progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay");
if (_progressOverlay != null) if (_progressOverlay != null)
_progressOverlay.Visible = false; _progressOverlay.Visible = false;
// Get durability from BuildingRegistry
var buildingData = Registry?.GetBuilding(TileId);
MaxDurability = buildingData?.Durability ?? 100; // Default to 100 if not found
CurrentDurability = MaxDurability;
IsDestroyed = false;
} }
public virtual void SetGhostMode(bool canPlace) public virtual void SetGhostMode(bool canPlace)
@@ -50,6 +66,81 @@ public partial class BaseTile : Node2D
_sprite.Modulate = Colors.White; _sprite.Modulate = Colors.White;
} }
public virtual bool TakeDamage(int damage)
{
if (IsDestroyed || IsConstructing) return false;
GD.Print($"[Tile] {TileId} {GetInstanceId()} took {damage} damage");
CurrentDurability = Mathf.Max(0, CurrentDurability - damage);
// Visual feedback for taking damage
ShowDamageEffect();
if (CurrentDurability <= 0)
{
Destroy();
return true; // Tile was destroyed
}
return false; // Tile is still alive
}
private void ShowDamageEffect()
{
if (_sprite == null) return;
// Cancel any existing tween
_damageTween?.Kill();
_damageTween = CreateTween();
// Flash red briefly
_damageTween.TweenProperty(_sprite, "modulate", Colors.Red, 0.1f);
_damageTween.TweenProperty(_sprite, "modulate", Colors.White, 0.1f);
// Shake effect
var originalPosition = _sprite.Position;
_damageTween.TweenMethod(
Callable.From<Vector2>(pos => _sprite.Position = pos),
originalPosition + new Vector2(-2, -2),
originalPosition + new Vector2(2, 2),
0.05f
).SetTrans(Tween.TransitionType.Bounce).SetEase(Tween.EaseType.InOut);
_damageTween.TweenMethod(
Callable.From<Vector2>(pos => _sprite.Position = pos),
originalPosition + new Vector2(2, 2),
originalPosition,
0.05f
).SetTrans(Tween.TransitionType.Bounce).SetEase(Tween.EaseType.InOut);
}
public virtual void Destroy()
{
if (IsDestroyed) return;
IsDestroyed = true;
// Disable collision using SetDeferred to avoid physics update conflicts
_collisionShape?.SetDeferred("disabled", true);
// Fade out and remove
var tween = CreateTween();
tween.TweenProperty(this, "modulate:a", 0f, 0.3f);
tween.TweenCallback(Callable.From(() => CallDeferred("queue_free")));
// Emit signal or call method when tile is destroyed
CallDeferred(nameof(OnTileDestroyed));
}
protected virtual void OnTileDestroyed()
{
// Can be overridden by derived classes for custom destruction behavior
var cell = GridUtils.WorldToGrid(Position);
var buildingInfo = Registry.GetBuilding(TileId);
Grid.FreeArea(cell, buildingInfo.Size, Rotation, buildingInfo.Layer);
}
// Building progress visualization // Building progress visualization
public void StartConstruction(float buildTime, Action onComplete = null) public void StartConstruction(float buildTime, Action onComplete = null)
{ {

View File

@@ -1,74 +0,0 @@
using AceFieldNewHorizon.Scripts.Entities;
using Godot;
namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class EnemyNestTile : BaseTile
{
[Export] public PackedScene EnemyScene;
[Export] public int MaxEnemies = 5;
[Export] public float SpawnDelay = 5.0f; // Time between spawn attempts
[Export] public float SpawnRadius = 50.0f; // Radius around the nest where enemies can spawn
[Export] public bool Active = true;
private Timer _spawnTimer;
private int _currentEnemyCount = 0;
public override void _Ready()
{
base._Ready();
// Create and configure the timer
_spawnTimer = new Timer
{
Autostart = true,
WaitTime = SpawnDelay
};
AddChild(_spawnTimer);
_spawnTimer.Timeout += OnSpawnTimerTimeout;
}
private void OnSpawnTimerTimeout()
{
if (!Active || EnemyScene == null || _currentEnemyCount >= MaxEnemies)
return;
// Check if we can spawn more enemies
var enemies = GetTree().GetNodesInGroup("Enemy");
_currentEnemyCount = enemies.Count;
if (_currentEnemyCount >= MaxEnemies)
return;
// Spawn a new enemy
var enemy = EnemyScene.Instantiate<Enemy>();
if (enemy != null)
{
GetParent().AddChild(enemy);
// Calculate a random position within the spawn radius
var randomAngle = GD.Randf() * Mathf.Pi * 2;
var randomOffset = new Vector2(
Mathf.Cos(randomAngle) * SpawnRadius,
Mathf.Sin(randomAngle) * SpawnRadius
);
enemy.GlobalPosition = GlobalPosition + randomOffset;
_currentEnemyCount++;
// Connect to the enemy's death signal if available
enemy.TreeExiting += () => OnEnemyDied();
}
}
private void OnEnemyDied()
{
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
}
public void SetActive(bool active)
{
Active = active;
_spawnTimer.Paused = !active;
}
}

View File

@@ -0,0 +1,129 @@
using AceFieldNewHorizon.Scripts.Entities;
using Godot;
namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class EnemyPortalTile : BaseTile
{
[Export] public PackedScene EnemyScene;
[Export] public int MaxEnemies = 5;
[Export] public float SpawnDelay = 5.0f; // Time between spawn attempts
[Export] public float SpawnRadius = 50.0f; // Radius around the nest where enemies can spawn
[Export] public bool Active = true;
private Timer _spawnTimer;
private int _currentEnemyCount;
public override void _Ready()
{
base._Ready();
// Add to Hostile group to prevent enemies from attacking their own nest
AddToGroup("Hostile");
// Create and configure the timer
_spawnTimer = new Timer
{
Autostart = true,
WaitTime = SpawnDelay
};
AddChild(_spawnTimer);
_spawnTimer.Timeout += OnSpawnTimerTimeout;
var sprite = GetNode<Sprite2D>("Sprite2D");
// Create and configure the shadow sprite
var shadow = new Sprite2D
{
Texture = sprite.Texture,
Scale = sprite.Scale * 1.05f, // Slightly larger than the original
Modulate = new Color(0, 0, 0, 0.5f), // Slightly more transparent
Position = new Vector2(0, 6), // Closer to the sprite (reduced from 30)
ZIndex = -1
};
AddChild(shadow);
// Create floating animation
const float floatOffset = 5.0f;
const float floatDuration = 2.0f;
var tween = CreateTween().SetLoops();
tween.TweenProperty(sprite, "position:y", -floatOffset, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.TweenProperty(sprite, "position:y", floatOffset, floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.TweenProperty(sprite, "position:y", 0, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
// Animate shadow
tween.Parallel().TweenProperty(shadow, "position:y", 12 - (floatOffset * 0.3f), floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.Parallel().TweenProperty(shadow, "scale", sprite.Scale * 1.02f, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.Parallel().TweenProperty(shadow, "modulate:a", 0.6f, floatDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine);
tween.Parallel().TweenProperty(shadow, "position:y", 12 + (floatOffset * 0.4f), floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine)
.SetDelay(floatDuration);
tween.Parallel().TweenProperty(shadow, "scale", sprite.Scale * 1.08f, floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine)
.SetDelay(floatDuration);
tween.Parallel().TweenProperty(shadow, "modulate:a", 0.4f, floatDuration * 2)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Sine)
.SetDelay(floatDuration);
}
private void OnSpawnTimerTimeout()
{
if (!Active || EnemyScene == null || _currentEnemyCount >= MaxEnemies)
return;
// Check if we can spawn more enemies
var enemies = GetTree().GetNodesInGroup("Enemy");
_currentEnemyCount = enemies.Count;
if (_currentEnemyCount >= MaxEnemies)
return;
// Spawn a new enemy
var enemy = EnemyScene.Instantiate<Enemy>();
if (enemy != null)
{
GetParent().AddChild(enemy);
// Calculate a random position within the spawn radius
var randomAngle = GD.Randf() * Mathf.Pi * 2;
var randomOffset = new Vector2(
Mathf.Cos(randomAngle) * SpawnRadius,
Mathf.Sin(randomAngle) * SpawnRadius
);
enemy.GlobalPosition = GlobalPosition + randomOffset;
_currentEnemyCount++;
// Connect to the enemy's death signal if available
enemy.TreeExiting += () => OnEnemyDied();
}
}
private void OnEnemyDied()
{
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
}
public void SetActive(bool active)
{
Active = active;
_spawnTimer.Paused = !active;
}
}

View File

@@ -8,6 +8,7 @@ public partial class GroundTile : BaseTile
{ {
var sprite = GetNode<Sprite2D>("Sprite2D"); var sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Modulate = new Color(0.75f, 0.75f, 0.75f); // Makes the sprite 25% darker sprite.Modulate = new Color(0.75f, 0.75f, 0.75f); // Makes the sprite 25% darker
sprite.ZIndex = -10;
base._Ready(); base._Ready();
} }

View File

@@ -0,0 +1,13 @@
namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class ReactorTile : BaseTile
{
public static readonly string ReactorGroupName = "Reactor";
public override void _Ready()
{
base._Ready();
AddToGroup(ReactorGroupName);
}
}

View File

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

View File

@@ -121,7 +121,7 @@ public partial class TurretTile : BaseTile
// Reset attack cooldown // Reset attack cooldown
_attackTimer = AttackCooldown; _attackTimer = AttackCooldown;
GD.Print("Turret attacking enemy!"); GD.Print("[Turret] Turret firing!");
} }
} }
} }