2026-09-12 12:52:53 +00:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// ArenaWalker.cs — 시범 전투장(WL_ArenaProto) 전용 이동 리그 (발주서 WL-814r · PD 「걸어다니며 크기 감 잡기」)
|
|
|
|
|
//
|
|
|
|
|
// ■ 왜 새 스크립트인가
|
|
|
|
|
// 이 씬의 캐릭터는 **전시용**이라 게임 컴포넌트(`WizardActor`·`NavMeshAgent`·`Rigidbody`·`CapsuleCollider`)를
|
|
|
|
|
// 전부 떼어 냈다(814r §1 · 그것들이 게임 초기화 없이 돌면서 `KeyNotFoundException: 'Freeze'` 가 프레임마다 터졌다).
|
|
|
|
|
// 되살리면 에러 0 이 깨지므로, **본편 시스템을 전혀 건드리지 않는 씬 전용 리그**를 따로 둔다.
|
|
|
|
|
//
|
|
|
|
|
// ■ 캐릭터에 붙이지 않고 별도 오브젝트에 두는 이유
|
|
|
|
|
// `PC_LH_M05` 는 프리팹 인스턴스다. 거기에 컴포넌트를 더하면 「전시용 = Transform·Animator·렌더러뿐」이라는
|
|
|
|
|
// 814r §1 의 완료 조건이 흐려진다. 그래서 이 리그가 밖에서 `body` 를 움직인다(프리팹 수정 0 · 인스턴스 추가 0).
|
|
|
|
|
//
|
|
|
|
|
// ■ 실측해서 가져온 값 (하드코딩 아님 · 전부 인스펙터 노출 · 출처 주석)
|
|
|
|
|
// · moveSpeed 3.9 = `Assets/Res_Addr/PC/LH_M05.prefab` NavMeshAgent `m_Speed` (인게임 이동 속도 그대로)
|
|
|
|
|
// · turnSpeed 300 = 같은 프리팹 NavMeshAgent `m_AngularSpeed`
|
|
|
|
|
// · 애니메이션 전환 = 원본 `Actor.AnimationPlay()` 가 쓰는 **상태 이름 직접 재생** 방식 그대로
|
|
|
|
|
// (`Assets/Script/Character/Actor.cs:2031` `m_animation.Play(aniname)` · 이름은 `MyValue.cs:72`
|
|
|
|
|
// `eAnim.Idle→"idle"` / `eAnim.Run→"run"` · `pcanim_mage.controller` 에 두 상태 모두 존재 실측).
|
|
|
|
|
// 🔴 이 컨트롤러의 Base Layer 에는 idle/run 으로 들어가는 **트랜지션이 없다** — 파라미터로는 못 바꾼다.
|
|
|
|
|
//
|
|
|
|
|
// ■ 바닥
|
|
|
|
|
// 콜라이더를 전부 뗐으므로 물리를 쓰지 않는다. 지형 높이는 `Terrain.SampleHeight()` 로 직접 샘플링한다
|
|
|
|
|
// (TerrainCollider 유무와 무관 · Raycast 보다 싸다). 바위·물 통과는 크기 확인용이라 신경 쓰지 않는다(발주 명시).
|
|
|
|
|
//
|
|
|
|
|
// ■ 입력 = 레거시 Input (이 프로젝트 `activeInputHandler: 0`)
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace WL.Look.Arena
|
|
|
|
|
{
|
|
|
|
|
[DisallowMultipleComponent]
|
|
|
|
|
public sealed class ArenaWalker : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
[Header("대상")]
|
|
|
|
|
[Tooltip("움직일 캐릭터 루트. 비우면 이 오브젝트 자신.")]
|
|
|
|
|
public Transform body;
|
|
|
|
|
|
|
|
|
|
[Tooltip("따라다닐 카메라. 비우면 Camera.main.")]
|
|
|
|
|
public Camera followCamera;
|
|
|
|
|
|
|
|
|
|
[Header("이동 (LH_M05.prefab NavMeshAgent 실측값)")]
|
|
|
|
|
[Tooltip("m/s · NavMeshAgent.m_Speed = 3.9")]
|
|
|
|
|
public float moveSpeed = 3.9f;
|
|
|
|
|
|
|
|
|
|
[Tooltip("deg/s · NavMeshAgent.m_AngularSpeed = 300")]
|
|
|
|
|
public float turnSpeed = 300f;
|
|
|
|
|
|
|
|
|
|
[Header("애니메이션 상태 이름 (MyValue.cs:72)")]
|
|
|
|
|
public string idleState = "idle";
|
|
|
|
|
public string runState = "run";
|
|
|
|
|
|
2026-09-12 13:04:42 +00:00
|
|
|
[Header("프로브 — 중앙→가장자리 소요 시간 실측용 (평소 off)")]
|
|
|
|
|
[Tooltip("켜면 입력 없이 autoWalkDir 로 자동 전진하고, 중앙에서 autoWalkGoal m 떨어지면 걸린 시간을 로그로 남긴다.")]
|
|
|
|
|
public bool autoWalk;
|
|
|
|
|
public Vector3 autoWalkDir = Vector3.right;
|
|
|
|
|
public float autoWalkGoal = 14f;
|
|
|
|
|
|
|
|
|
|
float _autoT = -1f;
|
|
|
|
|
/// <summary>자동 전진이 목표 거리에 도달한 시각(초). 아직이면 -1.</summary>
|
|
|
|
|
public float AutoWalkSeconds { get; private set; } = -1f;
|
|
|
|
|
|
2026-09-12 12:52:53 +00:00
|
|
|
Animator _anim;
|
|
|
|
|
Vector3 _camOffset;
|
|
|
|
|
bool _running;
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
|
|
|
|
if (body == null) body = transform;
|
|
|
|
|
if (followCamera == null) followCamera = Camera.main;
|
|
|
|
|
_anim = body.GetComponentInChildren<Animator>();
|
2026-09-12 13:04:42 +00:00
|
|
|
|
|
|
|
|
// 🔴 전시용 캐릭터에는 애니메이션 이벤트 수신자가 없다.
|
|
|
|
|
// 클립 `Idle` 이 `Event_Idle` 을 쏘는데 받는 쪽(`WizardActor`)을 떼어 냈으므로
|
|
|
|
|
// 루프마다 "AnimationEvent 'Event_Idle' … has no receiver!" 에러가 난다(실측 2초에 1회).
|
|
|
|
|
// 빈 수신자 컴포넌트를 새로 붙이는 대신 이벤트 발사 자체를 끈다(런타임 전용 플래그).
|
|
|
|
|
foreach (var a in body.GetComponentsInChildren<Animator>(true)) a.fireEvents = false;
|
|
|
|
|
|
2026-09-12 12:52:53 +00:00
|
|
|
if (followCamera != null) _camOffset = followCamera.transform.position - body.position;
|
|
|
|
|
SnapToGround();
|
|
|
|
|
PlayState(idleState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
|
|
|
|
if (body == null) return;
|
|
|
|
|
|
|
|
|
|
// 카메라 기준 입력(화면에서 보이는 방향대로 움직인다)
|
|
|
|
|
Vector3 fwd = Vector3.forward, right = Vector3.right;
|
|
|
|
|
if (followCamera != null)
|
|
|
|
|
{
|
|
|
|
|
fwd = Vector3.ProjectOnPlane(followCamera.transform.forward, Vector3.up);
|
|
|
|
|
if (fwd.sqrMagnitude < 1e-6f) fwd = Vector3.forward;
|
|
|
|
|
fwd.Normalize();
|
|
|
|
|
right = Vector3.Cross(Vector3.up, fwd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector3 dir = right * Input.GetAxisRaw("Horizontal") + fwd * Input.GetAxisRaw("Vertical");
|
2026-09-12 13:04:42 +00:00
|
|
|
if (autoWalk && AutoWalkSeconds < 0f)
|
|
|
|
|
{
|
|
|
|
|
if (_autoT < 0f) _autoT = Time.time;
|
|
|
|
|
dir = autoWalkDir;
|
|
|
|
|
if (DistanceFromCenter >= autoWalkGoal)
|
|
|
|
|
{
|
|
|
|
|
AutoWalkSeconds = Time.time - _autoT;
|
|
|
|
|
Debug.Log(string.Format("[WL-814r] 중앙→{0:F1} m 도달: {1:F2} 초 (speed {2} m/s)", autoWalkGoal, AutoWalkSeconds, moveSpeed));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-12 12:52:53 +00:00
|
|
|
if (dir.sqrMagnitude > 1f) dir.Normalize();
|
|
|
|
|
|
|
|
|
|
bool moving = dir.sqrMagnitude > 1e-4f;
|
|
|
|
|
if (moving)
|
|
|
|
|
{
|
|
|
|
|
body.position += dir * (moveSpeed * Time.deltaTime);
|
|
|
|
|
body.rotation = Quaternion.RotateTowards(
|
|
|
|
|
body.rotation, Quaternion.LookRotation(dir, Vector3.up), turnSpeed * Time.deltaTime);
|
|
|
|
|
}
|
|
|
|
|
SnapToGround();
|
|
|
|
|
|
|
|
|
|
if (moving != _running)
|
|
|
|
|
{
|
|
|
|
|
_running = moving;
|
|
|
|
|
PlayState(moving ? runState : idleState);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LateUpdate()
|
|
|
|
|
{
|
|
|
|
|
if (followCamera != null && body != null)
|
|
|
|
|
followCamera.transform.position = body.position + _camOffset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SnapToGround()
|
|
|
|
|
{
|
|
|
|
|
var t = Terrain.activeTerrain;
|
|
|
|
|
if (t == null) return;
|
|
|
|
|
var p = body.position;
|
|
|
|
|
p.y = t.SampleHeight(p) + t.transform.position.y;
|
|
|
|
|
body.position = p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PlayState(string state)
|
|
|
|
|
{
|
|
|
|
|
if (_anim != null && !string.IsNullOrEmpty(state)) _anim.Play(state, 0, 0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>프로브용 — 지금까지 중앙(원점)에서 떨어진 수평 거리(m).</summary>
|
|
|
|
|
public float DistanceFromCenter
|
|
|
|
|
{
|
|
|
|
|
get { var p = body != null ? body.position : transform.position; return new Vector2(p.x, p.z).magnitude; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|