Snapshots. Better actions

This commit is contained in:
ThomasCharester committed 2026-10-30 21:48:56 +03:00
1 parent 71bb4c3b5b
commit 31850e77bc
73 files changed
+31948 -80048

No files matched your search

Binary file not shown.
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 924f8182d7fa4e48a0ce1447e05f794f
timeCreated: 1793372499
@@ -3,5 +3,8 @@ namespace Code.OfficeGame.BattleMode.Actions
public abstract class BattleActionData
{
public int TotalFrames;
public abstract BattleActionData GetSnapshot();
public abstract void RestoreSnapshot(BattleActionData snapshot);
}
}
@@ -0,0 +1,55 @@
using System;
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;
public override BattleActionData GetSnapshot()
{
Vector3[] cornersCopy = null;
if (Corners != null)
{
cornersCopy = new Vector3[Corners.Length];
Array.Copy(Corners, cornersCopy, Corners.Length);
}
return new MovementBattleActionData
{
TotalFrames = TotalFrames,
Position = Position,
Rotation = Rotation,
Path = Path,
Corners = cornersCopy,
CurrentCornerIndex = CurrentCornerIndex
};
}
public override void RestoreSnapshot(BattleActionData snapshot)
{
TotalFrames = snapshot.TotalFrames;
if (snapshot is not MovementBattleActionData moveSnapshot) return;
Position = moveSnapshot.Position;
Rotation = moveSnapshot.Rotation;
Path = moveSnapshot.Path;
CurrentCornerIndex = moveSnapshot.CurrentCornerIndex;
if (moveSnapshot.Corners != null)
{
if (Corners == null || Corners.Length != moveSnapshot.Corners.Length)
Corners = new Vector3[moveSnapshot.Corners.Length];
Array.Copy(moveSnapshot.Corners, Corners, moveSnapshot.Corners.Length);
}
else Corners = null;
}
}
}
@@ -0,0 +1,15 @@
namespace Code.OfficeGame.BattleMode.Actions
{
public class NoneBattleActionData : BattleActionData
{
public override BattleActionData GetSnapshot()
{
return new NoneBattleActionData();
}
public override void RestoreSnapshot(BattleActionData snapshot)
{
TotalFrames = snapshot.TotalFrames;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73b4bda13326404db2b0ad653ad43dce
timeCreated: 1793372539
@@ -0,0 +1,24 @@
namespace Code.OfficeGame.BattleMode.Actions
{
public class WaitBattleActionData : BattleActionData
{
public int WaitFrames;
public override BattleActionData GetSnapshot()
{
return new WaitBattleActionData
{
TotalFrames = TotalFrames,
WaitFrames = WaitFrames
};
}
public override void RestoreSnapshot(BattleActionData snapshot)
{
TotalFrames = snapshot.TotalFrames;
if (snapshot is not WaitBattleActionData waitSnapshot) return;
WaitFrames = waitSnapshot.WaitFrames;
}
}
}
@@ -0,0 +1,20 @@
using Code.OfficeGame.BattleMode.Actions.Deciders;
namespace Code.OfficeGame.BattleMode.Actions
{
public class BattleActionNone : BattleAction
{
public override bool Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult,
BattleActionData battleActionData)
{
return true;
}
public override BattleActionType ActionType => BattleActionType.None;
public override DeciderType DeciderType => DeciderType.AsIs;
public override BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result)
{
return new NoneBattleActionData();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2371c1e8c84b444e9cec026acea67469
timeCreated: 1793377460
@@ -6,17 +6,84 @@ namespace Code.OfficeGame.BattleMode.Actions
{
public BattleActionType ActionType;
public BattleActionDecisionResultCode ResultCode;
public abstract BattleActionDecisionResult GetSnapshot();
public abstract void RestoreSnapshot(BattleActionDecisionResult snapshot);
}
public class PositionBattleActionDeciderResult : BattleActionDecisionResult
{
public Vector3 Position;
public Quaternion Rotation;
public override BattleActionDecisionResult GetSnapshot()
{
var snapshot = new PositionBattleActionDeciderResult
{
Position = Position,
Rotation = Rotation,
ActionType = ActionType,
ResultCode = ResultCode
};
return snapshot;
}
public override void RestoreSnapshot(BattleActionDecisionResult snapshot)
{
if (snapshot is not PositionBattleActionDeciderResult) return;
var deciderResult = (PositionBattleActionDeciderResult)snapshot;
Position = deciderResult.Position;
Rotation = deciderResult.Rotation;
ActionType = deciderResult.ActionType;
ResultCode = deciderResult.ResultCode;
}
}
public class FrameBattleActionDeciderResult : BattleActionDecisionResult
{
public int FrameCount;
public override BattleActionDecisionResult GetSnapshot()
{
var snapshot = new FrameBattleActionDeciderResult
{
FrameCount = FrameCount,
ActionType = ActionType,
ResultCode = ResultCode
};
return snapshot;
}
public override void RestoreSnapshot(BattleActionDecisionResult snapshot)
{
if (snapshot is not FrameBattleActionDeciderResult) return;
var deciderResult = (FrameBattleActionDeciderResult)snapshot;
FrameCount = deciderResult.FrameCount;
ActionType = deciderResult.ActionType;
ResultCode = deciderResult.ResultCode;
}
}
public class AsIsBattleActionDeciderResult : BattleActionDecisionResult
{
public override BattleActionDecisionResult GetSnapshot()
{
var snapshot = new AsIsBattleActionDeciderResult();
snapshot.ActionType = ActionType;
snapshot.ResultCode = ResultCode;
return snapshot;
}
public override void RestoreSnapshot(BattleActionDecisionResult snapshot)
{
ActionType = snapshot.ActionType;
ResultCode = snapshot.ResultCode;
}
}
}
@@ -41,7 +41,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
if (!IsMounted) throw new Exception("Decider is not mounted"); // А почему бы и нет?
_waitForEnterTokenSource = new CancellationTokenSource();
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
_decisionResultCode = BattleActionDecisionResultCode.Deciding;
await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
@@ -51,7 +51,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
return new PositionBattleActionDeciderResult()
{
ResultCode = _decisionResultCode,
Position = _targetPosition,
Position = _targetPosition
};
}
@@ -137,6 +137,8 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
_playerLockedDecisionMarker.MoveMarker(_targetPosition);
_playerLockedDecisionMarker.Toggle(true);
_waitForEnterTokenSource?.Cancel();
}
}
}
@@ -0,0 +1,231 @@
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 static EntitySnapshot GetSnapshot(EntityProperties entityProperties)
{
EntitySnapshot snapshot = new EntitySnapshot()
{
Position = entityProperties.KinematicCharacterMotor.TransientPosition,
Rotation = entityProperties.KinematicCharacterMotor.TransientRotation,
AnimatorSnapshot = entityProperties.animator.GetSnapshot()
};
return snapshot;
}
public static void RestoreSnapshot(EntityProperties entityProperties, EntitySnapshot snapshot)
{
entityProperties.animator.RestoreSnapshot(snapshot.AnimatorSnapshot);
entityProperties.KinematicCharacterMotor.SetPositionAndRotation(snapshot.Position, snapshot.Rotation);
}
}
public struct AnimatorSnapshot
{
public struct LayerStateSnapshot
{
public int FullPathHash;
public float NormalizedTime;
public float LayerWeight;
}
public struct ParameterSnapshot
{
public int NameHash;
public AnimatorControllerParameterType Type;
public float FloatValue;
public int IntValue;
public bool BoolValue;
}
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();
snapshot.CaptureFrom(animator);
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,
NormalizedTime = stateInfo.normalizedTime,
LayerWeight = animator.GetLayerWeight(i)
};
}
// 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
};
switch (param.type)
{
case AnimatorControllerParameterType.Float:
paramSnapshot.FloatValue = animator.GetFloat(hash);
break;
case AnimatorControllerParameterType.Int:
paramSnapshot.IntValue = animator.GetInteger(hash);
break;
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
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) не сдвинул модель повторно
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;
switch (p.Type)
{
case AnimatorControllerParameterType.Float:
targetAnimator.SetFloat(p.NameHash, p.FloatValue);
break;
case AnimatorControllerParameterType.Int:
targetAnimator.SetInteger(p.NameHash, p.IntValue);
break;
case AnimatorControllerParameterType.Bool:
targetAnimator.SetBool(p.NameHash, p.BoolValue);
break;
case AnimatorControllerParameterType.Trigger:
if (p.BoolValue) targetAnimator.SetTrigger(p.NameHash);
else targetAnimator.ResetTrigger(p.NameHash);
break;
}
}
}
// 3. Восстанавливаем стейты и точный кадр (NormalizedTime) на каждом слое
int layerCount = Mathf.Min(_layers.Length, targetAnimator.layerCount);
for (int i = 0; i < layerCount; i++)
{
var layer = _layers[i];
targetAnimator.SetLayerWeight(i, layer.LayerWeight);
targetAnimator.Play(layer.FullPathHash, i, layer.NormalizedTime);
}
// 4. Принудительно пересчитываем кости скелета в текущем кадре
targetAnimator.Update(0f);
// 5. Возвращаем локальное положение модели и настройки Root Motion
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;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b014c8950464495f9b5022a81d6f5e2e
timeCreated: 1793381535
@@ -1,14 +0,0 @@
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;
}
}
@@ -1,7 +0,0 @@
namespace Code.OfficeGame.BattleMode.Actions
{
public class WaitBattleActionData : BattleActionData
{
public int WaitFrames;
}
}