- feat: implement ai ship weapon firing (no collision yet) - fix: better ai movement implementation

This commit is contained in:
rsxri 2025-04-19 00:21:19 +01:00
parent 70862d3903
commit 3799687ae5
6 changed files with 101 additions and 106 deletions

View file

@ -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"]

View file

@ -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)

View file

@ -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();
}*/
}

View file

@ -41,6 +41,31 @@ public partial class game : Node2D
Enemies = GetNode<Node>("Ships/Enemy");
Friendlies = GetNode<Node>("Ships/Friendly");
Player = GetNode<CharacterBody2D>("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);

View file

@ -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();

View file

@ -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<Node2D>();
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;
}
}