mirror of
https://github.com/Bragin-Stepan/project-entity.git
synced 2026-03-02 22:31:10 +00:00
feat: add state machine
This commit is contained in:
@@ -15,6 +15,7 @@ using Assets._Project.Develop.Runtime.Utilities.DataManagement;
|
||||
using Assets._Project.Develop.Runtime.Utilities.DataManagement.DataProviders;
|
||||
using Assets._Project.Develop.Runtime.Utilities.LoadingScreen;
|
||||
using Assets._Project.Develop.Runtime.Utilities.SceneManagement;
|
||||
using Assets._Project.Develop.Runtime.Utilities.Timer;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
|
||||
@@ -24,6 +25,7 @@ namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
|
||||
public static void Process(DIContainer container)
|
||||
{
|
||||
container.RegisterAsSingle<ICoroutinesPerformer>(CreateCoroutinesPerformer);
|
||||
container.RegisterAsSingle(CreateTimerServiceFactory);
|
||||
container.RegisterAsSingle(CreateConfigsProviderService);
|
||||
container.RegisterAsSingle(CreateResourcesAssetsLoader);
|
||||
container.RegisterAsSingle(CreateSceneLoaderService);
|
||||
@@ -43,7 +45,7 @@ namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
|
||||
container.RegisterAsSingle(CreateViewsFactory);
|
||||
container.RegisterAsSingle<ILoadingScreen>(CreateLoadingScreen);
|
||||
}
|
||||
|
||||
|
||||
private static ConfigsProviderService CreateConfigsProviderService(DIContainer c)
|
||||
{
|
||||
ResourcesAssetsLoader resourcesAssetsLoader = c.Resolve<ResourcesAssetsLoader>();
|
||||
@@ -64,6 +66,8 @@ namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
|
||||
|
||||
private static InputFactory CreateInputFactory(DIContainer c) => new();
|
||||
|
||||
private static TimerServiceFactory CreateTimerServiceFactory(DIContainer c) => new (c);
|
||||
|
||||
private static PlayerInput CreatePlayerInput(DIContainer c) => c.Resolve<InputFactory>().CreatePlayerInput();
|
||||
|
||||
private static UIInput CreateUIInput(DIContainer c) => c.Resolve<InputFactory>().CreateUIInput();
|
||||
|
||||
@@ -31,11 +31,11 @@ namespace _Project.Develop.Runtime.Entities
|
||||
_monoEntitiesFactory = container.Resolve<MonoEntitiesFactory>();
|
||||
_playerInput = container.Resolve<IPlayerInput>();
|
||||
}
|
||||
|
||||
|
||||
public Entity CreateHero(Vector3 position)
|
||||
{
|
||||
Entity entity = CreateEmpty();
|
||||
|
||||
|
||||
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Hero);
|
||||
|
||||
entity
|
||||
@@ -65,29 +65,29 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddAttackCooldownInitialTime()
|
||||
.AddAttackCooldownCurrentTime()
|
||||
.AddInAttackCooldown();
|
||||
|
||||
|
||||
ICompositeCondition canMove = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
ICompositeCondition canRotate = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition mustDie = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0));
|
||||
|
||||
ICompositeCondition mustSelfRelease = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value))
|
||||
.Add(new FuncCondition(() => entity.InDeathProcess.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition canApplyDamage = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition canStartAttack = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false))
|
||||
.Add(new FuncCondition(() => entity.InAttackProcess.Value == false))
|
||||
.Add(new FuncCondition(() => entity.IsMoving.Value == false))
|
||||
.Add(new FuncCondition(() => entity.InAttackCooldown.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition mustCancelAttack = new CompositeCondition(LogicOperationsUtils.Or)
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value))
|
||||
.Add(new FuncCondition(() => entity.IsMoving.Value));
|
||||
@@ -107,33 +107,33 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddSystem(new RotateDirectionByMoveInputSystem(_playerInput))
|
||||
.AddSystem(new RigidbodyMovementSystem())
|
||||
.AddSystem(new RigidbodyRotationSystem())
|
||||
|
||||
|
||||
.AddSystem(new AttackCancelSystem())
|
||||
|
||||
|
||||
.AddSystem(new StartAttackSystem())
|
||||
.AddSystem(new ProcessAttackTimerSystem())
|
||||
.AddSystem(new AttackDelayEndTriggerSystem())
|
||||
.AddSystem(new InstantShootSystem(this))
|
||||
.AddSystem(new EndAttackSystem())
|
||||
.AddSystem(new AttackCooldownTimerSystem())
|
||||
|
||||
|
||||
.AddSystem(new ApplyDamageSystem())
|
||||
|
||||
|
||||
.AddSystem(new DeathSwitcherSystem())
|
||||
.AddSystem(new DeathProcessTimerSystem())
|
||||
|
||||
|
||||
.AddSystem(new DisableCollidersOnDeathSystem())
|
||||
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
|
||||
|
||||
|
||||
_entitiesLifeContext.Add(entity);
|
||||
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public Entity CreateGhost(Vector3 position)
|
||||
{
|
||||
Entity entity = CreateEmpty();
|
||||
|
||||
|
||||
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Ghost);
|
||||
|
||||
entity
|
||||
@@ -154,20 +154,20 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddInDeathProcess()
|
||||
.AddDeathProcessInitialTime(new ReactiveVariable<float>(2))
|
||||
.AddDeathProcessCurrentTime();
|
||||
|
||||
|
||||
ICompositeCondition canMove = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
ICompositeCondition canRotate = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition mustDie = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0));
|
||||
|
||||
ICompositeCondition mustSelfRelease = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value))
|
||||
.Add(new FuncCondition(() => entity.InDeathProcess.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition canApplyDamage = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
@@ -181,38 +181,38 @@ namespace _Project.Develop.Runtime.Entities
|
||||
entity
|
||||
.AddSystem(new RigidbodyMovementSystem())
|
||||
.AddSystem(new RigidbodyRotationSystem())
|
||||
|
||||
|
||||
.AddSystem(new BodyContactsDetectingSystem())
|
||||
.AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService))
|
||||
|
||||
|
||||
.AddSystem(new DealDamageOnContactSystem())
|
||||
.AddSystem(new ApplyDamageSystem())
|
||||
|
||||
|
||||
.AddSystem(new DeathSwitcherSystem())
|
||||
.AddSystem(new DeathProcessTimerSystem())
|
||||
|
||||
|
||||
.AddSystem(new DisableCollidersOnDeathSystem())
|
||||
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
|
||||
|
||||
|
||||
_entitiesLifeContext.Add(entity);
|
||||
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public Entity CreateTeleportWizard(Vector3 position)
|
||||
{
|
||||
Entity entity = CreateEmpty();
|
||||
|
||||
|
||||
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Mage);
|
||||
|
||||
entity
|
||||
.AddContactsDetectingMask(Layers.CharactersMask)
|
||||
.AddContactCollidersBuffer(new Buffer<Collider>(32))
|
||||
.AddContactEntitiesBuffer(new Buffer<Entity>(32))
|
||||
|
||||
|
||||
.AddMaxHealth(new ReactiveVariable<float>(150))
|
||||
.AddCurrentHealth(new ReactiveVariable<float>(150))
|
||||
|
||||
|
||||
.AddTeleportTarget(entity.Transform)
|
||||
.AddTeleportToPoint(entity.Transform)
|
||||
.AddStartTeleportEvent()
|
||||
@@ -221,14 +221,14 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddFindTeleportPointEvent()
|
||||
.AddFindTeleportPointRequest()
|
||||
.AddEndTeleportEvent()
|
||||
|
||||
|
||||
.AddTeleportDamage(new ReactiveVariable<float>(50))
|
||||
.AddTeleportDamageRadius(new ReactiveVariable<float>(6))
|
||||
.AddTeleportDamageMask(Layers.CharactersMask)
|
||||
|
||||
|
||||
.AddTeleportEnergyCost(new ReactiveVariable<int>(20))
|
||||
.AddTeleportSearchRadius(new ReactiveVariable<float>(6))
|
||||
|
||||
|
||||
.AddCurrentEnergy(new ReactiveVariable<int>(60))
|
||||
.AddMaxEnergy(new ReactiveVariable<int>(60))
|
||||
.AddUseEnergyEvent()
|
||||
@@ -239,7 +239,7 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddIsAutoRegenEnergy(new ReactiveVariable<bool>(true))
|
||||
.AddEnergyAutoRegenCurrentTime()
|
||||
.AddEnergyAutoRegenInitialTime(new ReactiveVariable<float>(3))
|
||||
|
||||
|
||||
.AddBodyContactDamage(new ReactiveVariable<float>(50))
|
||||
.AddTakeDamageRequest()
|
||||
.AddTakeDamageEvent()
|
||||
@@ -247,24 +247,24 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddInDeathProcess()
|
||||
.AddDeathProcessInitialTime(new ReactiveVariable<float>(2))
|
||||
.AddDeathProcessCurrentTime();
|
||||
|
||||
|
||||
ICompositeCondition canRegenEnergy = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
ICompositeCondition canUseEnergy = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition canStartTeleport = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false))
|
||||
.Add(new FuncCondition(() => entity.CurrentEnergy.Value >= entity.TeleportEnergyCost.Value));
|
||||
|
||||
|
||||
ICompositeCondition mustDie = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0));
|
||||
|
||||
ICompositeCondition mustSelfRelease = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value))
|
||||
.Add(new FuncCondition(() => entity.InDeathProcess.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition canApplyDamage = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
@@ -278,40 +278,40 @@ namespace _Project.Develop.Runtime.Entities
|
||||
|
||||
entity
|
||||
.AddSystem(new TeleportByInputSystem(_playerInput))
|
||||
|
||||
|
||||
// .AddSystem(new RegenEnergyByValueSystem())
|
||||
.AddSystem(new RegenEnergyByPercentageSystem())
|
||||
.AddSystem(new UseEnergySystem())
|
||||
.AddSystem(new AutoRegenEnergyTimerSystem())
|
||||
|
||||
|
||||
.AddSystem(new TeleportStartByEnergySystem())
|
||||
.AddSystem(new TeleportProcessSystem())
|
||||
.AddSystem(new FindRandomPointForTeleportSystem())
|
||||
.AddSystem(new EndTeleportSystem())
|
||||
.AddSystem(new InstantTeleportSystem())
|
||||
.AddSystem(new DealDamageAfterTeleportSystem(_collidersRegistryService))
|
||||
|
||||
|
||||
.AddSystem(new BodyContactsDetectingSystem())
|
||||
.AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService))
|
||||
|
||||
|
||||
.AddSystem(new DealDamageOnContactSystem())
|
||||
.AddSystem(new ApplyDamageSystem())
|
||||
|
||||
|
||||
.AddSystem(new DeathSwitcherSystem())
|
||||
.AddSystem(new DeathProcessTimerSystem())
|
||||
|
||||
|
||||
.AddSystem(new DisableCollidersOnDeathSystem())
|
||||
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
|
||||
|
||||
|
||||
_entitiesLifeContext.Add(entity);
|
||||
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
public Entity CreateProjectile(Vector3 position, Vector3 direction, float damage)
|
||||
{
|
||||
Entity entity = CreateEmpty();
|
||||
|
||||
|
||||
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Projectile);
|
||||
|
||||
entity
|
||||
@@ -327,13 +327,13 @@ namespace _Project.Develop.Runtime.Entities
|
||||
.AddIsMoving()
|
||||
.AddDeathMask(Layers.CharactersMask | Layers.EnvironmentMask)
|
||||
.AddIsTouchDeathMask();
|
||||
|
||||
|
||||
ICompositeCondition canMove = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
ICompositeCondition canRotate = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsDead.Value == false));
|
||||
|
||||
|
||||
ICompositeCondition mustDie = new CompositeCondition()
|
||||
.Add(new FuncCondition(() => entity.IsTouchDeathMask.Value));
|
||||
|
||||
@@ -349,19 +349,19 @@ namespace _Project.Develop.Runtime.Entities
|
||||
entity
|
||||
.AddSystem(new RigidbodyMovementSystem())
|
||||
.AddSystem(new RigidbodyRotationSystem())
|
||||
|
||||
|
||||
.AddSystem(new BodyContactsDetectingSystem())
|
||||
.AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService))
|
||||
|
||||
|
||||
.AddSystem(new DealDamageOnContactSystem())
|
||||
.AddSystem(new DeathMaskTouchDetectorSystem())
|
||||
.AddSystem(new DeathSwitcherSystem())
|
||||
|
||||
|
||||
.AddSystem(new DisableCollidersOnDeathSystem())
|
||||
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
|
||||
|
||||
|
||||
_entitiesLifeContext.Add(entity);
|
||||
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d79e4112f0c64a90b05cae78e957a829
|
||||
timeCreated: 1772454294
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public class AIBrainsContext : IDisposable
|
||||
{
|
||||
private readonly List<EntityToBrain> _entityToBrains = new ();
|
||||
|
||||
public void SetFor(Entity entity, IBrain brain)
|
||||
{
|
||||
foreach (EntityToBrain item in _entityToBrains)
|
||||
{
|
||||
if (item.Entity == entity)
|
||||
{
|
||||
item.Brain.Disable();
|
||||
item.Brain.Dispose();
|
||||
item.Brain = brain;
|
||||
item.Brain.Enable();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_entityToBrains.Add(new EntityToBrain(brain, entity));
|
||||
brain.Enable();
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
for (int i = 0; i < _entityToBrains.Count; i++)
|
||||
{
|
||||
if (_entityToBrains[i].Entity.IsInit == false)
|
||||
{
|
||||
int lastIndex = _entityToBrains.Count - 1;
|
||||
|
||||
_entityToBrains[i].Brain.Dispose();
|
||||
_entityToBrains[i] = _entityToBrains[lastIndex];
|
||||
_entityToBrains.RemoveAt(lastIndex);
|
||||
i--;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_entityToBrains[i].Brain.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (EntityToBrain entityToBrain in _entityToBrains)
|
||||
entityToBrain.Brain.Dispose();
|
||||
|
||||
_entityToBrains.Clear();
|
||||
}
|
||||
|
||||
private class EntityToBrain
|
||||
{
|
||||
public Entity Entity;
|
||||
public IBrain Brain;
|
||||
|
||||
public EntityToBrain(IBrain brain, Entity entity)
|
||||
{
|
||||
Brain = brain;
|
||||
Entity = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 647b53823c00413081b7c9221d5bb5c1
|
||||
timeCreated: 1772458604
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public class AIStateMachine : StateMachine<IUpdatableState>
|
||||
{
|
||||
public AIStateMachine(List<IDisposable> disposables) : base(disposables)
|
||||
{
|
||||
}
|
||||
|
||||
public AIStateMachine() : base (new List<IDisposable>())
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateLogic(float deltaTime)
|
||||
{
|
||||
base.UpdateLogic(deltaTime);
|
||||
|
||||
CurrentState?.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e225964fa5a4120a1e4bf1cc8d47641
|
||||
timeCreated: 1772455227
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
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 Assets._Project.Develop.Runtime.Infrastructure.DI;
|
||||
using Assets._Project.Develop.Runtime.Utilities.Timer;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public class BrainsFactory
|
||||
{
|
||||
private readonly DIContainer _container;
|
||||
|
||||
private readonly AIBrainsContext _aiBrainsContext;
|
||||
private readonly TimerServiceFactory _timerServiceFactory;
|
||||
|
||||
public BrainsFactory(DIContainer container)
|
||||
{
|
||||
_container = container;
|
||||
_aiBrainsContext = _container.Resolve<AIBrainsContext>();
|
||||
_timerServiceFactory = _container.Resolve<TimerServiceFactory>();
|
||||
}
|
||||
|
||||
public StateMachineBrain CreateGhostBrain(Entity entity)
|
||||
{
|
||||
AIStateMachine stateMachine = CreateRandomMovementStateMachine(entity);
|
||||
StateMachineBrain brain = new (stateMachine);
|
||||
|
||||
_aiBrainsContext.SetFor(entity, brain);
|
||||
|
||||
return brain;
|
||||
}
|
||||
|
||||
private AIStateMachine CreateRandomMovementStateMachine(Entity entity)
|
||||
{
|
||||
List<IDisposable> disposables = new ();
|
||||
|
||||
RandomMovementState randomMovementState = new (entity, 0.5f);
|
||||
EmptyState emptyState = new ();
|
||||
|
||||
TimerService movementTimer = _timerServiceFactory.Create(2f);
|
||||
disposables.Add(randomMovementState.Entered.Subscribe(movementTimer.Restart));
|
||||
disposables.Add(movementTimer);
|
||||
|
||||
TimerService idleTimer = _timerServiceFactory.Create(3f);
|
||||
disposables.Add(emptyState.Entered.Subscribe(idleTimer.Restart));
|
||||
disposables.Add(idleTimer);
|
||||
|
||||
FuncCondition movementTimerEndedCondition = new (() => movementTimer.IsOver);
|
||||
FuncCondition idleTimerEndedCondition = new (() => idleTimer.IsOver);
|
||||
|
||||
AIStateMachine stateMachine = new (disposables);
|
||||
|
||||
stateMachine.AddState(randomMovementState);
|
||||
stateMachine.AddState(emptyState);
|
||||
|
||||
stateMachine.AddTransition(randomMovementState, emptyState, movementTimerEndedCondition);
|
||||
stateMachine.AddTransition(emptyState, randomMovementState, idleTimerEndedCondition);
|
||||
|
||||
return stateMachine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b566da239ac47d5b8c828cc1ac54cbe
|
||||
timeCreated: 1772455908
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public interface IBrain : IDisposable
|
||||
{
|
||||
void Enable();
|
||||
|
||||
void Disable();
|
||||
|
||||
void Update(float deltaTime);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77beb88ee43645a5b0f85bf7d1518e4a
|
||||
timeCreated: 1772458164
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI
|
||||
{
|
||||
public class StateMachineBrain : IBrain
|
||||
{
|
||||
private readonly AIStateMachine _stateMachine;
|
||||
|
||||
private bool _isEnabled;
|
||||
|
||||
public StateMachineBrain(AIStateMachine stateMachine)
|
||||
{
|
||||
_stateMachine = stateMachine;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
_stateMachine.Enter();
|
||||
_isEnabled = true;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (_isEnabled == false)
|
||||
return;
|
||||
|
||||
_stateMachine.Update(deltaTime);
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_stateMachine.Exit();
|
||||
_stateMachine.Dispose();
|
||||
_isEnabled = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stateMachine.Dispose();
|
||||
_isEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45f2a68dedcb4a61a4ae6dc8a50227e4
|
||||
timeCreated: 1772458266
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0534b2062654595bef4d1e59081a533
|
||||
timeCreated: 1772454304
|
||||
@@ -0,0 +1,12 @@
|
||||
using Assets._Project.Develop.Runtime.Utilities.StateMachineCore;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.AI.States
|
||||
{
|
||||
public class EmptyState : State, IUpdatableState
|
||||
{
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f40181e821046968e6c69ecef7e2384
|
||||
timeCreated: 1772454318
|
||||
@@ -0,0 +1,67 @@
|
||||
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 RandomMovementState : State, IUpdatableState
|
||||
{
|
||||
private ReactiveVariable<Vector3> _moveDirection;
|
||||
private ReactiveVariable<Vector3> _rotateDirection;
|
||||
|
||||
private float _cooldownBetweenDirection;
|
||||
|
||||
private float _time;
|
||||
|
||||
|
||||
public RandomMovementState(Entity entity, float cooldownBetweenDirection)
|
||||
{
|
||||
_moveDirection = entity.MoveDirection;
|
||||
_rotateDirection = entity.RotateDirection;
|
||||
|
||||
_cooldownBetweenDirection = cooldownBetweenDirection;
|
||||
}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
base.Enter();
|
||||
|
||||
Vector3 randomDirection = new Vector3(Random.Range(-1f, 1f), 0, Random.Range(-1f, 1f)).normalized;
|
||||
_moveDirection.Value = randomDirection;
|
||||
_rotateDirection.Value = randomDirection;
|
||||
|
||||
_time = 0;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
_time += deltaTime;
|
||||
|
||||
if (_time >= _cooldownBetweenDirection)
|
||||
{
|
||||
Vector3 newDirection = GenerateNewDirection();
|
||||
|
||||
_moveDirection.Value = newDirection;
|
||||
_rotateDirection.Value = newDirection;
|
||||
|
||||
_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
base.Exit();
|
||||
|
||||
_moveDirection.Value = Vector3.zero;
|
||||
}
|
||||
|
||||
private Vector3 GenerateNewDirection()
|
||||
{
|
||||
Vector3 inverseDirection = -_moveDirection.Value.normalized;
|
||||
Quaternion randomTurn = Quaternion.Euler(0, Random.Range(-30, 30), 0);
|
||||
|
||||
return randomTurn * inverseDirection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd6446a9b2f646bc870251ae870904b3
|
||||
timeCreated: 1772454385
|
||||
@@ -3,7 +3,7 @@ using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utilities.Conditions;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.Energy.Systems
|
||||
{
|
||||
@@ -11,52 +11,52 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features.Energy.Systems
|
||||
{
|
||||
private ReactiveEvent<int> _regenEnergyRequest;
|
||||
private ReactiveEvent<int> _regenEnergyEvent;
|
||||
|
||||
|
||||
private ReactiveVariable<int> _currentEnergy;
|
||||
private ReactiveVariable<int> _maxEnergy;
|
||||
|
||||
|
||||
private ICompositeCondition _canRegen;
|
||||
|
||||
|
||||
private IDisposable _regenRequestDispose;
|
||||
|
||||
|
||||
public void OnInit(Entity entity)
|
||||
{
|
||||
_currentEnergy = entity.CurrentEnergy;
|
||||
_maxEnergy = entity.MaxEnergy;
|
||||
|
||||
|
||||
_regenEnergyRequest = entity.RegenEnergyRequest;
|
||||
_regenEnergyEvent = entity.RegenEnergyEvent;
|
||||
|
||||
|
||||
_canRegen = entity.CanRegenEnergy;
|
||||
|
||||
|
||||
_regenRequestDispose = _regenEnergyRequest.Subscribe(OnRegenRequest);
|
||||
}
|
||||
|
||||
|
||||
private void OnRegenRequest(int percentage)
|
||||
{
|
||||
if (percentage <= 0)
|
||||
throw new ArgumentException("Energy regen percentage must be positive", nameof(percentage));
|
||||
|
||||
|
||||
if (_canRegen.Evaluate() == false)
|
||||
return;
|
||||
|
||||
|
||||
int energyDifference = _maxEnergy.Value - _currentEnergy.Value;
|
||||
|
||||
if (energyDifference <= 0)
|
||||
return;
|
||||
|
||||
int regenAmount= (int)math.floor(_maxEnergy.Value * (percentage / 100f));
|
||||
|
||||
|
||||
int regenAmount = (int)Mathf.Floor(_maxEnergy.Value * (percentage / 100f));
|
||||
|
||||
if (regenAmount < 1 && _maxEnergy.Value > 0)
|
||||
regenAmount = 1;
|
||||
|
||||
int valueAdded = math.min(regenAmount, energyDifference);
|
||||
|
||||
int valueAdded = Mathf.Min(regenAmount, energyDifference);
|
||||
|
||||
_currentEnergy.Value += valueAdded;
|
||||
|
||||
|
||||
_regenEnergyEvent.Invoke(valueAdded);
|
||||
}
|
||||
|
||||
|
||||
public void OnDispose()
|
||||
{
|
||||
_regenRequestDispose.Dispose();
|
||||
|
||||
@@ -3,7 +3,6 @@ using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Utilities.Conditions;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.Energy.Systems
|
||||
@@ -12,47 +11,47 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features.Energy.Systems
|
||||
{
|
||||
private ReactiveEvent<int> _regenEnergyRequest;
|
||||
private ReactiveEvent<int> _regenEnergyEvent;
|
||||
|
||||
|
||||
private ReactiveVariable<int> _currentEnergy;
|
||||
private ReactiveVariable<int> _maxEnergy;
|
||||
|
||||
|
||||
private ICompositeCondition _canRegen;
|
||||
|
||||
|
||||
private IDisposable _regenRequestDispose;
|
||||
|
||||
|
||||
public void OnInit(Entity entity)
|
||||
{
|
||||
_currentEnergy = entity.CurrentEnergy;
|
||||
_maxEnergy = entity.MaxEnergy;
|
||||
|
||||
|
||||
_regenEnergyRequest = entity.RegenEnergyRequest;
|
||||
_regenEnergyEvent = entity.RegenEnergyEvent;
|
||||
|
||||
|
||||
_canRegen = entity.CanRegenEnergy;
|
||||
|
||||
|
||||
_regenRequestDispose = _regenEnergyRequest.Subscribe(OnRegenRequest);
|
||||
}
|
||||
|
||||
|
||||
private void OnRegenRequest(int value)
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentException("Energy regen value must be positive", nameof(value));
|
||||
|
||||
|
||||
if (_canRegen.Evaluate() == false)
|
||||
return;
|
||||
|
||||
|
||||
int energyDifference = _maxEnergy.Value - _currentEnergy.Value;
|
||||
|
||||
|
||||
if (energyDifference <= 0)
|
||||
return;
|
||||
|
||||
int valueAdded = math.min(value, energyDifference);
|
||||
|
||||
|
||||
int valueAdded = Mathf.Min(value, energyDifference);
|
||||
|
||||
_currentEnergy.Value += valueAdded;
|
||||
|
||||
|
||||
_regenEnergyEvent.Invoke(valueAdded);
|
||||
}
|
||||
|
||||
|
||||
public void OnDispose()
|
||||
{
|
||||
_regenRequestDispose.Dispose();
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections;
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Logic.Gameplay.Features;
|
||||
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI;
|
||||
using _Project.Develop.Runtime.Utils.InputManagement;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -16,7 +17,7 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
|
||||
|
||||
private DIContainer _container;
|
||||
private EntitiesLifeContext _entitiesLifeContext;
|
||||
private GameplayInputArgs _gameplayArgs;
|
||||
private AIBrainsContext _aiBrainsContext;
|
||||
|
||||
private IPlayerInput _playerInput;
|
||||
|
||||
@@ -28,12 +29,12 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
|
||||
throw new ArgumentException($"{nameof(sceneArgs)} is not match with {typeof(GameplayInputArgs)} type");
|
||||
|
||||
GameplayContextRegistrations.Process(_container);
|
||||
_gameplayArgs = gameplayInputArgs;
|
||||
}
|
||||
|
||||
public override IEnumerator Initialize()
|
||||
{
|
||||
_entitiesLifeContext = _container.Resolve<EntitiesLifeContext>();
|
||||
_aiBrainsContext = _container.Resolve<AIBrainsContext>();
|
||||
|
||||
_testGameplay.Initialize(_container);
|
||||
yield break;
|
||||
@@ -46,6 +47,7 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_aiBrainsContext?.Update(Time.deltaTime);
|
||||
_entitiesLifeContext?.Update(Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI;
|
||||
using _Project.Develop.Runtime.UI;
|
||||
using _Project.Develop.Runtime.UI.Core;
|
||||
using _Project.Develop.Runtime.UI.Screens.Gameplay;
|
||||
@@ -21,9 +22,13 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
|
||||
container.RegisterAsSingle(CreateEntitiesFactory);
|
||||
container.RegisterAsSingle(CreateEntitiesLifeContext);
|
||||
container.RegisterAsSingle(CreateCollidersRegistryService);
|
||||
container.RegisterAsSingle(CreateAIBrainContext);
|
||||
container.RegisterAsSingle(CreateBrainsFactory);
|
||||
container.RegisterAsSingle(CreateMonoEntitiesFactory).NonLazy();
|
||||
}
|
||||
|
||||
private static AIBrainsContext CreateAIBrainContext(DIContainer c) => new();
|
||||
private static BrainsFactory CreateBrainsFactory(DIContainer c) => new(c);
|
||||
private static EntitiesLifeContext CreateEntitiesLifeContext(DIContainer c) => new();
|
||||
|
||||
private static EntitiesFactory CreateEntitiesFactory(DIContainer c) => new(c);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using _Project.Develop.Runtime.Entities;
|
||||
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI;
|
||||
using _Project.Develop.Runtime.Utils.InputManagement;
|
||||
using Assets._Project.Develop.Runtime.Infrastructure.DI;
|
||||
using UnityEngine;
|
||||
@@ -10,8 +11,11 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features
|
||||
{
|
||||
private DIContainer _container;
|
||||
private EntitiesFactory _entitiesFactory;
|
||||
private BrainsFactory _brainsFactory;
|
||||
|
||||
private Entity _entity;
|
||||
private Entity _hero;
|
||||
private Entity _ghost;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public void Initialize(DIContainer container)
|
||||
@@ -20,15 +24,15 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features
|
||||
|
||||
_container.Resolve<IPlayerInput>().Enable();
|
||||
_entitiesFactory = _container.Resolve<EntitiesFactory>();
|
||||
_brainsFactory = _container.Resolve<BrainsFactory>();
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_entity = _entitiesFactory.CreateTeleportWizard(Vector3.zero);
|
||||
|
||||
_entitiesFactory.CreateGhost(Vector3.zero + Vector3.forward * 5);
|
||||
_entitiesFactory.CreateGhost(Vector3.zero + Vector3.right * 5);
|
||||
_entitiesFactory.CreateGhost(Vector3.zero + Vector3.left * 5);
|
||||
_hero = _entitiesFactory.CreateTeleportWizard(Vector3.zero);
|
||||
_ghost = _entitiesFactory.CreateGhost(Vector3.zero + Vector3.forward * 5);
|
||||
|
||||
_brainsFactory.CreateGhostBrain(_ghost);
|
||||
|
||||
_isRunning = true;
|
||||
}
|
||||
@@ -41,11 +45,11 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (_entity == null)
|
||||
if (_hero == null)
|
||||
return;
|
||||
|
||||
GUI.Label(new Rect(10, 20, 200, 50), $"Health: {_entity.CurrentHealth.Value}/{_entity.MaxHealth.Value}");
|
||||
GUI.Label(new Rect(10, 40, 200, 50), $"Energy: {_entity.CurrentEnergy.Value}/{_entity.MaxEnergy.Value}");
|
||||
GUI.Label(new Rect(10, 20, 200, 50), $"Health: {_hero.CurrentHealth.Value}/{_hero.MaxHealth.Value}");
|
||||
GUI.Label(new Rect(10, 40, 200, 50), $"Energy: {_hero.CurrentEnergy.Value}/{_hero.MaxEnergy.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event
|
||||
{
|
||||
public class ReactiveEvent<T> : IReadOnlyEvent<T>
|
||||
public class ReactiveEvent<T> : IReadOnlyEvent<T>
|
||||
{
|
||||
private readonly List<Subscriber<T>> _subscribers = new();
|
||||
private readonly List<Subscriber<T>> _toAdd = new();
|
||||
@@ -41,9 +41,9 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event
|
||||
|
||||
public class ReactiveEvent : IReadOnlyEvent
|
||||
{
|
||||
private readonly List<Subscriber> _subscribers = new();
|
||||
private readonly List<Subscriber> _toAdd = new();
|
||||
private readonly List<Subscriber> _toRemove = new();
|
||||
private readonly List<Subscriber> _subscribers = new List<Subscriber>();
|
||||
private readonly List<Subscriber> _toAdd = new List<Subscriber>();
|
||||
private readonly List<Subscriber> _toRemove = new List<Subscriber>();
|
||||
|
||||
public IDisposable Subscribe(Action action)
|
||||
{
|
||||
|
||||
@@ -5,16 +5,16 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement
|
||||
{
|
||||
public class ReactiveVariable<T> : IReadOnlyVariable<T> where T : IEquatable<T>
|
||||
{
|
||||
private readonly List<Subscriber<T, T>> _subscribers = new ();
|
||||
private readonly List<Subscriber<T, T>> _toAddList = new ();
|
||||
private readonly List<Subscriber<T, T>> _toRemoveList = new ();
|
||||
|
||||
private readonly List<Subscriber<T, T>> _subscribers = new List<Subscriber<T, T>>();
|
||||
private readonly List<Subscriber<T, T>> _toAddList = new List<Subscriber<T, T>>();
|
||||
private readonly List<Subscriber<T, T>> _toRemoveList = new List<Subscriber<T, T>>();
|
||||
|
||||
public ReactiveVariable() => _value = default(T);
|
||||
|
||||
|
||||
public ReactiveVariable(T value) => _value = value;
|
||||
|
||||
|
||||
private T _value;
|
||||
|
||||
|
||||
public T Value
|
||||
{
|
||||
get => _value;
|
||||
@@ -27,33 +27,33 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement
|
||||
Invoke(oldValue, _value);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(Action<T, T> action)
|
||||
{
|
||||
Subscriber<T, T> subscriber = new (action, RemoveSubscriber);
|
||||
_toAddList.Add(subscriber);
|
||||
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(Action<T, T> action)
|
||||
{
|
||||
Subscriber<T, T> subscriber = new Subscriber<T, T>(action, RemoveSubscriber);
|
||||
_toAddList.Add(subscriber);
|
||||
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
private void RemoveSubscriber(Subscriber<T, T> subscriber) => _toRemoveList.Add(subscriber);
|
||||
|
||||
private void Invoke(T oldValue, T newValue)
|
||||
{
|
||||
if(_toAddList.Count > 0)
|
||||
if (_toAddList.Count > 0)
|
||||
{
|
||||
_subscribers.AddRange(_toAddList);
|
||||
_toAddList.Clear();
|
||||
}
|
||||
|
||||
if(_toRemoveList.Count > 0)
|
||||
|
||||
if (_toRemoveList.Count > 0)
|
||||
{
|
||||
foreach (Subscriber<T, T> subscriber in _toRemoveList)
|
||||
_subscribers.Remove(subscriber);
|
||||
|
||||
|
||||
_toRemoveList.Clear();
|
||||
}
|
||||
|
||||
|
||||
foreach (Subscriber<T, T> subscriber in _subscribers)
|
||||
subscriber.Invoke(oldValue, newValue);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public interface IState
|
||||
{
|
||||
IReadOnlyEvent Entered { get; }
|
||||
IReadOnlyEvent Exited { get; }
|
||||
|
||||
void Enter();
|
||||
void Exit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aed062833c374a6c8f4f784616dad179
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public interface IUpdatableState : IState
|
||||
{
|
||||
void Update(float deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1ea582554484972bd37276974ad4434
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public abstract class ParallelState<TState> : State where TState : class, IState
|
||||
{
|
||||
private List<TState> _states;
|
||||
|
||||
public ParallelState(params TState[] states)
|
||||
{
|
||||
_states = new List<TState>(states);
|
||||
}
|
||||
|
||||
protected IReadOnlyList<TState> States => _states;
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
base.Enter();
|
||||
|
||||
foreach (TState state in _states)
|
||||
state.Enter();
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
base.Exit();
|
||||
|
||||
foreach (TState state in _states)
|
||||
state.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3084d8be941e4eb89f019da66b818702
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,18 @@
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public abstract class State : IState
|
||||
{
|
||||
private ReactiveEvent _entered = new();
|
||||
private ReactiveEvent _exited = new();
|
||||
|
||||
public IReadOnlyEvent Entered => _entered;
|
||||
|
||||
public IReadOnlyEvent Exited => _exited;
|
||||
|
||||
public virtual void Enter() => _entered.Invoke();
|
||||
|
||||
public virtual void Exit() => _exited.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 975fa4387d1e4f37afb584f32071e4bc
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using _Project.Develop.Runtime.Utilities.Conditions;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public abstract class StateMachine<TState> : State, IDisposable, IUpdatableState where TState : class, IState
|
||||
{
|
||||
private List<StateNode<TState>> _states = new();
|
||||
|
||||
private StateNode<TState> _currentState;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
private List<IDisposable> _disposables;
|
||||
|
||||
protected StateMachine(List<IDisposable> disposables)
|
||||
{
|
||||
_disposables = new List<IDisposable>(disposables);
|
||||
}
|
||||
|
||||
protected TState CurrentState => _currentState.State;
|
||||
|
||||
public void AddState(TState state) => _states.Add(new StateNode<TState>(state));
|
||||
|
||||
public void AddTransition(TState fromState, TState toState, ICondition condition)
|
||||
{
|
||||
StateNode<TState> from = _states.First(stateNode => stateNode.State == fromState);
|
||||
StateNode<TState> to = _states.First(stateNode => stateNode.State == toState);
|
||||
|
||||
from.AddTransition(new StateTransition<TState>(to, condition));
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (_isRunning == false)
|
||||
return;
|
||||
|
||||
foreach (StateTransition<TState> transition in _currentState.Transitions)
|
||||
{
|
||||
if (transition.Condition.Evaluate())
|
||||
{
|
||||
SwitchState(transition.ToState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLogic(deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void UpdateLogic(float deltaTime) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
foreach (StateNode<TState> stateNode in _states)
|
||||
if (stateNode.State is IDisposable disposableState)
|
||||
disposableState.Dispose();
|
||||
|
||||
_states.Clear();
|
||||
|
||||
foreach (IDisposable disposable in _disposables)
|
||||
disposable.Dispose();
|
||||
|
||||
_disposables.Clear();
|
||||
}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
base.Enter();
|
||||
|
||||
if (_currentState == null)
|
||||
SwitchState(_states[0]);
|
||||
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
base.Exit();
|
||||
|
||||
_currentState?.State.Exit();
|
||||
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
private void SwitchState(StateNode<TState> nextState)
|
||||
{
|
||||
_currentState?.State.Exit();
|
||||
_currentState = nextState;
|
||||
_currentState.State.Enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 421f6f14fa064baaa737bea3684d473f
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public class StateNode<TState> where TState : class, IState
|
||||
{
|
||||
private List<StateTransition<TState>> _transitions = new();
|
||||
|
||||
public StateNode(TState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
|
||||
public TState State { get; }
|
||||
|
||||
public IReadOnlyList<StateTransition<TState>> Transitions => _transitions;
|
||||
|
||||
public void AddTransition(StateTransition<TState> transition) => _transitions.Add(transition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dce697e6ac3b45b497e8e89c5ca258cc
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,16 @@
|
||||
using _Project.Develop.Runtime.Utilities.Conditions;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
|
||||
{
|
||||
public class StateTransition<TState> where TState : class, IState
|
||||
{
|
||||
public StateTransition(StateNode<TState> toState, ICondition condition)
|
||||
{
|
||||
ToState = toState;
|
||||
Condition = condition;
|
||||
}
|
||||
|
||||
public StateNode<TState> ToState { get; }
|
||||
public ICondition Condition { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07aef2e29c2643419ff17e32556da944
|
||||
timeCreated: 1772453453
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 831af1f46fb24b5c89e15c287f7bdfad
|
||||
timeCreated: 1772456814
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement;
|
||||
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
|
||||
using Assets._Project.Develop.Runtime.Utilities.CoroutinesManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.Timer
|
||||
{
|
||||
public class TimerService : IDisposable
|
||||
{
|
||||
private float _cooldown;
|
||||
|
||||
private ReactiveEvent _cooldownEnded;
|
||||
|
||||
private ReactiveVariable<float> _currentTime;
|
||||
|
||||
private ICoroutinesPerformer _coroutinePerformer;
|
||||
private Coroutine _cooldownProcess;
|
||||
|
||||
public TimerService(
|
||||
float cooldown,
|
||||
ICoroutinesPerformer coroutinePerformer)
|
||||
{
|
||||
_cooldown = cooldown;
|
||||
_coroutinePerformer = coroutinePerformer;
|
||||
|
||||
_cooldownEnded = new ReactiveEvent();
|
||||
_currentTime = new ReactiveVariable<float>();
|
||||
}
|
||||
|
||||
public IReadOnlyEvent CooldownEnded => _cooldownEnded;
|
||||
public IReadOnlyVariable<float> CurrentTime => _currentTime;
|
||||
public bool IsOver => _currentTime.Value <= 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_cooldownProcess != null)
|
||||
_coroutinePerformer.StopPerform(_cooldownProcess);
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Stop();
|
||||
|
||||
_cooldownProcess = _coroutinePerformer.StartPerform(CooldownProcess());
|
||||
}
|
||||
|
||||
private IEnumerator CooldownProcess()
|
||||
{
|
||||
_currentTime.Value = _cooldown;
|
||||
|
||||
while (IsOver == false)
|
||||
{
|
||||
_currentTime.Value -= Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_cooldownEnded.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5636415f9a840ab8ff88d96719179ec
|
||||
timeCreated: 1772456820
|
||||
@@ -0,0 +1,18 @@
|
||||
using Assets._Project.Develop.Runtime.Infrastructure.DI;
|
||||
using Assets._Project.Develop.Runtime.Utilities.CoroutinesManagement;
|
||||
|
||||
namespace Assets._Project.Develop.Runtime.Utilities.Timer
|
||||
{
|
||||
public class TimerServiceFactory
|
||||
{
|
||||
private readonly DIContainer _container;
|
||||
|
||||
public TimerServiceFactory(DIContainer container)
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
public TimerService Create(float cooldown)
|
||||
=> new TimerService(cooldown, _container.Resolve<ICoroutinesPerformer>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fbab9cab89b4ad094fc636facb8597a
|
||||
timeCreated: 1772456820
|
||||
Reference in New Issue
Block a user