✨ Turret shooting and damaing
This commit is contained in:
BIN
Scenes/Entities/Bullet.png
Normal file
BIN
Scenes/Entities/Bullet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 659 KiB |
34
Scenes/Entities/Bullet.png.import
Normal file
34
Scenes/Entities/Bullet.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b5mb8tu15rc2p"
|
||||||
|
path="res://.godot/imported/Bullet.png-db4c50cb16094f39ca6a1b9de30a2fe2.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Scenes/Entities/Bullet.png"
|
||||||
|
dest_files=["res://.godot/imported/Bullet.png-db4c50cb16094f39ca6a1b9de30a2fe2.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
|
18
Scenes/Entities/Bullet.tscn
Normal file
18
Scenes/Entities/Bullet.tscn
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://erqawdsydh6a"]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" uid="uid://b5mb8tu15rc2p" path="res://Scenes/Entities/Bullet.png" id="1_fi8au"]
|
||||||
|
[ext_resource type="Script" uid="uid://vgx2a8gm7l8b" path="res://Scripts/Entities/Bullet.cs" id="1_k5b1m"]
|
||||||
|
|
||||||
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_fi8au"]
|
||||||
|
size = Vector2(44, 12)
|
||||||
|
|
||||||
|
[node name="Bullet" type="Area2D"]
|
||||||
|
collision_mask = 2
|
||||||
|
script = ExtResource("1_k5b1m")
|
||||||
|
|
||||||
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
|
scale = Vector2(0.03, 0.03)
|
||||||
|
texture = ExtResource("1_fi8au")
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
|
shape = SubResource("RectangleShape2D_fi8au")
|
@@ -7,6 +7,8 @@
|
|||||||
size = Vector2(60, 74)
|
size = Vector2(60, 74)
|
||||||
|
|
||||||
[node name="Enemy" type="CharacterBody2D"]
|
[node name="Enemy" type="CharacterBody2D"]
|
||||||
|
collision_layer = 2
|
||||||
|
collision_mask = 3
|
||||||
script = ExtResource("1_jajit")
|
script = ExtResource("1_jajit")
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
[ext_resource type="Texture2D" uid="uid://jye6c2ehuxtg" path="res://Scenes/Entities/Player.png" id="1_ucweq"]
|
[ext_resource type="Texture2D" uid="uid://jye6c2ehuxtg" path="res://Scenes/Entities/Player.png" id="1_ucweq"]
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody2D"]
|
[node name="Player" type="CharacterBody2D"]
|
||||||
|
collision_mask = 3
|
||||||
script = ExtResource("1_08t41")
|
script = ExtResource("1_08t41")
|
||||||
MinZoom = 0.1
|
MinZoom = 0.1
|
||||||
MaxZoom = 5.0
|
MaxZoom = 5.0
|
||||||
|
@@ -8,6 +8,7 @@ size = Vector2(54, 54)
|
|||||||
|
|
||||||
[node name="GroundTile" type="StaticBody2D"]
|
[node name="GroundTile" type="StaticBody2D"]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
|
collision_mask = 0
|
||||||
script = ExtResource("1_mqsaf")
|
script = ExtResource("1_mqsaf")
|
||||||
TileId = "ground"
|
TileId = "ground"
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ size = Vector2(54, 54)
|
|||||||
|
|
||||||
[node name="OreIronTile" type="StaticBody2D"]
|
[node name="OreIronTile" type="StaticBody2D"]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
|
collision_mask = 0
|
||||||
script = ExtResource("1_exnim")
|
script = ExtResource("1_exnim")
|
||||||
TileId = "ore_iron"
|
TileId = "ore_iron"
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ size = Vector2(54, 54)
|
|||||||
|
|
||||||
[node name="StoneTile" type="StaticBody2D"]
|
[node name="StoneTile" type="StaticBody2D"]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
|
collision_mask = 0
|
||||||
script = ExtResource("1_rndy8")
|
script = ExtResource("1_rndy8")
|
||||||
TileId = "stone"
|
TileId = "stone"
|
||||||
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://dbup2pvjl8het"]
|
[gd_scene load_steps=6 format=3 uid="uid://dbup2pvjl8het"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://n5g6i0uovxfk" path="res://Scripts/Tiles/TurretTile.cs" id="1_j3157"]
|
[ext_resource type="Script" uid="uid://n5g6i0uovxfk" path="res://Scripts/Tiles/TurretTile.cs" id="1_j3157"]
|
||||||
[ext_resource type="Texture2D" uid="uid://ckssi7soymu7g" path="res://Scenes/Tiles/TurretTile.png" id="2_7ljeh"]
|
[ext_resource type="Texture2D" uid="uid://ckssi7soymu7g" path="res://Scenes/Tiles/TurretTile.png" id="2_7ljeh"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://erqawdsydh6a" path="res://Scenes/Entities/Bullet.tscn" id="2_gfad6"]
|
||||||
[ext_resource type="Texture2D" uid="uid://dmbbkwgff7dej" path="res://Scenes/Tiles/TurretTileBarrel.png" id="3_gfad6"]
|
[ext_resource type="Texture2D" uid="uid://dmbbkwgff7dej" path="res://Scenes/Tiles/TurretTileBarrel.png" id="3_gfad6"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_pndcb"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_pndcb"]
|
||||||
@@ -9,6 +10,8 @@ size = Vector2(54, 54)
|
|||||||
|
|
||||||
[node name="Turret" type="StaticBody2D"]
|
[node name="Turret" type="StaticBody2D"]
|
||||||
script = ExtResource("1_j3157")
|
script = ExtResource("1_j3157")
|
||||||
|
BulletScene = ExtResource("2_gfad6")
|
||||||
|
BarrelTipPath = NodePath("Barrel/Marker2D")
|
||||||
TileId = "turret"
|
TileId = "turret"
|
||||||
|
|
||||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
@@ -21,6 +24,10 @@ scale = Vector2(0.08, 0.08)
|
|||||||
texture = ExtResource("3_gfad6")
|
texture = ExtResource("3_gfad6")
|
||||||
offset = Vector2(0, -54)
|
offset = Vector2(0, -54)
|
||||||
|
|
||||||
|
[node name="Marker2D" type="Marker2D" parent="Barrel"]
|
||||||
|
position = Vector2(0, 37.5)
|
||||||
|
scale = Vector2(12.5, 12.5)
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
shape = SubResource("RectangleShape2D_pndcb")
|
shape = SubResource("RectangleShape2D_pndcb")
|
||||||
|
|
||||||
|
97
Scripts/Entities/Bullet.cs
Normal file
97
Scripts/Entities/Bullet.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace AceFieldNewHorizon.Scripts.Entities;
|
||||||
|
|
||||||
|
public partial class Bullet : Area2D
|
||||||
|
{
|
||||||
|
[Export] public float Speed = 400.0f;
|
||||||
|
[Export] public int Damage = 10;
|
||||||
|
[Export] public float MaxDistance = 1000.0f;
|
||||||
|
|
||||||
|
private Vector2 _direction = Vector2.Right;
|
||||||
|
private Vector2 _startPosition;
|
||||||
|
private float _distanceTraveled = 0f;
|
||||||
|
private bool _hasHit = false;
|
||||||
|
private uint _ignoreCollisionLayer = 0; // Layer to ignore (will be set by turret)
|
||||||
|
|
||||||
|
public void Initialize(Vector2 direction, Vector2 position, float rotation, uint ignoreLayer = 0)
|
||||||
|
{
|
||||||
|
_direction = direction.Normalized();
|
||||||
|
Position = position;
|
||||||
|
Rotation = rotation;
|
||||||
|
_startPosition = position;
|
||||||
|
_ignoreCollisionLayer = ignoreLayer;
|
||||||
|
|
||||||
|
// Connect the area entered signal
|
||||||
|
BodyEntered += OnBodyEntered;
|
||||||
|
AreaEntered += OnAreaEntered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
if (_hasHit) return;
|
||||||
|
|
||||||
|
// Move the bullet
|
||||||
|
var movement = _direction * Speed * (float)delta;
|
||||||
|
Position += movement;
|
||||||
|
_distanceTraveled += movement.Length();
|
||||||
|
|
||||||
|
// Check if bullet has traveled max distance
|
||||||
|
if (_distanceTraveled >= MaxDistance)
|
||||||
|
{
|
||||||
|
QueueFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBodyEntered(Node2D body)
|
||||||
|
{
|
||||||
|
HandleCollision(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAreaEntered(Area2D area)
|
||||||
|
{
|
||||||
|
HandleCollision(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCollision(Node2D node)
|
||||||
|
{
|
||||||
|
if (_hasHit) return;
|
||||||
|
|
||||||
|
// Skip collision if it's on the ignore layer
|
||||||
|
if (node is PhysicsBody2D physicsBody &&
|
||||||
|
(physicsBody.CollisionLayer & _ignoreCollisionLayer) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasHit = true;
|
||||||
|
|
||||||
|
// If we hit an enemy, deal damage
|
||||||
|
if (node is Enemy enemy)
|
||||||
|
{
|
||||||
|
// Get the global position where the bullet hit
|
||||||
|
var hitPosition = GlobalPosition;
|
||||||
|
enemy.TakeDamage(Damage, hitPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Add hit effect here
|
||||||
|
// CreateHitEffect();
|
||||||
|
|
||||||
|
// Remove the bullet
|
||||||
|
QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateHitEffect()
|
||||||
|
{
|
||||||
|
// You can add a hit effect here if desired
|
||||||
|
// For example, a small explosion or impact sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _ExitTree()
|
||||||
|
{
|
||||||
|
// Clean up signal connections
|
||||||
|
BodyEntered -= OnBodyEntered;
|
||||||
|
AreaEntered -= OnAreaEntered;
|
||||||
|
}
|
||||||
|
}
|
1
Scripts/Entities/Bullet.cs.uid
Normal file
1
Scripts/Entities/Bullet.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://vgx2a8gm7l8b
|
@@ -11,14 +11,52 @@ public partial class Enemy : CharacterBody2D
|
|||||||
[Export] public float AttackRange = 50.0f;
|
[Export] public float AttackRange = 50.0f;
|
||||||
[Export] public int Damage = 10;
|
[Export] public int Damage = 10;
|
||||||
[Export] public float AttackCooldown = 1.0f;
|
[Export] public float AttackCooldown = 1.0f;
|
||||||
|
[Export] public int MaxHealth = 100;
|
||||||
|
[Export] public bool ShowDamageNumbers = true;
|
||||||
|
|
||||||
private Player _player;
|
private Player _player;
|
||||||
private float _attackTimer = 0;
|
private float _attackTimer = 0;
|
||||||
private bool _isPlayerInRange = false;
|
private bool _isPlayerInRange = false;
|
||||||
private Area2D _detectionArea;
|
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()
|
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
|
// Create detection area
|
||||||
_detectionArea = new Area2D();
|
_detectionArea = new Area2D();
|
||||||
var collisionShape = new CollisionShape2D();
|
var collisionShape = new CollisionShape2D();
|
||||||
@@ -37,6 +75,8 @@ public partial class Enemy : CharacterBody2D
|
|||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
|
if (IsDead) return;
|
||||||
|
|
||||||
if (_player != null && _isPlayerInRange)
|
if (_player != null && _isPlayerInRange)
|
||||||
{
|
{
|
||||||
// Face the player
|
// Face the player
|
||||||
@@ -60,8 +100,68 @@ public partial class Enemy : CharacterBody2D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private void TryAttackPlayer(double delta)
|
||||||
{
|
{
|
||||||
|
if (IsDead) return;
|
||||||
|
|
||||||
_attackTimer += (float)delta;
|
_attackTimer += (float)delta;
|
||||||
|
|
||||||
if (_attackTimer >= AttackCooldown)
|
if (_attackTimer >= AttackCooldown)
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
using AceFieldNewHorizon.Scripts.Entities;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace AceFieldNewHorizon.Scripts.Tiles;
|
namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||||
@@ -5,31 +6,69 @@ namespace AceFieldNewHorizon.Scripts.Tiles;
|
|||||||
public partial class EnemyNestTile : BaseTile
|
public partial class EnemyNestTile : BaseTile
|
||||||
{
|
{
|
||||||
[Export] public PackedScene EnemyScene;
|
[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 Timer _spawnTimer;
|
||||||
|
private int _currentEnemyCount = 0;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
base._Ready();
|
base._Ready();
|
||||||
|
|
||||||
// Create and configure the timer
|
// Create and configure the timer
|
||||||
_spawnTimer = new Timer();
|
_spawnTimer = new Timer
|
||||||
|
{
|
||||||
|
Autostart = true,
|
||||||
|
WaitTime = SpawnDelay
|
||||||
|
};
|
||||||
AddChild(_spawnTimer);
|
AddChild(_spawnTimer);
|
||||||
_spawnTimer.Timeout += OnSpawnTimerTimeout;
|
_spawnTimer.Timeout += OnSpawnTimerTimeout;
|
||||||
_spawnTimer.Start(1.0f); // Start with 1 second interval
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSpawnTimerTimeout()
|
private void OnSpawnTimerTimeout()
|
||||||
{
|
{
|
||||||
if (EnemyScene != null)
|
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)
|
||||||
{
|
{
|
||||||
var enemy = EnemyScene.Instantiate();
|
|
||||||
GetParent().AddChild(enemy);
|
GetParent().AddChild(enemy);
|
||||||
|
|
||||||
// Position the enemy at the nest's position
|
// Calculate a random position within the spawn radius
|
||||||
if (enemy is Node2D enemy2D)
|
var randomAngle = GD.Randf() * Mathf.Pi * 2;
|
||||||
{
|
var randomOffset = new Vector2(
|
||||||
enemy2D.GlobalPosition = GlobalPosition;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,16 +1,37 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using AceFieldNewHorizon.Scripts.Entities;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace AceFieldNewHorizon.Scripts.Tiles;
|
namespace AceFieldNewHorizon.Scripts.Tiles;
|
||||||
|
|
||||||
public partial class TurretTile : BaseTile
|
public partial class TurretTile : BaseTile
|
||||||
{
|
{
|
||||||
private Sprite2D _spriteBarrel;
|
[Export] public PackedScene BulletScene;
|
||||||
|
|
||||||
|
[Export] public float RotationSpeed = 2.0f; // Radians per second
|
||||||
|
[Export] public float AttackRange = 300.0f;
|
||||||
|
[Export] public float AttackCooldown = 0.5f;
|
||||||
|
[Export] public int Damage = 10;
|
||||||
|
[Export] public float BulletSpeed = 400.0f;
|
||||||
|
[Export] public NodePath BarrelTipPath;
|
||||||
|
|
||||||
|
private Node2D _spriteBarrel;
|
||||||
|
private Node2D _barrelTip;
|
||||||
|
private float _attackTimer = 0;
|
||||||
|
private bool _hasTarget = false;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
base._Ready();
|
base._Ready();
|
||||||
|
|
||||||
_spriteBarrel = GetNodeOrNull<Sprite2D>("Barrel");
|
_spriteBarrel = GetNodeOrNull<Node2D>("Barrel");
|
||||||
|
_barrelTip = GetNodeOrNull<Node2D>(BarrelTipPath);
|
||||||
|
|
||||||
|
if (_barrelTip == null && _spriteBarrel != null)
|
||||||
|
{
|
||||||
|
// If no barrel tip is specified, use the end of the barrel sprite
|
||||||
|
_barrelTip = _spriteBarrel.GetNodeOrNull<Node2D>("BarrelTip");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetGhostMode(bool canPlace)
|
public override void SetGhostMode(bool canPlace)
|
||||||
@@ -30,4 +51,77 @@ public partial class TurretTile : BaseTile
|
|||||||
if (_spriteBarrel != null)
|
if (_spriteBarrel != null)
|
||||||
_spriteBarrel.Modulate = Colors.White;
|
_spriteBarrel.Modulate = Colors.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (!IsConstructed) return;
|
||||||
|
|
||||||
|
// Update attack cooldown
|
||||||
|
if (_attackTimer > 0)
|
||||||
|
{
|
||||||
|
_attackTimer -= (float)delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the nearest enemy
|
||||||
|
var enemies = GetTree().GetNodesInGroup(Enemy.EnemyGroupName)
|
||||||
|
.OfType<Enemy>()
|
||||||
|
.Where(e => e.GlobalPosition.DistanceTo(GlobalPosition) <= AttackRange)
|
||||||
|
.OrderBy(e => e.GlobalPosition.DistanceTo(GlobalPosition))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (enemies.Count > 0)
|
||||||
|
{
|
||||||
|
var nearestEnemy = enemies[0];
|
||||||
|
_hasTarget = true;
|
||||||
|
|
||||||
|
// Calculate target angle
|
||||||
|
var direction = (nearestEnemy.GlobalPosition - _spriteBarrel.GlobalPosition).Normalized();
|
||||||
|
var targetAngle = Mathf.Atan2(direction.Y, direction.X);
|
||||||
|
|
||||||
|
// Smoothly rotate towards target
|
||||||
|
_spriteBarrel.Rotation = Mathf.LerpAngle(_spriteBarrel.Rotation, targetAngle, (float)delta * RotationSpeed);
|
||||||
|
|
||||||
|
// Check if we're facing the target and can attack
|
||||||
|
if (Mathf.Abs(Mathf.Wrap(targetAngle - _spriteBarrel.Rotation, -Mathf.Pi, Mathf.Pi)) < 0.1f)
|
||||||
|
TryAttack(nearestEnemy.GlobalPosition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_hasTarget = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryAttack(Vector2 targetPosition)
|
||||||
|
{
|
||||||
|
if (_attackTimer <= 0 && BulletScene != null && _barrelTip != null)
|
||||||
|
{
|
||||||
|
// Create bullet instance
|
||||||
|
var bullet = BulletScene.Instantiate<Bullet>();
|
||||||
|
|
||||||
|
// Calculate direction and rotation
|
||||||
|
var direction = (targetPosition - _barrelTip.GlobalPosition).Normalized();
|
||||||
|
var bulletRotation = direction.Angle();
|
||||||
|
|
||||||
|
// Set bullet position and rotation
|
||||||
|
GetTree().CurrentScene.AddChild(bullet);
|
||||||
|
bullet.GlobalPosition = _barrelTip.GlobalPosition;
|
||||||
|
bullet.Rotation = bulletRotation; // Use the calculated rotation
|
||||||
|
|
||||||
|
// Initialize bullet with direction and damage
|
||||||
|
bullet.Initialize(
|
||||||
|
direction,
|
||||||
|
bullet.GlobalPosition,
|
||||||
|
bulletRotation, // Pass the calculated rotation
|
||||||
|
1 // Pass the turret's collision layer to ignore
|
||||||
|
);
|
||||||
|
bullet.Damage = Damage;
|
||||||
|
bullet.Speed = BulletSpeed;
|
||||||
|
bullet.MaxDistance = AttackRange * 1.5f; // Bullets can travel slightly further than attack range
|
||||||
|
|
||||||
|
// Reset attack cooldown
|
||||||
|
_attackTimer = AttackCooldown;
|
||||||
|
|
||||||
|
GD.Print("Turret attacking enemy!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user