111 lines
4.0 KiB
C#
111 lines
4.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.XR.Interaction.Toolkit.Locomotion;
|
|
|
|
// 트리거 존(이벤트): Player 태그가 들어오면 등록된 UnityEvent를 실행.
|
|
// (타임라인을 재생하는 존은 EventZoneTimeline)
|
|
[RequireComponent(typeof(Collider))]
|
|
public class EventZoneTrigger : MonoBehaviour
|
|
{
|
|
[Tooltip("StoryState에 기록되는 Id. 비워두면 오브젝트 이름 사용 (DialogCondition의 RequiredZoneIds에서 참조)")]
|
|
[SerializeField] private string _zoneId;
|
|
|
|
[Tooltip("한 번만 발동. 끄면 나갔다 다시 들어오면 또 발동한다")]
|
|
[SerializeField] private bool _triggerOnce = true;
|
|
|
|
[Tooltip("플레이어 진입 시 실행할 이벤트")]
|
|
[SerializeField] private UnityEvent _onEnter;
|
|
|
|
[Header("플레이어 잠금")]
|
|
[Tooltip("발동 시 플레이어 조작(로코모션)을 잠글지 여부. HMD 트래킹은 유지된다. " +
|
|
"타임라인이 없으므로 잠금 시간은 아래 초로 지정")]
|
|
[SerializeField] private bool _lockPlayerControl = false;
|
|
|
|
[Tooltip("잠금 유지 시간(초). 시간이 지나면 자동으로 풀린다")]
|
|
[SerializeField, Min(0f)] private float _lockDuration = 3f;
|
|
|
|
private bool _hasTriggered;
|
|
private bool _isLocking;
|
|
|
|
// 존이 실제로 발동(기록)되는 순간 ZoneId와 함께 호출 (SceneBgm 등에서 구독)
|
|
public static event Action<string> OnZoneTriggered;
|
|
|
|
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 (_triggerOnce && (_hasTriggered || (story != null && story.HasTriggeredZone(ZoneId))))
|
|
return;
|
|
|
|
// 밟은 사실은 대화 활성화 조건(RequiredZoneIds) 판정에 쓰이므로 기록
|
|
if (story != null)
|
|
story.RecordZoneTriggered(ZoneId);
|
|
_hasTriggered = true;
|
|
OnZoneTriggered?.Invoke(ZoneId);
|
|
|
|
if (_lockPlayerControl && _lockDuration > 0f && !_isLocking)
|
|
_ = LockPlayerFor(other.transform, _lockDuration);
|
|
|
|
_onEnter?.Invoke();
|
|
}
|
|
|
|
// 지정 시간 동안 플레이어 로코모션을 잠갔다가 자동 해제
|
|
private async Awaitable LockPlayerFor(Transform playerRoot, float duration)
|
|
{
|
|
_isLocking = true;
|
|
LockPlayer(playerRoot);
|
|
try
|
|
{
|
|
await Awaitable.WaitForSecondsAsync(duration, destroyCancellationToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return; // 파괴(씬 전환)로 취소됨 — OnDestroy가 해제를 처리
|
|
}
|
|
UnlockPlayer();
|
|
_isLocking = false;
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
// 잠금 중에 파괴돼도 플레이어 잠금이 남지 않도록
|
|
UnlockPlayer();
|
|
}
|
|
}
|