using System.Collections; using BaseGames.Feedback; using BaseGames.Player; using UnityEngine; namespace BaseGames.World { /// /// 同场景成对门传送组件。两扇门互相引用,玩家进入其中一扇门时被传送到另一扇。 /// /// 触发流程: /// /// 玩家进入触发器或按交互键 → 播放 转场反馈(淡出) /// 等待反馈完成后传送玩家到 位置,并设定朝向 /// 播放 转场反馈(淡入),目标门进入冷却 /// /// /// 不涉及场景加载;适用于同房间内不同小区域之间的传送门、电梯、秘道等。 /// [RequireComponent(typeof(Collider2D))] public class LinkedDoorTransition : MonoBehaviour, IInteractable { [Header("配对")] [Tooltip("与此门配对的另一扇门。玩家进入本门后传送到此目标门的出生点。")] [SerializeField] private LinkedDoorTransition _linkedDoor; [Header("出生配置")] [Tooltip("传送到此门后玩家的出生位置。拖动场景中的子节点 SpawnPoint 来调整。\n为空时回退到 GameObject 中心。")] [SerializeField] private Transform _spawnPoint; [Tooltip("传送到此门后玩家的朝向(+1 = 朝右,-1 = 朝左)。")] [SerializeField] private int _facingDirectionOnArrive = 1; [Header("触发方式")] [Tooltip("true = 玩家进入触发器自动触发;false = 需玩家按交互键。")] [SerializeField] private bool _autoTrigger = false; [Header("转场反馈")] [Tooltip("传送前播放(淡出)。留空则跳过,直接传送。")] [SerializeField] private SceneFeedback _transitionOut; [Tooltip("传送后播放(淡入)。留空则跳过。由目标门在玩家到达后自动播放。")] [SerializeField] private SceneFeedback _transitionIn; [Header("钥匙物品校验")] [SerializeField] private bool _requiresKeyItem; [SerializeField] private string _requiredItemId; [Header("世界状态")] [SerializeField] private WorldStateRegistry _worldState; [Header("冷却")] [Tooltip("传送完成后本门的冷却时间(秒)。防止玩家被反复来回传送。")] [SerializeField] private float _cooldown = 1.5f; private bool _triggered; private float _cooldownUntil; // ── IInteractable ───────────────────────────────────────────────────── public bool CanInteract => !_autoTrigger; public string InteractPrompt => "进入"; public void Interact(Transform player) => TryTrigger(player); public void OnPlayerEnterRange(Transform player) { } public void OnPlayerExitRange() { } // ── 触发 ────────────────────────────────────────────────────────────── private void OnTriggerEnter2D(Collider2D other) { if (!_autoTrigger) return; if (!other.CompareTag("Player")) return; TryTrigger(other.transform); } private void TryTrigger(Transform player) { if (_triggered) return; if (Time.time < _cooldownUntil) return; if (_linkedDoor == null) { Debug.LogWarning($"[LinkedDoorTransition] {name}:未配置 _linkedDoor,传送中止。"); return; } if (_requiresKeyItem && !HasItem(_requiredItemId)) return; _triggered = true; StartCoroutine(TransitionCoroutine(player)); } private void OnDisable() => _triggered = false; // ── 传送流程 ────────────────────────────────────────────────────────── private IEnumerator TransitionCoroutine(Transform player) { // 1. 淡出 if (_transitionOut != null) yield return _transitionOut.PlayAndWait(); // 2. 传送 player.position = _linkedDoor.GetSpawnWorldPosition(); // 通过 PlayerMovement 设定朝向(同步清零速度,防止旧速度覆盖朝向) var movement = player.GetComponentInChildren() ?? player.GetComponent(); if (movement != null) movement.SetFacingImmediate(_linkedDoor._facingDirectionOnArrive); // 4. 淡入(目标门播放)+ 冷却 _linkedDoor.PlayTransitionIn(); _linkedDoor.StartCooldown(_cooldown); _triggered = false; } /// 播放此门的淡入反馈。由传送发起方在玩家到达后调用。 public void PlayTransitionIn() => _transitionIn?.Play(); /// 启动传送冷却,在此期间本门不会再次触发传送。 public void StartCooldown(float duration) => _cooldownUntil = Time.time + duration; // ── 辅助 ────────────────────────────────────────────────────────────── /// 返回传送目标位置。优先使用 _spawnPoint 子节点,否则回退到门中心。 public Vector3 GetSpawnWorldPosition() => _spawnPoint != null ? _spawnPoint.position : transform.position; private bool HasItem(string itemId) { if (string.IsNullOrEmpty(itemId)) return true; if (_worldState == null) { Debug.LogWarning($"[LinkedDoorTransition] WorldStateRegistry 未配置,钥匙 {itemId} 检查跳过"); return false; } return _worldState.IsCollected(itemId); } // ── Gizmos ──────────────────────────────────────────────────────────── #if UNITY_EDITOR private void OnDrawGizmos() { var col = GetComponent(); if (col != null) { Gizmos.color = new Color(0.2f, 0.9f, 0.9f, 0.7f); Gizmos.DrawWireCube(col.bounds.center, col.bounds.size); Gizmos.color = new Color(0.2f, 0.9f, 0.9f, 0.15f); Gizmos.DrawCube(col.bounds.center, col.bounds.size); } Vector3 spawn = GetSpawnWorldPosition(); Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(spawn, 0.2f); if (_linkedDoor != null) { Gizmos.color = new Color(0.2f, 0.9f, 0.9f, 0.8f); Gizmos.DrawLine(spawn, _linkedDoor.GetSpawnWorldPosition()); } Vector3 labelPos = col != null ? col.bounds.center + Vector3.up * (col.bounds.extents.y + 0.3f) : transform.position + Vector3.up * 1.5f; string facing = _facingDirectionOnArrive >= 0 ? "→" : "←"; string trigger = _autoTrigger ? "Auto" : "Interact"; string linkName = _linkedDoor != null ? _linkedDoor.name : "未配对"; string keyInfo = _requiresKeyItem ? $" 🔑{_requiredItemId}" : ""; string grandparent = transform.parent != null ? $"[{transform.parent.name}] " : ""; string label = $"{grandparent}{name}\n→ {linkName} | {trigger} | 到达朝向:{facing}{keyInfo}"; UnityEditor.Handles.color = Color.white; UnityEditor.Handles.Label(labelPos, label); } #endif } }