From 3799687ae5c8c2b6719a64a9f51a3d3dd449fc6e Mon Sep 17 00:00:00 2001 From: rsxri Date: Sat, 19 Apr 2025 00:21:19 +0100 Subject: [PATCH] - feat: implement ai ship weapon firing (no collision yet) - fix: better ai movement implementation --- MB_FYP/scenes/game.tscn | 8 +- MB_FYP/scenes/ship.tscn | 4 +- MB_FYP/script/ai_fighter.cs | 147 ++++++++++++------------------------ MB_FYP/script/game.cs | 30 ++++++++ MB_FYP/script/player.cs | 7 +- MB_FYP/script/ship.cs | 11 ++- 6 files changed, 101 insertions(+), 106 deletions(-) diff --git a/MB_FYP/scenes/game.tscn b/MB_FYP/scenes/game.tscn index 6b96f2e..635d8f0 100644 --- a/MB_FYP/scenes/game.tscn +++ b/MB_FYP/scenes/game.tscn @@ -39,15 +39,14 @@ position = Vector2(387, 230) [node name="Friendly" type="Node" parent="Ships"] [node name="Player" parent="Ships/Friendly" instance=ExtResource("1_1w06w")] -position = Vector2(800, 450) +position = Vector2(959, 539) scale = Vector2(0.6, 0.6) collision_layer = 8 -type = 1 +type = 2 [node name="AI_Fighter" parent="Ships/Friendly" instance=ExtResource("5_nkk10")] -position = Vector2(989, 330) +position = Vector2(1151, 378) scale = Vector2(0.6, 0.6) -type = 0 faction = 1 [node name="Enemy" type="Node" parent="Ships"] @@ -55,6 +54,7 @@ faction = 1 [node name="AI_Fighter" parent="Ships/Enemy" instance=ExtResource("5_nkk10")] position = Vector2(992, 205) scale = Vector2(0.6, 0.6) +type = 0 [connection signal="Exploded" from="Asteroids/Asteroid" to="." method="OnAsteroidExploded"] [connection signal="Exploded" from="Asteroids/Asteroid2" to="." method="OnAsteroidExploded"] diff --git a/MB_FYP/scenes/ship.tscn b/MB_FYP/scenes/ship.tscn index 99212d2..566acec 100644 --- a/MB_FYP/scenes/ship.tscn +++ b/MB_FYP/scenes/ship.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://dx4wnk5okjs6x"] -[ext_resource type="Texture2D" path="res://assets/Player/Fighter/ShipBlue.png" id="1_g3tsu"] [ext_resource type="Script" path="res://script/ship.cs" id="1_u8ww1"] +[ext_resource type="Texture2D" uid="uid://ofevjaw7ld0a" path="res://assets/Ships/Fighters/Player/Fighter/ShipBlue.png" id="2_fvvdf"] [sub_resource type="CircleShape2D" id="CircleShape2D_tndfm"] radius = 41.0488 @@ -14,7 +14,7 @@ script = ExtResource("1_u8ww1") shape = SubResource("CircleShape2D_tndfm") [node name="ShipSprite" type="Sprite2D" parent="."] -texture = ExtResource("1_g3tsu") +texture = ExtResource("2_fvvdf") [node name="LaserSpawn" type="Node2D" parent="."] position = Vector2(0, -58) diff --git a/MB_FYP/script/ai_fighter.cs b/MB_FYP/script/ai_fighter.cs index 6fd9474..ce39194 100644 --- a/MB_FYP/script/ai_fighter.cs +++ b/MB_FYP/script/ai_fighter.cs @@ -7,8 +7,6 @@ public partial class ai_fighter : ship [Export] public float AggroRange = 500f; [Export] - public float FireCooldown = 2f; - [Export] public int ScorePayout = 50; [Export] public float EngageDistance = 300f; @@ -16,12 +14,10 @@ public partial class ai_fighter : ship //retreat logic private float previousDistance = 0f; private float stuckTime = 0f; - private const float stuckThreshold = 0.5f; // how many seconds to count as “stuck” - private const float distanceTolerance = 5f; // how little the distance must change - + private const float stuckThreshold = 0.5f; + private const float distanceTolerance = 5f; private Node2D currentTarget; - private float fireTimer = 0f; public override void _Ready() { @@ -35,25 +31,48 @@ public partial class ai_fighter : ship public override void _PhysicsProcess(double delta) { UpdateMovement(delta); - GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length()); + HandleFiring(delta); + //GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length()); } // SETUP - private void SetupAI() { - + return; } // COMBAT - private void FireWeapons() + private void HandleFiring(double delta) { + //GD.Print(Name, ": checking fire"); + //GD.Print(Name, ": fireTimer = ", fireTimer); + + fireTimer -= (float)delta; // countdown to next shot available + if (fireTimer > 0f) return; // if countdown not finished then no shoot + + if (currentTarget == null || !IsInstanceValid(currentTarget)) return; // checks for valid target, returns if not + + // determining direction and distance to target + Vector2 toTarget = (currentTarget.GlobalPosition - GlobalPosition).Normalized(); + float angleToTarget = toTarget.Angle() + Mathf.Pi / 2; + float angleDiff = Mathf.Abs(Mathf.AngleDifference(Rotation, angleToTarget)); + float distance = GlobalPosition.DistanceTo(currentTarget.GlobalPosition); + + //GD.Print(Name, ": angleDiff = ", Mathf.RadToDeg(angleDiff)); + + if (angleDiff < 0.4f && distance <= EngageDistance + 200f) // fire when in range and mostly aligned + { + //GD.Print(Name, ": angleDiff = ", angleDiff, ", distance = ", distance); + + ShootLaser(); + fireTimer = FireCooldown; // resets cooldown + } } // TARGETING - private Node2D FindClosestTarget() + private Node2D FindClosestTarget() // returns nearest valid ship depending on faction { Node2D closest = null; float closestDistance = Mathf.Inf; @@ -85,11 +104,10 @@ public partial class ai_fighter : ship } } } - return closest; } - private bool EnsureTarget() + private bool EnsureTarget() // finds a new target if no current or if invalid (dead) { if (currentTarget == null || !IsInstanceValid(currentTarget)) { @@ -99,7 +117,7 @@ public partial class ai_fighter : ship return true; } - private void UpdateDistanceTracking(float distance) + private void UpdateDistanceTracking(float distance) // tracks how long AI has been stuck in same spot { if (MathF.Abs(distance - previousDistance) < distanceTolerance) { @@ -112,53 +130,58 @@ public partial class ai_fighter : ship previousDistance = distance; } - private void HandleThrust(Vector2 direction, float angleDiff, float distance) + private void HandleThrust(float angleDiff, float distance) { - float retreatThreshold = EngageDistance * 0.75f; - if (Mathf.Abs(angleDiff) < 1f && Velocity.Length() < MaxSpeed - MainSpeed) + GD.Print(Name, " | angleDiff: ", Mathf.RadToDeg(angleDiff), " | velocity: ", Velocity.Length()); + + float retreatThreshold = EngageDistance * 0.75f; // retreat distance + + if (Mathf.Abs(angleDiff) < 1f) // only move forward when facing the target roughly { if (distance > EngageDistance) { - Velocity += -Transform.Y * MainSpeed; + Velocity += -Transform.Y * MainSpeed; // approach target } else if (distance < retreatThreshold) { if (stuckTime > stuckThreshold) { + // reposition if stuck Vector2 away = (GlobalPosition - currentTarget.GlobalPosition).Normalized(); Velocity += away * MainSpeed; } else { - Velocity += Transform.X * StrafeSpeed; + Velocity += Transform.X * StrafeSpeed; // try and orbit target laterally (strafe) } } + else + { + Velocity += Transform.X * StrafeSpeed * 0.5f; // lateral drift in hold range + } } } - private void RotateToTarget(Vector2 direction, double delta) + private void RotateToTarget(Vector2 direction, double delta) // rotates ship toward angle using rotation speed { float targetAngle = direction.Angle() + Mathf.Pi / 2; float angleDiff = Mathf.AngleDifference(Rotation, targetAngle); Rotation += Mathf.Clamp(angleDiff, -RotationSpeed * (float)delta, RotationSpeed * (float)delta); } - private void UpdateMovement(double delta) { - if (!EnsureTarget()) - { - return; - } + if (!EnsureTarget()) return; // skip if no valid target + // math for direction, distance and rotation Vector2 direction = (currentTarget.GlobalPosition - GlobalPosition).Normalized(); float angleToTarget = direction.Angle() + MathF.PI / 2; float angleDiff = Mathf.AngleDifference(Rotation, angleToTarget); float distance = GlobalPosition.DistanceTo(currentTarget.GlobalPosition); UpdateDistanceTracking(distance); - HandleThrust(direction, angleDiff, distance); + HandleThrust(angleDiff, distance); RotateToTarget(direction, delta); Velocity = Velocity.MoveToward(Vector2.Zero, 2.5f); //2.5f FA value (on for AI) @@ -166,76 +189,4 @@ public partial class ai_fighter : ship MoveAndSlide(); } - - /*private void UpdateMovement(double delta) - { - if (currentTarget == null || !IsInstanceValid(currentTarget)) - { - currentTarget = FindClosestTarget(); - return; - } - - Vector2 direction = (currentTarget.GlobalPosition - GlobalPosition).Normalized(); - float distance = GlobalPosition.DistanceTo(currentTarget.GlobalPosition); - float retreatThreshold = EngageDistance * 0.75f; - //Velocity += direction * MainSpeed; - float angleToTarget = direction.Angle() + Mathf.Pi / 2; - float angleDiff = Mathf.AngleDifference(Rotation, angleToTarget); - - - - - float currentDistance = GlobalPosition.DistanceTo(currentTarget.GlobalPosition); - - if (Mathf.Abs(currentDistance - previousDistance) < distanceTolerance) - stuckTime += (float)delta; - else - stuckTime = 0f; - - previousDistance = currentDistance; - - - //GD.Print("angleDiff: ", Mathf.RadToDeg(angleDiff)); - //GD.Print("rotation: ", Mathf.RadToDeg(Rotation), " target: ", Mathf.RadToDeg(angleToTarget)); - - - - if (Mathf.Abs(angleDiff) < 1f && Velocity.Length() < MaxSpeed - MainSpeed) - { - if (distance > EngageDistance) - { - Velocity += -Transform.Y * MainSpeed; - } - else if (distance < retreatThreshold) - { - if (stuckTime > stuckThreshold){ - Velocity += Transform.Y * MainSpeed; - } - else - { - Velocity += Transform.X * StrafeSpeed; - } - - //Velocity += Transform.Y * MainSpeed; - //Velocity += Transform.X * StrafeSpeed; - } - - } - - Velocity = Velocity.MoveToward(Vector2.Zero, 2.5f); - - - //GD.Print(MainSpeed); - //GD.Print(Velocity); - //Rotation = direction.Angle() + Mathf.Pi / 2; // Keeps ship pointing right way - - angleDiff = Mathf.AngleDifference(Rotation, direction.Angle() + Mathf.Pi / 2); - Rotation += Mathf.Clamp(angleDiff, -RotationSpeed * (float)delta, RotationSpeed * (float)delta); - - Velocity.LimitLength(MaxSpeed); - - MoveAndSlide(); - }*/ - - } diff --git a/MB_FYP/script/game.cs b/MB_FYP/script/game.cs index 5467cf8..e3831fe 100644 --- a/MB_FYP/script/game.cs +++ b/MB_FYP/script/game.cs @@ -41,6 +41,31 @@ public partial class game : Node2D Enemies = GetNode("Ships/Enemy"); Friendlies = GetNode("Ships/Friendly"); Player = GetNode("Ships/Friendly/Player"); + + + //AI fighter signals + foreach (Node node in Enemies.GetChildren()) + { + if (node is ai_fighter ai) + { + GD.Print("Connected laser signal for: ", ai.Name); + + ai.LaserShot += OnAILaserShot; + } + } + + foreach (Node node in Friendlies.GetChildren()) + { + if (node is player) continue; + + if (node is ai_fighter ai) + { + GD.Print("Connected laser signal for: ", ai.Name); + + ai.LaserShot += OnAILaserShot; + } + } + //var p = new player(); //p.LaserShot += OnPlayerLaserShot; } @@ -105,6 +130,11 @@ public partial class game : Node2D } //Signals and Connections + public void OnAILaserShot(Area2D Laser) + { + Lasers.AddChild(Laser); + } + public void OnPlayerLaserShot(Area2D Laser) { Lasers.AddChild(Laser); diff --git a/MB_FYP/script/player.cs b/MB_FYP/script/player.cs index 907a158..a8b5752 100644 --- a/MB_FYP/script/player.cs +++ b/MB_FYP/script/player.cs @@ -17,6 +17,7 @@ public partial class player : ship // Inherits from base ship class [Export] public float FlightAssistValue { get; set; } = 2.5f; + public void GetInput() { /*LookAt(GetGlobalMousePosition()); //used for mouse-based rotation and movement @@ -87,9 +88,13 @@ public partial class player : ship // Inherits from base ship class public override void _Process(double delta) { + fireTimer -= (float)delta; + if(Input.IsActionJustPressed("shoot")) { + if (fireTimer > 0f) return; ShootLaser(); + fireTimer = FireCooldown; } if (Health <= 0) @@ -106,7 +111,7 @@ public partial class player : ship // Inherits from base ship class //GD.Print(MainSpeed); //GD.Print("v ",Velocity, "v"); - GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length()); + //GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length()); MoveAndSlide(); diff --git a/MB_FYP/script/ship.cs b/MB_FYP/script/ship.cs index c3cfa57..909b0a8 100644 --- a/MB_FYP/script/ship.cs +++ b/MB_FYP/script/ship.cs @@ -25,7 +25,9 @@ public partial class ship : CharacterBody2D [Export] public float RotationSpeed { get; set; } [Export] - public int Damage {get; set;} + public int Damage {get; set; } + [Export] + public float FireCooldown { get; set; } [Export] public ShipType type; [Export] @@ -34,6 +36,8 @@ public partial class ship : CharacterBody2D public Sprite2D Sprite = new Sprite2D(); public Node2D LaserSpawn = null; + + protected float fireTimer = 0f; protected string spritePath = ""; @@ -43,6 +47,8 @@ public partial class ship : CharacterBody2D public virtual void ShootLaser() { + GD.Print(Name, ": firing laser"); + Node2D Laser = LaserScene.Instantiate(); Laser.Position = LaserSpawn.GlobalPosition; Laser.Rotation = Rotation; @@ -145,6 +151,7 @@ public partial class ship : CharacterBody2D RotationSpeed = 2f; MaxHealth = 100; Damage = 40; + FireCooldown = 0.6f; break; case ShipType.INTERCEPTOR: @@ -155,6 +162,7 @@ public partial class ship : CharacterBody2D RotationSpeed = 4f; MaxHealth = 75; Damage = 20; + FireCooldown = 0.3f; break; case ShipType.GUARDIAN: @@ -165,6 +173,7 @@ public partial class ship : CharacterBody2D RotationSpeed = 1.5f; MaxHealth = 200; Damage = 60; + FireCooldown = 1.1f; break; } }