From 065355f63a1eccbebc4d3f57968d44e551af3b18 Mon Sep 17 00:00:00 2001 From: jklee Date: Sun, 14 Jun 2026 20:00:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=83=91=EB=8B=A4=EC=9A=B4=20=EC=BA=90?= =?UTF-8?q?=EB=A6=AD=ED=84=B0=20=EC=9D=B4=EB=8F=99=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EA=B7=B8=EB=9D=BC=EC=9A=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PlayerController: WASD/방향키 이동 + 진행방향 회전 (새 Input System) - CameraFollow: 위에서 비스듬히 따라가는 카메라 - PlaygroundSetup: 바닥+캐릭터+카메라 자동 구성 메뉴 Co-Authored-By: Claude Opus 4.8 (1M context) --- Assets/Scripts/Editor/PlaygroundSetup.cs | 81 +++++++++++++++++++ Assets/Scripts/Editor/PlaygroundSetup.cs.meta | 2 + Assets/Scripts/Player.meta | 8 ++ Assets/Scripts/Player/CameraFollow.cs | 23 ++++++ Assets/Scripts/Player/CameraFollow.cs.meta | 2 + Assets/Scripts/Player/PlayerController.cs | 55 +++++++++++++ .../Scripts/Player/PlayerController.cs.meta | 2 + 7 files changed, 173 insertions(+) create mode 100644 Assets/Scripts/Editor/PlaygroundSetup.cs create mode 100644 Assets/Scripts/Editor/PlaygroundSetup.cs.meta create mode 100644 Assets/Scripts/Player.meta create mode 100644 Assets/Scripts/Player/CameraFollow.cs create mode 100644 Assets/Scripts/Player/CameraFollow.cs.meta create mode 100644 Assets/Scripts/Player/PlayerController.cs create mode 100644 Assets/Scripts/Player/PlayerController.cs.meta diff --git a/Assets/Scripts/Editor/PlaygroundSetup.cs b/Assets/Scripts/Editor/PlaygroundSetup.cs new file mode 100644 index 0000000..cc6d583 --- /dev/null +++ b/Assets/Scripts/Editor/PlaygroundSetup.cs @@ -0,0 +1,81 @@ +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using SinoWms.Player; + +namespace SinoWms.EditorTools +{ + /// + /// 캐릭터 이동 확인용 최소 플레이그라운드를 구성한다. + /// 메뉴 SinoWMS/Setup Playground Scene 실행 후 Play → WASD/방향키로 이동. + /// + public static class PlaygroundSetup + { + [MenuItem("SinoWMS/Setup Playground Scene")] + public static void Setup() + { + // 바닥 + var ground = GameObject.Find("Ground"); + if (ground == null) + { + ground = GameObject.CreatePrimitive(PrimitiveType.Plane); + ground.name = "Ground"; + ground.transform.localScale = new Vector3(5f, 1f, 5f); // 50x50 + ground.GetComponent().sharedMaterial = MakeMaterial(new Color(0.5f, 0.55f, 0.6f)); + } + + // 플레이어 + var player = GameObject.Find("Player"); + if (player == null) + { + player = GameObject.CreatePrimitive(PrimitiveType.Capsule); + player.name = "Player"; + player.transform.position = new Vector3(0f, 1.1f, 0f); + + // CharacterController와 기본 CapsuleCollider 충돌 방지 + var capCol = player.GetComponent(); + if (capCol != null) Object.DestroyImmediate(capCol); + + var cc = player.AddComponent(); + cc.height = 2f; + cc.radius = 0.5f; + cc.center = Vector3.zero; + + player.AddComponent(); + player.GetComponent().sharedMaterial = MakeMaterial(new Color(0.2f, 0.5f, 1f)); + + // 정면 표시용 작은 큐브(어느 쪽을 보는지) + var nose = GameObject.CreatePrimitive(PrimitiveType.Cube); + nose.name = "Nose"; + Object.DestroyImmediate(nose.GetComponent()); + nose.transform.SetParent(player.transform, false); + nose.transform.localScale = new Vector3(0.25f, 0.25f, 0.4f); + nose.transform.localPosition = new Vector3(0f, 0.3f, 0.5f); + nose.GetComponent().sharedMaterial = MakeMaterial(new Color(1f, 0.85f, 0.3f)); + } + + // 카메라 (탑다운 추적) + var cam = Camera.main; + if (cam == null) + { + var camGo = new GameObject("Main Camera") { tag = "MainCamera" }; + cam = camGo.AddComponent(); + } + var follow = cam.GetComponent() ?? cam.gameObject.AddComponent(); + follow.SetTarget(player.transform); + cam.transform.position = player.transform.position + new Vector3(0f, 12f, -8f); + cam.transform.LookAt(player.transform.position); + + Selection.activeGameObject = player; + EditorSceneManager.MarkSceneDirty(player.scene); + Debug.Log("[PlaygroundSetup] 완료. Play 후 WASD/방향키로 캐릭터를 움직여 보세요."); + } + + /// URP 환경에서 분홍색으로 안 보이도록 URP/Lit 머티리얼 생성. + private static Material MakeMaterial(Color color) + { + var shader = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); + return new Material(shader) { color = color }; + } + } +} diff --git a/Assets/Scripts/Editor/PlaygroundSetup.cs.meta b/Assets/Scripts/Editor/PlaygroundSetup.cs.meta new file mode 100644 index 0000000..d0a89f4 --- /dev/null +++ b/Assets/Scripts/Editor/PlaygroundSetup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6a6e35879fd6146759034c5dab7c4afd \ No newline at end of file diff --git a/Assets/Scripts/Player.meta b/Assets/Scripts/Player.meta new file mode 100644 index 0000000..d8afb3e --- /dev/null +++ b/Assets/Scripts/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7891d41cf22544f878fa7dee0c7cc07a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Player/CameraFollow.cs b/Assets/Scripts/Player/CameraFollow.cs new file mode 100644 index 0000000..d3fa4de --- /dev/null +++ b/Assets/Scripts/Player/CameraFollow.cs @@ -0,0 +1,23 @@ +using UnityEngine; + +namespace SinoWms.Player +{ + /// 대상(캐릭터)을 위에서 비스듬히 따라다니는 탑다운 카메라. + public class CameraFollow : MonoBehaviour + { + [SerializeField] private Transform target; + [SerializeField] private Vector3 offset = new Vector3(0f, 12f, -8f); + [SerializeField] private float smooth = 8f; + + public void SetTarget(Transform t) => target = t; + + private void LateUpdate() + { + if (target == null) return; + + Vector3 desired = target.position + offset; + transform.position = Vector3.Lerp(transform.position, desired, smooth * Time.deltaTime); + transform.LookAt(target.position); + } + } +} diff --git a/Assets/Scripts/Player/CameraFollow.cs.meta b/Assets/Scripts/Player/CameraFollow.cs.meta new file mode 100644 index 0000000..c8e0737 --- /dev/null +++ b/Assets/Scripts/Player/CameraFollow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 378ab13c02a4c4074878e994d1593434 \ No newline at end of file diff --git a/Assets/Scripts/Player/PlayerController.cs b/Assets/Scripts/Player/PlayerController.cs new file mode 100644 index 0000000..2eb9964 --- /dev/null +++ b/Assets/Scripts/Player/PlayerController.cs @@ -0,0 +1,55 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace SinoWms.Player +{ + /// + /// 탑다운 캐릭터 이동. 새 Input System(Keyboard.current)으로 WASD/방향키 입력을 받아 + /// XZ 평면을 이동하고, 진행 방향으로 부드럽게 회전한다. + /// + [RequireComponent(typeof(CharacterController))] + public class PlayerController : MonoBehaviour + { + [SerializeField] private float moveSpeed = 5f; + [SerializeField] private float turnSpeed = 12f; + [SerializeField] private float gravity = -20f; + + private CharacterController _cc; + private float _verticalVelocity; + + private void Awake() + { + _cc = GetComponent(); + } + + private void Update() + { + var kb = Keyboard.current; + if (kb == null) return; + + Vector2 input = Vector2.zero; + if (kb.wKey.isPressed || kb.upArrowKey.isPressed) input.y += 1f; + if (kb.sKey.isPressed || kb.downArrowKey.isPressed) input.y -= 1f; + if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) input.x += 1f; + if (kb.aKey.isPressed || kb.leftArrowKey.isPressed) input.x -= 1f; + + Vector3 move = new Vector3(input.x, 0f, input.y); + if (move.sqrMagnitude > 1f) move.Normalize(); + + // 중력 (바닥에 붙어있게) + if (_cc.isGrounded && _verticalVelocity < 0f) _verticalVelocity = -2f; + _verticalVelocity += gravity * Time.deltaTime; + + Vector3 velocity = move * moveSpeed; + velocity.y = _verticalVelocity; + _cc.Move(velocity * Time.deltaTime); + + // 진행 방향으로 회전 + if (move.sqrMagnitude > 0.01f) + { + Quaternion target = Quaternion.LookRotation(move, Vector3.up); + transform.rotation = Quaternion.Slerp(transform.rotation, target, turnSpeed * Time.deltaTime); + } + } + } +} diff --git a/Assets/Scripts/Player/PlayerController.cs.meta b/Assets/Scripts/Player/PlayerController.cs.meta new file mode 100644 index 0000000..88274ff --- /dev/null +++ b/Assets/Scripts/Player/PlayerController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 00269464ac30a416992064182bd7186c \ No newline at end of file