171 lines
7.5 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|