✨ 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( | ||||||
|  |                 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() | ||||||
|     { |     { | ||||||
|                 enemy2D.GlobalPosition = GlobalPosition; |         _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