mirror of
https://github.com/Bragin-Stepan/project-entity.git
synced 2026-03-05 15:51:10 +00:00
feat: add auto attack state
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public class CurrentTarget : IEntityComponent { public ReactiveVariable<Entity> Value; }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78216e3102e549f39cb690e777d00e88
|
||||
timeCreated: 1772470309
|
||||
@@ -0,0 +1,16 @@
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public class AIParallelState : ParallelState<IUpdatableState>, IUpdatableState
|
||||
{
|
||||
public AIParallelState(params IUpdatableState[] states) : base(states)
|
||||
{ }
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (IUpdatableState state in States)
|
||||
state.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0ae2666ed0a43d590866fff4686222a
|
||||
timeCreated: 1772538444
|
||||
@@ -3,8 +3,11 @@ using System.Collections.Generic;
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States;
|
||||
using _Project.Develop.Runtime.Utilities.Conditions;
|
||||
using _Project.Develop.Runtime.Utils.InputManagement;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using Assets._Project.Develop.Runtime.Infrastructure.DI;
|
||||
using Assets._Project.Develop.Runtime.Utilities.Timer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
@@ -12,14 +15,19 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
private readonly DIContainer _container;
|
||||
|
||||
private readonly EntitiesLifeContext _entitiesLifeContext;
|
||||
private readonly AIBrainsContext _aiBrainsContext;
|
||||
private readonly TimerServiceFactory _timerServiceFactory;
|
||||
private readonly IPlayerInput _playerInput;
|
||||
|
||||
public BrainsFactory(DIContainer container)
|
||||
{
|
||||
_container = container;
|
||||
|
||||
_playerInput = _container.Resolve<IPlayerInput>();
|
||||
_aiBrainsContext = _container.Resolve<AIBrainsContext>();
|
||||
_timerServiceFactory = _container.Resolve<TimerServiceFactory>();
|
||||
_entitiesLifeContext = _container.Resolve<EntitiesLifeContext>();
|
||||
}
|
||||
|
||||
public StateMachineBrain CreateGhostBrain(Entity entity)
|
||||
@@ -31,6 +39,43 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
|
||||
return brain;
|
||||
}
|
||||
|
||||
public StateMachineBrain CreateMainHeroBrain(Entity entity, ITargetSelector targetSelector)
|
||||
{
|
||||
AIStateMachine combatState = CreateAutoAttackStateMachine(entity);
|
||||
PlayerInputMovementState movementState = new (entity, _playerInput);
|
||||
|
||||
ReactiveVariable<Entity> currentTarget = entity.CurrentTarget;
|
||||
|
||||
ICompositeCondition fromMovementToCombatStateCondition = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => currentTarget.Value != null))
|
||||
.Add(new FuncCondition(() => _playerInput.Move.Value == Vector2.zero));
|
||||
|
||||
ICompositeCondition fromCombatToMovementStateCondition = new CompositeCondition(LogicOperationsUtils.Or)
|
||||
.Add(new FuncCondition(() => currentTarget.Value == null))
|
||||
.Add(new FuncCondition(() => _playerInput.Move.Value != Vector2.zero));
|
||||
|
||||
AIStateMachine behaviour = new ();
|
||||
|
||||
behaviour.AddState(combatState);
|
||||
behaviour.AddState(movementState);
|
||||
|
||||
behaviour.AddTransition(combatState, movementState, fromCombatToMovementStateCondition);
|
||||
behaviour.AddTransition(movementState, combatState, fromMovementToCombatStateCondition);
|
||||
|
||||
FindTargetState findTargetState = new (_entitiesLifeContext, entity, targetSelector);
|
||||
AIParallelState parallelState = new (findTargetState, behaviour);
|
||||
|
||||
AIStateMachine rootStateMachine = new ();
|
||||
|
||||
rootStateMachine.AddState(parallelState);
|
||||
|
||||
StateMachineBrain brain = new (rootStateMachine);
|
||||
|
||||
_aiBrainsContext.SetFor(entity, brain);
|
||||
|
||||
return brain;
|
||||
}
|
||||
|
||||
private AIStateMachine CreateRandomMovementStateMachine(Entity entity)
|
||||
{
|
||||
@@ -60,5 +105,46 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
|
||||
return stateMachine;
|
||||
}
|
||||
|
||||
private AIStateMachine CreateAutoAttackStateMachine(Entity entity)
|
||||
{
|
||||
RotateToTargetState rotateToTargetState = new (entity);
|
||||
AttackTriggerState attackTriggerState = new (entity);
|
||||
|
||||
ICondition canAttack = entity.CanStartAttack;
|
||||
Transform transform = entity.Transform;
|
||||
ReactiveVariable<Entity> currentTarget = entity.CurrentTarget;
|
||||
|
||||
ICompositeCondition fromRotateToAttackCondition = new CompositeCondition()
|
||||
.Add(canAttack)
|
||||
.Add(new FuncCondition(() =>
|
||||
{
|
||||
Entity target = currentTarget.Value;
|
||||
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
float angleToTarget = Quaternion.Angle(
|
||||
transform.rotation,
|
||||
Quaternion.LookRotation(target.Transform.position - transform.position));
|
||||
|
||||
return angleToTarget < 1f;
|
||||
}
|
||||
));
|
||||
|
||||
ReactiveVariable<bool> inAttackProcess = entity.InAttackProcess;
|
||||
|
||||
ICondition fromAttackToRotateStateCondition = new FuncCondition(() => inAttackProcess.Value == false);
|
||||
|
||||
AIStateMachine stateMachine = new ();
|
||||
|
||||
stateMachine.AddState(rotateToTargetState);
|
||||
stateMachine.AddState(attackTriggerState);
|
||||
|
||||
stateMachine.AddTransition(rotateToTargetState, attackTriggerState, fromRotateToAttackCondition);
|
||||
stateMachine.AddTransition(attackTriggerState, rotateToTargetState, fromAttackToRotateStateCondition);
|
||||
|
||||
return stateMachine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public class AttackTriggerState : State, IUpdatableState
|
||||
{
|
||||
private ReactiveEvent _attackRequest;
|
||||
|
||||
public AttackTriggerState(Entity entity)
|
||||
{
|
||||
_attackRequest = entity.StartAttackRequest;
|
||||
}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
base.Enter();
|
||||
_attackRequest.Invoke();
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d58a80b452346bda2d968af0f76fdfa
|
||||
timeCreated: 1772469124
|
||||
@@ -0,0 +1,28 @@
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public class FindTargetState : State, IUpdatableState
|
||||
{
|
||||
private ITargetSelector _targetSelector;
|
||||
private EntitiesLifeContext _entitiesLifeContext;
|
||||
private ReactiveVariable<Entity> _currentTarget;
|
||||
|
||||
public FindTargetState(
|
||||
EntitiesLifeContext entitiesLifeContext,
|
||||
Entity entity,
|
||||
ITargetSelector targetSelector)
|
||||
{
|
||||
_currentTarget = entity.CurrentTarget;
|
||||
_targetSelector = targetSelector;
|
||||
_entitiesLifeContext = entitiesLifeContext;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
_currentTarget.Value = _targetSelector.SelectTargetFrom(_entitiesLifeContext.Entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c92a70ea84c48f49ae461eae381a1ce
|
||||
timeCreated: 1772539593
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public interface ITargetSelector
|
||||
{
|
||||
public Entity SelectTargetFrom(IEnumerable<Entity> targets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15c5398fa2f84d109f74fbe2cd14bcda
|
||||
timeCreated: 1772538651
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Logic.Gameplay.Features.Damage;
|
||||
using _Project.Develop.Runtime.Utilities.Conditions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public class NearestDamageableTargetSelector : ITargetSelector
|
||||
{
|
||||
private readonly Entity _source;
|
||||
private readonly Transform _sourceTransform;
|
||||
|
||||
public NearestDamageableTargetSelector(Entity entity)
|
||||
{
|
||||
_source = entity;
|
||||
_sourceTransform = entity.Transform;
|
||||
}
|
||||
|
||||
public Entity SelectTargetFrom(IEnumerable<Entity> targets)
|
||||
{
|
||||
IEnumerable<Entity> selectedTargets = FindSelectedTargets(targets);
|
||||
|
||||
IEnumerable<Entity> enumerable = selectedTargets.ToList();
|
||||
|
||||
if (enumerable.Any() == false)
|
||||
return null;
|
||||
|
||||
Entity closetsTarget = enumerable.First();
|
||||
float minDistance = GetDistanceTo(closetsTarget);
|
||||
|
||||
foreach (Entity target in enumerable)
|
||||
{
|
||||
float distance = GetDistanceTo(target);
|
||||
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closetsTarget = target;
|
||||
}
|
||||
}
|
||||
|
||||
return closetsTarget;
|
||||
}
|
||||
|
||||
private IEnumerable<Entity> FindSelectedTargets(IEnumerable<Entity> targets)
|
||||
{
|
||||
return targets.Where(target =>
|
||||
{
|
||||
bool result = target.HasComponent<TakeDamageRequest>();
|
||||
|
||||
if (target.TryGetCanApplyDamage(out ICompositeCondition value))
|
||||
result = result && value.Evaluate();
|
||||
|
||||
result = result && (target != _source);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private float GetDistanceTo(Entity target) => (_sourceTransform.position - target.Transform.position).magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5efdb899e3ea4373b66f0610ea05b5c5
|
||||
timeCreated: 1772538727
|
||||
@@ -0,0 +1,37 @@
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utils.InputManagement;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
using UnityEngine;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public class PlayerInputMovementState : State, IUpdatableState
|
||||
{
|
||||
private readonly IPlayerInput _playerInput;
|
||||
|
||||
private ReactiveVariable<Vector3> _rotateDirection;
|
||||
private ReactiveVariable<Vector3> _moveDirection;
|
||||
|
||||
public PlayerInputMovementState(Entity entity, IPlayerInput playerInput)
|
||||
{
|
||||
_playerInput = playerInput;
|
||||
|
||||
_rotateDirection = entity.RotateDirection;
|
||||
_moveDirection = entity.MoveDirection;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
_moveDirection.Value = new Vector3(_playerInput.Move.Value.x, 0, _playerInput.Move.Value.y);
|
||||
_rotateDirection.Value = new Vector3(_playerInput.Move.Value.x, 0, _playerInput.Move.Value.y);
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
base.Exit();
|
||||
|
||||
_moveDirection.Value = Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 449f5d14d62348c5a770bd34869f384f
|
||||
timeCreated: 1772468559
|
||||
@@ -0,0 +1,28 @@
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
using UnityEngine;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public class RotateToTargetState : State, IUpdatableState
|
||||
{
|
||||
private ReactiveVariable<Vector3> _rotateDirection;
|
||||
private ReactiveVariable<Entity> _currentTarget;
|
||||
|
||||
private Transform _transform;
|
||||
|
||||
public RotateToTargetState(Entity entity)
|
||||
{
|
||||
_rotateDirection = entity.RotateDirection;
|
||||
_currentTarget = entity.CurrentTarget;
|
||||
_transform = entity.Transform;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (_currentTarget.Value != null)
|
||||
_rotateDirection.Value = (_currentTarget.Value.Transform.position - _transform.position).normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a16d8f8d923476b9a5544d64f3b469e
|
||||
timeCreated: 1772473180
|
||||
Reference in New Issue
Block a user