애니메이션 추가

This commit is contained in:
2026-09-01 20:19:59 +09:00
parent 01c852fef9
commit 7ec587353a
1542 changed files with 585048 additions and 21 deletions

View File

@@ -6,7 +6,7 @@ public enum ClimbState
Falling, // 낙하 중
Standing, // 발판 위에 서 있음
Walking, // 발판 위를 걷는 중 (목표 X 로 이동)
Climbing, // 다른 발판으로 올라가는 중
Jumping, // 다른 발판으로 뛰어오르는 중
}
/// <summary>
@@ -28,11 +28,16 @@ public class WindowClimber : MonoBehaviour
[SerializeField] Camera viewCamera;
[Header("물리 (화면 픽셀 기준)")]
[Tooltip("초당 낙하 가속도")]
[SerializeField] float gravity = 2600f;
// 2600 정도가 실제 중력에 해당한다(캐릭터 키를 화면 400px 로 보면 1m≈250px,
// 9.8m/s² ≈ 2450px/s²). 그런데 실제 중력으로 떨어지면 400px 낙하가 0.56초라
// 낙하 애니메이션이 눈에 들어오지 않는다. 데스크톱 펫은 일부러 둥실하게 둔다.
[Tooltip("초당 낙하 가속도. 낮출수록 천천히 떨어져 낙하 동작이 잘 보인다. " +
"2600 은 실제 중력에 가까워 너무 빠르다")]
[SerializeField] float gravity = 1200f;
[Tooltip("최대 낙하 속도")]
[SerializeField] float maxFallSpeed = 1800f;
[Tooltip("최대 낙하 속도. 긴 낙하의 체감 속도를 좌우한다. " +
"이 값이 크면 화면을 가로지르는 낙하가 순식간에 끝난다")]
[SerializeField] float maxFallSpeed = 900f;
[Tooltip("걷는 속도 (픽셀/초)")]
[SerializeField] float walkSpeed = 160f;
@@ -63,6 +68,15 @@ public class WindowClimber : MonoBehaviour
[Tooltip("올라가는 속도 (픽셀/초). 거리에 비례해 시간이 정해진다")]
[SerializeField] float climbSpeed = 420f;
[Tooltip("점프할 때 목표보다 얼마나 더 높이 떠오르는지(픽셀). 0 이면 직선으로 간다")]
[SerializeField] float jumpArcHeight = 70f;
[Tooltip("가장 짧은 점프에 걸리는 시간(초). 너무 작으면 순간이동처럼 보인다")]
[SerializeField] float minJumpDuration = 0.35f;
[Tooltip("가장 먼 점프에 걸리는 시간(초). 너무 크면 화면을 오래 가린다")]
[SerializeField] float maxJumpDuration = 0.9f;
[Header("발판 여백")]
[Tooltip("발판 좌우 끝에서 최소한 이만큼 안쪽에 선다. " +
"캐릭터 반폭보다 작으면 반폭이 우선 적용된다")]
@@ -75,7 +89,27 @@ public class WindowClimber : MonoBehaviour
[Tooltip("착지 판정 여유. 발판을 살짝 지나쳐도 잡아준다")]
[SerializeField] float landTolerance = 24f;
public ClimbState State { get; private set; } = ClimbState.Falling;
ClimbState state = ClimbState.Falling;
/// <summary>
/// 지금 이동 상태. 값이 실제로 바뀔 때만 <see cref="StateChanged"/> 가 불린다.
///
/// 대입하는 자리가 여러 군데라 각각에서 이벤트를 쏘면 빠뜨리기 쉽다.
/// 프로퍼티 한 곳에 모아두면 새 전환을 추가해도 자동으로 알려진다.
/// </summary>
public ClimbState State
{
get => state;
private set
{
if (state == value) return;
state = value;
StateChanged?.Invoke(value);
}
}
/// <summary>이동 상태가 바뀔 때. 애니메이션 전환이 이걸 듣는다.</summary>
public event System.Action<ClimbState> StateChanged;
Transform character;
float depth; // 카메라로부터의 깊이. 드래그와 같은 평면을 유지한다.
@@ -159,7 +193,7 @@ void LateUpdate()
case ClimbState.Falling: TickFalling(dt); break;
case ClimbState.Standing: TickStanding(dt); break;
case ClimbState.Walking: TickWalking(dt); break;
case ClimbState.Climbing: TickClimbing(dt); break;
case ClimbState.Jumping: TickJumping(dt); break;
}
Apply();
@@ -249,21 +283,36 @@ void BeginClimbArc()
climbProgress = 0f;
float distance = Vector2.Distance(climbStart, new Vector2(ClampToPlatform(climbTarget), climbTarget.Y));
climbDuration = Mathf.Max(0.25f, distance / Mathf.Max(1f, climbSpeed));
State = ClimbState.Climbing;
// 점프는 거리에 정비례해 느려지지 않는다. 아주 짧은 도약이 순간이동처럼 보이거나
// 먼 도약이 하염없이 길어지지 않도록 위아래를 막는다.
climbDuration = Mathf.Clamp(distance / Mathf.Max(1f, climbSpeed),
minJumpDuration, maxJumpDuration);
State = ClimbState.Jumping;
}
void TickClimbing(float dt)
/// <summary>
/// 발판 사이를 뛰어오른다.
///
/// 원래는 대각선으로 미끄러지듯 올라갔는데, 그러면 화면을 오래 가리고
/// "뒤로 도는지 옆으로 도는지" 애매해서 어떤 클라이밍 클립과도 맞지 않았다.
/// 포물선 도약은 짧고, 방향이 분명하고, 점프 클립 하나로 어떤 거리든 커버된다.
/// (Desktop Mate 도 Shimeji 도 "올라가는 과정"을 길게 보여주지 않는다)
/// </summary>
void TickJumping(float dt)
{
climbProgress += dt / climbDuration;
float t = Mathf.Clamp01(climbProgress);
float targetX = ClampToPlatform(climbTarget);
// 수평은 일정하게, 수직은 뒤로 갈수록 느려지게 해서 "올라타는" 느낌을 준다.
screenPos.x = Mathf.Lerp(climbStart.x, targetX, t);
screenPos.y = Mathf.Lerp(climbStart.y, climbTarget.Y, Mathf.SmoothStep(0f, 1f, t));
// 4t(1-t) 는 t=0.5 에서 1, 양 끝에서 0 이다. 그래서 시작점과 도착점을
// 정확히 지나면서 가운데만 부풀어 오른다 — 목표보다 높이 떠올랐다 내려앉는다.
float straight = Mathf.Lerp(climbStart.y, climbTarget.Y, t);
screenPos.y = straight + jumpArcHeight * 4f * t * (1f - t);
if (t >= 1f) Land(climbTarget);
}
@@ -398,8 +447,8 @@ void ConsiderClimb()
walkTargetX = ClampToPlatform(best);
nextClimbAllowedTime = Time.unscaledTime + climbCooldown;
State = Mathf.Abs(walkTargetX - screenPos.x) > 2f ? ClimbState.Walking : ClimbState.Climbing;
if (State == ClimbState.Climbing) BeginClimbArc();
State = Mathf.Abs(walkTargetX - screenPos.x) > 2f ? ClimbState.Walking : ClimbState.Jumping;
if (State == ClimbState.Jumping) BeginClimbArc();
}
// ---------------- 반영 ----------------