From aac24d825f3d199871be0f9a970a96a4efc3df49 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Tue, 19 May 2026 16:04:40 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4linkdoor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/_Game/Scenes/Testings/TestRoomA.unity | 302 ++++++++++++++++++ .../Editor/Scene/SceneObjectPlacerTool.cs | 37 ++- .../_Game/Scripts/Feedback/SceneFeedback.cs | 15 +- Assets/_Game/Scripts/Player/PlayerMovement.cs | 12 + .../Scripts/World/LinkedDoorTransition.cs | 105 +++--- .../World/LinkedDoorTransition.cs.meta | 11 + 6 files changed, 410 insertions(+), 72 deletions(-) create mode 100644 Assets/_Game/Scripts/World/LinkedDoorTransition.cs.meta diff --git a/Assets/_Game/Scenes/Testings/TestRoomA.unity b/Assets/_Game/Scenes/Testings/TestRoomA.unity index 1a9d34a..2ed888b 100644 --- a/Assets/_Game/Scenes/Testings/TestRoomA.unity +++ b/Assets/_Game/Scenes/Testings/TestRoomA.unity @@ -5413,6 +5413,37 @@ MonoBehaviour: _noiseFrequency: 1 _dedicatedCamera: {fileID: 2004004504} _dedicatedPriority: 20 +--- !u!1 &282308594 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 282308595} + m_Layer: 0 + m_Name: SpawnPoint + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &282308595 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 282308594} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.2, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1526156801} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &284070736 GameObject: m_ObjectHideFlags: 0 @@ -14318,6 +14349,107 @@ MonoBehaviour: BarrelClipping: 0.25 Anamorphism: 0 BlendHint: 0 +--- !u!1 &840725877 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 840725878} + - component: {fileID: 840725880} + - component: {fileID: 840725879} + m_Layer: 12 + m_Name: LinkedDoor_B + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &840725878 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 840725877} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 3, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1722322723} + m_Father: {fileID: 941514249} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &840725879 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 840725877} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8fc461876b6b1e44788334d304144e18, type: 3} + m_Name: + m_EditorClassIdentifier: + _linkedDoor: {fileID: 1526156799} + _spawnPoint: {fileID: 1722322723} + _facingDirectionOnArrive: -1 + _autoTrigger: 1 + _transitionOut: {fileID: 0} + _transitionIn: {fileID: 0} + _requiresKeyItem: 0 + _requiredItemId: + _worldState: {fileID: 0} + _cooldown: 1.5 +--- !u!61 &840725880 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 840725877} + m_Enabled: 1 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 1 + m_UsedByEffector: 0 + m_UsedByComposite: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0, y: 0} + oldSize: {x: 0, y: 0} + newSize: {x: 0, y: 0} + adaptiveTilingThreshold: 0 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + serializedVersion: 2 + m_Size: {x: 1.5, y: 2.5} + m_EdgeRadius: 0 --- !u!1 &842908939 GameObject: m_ObjectHideFlags: 0 @@ -15086,6 +15218,39 @@ MonoBehaviour: _noiseFrequency: 1 _dedicatedCamera: {fileID: 1434238745} _dedicatedPriority: 20 +--- !u!1 &941514248 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 941514249} + m_Layer: 0 + m_Name: LinkedDoorPair + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &941514249 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 941514248} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -29.5, y: 2.9, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1526156801} + - {fileID: 840725878} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &945934489 GameObject: m_ObjectHideFlags: 0 @@ -21629,6 +21794,10 @@ PrefabInstance: propertyPath: m_IsActive value: 1 objectReference: {fileID: 0} + - target: {fileID: 84902510026916610, guid: f69fa61624ad24b45aea231f95b304f7, type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 84902510500730246, guid: f69fa61624ad24b45aea231f95b304f7, type: 3} propertyPath: m_IsActive value: 1 @@ -22399,6 +22568,107 @@ MonoBehaviour: _noiseFrequency: 1 _dedicatedCamera: {fileID: 119603425} _dedicatedPriority: 20 +--- !u!1 &1526156798 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1526156801} + - component: {fileID: 1526156800} + - component: {fileID: 1526156799} + m_Layer: 12 + m_Name: LinkedDoor_A + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1526156799 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1526156798} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8fc461876b6b1e44788334d304144e18, type: 3} + m_Name: + m_EditorClassIdentifier: + _linkedDoor: {fileID: 840725879} + _spawnPoint: {fileID: 282308595} + _facingDirectionOnArrive: 1 + _autoTrigger: 1 + _transitionOut: {fileID: 0} + _transitionIn: {fileID: 0} + _requiresKeyItem: 0 + _requiredItemId: + _worldState: {fileID: 0} + _cooldown: 1.5 +--- !u!61 &1526156800 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1526156798} + m_Enabled: 1 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 1 + m_UsedByEffector: 0 + m_UsedByComposite: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0, y: 0} + oldSize: {x: 0, y: 0} + newSize: {x: 0, y: 0} + adaptiveTilingThreshold: 0 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + serializedVersion: 2 + m_Size: {x: 1.5, y: 2.5} + m_EdgeRadius: 0 +--- !u!4 &1526156801 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1526156798} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -3, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 282308595} + m_Father: {fileID: 941514249} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1533332516 GameObject: m_ObjectHideFlags: 0 @@ -24645,6 +24915,37 @@ MonoBehaviour: _noiseFrequency: 1 _dedicatedCamera: {fileID: 1936840579} _dedicatedPriority: 20 +--- !u!1 &1722322722 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1722322723} + m_Layer: 0 + m_Name: SpawnPoint + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1722322723 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1722322722} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1.2, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 840725878} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1723674067 GameObject: m_ObjectHideFlags: 0 @@ -30831,3 +31132,4 @@ SceneRoots: - {fileID: 1354690328} - {fileID: 783576435} - {fileID: 430284931} + - {fileID: 941514249} diff --git a/Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs b/Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs index a98ef8a..1184f78 100644 --- a/Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs +++ b/Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs @@ -548,9 +548,15 @@ namespace BaseGames.Editor Vector3 basePos = GetDropPosition(); + // ── 共同父节点 ──────────────────────────────────────────────────── + GameObject parent = new GameObject("LinkedDoorPair"); + Undo.RegisterCreatedObjectUndo(parent, "Place LinkedDoorPair Root"); + parent.transform.position = basePos; + // ── 门 A ───────────────────────────────────────────────────────── GameObject goA = new GameObject("LinkedDoor_A"); Undo.RegisterCreatedObjectUndo(goA, "Place LinkedDoor_A"); + Undo.SetTransformParent(goA.transform, parent.transform, "Parent LinkedDoor_A"); goA.transform.position = basePos + new Vector3(-3f, 0f, 0f); SetLayer(goA, "TriggerZone", report); @@ -558,18 +564,18 @@ namespace BaseGames.Editor colA.isTrigger = true; colA.size = new Vector2(1.5f, 2.5f); - SetupSpriteRenderer(goA); - GetOrAddComponent(goA); - AnimancerComponent animancerA = GetOrAddComponent(goA); + Transform spawnA = GetOrCreateChild(goA.transform, "SpawnPoint"); + spawnA.localPosition = new Vector3(1.2f, 0f, 0f); // 门右侧走出 LinkedDoorTransition doorA = GetOrAddComponent(goA); AssignBool(doorA, "_autoTrigger", true); - AssignReference(doorA, "_animancer", animancerA, report); - AssignInt(doorA, "_facingDirectionOnArrive", 1); // 朝右走出 + AssignReference(doorA, "_spawnPoint", spawnA, report); + AssignInt(doorA, "_facingDirectionOnArrive", 1); // 朝右走出 // ── 门 B ───────────────────────────────────────────────────────── GameObject goB = new GameObject("LinkedDoor_B"); Undo.RegisterCreatedObjectUndo(goB, "Place LinkedDoor_B"); + Undo.SetTransformParent(goB.transform, parent.transform, "Parent LinkedDoor_B"); goB.transform.position = basePos + new Vector3(3f, 0f, 0f); SetLayer(goB, "TriggerZone", report); @@ -577,30 +583,27 @@ namespace BaseGames.Editor colB.isTrigger = true; colB.size = new Vector2(1.5f, 2.5f); - SetupSpriteRenderer(goB); - GetOrAddComponent(goB); - AnimancerComponent animancerB = GetOrAddComponent(goB); + Transform spawnB = GetOrCreateChild(goB.transform, "SpawnPoint"); + spawnB.localPosition = new Vector3(-1.2f, 0f, 0f); // 门左侧走出 LinkedDoorTransition doorB = GetOrAddComponent(goB); AssignBool(doorB, "_autoTrigger", true); - AssignReference(doorB, "_animancer", animancerB, report); - AssignInt(doorB, "_facingDirectionOnArrive", -1); // 朝左走出 + AssignReference(doorB, "_spawnPoint", spawnB, report); + AssignInt(doorB, "_facingDirectionOnArrive", -1); // 朝左走出 // ── 互相绑定 ───────────────────────────────────────────────────── AssignReference(doorA, "_linkedDoor", doorB, report); AssignReference(doorB, "_linkedDoor", doorA, report); - report.Add("LinkedDoor_A ↔ LinkedDoor_B 已互相绑定。"); - report.Add("将两扇门分别移到场景中正确位置(如两个区域的入口处)。"); - report.Add("调整 _spawnOffset 使玩家传送后不被触发器再次捕获(如偏移 1f 让玩家落在门外侧)。"); + report.Add("LinkedDoor_A ↔ LinkedDoor_B 已互相绑定,统一挂在 LinkedDoorPair 父节点下。"); + report.Add("将两扇门移到场景中正确位置后,拖动各自的子节点 SpawnPoint 调整玩家传送到达位置。"); + report.Add("转场效果:在各门 GameObject 上添加 SceneFeedback 组件并绑定 MMF_Player(如淡入淡出),再将其拖入 _transitionOut(淡出)和 _transitionIn(淡入)字段。"); report.Add("_facingDirectionOnArrive:A→B 时玩家朝向由 B 的该值决定,B→A 反之。"); - report.Add("若需门动画,将 AnimationClip 拖入各自的 _openClip / _enterClip。"); Undo.CollapseUndoOperations(undoGroup); - // 选中 A(便于立即调整位置) - Selection.activeGameObject = goA; - MarkDirtyAndLog("Linked Door Pair (Same-Scene)", goA, report); + Selection.activeGameObject = parent; + MarkDirtyAndLog("Linked Door Pair (Same-Scene)", parent, report); } [MenuItem("BaseGames/Scene/Place/Camera Area", priority = 140)] diff --git a/Assets/_Game/Scripts/Feedback/SceneFeedback.cs b/Assets/_Game/Scripts/Feedback/SceneFeedback.cs index ad56c55..289d0ae 100644 --- a/Assets/_Game/Scripts/Feedback/SceneFeedback.cs +++ b/Assets/_Game/Scripts/Feedback/SceneFeedback.cs @@ -1,3 +1,4 @@ +using System.Collections; using UnityEngine; using MoreMountains.Feedbacks; @@ -12,7 +13,7 @@ namespace BaseGames.Feedback /// 用法: /// 1. 在需要反馈的 GameObject 上添加 SceneFeedback 组件 /// 2. 在 Inspector 中将 MMF_Player 拖入 _player 槽位 - /// 3. 调用者持有 [SerializeField] SceneFeedback 引用,调用 .Play() + /// 3. 调用者持有 [SerializeField] SceneFeedback 引用,调用 .Play() 或 yield return .PlayAndWait() /// [AddComponentMenu("BaseGames/Feedback/Scene Feedback")] public class SceneFeedback : MonoBehaviour @@ -24,5 +25,17 @@ namespace BaseGames.Feedback /// 立即停止正在播放的反馈。 public void Stop() => _player?.StopFeedbacks(); + + /// + /// 播放反馈并等待其完成后继续。若 _player 未配置则立即返回。 + /// 用于需要等待淡出/淡入完成后再执行下一步的场景(如传送、开门等)。 + /// + public IEnumerator PlayAndWait() + { + if (_player == null) yield break; + _player.PlayFeedbacks(); + while (_player.IsPlaying) + yield return null; + } } } diff --git a/Assets/_Game/Scripts/Player/PlayerMovement.cs b/Assets/_Game/Scripts/Player/PlayerMovement.cs index 672f5d5..1d4a5ff 100644 --- a/Assets/_Game/Scripts/Player/PlayerMovement.cs +++ b/Assets/_Game/Scripts/Player/PlayerMovement.cs @@ -179,6 +179,18 @@ namespace BaseGames.Player transform.localScale = new Vector3(dir, 1f, 1f); } + /// + /// 强制设定朝向,并同步清零速度,确保传送/出生后不被旧速度立即覆盖。 + /// dir:+1 = 朝右,-1 = 朝左。 + /// + public void SetFacingImmediate(int dir) + { + if (dir == 0) return; + _facingDirection = dir; + transform.localScale = new Vector3(dir, 1f, 1f); + _rb.velocity = Vector2.zero; + } + // ── 取消窗口 ────────────────────────────────────────────────────────── public void SetCancelWindowOpen(bool open) => _cancelWindowOpen = open; diff --git a/Assets/_Game/Scripts/World/LinkedDoorTransition.cs b/Assets/_Game/Scripts/World/LinkedDoorTransition.cs index 1be9a89..c60f41c 100644 --- a/Assets/_Game/Scripts/World/LinkedDoorTransition.cs +++ b/Assets/_Game/Scripts/World/LinkedDoorTransition.cs @@ -1,5 +1,6 @@ using System.Collections; -using Animancer; +using BaseGames.Feedback; +using BaseGames.Player; using UnityEngine; namespace BaseGames.World @@ -9,9 +10,9 @@ namespace BaseGames.World /// /// 触发流程: /// - /// 玩家进入触发器或按交互键 → 播放 开门动画(本门) - /// 传送玩家到 的出生位置,并设定朝向 - /// 目标门播放 玩家走出动画 + /// 玩家进入触发器或按交互键 → 播放 转场反馈(淡出) + /// 等待反馈完成后传送玩家到 位置,并设定朝向 + /// 播放 转场反馈(淡入),目标门进入冷却 /// /// /// 不涉及场景加载;适用于同房间内不同小区域之间的传送门、电梯、秘道等。 @@ -24,8 +25,8 @@ namespace BaseGames.World [SerializeField] private LinkedDoorTransition _linkedDoor; [Header("出生配置")] - [Tooltip("传送后玩家在此门的出生位置偏移(相对于本 GameObject 中心,默认 0)。")] - [SerializeField] private Vector2 _spawnOffset = new Vector2(0f, 0f); + [Tooltip("传送到此门后玩家的出生位置。拖动场景中的子节点 SpawnPoint 来调整。\n为空时回退到 GameObject 中心。")] + [SerializeField] private Transform _spawnPoint; [Tooltip("传送到此门后玩家的朝向(+1 = 朝右,-1 = 朝左)。")] [SerializeField] private int _facingDirectionOnArrive = 1; @@ -34,14 +35,12 @@ namespace BaseGames.World [Tooltip("true = 玩家进入触发器自动触发;false = 需玩家按交互键。")] [SerializeField] private bool _autoTrigger = false; - [Header("门动画")] - [SerializeField] private AnimancerComponent _animancer; + [Header("转场反馈")] + [Tooltip("传送前播放(淡出)。留空则跳过,直接传送。")] + [SerializeField] private SceneFeedback _transitionOut; - [Tooltip("玩家从外侧进入时的开门动画。留空则跳过。")] - [SerializeField] private AnimationClip _openClip; - - [Tooltip("玩家从内侧走出时的动画(目标门调用)。留空则跳过。")] - [SerializeField] private AnimationClip _enterClip; + [Tooltip("传送后播放(淡入)。留空则跳过。由目标门在玩家到达后自动播放。")] + [SerializeField] private SceneFeedback _transitionIn; [Header("钥匙物品校验")] [SerializeField] private bool _requiresKeyItem; @@ -50,7 +49,6 @@ namespace BaseGames.World [Header("世界状态")] [SerializeField] private WorldStateRegistry _worldState; - // 传送后短暂冷却,防止玩家在目标门处被立即再次传回 [Header("冷却")] [Tooltip("传送完成后本门的冷却时间(秒)。防止玩家被反复来回传送。")] [SerializeField] private float _cooldown = 1.5f; @@ -98,54 +96,37 @@ namespace BaseGames.World private IEnumerator TransitionCoroutine(Transform player) { - // 1. 播放本门开门动画 - if (_animancer != null && _openClip != null) - { - var state = _animancer.Play(_openClip); - yield return state; - } + // 1. 淡出 + if (_transitionOut != null) + yield return _transitionOut.PlayAndWait(); - // 2. 传送玩家到目标门 - Vector3 destination = _linkedDoor.GetSpawnWorldPosition(); - player.position = destination; + // 2. 传送 + player.position = _linkedDoor.GetSpawnWorldPosition(); - // 朝向(通过 localScale X 翻转,或通知 PlayerController) - ApplyFacing(player, _linkedDoor._facingDirectionOnArrive); + // 通过 PlayerMovement 设定朝向(同步清零速度,防止旧速度覆盖朝向) + var movement = player.GetComponentInChildren() + ?? player.GetComponent(); + if (movement != null) + movement.SetFacingImmediate(_linkedDoor._facingDirectionOnArrive); - // 3. 目标门播放玩家走出动画,并进入冷却 - _linkedDoor.PlayEnterAnimation(); + // 4. 淡入(目标门播放)+ 冷却 + _linkedDoor.PlayTransitionIn(); _linkedDoor.StartCooldown(_cooldown); _triggered = false; } - /// 玩家从门内侧走出时的动画。由传送发起方在传送完成后调用。 - public void PlayEnterAnimation() - { - if (_animancer != null && _enterClip != null) - _animancer.Play(_enterClip); - } + /// 播放此门的淡入反馈。由传送发起方在玩家到达后调用。 + public void PlayTransitionIn() => _transitionIn?.Play(); /// 启动传送冷却,在此期间本门不会再次触发传送。 - public void StartCooldown(float duration) - { - _cooldownUntil = Time.time + duration; - } + public void StartCooldown(float duration) => _cooldownUntil = Time.time + duration; // ── 辅助 ────────────────────────────────────────────────────────────── - /// 返回传送目标门的出生世界坐标(含偏移)。 + /// 返回传送目标位置。优先使用 _spawnPoint 子节点,否则回退到门中心。 public Vector3 GetSpawnWorldPosition() - => transform.position + new Vector3(_spawnOffset.x, _spawnOffset.y, 0f); - - private static void ApplyFacing(Transform player, int facing) - { - if (facing == 0) return; - Vector3 s = player.localScale; - float absX = Mathf.Abs(s.x); - s.x = facing > 0 ? absX : -absX; - player.localScale = s; - } + => _spawnPoint != null ? _spawnPoint.position : transform.position; private bool HasItem(string itemId) { @@ -158,29 +139,45 @@ namespace BaseGames.World 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(transform.position, col.bounds.size); - Gizmos.color = new Color(0.2f, 0.9f, 0.9f, 0.2f); - Gizmos.DrawCube(transform.position, col.bounds.size); + 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 label = $"{name}\n→ {linkName} | {trigger} | 到达朝向:{facing}{keyInfo}"; + + UnityEditor.Handles.color = Color.white; + UnityEditor.Handles.Label(labelPos, label); } +#endif } } + diff --git a/Assets/_Game/Scripts/World/LinkedDoorTransition.cs.meta b/Assets/_Game/Scripts/World/LinkedDoorTransition.cs.meta new file mode 100644 index 0000000..197a664 --- /dev/null +++ b/Assets/_Game/Scripts/World/LinkedDoorTransition.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8fc461876b6b1e44788334d304144e18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: