Enemy strafing

This commit is contained in:
MaddoScientisto 2025-03-23 18:22:12 +01:00
commit b35772f519
6 changed files with 112 additions and 16 deletions

View file

@ -108,11 +108,12 @@ DamageReceiver = NodePath("../../DamageReceiver")
NavigationModule = NodePath("../../NavigationModule")
_moduleNodes = [NodePath("../../AnimationModule")]
[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "EquippedWeapon", "_moduleNodes")]
[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "EquippedWeapon", "_moduleNodes")]
script = ExtResource("7_br0mr")
StorageModule = NodePath("../../Storage")
PlayerDetection = NodePath("../../PlayerDetection")
DamageReceiver = NodePath("../../DamageReceiver")
NavigationModule = NodePath("../../NavigationModule")
EquippedWeapon = NodePath("../../EnemyWeapon")
_moduleNodes = [NodePath("../../AnimationModule")]

View file

@ -108,11 +108,12 @@ DamageReceiver = NodePath("../../DamageReceiver")
NavigationModule = NodePath("../../NavigationModule")
_moduleNodes = [NodePath("../../AnimationModule")]
[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "EquippedWeapon", "_moduleNodes")]
[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "EquippedWeapon", "_moduleNodes")]
script = ExtResource("7_w6ssf")
StorageModule = NodePath("../../Storage")
PlayerDetection = NodePath("../../PlayerDetection")
DamageReceiver = NodePath("../../DamageReceiver")
NavigationModule = NodePath("../../NavigationModule")
EquippedWeapon = NodePath("../../EnemyWeapon")
_moduleNodes = [NodePath("../../AnimationModule")]
@ -173,6 +174,7 @@ target_desired_distance = 8.0
path_max_distance = 800.0
path_postprocessing = 1
avoidance_enabled = true
debug_enabled = true
debug_path_custom_color = Color(1, 0, 0, 1)
[node name="EnemyWeapon" parent="." instance=ExtResource("17_i8o8t")]

View file

@ -108,11 +108,12 @@ DamageReceiver = NodePath("../../DamageReceiver")
NavigationModule = NodePath("../../NavigationModule")
_moduleNodes = [NodePath("../../AnimationModule")]
[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "EquippedWeapon", "_moduleNodes")]
[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "EquippedWeapon", "_moduleNodes")]
script = ExtResource("7_0b4ec")
StorageModule = NodePath("../../Storage")
PlayerDetection = NodePath("../../PlayerDetection")
DamageReceiver = NodePath("../../DamageReceiver")
NavigationModule = NodePath("../../NavigationModule")
EquippedWeapon = NodePath("../../EnemyWeapon")
_moduleNodes = [NodePath("../../AnimationModule")]

View file

@ -66,4 +66,15 @@ public partial class NavigationMovementModule : Node2D
StorageModule.FacingDirection = _characterBody.Velocity.Normalized();
}
}
public bool IsNavigable(Vector2 newPos)
{
_navigationAgent.SetTargetPosition(newPos);
return _navigationAgent.IsTargetReachable();
}
public bool IsNavigationFinished()
{
return _navigationAgent.IsNavigationFinished();
}
}

View file

