feat: 적치 슬롯 + 정답 판정

- PutawaySlot: 박스 스냅 배치 + 기대 품목 비교 → 패드 색(초록/빨강)
- PickupController: 빈 슬롯에 적치 / 슬롯에서 재집기 / 'E: 적치' 힌트
- SlotSetup: 슬롯 4개 배치 + 박스별 기대 품목 매핑

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jklee
2026-06-14 20:11:09 +09:00
parent 0a372cb88d
commit aa302b1d67
5 changed files with 171 additions and 4 deletions
+42 -4
View File
@@ -15,6 +15,7 @@ namespace SinoWms.Player
private Carryable _carried;
private Carryable _target; // 현재 집을 수 있는 후보(힌트 표시용)
private PutawaySlot _targetSlot; // 들고 있을 때 적치 가능한 빈 슬롯
private GUIStyle _style;
private void Awake()
@@ -34,7 +35,14 @@ namespace SinoWms.Player
if (kb == null) return;
if (_carried == null)
{
_target = FindNearest();
_targetSlot = null;
}
else
{
_targetSlot = FindNearestSlot();
}
if (kb.eKey.wasPressedThisFrame)
{
@@ -58,8 +66,27 @@ namespace SinoWms.Player
return best;
}
private PutawaySlot FindNearestSlot()
{
var hits = Physics.OverlapSphere(transform.position, pickupRange);
PutawaySlot best = null;
float bestDist = float.MaxValue;
foreach (var h in hits)
{
var s = h.GetComponentInParent<PutawaySlot>();
if (s == null || s.IsOccupied) continue;
float d = Vector3.Distance(transform.position, s.transform.position);
if (d < bestDist) { bestDist = d; best = s; }
}
return best;
}
private void PickUp(Carryable c)
{
// 슬롯에 적치돼 있던 박스라면 슬롯을 비운다
var fromSlot = c.GetComponentInParent<PutawaySlot>();
if (fromSlot != null && fromSlot.Occupant == c) fromSlot.Clear();
_carried = c;
_target = null;
@@ -75,11 +102,18 @@ namespace SinoWms.Player
{
var c = _carried;
_carried = null;
c.transform.SetParent(null);
// 근처 빈 슬롯이 있으면 적치(스냅 + 정답 판정)
if (_targetSlot != null && _targetSlot.TryPlace(c))
{
_targetSlot = null;
return;
}
// 아니면 그냥 바닥에 내려놓기
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;
}
@@ -89,8 +123,12 @@ namespace SinoWms.Player
_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 (_carried != null)
msg = _targetSlot != null
? $"E: 적치 — {_targetSlot.Title}"
: $"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);
+60
View File
@@ -0,0 +1,60 @@
using UnityEngine;
namespace SinoWms.Gameplay
{
/// <summary>
/// 적치 슬롯. 캐릭터가 들고 온 Carryable을 받아 스냅 배치하고,
/// 기대 품목(expectedLabel)과 비교해 정답/오답을 바닥 패드 색으로 표시한다.
/// </summary>
public class PutawaySlot : MonoBehaviour
{
[Tooltip("이 슬롯에 와야 하는 품목 라벨. 비우면 아무거나 정답 처리.")]
public string expectedLabel;
[Tooltip("바닥 표시용 패드 렌더러(색으로 상태 표시).")]
[SerializeField] private Renderer pad;
[Tooltip("박스가 스냅될 패드 위 높이.")]
[SerializeField] private float dropHeight = 0.5f;
private static readonly Color Empty = new Color(1f, 0.82f, 0.3f);
private static readonly Color Correct = new Color(0.3f, 0.8f, 0.4f);
private static readonly Color Wrong = new Color(0.85f, 0.3f, 0.3f);
public Carryable Occupant { get; private set; }
public bool IsOccupied => Occupant != null;
public bool IsCorrect =>
IsOccupied && (string.IsNullOrEmpty(expectedLabel) || Occupant.Label == expectedLabel);
public string Title => string.IsNullOrEmpty(expectedLabel) ? name : expectedLabel;
private void Start() => UpdateMarker();
public bool TryPlace(Carryable c)
{
if (IsOccupied) return false;
Occupant = c;
c.transform.SetParent(transform);
c.transform.localPosition = new Vector3(0f, dropHeight, 0f);
c.transform.localRotation = Quaternion.identity;
if (c.TryGetComponent<Collider>(out var col)) col.enabled = true;
if (c.TryGetComponent<Rigidbody>(out var rb)) rb.isKinematic = true;
UpdateMarker();
return true;
}
public void Clear()
{
Occupant = null;
UpdateMarker();
}
private void UpdateMarker()
{
if (pad == null) return;
pad.material.color = !IsOccupied ? Empty : (IsCorrect ? Correct : Wrong);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c9d533b08ac4a487182866db79a8e2e3