From f8577c5f7d3070503c1fdb545fc6346f7330a63f Mon Sep 17 00:00:00 2001 From: jklee Date: Sun, 14 Jun 2026 20:05:50 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A7=91=EA=B8=B0/=EB=86=93=EA=B8=B0?= =?UTF-8?q?=20=EC=83=81=ED=98=B8=EC=9E=91=EC=9A=A9=20(=EA=B2=8C=EC=9E=84?= =?UTF-8?q?=20=ED=95=B5=EC=8B=AC=20=EB=A3=A8=ED=94=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Carryable: 집을 수 있는 물건 표시 - PickupController: 범위 내 박스를 E키로 집고/놓기 + 화면 힌트 - PickupDemoSetup: 플레이어에 PickupController 부착 + 입고 박스 4개 스폰 Co-Authored-By: Claude Opus 4.8 (1M context) --- Assets/Scripts/Editor/PickupDemoSetup.cs | 68 +++++++++++++ Assets/Scripts/Editor/PickupDemoSetup.cs.meta | 2 + Assets/Scripts/Gameplay.meta | 8 ++ Assets/Scripts/Gameplay/Carryable.cs | 15 +++ Assets/Scripts/Gameplay/Carryable.cs.meta | 2 + Assets/Scripts/Gameplay/PickupController.cs | 99 +++++++++++++++++++ .../Scripts/Gameplay/PickupController.cs.meta | 2 + 7 files changed, 196 insertions(+) create mode 100644 Assets/Scripts/Editor/PickupDemoSetup.cs create mode 100644 Assets/Scripts/Editor/PickupDemoSetup.cs.meta create mode 100644 Assets/Scripts/Gameplay.meta create mode 100644 Assets/Scripts/Gameplay/Carryable.cs create mode 100644 Assets/Scripts/Gameplay/Carryable.cs.meta create mode 100644 Assets/Scripts/Gameplay/PickupController.cs create mode 100644 Assets/Scripts/Gameplay/PickupController.cs.meta diff --git a/Assets/Scripts/Editor/PickupDemoSetup.cs b/Assets/Scripts/Editor/PickupDemoSetup.cs new file mode 100644 index 0000000..06979c1 --- /dev/null +++ b/Assets/Scripts/Editor/PickupDemoSetup.cs @@ -0,0 +1,68 @@ +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using SinoWms.Gameplay; +using SinoWms.Player; + +namespace SinoWms.EditorTools +{ + /// + /// 집기/놓기 데모 구성: 플레이어에 PickupController 추가 + 입고 박스 스폰. + /// 메뉴 SinoWMS/Setup Pickup Demo. Play 후 박스에 다가가 E키로 집고 놓는다. + /// + public static class PickupDemoSetup + { + [MenuItem("SinoWMS/Setup Pickup Demo")] + public static void Setup() + { + var player = GameObject.Find("Player"); + if (player == null) + { + Debug.LogError("[PickupDemoSetup] Player가 없습니다. 먼저 'Setup Playground Scene'을 실행하세요."); + return; + } + if (player.GetComponent() == null) + player.AddComponent(); + + var container = GameObject.Find("InboundBoxes") ?? new GameObject("InboundBoxes"); + + // 입고 대기 박스 4개 (남쪽 통로 근처에 배치) + var colors = new[] + { + new Color(0.75f, 0.55f, 0.35f), + new Color(0.70f, 0.50f, 0.30f), + new Color(0.80f, 0.60f, 0.40f), + new Color(0.65f, 0.45f, 0.28f), + }; + string[] names = { "박스 A (생수)", "박스 B (우유)", "박스 C (라면)", "박스 D (잡화)" }; + + for (int i = 0; i < 4; i++) + { + string objName = $"Box_{i + 1}"; + if (container.transform.Find(objName) != null) continue; + + var box = GameObject.CreatePrimitive(PrimitiveType.Cube); + box.name = objName; + box.transform.SetParent(container.transform, false); + box.transform.localPosition = new Vector3(-3f + i * 2f, 0.5f, -20f); + box.transform.localScale = Vector3.one; + box.GetComponent().sharedMaterial = MakeMaterial(colors[i]); + + var rb = box.AddComponent(); + rb.mass = 1f; + + box.AddComponent().displayName = names[i]; + } + + EditorSceneManager.MarkSceneDirty(player.scene); + Selection.activeGameObject = player; + Debug.Log("[PickupDemoSetup] 완료. Play 후 박스에 다가가 E키로 집고 놓아 보세요."); + } + + 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/PickupDemoSetup.cs.meta b/Assets/Scripts/Editor/PickupDemoSetup.cs.meta new file mode 100644 index 0000000..2691389 --- /dev/null +++ b/Assets/Scripts/Editor/PickupDemoSetup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 10eba722501bf4dcaa1d33cc90d763b1 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay.meta b/Assets/Scripts/Gameplay.meta new file mode 100644 index 0000000..0a85f5b --- /dev/null +++ b/Assets/Scripts/Gameplay.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3ee476866b1bf47f5a10539f8b8dcef5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Carryable.cs b/Assets/Scripts/Gameplay/Carryable.cs new file mode 100644 index 0000000..a1e6d1a --- /dev/null +++ b/Assets/Scripts/Gameplay/Carryable.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace SinoWms.Gameplay +{ + /// 플레이어가 집어 옮길 수 있는 물건(박스/팔레트) 표시 컴포넌트. + [RequireComponent(typeof(Rigidbody))] + [RequireComponent(typeof(Collider))] + public class Carryable : MonoBehaviour + { + [Tooltip("힌트/로그에 표시할 이름. 비우면 오브젝트 이름 사용.")] + public string displayName; + + public string Label => string.IsNullOrEmpty(displayName) ? name : displayName; + } +} diff --git a/Assets/Scripts/Gameplay/Carryable.cs.meta b/Assets/Scripts/Gameplay/Carryable.cs.meta new file mode 100644 index 0000000..9451e4f --- /dev/null +++ b/Assets/Scripts/Gameplay/Carryable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 315a7e23f649b4187a444b01f14a8e9d \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/PickupController.cs b/Assets/Scripts/Gameplay/PickupController.cs new file mode 100644 index 0000000..9d2eb85 --- /dev/null +++ b/Assets/Scripts/Gameplay/PickupController.cs @@ -0,0 +1,99 @@ +using UnityEngine; +using UnityEngine.InputSystem; +using SinoWms.Gameplay; + +namespace SinoWms.Player +{ + /// + /// 캐릭터의 집기/놓기 상호작용. 범위 내 가장 가까운 Carryable을 E키로 집어 + /// 앞쪽 홀드 포인트에 들고 다니고, 다시 E키로 내려놓는다. + /// + public class PickupController : MonoBehaviour + { + [SerializeField] private float pickupRange = 2.5f; + [SerializeField] private Transform holdPoint; + + private Carryable _carried; + private Carryable _target; // 현재 집을 수 있는 후보(힌트 표시용) + private GUIStyle _style; + + private void Awake() + { + if (holdPoint == null) + { + var hp = new GameObject("HoldPoint").transform; + hp.SetParent(transform, false); + hp.localPosition = new Vector3(0f, 0.6f, 0.9f); + holdPoint = hp; + } + } + + private void Update() + { + var kb = Keyboard.current; + if (kb == null) return; + + if (_carried == null) + _target = FindNearest(); + + if (kb.eKey.wasPressedThisFrame) + { + if (_carried != null) Drop(); + else if (_target != null) PickUp(_target); + } + } + + private Carryable FindNearest() + { + var hits = Physics.OverlapSphere(transform.position, pickupRange); + Carryable best = null; + float bestDist = float.MaxValue; + foreach (var h in hits) + { + var c = h.GetComponentInParent(); + if (c == null || c == _carried) continue; + float d = Vector3.Distance(transform.position, c.transform.position); + if (d < bestDist) { bestDist = d; best = c; } + } + return best; + } + + private void PickUp(Carryable c) + { + _carried = c; + _target = null; + + if (c.TryGetComponent(out var rb)) rb.isKinematic = true; + if (c.TryGetComponent(out var col)) col.enabled = false; + + c.transform.SetParent(holdPoint); + c.transform.localPosition = Vector3.zero; + c.transform.localRotation = Quaternion.identity; + } + + private void Drop() + { + var c = _carried; + _carried = null; + + c.transform.SetParent(null); + c.transform.position = holdPoint.position; + c.transform.rotation = holdPoint.rotation; + + if (c.TryGetComponent(out var col)) col.enabled = true; + if (c.TryGetComponent(out var rb)) rb.isKinematic = false; + } + + private void OnGUI() + { + _style ??= new GUIStyle(GUI.skin.box) { fontSize = 18, alignment = TextAnchor.MiddleCenter }; + + string msg = null; + if (_carried != null) msg = $"E: 내려놓기 — {_carried.Label}"; + else if (_target != null) msg = $"E: 집기 — {_target.Label}"; + if (msg == null) return; + + GUI.Label(new Rect(Screen.width / 2f - 150f, 24f, 300f, 32f), msg, _style); + } + } +} diff --git a/Assets/Scripts/Gameplay/PickupController.cs.meta b/Assets/Scripts/Gameplay/PickupController.cs.meta new file mode 100644 index 0000000..9d296f3 --- /dev/null +++ b/Assets/Scripts/Gameplay/PickupController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8525e0c0e962c47b78e49a7e3371fe97 \ No newline at end of file