Files
test/Assets/PerfectWorld/Scripts/UI/ElsePlayerPortraitCapture.cs
T

171 lines
7.5 KiB
C#

using BrewMonster.Scripts.Managers;
using UnityEngine;
namespace BrewMonster
{
/// <summary>
/// Renders an EC_ElsePlayer portrait into a RenderTexture for the target HUD (HUDNPC).
///
/// Instead of following the live player transform every frame (which causes jitter when
/// the target moves/jumps), this system clones the visual model into an isolated
/// "portrait stage" at a fixed world position. The portrait camera only ever looks at
/// the static clone, so the output is perfectly stable.
///
/// Public API (unchanged — CECUIManager needs no edits):
/// SetTarget(Transform playerRoot) — clone model, start rendering
/// ClearTarget() — destroy clone, stop rendering
/// OutputTexture — wire to RawImage.texture
/// </summary>
public class ElsePlayerPortraitCapture : MonoSingleton<ElsePlayerPortraitCapture>
{
[Header("Portrait Camera")]
[SerializeField] private Camera portraitCamera;
[Header("Render Output")]
[SerializeField] private RenderTexture outputTexture;
[Header("Portrait Stage")]
[Tooltip("Layer index for the Portrait layer (Project Settings > Tags and Layers).")]
[SerializeField] private int portraitLayer = 29;
[Tooltip("Fixed world position of the clone. Keep far from gameplay to avoid overlap.")]
[SerializeField] private Vector3 stagePosition = new Vector3(9999f, 0f, 0f);
[Header("Framing")]
[Tooltip("Distance from head focus point to camera.")]
[SerializeField] private float cameraDistance = 1.2f;
[Tooltip("Height offset above stage origin used when head bone cannot be found.")]
[SerializeField] private float fallbackHeadHeight = 1.6f;
[Tooltip("Slight horizontal angle so the portrait isn't perfectly front-on (degrees).")]
[SerializeField][Range(0f, 45f)] private float horizontalAngleDeg = 15f;
[SerializeField][Range(10f, 60f)] private float fieldOfView = 35f;
// ── runtime state ────────────────────────────────────────────────────────────
private GameObject _portraitClone;
private Transform _headBone;
public RenderTexture OutputTexture => outputTexture;
// ──────────────────────────────────────────────────────────────────────────────
// Lifecycle
// ──────────────────────────────────────────────────────────────────────────────
protected override void Initialize()
{
EnsureCamera();
EnsureRenderTexture();
}
protected override void OnDestroy()
{
base.OnDestroy();
ClearTarget();
if (outputTexture != null && outputTexture.IsCreated())
outputTexture.Release();
}
// ──────────────────────────────────────────────────────────────────────────────
// Public API
// ──────────────────────────────────────────────────────────────────────────────
/// <summary>
/// Clones the visual model of <paramref name="playerRoot"/> into the portrait stage
/// and starts rendering. Safe to call multiple times — old clone is destroyed first.
/// </summary>
public void SetTarget(Transform playerRoot)
{
ClearTarget();
if (playerRoot == null) return;
Transform modelRoot = PortraitCaptureUtils.FindVisualModelRoot(playerRoot);
if (modelRoot == null)
{
BMLogger.LogWarning("[ElsePlayerPortraitCapture] Visual model root not found — portrait skipped.");
return;
}
_portraitClone = Object.Instantiate(modelRoot.gameObject);
_portraitClone.name = "[Portrait] ElsePlayer";
_portraitClone.transform.position = stagePosition;
_portraitClone.transform.rotation = Quaternion.identity;
// Strip physics / game-logic components; keep Animator for idle animation
PortraitCaptureUtils.CleanupCloneComponents(_portraitClone);
// Hide from main camera — portrait camera sees only this layer
PortraitCaptureUtils.SetLayerRecursive(_portraitClone, portraitLayer);
_headBone = PortraitCaptureUtils.FindHeadBone(_portraitClone.transform);
PositionCamera();
SetCameraEnabled(true);
}
/// <summary>
/// Destroys the portrait clone and disables the camera.
/// Call from OnTargetHUDClear / TryHideUINPC.
/// </summary>
public void ClearTarget()
{
if (_portraitClone != null)
{
Object.Destroy(_portraitClone);
_portraitClone = null;
}
_headBone = null;
SetCameraEnabled(false);
}
// ──────────────────────────────────────────────────────────────────────────────
// Internal
// ──────────────────────────────────────────────────────────────────────────────
private void PositionCamera()
{
if (portraitCamera == null) return;
Vector3 focusPoint = _headBone != null
? _headBone.position
: stagePosition + Vector3.up * fallbackHeadHeight;
float rad = horizontalAngleDeg * Mathf.Deg2Rad;
Vector3 offset = new Vector3(Mathf.Sin(rad) * cameraDistance, 0f, -cameraDistance);
portraitCamera.transform.position = focusPoint + offset;
portraitCamera.transform.LookAt(focusPoint);
}
private void SetCameraEnabled(bool enabled)
{
if (portraitCamera != null)
portraitCamera.enabled = enabled;
}
private void EnsureCamera()
{
if (portraitCamera != null)
{
portraitCamera.cullingMask = 1 << portraitLayer;
portraitCamera.fieldOfView = fieldOfView;
portraitCamera.enabled = false;
return;
}
portraitCamera = PortraitCaptureUtils.CreatePortraitCamera(
transform, "PortraitCamera_ElsePlayer", portraitLayer, fieldOfView);
}
private void EnsureRenderTexture()
{
if (outputTexture != null)
{
if (portraitCamera != null) portraitCamera.targetTexture = outputTexture;
return;
}
outputTexture = PortraitCaptureUtils.CreatePortraitRT("ElsePlayerPortraitRT", portraitCamera);
}
}
}