Split 2D and 3D enemy concepts

This commit is contained in:
2025-02-25 22:02:02 -08:00
parent 282a2eddb0
commit 221ade11e3
1320 changed files with 3722 additions and 3451 deletions

View File

@@ -2,9 +2,7 @@ using Chickensoft.AutoInject;
using Chickensoft.Collections;
using Chickensoft.Introspection;
using Godot;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace GameJamDungeon;
@@ -36,19 +34,15 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
#endregion
#region Node Dependencies
[Node] private CollisionShape3D CollisionShape { get; set; } = default!;
[Node] private CollisionShape3D _collisionShape { get; set; } = default!;
[Node] private NavigationAgent3D NavAgent { get; set; } = default!;
[Node] private Area3D _lineOfSight { get; set; } = default!;
[Node] private Area3D LineOfSight { get; set; } = default!;
[Node] private Timer _attackTimer { get; set; } = default!;
[Node] private Timer PatrolTimer { get; set; } = default!;
[Node] private RayCast3D _raycast { get; set; } = default!;
[Node] private Timer AttackTimer { get; set; } = default!;
[Node] private RayCast3D Raycast { get; set; } = default!;
[Node] protected EnemyModelView EnemyModelView { get; set; } = default!;
[Node] protected IEnemyModelView _enemyModelView { get; set; } = default!;
#endregion
public double CurrentHP => _currentHP.Value;
@@ -59,10 +53,6 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
private Vector3 _knockbackDirection = Vector3.Zero;
private Vector3 _currentTarget = Vector3.Zero;
private Timer _thinkTimer;
#region Godot methods
public void Setup()
{
@@ -70,19 +60,6 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
_enemyLogic.Set(_enemyStatResource);
_enemyLogic.Set(this as IEnemy);
_enemyLogic.Set(Player);
_currentTarget = GlobalPosition;
NavAgent.VelocityComputed += NavAgent_VelocityComputed;
NavAgent.TargetReached += NavAgent_TargetReached;
_thinkTimer = new Timer
{
WaitTime = 0.4f
};
AddChild(_thinkTimer);
_thinkTimer.Timeout += NavAgent_TargetReached;
_thinkTimer.Start();
}
public void OnResolved()
@@ -104,16 +81,21 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
_currentHP = new AutoProp<double>(_enemyStatResource.MaximumHP);
_currentHP.Sync += OnHPChanged;
LineOfSight.BodyEntered += LineOfSight_BodyEntered;
PatrolTimer.Timeout += OnPatrolTimeout;
var rng = new RandomNumberGenerator();
rng.Randomize();
PatrolTimer.WaitTime = rng.RandfRange(7.0f, 15.0f);
_lineOfSight.BodyEntered += LineOfSight_BodyEntered;
}
public override void _PhysicsProcess(double delta)
{
CalculateVelocity();
if (CurrentHP <= 0)
return;
var lookDir = GlobalPosition + Velocity;
if (_enemyLogic.Value is not EnemyLogic.State.Attacking && (!lookDir.IsEqualApprox(GlobalPosition) || !Velocity.IsZeroApprox()))
LookAt(lookDir, Vector3.Up, true);
var isWalking = _enemyLogic.Value is EnemyLogic.State.Patrolling or EnemyLogic.State.FollowPlayer;
if (_enemyModelView is EnemyModelView2D enemyModelView2D)
enemyModelView2D.RotateModel(GlobalTransform.Basis, -Player.CurrentBasis.Z, isWalking);
}
#endregion
@@ -121,9 +103,16 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
{
}
public void SetTarget(Vector3 target)
public virtual void SetTarget(Vector3 target)
{
Task.Delay(TimeSpan.FromSeconds(1.5)).ContinueWith(_ => _currentTarget = new Vector3(target.X, -1.75f, target.Z));
}
public virtual void Move(Vector3 velocity)
{
_knockbackStrength = _knockbackStrength * 0.9f;
Velocity = velocity + (_knockbackDirection * _knockbackStrength);
MoveAndSlide();
}
public virtual void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false, bool ignoreDefense = false, bool ignoreElementalResistance = false)
@@ -143,9 +132,8 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
if (_currentHP.Value <= 0)
return;
EnemyModelView.PlayHitAnimation();
_enemyModelView.PlayHitAnimation();
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
NavAgent_TargetReached();
if (Player.EquippedWeapon.Value.WeaponTags.Contains(WeaponTag.SelfDamage))
Player.Stats.SetCurrentHP(Player.Stats.CurrentHP.Value - 5);
@@ -161,74 +149,25 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
public void Die()
{
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
CollisionShape.SetDeferred("disabled", true);
EnemyModelView.PlayDeathAnimation();
_collisionShape.SetDeferred("disabled", true);
_enemyModelView.PlayDeathAnimation();
var tweener = GetTree().CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
GameEventDepot.OnEnemyDefeated(GlobalPosition, _enemyStatResource);
}
private void NavAgent_TargetReached()
{
NavAgent.TargetPosition = _currentTarget;
}
private void NavAgent_VelocityComputed(Vector3 safeVelocity)
{
if (CurrentHP <= 0)
return;
_knockbackStrength = _knockbackStrength * 0.9f;
Velocity = safeVelocity + (_knockbackDirection * _knockbackStrength);
MoveAndSlide();
}
private void OnPatrolTimeout()
{
var rng = new RandomNumberGenerator();
rng.Randomize();
var randomizedSpot = new Vector3(rng.RandfRange(-5.0f, 5.0f), 0, rng.RandfRange(-5.0f, 5.0f));
_enemyLogic.Input(new EnemyLogic.Input.PatrolToRandomSpot(GlobalPosition + randomizedSpot));
_enemyLogic.Input(new EnemyLogic.Input.StartPatrol());
PatrolTimer.WaitTime = rng.RandfRange(5.0f, 10.0f);
}
public void StartAttackTimer()
{
AttackTimer.Timeout += OnAttackTimeout;
_attackTimer.Timeout += OnAttackTimeout;
}
public void StopAttackTimer()
{
AttackTimer.Timeout -= OnAttackTimeout;
_attackTimer.Timeout -= OnAttackTimeout;
}
private void CalculateVelocity()
{
if (CurrentHP <= 0)
return;
if (_enemyLogic.Value is EnemyLogic.State.Attacking)
SetTarget(GlobalPosition);
var nextPathPosition = NavAgent.GetNextPathPosition();
var newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * 2f;
if (NavAgent.AvoidanceEnabled)
NavAgent.Velocity = newVelocity;
else
NavAgent_VelocityComputed(newVelocity);
var lookDir = GlobalPosition + Velocity;
if (_enemyLogic.Value is not EnemyLogic.State.Attacking && (!lookDir.IsEqualApprox(GlobalPosition) || !Velocity.IsZeroApprox()))
LookAt(lookDir, Vector3.Up, true);
var isWalking = _enemyLogic.Value is EnemyLogic.State.Patrolling or EnemyLogic.State.FollowPlayer;
EnemyModelView.RotateModel(GlobalTransform.Basis, -Player.CurrentBasis.Z, isWalking);
}
private void OnAttackTimeout()
{
if (GlobalPosition.DistanceTo(Player.CurrentPosition) > 5f)
@@ -240,25 +179,25 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
var rng = new RandomNumberGenerator();
rng.Randomize();
_enemyLogic.Input(new EnemyLogic.Input.AttackTimer());
AttackTimer.Stop();
AttackTimer.WaitTime = rng.RandfRange(2f, 5.0f);
AttackTimer.Start();
_attackTimer.Stop();
_attackTimer.WaitTime = rng.RandfRange(2f, 5.0f);
_attackTimer.Start();
}
private void LineOfSight_BodyEntered(Node3D body)
{
var overlappingBodies = LineOfSight.GetOverlappingBodies();
var overlappingBodies = _lineOfSight.GetOverlappingBodies();
foreach (var _ in overlappingBodies)
{
if (Raycast.GlobalPosition != Player.CurrentPosition)
Raycast.LookAt(Player.CurrentPosition, Vector3.Up);
Raycast.ForceRaycastUpdate();
if (Raycast.IsColliding())
if (_raycast.GlobalPosition != Player.CurrentPosition)
_raycast.LookAt(Player.CurrentPosition, Vector3.Up);
_raycast.ForceRaycastUpdate();
if (_raycast.IsColliding())
{
var collider = Raycast.GetCollider();
var collider = _raycast.GetCollider();
if (collider is IPlayer)
{
Raycast.DebugShapeCustomColor = Color.FromString("Purple", Colors.Purple);
_raycast.DebugShapeCustomColor = Color.FromString("Purple", Colors.Purple);
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
}
}