물품들 상호작용 달기
This commit is contained in:
82
Assets/02_Scripts/Mission/Mission.cs
Normal file
82
Assets/02_Scripts/Mission/Mission.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
// 미션 하나. 자식의 MissionObjective들이 전부 완료되면 미션이 완료된다.
|
||||
// 시작은 Activate()를 UnityEvent(대화 노드 이벤트, 이벤트존, 버튼 등)로 호출하거나
|
||||
// ActivateOnStart를 켠다. 완료는 StoryState에 영구 기록되므로
|
||||
// DialogCondition의 RequiredMissionIds로 대화 활성화 조건에 쓸 수 있다.
|
||||
public class Mission : MonoBehaviour
|
||||
{
|
||||
public enum MissionState { Inactive, Active, Completed }
|
||||
|
||||
[Tooltip("StoryState에 기록되는 Id. 비워두면 오브젝트 이름 사용 (DialogCondition의 RequiredMissionIds에서 참조)")]
|
||||
[SerializeField] private string _id;
|
||||
|
||||
[Tooltip("씬 시작 시 자동으로 시작. 끄면 Activate()를 대화 노드 이벤트/존/버튼 등에서 호출할 것")]
|
||||
[SerializeField] private bool _activateOnStart;
|
||||
|
||||
[Tooltip("이 미션을 처음 완료하면 메인 진행도 +N")]
|
||||
[Min(0)] [SerializeField] private int _progressOnComplete;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent OnActivated; // 미션 시작 시 (안내 대사, 미션 UI 표시 등)
|
||||
public UnityEvent OnCompleted; // 미션 완료 시 (보상 대사, VFX, UI 숨김 등)
|
||||
|
||||
public MissionState State { get; private set; } = MissionState.Inactive;
|
||||
public string Id => string.IsNullOrEmpty(_id) ? name : _id;
|
||||
|
||||
private MissionObjective[] _objectives;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_objectives = GetComponentsInChildren<MissionObjective>(true);
|
||||
foreach (var objective in _objectives)
|
||||
objective.Changed += Reevaluate;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 저장된 상태에 이미 완료로 기록돼 있으면(이어하기 등) 이벤트 없이 완료 상태로 시작
|
||||
if (StoryManager.Instance.IsMissionCompleted(Id))
|
||||
State = MissionState.Completed;
|
||||
else if (_activateOnStart)
|
||||
Activate();
|
||||
}
|
||||
|
||||
// UnityEvent 연결용 — 대화의 '수락' 노드 이벤트 등에서 호출
|
||||
public void Activate()
|
||||
{
|
||||
if (State != MissionState.Inactive) return;
|
||||
if (StoryManager.Instance.IsMissionCompleted(Id))
|
||||
{
|
||||
State = MissionState.Completed;
|
||||
return;
|
||||
}
|
||||
|
||||
State = MissionState.Active;
|
||||
OnActivated?.Invoke();
|
||||
Reevaluate(); // 이미 조건이 갖춰져 있을 수도 있다 (물건을 미리 옮겨둔 경우)
|
||||
}
|
||||
|
||||
private void Reevaluate()
|
||||
{
|
||||
if (State != MissionState.Active) return;
|
||||
foreach (var objective in _objectives)
|
||||
if (!objective.IsComplete) return;
|
||||
Complete();
|
||||
}
|
||||
|
||||
private void Complete()
|
||||
{
|
||||
State = MissionState.Completed;
|
||||
|
||||
var story = StoryManager.Instance;
|
||||
bool firstTime = story.MarkMissionCompleted(Id);
|
||||
if (firstTime && _progressOnComplete > 0)
|
||||
story.MainProgress += _progressOnComplete;
|
||||
story.Save();
|
||||
|
||||
OnCompleted?.Invoke();
|
||||
Debug.Log($"[Mission] 완료: {Id}");
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Mission/Mission.cs.meta
Normal file
2
Assets/02_Scripts/Mission/Mission.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d878a406b99ed614bbbce6a14c14f8c2
|
||||
16
Assets/02_Scripts/Mission/MissionObjective.cs
Normal file
16
Assets/02_Scripts/Mission/MissionObjective.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
// 미션 목표 하나의 베이스. Mission 오브젝트의 자식으로 두면 자동 수집된다.
|
||||
// 새 종류의 미션이 필요하면 이걸 상속해서 IsComplete 구현 + 상태가 바뀔 때
|
||||
// RaiseChanged()만 호출하면 된다 (예: 물건 옮기기, 특정 위치 방문, NPC에게 아이템 전달).
|
||||
public abstract class MissionObjective : MonoBehaviour
|
||||
{
|
||||
// 목표 달성 여부 — 되돌릴 수 있는 목표(물건을 도로 빼는 등)는 false로 돌아가도 된다.
|
||||
// 미션 완료는 "모든 목표가 동시에 true"인 순간 Mission 쪽에서 확정(래치)된다.
|
||||
public abstract bool IsComplete { get; }
|
||||
|
||||
// 달성 상태가 바뀔 때마다 발행 — Mission이 구독해서 완료를 재판정한다
|
||||
public event Action Changed;
|
||||
protected void RaiseChanged() => Changed?.Invoke();
|
||||
}
|
||||
2
Assets/02_Scripts/Mission/MissionObjective.cs.meta
Normal file
2
Assets/02_Scripts/Mission/MissionObjective.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6486c131b8f11624fbd409cea08cbbde
|
||||
67
Assets/02_Scripts/Mission/MoveItemsToZoneObjective.cs
Normal file
67
Assets/02_Scripts/Mission/MoveItemsToZoneObjective.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
// "지정한 물건들을 전부 이 존 안으로 옮기기" 목표.
|
||||
// 트리거 콜라이더가 붙은 존 오브젝트에 추가하고 물건들을 목록에 등록한다.
|
||||
// 물건 쪽에 Rigidbody가 있어야 트리거 이벤트가 발생한다 (XRGrabInteractable이면 이미 있음).
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class MoveItemsToZoneObjective : MissionObjective
|
||||
{
|
||||
[Tooltip("이 존 안으로 옮겨야 하는 물건들 (씬 오브젝트 루트)")]
|
||||
[SerializeField] private List<Transform> _requiredItems = new();
|
||||
|
||||
[Tooltip("들어온 개수가 바뀔 때마다 (현재 개수, 전체 개수) — 진행 UI 연결용")]
|
||||
public UnityEvent<int, int> OnCountChanged;
|
||||
|
||||
[Tooltip("대사 텍스트에서 {키}로 진행 상황을 쓰려면 키 이름 지정 (값 예: \"3/5\"). 비우면 안 씀")]
|
||||
[SerializeField] private string _dialogVariableKey;
|
||||
|
||||
private readonly HashSet<Transform> _inside = new();
|
||||
|
||||
public int Count => _inside.Count;
|
||||
public int Total => _requiredItems.Count;
|
||||
public override bool IsComplete => Count >= Total;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var col = GetComponent<Collider>();
|
||||
if (!col.isTrigger)
|
||||
Debug.LogWarning($"[MoveItemsToZoneObjective] 콜라이더가 트리거가 아님: {name}");
|
||||
}
|
||||
|
||||
private void Start() => Publish(); // UI/대사 변수 초기값 (0/N)
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
var item = FindRequiredItem(other);
|
||||
if (item != null && _inside.Add(item)) Publish();
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
var item = FindRequiredItem(other);
|
||||
if (item != null && _inside.Remove(item)) Publish();
|
||||
}
|
||||
|
||||
// 콜라이더에서 등록된 물건을 찾는다 (자식 콜라이더 대응 — 부모로 거슬러 올라가며 확인)
|
||||
private Transform FindRequiredItem(Collider col)
|
||||
{
|
||||
var t = col.attachedRigidbody != null ? col.attachedRigidbody.transform : col.transform;
|
||||
while (t != null)
|
||||
{
|
||||
if (_requiredItems.Contains(t)) return t;
|
||||
t = t.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Publish()
|
||||
{
|
||||
_inside.RemoveWhere(t => t == null); // 파괴된 물건 정리
|
||||
if (!string.IsNullOrEmpty(_dialogVariableKey))
|
||||
DialogVariables.Set(_dialogVariableKey, $"{Count}/{Total}");
|
||||
OnCountChanged?.Invoke(Count, Total);
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c0d5ca4a604f5142873570997cb5fac
|
||||
Reference in New Issue
Block a user