HitboxDisplay lots of broken stuff
This commit is contained in:
1 parent
9203f269db
commit
f40a10912c
78 files changed
+12768
-309066
No files matched your search
@@ -0,0 +1,182 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public class EntityPreviewVisuals : MonoBehaviour
|
||||
{
|
||||
[Header("Материалы для MKToon")]
|
||||
[Tooltip("Полупрозрачный материал для статичного слепка в точке снапшота")]
|
||||
[SerializeField]
|
||||
private Material snapshotGhostMaterial;
|
||||
|
||||
[Tooltip(
|
||||
"Опционально: материал для самой движущейся модели во время превью (если null — меняется только Rim/Tint)")]
|
||||
[SerializeField]
|
||||
private Material activePreviewMaterial;
|
||||
|
||||
[Header("Настройки подсветки MKToon (если activePreviewMaterial не задан)")] [SerializeField]
|
||||
private Color previewAlbedoTint = new Color(0.6f, 0.85f, 1f, 1f);
|
||||
|
||||
[SerializeField, ColorUsage(true, true)]
|
||||
private Color previewRimColor = new Color(0f, 1.5f, 2f, 1f);
|
||||
|
||||
private SkinnedMeshRenderer[] _skinnedRenderers;
|
||||
private MeshRenderer[] _meshRenderers;
|
||||
|
||||
// Кэш оригинальных материалов
|
||||
private readonly Dictionary<Renderer, Material[]> _originalMaterials = new();
|
||||
|
||||
// Пул объектов для статичного слепка (чтобы не делать Instantiate и new Mesh каждый цикл превью)
|
||||
private GameObject _snapshotGhostRoot;
|
||||
private readonly List<Mesh> _bakedMeshes = new();
|
||||
private readonly List<MeshFilter> _ghostMeshFilters = new();
|
||||
private readonly List<MeshRenderer> _ghostMeshRenderers = new();
|
||||
|
||||
private MaterialPropertyBlock _propBlock;
|
||||
private static readonly int AlbedoColorId = Shader.PropertyToID("_AlbedoColor");
|
||||
private static readonly int RimColorId = Shader.PropertyToID("_RimColor");
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_propBlock = new MaterialPropertyBlock();
|
||||
_skinnedRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
_meshRenderers = GetComponentsInChildren<MeshRenderer>();
|
||||
|
||||
// Сохраняем родные материалы MKToon
|
||||
foreach (var sr in _skinnedRenderers)
|
||||
_originalMaterials[sr] = sr.sharedMaterials;
|
||||
|
||||
foreach (var mr in _meshRenderers)
|
||||
_originalMaterials[mr] = mr.sharedMaterials;
|
||||
|
||||
InitializeGhostPool();
|
||||
}
|
||||
|
||||
private void InitializeGhostPool()
|
||||
{
|
||||
_snapshotGhostRoot = new GameObject($"{gameObject.name}_SnapshotGhost");
|
||||
_snapshotGhostRoot.SetActive(false);
|
||||
|
||||
// Создаём легковесные меш-объекты под каждый SkinnedMeshRenderer персонажа
|
||||
for (int i = 0; i < _skinnedRenderers.Length; i++)
|
||||
{
|
||||
var partGo = new GameObject($"GhostPart_{i}");
|
||||
partGo.transform.SetParent(_snapshotGhostRoot.transform, false);
|
||||
|
||||
var mf = partGo.AddComponent<MeshFilter>();
|
||||
var mr = partGo.AddComponent<MeshRenderer>();
|
||||
|
||||
// Отключаем тени у призрака для чистоты картинки и производительности
|
||||
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
|
||||
var bakedMesh = new Mesh { name = $"BakedGhostMesh_{i}" };
|
||||
mf.sharedMesh = bakedMesh;
|
||||
|
||||
_bakedMeshes.Add(bakedMesh);
|
||||
_ghostMeshFilters.Add(mf);
|
||||
_ghostMeshRenderers.Add(mr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается перед стартом цикла PreviewBattleLoopAsync (в момент снятия снапшота)
|
||||
/// </summary>
|
||||
public void ShowPreviewState(bool spawnStaticSnapshotGhost = true)
|
||||
{
|
||||
// 1. Запекаем полупрозрачную статую в текущей точке снапшота
|
||||
if (spawnStaticSnapshotGhost && snapshotGhostMaterial != null)
|
||||
{
|
||||
_snapshotGhostRoot.transform.SetPositionAndRotation(transform.position, transform.rotation);
|
||||
|
||||
for (int i = 0; i < _skinnedRenderers.Length; i++)
|
||||
{
|
||||
var sr = _skinnedRenderers[i];
|
||||
sr.BakeMesh(_bakedMeshes[i]); // Мгновенный снимок позы скелета!
|
||||
|
||||
var ghostTransform = _ghostMeshFilters[i].transform;
|
||||
ghostTransform.SetPositionAndRotation(sr.transform.position, sr.transform.rotation);
|
||||
// ghostTransform.localScale = sr.transform.lossyScale;
|
||||
|
||||
// Назначаем прозрачный материал на все саб-меши
|
||||
int subMeshCount = _bakedMeshes[i].subMeshCount;
|
||||
Material[] ghostMats = new Material[Mathf.Max(1, subMeshCount)];
|
||||
for (int m = 0; m < ghostMats.Length; m++)
|
||||
ghostMats[m] = snapshotGhostMaterial;
|
||||
|
||||
_ghostMeshRenderers[i].sharedMaterials = ghostMats;
|
||||
}
|
||||
|
||||
_snapshotGhostRoot.SetActive(true);
|
||||
}
|
||||
|
||||
// 2. Меняем внешний вид самой модели, которая сейчас побежит в превью
|
||||
ApplyPreviewLookToMainModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается в блоке finally в PreviewBattleLoopAsync
|
||||
/// </summary>
|
||||
public void HidePreviewState()
|
||||
{
|
||||
if (_snapshotGhostRoot != null)
|
||||
_snapshotGhostRoot.SetActive(false);
|
||||
|
||||
RestoreMainModelLook();
|
||||
}
|
||||
|
||||
private void ApplyPreviewLookToMainModel()
|
||||
{
|
||||
foreach (var (renderer, origMats) in _originalMaterials)
|
||||
{
|
||||
if (renderer == null) continue;
|
||||
|
||||
// Способ 1: Если задан отдельный материал для превью — подменяем sharedMaterials
|
||||
if (activePreviewMaterial != null)
|
||||
{
|
||||
Material[] previewMats = new Material[origMats.Length];
|
||||
for (int i = 0; i < previewMats.Length; i++)
|
||||
previewMats[i] = activePreviewMaterial;
|
||||
|
||||
renderer.sharedMaterials = previewMats;
|
||||
}
|
||||
// Способ 2: Иначе подкрашиваем родной MKToon через MaterialPropertyBlock (без аллокаций)
|
||||
else
|
||||
{
|
||||
renderer.GetPropertyBlock(_propBlock);
|
||||
_propBlock.SetColor(AlbedoColorId, previewAlbedoTint);
|
||||
_propBlock.SetColor(RimColorId, previewRimColor);
|
||||
renderer.SetPropertyBlock(_propBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreMainModelLook()
|
||||
{
|
||||
foreach (var (renderer, origMats) in _originalMaterials)
|
||||
{
|
||||
if (renderer == null) continue;
|
||||
|
||||
if (activePreviewMaterial != null)
|
||||
{
|
||||
renderer.sharedMaterials = origMats;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.SetPropertyBlock(null); // Сбрасывает все переопределения PropertyBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
foreach (var mesh in _bakedMeshes)
|
||||
{
|
||||
if (mesh != null) Destroy(mesh);
|
||||
}
|
||||
|
||||
if (_snapshotGhostRoot != null)
|
||||
Destroy(_snapshotGhostRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97e5f578c2c24bb7b3bd55d2b3ade97a
|
||||
timeCreated: 1793990043
|
||||
@@ -0,0 +1,18 @@
|
||||
using Code.Game.Gameplay.Player;
|
||||
using Code.OfficeGame.BattleMode.HitBoxes;
|
||||
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;
|
||||
public EntityHitBoxes EntityHitBoxes;
|
||||
public EntityPreviewVisuals EntityPreviewVisuals;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e4a996f851e47ca8fc906e7e89fdca5
|
||||
timeCreated: 1793291498
|
||||
@@ -0,0 +1,173 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public struct EntitySnapshot
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public AnimatorSnapshot AnimatorSnapshot;
|
||||
|
||||
public static EntitySnapshot GetSnapshot(EntityProperties entityProperties)
|
||||
{
|
||||
return new EntitySnapshot
|
||||
{
|
||||
Position = entityProperties.KinematicCharacterMotor.TransientPosition,
|
||||
Rotation = entityProperties.KinematicCharacterMotor.TransientRotation,
|
||||
AnimatorSnapshot = AnimatorSnapshot.GetSnapshot(entityProperties.animator)
|
||||
};
|
||||
}
|
||||
|
||||
public static void RestoreSnapshot(EntityProperties entityProperties, EntitySnapshot snapshot)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
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;
|
||||
|
||||
Transform animTransform = animator.transform;
|
||||
_localPosition = animTransform.localPosition;
|
||||
_localRotation = animTransform.localRotation;
|
||||
_speed = animator.speed;
|
||||
|
||||
int layerCount = animator.layerCount;
|
||||
if (_layers == null || _layers.Length != layerCount)
|
||||
_layers = new LayerStateSnapshot[layerCount];
|
||||
|
||||
for (int i = 0; i < layerCount; i++)
|
||||
{
|
||||
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(i);
|
||||
_layers[i] = new LayerStateSnapshot
|
||||
{
|
||||
FullPathHash = stateInfo.fullPathHash,
|
||||
NormalizedTime = stateInfo.normalizedTime,
|
||||
LayerWeight = animator.GetLayerWeight(i)
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
targetAnimator.Rebind();
|
||||
|
||||
// Отключаем applyRootMotion на время Update(0f), чтобы Play() не начислил дельту смещения
|
||||
bool prevRootMotion = targetAnimator.applyRootMotion;
|
||||
targetAnimator.applyRootMotion = false;
|
||||
targetAnimator.speed = _speed;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
targetAnimator.Update(0f);
|
||||
|
||||
// ОБЯЗАТЕЛЬНО восстанавливаем сохраненные локальные координаты меша после Update(0f)!
|
||||
Transform targetTransform = targetAnimator.transform;
|
||||
targetTransform.localPosition = _localPosition;
|
||||
targetTransform.localRotation = _localRotation;
|
||||
|
||||
targetAnimator.applyRootMotion = prevRootMotion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b014c8950464495f9b5022a81d6f5e2e
|
||||
timeCreated: 1793381535
|
||||
Reference in new issue
Block a user