Better action system

This commit is contained in:
ThomasCharester committed 2026-10-30 02:58:51 +03:00
1 parent 2bd71f8dc5
commit 71bb4c3b5b
110 files changed
+73523 -86288

No files matched your search

+7 -35
View File
@@ -65,14 +65,8 @@ namespace Code.Game.TestingLevel
.AsUniTask();
var factoryConfig = new SingleConfig<PlayerFactorySettings>();
var playerDecisionMarkerConfig = new SingleConfig<PlayerDecisionMarkerConfig>();
var attackActionsConfigs = new MultipleConfig<BattleActionParameters>();
await UniTask.WhenAll(
factoryConfig.LoadConfig(),
attackActionsConfigs.LoadConfigs(),
playerDecisionMarkerConfig.LoadConfig());
await factoryConfig.LoadConfig();
var factory = new PlayerFactory(factoryConfig);
var player = await factory.CreatePlayer();
@@ -80,36 +74,19 @@ namespace Code.Game.TestingLevel
var playerMovementController = player.GetComponent<MovementController>();
var playerControlsProvider = player.GetComponent<PlayerControlsProvider>();
var playerAnimatorProvider = player.GetComponent<EntityAnimator>();
var playerTimelineActor = player.GetComponent<PlayerControlledTimelineActor>();
var playerProperties = player.GetComponent<EntityProperties>();
playerMovementController.Freeze = true;
BattleActionDecisionPanel battleActionDecisionPanel =
(BattleActionDecisionPanel)await Context.UIFactory
.LoadPanel(UIPanelType.BattleActionDecision, false);
BattleTimelinePanel battleTimelinePanel =
(BattleTimelinePanel)await Context.UIFactory
.LoadPanel(UIPanelType.BattleTimelinePanel, false);
MarkerManager markerManager = new(playerDecisionMarkerConfig);
void InstallExtra(Scene scene, ContainerBuilder builder)
{
builder.RegisterValue(attackActionsConfigs);
builder.RegisterValue(playerDecisionMarkerConfig);
builder.RegisterValue(markerManager);
builder.RegisterValue(factoryConfig);
builder.RegisterValue(factory, new[] { typeof(IPlayerFactory) });
builder.RegisterValue(playerKinematicCharacterMotor);
builder.RegisterValue(playerMovementController);
builder.RegisterValue(playerControlsProvider);
builder.RegisterValue(playerAnimatorProvider);
builder.RegisterValue(playerTimelineActor);
builder.RegisterValue(battleActionDecisionPanel);
builder.RegisterValue(battleTimelinePanel);
builder.RegisterValue(playerProperties);
}
await UniTask.WhenAll(
@@ -119,6 +96,7 @@ namespace Code.Game.TestingLevel
LoadSceneMode.Single,
true,
InstallExtra),
//////
Context.SceneLoader
.LoadSceneAsync(
SceneId.FirstTestingSegment,
@@ -128,7 +106,8 @@ namespace Code.Game.TestingLevel
.LoadSceneAsync(
SceneId.SecondTestingSegment,
LoadSceneMode.Additive,
false),
false),
//////
waitForContainer
);
@@ -138,14 +117,7 @@ namespace Code.Game.TestingLevel
.SetPositionAndRotation(
container.Resolve<TestingLevelData>().playerSpawnPoint.position,
container.Resolve<TestingLevelData>().playerSpawnPoint.rotation);
var entities =
Object.FindObjectsByType<EntityTimelineActor>()
.Select(x => x.gameObject)
.ToList();
GameObjectInjector.InjectRecursiveMany(entities, container);
playerMovementController.Freeze = false;
}
@@ -1,151 +1,147 @@
using System.Linq;
using System.Threading;
using Code.Infrastructure.Input;
using Code.Infrastructure.Utils;
using Code.OfficeGame.BattleMode.Actions.HoloCopy;
using Code.OfficeGame.BattleMode.HitBoxes;
using Cysharp.Threading.Tasks;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
namespace Code.OfficeGame.BattleMode.Actions.Attacks
{
public class BattleActionPlayerAttackMMA : BattleAction
{
[SerializeField] private HoloCop holoCop;
[SerializeField] private Animator animator;
private EntityHitBoxes _entityHitBoxes;
private AttackActionConfig _attackData;
public int EntryStateHash => Animator.StringToHash("WalkBlend");
private int _currentFrame;
private IInputService _inputService;
private CancellationTokenSource _waitForEnterTokenSource;
private BattleActionDecisionResultCode _decisionResultCode;
[Inject]
public void Construct(MultipleConfig<BattleActionParameters> attackData, IInputService inputService)
{
_inputService = inputService;
_entityHitBoxes = GetComponent<EntityHitBoxes>();
_attackData = (AttackActionConfig) attackData.Configs.SingleOrDefault(x=>x.ActionType == ActionType);
}
public override async UniTask<BattleActionDecisionResult> DecideAction()
{
_currentFrame = 0;
Finished = false;
_waitForEnterTokenSource = new CancellationTokenSource();
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmAttack);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelAttack);
holoCop.Simulate(TickHoloCopy, _waitForEnterTokenSource.Token).Forget();
await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmAttack);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelAttack);
_currentFrame = 0;
return new() { ResultCode = _decisionResultCode, FrameDuration = _attackData.TotalFrames};
}
private void CancelAttack(InputAction.CallbackContext obj)
{
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_waitForEnterTokenSource?.Cancel();
}
private void ConfirmAttack(InputAction.CallbackContext obj)
{
_decisionResultCode = BattleActionDecisionResultCode.Decided;
_waitForEnterTokenSource?.Cancel();
}
public override BattleActionType ActionType => BattleActionType.PlayerAttackMMA;
public override void Tick(float tickDelta)
{
if (Finished) return;
if (_currentFrame == 0)
{
// TODO Root Motion не должен быть использован в нормальных анимациях
animator.applyRootMotion = true;
animator.Play(_attackData.StateHash, 0, 0f);
}
_currentFrame++;
if (_currentFrame >= _attackData.HitboxStartFrame && _currentFrame <= _attackData.HitboxEndFrame)
{
_entityHitBoxes.EnableHitbox(_attackData.AttackingBodyPart);
_entityHitBoxes.CheckHitsForCurrentFrame(OnEnemyHit);
}
else
{
_entityHitBoxes.DisableAllHitboxes();
}
if (_currentFrame >= _attackData.TotalFrames)
{
CompleteOrInterruptAction();
}
}
private void OnEnemyHit(Collider enemyCollider)
{
Debug.Log($"Попадание в {enemyCollider.name} на {_currentFrame}-м кадре! Урон: {_attackData.Damage}");
var entityActor = enemyCollider.GetComponentInParent<EntityTimelineActor>();
entityActor.ForceDecideAction(BattleActionType.Stugger);
}
public void CompleteOrInterruptAction()
{
animator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
animator.Update(0f);
_entityHitBoxes.DisableAllHitboxes();
// TODO С нормальными анимациями не нужен будет рут моушен√
animator.applyRootMotion = false;
animator.transform.localPosition = Vector3.zero;
animator.transform.localRotation = Quaternion.identity;
Finished = true;
}
// HoloCopy
private bool TickHoloCopy(HoloCop holoCop, float tickDelta)
{
if (_currentFrame == 0)
{
holoCop.Animator.applyRootMotion = true;
holoCop.Animator.Play(_attackData.StateHash, 0, 0f);
}
_currentFrame++;
if(_currentFrame >= _attackData.TotalFrames)
{
_currentFrame = 0;
holoCop.Animator.applyRootMotion = false;
return true;
}
return false;
}
}
}
// using System.Linq;
// using System.Threading;
// using Code.Infrastructure.Input;
// using Code.Infrastructure.Utils;
// using Code.OfficeGame.BattleMode.Actions.Deciders;
// using Code.OfficeGame.BattleMode.Actions.HoloCopy;
// using Code.OfficeGame.BattleMode.HitBoxes;
// using Cysharp.Threading.Tasks;
// using Reflex.Attributes;
// using UnityEngine;
// using UnityEngine.InputSystem;
//
// namespace Code.OfficeGame.BattleMode.Actions.Attacks
// {
// public class BattleActionPlayerAttackMMA : BattleAction
// {
// [SerializeField] private HoloCop holoCop;
// [SerializeField] private Animator animator;
// private EntityHitBoxes _entityHitBoxes;
// private AttackActionConfig _attackData;
// public int EntryStateHash => Animator.StringToHash("WalkBlend");
//
// private int _currentFrame;
// private IInputService _inputService;
// private CancellationTokenSource _waitForEnterTokenSource;
// private BattleActionDecisionResultCode _decisionResultCode;
//
// [Inject]
// public void Construct(MultipleConfig<BattleActionParameters> attackData, IInputService inputService)
// {
// _inputService = inputService;
//
// _entityHitBoxes = GetComponent<EntityHitBoxes>();
//
// _attackData = (AttackActionConfig) attackData.Configs.SingleOrDefault(x=>x.ActionType == ActionType);
// }
//
// public override async UniTask<BattleActionDecisionResult> DecideAction()
// {
// _currentFrame = 0;
// Finished = false;
//
// _waitForEnterTokenSource = new CancellationTokenSource();
// _decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
//
// _inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
// ConfirmAttack);
// _inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
// CancelAttack);
//
// holoCop.Simulate(TickHoloCopy, _waitForEnterTokenSource.Token).Forget();
//
// await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
//
// _inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
// ConfirmAttack);
// _inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
// CancelAttack);
//
// _currentFrame = 0;
//
// return new() { ResultCode = _decisionResultCode, FrameDuration = _attackData.TotalFrames};
// }
//
// private void CancelAttack(InputAction.CallbackContext obj)
// {
// _decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
// _waitForEnterTokenSource?.Cancel();
// }
//
// private void ConfirmAttack(InputAction.CallbackContext obj)
// {
// _decisionResultCode = BattleActionDecisionResultCode.Decided;
// _waitForEnterTokenSource?.Cancel();
// }
//
// public override BattleActionType ActionType => BattleActionType.PlayerAttackMMA;
//
// public override bool Tick(float tickDelta, EntityProperties entity)
// {
// if (Finished) return;
//
// if (_currentFrame == 0)
// {
// // TODO Root Motion не должен быть использован в нормальных анимациях
// animator.applyRootMotion = true;
// animator.Play(_attackData.StateHash, 0, 0f);
// }
//
// _currentFrame++;
//
// if (_currentFrame >= _attackData.HitboxStartFrame && _currentFrame <= _attackData.HitboxEndFrame)
// {
// _entityHitBoxes.CheckHitsForCurrentFrame(OnEnemyHit, HitBoxPartType.LArm);
// }
//
// if (_currentFrame >= _attackData.TotalFrames)
// {
// CompleteOrInterruptAction();
// }
// }
//
// public override DeciderType DeciderType => DeciderType.Position;
//
// private void OnEnemyHit(Collider enemyCollider)
// {
// Debug.Log($"Попадание в {enemyCollider.name} на {_currentFrame}-м кадре! Урон: {_attackData.Damage}");
// var entityActor = enemyCollider.GetComponentInParent<EntityTimelineActor>();
// entityActor.ForceDecideAction(BattleActionType.Stugger);
// }
//
// public void CompleteOrInterruptAction()
// {
// animator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
// animator.Update(0f);
//
// // TODO С нормальными анимациями не нужен будет рут моушен√
// animator.applyRootMotion = false;
// animator.transform.localPosition = Vector3.zero;
// animator.transform.localRotation = Quaternion.identity;
//
// Finished = true;
// }
//
// // HoloCopy
//
// private bool TickHoloCopy(HoloCop holoCop, float tickDelta)
// {
// if (_currentFrame == 0)
// {
// holoCop.Animator.applyRootMotion = true;
// holoCop.Animator.Play(_attackData.StateHash, 0, 0f);
// }
//
// _currentFrame++;
//
// if(_currentFrame >= _attackData.TotalFrames)
// {
// _currentFrame = 0;
// holoCop.Animator.applyRootMotion = false;
// return true;
// }
//
// return false;
// }
// }
// }
@@ -1,13 +1,13 @@
using Cysharp.Threading.Tasks;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
{
public abstract class BattleAction : MonoBehaviour
public abstract class BattleAction
{
public bool Finished { get; protected set; }
public abstract void Tick(float tickDelta);
public abstract UniTask<BattleActionDecisionResult> DecideAction();
public abstract bool Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult, BattleActionData battleActionData);
public abstract BattleActionType ActionType { get; }
public abstract DeciderType DeciderType { get; }
public abstract BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result);
}
}
@@ -0,0 +1,7 @@
namespace Code.OfficeGame.BattleMode.Actions
{
public abstract class BattleActionData
{
public int TotalFrames;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dec8cd0d83354b05a5766a6fd7f3db95
timeCreated: 1793314110
@@ -1,8 +0,0 @@
namespace Code.OfficeGame.BattleMode.Actions
{
public struct BattleActionDecisionResult
{
public BattleActionDecisionResultCode ResultCode;
public int FrameDuration;
}
}
@@ -2,6 +2,7 @@ using System.Threading;
using Code.Game.Gameplay.Player;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Input;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using Code.OfficeGame.BattleMode.Actions.Player;
using Code.OfficeGame.BattleMode.Marker;
using Code.OfficeGame.BattleMode.Timeline;
@@ -16,79 +17,61 @@ namespace Code.OfficeGame.BattleMode.Actions
{
public class BattleActionMove : BattleAction
{
private CameraStateMachine _cameraStateMachine;
private IInputService _inputService;
private MovementController _movementController;
private NavMeshPath _path;
private Vector3[] _corners;
private int _currentCornerIndex;
private Vector3 _targetPosition;
private LayerMask _navMeshMask;
private const float StoppingDistance = 0.15f;
[Inject]
public void Construct(
CameraStateMachine cameraStateMachine,
IInputService inputService,
MarkerManager markerManager)
public override bool Tick(
float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
_markerManager = markerManager;
_cameraStateMachine = cameraStateMachine;
_inputService = inputService;
_movementController = GetComponent<MovementController>();
_navMeshMask = LayerMask.GetMask("Environment");
}
var movementActionData = (MovementBattleActionData) battleActionData;
var entityMovementController = entity.MovementController;
public override void Tick(float tickDelta)
{
if (Finished) return;
if (_corners == null || _currentCornerIndex >= _corners.Length)
if (movementActionData.Corners == null || movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length)
{
StopMovementAndFinish();
return;
StopMovementAndFinish(entityMovementController);
return true;
}
Vector3 currentPos = _movementController.Motor.TransientPosition;
Vector3 currentPos = entity.KinematicCharacterMotor.TransientPosition;
while (_currentCornerIndex < _corners.Length)
while (movementActionData.CurrentCornerIndex < movementActionData.Corners.Length)
{
Vector3 toCorner = _corners[_currentCornerIndex] - currentPos;
Vector3 toCorner = movementActionData.Corners[movementActionData.CurrentCornerIndex] - currentPos;
toCorner.y = 0f;
if (toCorner.sqrMagnitude <= StoppingDistance * StoppingDistance)
{
_currentCornerIndex++;
}
movementActionData.CurrentCornerIndex++;
else
{
break;
}
}
if (_currentCornerIndex >= _corners.Length)
if (movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length)
{
StopMovementAndFinish();
StopMovementAndFinish(entity.MovementController);
KinematicCharacterSystem.Simulate(
tickDelta,
KinematicCharacterSystem.CharacterMotors,
KinematicCharacterSystem.PhysicsMovers
);
return;
return true;
}
Vector3 targetCorner = _corners[_currentCornerIndex];
Vector3 targetCorner = movementActionData.Corners[movementActionData.CurrentCornerIndex];
Vector3 moveDirection = targetCorner - currentPos;
moveDirection.y = 0f;
float distance = moveDirection.magnitude;
Vector3 moveNormal = distance > 0.0001f ? moveDirection / distance : Vector3.zero;
float maxStep = _movementController.MaxStableMoveSpeed * tickDelta;
float inputMagnitude = (_currentCornerIndex == _corners.Length - 1 && distance < maxStep && maxStep > 0f)
float maxStep = entityMovementController.MaxStableMoveSpeed * tickDelta;
float inputMagnitude =
(movementActionData.CurrentCornerIndex == movementActionData.Corners.Length - 1
&& distance < maxStep
&& maxStep > 0f)
? distance / maxStep
: 1f;
@@ -97,11 +80,11 @@ namespace Code.OfficeGame.BattleMode.Actions
{
MoveVector = moveNormal * inputMagnitude,
Look = moveNormal.sqrMagnitude > 0.0001f
? Quaternion.LookRotation(moveNormal, _movementController.Motor.CharacterUp)
: _movementController.Motor.TransientRotation
? Quaternion.LookRotation(moveNormal, entityMovementController.Motor.CharacterUp)
: entityMovementController.Motor.TransientRotation
};
_movementController.SetInputs(ref aiInputs);
entityMovementController.SetInputs(ref aiInputs);
KinematicCharacterSystem.Simulate(
tickDelta,
@@ -109,110 +92,58 @@ namespace Code.OfficeGame.BattleMode.Actions
KinematicCharacterSystem.PhysicsMovers
);
Vector3 postMovePos = _movementController.Motor.TransientPosition;
Vector3 toFinal = _corners[^1] - postMovePos;
Vector3 postMovePos = entityMovementController.Motor.TransientPosition;
Vector3 toFinal = movementActionData.Corners[^1] - postMovePos;
toFinal.y = 0f;
if (_currentCornerIndex >= _corners.Length - 1 && toFinal.sqrMagnitude <= StoppingDistance * StoppingDistance)
if (movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length - 1
&& toFinal.sqrMagnitude <= StoppingDistance * StoppingDistance)
{
StopMovementAndFinish();
StopMovementAndFinish(entityMovementController);
return true;
}
return false;
}
private void StopMovementAndFinish()
private void StopMovementAndFinish(MovementController movementController)
{
Debug.Log("MovementFinished");
var emptyInputs = new AICharacterInputs
{
MoveVector = Vector3.zero,
Look = _movementController.Motor.TransientRotation
Look = movementController.Motor.TransientRotation
};
_movementController.SetInputs(ref emptyInputs);
_movementController.Motor.BaseVelocity = Vector3.zero;
Finished = true;
}
private CancellationTokenSource _waitForEnterTokenSource;
private BattleActionDecisionResultCode _decisionResultCode;
public override async UniTask<BattleActionDecisionResult> DecideAction()
{
_waitForEnterTokenSource = new CancellationTokenSource();
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_targetPosition = _movementController.transform.position;
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
MarkPosition);
_playerLockedDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker.Toggle(true);
await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
_playerDecisionMarker.Toggle(false);
_markerManager.ReleaseMarkerInstance(_playerDecisionMarker);
_markerManager.ReleaseMarkerInstance(_playerLockedDecisionMarker);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
MarkPosition);
_waitForEnterTokenSource.Dispose();
_waitForEnterTokenSource = null;
Finished = _decisionResultCode != BattleActionDecisionResultCode.Decided;
return new() { ResultCode = _decisionResultCode, FrameDuration = _totalFrames};
movementController.SetInputs(ref emptyInputs);
movementController.Motor.BaseVelocity = Vector3.zero;
}
public override BattleActionType ActionType => BattleActionType.Move;
public override DeciderType DeciderType => DeciderType.Position;
private int _totalFrames;
private MarkerManager _markerManager;
private void CancelPosition(InputAction.CallbackContext obj)
public override BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result)
{
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_waitForEnterTokenSource?.Cancel();
}
private void ConfirmPosition(InputAction.CallbackContext input)
{
Vector3 startPos = _movementController.Motor.TransientPosition;
_path = new();
MovementBattleActionData movementBattleActionData = new();
if ((_targetPosition - startPos).sqrMagnitude > StoppingDistance * StoppingDistance
&& NavMesh.CalculatePath(startPos, _targetPosition, NavMesh.AllAreas, _path)
&& _path.corners.Length > 0)
{
_corners = _path.corners;
_currentCornerIndex = _corners.Length > 1 ? 1 : 0;
_decisionResultCode = BattleActionDecisionResultCode.Decided;
_totalFrames = CalculatePathFrames(_corners);
}
else
{
_decisionResultCode = BattleActionDecisionResultCode.Deciding;
_totalFrames = 0;
}
Vector3 startPos = entity.KinematicCharacterMotor.TransientPosition;
_waitForEnterTokenSource?.Cancel();
NavMeshPath path = new();
var positionDecisionResult = (PositionBattleActionDeciderResult) result;
if ((positionDecisionResult.Position - startPos).sqrMagnitude > StoppingDistance * StoppingDistance
&& NavMesh.CalculatePath(startPos, positionDecisionResult.Position, NavMesh.AllAreas, path)
&& path.corners.Length > 0)
{
movementBattleActionData.Corners = path.corners;
movementBattleActionData.CurrentCornerIndex = movementBattleActionData.Corners.Length > 1 ? 1 : 0;
movementBattleActionData.TotalFrames = CalculatePathFrames(movementBattleActionData.Corners, entity.MovementController);
}
else movementBattleActionData.TotalFrames = 0;
return movementBattleActionData;
}
private int CalculatePathFrames(Vector3[] corners)
private int CalculatePathFrames(Vector3[] corners, MovementController movementController)
{
if (corners == null || corners.Length < 2)
return 0;
@@ -228,42 +159,12 @@ namespace Code.OfficeGame.BattleMode.Actions
float effectiveDistance = Mathf.Max(0f, totalDistanceXZ - StoppingDistance);
float stepPerFrame = _movementController.MaxStableMoveSpeed * BattleTimeline.TickDelta;
float stepPerFrame = movementController.MaxStableMoveSpeed * BattleTimeline.TickDelta;
if (stepPerFrame <= 0.0001f)
return 0;
// Округляем вверх, так как последний неполный шаг тоже занимает 1 целый кадр
return Mathf.CeilToInt(effectiveDistance / stepPerFrame);
}
private RaycastHit[] _hitBuffer = new RaycastHit[1];
private Vector3 _lastViewedPosition;
private PlayerDecisionMarker _playerDecisionMarker;
private PlayerDecisionMarker _playerLockedDecisionMarker;
private void MarkPosition(InputAction.CallbackContext input)
{
var position = _cameraStateMachine.Camera.transform.position;
var direction = _cameraStateMachine.Camera.transform.forward;
var hitCount = Physics.RaycastNonAlloc(position, direction, _hitBuffer, Mathf.Infinity, _navMeshMask);
if (hitCount == 0) return;
var hit = _hitBuffer[0];
_playerDecisionMarker.MoveMarker(hit.point);
_lastViewedPosition = hit.point;
}
private void SelectPosition(InputAction.CallbackContext input)
{
_targetPosition = _lastViewedPosition;
_playerLockedDecisionMarker.MoveMarker(_targetPosition);
_playerLockedDecisionMarker.Toggle(true);
}
}
}
}
@@ -1,64 +1,64 @@
using System.Linq;
using Code.Infrastructure.Utils;
using Code.OfficeGame.BattleMode.HitBoxes;
using Cysharp.Threading.Tasks;
using Reflex.Attributes;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
{
public class BattleActionStugger : BattleAction
{
private Animator _animator;
private EntityHitBoxes _entityHitBoxes;
private int EntryStateHash => Animator.StringToHash("WalkBlend");
private int StuggerStateHash => Animator.StringToHash("Stugger");
private BattleActionParameters _actionParameters;
private int _currentFrame;
[Inject]
public void Construct(MultipleConfig<BattleActionParameters> actionData)
{
_animator = GetComponentInChildren<Animator>();
_entityHitBoxes = GetComponent<EntityHitBoxes>();
_actionParameters = actionData.Configs.SingleOrDefault(x => x.ActionType == ActionType);
}
public override async UniTask<BattleActionDecisionResult> DecideAction()
{
await UniTask.Yield();
_currentFrame = 0;
Finished = false;
return new() { ResultCode = BattleActionDecisionResultCode.Decided, FrameDuration = _actionParameters.TotalFrames};
}
public override BattleActionType ActionType => BattleActionType.Stugger;
public override void Tick(float tickDelta)
{
if (Finished) return;
if (_currentFrame == 0)
{
_animator.Play(StuggerStateHash, 0, 0f);
}
_currentFrame++;
if (_currentFrame >= _actionParameters.TotalFrames)
{
CompleteOrInterruptAction();
}
}
public void CompleteOrInterruptAction()
{
_animator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
Finished = true;
}
}
}
// using System.Linq;
// using Code.Infrastructure.Utils;
// using Code.OfficeGame.BattleMode.HitBoxes;
// using Cysharp.Threading.Tasks;
// using Reflex.Attributes;
// using UnityEngine;
//
// namespace Code.OfficeGame.BattleMode.Actions
// {
// public class BattleActionStugger : BattleAction
// {
// private Animator _animator;
// private EntityHitBoxes _entityHitBoxes;
// private int EntryStateHash => Animator.StringToHash("WalkBlend");
// private int StuggerStateHash => Animator.StringToHash("Stugger");
//
// private BattleActionParameters _actionParameters;
//
// private int _currentFrame;
//
// [Inject]
// public void Construct(MultipleConfig<BattleActionParameters> actionData)
// {
// _animator = GetComponentInChildren<Animator>();
// _entityHitBoxes = GetComponent<EntityHitBoxes>();
// _actionParameters = actionData.Configs.SingleOrDefault(x => x.ActionType == ActionType);
// }
// public override async UniTask<BattleActionDecisionResult> DecideAction()
// {
// await UniTask.Yield();
//
// _currentFrame = 0;
// Finished = false;
//
// return new() { ResultCode = BattleActionDecisionResultCode.Decided, FrameDuration = _actionParameters.TotalFrames};
//
// }
//
// public override BattleActionType ActionType => BattleActionType.Stugger;
//
// public override bool Tick(float tickDelta, EntityProperties entity)
// {
// if (Finished) return;
//
// if (_currentFrame == 0)
// {
// _animator.Play(StuggerStateHash, 0, 0f);
// }
//
// _currentFrame++;
//
// if (_currentFrame >= _actionParameters.TotalFrames)
// {
// CompleteOrInterruptAction();
// }
// }
//
// public void CompleteOrInterruptAction()
// {
// _animator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
// Finished = true;
// }
// }
// }
@@ -1,31 +1,32 @@
using Cysharp.Threading.Tasks;
using Code.OfficeGame.BattleMode.Actions.Deciders;
namespace Code.OfficeGame.BattleMode.Actions
{
public class BattleActionWait : BattleAction
{
private int _waitFrames;
public override void Tick(float tickDelta)
public override bool Tick(float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
_waitFrames--;
if (_waitFrames <= 0) Finished = true;
}
public override async UniTask<BattleActionDecisionResult> DecideAction()
{
Finished = false;
var waitActionData = (WaitBattleActionData) battleActionData;
await UniTask.Yield();
_waitFrames = 120;
waitActionData.WaitFrames--;
if (waitActionData.WaitFrames <= 0) return true;
return new()
{
ResultCode = BattleActionDecisionResultCode.Decided,
FrameDuration = _waitFrames
};
return false;
}
public override BattleActionType ActionType => BattleActionType.Wait;
public override DeciderType DeciderType => DeciderType.AsIs;
public override BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result)
{
WaitBattleActionData actionData = new()
{
WaitFrames = 120
};
return actionData;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a5f1323a1c04e9ead8fa528427c62e0
timeCreated: 1793278477
@@ -0,0 +1,24 @@
using Cysharp.Threading.Tasks;
namespace Code.OfficeGame.BattleMode.Actions.Deciders
{
public class AsIsBattleActionDecider : BattleActionDecider
{
public override DeciderType DeciderType => DeciderType.AsIs;
public override async UniTask<BattleActionDecisionResult> GetDecision()
{
await UniTask.Yield();
return new AsIsBattleActionDeciderResult()
{
ResultCode = BattleActionDecisionResultCode.Decided
};
}
public override void UnmountDecider()
{ }
public override void MountDecider()
{ }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2034bb40c0ea462baeac67d89aef0429
timeCreated: 1793316764
@@ -0,0 +1,15 @@
using Cysharp.Threading.Tasks;
namespace Code.OfficeGame.BattleMode.Actions.Deciders
{
public abstract class BattleActionDecider
{
public abstract DeciderType DeciderType { get; }
protected bool IsMounted;
public abstract UniTask<BattleActionDecisionResult> GetDecision();
public abstract void UnmountDecider();
public abstract void MountDecider();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b6762673a7f4e40979aa0bd6532f836
timeCreated: 1793278644
@@ -0,0 +1,22 @@
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
{
public abstract class BattleActionDecisionResult
{
public BattleActionType ActionType;
public BattleActionDecisionResultCode ResultCode;
}
public class PositionBattleActionDeciderResult : BattleActionDecisionResult
{
public Vector3 Position;
public Quaternion Rotation;
}
public class FrameBattleActionDeciderResult : BattleActionDecisionResult
{
public int FrameCount;
}
public class AsIsBattleActionDeciderResult : BattleActionDecisionResult
{
}
}
@@ -0,0 +1,142 @@
using System;
using System.Threading;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Input;
using Code.OfficeGame.BattleMode.Actions.Player;
using Code.OfficeGame.BattleMode.Marker;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;
namespace Code.OfficeGame.BattleMode.Actions.Deciders
{
public class BattleActionPositionDecider : BattleActionDecider
{
private CameraStateMachine _cameraStateMachine;
private IInputService _inputService;
private Vector3 _targetPosition;
private LayerMask _navMeshMask;
private const float StoppingDistance = 0.15f;
public BattleActionPositionDecider(
CameraStateMachine cameraStateMachine,
IInputService inputService,
MarkerManager markerManager)
{
_markerManager = markerManager;
_cameraStateMachine = cameraStateMachine;
_inputService = inputService;
_navMeshMask = LayerMask.GetMask("Environment");
}
private CancellationTokenSource _waitForEnterTokenSource;
private BattleActionDecisionResultCode _decisionResultCode;
public override DeciderType DeciderType => DeciderType.Position;
public override async UniTask<BattleActionDecisionResult> GetDecision()
{
if (!IsMounted) throw new Exception("Decider is not mounted"); // А почему бы и нет?
_waitForEnterTokenSource = new CancellationTokenSource();
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
_waitForEnterTokenSource.Dispose();
_waitForEnterTokenSource = null;
return new PositionBattleActionDeciderResult()
{
ResultCode = _decisionResultCode,
Position = _targetPosition,
};
}
public override void UnmountDecider()
{
if (!IsMounted) return;
_playerDecisionMarker.Toggle(false);
_markerManager.ReleaseMarkerInstance(_playerDecisionMarker);
_markerManager.ReleaseMarkerInstance(_playerLockedDecisionMarker);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
MarkPosition);
IsMounted = false;
}
public override void MountDecider()
{
if (IsMounted) return;
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
MarkPosition);
_playerLockedDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker.Toggle(true);
IsMounted = true;
}
private MarkerManager _markerManager;
private void CancelPosition(InputAction.CallbackContext obj)
{
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_waitForEnterTokenSource?.Cancel();
}
private void ConfirmPosition(InputAction.CallbackContext input)
{
_decisionResultCode = BattleActionDecisionResultCode.Decided;
_waitForEnterTokenSource?.Cancel();
}
private RaycastHit[] _hitBuffer = new RaycastHit[1];
private Vector3 _lastViewedPosition;
private PlayerDecisionMarker _playerDecisionMarker;
private PlayerDecisionMarker _playerLockedDecisionMarker;
private void MarkPosition(InputAction.CallbackContext input)
{
var position = _cameraStateMachine.Camera.transform.position;
var direction = _cameraStateMachine.Camera.transform.forward;
var hitCount = Physics.RaycastNonAlloc(position, direction, _hitBuffer, Mathf.Infinity, _navMeshMask);
if (hitCount == 0) return;
var hit = _hitBuffer[0];
_playerDecisionMarker.MoveMarker(hit.point);
_lastViewedPosition = hit.point;
}
private void SelectPosition(InputAction.CallbackContext input)
{
_targetPosition = _lastViewedPosition;
_playerLockedDecisionMarker.MoveMarker(_targetPosition);
_playerLockedDecisionMarker.Toggle(true);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 220462a568ce470482b264d105953329
timeCreated: 1793278626
@@ -0,0 +1,8 @@
namespace Code.OfficeGame.BattleMode.Actions.Deciders
{
public enum DeciderType
{
AsIs,
Position
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 534386b84312450f997b33343b402f74
timeCreated: 1793278491
@@ -0,0 +1,15 @@
using Code.Game.Gameplay.Player;
using KinematicCharacterController;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
{
public class EntityProperties : MonoBehaviour
{
public Animator animator;
public BattleActionType[] battleActionTypes;
public MovementController MovementController;
public KinematicCharacterMotor KinematicCharacterMotor;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8e4a996f851e47ca8fc906e7e89fdca5
timeCreated: 1793291498
@@ -1,51 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using Code.OfficeGame.BattleMode.Timeline;
using Cysharp.Threading.Tasks;
using R3;
using Unity.VisualScripting;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
{
public abstract class EntityTimelineActor : MonoBehaviour
{
public BattleAction CurrentAction { get; protected set;}
public bool Finished => CurrentAction == null || CurrentAction.Finished;
private readonly Subject<TimelineBattleAction> _onActionSet = new();
public Observable<TimelineBattleAction> OnActionSet => _onActionSet;
protected void SetCurrentAction(BattleAction action, int durationInFrames)
{
CurrentAction = action;
_onActionSet.OnNext(new TimelineBattleAction
{
EntityId = gameObject.GetEntityId(),
Frame = durationInFrames
});
}
private void OnDestroy()
{
_onActionSet.Dispose();
}
public abstract UniTask DecideAction(CancellationToken ctx);
public abstract UniTaskVoid ForceDecideAction(BattleActionType actionType);
[DoNotSerialize] public Animator animator;
protected readonly Dictionary<BattleActionType, BattleAction> BattleActions = new();
private void Awake()
{
animator = GetComponentInChildren<Animator>();
foreach (var action in GetComponents<BattleAction>())
{
BattleActions.Add(action.ActionType, action);
}
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 17efe885cebe451d9cc5db4706f13ffe
timeCreated: 1792477417
@@ -0,0 +1,14 @@
using UnityEngine;
using UnityEngine.AI;
namespace Code.OfficeGame.BattleMode.Actions
{
public class MovementBattleActionData : BattleActionData
{
public Vector3 Position;
public Quaternion Rotation;
public NavMeshPath Path;
public Vector3[] Corners;
public int CurrentCornerIndex;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 39d3651a2dc5444ca0801b647b60d47e
timeCreated: 1793314117
@@ -1,62 +0,0 @@
using System.Threading;
using Code.Game.OfficeGame.BattleMode;
using Code.Infrastructure.Camera;
using Code.OfficeGame.BattleMode.UI;
using Cysharp.Threading.Tasks;
using Reflex.Attributes;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions.Player
{
public class PlayerControlledTimelineActor : EntityTimelineActor
{
[Inject] private BattleActionDecisionPanel _battleActionDecisionPanel;
[Inject] private CameraStateMachine _cameraStateMachine;
public override async UniTask DecideAction(CancellationToken ctx)
{
_battleActionDecisionPanel.SetEntityInfo(BattleActions);
BattleActionType decidedAction = BattleActionType.None;
BattleActionDecisionResult decision = new();
while (decidedAction == BattleActionType.None || ctx.IsCancellationRequested)
{
Cursor.lockState = CursorLockMode.Confined;
((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = true;
await _battleActionDecisionPanel.Open(ctx);
decidedAction = await _battleActionDecisionPanel.GetDecision(ctx);
while (decidedAction != BattleActionType.None
&& !ctx.IsCancellationRequested)
{
Cursor.lockState = CursorLockMode.Locked;
((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = false;
await _battleActionDecisionPanel.Close(ctx);
decision = await BattleActions[decidedAction].DecideAction();
if (decision.ResultCode == BattleActionDecisionResultCode.CancelDecision)
decidedAction = BattleActionType.None;
if (decision.ResultCode != BattleActionDecisionResultCode.Deciding)
break;
}
}
SetCurrentAction(BattleActions[decidedAction], decision.FrameDuration);
}
public override async UniTaskVoid ForceDecideAction(BattleActionType actionType)
{
var decision = await CurrentAction.DecideAction();
SetCurrentAction(BattleActions[actionType], decision.FrameDuration);
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f9f637cef8714ef1b5ec779d21630ebd
timeCreated: 1792693336
@@ -0,0 +1,7 @@
namespace Code.OfficeGame.BattleMode.Actions
{
public class WaitBattleActionData : BattleActionData
{
public int WaitFrames;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1650fc3ffbfd48b0b2bd9a1b31988d8f
timeCreated: 1793314128
+103 -23
View File
@@ -7,17 +7,26 @@ using Code.Infrastructure.Camera;
using Code.Infrastructure.Generators;
using Code.Infrastructure.Input;
using Code.Infrastructure.SceneLoader;
using Code.Infrastructure.UI;
using Code.Infrastructure.Utils;
using Code.OfficeGame.BattleMode.Actions;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using Code.OfficeGame.BattleMode.Actions.Player;
using Code.OfficeGame.BattleMode.Marker;
using Code.OfficeGame.BattleMode.Timeline;
using Code.OfficeGame.BattleMode.UI;
using Code.OfficeGame.Player;
using Cysharp.Threading.Tasks;
using KinematicCharacterController;
using ObservableCollections;
using R3;
using Reflex.Core;
using Reflex.Enums;
using Reflex.Injectors;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using Resolution = Reflex.Enums.Resolution;
namespace Code.Game.OfficeGame.GameStates
{
@@ -32,7 +41,7 @@ namespace Code.Game.OfficeGame.GameStates
private BattleTimeline _battleTimeline;
private CancellationTokenSource _battleTokenSource = new CancellationTokenSource();
public override async UniTask EnterState()
{
#if UNITY_EDITOR
@@ -44,23 +53,88 @@ namespace Code.Game.OfficeGame.GameStates
Context.InputService.EnableMap(ActionMappings.BattleMode);
var container = Context.SceneContainerManager.ScenesContainers[SceneId.Testing];
// T
var entities =
Object.FindObjectsByType<EntityTimelineActor>().ToList();
////// T
var entities =
Object.FindObjectsByType<EntityProperties>()
.ToList();
Container container;
if (Context.StateMachine.CurrentState == AppStates.Testing)
{
var gameplayContainer = Context.SceneContainerManager.ScenesContainers[SceneId.Testing];
var waitForContainer =
Context.SceneContainerManager.ScenesContainers
.ObserveDictionaryAdd()
.Where(x => x.Key == SceneId.Battle)
.Take(1)
.WaitAsync()
.AsUniTask();
var playerDecisionMarkerConfig = new SingleConfig<PlayerDecisionMarkerConfig>();
var attackActionsConfigs = new MultipleConfig<BattleActionParameters>();
await UniTask.WhenAll(
attackActionsConfigs.LoadConfigs(),
playerDecisionMarkerConfig.LoadConfig());
BattleActionDecisionPanel battleActionDecisionPanel =
(BattleActionDecisionPanel)await Context.UIFactory
.LoadPanel(UIPanelType.BattleActionDecision, false);
BattleTimelinePanel battleTimelinePanel =
(BattleTimelinePanel)await Context.UIFactory
.LoadPanel(UIPanelType.BattleTimelinePanel, false);
void InstallExtra(Scene scene, ContainerBuilder builder)
{
builder.SetParent(gameplayContainer);
builder.RegisterValue(attackActionsConfigs);
builder.RegisterValue(playerDecisionMarkerConfig);
builder.RegisterType(typeof(MarkerManager), Lifetime.Singleton, Resolution.Eager);
builder.RegisterValue(battleActionDecisionPanel);
builder.RegisterValue(battleTimelinePanel);
builder.RegisterType(typeof(BattleActionMove), new[]{ typeof(BattleAction) }, Lifetime.Singleton, Resolution.Eager);
builder.RegisterType(typeof(BattleActionWait), new[]{ typeof(BattleAction) }, Lifetime.Singleton, Resolution.Eager);
builder.RegisterType(typeof(BattleActionPositionDecider), new[]{ typeof(BattleActionDecider) } , Lifetime.Singleton, Resolution.Eager);
builder.RegisterType(typeof(AsIsBattleActionDecider), new[]{ typeof(BattleActionDecider) } , Lifetime.Singleton, Resolution.Eager);
builder.RegisterType(typeof(BattleTimeline), Lifetime.Singleton, Resolution.Eager);
}
await UniTask.WhenAll(
Context.SceneLoader
.LoadSceneAsync(
SceneId.Battle,
LoadSceneMode.Additive,
true,
InstallExtra),
waitForContainer
);
container = Context.SceneContainerManager.ScenesContainers[SceneId.Battle];
///// T
GameObjectInjector
.InjectRecursiveMany(
entities.Select(x => x.gameObject).ToList(), container);
/////
_battleTokenSource = new();
_battleTimeline = ConstructorInjector.Construct(typeof(BattleTimeline), container) as BattleTimeline;
_battleTimeline = container.Resolve<BattleTimeline>();
Physics.simulationMode = SimulationMode.Script;
KinematicCharacterSystem.EnsureCreation();
KinematicCharacterSystem.Settings.AutoSimulation = false;
KinematicCharacterSystem.Settings.Interpolate = false;
TogglePlayerBattleMode(container, false);
await Context.Camera.SwitchState(CameraMode.BattleMode);
@@ -70,9 +144,13 @@ namespace Code.Game.OfficeGame.GameStates
entity.animator.enabled = false;
}
}
else
{
container = Context.SceneContainerManager.ScenesContainers[SceneId.Battle];
}
_battleTimeline?.RunBattleLoopAsync(entities, _battleTokenSource.Token).Forget();
_battleTimeline?.RunBattleLoopAsync(entities,_battleTokenSource.Token).Forget();
await container.Resolve<BattleTimelinePanel>().Open();
}
@@ -81,7 +159,7 @@ namespace Code.Game.OfficeGame.GameStates
container.Resolve<EntityAnimator>().Animator.enabled = enabled;
container.Resolve<PlayerControlsProvider>().enabled = enabled;
}
private void ToggleBattleMode(InputAction.CallbackContext obj)
{
Context.StateMachine.SwitchState(AppStates.Testing).Forget();
@@ -90,36 +168,38 @@ namespace Code.Game.OfficeGame.GameStates
public override async UniTask ExitState()
{
if (Context.StateMachine.NextState == AppStates.Testing)
{
{
_battleTokenSource.Cancel();
_battleTokenSource.Dispose();
Physics.simulationMode = SimulationMode.FixedUpdate;
KinematicCharacterSystem.Settings.AutoSimulation = true;
KinematicCharacterSystem.Settings.Interpolate = true;
var container = Context.SceneContainerManager.ScenesContainers[SceneId.Testing];
var container = Context.SceneContainerManager.ScenesContainers[SceneId.Battle];
TogglePlayerBattleMode(container, true);
await container.Resolve<BattleTimelinePanel>().Close();
// T
var entities =
Object.FindObjectsByType<EntityTimelineActor>().ToList();
var entities =
Object.FindObjectsByType<EntityProperties>().ToList();
foreach (var entity in entities)
{
entity.animator.enabled = false;
}
await Context.SceneLoader.UnloadSceneAsync(SceneId.Battle);
}
#if UNITY_EDITOR
Context.InputService.DisableMap(ActionMappings.Debug);
Context.InputService.UnbindActionPerformed((int)ActionMappings.Debug, (int)DebugActions.ToggleBattleMode,
ToggleBattleMode);
#endif
Context.InputService.DisableMap(ActionMappings.BattleMode);
}
}
@@ -0,0 +1,39 @@
using System;
using Code.Infrastructure.SceneLoader;
using Cysharp.Threading.Tasks;
using R3;
using Reflex.Core;
using UnityEngine;
namespace Code.OfficeGame.BattleMode
{
public class BattleStateSceneInstaller: MonoBehaviour, IInstaller, IDisposable
{
private IDisposable _onContainerBuildDisposable;
public void InstallBindings(ContainerBuilder containerBuilder)
{
_onContainerBuildDisposable = Observable
.FromEvent<Container>(
h => containerBuilder.OnContainerBuilt += h,
h => containerBuilder.OnContainerBuilt -= h)
.Subscribe(x => OnContainerBuilt(x).Forget());
}
private ISceneContainerManager _sceneContainerManager;
private async UniTaskVoid OnContainerBuilt(Container container)
{
await UniTask.Yield();
_onContainerBuildDisposable.Dispose();
_sceneContainerManager = container.Resolve<ISceneContainerManager>();
_sceneContainerManager.ScenesContainers.Add(SceneId.Battle, container);
}
public void Dispose()
{
_sceneContainerManager.ScenesContainers.Remove(SceneId.Battle);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 76d99016e7074ce2bf981a533ff1d1c2
timeCreated: 1793316405
@@ -7,63 +7,24 @@ namespace Code.OfficeGame.BattleMode.HitBoxes
// TODO Добавить меотды отключения,чтобы не получать урон во время стаггера
public class EntityHitBoxes : MonoBehaviour
{
[Serializable]
public struct HitBoxPartCollider
{
public HitBoxPartType PartType;
public BoxCollider Collider;
}
[SerializeField] private HitBoxPartCollider[] bodyParts;
[SerializeField] private Dictionary<HitBoxPartType, BoxCollider> bodyParts;
private LayerMask _hitBoxLayerMask;
private readonly Dictionary<HitBoxPartType, BoxCollider> _colliderMap = new();
private readonly Collider[] _hitResults = new Collider[8];
// Список уже поражённых целей за один взмах, чтобы один удар не наносил урон 8 раз за 8 активных кадров
private readonly HashSet<GameObject> _alreadyHitTargets = new();
private HitBoxPartType? _activePart;
private void Awake()
{
foreach (var part in bodyParts)
foreach (var part in bodyParts.Values)
{
part.Collider.enabled = false;
_colliderMap[part.PartType] = part.Collider;
part.enabled = false;
}
_hitBoxLayerMask = LayerMask.GetMask("HitBox");
}
public void EnableHitbox(HitBoxPartType part)
public void CheckHitsForCurrentFrame(Action<Collider> onNewTargetHit, HitBoxPartType hitBoxPartType)
{
if (_activePart == part) return;
_alreadyHitTargets.Clear();
_activePart = part;
if (_colliderMap.TryGetValue(part, out var col))
{
col.enabled = true;
}
}
public void DisableAllHitboxes()
{
if (_activePart.HasValue && _colliderMap.TryGetValue(_activePart.Value, out var col))
{
col.enabled = false;
}
_activePart = null;
_alreadyHitTargets.Clear();
}
public void CheckHitsForCurrentFrame(Action<Collider> onNewTargetHit)
{
if (!_activePart.HasValue) return;
BoxCollider box = _colliderMap[_activePart.Value];
BoxCollider box = bodyParts[hitBoxPartType];
Transform boxTransform = box.transform;
// Вычисляем точный мировой центр и размер коллайдера на кости в текущем кадре анимации
@@ -98,7 +59,17 @@ namespace Code.OfficeGame.BattleMode.HitBoxes
{
Head,
Chest,
Arm,
Leg
LArm,
LElbow,
LWrist,
LLeg,
LKnee,
LAnkle,
RArm,
RElbow,
RWrist,
RLeg,
RKnee,
RAnkle
}
}
@@ -1,6 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Code.Game.OfficeGame.BattleMode;
using Code.Infrastructure.Camera;
using Code.OfficeGame.BattleMode.Actions;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using Code.OfficeGame.BattleMode.UI;
using Cysharp.Threading.Tasks;
using R3;
@@ -17,14 +22,31 @@ namespace Code.OfficeGame.BattleMode.Timeline
public ReadOnlyReactiveProperty<int> CurrentBattleFrame => _currentBattleFrame;
private readonly BattleTimelinePanel _timelinePanel;
private List<EntityTimelineActor> _entities;
private List<EntityProperties> _entities;
private DisposableBag _battleSubscriptions;
private Dictionary<BattleActionType, BattleAction> _battleActions;
private Dictionary<DeciderType, BattleActionDecider> _battleActionDeciders;
public BattleTimeline(BattleTimelinePanel timelinePanel)
private Dictionary<EntityId, BattleActionDecisionResult> _actionDecisionResults = new();
private Dictionary<EntityId, BattleActionData> _battleActionDatas = new();
private BattleActionDecisionPanel _battleActionDecisionPanel;
private CameraStateMachine _cameraStateMachine;
public BattleTimeline(
BattleTimelinePanel timelinePanel,
IEnumerable<BattleAction> battleActions,
IEnumerable<BattleActionDecider> battleActionDeciders,
BattleActionDecisionPanel battleActionDecisionPanel,
CameraStateMachine cameraStateMachine)
{
_cameraStateMachine = cameraStateMachine;
_battleActionDecisionPanel = battleActionDecisionPanel;
_timelinePanel = timelinePanel;
_battleActions = battleActions.ToDictionary(x=>x.ActionType);
_battleActionDeciders = battleActionDeciders.ToDictionary(x => x.DeciderType);
}
public async UniTask RunBattleLoopAsync(List<EntityTimelineActor> entities, CancellationToken ctx)
public async UniTask RunBattleLoopAsync(List<EntityProperties> entities, CancellationToken ctx)
{
_entities = entities;
_currentBattleFrame.Value = 0;
@@ -37,20 +59,20 @@ namespace Code.OfficeGame.BattleMode.Timeline
.Subscribe(_timelinePanel.OnFrameTick)
.AddTo(ref _battleSubscriptions);
foreach (var entity in entities)
entity.OnActionSet
.Subscribe(action =>
{
action.Frame += _currentBattleFrame.Value;
_timelinePanel.SetAction(action);
})
.AddTo(ref _battleSubscriptions);
// foreach (var entity in entities)
// entity.OnActionSet
// .Subscribe(action =>
// {
// action.Frame += _currentBattleFrame.Value;
// _timelinePanel.SetAction(action);
// })
// .AddTo(ref _battleSubscriptions);
await DecideActions(_entities, ctx);
while (!IsBattleOver() && !ctx.IsCancellationRequested)
while (!ctx.IsCancellationRequested)
{
List<EntityTimelineActor> entitiesNeedOrders = await PlayTimelineUntilPause(ctx);
List<EntityProperties> entitiesNeedOrders = await PlayTimelineUntilPause(ctx);
await DecideActions(entitiesNeedOrders, ctx);
}
@@ -61,19 +83,32 @@ namespace Code.OfficeGame.BattleMode.Timeline
}
}
private async UniTask DecideActions(List<EntityTimelineActor> entityActionMakers, CancellationToken ct)
private async UniTask DecideActions(List<EntityProperties> entities, CancellationToken ctx)
{
foreach (var entity in entityActionMakers)
foreach (var entity in entities)
{
Debug.Log($"Deciding action for {entity.name}");
await entity.DecideAction(ct);
var entityId = entity.GetEntityId();
_actionDecisionResults.Remove(entityId);
_actionDecisionResults[entityId] = await DecideAction(ctx, entity);
_battleActionDatas.Remove(entityId);
var decidedAction = _actionDecisionResults[entityId];
_battleActionDatas[entityId] =
_battleActions[decidedAction.ActionType]
.MountAndGetFrames(entity, decidedAction);
}
}
private async UniTask<List<EntityTimelineActor>> PlayTimelineUntilPause(CancellationToken ctx)
private async UniTask<List<EntityProperties>> PlayTimelineUntilPause(CancellationToken ctx)
{
float accumulator = 0f;
var readyEntities = new List<EntityTimelineActor>();
var readyEntities = new List<EntityProperties>();
while (readyEntities.Count == 0 && !ctx.IsCancellationRequested)
{
@@ -93,16 +128,24 @@ namespace Code.OfficeGame.BattleMode.Timeline
return readyEntities;
}
private void SimulateSingleFrame(List<EntityTimelineActor> outReadyEntities)
private void SimulateSingleFrame(List<EntityProperties> outReadyEntities)
{
_currentBattleFrame.Value++;
foreach (var entity in _entities)
{
entity.CurrentAction.Tick(TickDelta);
var entityId = entity.GetEntityId();
var finished = _battleActions[_actionDecisionResults[entityId].ActionType]
.Tick(
TickDelta,
entity,
_actionDecisionResults[entityId],
_battleActionDatas[entityId]);
entity.animator.Update(TickDelta);
if (entity.Finished)
if (finished)
{
outReadyEntities.Add(entity);
}
@@ -112,6 +155,55 @@ namespace Code.OfficeGame.BattleMode.Timeline
Physics.SyncTransforms();
}
private bool IsBattleOver() => false;
private async UniTask<BattleActionDecisionResult> DecideAction(CancellationToken ctx, EntityProperties entity)
{
_battleActionDecisionPanel.SetEntityInfo(entity.battleActionTypes);
BattleActionType decidedAction = BattleActionType.None;
BattleActionDecisionResult decision = null;
while (decidedAction == BattleActionType.None || ctx.IsCancellationRequested)
{
Cursor.lockState = CursorLockMode.Confined;
((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = true;
await _battleActionDecisionPanel.Open(ctx);
decidedAction = await _battleActionDecisionPanel.GetDecision(ctx);
if (decidedAction == BattleActionType.None) continue;
var deciderType = _battleActions[decidedAction].DeciderType;
_battleActionDeciders[deciderType].MountDecider();
while (!ctx.IsCancellationRequested)
{
Cursor.lockState = CursorLockMode.Locked;
((CameraBattleModeState)_cameraStateMachine.CurrentBaseState).battleDeciding = false;
await _battleActionDecisionPanel.Close(ctx);
decision = await _battleActionDeciders[deciderType].GetDecision();
if (decision.ResultCode == BattleActionDecisionResultCode.CancelDecision)
decidedAction = BattleActionType.None;
if (decision.ResultCode != BattleActionDecisionResultCode.Deciding)
break;
}
_battleActionDeciders[deciderType].UnmountDecider();
}
if (decision == null) throw new ArgumentNullException(nameof(decision));
decision.ActionType = decidedAction;
return decision;
}
}
}
@@ -20,13 +20,13 @@ namespace Code.OfficeGame.BattleMode.UI
private MotionHandle _motionHandle;
private UniTaskCompletionSource<BattleActionType> _decisionCompletionSource;
public void SetEntityInfo(Dictionary<BattleActionType, BattleAction> battleActions)
public void SetEntityInfo(BattleActionType[] battleActions)
{
foreach (var button in _actionButtons) Destroy(button.gameObject);
_actionButtons.Clear();
foreach (var action in battleActions.Keys)
foreach (var action in battleActions)
{
var button = Instantiate(actionButtonPrefab, transform);
+1
View File
@@ -6,6 +6,7 @@ namespace Code.Infrastructure.SceneLoader
public enum SceneId
{
Testing,
Battle,
FirstTestingSegment,
SecondTestingSegment
}