182 lines
7.7 KiB
C#
182 lines
7.7 KiB
C#
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);
|
|
}
|
|
}
|
|
} |