From 7737ee31588e64646e8e28399fc68165096123ee Mon Sep 17 00:00:00 2001 From: Bragin Stepan Date: Mon, 2 Mar 2026 19:00:06 +0500 Subject: [PATCH] feat: add state machine --- .gitignore | 1 + .vscode/extensions.json | 5 + .vscode/launch.json | 10 ++ .vscode/settings.json | 8 +- .../EntryPoint/ProjectContextRegistrations.cs | 6 +- .../Gameplay/Entities/EntitiesFactory.cs | 104 +++++++++--------- .../Runtime/Logic/Gameplay/Features/AI.meta | 3 + .../Gameplay/Features/AI/AIBrainsContext.cs | 70 ++++++++++++ .../Features/AI/AIBrainsContext.cs.meta | 3 + .../Gameplay/Features/AI/AIStateMachine.cs | 24 ++++ .../Features/AI/AIStateMachine.cs.meta | 3 + .../Gameplay/Features/AI/BrainsFactory.cs | 64 +++++++++++ .../Features/AI/BrainsFactory.cs.meta | 3 + .../Logic/Gameplay/Features/AI/IBrain.cs | 14 +++ .../Logic/Gameplay/Features/AI/IBrain.cs.meta | 3 + .../Gameplay/Features/AI/StateMachineBrain.cs | 41 +++++++ .../Features/AI/StateMachineBrain.cs.meta | 3 + .../Logic/Gameplay/Features/AI/States.meta | 3 + .../Gameplay/Features/AI/States/EmptyState.cs | 12 ++ .../Features/AI/States/EmptyState.cs.meta | 3 + .../Features/AI/States/RandomMovementState.cs | 67 +++++++++++ .../AI/States/RandomMovementState.cs.meta | 3 + .../Systems/RegenEnergyByPercentageSystem.cs | 36 +++--- .../Systems/RegenEnergyByValueSystem.cs | 33 +++--- .../Infrastructure/GameplayBootstrap.cs | 6 +- .../GameplayContextRegistrations.cs | 5 + .../Gameplay/Infrastructure/TestGameplay.cs | 22 ++-- .../ReactiveManagement/Event/ReactiveEvent.cs | 8 +- .../Variable/ReactiveVariable.cs | 40 +++---- .../Utilities/StatesManagement/IState.cs | 14 +++ .../Utilities/StatesManagement/IState.cs.meta | 3 + .../StatesManagement/IUpdatableState.cs | 7 ++ .../StatesManagement/IUpdatableState.cs.meta | 3 + .../StatesManagement/ParallelState.cs | 32 ++++++ .../StatesManagement/ParallelState.cs.meta | 3 + .../Utilities/StatesManagement/State.cs | 18 +++ .../Utilities/StatesManagement/State.cs.meta | 3 + .../StatesManagement/StateMachine.cs | 96 ++++++++++++++++ .../StatesManagement/StateMachine.cs.meta | 3 + .../Utilities/StatesManagement/StateNode.cs | 20 ++++ .../StatesManagement/StateNode.cs.meta | 3 + .../StatesManagement/StateTransition.cs | 16 +++ .../StatesManagement/StateTransition.cs.meta | 3 + .../Runtime/Utilities/TimeManagement.meta | 3 + .../Utilities/TimeManagement/TimerService.cs | 67 +++++++++++ .../TimeManagement/TimerService.cs.meta | 3 + .../TimeManagement/TimerServiceFactory.cs | 18 +++ .../TimerServiceFactory.cs.meta | 3 + ProjectSettings/ProjectSettings.asset | 2 +- project-entity.slnx | 33 ++++++ 50 files changed, 828 insertions(+), 128 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs create mode 100644 Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/TimeManagement.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs.meta create mode 100644 Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs create mode 100644 Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs.meta create mode 100644 project-entity.slnx diff --git a/.gitignore b/.gitignore index 96ae0de..a2ffc6d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ # Visual Studio cache directory .vs/ +.agent/ # Gradle cache directory .gradle/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ddb6ff8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "visualstudiotoolsforunity.vstuc" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a56490c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Unity", + "type": "vstuc", + "request": "attach" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 1123c0f..d1a5610 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "dotrush.roslyn.projectOrSolutionFiles": [ - "e:\\_Programming\\Unity\\UnityGames\\project-entity\\project-entity.sln" - ] -} \ No newline at end of file + "dotrush.roslyn.projectOrSolutionFiles": [ + "e:\\_Programming\\Unity\\UnityGames\\project-entity\\project-entity.sln" + ] +} diff --git a/Assets/_Project/Develop/Runtime/Infrastructure/EntryPoint/ProjectContextRegistrations.cs b/Assets/_Project/Develop/Runtime/Infrastructure/EntryPoint/ProjectContextRegistrations.cs index 115be3b..a4fd9bb 100644 --- a/Assets/_Project/Develop/Runtime/Infrastructure/EntryPoint/ProjectContextRegistrations.cs +++ b/Assets/_Project/Develop/Runtime/Infrastructure/EntryPoint/ProjectContextRegistrations.cs @@ -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(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(CreateLoadingScreen); } - + private static ConfigsProviderService CreateConfigsProviderService(DIContainer c) { ResourcesAssetsLoader resourcesAssetsLoader = c.Resolve(); @@ -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().CreatePlayerInput(); private static UIInput CreateUIInput(DIContainer c) => c.Resolve().CreateUIInput(); diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Entities/EntitiesFactory.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Entities/EntitiesFactory.cs index ab951c3..8aaacc6 100644 --- a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Entities/EntitiesFactory.cs +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Entities/EntitiesFactory.cs @@ -31,11 +31,11 @@ namespace _Project.Develop.Runtime.Entities _monoEntitiesFactory = container.Resolve(); _playerInput = container.Resolve(); } - + 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(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(32)) .AddContactEntitiesBuffer(new Buffer(32)) - + .AddMaxHealth(new ReactiveVariable(150)) .AddCurrentHealth(new ReactiveVariable(150)) - + .AddTeleportTarget(entity.Transform) .AddTeleportToPoint(entity.Transform) .AddStartTeleportEvent() @@ -221,14 +221,14 @@ namespace _Project.Develop.Runtime.Entities .AddFindTeleportPointEvent() .AddFindTeleportPointRequest() .AddEndTeleportEvent() - + .AddTeleportDamage(new ReactiveVariable(50)) .AddTeleportDamageRadius(new ReactiveVariable(6)) .AddTeleportDamageMask(Layers.CharactersMask) - + .AddTeleportEnergyCost(new ReactiveVariable(20)) .AddTeleportSearchRadius(new ReactiveVariable(6)) - + .AddCurrentEnergy(new ReactiveVariable(60)) .AddMaxEnergy(new ReactiveVariable(60)) .AddUseEnergyEvent() @@ -239,7 +239,7 @@ namespace _Project.Develop.Runtime.Entities .AddIsAutoRegenEnergy(new ReactiveVariable(true)) .AddEnergyAutoRegenCurrentTime() .AddEnergyAutoRegenInitialTime(new ReactiveVariable(3)) - + .AddBodyContactDamage(new ReactiveVariable(50)) .AddTakeDamageRequest() .AddTakeDamageEvent() @@ -247,24 +247,24 @@ namespace _Project.Develop.Runtime.Entities .AddInDeathProcess() .AddDeathProcessInitialTime(new ReactiveVariable(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; } diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI.meta new file mode 100644 index 0000000..140cef0 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d79e4112f0c64a90b05cae78e957a829 +timeCreated: 1772454294 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs new file mode 100644 index 0000000..bd7d6f6 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs @@ -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 _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; + } + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs.meta new file mode 100644 index 0000000..56795cc --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIBrainsContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 647b53823c00413081b7c9221d5bb5c1 +timeCreated: 1772458604 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs new file mode 100644 index 0000000..3de0b8e --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs @@ -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 + { + public AIStateMachine(List disposables) : base(disposables) + { + } + + public AIStateMachine() : base (new List()) + { + } + + protected override void UpdateLogic(float deltaTime) + { + base.UpdateLogic(deltaTime); + + CurrentState?.Update(deltaTime); + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs.meta new file mode 100644 index 0000000..31d45fa --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/AIStateMachine.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1e225964fa5a4120a1e4bf1cc8d47641 +timeCreated: 1772455227 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs new file mode 100644 index 0000000..dbcf065 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs @@ -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(); + _timerServiceFactory = _container.Resolve(); + } + + 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 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; + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs.meta new file mode 100644 index 0000000..740ecf7 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/BrainsFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0b566da239ac47d5b8c828cc1ac54cbe +timeCreated: 1772455908 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs new file mode 100644 index 0000000..07ac15e --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs @@ -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); + + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs.meta new file mode 100644 index 0000000..aa6969c --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/IBrain.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 77beb88ee43645a5b0f85bf7d1518e4a +timeCreated: 1772458164 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs new file mode 100644 index 0000000..3b2aa29 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs.meta new file mode 100644 index 0000000..2e85718 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/StateMachineBrain.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 45f2a68dedcb4a61a4ae6dc8a50227e4 +timeCreated: 1772458266 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States.meta new file mode 100644 index 0000000..f7aca54 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a0534b2062654595bef4d1e59081a533 +timeCreated: 1772454304 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs new file mode 100644 index 0000000..c5a9b89 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs @@ -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) + { + + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs.meta new file mode 100644 index 0000000..cad7e37 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/EmptyState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3f40181e821046968e6c69ecef7e2384 +timeCreated: 1772454318 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs new file mode 100644 index 0000000..ab4f4c9 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs @@ -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 _moveDirection; + private ReactiveVariable _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; + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs.meta b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs.meta new file mode 100644 index 0000000..7bf689e --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/AI/States/RandomMovementState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dd6446a9b2f646bc870251ae870904b3 +timeCreated: 1772454385 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByPercentageSystem.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByPercentageSystem.cs index 7a12592..3bfbe90 100644 --- a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByPercentageSystem.cs +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByPercentageSystem.cs @@ -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 _regenEnergyRequest; private ReactiveEvent _regenEnergyEvent; - + private ReactiveVariable _currentEnergy; private ReactiveVariable _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(); diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByValueSystem.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByValueSystem.cs index a563f6f..2c35d79 100644 --- a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByValueSystem.cs +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Features/Energy/Systems/RegenEnergyByValueSystem.cs @@ -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 _regenEnergyRequest; private ReactiveEvent _regenEnergyEvent; - + private ReactiveVariable _currentEnergy; private ReactiveVariable _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(); diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayBootstrap.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayBootstrap.cs index 8247b1f..dc61bd2 100644 --- a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayBootstrap.cs +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayBootstrap.cs @@ -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(); + _aiBrainsContext = _container.Resolve(); _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); } } diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayContextRegistrations.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayContextRegistrations.cs index a89042e..df3500b 100644 --- a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayContextRegistrations.cs +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/GameplayContextRegistrations.cs @@ -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); diff --git a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/TestGameplay.cs b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/TestGameplay.cs index 2287f57..f354e25 100644 --- a/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/TestGameplay.cs +++ b/Assets/_Project/Develop/Runtime/Logic/Gameplay/Infrastructure/TestGameplay.cs @@ -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().Enable(); _entitiesFactory = _container.Resolve(); + _brainsFactory = _container.Resolve(); } 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}"); } } } \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Event/ReactiveEvent.cs b/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Event/ReactiveEvent.cs index aeb9101..8fd901f 100644 --- a/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Event/ReactiveEvent.cs +++ b/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Event/ReactiveEvent.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event { - public class ReactiveEvent : IReadOnlyEvent + public class ReactiveEvent : IReadOnlyEvent { private readonly List> _subscribers = new(); private readonly List> _toAdd = new(); @@ -41,9 +41,9 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement.Event public class ReactiveEvent : IReadOnlyEvent { - private readonly List _subscribers = new(); - private readonly List _toAdd = new(); - private readonly List _toRemove = new(); + private readonly List _subscribers = new List(); + private readonly List _toAdd = new List(); + private readonly List _toRemove = new List(); public IDisposable Subscribe(Action action) { diff --git a/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Variable/ReactiveVariable.cs b/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Variable/ReactiveVariable.cs index 30cacfa..f57cd91 100644 --- a/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Variable/ReactiveVariable.cs +++ b/Assets/_Project/Develop/Runtime/Utilities/ReactiveManagement/Variable/ReactiveVariable.cs @@ -5,16 +5,16 @@ namespace _Project.Develop.Runtime.Utils.ReactiveManagement { public class ReactiveVariable : IReadOnlyVariable where T : IEquatable { - private readonly List> _subscribers = new (); - private readonly List> _toAddList = new (); - private readonly List> _toRemoveList = new (); - + private readonly List> _subscribers = new List>(); + private readonly List> _toAddList = new List>(); + private readonly List> _toRemoveList = new List>(); + 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 action) - { - Subscriber subscriber = new (action, RemoveSubscriber); - _toAddList.Add(subscriber); - return subscriber; - } - + public IDisposable Subscribe(Action action) + { + Subscriber subscriber = new Subscriber(action, RemoveSubscriber); + _toAddList.Add(subscriber); + + return subscriber; + } + private void RemoveSubscriber(Subscriber 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 subscriber in _toRemoveList) _subscribers.Remove(subscriber); - + _toRemoveList.Clear(); } - + foreach (Subscriber subscriber in _subscribers) subscriber.Invoke(oldValue, newValue); } diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs new file mode 100644 index 0000000..594a59a --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs @@ -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(); + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs.meta new file mode 100644 index 0000000..3ebe53a --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aed062833c374a6c8f4f784616dad179 +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs new file mode 100644 index 0000000..be9be2a --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs @@ -0,0 +1,7 @@ +namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore +{ + public interface IUpdatableState : IState + { + void Update(float deltaTime); + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs.meta new file mode 100644 index 0000000..55143b8 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/IUpdatableState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c1ea582554484972bd37276974ad4434 +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs new file mode 100644 index 0000000..9381da8 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore +{ + public abstract class ParallelState : State where TState : class, IState + { + private List _states; + + public ParallelState(params TState[] states) + { + _states = new List(states); + } + + protected IReadOnlyList 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(); + } + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs.meta new file mode 100644 index 0000000..32b0b13 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/ParallelState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3084d8be941e4eb89f019da66b818702 +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs new file mode 100644 index 0000000..e83e589 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs @@ -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(); + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs.meta new file mode 100644 index 0000000..0772c6a --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/State.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 975fa4387d1e4f37afb584f32071e4bc +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs new file mode 100644 index 0000000..d136db1 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs @@ -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 : State, IDisposable, IUpdatableState where TState : class, IState + { + private List> _states = new(); + + private StateNode _currentState; + + private bool _isRunning; + + private List _disposables; + + protected StateMachine(List disposables) + { + _disposables = new List(disposables); + } + + protected TState CurrentState => _currentState.State; + + public void AddState(TState state) => _states.Add(new StateNode(state)); + + public void AddTransition(TState fromState, TState toState, ICondition condition) + { + StateNode from = _states.First(stateNode => stateNode.State == fromState); + StateNode to = _states.First(stateNode => stateNode.State == toState); + + from.AddTransition(new StateTransition(to, condition)); + } + + public void Update(float deltaTime) + { + if (_isRunning == false) + return; + + foreach (StateTransition 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 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 nextState) + { + _currentState?.State.Exit(); + _currentState = nextState; + _currentState.State.Enter(); + } + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs.meta new file mode 100644 index 0000000..9dbc980 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateMachine.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 421f6f14fa064baaa737bea3684d473f +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs new file mode 100644 index 0000000..b71ef1d --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore +{ + public class StateNode where TState : class, IState + { + private List> _transitions = new(); + + public StateNode(TState state) + { + State = state; + } + + public TState State { get; } + + public IReadOnlyList> Transitions => _transitions; + + public void AddTransition(StateTransition transition) => _transitions.Add(transition); + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs.meta new file mode 100644 index 0000000..64d1f78 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dce697e6ac3b45b497e8e89c5ca258cc +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs new file mode 100644 index 0000000..527dd50 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs @@ -0,0 +1,16 @@ +using _Project.Develop.Runtime.Utilities.Conditions; + +namespace Assets._Project.Develop.Runtime.Utilities.StateMachineCore +{ + public class StateTransition where TState : class, IState + { + public StateTransition(StateNode toState, ICondition condition) + { + ToState = toState; + Condition = condition; + } + + public StateNode ToState { get; } + public ICondition Condition { get; } + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs.meta new file mode 100644 index 0000000..9d08a3b --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/StatesManagement/StateTransition.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 07aef2e29c2643419ff17e32556da944 +timeCreated: 1772453453 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/TimeManagement.meta b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement.meta new file mode 100644 index 0000000..0c0f4cd --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 831af1f46fb24b5c89e15c287f7bdfad +timeCreated: 1772456814 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs new file mode 100644 index 0000000..ca06d26 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs @@ -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 _currentTime; + + private ICoroutinesPerformer _coroutinePerformer; + private Coroutine _cooldownProcess; + + public TimerService( + float cooldown, + ICoroutinesPerformer coroutinePerformer) + { + _cooldown = cooldown; + _coroutinePerformer = coroutinePerformer; + + _cooldownEnded = new ReactiveEvent(); + _currentTime = new ReactiveVariable(); + } + + public IReadOnlyEvent CooldownEnded => _cooldownEnded; + public IReadOnlyVariable 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(); + } + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs.meta new file mode 100644 index 0000000..def458b --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerService.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e5636415f9a840ab8ff88d96719179ec +timeCreated: 1772456820 \ No newline at end of file diff --git a/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs new file mode 100644 index 0000000..8aa4475 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs @@ -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()); + } +} diff --git a/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs.meta b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs.meta new file mode 100644 index 0000000..0a45cd2 --- /dev/null +++ b/Assets/_Project/Develop/Runtime/Utilities/TimeManagement/TimerServiceFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6fbab9cab89b4ad094fc636facb8597a +timeCreated: 1772456820 \ No newline at end of file diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 543c602..f26102e 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -924,7 +924,7 @@ PlayerSettings: embeddedLinuxEnableGamepadInput: 1 hmiLogStartupTiming: 0 hmiCpuConfiguration: - apiCompatibilityLevel: 6 + apiCompatibilityLevel: 3 activeInputHandler: 2 windowsGamepadBackendHint: 0 cloudProjectId: 4a15d730-99ee-4a69-b00c-c0c75aa639bd diff --git a/project-entity.slnx b/project-entity.slnx new file mode 100644 index 0000000..c6c717d --- /dev/null +++ b/project-entity.slnx @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +