오류수정
This commit is contained in:
@@ -19,6 +19,7 @@ public class CharacterMotionDirector : MonoBehaviour
|
||||
[Header("참조 (비우면 씬에서 탐색)")]
|
||||
[SerializeField] VrmCharacterLoader loader;
|
||||
[SerializeField] WindowClimber climber;
|
||||
[SerializeField] CharacterDragger dragger;
|
||||
|
||||
[Header("애니메이션")]
|
||||
[Tooltip("Humanoid 클립을 담은 Animator Controller. 비우면 ProceduralIdle 로 동작한다")]
|
||||
@@ -39,28 +40,32 @@ public class CharacterMotionDirector : MonoBehaviour
|
||||
[SerializeField] string sitState = "Sit";
|
||||
[SerializeField] string lieDownState = "LieDown";
|
||||
|
||||
[Header("쉬는 자세")]
|
||||
[Tooltip("발판에 선 뒤 자세를 바꾸기까지 기다리는 시간의 최소/최대(초). " +
|
||||
"매번 다르게 해야 기계적으로 보이지 않는다")]
|
||||
[SerializeField] float restDelayMin = 5f;
|
||||
[SerializeField] float restDelayMax = 14f;
|
||||
[Header("포즈 (캐릭터를 우클릭하면 목록이 뜬다)")]
|
||||
[Tooltip("비우면 씬에서 탐색. 없으면 우클릭이 다음 포즈로 넘기는 방식으로 대체된다")]
|
||||
[SerializeField] PoseMenuUI poseMenu;
|
||||
|
||||
[Tooltip("서 있을 때 고를 자세들. Idle 을 섞어둬야 계속 누워만 있지 않는다")]
|
||||
[Tooltip("우클릭 목록에 나올 포즈들. 첫 항목이 기본 자세이고, " +
|
||||
"걷거나 떨어졌다 돌아오면 항상 첫 항목으로 되돌아온다. " +
|
||||
"각 항목의 Hide Behind Platform 을 켜면 그 포즈일 때 발판 아래가 가려진다")]
|
||||
[SerializeField]
|
||||
CharacterMotionKind[] restPoses =
|
||||
PoseOption[] poses =
|
||||
{
|
||||
CharacterMotionKind.Idle,
|
||||
CharacterMotionKind.Sit,
|
||||
CharacterMotionKind.LieDown,
|
||||
new PoseOption { kind = CharacterMotionKind.Idle },
|
||||
new PoseOption { kind = CharacterMotionKind.Sit },
|
||||
new PoseOption { kind = CharacterMotionKind.LieDown },
|
||||
};
|
||||
|
||||
[Header("발판 뒤로 숨기기")]
|
||||
[Tooltip("비우면 씬에서 탐색. 없으면 가림 기능만 조용히 꺼진다")]
|
||||
[SerializeField] PlatformOccluder occluder;
|
||||
|
||||
[Header("동작")]
|
||||
[Tooltip("클립이 준비되면 ProceduralIdle 을 끈다. 둘 다 같은 본을 써서 함께 두면 싸운다")]
|
||||
[SerializeField] bool disableProceduralIdle = true;
|
||||
|
||||
ICharacterMotion motion;
|
||||
ClimbState currentState = ClimbState.Falling;
|
||||
float nextRestChangeTime;
|
||||
int poseIndex;
|
||||
|
||||
Dictionary<CharacterMotionKind, string> BuildNames() => new Dictionary<CharacterMotionKind, string>
|
||||
{
|
||||
@@ -76,6 +81,27 @@ void Awake()
|
||||
{
|
||||
if (loader == null) loader = FindFirstObjectByType<VrmCharacterLoader>();
|
||||
if (climber == null) climber = FindFirstObjectByType<WindowClimber>();
|
||||
if (dragger == null) dragger = FindFirstObjectByType<CharacterDragger>();
|
||||
if (occluder == null) occluder = FindFirstObjectByType<PlatformOccluder>();
|
||||
if (poseMenu == null) poseMenu = FindFirstObjectByType<PoseMenuUI>();
|
||||
|
||||
WarnIfOccluderMissing();
|
||||
}
|
||||
|
||||
/// <summary>가림을 켜둔 포즈가 있는데 가림 컴포넌트가 없으면, 조용히 안 되는 대신 알린다.</summary>
|
||||
void WarnIfOccluderMissing()
|
||||
{
|
||||
if (poses == null || occluder != null) return;
|
||||
|
||||
foreach (var pose in poses)
|
||||
{
|
||||
if (!pose.hideBehindPlatform) continue;
|
||||
|
||||
Debug.LogWarning($"[CharacterMotionDirector] '{pose.kind}' 에 발판 가림이 켜져 있지만 " +
|
||||
"씬에 PlatformOccluder 가 없어 동작하지 않습니다. " +
|
||||
"메뉴 10 을 다시 실행하세요.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
@@ -92,25 +118,51 @@ void OnEnable()
|
||||
climber.StateChanged += OnStateChanged;
|
||||
currentState = climber.State;
|
||||
}
|
||||
|
||||
if (dragger != null) dragger.RightClicked += OnRightClicked;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (loader != null) loader.Loaded -= OnCharacterLoaded;
|
||||
if (climber != null) climber.StateChanged -= OnStateChanged;
|
||||
if (dragger != null) dragger.RightClicked -= OnRightClicked;
|
||||
}
|
||||
|
||||
void Update()
|
||||
/// <summary>
|
||||
/// 캐릭터를 우클릭하면 포즈 목록을 띄운다.
|
||||
///
|
||||
/// 서 있을 때만 받는다. 걷거나 떨어지는 중에 눕힐 수는 없다.
|
||||
/// </summary>
|
||||
void OnRightClicked()
|
||||
{
|
||||
// 서 있을 때만 자세를 바꾼다. 걷거나 떨어지는 중에 눕게 할 수는 없다.
|
||||
if (motion == null || currentState != ClimbState.Standing) return;
|
||||
if (Time.unscaledTime < nextRestChangeTime) return;
|
||||
if (poses == null || poses.Length == 0) return;
|
||||
|
||||
ScheduleNextRestChange();
|
||||
|
||||
if (restPoses != null && restPoses.Length > 0)
|
||||
if (poseMenu == null)
|
||||
{
|
||||
motion.Play(restPoses[Random.Range(0, restPoses.Length)]);
|
||||
// 메뉴가 없으면 최소한 다음 포즈로라도 넘어간다. 아무 반응이 없는 것보다 낫다.
|
||||
poseIndex = (poseIndex + 1) % poses.Length;
|
||||
PlayPose(poses[poseIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (poseMenu.IsOpen)
|
||||
{
|
||||
poseMenu.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DesktopCursor.TryGetScreenPosition(CursorHwnd, out Vector2 cursor)) return;
|
||||
poseMenu.Show(cursor, poses, PlayPose);
|
||||
}
|
||||
|
||||
System.IntPtr CursorHwnd
|
||||
{
|
||||
get
|
||||
{
|
||||
var window = FindFirstObjectByType<TransparentWindow>();
|
||||
return window != null ? window.Hwnd : System.IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,24 +197,51 @@ void OnCharacterLoaded(ICharacterAvatar avatar)
|
||||
}
|
||||
|
||||
currentState = climber != null ? climber.State : ClimbState.Standing;
|
||||
motion.Play(ToMotionKind(currentState));
|
||||
ScheduleNextRestChange();
|
||||
poseIndex = 0;
|
||||
ApplyState(currentState);
|
||||
}
|
||||
|
||||
void OnStateChanged(ClimbState state)
|
||||
{
|
||||
currentState = state;
|
||||
motion?.Play(ToMotionKind(state));
|
||||
|
||||
// 서기 시작한 시점부터 다시 센다. 착지하자마자 눕지 않게.
|
||||
if (state == ClimbState.Standing) ScheduleNextRestChange();
|
||||
// 움직이기 시작하면 고른 포즈는 버린다. 걷다가 다시 서면 기본 자세부터.
|
||||
poseIndex = 0;
|
||||
ApplyState(state);
|
||||
}
|
||||
|
||||
void ScheduleNextRestChange()
|
||||
/// <summary>
|
||||
/// 이동 상태에 맞는 몸짓으로 간다. 서 있는 상태만 "포즈"로 취급해
|
||||
/// 목록의 첫 항목(가림 설정 포함)을 쓰고, 나머지는 그냥 이동 몸짓이다.
|
||||
/// </summary>
|
||||
void ApplyState(ClimbState state)
|
||||
{
|
||||
float min = Mathf.Max(0.5f, restDelayMin);
|
||||
float max = Mathf.Max(min, restDelayMax);
|
||||
nextRestChangeTime = Time.unscaledTime + Random.Range(min, max);
|
||||
if (state == ClimbState.Standing) PlayDefaultPose();
|
||||
else PlayMotion(ToMotionKind(state));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 고른 포즈를 재생하고, 그 포즈의 가림 설정도 함께 반영한다.
|
||||
/// 포즈를 바꾸는 곳이 여러 군데라 한 곳으로 모은다 — 따로 두면 가림만 남는 사고가 난다.
|
||||
/// </summary>
|
||||
void PlayPose(PoseOption pose)
|
||||
{
|
||||
motion?.Play(pose.kind);
|
||||
if (occluder != null) occluder.Active = pose.hideBehindPlatform;
|
||||
}
|
||||
|
||||
/// <summary>이동 중의 몸짓. 걷거나 떨어지는 동안에는 가리지 않는다.</summary>
|
||||
void PlayMotion(CharacterMotionKind kind)
|
||||
{
|
||||
motion?.Play(kind);
|
||||
if (occluder != null) occluder.Active = false;
|
||||
}
|
||||
|
||||
/// <summary>발판에 섰을 때의 기본 자세. 목록의 첫 항목이다.</summary>
|
||||
void PlayDefaultPose()
|
||||
{
|
||||
if (poses != null && poses.Length > 0) PlayPose(poses[0]);
|
||||
else PlayMotion(CharacterMotionKind.Idle);
|
||||
}
|
||||
|
||||
static CharacterMotionKind ToMotionKind(ClimbState state)
|
||||
|
||||
Reference in New Issue
Block a user