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; /// /// Создаёт новый экземпляр снапшота с текущим состоянием аниматора /// public static AnimatorSnapshot GetSnapshot(Animator animator) { var snapshot = new AnimatorSnapshot(); snapshot.CaptureFrom(animator); return snapshot; } /// /// Записывает текущее состояние аниматора в этот снапшот (без лишних аллокаций при повторном вызове) /// 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; } } /// /// Восстанавливает состояние в целевой аниматор (оригинал или клон-призрак) /// 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; } /// /// Создаёт глубокую копию самого снапшота /// 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; } } }