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

1
.gitignore vendored
View File

@@ -27,6 +27,7 @@
# Visual Studio cache directory # Visual Studio cache directory
.vs/ .vs/
.agent/
# Gradle cache directory # Gradle cache directory
.gradle/ .gradle/

5
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"recommendations": [
"visualstudiotoolsforunity.vstuc"
]
}

10
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Unity",
"type": "vstuc",
"request": "attach"
}
]
}

View File

@@ -1,5 +1,5 @@
{ {
"dotrush.roslyn.projectOrSolutionFiles": [ "dotrush.roslyn.projectOrSolutionFiles": [
"e:\\_Programming\\Unity\\UnityGames\\project-entity\\project-entity.sln" "e:\\_Programming\\Unity\\UnityGames\\project-entity\\project-entity.sln"
] ]
} }

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.DataManagement.DataProviders;
using Assets._Project.Develop.Runtime.Utilities.LoadingScreen; using Assets._Project.Develop.Runtime.Utilities.LoadingScreen;
using Assets._Project.Develop.Runtime.Utilities.SceneManagement; using Assets._Project.Develop.Runtime.Utilities.SceneManagement;
using Assets._Project.Develop.Runtime.Utilities.Timer;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
@@ -24,6 +25,7 @@ namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
public static void Process(DIContainer container) public static void Process(DIContainer container)
{ {
container.RegisterAsSingle<ICoroutinesPerformer>(CreateCoroutinesPerformer); container.RegisterAsSingle<ICoroutinesPerformer>(CreateCoroutinesPerformer);
container.RegisterAsSingle(CreateTimerServiceFactory);
container.RegisterAsSingle(CreateConfigsProviderService); container.RegisterAsSingle(CreateConfigsProviderService);
container.RegisterAsSingle(CreateResourcesAssetsLoader); container.RegisterAsSingle(CreateResourcesAssetsLoader);
container.RegisterAsSingle(CreateSceneLoaderService); container.RegisterAsSingle(CreateSceneLoaderService);
@@ -43,7 +45,7 @@ namespace Assets._Project.Develop.Runtime.Infrastructure.EntryPoint
container.RegisterAsSingle(CreateViewsFactory); container.RegisterAsSingle(CreateViewsFactory);
container.RegisterAsSingle<ILoadingScreen>(CreateLoadingScreen); container.RegisterAsSingle<ILoadingScreen>(CreateLoadingScreen);
} }
private static ConfigsProviderService CreateConfigsProviderService(DIContainer c) private static ConfigsProviderService CreateConfigsProviderService(DIContainer c)
{ {
ResourcesAssetsLoader resourcesAssetsLoader = c.Resolve<ResourcesAssetsLoader>(); 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 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 PlayerInput CreatePlayerInput(DIContainer c) => c.Resolve<InputFactory>().CreatePlayerInput();
private static UIInput CreateUIInput(DIContainer c) => c.Resolve<InputFactory>().CreateUIInput(); 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>(); _monoEntitiesFactory = container.Resolve<MonoEntitiesFactory>();
_playerInput = container.Resolve<IPlayerInput>(); _playerInput = container.Resolve<IPlayerInput>();
} }
public Entity CreateHero(Vector3 position) public Entity CreateHero(Vector3 position)
{ {
Entity entity = CreateEmpty(); Entity entity = CreateEmpty();
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Hero); _monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Hero);
entity entity
@@ -65,29 +65,29 @@ namespace _Project.Develop.Runtime.Entities
.AddAttackCooldownInitialTime() .AddAttackCooldownInitialTime()
.AddAttackCooldownCurrentTime() .AddAttackCooldownCurrentTime()
.AddInAttackCooldown(); .AddInAttackCooldown();
ICompositeCondition canMove = new CompositeCondition() ICompositeCondition canMove = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition canRotate = new CompositeCondition() ICompositeCondition canRotate = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition mustDie = new CompositeCondition() ICompositeCondition mustDie = new CompositeCondition()
.Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0)); .Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0));
ICompositeCondition mustSelfRelease = new CompositeCondition() ICompositeCondition mustSelfRelease = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value)) .Add(new FuncCondition(() => entity.IsDead.Value))
.Add(new FuncCondition(() => entity.InDeathProcess.Value == false)); .Add(new FuncCondition(() => entity.InDeathProcess.Value == false));
ICompositeCondition canApplyDamage = new CompositeCondition() ICompositeCondition canApplyDamage = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition canStartAttack = new CompositeCondition() ICompositeCondition canStartAttack = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)) .Add(new FuncCondition(() => entity.IsDead.Value == false))
.Add(new FuncCondition(() => entity.InAttackProcess.Value == false)) .Add(new FuncCondition(() => entity.InAttackProcess.Value == false))
.Add(new FuncCondition(() => entity.IsMoving.Value == false)) .Add(new FuncCondition(() => entity.IsMoving.Value == false))
.Add(new FuncCondition(() => entity.InAttackCooldown.Value == false)); .Add(new FuncCondition(() => entity.InAttackCooldown.Value == false));
ICompositeCondition mustCancelAttack = new CompositeCondition(LogicOperationsUtils.Or) ICompositeCondition mustCancelAttack = new CompositeCondition(LogicOperationsUtils.Or)
.Add(new FuncCondition(() => entity.IsDead.Value)) .Add(new FuncCondition(() => entity.IsDead.Value))
.Add(new FuncCondition(() => entity.IsMoving.Value)); .Add(new FuncCondition(() => entity.IsMoving.Value));
@@ -107,33 +107,33 @@ namespace _Project.Develop.Runtime.Entities
.AddSystem(new RotateDirectionByMoveInputSystem(_playerInput)) .AddSystem(new RotateDirectionByMoveInputSystem(_playerInput))
.AddSystem(new RigidbodyMovementSystem()) .AddSystem(new RigidbodyMovementSystem())
.AddSystem(new RigidbodyRotationSystem()) .AddSystem(new RigidbodyRotationSystem())
.AddSystem(new AttackCancelSystem()) .AddSystem(new AttackCancelSystem())
.AddSystem(new StartAttackSystem()) .AddSystem(new StartAttackSystem())
.AddSystem(new ProcessAttackTimerSystem()) .AddSystem(new ProcessAttackTimerSystem())
.AddSystem(new AttackDelayEndTriggerSystem()) .AddSystem(new AttackDelayEndTriggerSystem())
.AddSystem(new InstantShootSystem(this)) .AddSystem(new InstantShootSystem(this))
.AddSystem(new EndAttackSystem()) .AddSystem(new EndAttackSystem())
.AddSystem(new AttackCooldownTimerSystem()) .AddSystem(new AttackCooldownTimerSystem())
.AddSystem(new ApplyDamageSystem()) .AddSystem(new ApplyDamageSystem())
.AddSystem(new DeathSwitcherSystem()) .AddSystem(new DeathSwitcherSystem())
.AddSystem(new DeathProcessTimerSystem()) .AddSystem(new DeathProcessTimerSystem())
.AddSystem(new DisableCollidersOnDeathSystem()) .AddSystem(new DisableCollidersOnDeathSystem())
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext)); .AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
_entitiesLifeContext.Add(entity); _entitiesLifeContext.Add(entity);
return entity; return entity;
} }
public Entity CreateGhost(Vector3 position) public Entity CreateGhost(Vector3 position)
{ {
Entity entity = CreateEmpty(); Entity entity = CreateEmpty();
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Ghost); _monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Ghost);
entity entity
@@ -154,20 +154,20 @@ namespace _Project.Develop.Runtime.Entities
.AddInDeathProcess() .AddInDeathProcess()
.AddDeathProcessInitialTime(new ReactiveVariable<float>(2)) .AddDeathProcessInitialTime(new ReactiveVariable<float>(2))
.AddDeathProcessCurrentTime(); .AddDeathProcessCurrentTime();
ICompositeCondition canMove = new CompositeCondition() ICompositeCondition canMove = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition canRotate = new CompositeCondition() ICompositeCondition canRotate = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition mustDie = new CompositeCondition() ICompositeCondition mustDie = new CompositeCondition()
.Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0)); .Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0));
ICompositeCondition mustSelfRelease = new CompositeCondition() ICompositeCondition mustSelfRelease = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value)) .Add(new FuncCondition(() => entity.IsDead.Value))
.Add(new FuncCondition(() => entity.InDeathProcess.Value == false)); .Add(new FuncCondition(() => entity.InDeathProcess.Value == false));
ICompositeCondition canApplyDamage = new CompositeCondition() ICompositeCondition canApplyDamage = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
@@ -181,38 +181,38 @@ namespace _Project.Develop.Runtime.Entities
entity entity
.AddSystem(new RigidbodyMovementSystem()) .AddSystem(new RigidbodyMovementSystem())
.AddSystem(new RigidbodyRotationSystem()) .AddSystem(new RigidbodyRotationSystem())
.AddSystem(new BodyContactsDetectingSystem()) .AddSystem(new BodyContactsDetectingSystem())
.AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService)) .AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService))
.AddSystem(new DealDamageOnContactSystem()) .AddSystem(new DealDamageOnContactSystem())
.AddSystem(new ApplyDamageSystem()) .AddSystem(new ApplyDamageSystem())
.AddSystem(new DeathSwitcherSystem()) .AddSystem(new DeathSwitcherSystem())
.AddSystem(new DeathProcessTimerSystem()) .AddSystem(new DeathProcessTimerSystem())
.AddSystem(new DisableCollidersOnDeathSystem()) .AddSystem(new DisableCollidersOnDeathSystem())
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext)); .AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
_entitiesLifeContext.Add(entity); _entitiesLifeContext.Add(entity);
return entity; return entity;
} }
public Entity CreateTeleportWizard(Vector3 position) public Entity CreateTeleportWizard(Vector3 position)
{ {
Entity entity = CreateEmpty(); Entity entity = CreateEmpty();
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Mage); _monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Mage);
entity entity
.AddContactsDetectingMask(Layers.CharactersMask) .AddContactsDetectingMask(Layers.CharactersMask)
.AddContactCollidersBuffer(new Buffer<Collider>(32)) .AddContactCollidersBuffer(new Buffer<Collider>(32))
.AddContactEntitiesBuffer(new Buffer<Entity>(32)) .AddContactEntitiesBuffer(new Buffer<Entity>(32))
.AddMaxHealth(new ReactiveVariable<float>(150)) .AddMaxHealth(new ReactiveVariable<float>(150))
.AddCurrentHealth(new ReactiveVariable<float>(150)) .AddCurrentHealth(new ReactiveVariable<float>(150))
.AddTeleportTarget(entity.Transform) .AddTeleportTarget(entity.Transform)
.AddTeleportToPoint(entity.Transform) .AddTeleportToPoint(entity.Transform)
.AddStartTeleportEvent() .AddStartTeleportEvent()
@@ -221,14 +221,14 @@ namespace _Project.Develop.Runtime.Entities
.AddFindTeleportPointEvent() .AddFindTeleportPointEvent()
.AddFindTeleportPointRequest() .AddFindTeleportPointRequest()
.AddEndTeleportEvent() .AddEndTeleportEvent()
.AddTeleportDamage(new ReactiveVariable<float>(50)) .AddTeleportDamage(new ReactiveVariable<float>(50))
.AddTeleportDamageRadius(new ReactiveVariable<float>(6)) .AddTeleportDamageRadius(new ReactiveVariable<float>(6))
.AddTeleportDamageMask(Layers.CharactersMask) .AddTeleportDamageMask(Layers.CharactersMask)
.AddTeleportEnergyCost(new ReactiveVariable<int>(20)) .AddTeleportEnergyCost(new ReactiveVariable<int>(20))
.AddTeleportSearchRadius(new ReactiveVariable<float>(6)) .AddTeleportSearchRadius(new ReactiveVariable<float>(6))
.AddCurrentEnergy(new ReactiveVariable<int>(60)) .AddCurrentEnergy(new ReactiveVariable<int>(60))
.AddMaxEnergy(new ReactiveVariable<int>(60)) .AddMaxEnergy(new ReactiveVariable<int>(60))
.AddUseEnergyEvent() .AddUseEnergyEvent()
@@ -239,7 +239,7 @@ namespace _Project.Develop.Runtime.Entities
.AddIsAutoRegenEnergy(new ReactiveVariable<bool>(true)) .AddIsAutoRegenEnergy(new ReactiveVariable<bool>(true))
.AddEnergyAutoRegenCurrentTime() .AddEnergyAutoRegenCurrentTime()
.AddEnergyAutoRegenInitialTime(new ReactiveVariable<float>(3)) .AddEnergyAutoRegenInitialTime(new ReactiveVariable<float>(3))
.AddBodyContactDamage(new ReactiveVariable<float>(50)) .AddBodyContactDamage(new ReactiveVariable<float>(50))
.AddTakeDamageRequest() .AddTakeDamageRequest()
.AddTakeDamageEvent() .AddTakeDamageEvent()
@@ -247,24 +247,24 @@ namespace _Project.Develop.Runtime.Entities
.AddInDeathProcess() .AddInDeathProcess()
.AddDeathProcessInitialTime(new ReactiveVariable<float>(2)) .AddDeathProcessInitialTime(new ReactiveVariable<float>(2))
.AddDeathProcessCurrentTime(); .AddDeathProcessCurrentTime();
ICompositeCondition canRegenEnergy = new CompositeCondition() ICompositeCondition canRegenEnergy = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition canUseEnergy = new CompositeCondition() ICompositeCondition canUseEnergy = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition canStartTeleport = new CompositeCondition() ICompositeCondition canStartTeleport = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)) .Add(new FuncCondition(() => entity.IsDead.Value == false))
.Add(new FuncCondition(() => entity.CurrentEnergy.Value >= entity.TeleportEnergyCost.Value)); .Add(new FuncCondition(() => entity.CurrentEnergy.Value >= entity.TeleportEnergyCost.Value));
ICompositeCondition mustDie = new CompositeCondition() ICompositeCondition mustDie = new CompositeCondition()
.Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0)); .Add(new FuncCondition(() => entity.CurrentHealth.Value <= 0));
ICompositeCondition mustSelfRelease = new CompositeCondition() ICompositeCondition mustSelfRelease = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value)) .Add(new FuncCondition(() => entity.IsDead.Value))
.Add(new FuncCondition(() => entity.InDeathProcess.Value == false)); .Add(new FuncCondition(() => entity.InDeathProcess.Value == false));
ICompositeCondition canApplyDamage = new CompositeCondition() ICompositeCondition canApplyDamage = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
@@ -278,40 +278,40 @@ namespace _Project.Develop.Runtime.Entities
entity entity
.AddSystem(new TeleportByInputSystem(_playerInput)) .AddSystem(new TeleportByInputSystem(_playerInput))
// .AddSystem(new RegenEnergyByValueSystem()) // .AddSystem(new RegenEnergyByValueSystem())
.AddSystem(new RegenEnergyByPercentageSystem()) .AddSystem(new RegenEnergyByPercentageSystem())
.AddSystem(new UseEnergySystem()) .AddSystem(new UseEnergySystem())
.AddSystem(new AutoRegenEnergyTimerSystem()) .AddSystem(new AutoRegenEnergyTimerSystem())
.AddSystem(new TeleportStartByEnergySystem()) .AddSystem(new TeleportStartByEnergySystem())
.AddSystem(new TeleportProcessSystem()) .AddSystem(new TeleportProcessSystem())
.AddSystem(new FindRandomPointForTeleportSystem()) .AddSystem(new FindRandomPointForTeleportSystem())
.AddSystem(new EndTeleportSystem()) .AddSystem(new EndTeleportSystem())
.AddSystem(new InstantTeleportSystem()) .AddSystem(new InstantTeleportSystem())
.AddSystem(new DealDamageAfterTeleportSystem(_collidersRegistryService)) .AddSystem(new DealDamageAfterTeleportSystem(_collidersRegistryService))
.AddSystem(new BodyContactsDetectingSystem()) .AddSystem(new BodyContactsDetectingSystem())
.AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService)) .AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService))
.AddSystem(new DealDamageOnContactSystem()) .AddSystem(new DealDamageOnContactSystem())
.AddSystem(new ApplyDamageSystem()) .AddSystem(new ApplyDamageSystem())
.AddSystem(new DeathSwitcherSystem()) .AddSystem(new DeathSwitcherSystem())
.AddSystem(new DeathProcessTimerSystem()) .AddSystem(new DeathProcessTimerSystem())
.AddSystem(new DisableCollidersOnDeathSystem()) .AddSystem(new DisableCollidersOnDeathSystem())
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext)); .AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
_entitiesLifeContext.Add(entity); _entitiesLifeContext.Add(entity);
return entity; return entity;
} }
public Entity CreateProjectile(Vector3 position, Vector3 direction, float damage) public Entity CreateProjectile(Vector3 position, Vector3 direction, float damage)
{ {
Entity entity = CreateEmpty(); Entity entity = CreateEmpty();
_monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Projectile); _monoEntitiesFactory.Create(entity, position, PathToResources.Entity.Projectile);
entity entity
@@ -327,13 +327,13 @@ namespace _Project.Develop.Runtime.Entities
.AddIsMoving() .AddIsMoving()
.AddDeathMask(Layers.CharactersMask | Layers.EnvironmentMask) .AddDeathMask(Layers.CharactersMask | Layers.EnvironmentMask)
.AddIsTouchDeathMask(); .AddIsTouchDeathMask();
ICompositeCondition canMove = new CompositeCondition() ICompositeCondition canMove = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition canRotate = new CompositeCondition() ICompositeCondition canRotate = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsDead.Value == false)); .Add(new FuncCondition(() => entity.IsDead.Value == false));
ICompositeCondition mustDie = new CompositeCondition() ICompositeCondition mustDie = new CompositeCondition()
.Add(new FuncCondition(() => entity.IsTouchDeathMask.Value)); .Add(new FuncCondition(() => entity.IsTouchDeathMask.Value));
@@ -349,19 +349,19 @@ namespace _Project.Develop.Runtime.Entities
entity entity
.AddSystem(new RigidbodyMovementSystem()) .AddSystem(new RigidbodyMovementSystem())
.AddSystem(new RigidbodyRotationSystem()) .AddSystem(new RigidbodyRotationSystem())
.AddSystem(new BodyContactsDetectingSystem()) .AddSystem(new BodyContactsDetectingSystem())
.AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService)) .AddSystem(new BodyContactsEntitiesFilterSystem(_collidersRegistryService))
.AddSystem(new DealDamageOnContactSystem()) .AddSystem(new DealDamageOnContactSystem())
.AddSystem(new DeathMaskTouchDetectorSystem()) .AddSystem(new DeathMaskTouchDetectorSystem())
.AddSystem(new DeathSwitcherSystem()) .AddSystem(new DeathSwitcherSystem())
.AddSystem(new DisableCollidersOnDeathSystem()) .AddSystem(new DisableCollidersOnDeathSystem())
.AddSystem(new SelfReleaseSystem(_entitiesLifeContext)); .AddSystem(new SelfReleaseSystem(_entitiesLifeContext));
_entitiesLifeContext.Add(entity); _entitiesLifeContext.Add(entity);
return 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.Utilities.Conditions;
using _Project.Develop.Runtime.Utils.ReactiveManagement; using _Project.Develop.Runtime.Utils.ReactiveManagement;
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event; using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
using Unity.Mathematics; using UnityEngine;
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.Energy.Systems 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> _regenEnergyRequest;
private ReactiveEvent<int> _regenEnergyEvent; private ReactiveEvent<int> _regenEnergyEvent;
private ReactiveVariable<int> _currentEnergy; private ReactiveVariable<int> _currentEnergy;
private ReactiveVariable<int> _maxEnergy; private ReactiveVariable<int> _maxEnergy;
private ICompositeCondition _canRegen; private ICompositeCondition _canRegen;
private IDisposable _regenRequestDispose; private IDisposable _regenRequestDispose;
public void OnInit(Entity entity) public void OnInit(Entity entity)
{ {
_currentEnergy = entity.CurrentEnergy; _currentEnergy = entity.CurrentEnergy;
_maxEnergy = entity.MaxEnergy; _maxEnergy = entity.MaxEnergy;
_regenEnergyRequest = entity.RegenEnergyRequest; _regenEnergyRequest = entity.RegenEnergyRequest;
_regenEnergyEvent = entity.RegenEnergyEvent; _regenEnergyEvent = entity.RegenEnergyEvent;
_canRegen = entity.CanRegenEnergy; _canRegen = entity.CanRegenEnergy;
_regenRequestDispose = _regenEnergyRequest.Subscribe(OnRegenRequest); _regenRequestDispose = _regenEnergyRequest.Subscribe(OnRegenRequest);
} }
private void OnRegenRequest(int percentage) private void OnRegenRequest(int percentage)
{ {
if (percentage <= 0) if (percentage <= 0)
throw new ArgumentException("Energy regen percentage must be positive", nameof(percentage)); throw new ArgumentException("Energy regen percentage must be positive", nameof(percentage));
if (_canRegen.Evaluate() == false) if (_canRegen.Evaluate() == false)
return; return;
int energyDifference = _maxEnergy.Value - _currentEnergy.Value; int energyDifference = _maxEnergy.Value - _currentEnergy.Value;
if (energyDifference <= 0) if (energyDifference <= 0)
return; 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) if (regenAmount < 1 && _maxEnergy.Value > 0)
regenAmount = 1; regenAmount = 1;
int valueAdded = math.min(regenAmount, energyDifference); int valueAdded = Mathf.Min(regenAmount, energyDifference);
_currentEnergy.Value += valueAdded; _currentEnergy.Value += valueAdded;
_regenEnergyEvent.Invoke(valueAdded); _regenEnergyEvent.Invoke(valueAdded);
} }
public void OnDispose() public void OnDispose()
{ {
_regenRequestDispose.Dispose(); _regenRequestDispose.Dispose();

View File

@@ -3,7 +3,6 @@ using _Project.Develop.Runtime.Entities;
using _Project.Develop.Runtime.Utilities.Conditions; using _Project.Develop.Runtime.Utilities.Conditions;
using _Project.Develop.Runtime.Utils.ReactiveManagement; using _Project.Develop.Runtime.Utils.ReactiveManagement;
using _Project.Develop.Runtime.Utils.ReactiveManagement.Event; using _Project.Develop.Runtime.Utils.ReactiveManagement.Event;
using Unity.Mathematics;
using UnityEngine; using UnityEngine;
namespace _Project.Develop.Runtime.Logic.Gameplay.Features.Energy.Systems 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> _regenEnergyRequest;
private ReactiveEvent<int> _regenEnergyEvent; private ReactiveEvent<int> _regenEnergyEvent;
private ReactiveVariable<int> _currentEnergy; private ReactiveVariable<int> _currentEnergy;
private ReactiveVariable<int> _maxEnergy; private ReactiveVariable<int> _maxEnergy;
private ICompositeCondition _canRegen; private ICompositeCondition _canRegen;
private IDisposable _regenRequestDispose; private IDisposable _regenRequestDispose;
public void OnInit(Entity entity) public void OnInit(Entity entity)
{ {
_currentEnergy = entity.CurrentEnergy; _currentEnergy = entity.CurrentEnergy;
_maxEnergy = entity.MaxEnergy; _maxEnergy = entity.MaxEnergy;
_regenEnergyRequest = entity.RegenEnergyRequest; _regenEnergyRequest = entity.RegenEnergyRequest;
_regenEnergyEvent = entity.RegenEnergyEvent; _regenEnergyEvent = entity.RegenEnergyEvent;
_canRegen = entity.CanRegenEnergy; _canRegen = entity.CanRegenEnergy;
_regenRequestDispose = _regenEnergyRequest.Subscribe(OnRegenRequest); _regenRequestDispose = _regenEnergyRequest.Subscribe(OnRegenRequest);
} }
private void OnRegenRequest(int value) private void OnRegenRequest(int value)
{ {
if (value <= 0) if (value <= 0)
throw new ArgumentException("Energy regen value must be positive", nameof(value)); throw new ArgumentException("Energy regen value must be positive", nameof(value));
if (_canRegen.Evaluate() == false) if (_canRegen.Evaluate() == false)
return; return;
int energyDifference = _maxEnergy.Value - _currentEnergy.Value; int energyDifference = _maxEnergy.Value - _currentEnergy.Value;
if (energyDifference <= 0) if (energyDifference <= 0)
return; return;
int valueAdded = math.min(value, energyDifference); int valueAdded = Mathf.Min(value, energyDifference);
_currentEnergy.Value += valueAdded; _currentEnergy.Value += valueAdded;
_regenEnergyEvent.Invoke(valueAdded); _regenEnergyEvent.Invoke(valueAdded);
} }
public void OnDispose() public void OnDispose()
{ {
_regenRequestDispose.Dispose(); _regenRequestDispose.Dispose();

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections; using System.Collections;
using _Project.Develop.Runtime.Entities; using _Project.Develop.Runtime.Entities;
using _Project.Develop.Runtime.Logic.Gameplay.Features; using _Project.Develop.Runtime.Logic.Gameplay.Features;
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI;
using _Project.Develop.Runtime.Utils.InputManagement; using _Project.Develop.Runtime.Utils.InputManagement;
using UnityEngine; using UnityEngine;
@@ -16,7 +17,7 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
private DIContainer _container; private DIContainer _container;
private EntitiesLifeContext _entitiesLifeContext; private EntitiesLifeContext _entitiesLifeContext;
private GameplayInputArgs _gameplayArgs; private AIBrainsContext _aiBrainsContext;
private IPlayerInput _playerInput; 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"); throw new ArgumentException($"{nameof(sceneArgs)} is not match with {typeof(GameplayInputArgs)} type");
GameplayContextRegistrations.Process(_container); GameplayContextRegistrations.Process(_container);
_gameplayArgs = gameplayInputArgs;
} }
public override IEnumerator Initialize() public override IEnumerator Initialize()
{ {
_entitiesLifeContext = _container.Resolve<EntitiesLifeContext>(); _entitiesLifeContext = _container.Resolve<EntitiesLifeContext>();
_aiBrainsContext = _container.Resolve<AIBrainsContext>();
_testGameplay.Initialize(_container); _testGameplay.Initialize(_container);
yield break; yield break;
@@ -46,6 +47,7 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
private void Update() private void Update()
{ {
_aiBrainsContext?.Update(Time.deltaTime);
_entitiesLifeContext?.Update(Time.deltaTime); _entitiesLifeContext?.Update(Time.deltaTime);
} }
} }

View File

@@ -1,4 +1,5 @@
using _Project.Develop.Runtime.Entities; using _Project.Develop.Runtime.Entities;
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI;
using _Project.Develop.Runtime.UI; using _Project.Develop.Runtime.UI;
using _Project.Develop.Runtime.UI.Core; using _Project.Develop.Runtime.UI.Core;
using _Project.Develop.Runtime.UI.Screens.Gameplay; using _Project.Develop.Runtime.UI.Screens.Gameplay;
@@ -21,9 +22,13 @@ namespace Assets._Project.Develop.Runtime.Gameplay.Infrastructure
container.RegisterAsSingle(CreateEntitiesFactory); container.RegisterAsSingle(CreateEntitiesFactory);
container.RegisterAsSingle(CreateEntitiesLifeContext); container.RegisterAsSingle(CreateEntitiesLifeContext);
container.RegisterAsSingle(CreateCollidersRegistryService); container.RegisterAsSingle(CreateCollidersRegistryService);
container.RegisterAsSingle(CreateAIBrainContext);
container.RegisterAsSingle(CreateBrainsFactory);
container.RegisterAsSingle(CreateMonoEntitiesFactory).NonLazy(); 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 EntitiesLifeContext CreateEntitiesLifeContext(DIContainer c) => new();
private static EntitiesFactory CreateEntitiesFactory(DIContainer c) => new(c); private static EntitiesFactory CreateEntitiesFactory(DIContainer c) => new(c);

View File

@@ -1,5 +1,6 @@
using System; using System;
using _Project.Develop.Runtime.Entities; using _Project.Develop.Runtime.Entities;
using _Project.Develop.Runtime.Logic.Gameplay.Features.AI;
using _Project.Develop.Runtime.Utils.InputManagement; using _Project.Develop.Runtime.Utils.InputManagement;
using Assets._Project.Develop.Runtime.Infrastructure.DI; using Assets._Project.Develop.Runtime.Infrastructure.DI;
using UnityEngine; using UnityEngine;
@@ -10,8 +11,11 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features
{ {
private DIContainer _container; private DIContainer _container;
private EntitiesFactory _entitiesFactory; private EntitiesFactory _entitiesFactory;
private BrainsFactory _brainsFactory;
private Entity _entity; private Entity _hero;
private Entity _ghost;
private bool _isRunning; private bool _isRunning;
public void Initialize(DIContainer container) public void Initialize(DIContainer container)
@@ -20,15 +24,15 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features
_container.Resolve<IPlayerInput>().Enable(); _container.Resolve<IPlayerInput>().Enable();
_entitiesFactory = _container.Resolve<EntitiesFactory>(); _entitiesFactory = _container.Resolve<EntitiesFactory>();
_brainsFactory = _container.Resolve<BrainsFactory>();
} }
public void Run() public void Run()
{ {
_entity = _entitiesFactory.CreateTeleportWizard(Vector3.zero); _hero = _entitiesFactory.CreateTeleportWizard(Vector3.zero);
_ghost = _entitiesFactory.CreateGhost(Vector3.zero + Vector3.forward * 5);
_entitiesFactory.CreateGhost(Vector3.zero + Vector3.forward * 5);
_entitiesFactory.CreateGhost(Vector3.zero + Vector3.right * 5); _brainsFactory.CreateGhostBrain(_ghost);
_entitiesFactory.CreateGhost(Vector3.zero + Vector3.left * 5);
_isRunning = true; _isRunning = true;
} }
@@ -41,11 +45,11 @@ namespace _Project.Develop.Runtime.Logic.Gameplay.Features
private void OnGUI() private void OnGUI()
{ {
if (_entity == null) if (_hero == null)
return; return;
GUI.Label(new Rect(10, 20, 200, 50), $"Health: {_entity.CurrentHealth.Value}/{_entity.MaxHealth.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: {_entity.CurrentEnergy.Value}/{_entity.MaxEnergy.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 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>> _subscribers = new();
private readonly List<Subscriber<T>> _toAdd = new(); private readonly List<Subscriber<T>> _toAdd = new();
@@ -41,9 +41,9 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event
public class ReactiveEvent : IReadOnlyEvent public class ReactiveEvent : IReadOnlyEvent
{ {
private readonly List<Subscriber> _subscribers = new(); private readonly List<Subscriber> _subscribers = new List<Subscriber>();
private readonly List<Subscriber> _toAdd = new(); private readonly List<Subscriber> _toAdd = new List<Subscriber>();
private readonly List<Subscriber> _toRemove = new(); private readonly List<Subscriber> _toRemove = new List<Subscriber>();
public IDisposable Subscribe(Action action) 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> public class ReactiveVariable<T> : IReadOnlyVariable<T> where T : IEquatable<T>
{ {
private readonly List<Subscriber<T, T>> _subscribers = new (); private readonly List<Subscriber<T, T>> _subscribers = new List<Subscriber<T, T>>();
private readonly List<Subscriber<T, T>> _toAddList = new (); private readonly List<Subscriber<T, T>> _toAddList = new List<Subscriber<T, T>>();
private readonly List<Subscriber<T, T>> _toRemoveList = new (); private readonly List<Subscriber<T, T>> _toRemoveList = new List<Subscriber<T, T>>();
public ReactiveVariable() => _value = default(T); public ReactiveVariable() => _value = default(T);
public ReactiveVariable(T value) => _value = value; public ReactiveVariable(T value) => _value = value;
private T _value; private T _value;
public T Value public T Value
{ {
get => _value; get => _value;
@@ -27,33 +27,33 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement
Invoke(oldValue, _value); 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 RemoveSubscriber(Subscriber<T, T> subscriber) => _toRemoveList.Add(subscriber);
private void Invoke(T oldValue, T newValue) private void Invoke(T oldValue, T newValue)
{ {
if(_toAddList.Count > 0) if (_toAddList.Count > 0)
{ {
_subscribers.AddRange(_toAddList); _subscribers.AddRange(_toAddList);
_toAddList.Clear(); _toAddList.Clear();
} }
if(_toRemoveList.Count > 0) if (_toRemoveList.Count > 0)
{ {
foreach (Subscriber<T, T> subscriber in _toRemoveList) foreach (Subscriber<T, T> subscriber in _toRemoveList)
_subscribers.Remove(subscriber); _subscribers.Remove(subscriber);
_toRemoveList.Clear(); _toRemoveList.Clear();
} }
foreach (Subscriber<T, T> subscriber in _subscribers) foreach (Subscriber<T, T> subscriber in _subscribers)
subscriber.Invoke(oldValue, newValue); 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

View File

@@ -924,7 +924,7 @@ PlayerSettings:
embeddedLinuxEnableGamepadInput: 1 embeddedLinuxEnableGamepadInput: 1
hmiLogStartupTiming: 0 hmiLogStartupTiming: 0
hmiCpuConfiguration: hmiCpuConfiguration:
apiCompatibilityLevel: 6 apiCompatibilityLevel: 3
activeInputHandler: 2 activeInputHandler: 2
windowsGamepadBackendHint: 0 windowsGamepadBackendHint: 0
cloudProjectId: 4a15d730-99ee-4a69-b00c-c0c75aa639bd cloudProjectId: 4a15d730-99ee-4a69-b00c-c0c75aa639bd

33
project-entity.slnx Normal file
View File

@@ -0,0 +1,33 @@
<Solution>
<Project Path="Unity.Timeline.csproj" />
<Project Path="Unity.Timeline.Editor.csproj" />
<Project Path="Assembly-CSharp.csproj" />
<Project Path="Unity.InputSystem.csproj" />
<Project Path="Unity.TextMeshPro.Editor.csproj" />
<Project Path="Unity.PlasticSCM.Editor.csproj" />
<Project Path="Unity.VisualScripting.Flow.csproj" />
<Project Path="Unity.VisualScripting.Core.csproj" />
<Project Path="Unity.Mathematics.csproj" />
<Project Path="Unity.VisualScripting.Core.Editor.csproj" />
<Project Path="Unity.VisualScripting.Flow.Editor.csproj" />
<Project Path="UnityEditor.TestRunner.csproj" />
<Project Path="Unity.VisualScripting.SettingsProvider.Editor.csproj" />
<Project Path="UnityEngine.TestRunner.csproj" />
<Project Path="Unity.Searcher.Editor.csproj" />
<Project Path="Unity.Burst.csproj" />
<Project Path="Unity.VisualStudio.Editor.csproj" />
<Project Path="Unity.Rider.Editor.csproj" />
<Project Path="Unity.CollabProxy.Editor.csproj" />
<Project Path="Unity.VisualScripting.State.Editor.csproj" />
<Project Path="Unity.Burst.CodeGen.csproj" />
<Project Path="Unity.VisualScripting.State.csproj" />
<Project Path="Unity.TextMeshPro.csproj" />
<Project Path="Assembly-CSharp-firstpass.csproj" />
<Project Path="Unity.VSCode.Editor.csproj" />
<Project Path="Unity.Burst.Editor.csproj" />
<Project Path="Unity.InputSystem.ForUI.csproj" />
<Project Path="Unity.Mathematics.Editor.csproj" />
<Project Path="Unity.InputSystem.TestFramework.csproj" />
<Project Path="Unity.VisualScripting.Shared.Editor.csproj" />
<Project Path="Assembly-CSharp-Editor.csproj" />
</Solution>