창 올라타기
This commit is contained in:
111
Assets/02_Scripts/Character/CharacterCameraFit.cs
Normal file
111
Assets/02_Scripts/Character/CharacterCameraFit.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 카메라를 직교 투영으로 바꾸고, 불러온 캐릭터 크기에 맞춰 자동으로 구도를 잡는다.
|
||||
///
|
||||
/// 왜 직교 투영인가:
|
||||
/// 원근 투영에서는 화면 중앙을 벗어날수록 캐릭터를 비스듬히 옆에서 보게 되어
|
||||
/// 형태가 왜곡된다. 데스크톱 캐릭터는 화면 어디로든 이동하므로 이 왜곡이 그대로
|
||||
/// 드러난다. 직교 투영은 위치와 무관하게 항상 같은 각도로 보인다.
|
||||
///
|
||||
/// 왜 자동인가:
|
||||
/// 사용자가 임의의 VRM 을 넣는 구조라 캐릭터 키를 미리 알 수 없다. 모델마다
|
||||
/// 직교 크기를 손으로 맞추면 모델을 바꿀 때마다 다시 맞춰야 한다.
|
||||
/// </summary>
|
||||
public class CharacterCameraFit : MonoBehaviour
|
||||
{
|
||||
[Header("참조")]
|
||||
[Tooltip("비우면 같은 오브젝트의 Camera 또는 Camera.main")]
|
||||
[SerializeField] Camera targetCamera;
|
||||
|
||||
[Tooltip("비우면 씬에서 탐색")]
|
||||
[SerializeField] VrmCharacterLoader loader;
|
||||
|
||||
[Header("구도")]
|
||||
[Tooltip("직교 투영으로 강제한다. 끄면 현재 투영 방식을 유지한 채 크기만 맞춘다")]
|
||||
[SerializeField] bool forceOrthographic = true;
|
||||
|
||||
[Tooltip("캐릭터가 화면 높이에서 차지할 비율. 0.30면 화면의 30%")]
|
||||
[Range(0.1f, 1f)]
|
||||
[SerializeField] float screenHeightFraction = 0.30f;
|
||||
|
||||
[Tooltip("캐릭터가 처음에 화면 중앙에 오도록 카메라 위치도 맞춘다")]
|
||||
[SerializeField] bool centerOnCharacter = true;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (targetCamera == null) targetCamera = GetComponent<Camera>();
|
||||
if (targetCamera == null) targetCamera = Camera.main;
|
||||
if (loader == null) loader = FindFirstObjectByType<VrmCharacterLoader>();
|
||||
|
||||
if (loader != null) loader.Loaded += OnCharacterLoaded;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (loader != null) loader.Loaded -= OnCharacterLoaded;
|
||||
}
|
||||
|
||||
void OnCharacterLoaded(ICharacterAvatar avatar)
|
||||
{
|
||||
if (avatar != null) Fit(avatar.Root);
|
||||
}
|
||||
|
||||
/// <summary>캐릭터를 감싸도록 카메라 크기와 위치를 맞춘다.</summary>
|
||||
public void Fit(Transform character)
|
||||
{
|
||||
if (targetCamera == null || character == null) return;
|
||||
|
||||
var renderers = character.GetComponentsInChildren<Renderer>(true);
|
||||
if (renderers.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[CharacterCameraFit] 렌더러가 없어 구도를 잡지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
Bounds bounds = renderers[0].bounds;
|
||||
for (int i = 1; i < renderers.Length; i++) bounds.Encapsulate(renderers[i].bounds);
|
||||
|
||||
float characterHeight = Mathf.Max(0.01f, bounds.size.y);
|
||||
|
||||
if (forceOrthographic) targetCamera.orthographic = true;
|
||||
|
||||
if (targetCamera.orthographic)
|
||||
{
|
||||
// orthographicSize 는 화면에 보이는 세계 높이의 절반이다.
|
||||
// 보이는 높이 = 캐릭터 키 / 비율 이므로, 그 절반이 필요한 값이다.
|
||||
targetCamera.orthographicSize = characterHeight / (2f * screenHeightFraction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 원근이면 거리로 맞춘다. 왜곡은 남으므로 권장하지 않는다.
|
||||
float visibleHeight = characterHeight / screenHeightFraction;
|
||||
float halfFovRad = targetCamera.fieldOfView * 0.5f * Mathf.Deg2Rad;
|
||||
float distance = visibleHeight * 0.5f / Mathf.Tan(halfFovRad);
|
||||
|
||||
Vector3 dir = targetCamera.transform.forward;
|
||||
targetCamera.transform.position = bounds.center - dir * distance;
|
||||
}
|
||||
|
||||
if (centerOnCharacter)
|
||||
{
|
||||
// 카메라의 시선 방향(깊이)은 유지하고, 화면상 중앙만 캐릭터에 맞춘다.
|
||||
Vector3 camPos = targetCamera.transform.position;
|
||||
Vector3 toCharacter = bounds.center - camPos;
|
||||
Vector3 forward = targetCamera.transform.forward;
|
||||
|
||||
// 깊이 성분만 남기고 나머지를 상쇄해 캐릭터가 화면 중앙에 오게 한다.
|
||||
Vector3 depthOnly = Vector3.Project(toCharacter, forward);
|
||||
targetCamera.transform.position = bounds.center - depthOnly;
|
||||
}
|
||||
|
||||
// 직교 투영에서 near 가 캐릭터보다 앞에 있으면 잘려 보인다.
|
||||
if (targetCamera.orthographic && targetCamera.nearClipPlane > 0.05f)
|
||||
{
|
||||
targetCamera.nearClipPlane = 0.05f;
|
||||
}
|
||||
|
||||
Debug.Log($"[CharacterCameraFit] 구도 적용. 캐릭터 키 {characterHeight:F2}m, " +
|
||||
$"직교={targetCamera.orthographic}, size={targetCamera.orthographicSize:F2}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user