2026-07-08 이벤트존 추가
This commit is contained in:
@@ -19,6 +19,9 @@ public class DialogCondition
|
||||
[Tooltip("골랐어야 하는 선택지 Code들 (전부 필요)")]
|
||||
public List<string> RequiredChoiceCodes = new();
|
||||
|
||||
[Tooltip("밟았어야 하는 이벤트존 Id들 (전부 필요)")]
|
||||
public List<string> RequiredZoneIds = new();
|
||||
|
||||
// affectionTarget: 호감도 조건을 검사할 캐릭터 (보통 대화를 거는 NPC 자신)
|
||||
public bool IsMet(CharacterData affectionTarget)
|
||||
{
|
||||
@@ -38,6 +41,10 @@ public bool IsMet(CharacterData affectionTarget)
|
||||
if (!string.IsNullOrEmpty(code) && !story.HasChosen(code))
|
||||
return false;
|
||||
|
||||
foreach (var zoneId in RequiredZoneIds)
|
||||
if (!string.IsNullOrEmpty(zoneId) && !story.HasTriggeredZone(zoneId))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,15 @@ public void RecordChoice(string code)
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// ── 이벤트존 이력 ────────────────────────────────────────────
|
||||
public bool HasTriggeredZone(string zoneId) => _state.TriggeredZones.Contains(zoneId);
|
||||
|
||||
public void RecordZoneTriggered(string zoneId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(zoneId) || !_state.TriggeredZones.Add(zoneId)) return;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
// ── 저장 / 로드 ──────────────────────────────────────────────
|
||||
// 플레이 시작 시엔 항상 빈 상태로 시작한다 (테스트 반복이 꼬이지 않게).
|
||||
// 이어하기를 만들 때 타이틀 화면 등에서 Load()를 호출하면 된다.
|
||||
|
||||
104
Assets/02_Scripts/Story/EventZone.cs
Normal file
104
Assets/02_Scripts/Story/EventZone.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Locomotion;
|
||||
|
||||
// 트리거 존: Player 태그가 들어오면 등록된 타임라인을 재생.
|
||||
// 옵션으로 재생 동안 플레이어 로코모션(이동/회전)을 잠글 수 있다.
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class EventZone : MonoBehaviour
|
||||
{
|
||||
[Tooltip("플레이어 진입 시 재생할 타임라인")]
|
||||
[SerializeField] private PlayableDirector _timeline;
|
||||
|
||||
[Tooltip("StoryState에 기록되는 Id. 비워두면 오브젝트 이름 사용 (DialogCondition의 RequiredZoneIds에서 참조)")]
|
||||
[SerializeField] private string _zoneId;
|
||||
|
||||
[Header("옵션")]
|
||||
[Tooltip("재생 동안 플레이어 조작(로코모션)을 잠글지 여부. HMD 트래킹은 유지된다")]
|
||||
[SerializeField] private bool _lockPlayerControl = true;
|
||||
|
||||
[Tooltip("한 번만 재생. 끄면 재생이 끝난 뒤 다시 들어오면 또 재생된다")]
|
||||
[SerializeField] private bool _playOnce = true;
|
||||
|
||||
private bool _hasPlayed;
|
||||
private bool _isPlaying;
|
||||
|
||||
public string ZoneId => string.IsNullOrEmpty(_zoneId) ? name : _zoneId;
|
||||
|
||||
// 잠글 때 켜져 있던 프로바이더만 기록해서, 해제 시 원래 꺼져 있던 것까지 켜지 않도록 한다.
|
||||
private readonly List<LocomotionProvider> _lockedProviders = new();
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
GetComponent<Collider>().isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!other.CompareTag("Player")) return;
|
||||
|
||||
// 이어하기 로드 후에도 한 번 밟은 존은 다시 재생되지 않도록 StoryState 기록도 함께 본다.
|
||||
var story = StoryManager.Instance; // 매니저 없는 테스트 씬에서도 재생은 되도록 null 허용
|
||||
if (_isPlaying || (_playOnce && (_hasPlayed || (story != null && story.HasTriggeredZone(ZoneId)))))
|
||||
return;
|
||||
|
||||
// 밟은 사실은 대화 활성화 조건(RequiredZoneIds) 판정에 쓰이므로 타임라인 유무와 무관하게 기록
|
||||
if (story != null)
|
||||
story.RecordZoneTriggered(ZoneId);
|
||||
_hasPlayed = true;
|
||||
|
||||
if (_timeline == null)
|
||||
{
|
||||
Debug.LogWarning($"[EventZone] 타임라인이 등록되지 않음 (기록만 됨): {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
_isPlaying = true;
|
||||
|
||||
if (_lockPlayerControl)
|
||||
LockPlayer(other.transform);
|
||||
|
||||
_timeline.stopped += OnTimelineStopped;
|
||||
_timeline.Play();
|
||||
}
|
||||
|
||||
// 타임라인이 끝(또는 외부에서 Stop)나면 호출됨. Extrapolation이 Hold면 stopped가 오지 않으니 None 권장.
|
||||
private void OnTimelineStopped(PlayableDirector director)
|
||||
{
|
||||
director.stopped -= OnTimelineStopped;
|
||||
_isPlaying = false;
|
||||
UnlockPlayer();
|
||||
}
|
||||
|
||||
// Player 태그 루트(XR Origin) 아래의 로코모션 프로바이더를 전부 꺼서 이동/회전을 막는다.
|
||||
private void LockPlayer(Transform playerRoot)
|
||||
{
|
||||
_lockedProviders.Clear();
|
||||
foreach (var provider in playerRoot.GetComponentsInChildren<LocomotionProvider>())
|
||||
{
|
||||
if (!provider.enabled) continue;
|
||||
provider.enabled = false;
|
||||
_lockedProviders.Add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnlockPlayer()
|
||||
{
|
||||
foreach (var provider in _lockedProviders)
|
||||
{
|
||||
if (provider != null)
|
||||
provider.enabled = true;
|
||||
}
|
||||
_lockedProviders.Clear();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_timeline != null)
|
||||
_timeline.stopped -= OnTimelineStopped;
|
||||
|
||||
// 존이 재생 중에 파괴돼도 플레이어가 잠긴 채 남지 않도록
|
||||
UnlockPlayer();
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Story/EventZone.cs.meta
Normal file
2
Assets/02_Scripts/Story/EventZone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a1da05e6a37ca14db9e919cb2293e49
|
||||
@@ -10,6 +10,7 @@ public class StoryState
|
||||
public readonly Dictionary<string, int> Affection = new(); // 캐릭터 Id → 호감도
|
||||
public readonly HashSet<string> CompletedDialogs = new(); // 완료한 DialogGroup 이름
|
||||
public readonly HashSet<string> ChosenCodes = new(); // 골랐던 선택지 Code
|
||||
public readonly HashSet<string> TriggeredZones = new(); // 밟았던 EventZone Id
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
@@ -17,6 +18,7 @@ public void Clear()
|
||||
Affection.Clear();
|
||||
CompletedDialogs.Clear();
|
||||
ChosenCodes.Clear();
|
||||
TriggeredZones.Clear();
|
||||
}
|
||||
|
||||
// ── JSON 변환 ────────────────────────────────────────────────
|
||||
@@ -29,6 +31,7 @@ private class JsonData
|
||||
public List<int> AffectionValues = new();
|
||||
public List<string> CompletedDialogs = new();
|
||||
public List<string> ChosenCodes = new();
|
||||
public List<string> TriggeredZones = new();
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
@@ -41,6 +44,7 @@ public string ToJson()
|
||||
}
|
||||
data.CompletedDialogs.AddRange(CompletedDialogs);
|
||||
data.ChosenCodes.AddRange(ChosenCodes);
|
||||
data.TriggeredZones.AddRange(TriggeredZones);
|
||||
return JsonUtility.ToJson(data, prettyPrint: true);
|
||||
}
|
||||
|
||||
@@ -55,6 +59,7 @@ public static StoryState FromJson(string json)
|
||||
state.Affection[data.AffectionIds[i]] = data.AffectionValues[i];
|
||||
state.CompletedDialogs.UnionWith(data.CompletedDialogs);
|
||||
state.ChosenCodes.UnionWith(data.ChosenCodes);
|
||||
state.TriggeredZones.UnionWith(data.TriggeredZones);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user