feat: 집기/놓기 상호작용 (게임 핵심 루프)
- Carryable: 집을 수 있는 물건 표시 - PickupController: 범위 내 박스를 E키로 집고/놓기 + 화면 힌트 - PickupDemoSetup: 플레이어에 PickupController 부착 + 입고 박스 4개 스폰 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine;
|
||||||
|
using SinoWms.Gameplay;
|
||||||
|
using SinoWms.Player;
|
||||||
|
|
||||||
|
namespace SinoWms.EditorTools
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 집기/놓기 데모 구성: 플레이어에 PickupController 추가 + 입고 박스 스폰.
|
||||||
|
/// 메뉴 <b>SinoWMS/Setup Pickup Demo</b>. Play 후 박스에 다가가 E키로 집고 놓는다.
|
||||||
|
/// </summary>
|
||||||
|
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<PickupController>() == null)
|
||||||
|
player.AddComponent<PickupController>();
|
||||||
|
|
||||||
|
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<Renderer>().sharedMaterial = MakeMaterial(colors[i]);
|
||||||
|
|
||||||
|
var rb = box.AddComponent<Rigidbody>();
|
||||||
|
rb.mass = 1f;
|
||||||
|
|
||||||
|
box.AddComponent<Carryable>().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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 10eba722501bf4dcaa1d33cc90d763b1
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3ee476866b1bf47f5a10539f8b8dcef5
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace SinoWms.Gameplay
|
||||||
|
{
|
||||||
|
/// <summary>플레이어가 집어 옮길 수 있는 물건(박스/팔레트) 표시 컴포넌트.</summary>
|
||||||
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
|
[RequireComponent(typeof(Collider))]
|
||||||
|
public class Carryable : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("힌트/로그에 표시할 이름. 비우면 오브젝트 이름 사용.")]
|
||||||
|
public string displayName;
|
||||||
|
|
||||||
|
public string Label => string.IsNullOrEmpty(displayName) ? name : displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 315a7e23f649b4187a444b01f14a8e9d
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
|
using SinoWms.Gameplay;
|
||||||
|
|
||||||
|
namespace SinoWms.Player
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 캐릭터의 집기/놓기 상호작용. 범위 내 가장 가까운 Carryable을 E키로 집어
|
||||||
|
/// 앞쪽 홀드 포인트에 들고 다니고, 다시 E키로 내려놓는다.
|
||||||
|
/// </summary>
|
||||||
|
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<Carryable>();
|
||||||
|
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<Rigidbody>(out var rb)) rb.isKinematic = true;
|
||||||
|
if (c.TryGetComponent<Collider>(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<Collider>(out var col)) col.enabled = true;
|
||||||
|
if (c.TryGetComponent<Rigidbody>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8525e0c0e962c47b78e49a7e3371fe97
|
||||||
Reference in New Issue
Block a user