♻️ Optimizations of the various system
🍱 Retexture of the enemy portal
This commit is contained in:
@@ -42,14 +42,14 @@
|
|||||||
"layer": 1,
|
"layer": 1,
|
||||||
"size": [3, 3]
|
"size": [3, 3]
|
||||||
},
|
},
|
||||||
"enemy_nest": {
|
"enemy_portal": {
|
||||||
"scene": "res://Scenes/Tiles/EnemyNest.tscn",
|
"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",
|
||||||
|
@@ -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")
|
|
BIN
Scenes/Tiles/EnemyPortalTile.png
Normal file
BIN
Scenes/Tiles/EnemyPortalTile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 431 KiB |
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://vwfs68ftvjr4"
|
uid="uid://dv2xwfyshxdtp"
|
||||||
path="res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex"
|
path="res://.godot/imported/EnemyPortalTile.png-3904776a211e67c58254b1bdc9aba071.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Scenes/Tiles/EnemyNest.jpg"
|
source_file="res://Scenes/Tiles/EnemyPortalTile.png"
|
||||||
dest_files=["res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex"]
|
dest_files=["res://.godot/imported/EnemyPortalTile.png-3904776a211e67c58254b1bdc9aba071.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
21
Scenes/Tiles/EnemyPortalTile.tscn
Normal file
21
Scenes/Tiles/EnemyPortalTile.tscn
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://dup2su0s3ybcy"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://26hl5mk4mqur" path="res://Scripts/Tiles/EnemyPortalTile.cs" id="1_o543x"]
|
||||||
|
[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")
|
||||||
|
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")
|
@@ -5,266 +5,9 @@ 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 AttackRadius = 80.0f;
|
|
||||||
[Export] public int Damage = 10;
|
|
||||||
[Export] public float AttackCooldown = 1.0f;
|
|
||||||
[Export] public int MaxHealth = 100;
|
|
||||||
[Export] public bool ShowDamageNumbers = true;
|
|
||||||
|
|
||||||
private BaseTile _targetTile;
|
|
||||||
private ReactorTile _reactor;
|
|
||||||
private float _attackTimer = 0;
|
|
||||||
private Area2D _detectionArea;
|
|
||||||
private Area2D _attackArea;
|
|
||||||
private int _currentHealth;
|
|
||||||
private ProgressBar _healthBar;
|
|
||||||
private GridManager _gridManager;
|
|
||||||
|
|
||||||
// Track collisions with potential targets
|
|
||||||
private readonly HashSet<BaseTile> _collidingTiles = [];
|
|
||||||
|
|
||||||
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;
|
|
||||||
_gridManager = DependencyInjection.Container.GetInstance<GridManager>();
|
|
||||||
|
|
||||||
// Find the reactor in the scene
|
|
||||||
_reactor = GetTree().GetFirstNodeInGroup(ReactorTile.ReactorGroupName) as ReactorTile;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Create detection area for finding targets
|
|
||||||
_detectionArea = new Area2D();
|
|
||||||
var collisionShape = new CollisionShape2D();
|
|
||||||
var shape = new CircleShape2D();
|
|
||||||
shape.Radius = DetectionRadius;
|
|
||||||
collisionShape.Shape = shape;
|
|
||||||
_detectionArea.AddChild(collisionShape);
|
|
||||||
AddChild(_detectionArea);
|
|
||||||
|
|
||||||
_attackArea = GetNodeOrNull<Area2D>("AttackArea");
|
|
||||||
|
|
||||||
// Connect signals
|
|
||||||
_detectionArea.BodyEntered += OnBodyEnteredDetection;
|
|
||||||
_detectionArea.BodyExited += OnBodyExitedDetection;
|
|
||||||
if (_attackArea != null)
|
|
||||||
{
|
|
||||||
_attackArea.BodyEntered += OnBodyEntered;
|
|
||||||
_attackArea.BodyExited += OnBodyExited;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddToGroup(EnemyGroupName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
if (IsDead) return;
|
|
||||||
|
|
||||||
// Find the best target if we don't have one
|
|
||||||
if (_targetTile == null || _targetTile.IsDestroyed || !_targetTile.IsInsideTree())
|
|
||||||
{
|
|
||||||
UpdateTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move towards current target
|
|
||||||
if (_targetTile != null)
|
|
||||||
{
|
|
||||||
var direction = GlobalPosition.DirectionTo(_targetTile.GlobalPosition);
|
|
||||||
Velocity = direction * MoveSpeed;
|
|
||||||
LookAt(_targetTile.GlobalPosition);
|
|
||||||
MoveAndSlide();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attack any colliding tiles
|
|
||||||
if (_attackTimer > 0)
|
|
||||||
{
|
|
||||||
_attackTimer -= (float)delta;
|
|
||||||
}
|
|
||||||
else if (_collidingTiles.Count > 0)
|
|
||||||
{
|
|
||||||
// Attack the first colliding tile
|
|
||||||
foreach (var tile in _collidingTiles)
|
|
||||||
{
|
|
||||||
if (tile != null && !tile.IsDestroyed && tile.IsInsideTree())
|
|
||||||
{
|
|
||||||
TryAttackTile(tile);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBodyExited(Node2D body)
|
|
||||||
{
|
|
||||||
if (body.GetParent() is BaseTile tile)
|
|
||||||
{
|
|
||||||
_collidingTiles.Remove(tile);
|
|
||||||
if (_targetTile == tile)
|
|
||||||
{
|
|
||||||
_targetTile = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBodyEnteredDetection(Node2D body)
|
|
||||||
{
|
|
||||||
// Keep this for initial target detection if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBodyExitedDetection(Node2D body)
|
|
||||||
{
|
|
||||||
// Keep this for target cleanup if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
283
Scripts/Entities/EnemyBase.cs
Normal file
283
Scripts/Entities/EnemyBase.cs
Normal 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 = 1.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);
|
||||||
|
}
|
||||||
|
}
|
1
Scripts/Entities/EnemyBase.cs.uid
Normal file
1
Scripts/Entities/EnemyBase.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://6oduws4kbdlf
|
@@ -43,10 +43,28 @@ 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())
|
||||||
|
{
|
||||||
|
var (building, buildingSize, buildingRotation) = _layers[layer][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())
|
||||||
{
|
{
|
||||||
if (_layers[layer].ContainsKey(cell))
|
|
||||||
_layers[layer].Remove(cell);
|
_layers[layer].Remove(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,15 +55,15 @@ 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}");
|
||||||
@@ -287,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
|
||||||
@@ -416,7 +416,7 @@ public partial class NaturalResourceGenerator : Node2D
|
|||||||
|
|
||||||
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,16 +428,14 @@ 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
|
// Find the top-left position of the building
|
||||||
for (int x = 0; x < 100; x++) // Arbitrary max size
|
for (var x = 0; x < 100; x++) // Arbitrary max size
|
||||||
{
|
{
|
||||||
for (int y = 0; y < 100; y++)
|
for (var y = 0; y < 100; y++)
|
||||||
{
|
{
|
||||||
var checkCell = new Vector2I(cell.X - x, cell.Y - y);
|
var checkCell = new Vector2I(cell.X - x, cell.Y - y);
|
||||||
if (grid.GetTileAtCell(checkCell, layer) == building)
|
if (grid.GetTileAtCell(checkCell, layer) != building) continue;
|
||||||
{
|
|
||||||
// Found the top-left corner, now find the size
|
// Found the top-left corner, now find the size
|
||||||
var size = Vector2I.One;
|
var size = Vector2I.One;
|
||||||
// Search right
|
// Search right
|
||||||
@@ -450,12 +448,10 @@ public static class GridManagerExtensions
|
|||||||
size.Y++;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -136,6 +136,9 @@ public partial class BaseTile : Node2D
|
|||||||
protected virtual void OnTileDestroyed()
|
protected virtual void OnTileDestroyed()
|
||||||
{
|
{
|
||||||
// Can be overridden by derived classes for custom destruction behavior
|
// 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
|
||||||
|
@@ -1,77 +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();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
129
Scripts/Tiles/EnemyPortalTile.cs
Normal file
129
Scripts/Tiles/EnemyPortalTile.cs
Normal 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 = 0;
|
||||||
|
|
||||||
|
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, 12), // 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user