Exaction timelinetion

This commit is contained in:
ThomasCharester committed 2026-11-06 19:40:08 +03:00
1 parent 921c4125b7
commit 0288afb00a
722 files changed
+144715 -152451

No files matched your search

@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions
public class AttackBattleActionData : BattleActionData
{
public int CurrentFrame;
public int TransitionFrames;
public override BattleActionData GetSnapshot()
{
var snapshot = new AttackBattleActionData()
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions
public abstract class BattleActionData
{
public int TotalFrames;
public bool Finished = false;
public abstract BattleActionData GetSnapshot();
public abstract void RestoreSnapshot(BattleActionData snapshot);
@@ -6,6 +6,7 @@ namespace Code.OfficeGame.BattleMode.Actions
{
public class MovementBattleActionData : BattleActionData
{
public bool WasStarted = false;
public Vector3 Position;
public Quaternion Rotation;
public NavMeshPath Path;
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions
public class StuggerActionData : BattleActionData
{
public int CurrentFrame;
public int TransitionFrames = 60;
public override BattleActionData GetSnapshot()
{
var snapshot = new AttackBattleActionData()
@@ -1,7 +1,9 @@
using System.Linq;
using Code.Game.Entities;
using Code.Infrastructure.Particles;
using Code.Infrastructure.Utils;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using Code.OfficeGame.BattleMode.Timeline;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions.Attacks
@@ -11,61 +13,77 @@ namespace Code.OfficeGame.BattleMode.Actions.Attacks
private AttackActionConfig _attackData;
private IParticleService _particleService;
public int EntryStateHash => Animator.StringToHash("WalkBlend");
public BattleActionPlayerAttackMMA(MultipleConfig<BattleActionParameters> attackData, IParticleService particleService)
public BattleActionPlayerAttackMMA(MultipleConfig<BattleActionParameters> attackData,
IParticleService particleService)
{
_particleService = particleService;
_attackData = (AttackActionConfig)
attackData.Configs.SingleOrDefault(x=>x.ActionType == ActionType);
_attackData = (AttackActionConfig)
attackData.Configs.SingleOrDefault(x => x.ActionType == ActionType);
}
public override BattleActionType ActionType => BattleActionType.PlayerAttackMMA;
public override bool Tick(float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
public override void Tick(float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
if (battleActionData is not AttackBattleActionData actionData) return true;
if (battleActionData is not AttackBattleActionData actionData
|| battleActionDecisionResult is not RotationBattleActionDeciderResult deciderResult)
{
battleActionData.Finished = true;
return;
}
if (actionData.CurrentFrame == 0)
{
var entityAnimator = entity.animator;
actionData.TransitionFrames = _attackData.TransitionFrames;
actionData.TotalFrames = _attackData.TotalFrames;
// TODO Root Motion не должен быть использован в нормальных анимациях
entityAnimator.applyRootMotion = true;
entityAnimator.Play(_attackData.StateHash, 0, 0f);
entityAnimator.Update(0f);
entityAnimator.ResetAnimatorPosition();
entity.SyncRootMotionToMotor();
var entityAnimator = entity.animator;
entityAnimator.applyRootMotion = true;
var transitionTime = actionData.TransitionFrames * BattleTimeline.TickDelta;
entityAnimator.CrossFadeInFixedTime(_attackData.StateHash, transitionTime);
}
else if (actionData.CurrentFrame <= actionData.TransitionFrames
&& actionData.TransitionFrames > 0)
{
float t = (float)actionData.CurrentFrame / actionData.TransitionFrames;
Quaternion transientRotation = Quaternion.Lerp(
entity.KinematicCharacterMotor.TransientRotation,
deciderResult.Rotation,
t);
entity.KinematicCharacterMotor.SetRotation(transientRotation);
}
actionData.CurrentFrame++;
if (actionData.CurrentFrame >= _attackData.HitboxStartFrame
if (actionData.CurrentFrame >= _attackData.HitboxStartFrame
&& actionData.CurrentFrame <= _attackData.HitboxEndFrame)
{
entity.EntityHitBoxes.CheckHitsForCurrentFrame(OnEnemyHit, _attackData.AttackingBodyPart);
}
if (actionData.CurrentFrame >= _attackData.TotalFrames)
if (actionData.CurrentFrame >= actionData.TotalFrames)
{
var entityAnimator = entity.animator;
entityAnimator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
entity.SyncRootMotionToMotor();
entityAnimator.Play(EntryStateHash, 0, 0f);
entityAnimator.Update(0f);
// TODO С нормальными анимациями не нужен будет рут моушен√
entityAnimator.ResetAnimatorPosition();
return true;
battleActionData.Finished = true;
}
return false;
}
public override DeciderType DeciderType => DeciderType.Position;
public override DeciderType DeciderType => DeciderType.Rotation;
public override BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result)
{
return new AttackBattleActionData() { TotalFrames = _attackData.TotalFrames };
@@ -74,11 +92,11 @@ namespace Code.OfficeGame.BattleMode.Actions.Attacks
private void OnEnemyHit(Collider enemyCollider)
{
var originEntityId = enemyCollider.GetComponentInParent<EntityProperties>().GetEntityId();
var actionData = new StuggerActionData { TotalFrames = _attackData.StuggerFrames, CurrentFrame = 0};
var decisionResult = new AsIsBattleActionDeciderResult {ActionType = BattleActionType.Stugger};
BattleTimeline.ForceDecideAction(originEntityId, actionData, decisionResult);
var actionData = new StuggerActionData { TotalFrames = _attackData.StuggerFrames, CurrentFrame = 0 };
var decisionResult = new AsIsBattleActionDeciderResult { ActionType = BattleActionType.Stugger };
battleTimeline.ForceDecideAction(originEntityId, actionData, decisionResult);
_particleService.EmitOneShot(
new ParticlePartitionIDPair(ParticlePartition.HitParticles, 0),
@@ -5,12 +5,12 @@ namespace Code.OfficeGame.BattleMode.Actions
{
public abstract class BattleAction
{
public abstract bool Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult, BattleActionData battleActionData);
public abstract void 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);
protected BattleTimeline BattleTimeline;
public void SetBattleTimeline(BattleTimeline battleTimeline) => BattleTimeline = battleTimeline;
protected BattleTimeline battleTimeline;
public void SetBattleTimeline(BattleTimeline battleTimeline) => this.battleTimeline = battleTimeline;
}
}
@@ -1,40 +1,50 @@
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;
using Cysharp.Threading.Tasks;
using KinematicCharacterController;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.InputSystem;
namespace Code.OfficeGame.BattleMode.Actions
{
public class BattleActionMove : BattleAction
{
private const float StoppingDistance = 0.15f;
private const float RotateDistance = 1f;
public override bool Tick(
private int MoveStateHash => Animator.StringToHash("WalkBlend");
public override void Tick(
float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
var movementActionData = (MovementBattleActionData) battleActionData;
if(battleActionData is not MovementBattleActionData movementActionData
|| battleActionDecisionResult is not PositionBattleActionDeciderResult positionDecisionResult)
{
battleActionData.Finished = true;
return;
}
var entityMovementController = entity.MovementController;
if (movementActionData.Corners == null || movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length)
{
StopMovementAndFinish(entityMovementController);
return true;
battleActionData.Finished = true;
return;
}
if (!movementActionData.WasStarted)
{
movementActionData.WasStarted = true;
float transitionDurationSeconds = 10 * BattleTimeline.TickDelta;
entity.animator.CrossFadeInFixedTime(MoveStateHash, transitionDurationSeconds);
}
Vector3 currentPos = entity.KinematicCharacterMotor.TransientPosition;
while (movementActionData.CurrentCornerIndex < movementActionData.Corners.Length)
@@ -56,32 +66,42 @@ namespace Code.OfficeGame.BattleMode.Actions
KinematicCharacterSystem.CharacterMotors,
KinematicCharacterSystem.PhysicsMovers
);
return true;
battleActionData.Finished = true;
return;
}
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;
bool preLastPoint = movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length - 1
&& distance < RotateDistance;
float maxStep = entityMovementController.MaxStableMoveSpeed * tickDelta;
float inputMagnitude =
(movementActionData.CurrentCornerIndex == movementActionData.Corners.Length - 1
&& distance < maxStep
&& maxStep > 0f)
? distance / maxStep
: 1f;
? distance / maxStep
: 1f;
Quaternion lookRotation;
if(preLastPoint) lookRotation = positionDecisionResult.Rotation;
else lookRotation = moveNormal.sqrMagnitude > 0.0001f
? Quaternion.LookRotation(moveNormal, entityMovementController.Motor.CharacterUp)
: entityMovementController.Motor.TransientRotation;
// TODO Для последнего кадра должен быть особый Look совпадающий с маркером
var aiInputs = new AICharacterInputs
{
MoveVector = moveNormal * inputMagnitude,
Look = moveNormal.sqrMagnitude > 0.0001f
? Quaternion.LookRotation(moveNormal, entityMovementController.Motor.CharacterUp)
: entityMovementController.Motor.TransientRotation
Look = lookRotation
};
entityMovementController.SetInputs(ref aiInputs);
@@ -99,11 +119,10 @@ namespace Code.OfficeGame.BattleMode.Actions
if (movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length - 1
&& toFinal.sqrMagnitude <= StoppingDistance * StoppingDistance)
{
entity.KinematicCharacterMotor.SetRotation(positionDecisionResult.Rotation);
StopMovementAndFinish(entityMovementController);
return true;
battleActionData.Finished = true;
}
return false;
}
private void StopMovementAndFinish(MovementController movementController)
@@ -4,10 +4,10 @@ namespace Code.OfficeGame.BattleMode.Actions
{
public class BattleActionNone : BattleAction
{
public override bool Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult,
public override void Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
return true;
battleActionData.Finished = true;
}
public override BattleActionType ActionType => BattleActionType.None;
@@ -14,5 +14,6 @@ namespace Code.OfficeGame.BattleMode.Actions
public int StateHash => Animator.StringToHash(AnimationClip.name);
public int TotalFrames;
public int TransitionFrames;
}
}
@@ -1,6 +1,7 @@
using System.Linq;
using Code.Infrastructure.Utils;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using Code.OfficeGame.BattleMode.Timeline;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
@@ -24,27 +25,30 @@ namespace Code.OfficeGame.BattleMode.Actions
return new StuggerActionData { TotalFrames = _actionParameters.TotalFrames};
}
public override bool Tick(float tickDelta,
public override void Tick(float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
if (battleActionData is not StuggerActionData stuggerActionData) return true;
if (battleActionData is not StuggerActionData stuggerActionData)
{
battleActionData.Finished = true;
return;
}
if (stuggerActionData.CurrentFrame == 0)
{
entity.animator.Play(StuggerStateHash, 0, 0f);
var transitionTime = stuggerActionData.TransitionFrames * BattleTimeline.TickDelta;
entity.animator.ResetAnimatorPosition();
entity.animator.CrossFadeInFixedTime(StuggerStateHash, transitionTime);
}
stuggerActionData.CurrentFrame++;
if (stuggerActionData.CurrentFrame >= _actionParameters.TotalFrames)
{
entity.animator.Play(EntryStateHash, 0, 0f);
return true;
battleActionData.Finished = true;
}
return false;
}
}
}
@@ -1,5 +1,6 @@
using Code.Infrastructure.Utils;
using Code.OfficeGame.BattleMode.Actions.Deciders;
using Code.OfficeGame.BattleMode.Timeline;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
@@ -8,12 +9,16 @@ namespace Code.OfficeGame.BattleMode.Actions
{
private int EntryStateHash => Animator.StringToHash("WalkBlend");
public override bool Tick(float tickDelta,
public override void Tick(float tickDelta,
EntityProperties entity,
BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
if (battleActionData is not WaitBattleActionData waitActionData) return true;
if (battleActionData is not WaitBattleActionData waitActionData)
{
battleActionData.Finished = true;
return;
}
if (!waitActionData.StartedWaiting)
{
@@ -21,17 +26,22 @@ namespace Code.OfficeGame.BattleMode.Actions
var entityAnimator = entity.animator;
entityAnimator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
entityAnimator.Update(0f);
float transitionDurationSeconds = 30 * BattleTimeline.TickDelta;
entityAnimator.CrossFadeInFixedTime(EntryStateHash, transitionDurationSeconds);
// entityAnimator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
// entityAnimator.Update(0f);
// TODO С нормальными анимациями не нужен будет рут моушен√
entityAnimator.ResetAnimatorPosition();
}
waitActionData.WaitFrames--;
if (waitActionData.WaitFrames <= 0) return true;
return false;
if (waitActionData.WaitFrames <= 0)
{
battleActionData.Finished = true;
}
}
public override BattleActionType ActionType => BattleActionType.Wait;
@@ -18,7 +18,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
public override void UnmountDecider()
{ }
public override void MountDecider()
public override void MountDecider(EntityProperties entityProperties)
{ }
}
}
@@ -10,6 +10,6 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
public abstract void UnmountDecider();
public abstract void MountDecider();
public abstract void MountDecider(EntityProperties entityProperties);
}
}
@@ -41,7 +41,28 @@ namespace Code.OfficeGame.BattleMode.Actions
ResultCode = deciderResult.ResultCode;
}
}
public class RotationBattleActionDeciderResult : BattleActionDecisionResult
{
public Quaternion Rotation;
public override BattleActionDecisionResult GetSnapshot()
{
return new RotationBattleActionDeciderResult
{
Rotation = Rotation,
ActionType = ActionType,
ResultCode = ResultCode
};
}
public override void RestoreSnapshot(BattleActionDecisionResult snapshot)
{
if (snapshot is not RotationBattleActionDeciderResult deciderResult) return;
Rotation = deciderResult.Rotation;
ActionType = deciderResult.ActionType;
ResultCode = deciderResult.ResultCode;
}
}
public class FrameBattleActionDeciderResult : BattleActionDecisionResult
{
public int FrameCount;
@@ -16,10 +16,10 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
private IInputService _inputService;
private Vector3 _targetPosition;
private Quaternion _targetRotation;
private LayerMask _navMeshMask;
private const float StoppingDistance = 0.15f;
public BattleActionPositionDecider(
CameraStateMachine cameraStateMachine,
IInputService inputService,
@@ -51,7 +51,8 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
return new PositionBattleActionDeciderResult()
{
ResultCode = _decisionResultCode,
Position = _targetPosition
Position = _targetPosition,
Rotation = _targetRotation
};
}
@@ -63,6 +64,10 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
_markerManager.ReleaseMarkerInstance(_playerDecisionMarker);
_markerManager.ReleaseMarkerInstance(_playerLockedDecisionMarker);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
ActivateRotationChange);
_inputService.UnbindActionCancelled((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
DeactivateRotationChange);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectPosition);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
@@ -75,10 +80,24 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
IsMounted = false;
}
public override void MountDecider()
private bool _rotationChanging = false;
private void ActivateRotationChange(InputAction.CallbackContext obj)
{
_rotationChanging = true;
}
private void DeactivateRotationChange(InputAction.CallbackContext obj)
{
_rotationChanging = false;
}
public override void MountDecider(EntityProperties entityProperties)
{
if (IsMounted) return;
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
ActivateRotationChange);
_inputService.BindActionCancelled((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
DeactivateRotationChange);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectPosition);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
@@ -92,6 +111,8 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
_playerDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker.Toggle(true);
_targetRotation = Quaternion.identity;
IsMounted = true;
}
@@ -111,7 +132,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
}
private RaycastHit[] _hitBuffer = new RaycastHit[1];
private Vector3 _lastViewedPosition;
private Vector3 _lastViewedPosition = Vector3.zero;
private PlayerDecisionMarker _playerDecisionMarker;
private PlayerDecisionMarker _playerLockedDecisionMarker;
@@ -125,10 +146,23 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
if (hitCount == 0) return;
var hit = _hitBuffer[0];
_playerDecisionMarker.MoveMarker(hit.point);
if(!_rotationChanging)
{
_playerDecisionMarker.MoveMarker(hit.point);
_lastViewedPosition = hit.point;
_lastViewedPosition = hit.point;
}
else if(_lastViewedPosition != Vector3.zero)
{
var lookDirection = hit.point - _lastViewedPosition;
lookDirection.y = 0;
_targetRotation = Quaternion.LookRotation(lookDirection.normalized, Vector3.up);
_playerDecisionMarker.transform.rotation = _targetRotation;
_playerLockedDecisionMarker.transform.rotation = _targetRotation;
}
}
private void SelectPosition(InputAction.CallbackContext input)
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
public enum DeciderType
{
AsIs,
Position
Position,
Rotation
}
}
@@ -0,0 +1,151 @@
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 RotationBattleActionDecider: BattleActionDecider
{
private CameraStateMachine _cameraStateMachine;
private IInputService _inputService;
private Quaternion _targetRotation;
private LayerMask _navMeshMask;
public RotationBattleActionDecider(
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.Rotation;
public override async UniTask<BattleActionDecisionResult> GetDecision()
{
if (!IsMounted) throw new Exception("Decider is not mounted"); // А почему бы и нет?
_waitForEnterTokenSource = new CancellationTokenSource();
_decisionResultCode = BattleActionDecisionResultCode.Deciding;
await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
_waitForEnterTokenSource.Dispose();
_waitForEnterTokenSource = null;
return new RotationBattleActionDeciderResult()
{
ResultCode = _decisionResultCode,
Rotation = _targetRotation
};
}
public override void UnmountDecider()
{
if (!IsMounted) return;
_playerDecisionMarker.Toggle(false);
_markerManager.ReleaseMarkerInstance(_playerDecisionMarker);
_playerLockedDecisionMarker.Toggle(false);
_markerManager.ReleaseMarkerInstance(_playerLockedDecisionMarker);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectRotation);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmRotation);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelRotation);
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
MarkPosition);
IsMounted = false;
}
public override void MountDecider(EntityProperties entityProperties)
{
if (IsMounted) return;
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
SelectRotation);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
ConfirmRotation);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
CancelRotation);
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
MarkPosition);
_playerLockedDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker = _markerManager.GetMarkerInstance();
_playerDecisionMarker.Toggle(true);
_targetRotation = entityProperties.KinematicCharacterMotor.TransientRotation;
_entityPosition = entityProperties.KinematicCharacterMotor.TransientPosition;
IsMounted = true;
}
private MarkerManager _markerManager;
private void CancelRotation(InputAction.CallbackContext obj)
{
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_waitForEnterTokenSource?.Cancel();
}
private void ConfirmRotation(InputAction.CallbackContext input)
{
_decisionResultCode = BattleActionDecisionResultCode.Decided;
_waitForEnterTokenSource?.Cancel();
}
private RaycastHit[] _hitBuffer = new RaycastHit[1];
private PlayerDecisionMarker _playerDecisionMarker;
private PlayerDecisionMarker _playerLockedDecisionMarker;
private Vector3 _entityPosition;
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
|| _entityPosition == Vector3.zero) return;
var hit = _hitBuffer[0];
var lookDirection = hit.point - _entityPosition;
_targetRotation = Quaternion.LookRotation(lookDirection.normalized, Vector3.up);
_playerDecisionMarker.transform.rotation = _targetRotation;
_playerDecisionMarker.transform.position = hit.point;
}
private void SelectRotation(InputAction.CallbackContext input)
{
_playerLockedDecisionMarker.MoveMarker(_entityPosition);
_playerLockedDecisionMarker.transform.rotation = _targetRotation;
_playerLockedDecisionMarker.Toggle(true);
_waitForEnterTokenSource?.Cancel();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 741d431fb1944bd48206d1410ee3f098
timeCreated: 1793890803
@@ -1,38 +1,37 @@
using System;
using Code.Infrastructure.Utils;
using UnityEngine;
namespace Code.OfficeGame.BattleMode.Actions
{
public struct EntitySnapshot
{
// Transforms
public Vector3 Position;
public Quaternion Rotation;
// Animator
AnimatorSnapshot AnimatorSnapshot;
public AnimatorSnapshot AnimatorSnapshot;
public static EntitySnapshot GetSnapshot(EntityProperties entityProperties)
{
EntitySnapshot snapshot = new EntitySnapshot()
return new EntitySnapshot
{
Position = entityProperties.KinematicCharacterMotor.TransientPosition,
Rotation = entityProperties.KinematicCharacterMotor.TransientRotation,
AnimatorSnapshot = entityProperties.animator.GetSnapshot()
AnimatorSnapshot = AnimatorSnapshot.GetSnapshot(entityProperties.animator)
};
return snapshot;
}
public static void RestoreSnapshot(EntityProperties entityProperties, EntitySnapshot snapshot)
{
entityProperties.animator.RestoreSnapshot(snapshot.AnimatorSnapshot);
// 1. Возвращаем капсулу KCC
entityProperties.KinematicCharacterMotor.SetPositionAndRotation(snapshot.Position, snapshot.Rotation);
entityProperties.KinematicCharacterMotor.BaseVelocity = Vector3.zero;
// 2. Возвращаем позу и локальное смещение меша
snapshot.AnimatorSnapshot.RestoreTo(entityProperties.animator);
// 3. Синхронизируем физику
Physics.SyncTransforms();
}
}
public struct AnimatorSnapshot
{
public struct LayerStateSnapshot
@@ -53,14 +52,10 @@ namespace Code.OfficeGame.BattleMode.Actions
private LayerStateSnapshot[] _layers;
private ParameterSnapshot[] _parameters;
private Vector3 _localPosition;
private Quaternion _localRotation;
private float _speed;
/// <summary>
/// Создаёт новый экземпляр снапшота с текущим состоянием аниматора
/// </summary>
public static AnimatorSnapshot GetSnapshot(Animator animator)
{
var snapshot = new AnimatorSnapshot();
@@ -68,32 +63,22 @@ namespace Code.OfficeGame.BattleMode.Actions
return snapshot;
}
/// <summary>
/// Записывает текущее состояние аниматора в этот снапшот (без лишних аллокаций при повторном вызове)
/// </summary>
public void CaptureFrom(Animator animator)
{
if (animator == null) return;
// 1. Сохраняем локальный трансформ модели (защита от смещения Root Motion)
Transform animTransform = animator.transform;
_localPosition = animTransform.localPosition;
_localRotation = animTransform.localRotation;
_speed = animator.speed;
// 2. Сохраняем состояния и кадры всех слоёв
int layerCount = animator.layerCount;
if (_layers == null || _layers.Length != layerCount)
{
_layers = new LayerStateSnapshot[layerCount];
}
for (int i = 0; i < layerCount; i++)
{
// Если аниматор находился в процессе перехода (Transition),
// можно при необходимости брать GetNextAnimatorStateInfo(i), но для покадрового Play обычно используется текущий:
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(i);
_layers[i] = new LayerStateSnapshot
{
FullPathHash = stateInfo.fullPathHash,
@@ -102,23 +87,15 @@ namespace Code.OfficeGame.BattleMode.Actions
};
}
// 3. Сохраняем значения всех параметров
int paramCount = animator.parameterCount;
if (_parameters == null || _parameters.Length != paramCount)
{
_parameters = new ParameterSnapshot[paramCount];
}
for (int i = 0; i < paramCount; i++)
{
AnimatorControllerParameter param = animator.GetParameter(i);
int hash = param.nameHash;
var paramSnapshot = new ParameterSnapshot
{
NameHash = hash,
Type = param.type
};
var paramSnapshot = new ParameterSnapshot { NameHash = hash, Type = param.type };
switch (param.type)
{
@@ -133,31 +110,26 @@ namespace Code.OfficeGame.BattleMode.Actions
paramSnapshot.BoolValue = animator.GetBool(hash);
break;
}
_parameters[i] = paramSnapshot;
}
}
/// <summary>
/// Восстанавливает состояние в целевой аниматор (оригинал или клон-призрак)
/// </summary>
public void RestoreTo(Animator targetAnimator)
{
if (targetAnimator == null || _layers == null) return;
// 1. Временно выключаем Root Motion, чтобы Update(0f) не сдвинул модель повторно
targetAnimator.Rebind();
// Отключаем applyRootMotion на время Update(0f), чтобы Play() не начислил дельту смещения
bool prevRootMotion = targetAnimator.applyRootMotion;
targetAnimator.applyRootMotion = false;
targetAnimator.speed = _speed;
// 2. Восстанавливаем параметры
if (_parameters != null)
{
for (int i = 0; i < _parameters.Length; i++)
{
var p = _parameters[i];
// Пропускаем параметры, которые управляются кривыми внутри самой анимации
if (targetAnimator.IsParameterControlledByCurve(p.NameHash))
continue;
@@ -180,7 +152,6 @@ namespace Code.OfficeGame.BattleMode.Actions
}
}
// 3. Восстанавливаем стейты и точный кадр (NormalizedTime) на каждом слое
int layerCount = Mathf.Min(_layers.Length, targetAnimator.layerCount);
for (int i = 0; i < layerCount; i++)
{
@@ -189,43 +160,14 @@ namespace Code.OfficeGame.BattleMode.Actions
targetAnimator.Play(layer.FullPathHash, i, layer.NormalizedTime);
}
// 4. Принудительно пересчитываем кости скелета в текущем кадре
targetAnimator.Update(0f);
// 5. Возвращаем локальное положение модели и настройки Root Motion
// ОБЯЗАТЕЛЬНО восстанавливаем сохраненные локальные координаты меша после Update(0f)!
Transform targetTransform = targetAnimator.transform;
targetTransform.localPosition = _localPosition;
targetTransform.localRotation = _localRotation;
targetAnimator.applyRootMotion = prevRootMotion;
}
/// <summary>
/// Создаёт глубокую копию самого снапшота
/// </summary>
public AnimatorSnapshot Clone()
{
var clone = new AnimatorSnapshot
{
_localPosition = _localPosition,
_localRotation = _localRotation,
_speed = _speed
};
if (_layers != null)
{
clone._layers = new LayerStateSnapshot[_layers.Length];
Array.Copy(_layers, clone._layers, _layers.Length);
}
if (_parameters != null)
{
clone._parameters = new ParameterSnapshot[_parameters.Length];
Array.Copy(_parameters, clone._parameters, _parameters.Length);
}
return clone;
}
}
}