@ -60,18 +60,20 @@ public partial class PlayerDetectionModule : Area2D
//if (_cachedPlayer == null) return false;
if (!GameManager.Instance.PlayerPosition.HasValue) return false;
var spaceState = GetWorld2D().DirectSpaceState;
var found = HasLineOfSight(this.GlobalPosition, GameManager.Instance.PlayerPosition.Value);
// It needs to use its own collision mask because it's detecting obstacles rather than the player
var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, GameManager.Instance.PlayerPosition.Value, ObstaclesCollisionMask,
[GetRid()]);
//query.CollideWithBodies = true;
//query.CollideWithAreas = true;
// var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
var result = spaceState.IntersectRay(query);
// If count is 0 then the player is in sight, otherwise there is level geometry in the way
var found = result.Count == 0;
// var spaceState = GetWorld2D().DirectSpaceState;
//
// // It needs to use its own collision mask because it's detecting obstacles rather than the player
// var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, GameManager.Instance.PlayerPosition.Value, ObstaclesCollisionMask,
// [GetRid()]);
// //query.CollideWithBodies = true;
// //query.CollideWithAreas = true;
// // var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
// var result = spaceState.IntersectRay(query);
//
// // If count is 0 then the player is in sight, otherwise there is level geometry in the way
// var found = result.Count == 0;
if (found)
{
LastKnownPlayerPosition = GameManager.Instance.PlayerPosition;
@ -92,4 +94,22 @@ public partial class PlayerDetectionModule : Area2D
EmitSignal(SignalName.PlayerOutOfRange);
//PlayerInActiveArea = false;
}
public bool HasLineOfSight(Vector2 startPos, Vector2 endPos)
{
var spaceState = GetWorld2D().DirectSpaceState;
// It needs to use its own collision mask because it's detecting obstacles rather than the player
var query = PhysicsRayQueryParameters2D.Create(startPos, endPos, ObstaclesCollisionMask,
[GetRid()]);
//query.CollideWithBodies = true;
//query.CollideWithAreas = true;
// var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
var result = spaceState.IntersectRay(query);
// If count is 0 then the player is in sight, otherwise there is level geometry in the way
var found = result.Count == 0;
return found;
}
}

View file

@ -17,10 +17,18 @@ public partial class Shooting : EnemyStateBase
[Export]
public GenericDamageReceiver DamageReceiver { get; private set; }
[Export]
public NavigationMovementModule NavigationModule { get; private set; }
[Export] public float MaxStrafeDistance { get; private set; } = 64f;
[Export] public float MinStrafeDistance { get; private set; } = 16f;
[Export] public Weapon EquippedWeapon;
private bool _isPlayerInRange = false;
private Vector2? _currentStrafeTarget = null;
public override void EnterState()
{
base.EnterState();
@ -35,6 +43,8 @@ public partial class Shooting : EnemyStateBase
DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted;
EquippedWeapon.WeaponData = StorageModule.Root.EnemyResource.Weapon;
_currentStrafeTarget = null;
// PlayerDetection.SetRange(StorageModule.Root.EnemyResource.PlayerDetectionRange);
//
// _isPlayerInRange = PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.PlayerDetectionRange);
@ -67,6 +77,9 @@ public partial class Shooting : EnemyStateBase
PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange;
DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
_currentStrafeTarget = null;
DamageReceiver.ChangeState(false);
}
@ -79,14 +92,62 @@ public partial class Shooting : EnemyStateBase
{
// SHOOT
Shoot();
// Check if a strafe position is needed
if (!_currentStrafeTarget.HasValue || NavigationModule.IsNavigationFinished())
{
_currentStrafeTarget = CalculateStrafePosition();
}
}
else
{
StateMachine.SetState(EnemyState.Alert);
return;
}
// Strafe
if (_currentStrafeTarget.HasValue)
{
NavigationModule.SetTarget(_currentStrafeTarget.Value);
NavigationModule.Move();
}
}
private Vector2? CalculateStrafePosition()
{
Vector2 playerPos = PlayerDetection.LastKnownPlayerPosition.Value;
Vector2 enemyPos = MainObject.GlobalPosition;
// Calculate direction to player
Vector2 directionToPlayer = (playerPos - enemyPos).Normalized();
// Get perpendicular vectors (left and right strafing directions)
Vector2 leftStrafe = directionToPlayer.Rotated(-Mathf.Pi / 2);
Vector2 rightStrafe = directionToPlayer.Rotated(Mathf.Pi / 2);
// Randomly decide left or right first
bool tryLeftFirst = GD.Randf() < 0.5f;
for (float factor = 1f; factor > 0.25f; factor *= 0.75f)
{
float strafeDistance = Mathf.Lerp(MinStrafeDistance, MaxStrafeDistance, factor);
Vector2 newPos = enemyPos + (tryLeftFirst ? leftStrafe : rightStrafe) * strafeDistance;
if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos))
{
return newPos;
}
}
for (float factor = 1f; factor > 0.25f; factor *= 0.75f)
{
float strafeDistance = Mathf.Lerp(MinStrafeDistance, MaxStrafeDistance, factor);
Vector2 newPos = enemyPos + (tryLeftFirst ? rightStrafe : leftStrafe) * strafeDistance;
if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos))
{
return newPos;
}
}
return null; // No valid strafe position found
}
private void Shoot()