오류수정
This commit is contained in:
@@ -43,6 +43,8 @@ public class WindowClimber : MonoBehaviour
|
||||
[SerializeField] float walkSpeed = 160f;
|
||||
|
||||
[Header("자동 올라타기")]
|
||||
[Tooltip("스스로 창을 찾아 올라간다. 런타임에도 끌 수 있다 " +
|
||||
"(트레이 아이콘 우클릭 메뉴, 또는 에디터에서는 이 체크박스)")]
|
||||
[SerializeField] bool autoClimb = true;
|
||||
|
||||
[Tooltip("바닥에 있을 때만 올라탄다. 이미 창 위에 있으면 다른 창으로 옮기지 않는다")]
|
||||
@@ -85,6 +87,21 @@ public class WindowClimber : MonoBehaviour
|
||||
[Tooltip("화면 위쪽 여백. 캐릭터 머리가 이보다 위로 가면 발판에서 뛰어내린다")]
|
||||
[SerializeField] float ceilingMargin = 8f;
|
||||
|
||||
[Header("방향 전환")]
|
||||
[Tooltip("걷고 뛰는 방향으로 몸을 돌린다. 끄면 늘 정면을 본 채 옆으로 미끄러진다")]
|
||||
[SerializeField] bool turnToWalkDirection = true;
|
||||
|
||||
[Tooltip("얼마나 돌릴지(도). 90 이면 완전히 옆모습이라 얼굴이 안 보인다. " +
|
||||
"70 전후가 걷는 티는 나면서 얼굴도 살짝 보인다")]
|
||||
[Range(0f, 90f)]
|
||||
[SerializeField] float walkTurnAngle = 70f;
|
||||
|
||||
[Tooltip("도는 속도(도/초). 낮추면 천천히 돌아선다")]
|
||||
[SerializeField] float turnSpeed = 540f;
|
||||
|
||||
[Tooltip("도는 쪽이 반대라면 켠다. 모델의 정면 축에 따라 달라진다")]
|
||||
[SerializeField] bool invertTurnDirection = false;
|
||||
|
||||
[Header("기타")]
|
||||
[Tooltip("착지 판정 여유. 발판을 살짝 지나쳐도 잡아준다")]
|
||||
[SerializeField] float landTolerance = 24f;
|
||||
@@ -111,6 +128,34 @@ private set
|
||||
/// <summary>이동 상태가 바뀔 때. 애니메이션 전환이 이걸 듣는다.</summary>
|
||||
public event System.Action<ClimbState> StateChanged;
|
||||
|
||||
const string AutoClimbPrefKey = "MyCharacterAgent.AutoClimb";
|
||||
|
||||
/// <summary>
|
||||
/// 스스로 창을 찾아 올라갈지. 런타임에 바꿀 수 있고 다음 실행에도 유지된다.
|
||||
///
|
||||
/// 저장은 PlayerPrefs 로 한다. 모델 경로와 같은 방식이고, 채팅 설정 파일에
|
||||
/// 캐릭터 동작을 섞지 않으려는 이유도 있다.
|
||||
///
|
||||
/// 끄더라도 진행 중인 이동은 끝까지 간다. 공중에서 갑자기 멈추면 더 어색하다.
|
||||
/// </summary>
|
||||
public bool AutoClimb
|
||||
{
|
||||
get => autoClimb;
|
||||
set
|
||||
{
|
||||
if (autoClimb == value) return;
|
||||
autoClimb = value;
|
||||
|
||||
PlayerPrefs.SetInt(AutoClimbPrefKey, value ? 1 : 0);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
AutoClimbChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>자동 올라타기 설정이 바뀔 때. 트레이 메뉴 표시 갱신 등에 쓴다.</summary>
|
||||
public event System.Action<bool> AutoClimbChanged;
|
||||
|
||||
Transform character;
|
||||
float depth; // 카메라로부터의 깊이. 드래그와 같은 평면을 유지한다.
|
||||
Vector2 screenPos; // 캐릭터 발밑의 화면 좌표
|
||||
@@ -120,6 +165,16 @@ private set
|
||||
System.IntPtr standingHwnd;
|
||||
float offsetFromWindowLeft;
|
||||
bool hasStanding;
|
||||
DesktopPlatform standingPlatform;
|
||||
|
||||
/// <summary>
|
||||
/// 지금 서 있는 발판. 서 있지 않으면 null.
|
||||
/// 발판 아래를 가리는 처리(PlatformOccluder)가 이 사각형을 쓴다.
|
||||
/// </summary>
|
||||
public DesktopPlatform? CurrentPlatform => hasStanding ? standingPlatform : (DesktopPlatform?)null;
|
||||
|
||||
/// <summary>카메라로부터 캐릭터까지의 깊이. 가림 사각형을 같은 평면 근처에 두는 데 쓴다.</summary>
|
||||
public float CharacterDepth => depth;
|
||||
|
||||
// 목표
|
||||
float walkTargetX;
|
||||
@@ -134,6 +189,10 @@ private set
|
||||
Vector2 charOffMin, charOffMax;
|
||||
bool hasExtents;
|
||||
|
||||
// 로더가 정한 "정면" 자세. 걷는 방향 회전은 여기에 더해서 얹는다.
|
||||
Quaternion baseRotation = Quaternion.identity;
|
||||
float currentTurn;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (scanner == null) scanner = FindFirstObjectByType<DesktopPlatformScanner>();
|
||||
@@ -141,6 +200,12 @@ void Awake()
|
||||
if (loader == null) loader = FindFirstObjectByType<VrmCharacterLoader>();
|
||||
if (viewCamera == null) viewCamera = Camera.main;
|
||||
|
||||
// 저장된 값이 있으면 그걸 쓰고, 없으면 인스펙터 기본값을 그대로 둔다.
|
||||
if (PlayerPrefs.HasKey(AutoClimbPrefKey))
|
||||
{
|
||||
autoClimb = PlayerPrefs.GetInt(AutoClimbPrefKey, 1) != 0;
|
||||
}
|
||||
|
||||
if (loader != null) loader.Loaded += OnCharacterLoaded;
|
||||
if (scanner != null) scanner.Rescanned += OnRescanned;
|
||||
if (dragger != null) dragger.DragEnded += OnDragEnded;
|
||||
@@ -164,6 +229,11 @@ void OnCharacterLoaded(ICharacterAvatar avatar)
|
||||
|
||||
hasExtents = CharacterScreenBounds.TryMeasure(viewCamera, character,
|
||||
out charOffMin, out charOffMax);
|
||||
|
||||
// 로더가 걸어둔 회전이 "정면"이다. 여기서 잡아둬야 걷기 회전을 얹었다 뺄 수 있다.
|
||||
baseRotation = character.localRotation;
|
||||
currentTurn = 0f;
|
||||
|
||||
EnterFalling();
|
||||
}
|
||||
|
||||
@@ -255,6 +325,7 @@ void Land(DesktopPlatform p)
|
||||
|
||||
standingHwnd = p.Hwnd;
|
||||
offsetFromWindowLeft = screenPos.x - p.WindowLeft;
|
||||
standingPlatform = p;
|
||||
hasStanding = true;
|
||||
|
||||
State = ClimbState.Standing;
|
||||
@@ -383,6 +454,9 @@ void UpdateRiding()
|
||||
|
||||
var m = match.Value;
|
||||
|
||||
// 창이 움직였으면 발판 사각형도 갱신한다. 가림 처리가 이 값을 본다.
|
||||
standingPlatform = m;
|
||||
|
||||
// 창을 위로 계속 끌어올리면 캐릭터가 화면 밖으로 밀려난다. 그전에 뛰어내린다.
|
||||
if (WouldBeClipped(m.Y))
|
||||
{
|
||||
@@ -465,5 +539,41 @@ void Apply()
|
||||
|
||||
character.position = viewCamera.ScreenToWorldPoint(
|
||||
new Vector3(screenPos.x, screenPos.y, depth));
|
||||
|
||||
ApplyFacing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 걷거나 뛰는 방향으로 몸을 돌린다.
|
||||
///
|
||||
/// 정면을 본 채 옆으로 미끄러지면 게처럼 보인다. 다만 완전히 옆(90도)으로 돌리면
|
||||
/// 얼굴이 안 보여서 데스크톱 펫으로서는 손해다. 기본값을 70도로 둬서 걷는 티는 나되
|
||||
/// 얼굴은 살짝 이쪽을 향하게 한다. 머리는 HeadLookAt 이 커서 쪽으로 되돌리므로
|
||||
/// 몸만 돌아가고 시선은 사용자를 따라오는 그림이 된다.
|
||||
///
|
||||
/// 회전은 캐릭터 루트에 건다. 컨트롤 리그는 이미 만들어진 뒤이므로(로더 참고)
|
||||
/// 자식으로서 통째로 같이 돈다.
|
||||
/// </summary>
|
||||
void ApplyFacing()
|
||||
{
|
||||
if (!turnToWalkDirection) return;
|
||||
|
||||
float dir = 0f;
|
||||
if (State == ClimbState.Walking)
|
||||
{
|
||||
dir = Mathf.Sign(walkTargetX - screenPos.x);
|
||||
}
|
||||
else if (State == ClimbState.Jumping)
|
||||
{
|
||||
dir = Mathf.Sign(ClampToPlatform(climbTarget) - climbStart.x);
|
||||
}
|
||||
|
||||
// 서 있거나 떨어지는 중이면 정면으로 되돌아온다.
|
||||
float desired = Mathf.Abs(dir) > 0.01f
|
||||
? walkTurnAngle * dir * (invertTurnDirection ? 1f : -1f)
|
||||
: 0f;
|
||||
|
||||
currentTurn = Mathf.MoveTowards(currentTurn, desired, turnSpeed * Time.deltaTime);
|
||||
character.localRotation = baseRotation * Quaternion.Euler(0f, currentTurn, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user