✨ Turret shooting and damaing
This commit is contained in:
		| @@ -1,3 +1,4 @@ | ||||
| using AceFieldNewHorizon.Scripts.Entities; | ||||
| using Godot; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.Tiles; | ||||
| @@ -5,31 +6,69 @@ 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(); | ||||
|         _spawnTimer = new Timer | ||||
|         { | ||||
|             Autostart = true, | ||||
|             WaitTime = SpawnDelay | ||||
|         }; | ||||
|         AddChild(_spawnTimer); | ||||
|         _spawnTimer.Timeout += OnSpawnTimerTimeout; | ||||
|         _spawnTimer.Start(1.0f); // Start with 1 second interval | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|              | ||||
|             // Position the enemy at the nest's position | ||||
|             if (enemy is Node2D enemy2D) | ||||
|             { | ||||
|                 enemy2D.GlobalPosition = GlobalPosition; | ||||
|             } | ||||
|             // 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; | ||||
|     } | ||||
| } | ||||
| @@ -1,22 +1,43 @@ | ||||
| using System.Linq; | ||||
| using AceFieldNewHorizon.Scripts.Entities; | ||||
| using Godot; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.Tiles; | ||||
|  | ||||
| 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() | ||||
|     { | ||||
|         base._Ready(); | ||||
|  | ||||
|         _spriteBarrel = GetNodeOrNull<Node2D>("Barrel"); | ||||
|         _barrelTip = GetNodeOrNull<Node2D>(BarrelTipPath); | ||||
|          | ||||
|         _spriteBarrel = GetNodeOrNull<Sprite2D>("Barrel"); | ||||
|         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) | ||||
|     { | ||||
|         base.SetGhostMode(canPlace); | ||||
|          | ||||
|  | ||||
|         if (_spriteBarrel != null) | ||||
|             _spriteBarrel.Modulate = canPlace | ||||
|                 ? new Color(0, 1, 0, 0.5f) | ||||
| @@ -26,8 +47,81 @@ public partial class TurretTile : BaseTile | ||||
|     public override void FinalizePlacement() | ||||
|     { | ||||
|         base.FinalizePlacement(); | ||||
|          | ||||
|  | ||||
|         if (_spriteBarrel != null) | ||||
|             _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