Split 2D and 3D enemy concepts
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user