feat: add state machine

This commit is contained in:
Bragin Stepan
2026-03-02 19:00:06 +05:00
parent 99c88c071f
commit 7737ee3158
50 changed files with 828 additions and 128 deletions

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d79e4112f0c64a90b05cae78e957a829
timeCreated: 1772454294

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 647b53823c00413081b7c9221d5bb5c1
timeCreated: 1772458604

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1e225964fa5a4120a1e4bf1cc8d47641
timeCreated: 1772455227

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0b566da239ac47d5b8c828cc1ac54cbe
timeCreated: 1772455908

View File

@@ -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);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77beb88ee43645a5b0f85bf7d1518e4a
timeCreated: 1772458164

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45f2a68dedcb4a61a4ae6dc8a50227e4
timeCreated: 1772458266

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a0534b2062654595bef4d1e59081a533
timeCreated: 1772454304

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3f40181e821046968e6c69ecef7e2384
timeCreated: 1772454318

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd6446a9b2f646bc870251ae870904b3
timeCreated: 1772454385

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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}");
}
}
}

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aed062833c374a6c8f4f784616dad179
timeCreated: 1772453453

View File

@@ -0,0 +1,7 @@
namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore
{
public interface IUpdatableState : IState
{
void Update(float deltaTime);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c1ea582554484972bd37276974ad4434
timeCreated: 1772453453

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3084d8be941e4eb89f019da66b818702
timeCreated: 1772453453

View File

@@ -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();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 975fa4387d1e4f37afb584f32071e4bc
timeCreated: 1772453453

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 421f6f14fa064baaa737bea3684d473f
timeCreated: 1772453453

View File

@@ -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);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dce697e6ac3b45b497e8e89c5ca258cc
timeCreated: 1772453453

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 07aef2e29c2643419ff17e32556da944
timeCreated: 1772453453

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 831af1f46fb24b5c89e15c287f7bdfad
timeCreated: 1772456814

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e5636415f9a840ab8ff88d96719179ec
timeCreated: 1772456820

View File

@@ -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>());
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6fbab9cab89b4ad094fc636facb8597a
timeCreated: 1772456820