Compare commits

...

2 Commits

Author SHA1 Message Date
aac24d825f 调整linkdoor 2026-05-19 16:04:40 +08:00
0559d2cd36 linkdoor 2026-05-19 15:30:27 +08:00
6 changed files with 634 additions and 6 deletions

View File

@@ -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
@@ -7686,7 +7717,7 @@ MonoBehaviour:
_input: {fileID: 0}
_formController: {fileID: 0}
_modifiers: {fileID: 0}
_skillSocket: {fileID: 0}
_skillSocket: {fileID: 676050686}
_formSkillSets: []
--- !u!114 &430284920
MonoBehaviour:
@@ -7716,7 +7747,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
_config: {fileID: 11400000, guid: 8f33c49c9bd20ac47a6867f1f19f24a9, type: 2}
_input: {fileID: 0}
_input: {fileID: 11400000, guid: 88fadef8bc554e04483edd7418d20aa2, type: 2}
_onFormChanged: {fileID: 0}
_onSkillSetChanged: {fileID: 0}
--- !u!114 &430284922
@@ -7750,7 +7781,6 @@ MonoBehaviour:
_groundLayer:
serializedVersion: 2
m_Bits: 128
_spriteRenderer: {fileID: 0}
_dbg_Position:
_dbg_VelocityX: 0
_dbg_VelocityY: 0
@@ -8078,6 +8108,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
_transitionId: default
_facingDirection: 1
_exitDoor: {fileID: 0}
--- !u!4 &442873752
Transform:
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
@@ -19133,7 +19298,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!61 &1354690326
BoxCollider2D:
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
@@ -26350,7 +26651,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!61 &1865796629
BoxCollider2D:
m_ObjectHideFlags: 0
@@ -30831,3 +31132,4 @@ SceneRoots:
- {fileID: 1354690328}
- {fileID: 783576435}
- {fileID: 430284931}
- {fileID: 941514249}

View File

@@ -499,6 +499,113 @@ namespace BaseGames.Editor
MarkDirtyAndLog("Room Transition", go, report);
}
[MenuItem("BaseGames/Scene/Place/Door Transition", priority = 141)]
public static void PlaceDoorTransition()
{
var report = new List<string>();
int undoGroup = Undo.GetCurrentGroup();
Undo.SetCurrentGroupName("Place Door Transition");
GameObject go = new GameObject("DoorTransition");
Undo.RegisterCreatedObjectUndo(go, "Place Door Transition");
go.transform.position = GetDropPosition();
SetLayer(go, "TriggerZone", report);
// 触发碰撞体(门宽×门高)
BoxCollider2D col = GetOrAddComponent<BoxCollider2D>(go);
col.isTrigger = true;
col.size = new Vector2(1.5f, 2.5f);
// 精灵渲染器 + 动画(门对象通常有外观与开关动画)
SetupSpriteRenderer(go);
GetOrAddComponent<Animator>(go);
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
// DoorTransition 组件
DoorTransition door = GetOrAddComponent<DoorTransition>(go);
AssignBool(door, "_autoTrigger", false); // 默认需玩家按交互键
AssignReference(door, "_animancer", animancer, report);
AssignAsset(door, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest");
report.Add("填写 _targetSceneAddress目标场景 Addressable Key与 _targetTransitionId目标 PlayerSpawnPoint 的 _transitionId。");
report.Add("将开门动画片段拖入 _openClip若目标场景有玩家从门中走出的动画拖入 _enterClip 并在目标场景 PlayerSpawnPoint._exitDoor 引用该侧的 DoorTransition。");
report.Add("过渡类型默认 Room极短淡出若跨大区域将 _transitionType 改为 Scene。");
report.Add("若需钥匙解锁,设 _requiresKeyItem = true 并填写 _requiredItemId。");
Undo.CollapseUndoOperations(undoGroup);
Selection.activeGameObject = go;
MarkDirtyAndLog("Door Transition", go, report);
}
[MenuItem("BaseGames/Scene/Place/Linked Door Pair (Same-Scene)", priority = 142)]
public static void PlaceLinkedDoorPair()
{
var report = new List<string>();
int undoGroup = Undo.GetCurrentGroup();
Undo.SetCurrentGroupName("Place Linked Door Pair");
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);
BoxCollider2D colA = GetOrAddComponent<BoxCollider2D>(goA);
colA.isTrigger = true;
colA.size = new Vector2(1.5f, 2.5f);
Transform spawnA = GetOrCreateChild(goA.transform, "SpawnPoint");
spawnA.localPosition = new Vector3(1.2f, 0f, 0f); // 门右侧走出
LinkedDoorTransition doorA = GetOrAddComponent<LinkedDoorTransition>(goA);
AssignBool(doorA, "_autoTrigger", true);
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);
BoxCollider2D colB = GetOrAddComponent<BoxCollider2D>(goB);
colB.isTrigger = true;
colB.size = new Vector2(1.5f, 2.5f);
Transform spawnB = GetOrCreateChild(goB.transform, "SpawnPoint");
spawnB.localPosition = new Vector3(-1.2f, 0f, 0f); // 门左侧走出
LinkedDoorTransition doorB = GetOrAddComponent<LinkedDoorTransition>(goB);
AssignBool(doorB, "_autoTrigger", true);
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 已互相绑定,统一挂在 LinkedDoorPair 父节点下。");
report.Add("将两扇门移到场景中正确位置后,拖动各自的子节点 SpawnPoint 调整玩家传送到达位置。");
report.Add("转场效果:在各门 GameObject 上添加 SceneFeedback 组件并绑定 MMF_Player如淡入淡出再将其拖入 _transitionOut淡出和 _transitionIn淡入字段。");
report.Add("_facingDirectionOnArriveA→B 时玩家朝向由 B 的该值决定B→A 反之。");
Undo.CollapseUndoOperations(undoGroup);
Selection.activeGameObject = parent;
MarkDirtyAndLog("Linked Door Pair (Same-Scene)", parent, report);
}
[MenuItem("BaseGames/Scene/Place/Camera Area", priority = 140)]
public static void PlaceCameraArea() => PlaceCameraArea("CameraArea");

View File

@@ -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()
/// </summary>
[AddComponentMenu("BaseGames/Feedback/Scene Feedback")]
public class SceneFeedback : MonoBehaviour
@@ -24,5 +25,17 @@ namespace BaseGames.Feedback
/// <summary>立即停止正在播放的反馈。</summary>
public void Stop() => _player?.StopFeedbacks();
/// <summary>
/// 播放反馈并等待其完成后继续。若 _player 未配置则立即返回。
/// 用于需要等待淡出/淡入完成后再执行下一步的场景(如传送、开门等)。
/// </summary>
public IEnumerator PlayAndWait()
{
if (_player == null) yield break;
_player.PlayFeedbacks();
while (_player.IsPlaying)
yield return null;
}
}
}

View File

@@ -179,6 +179,18 @@ namespace BaseGames.Player
transform.localScale = new Vector3(dir, 1f, 1f);
}
/// <summary>
/// 强制设定朝向,并同步清零速度,确保传送/出生后不被旧速度立即覆盖。
/// dir+1 = 朝右,-1 = 朝左。
/// </summary>
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;

View File

@@ -0,0 +1,183 @@
using System.Collections;
using BaseGames.Feedback;
using BaseGames.Player;
using UnityEngine;
namespace BaseGames.World
{
/// <summary>
/// 同场景成对门传送组件。两扇门互相引用,玩家进入其中一扇门时被传送到另一扇。
/// <para>
/// 触发流程:
/// <list type="number">
/// <item>玩家进入触发器或按交互键 → 播放 <see cref="_transitionOut"/> 转场反馈(淡出)</item>
/// <item>等待反馈完成后传送玩家到 <see cref="_linkedDoor"/> 的 <see cref="_spawnPoint"/> 位置,并设定朝向</item>
/// <item>播放 <see cref="_linkedDoor"/> 的 <see cref="_transitionIn"/> 转场反馈(淡入),目标门进入冷却</item>
/// </list>
/// </para>
/// <para>不涉及场景加载;适用于同房间内不同小区域之间的传送门、电梯、秘道等。</para>
/// </summary>
[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<PlayerMovement>()
?? player.GetComponent<PlayerMovement>();
if (movement != null)
movement.SetFacingImmediate(_linkedDoor._facingDirectionOnArrive);
// 4. 淡入(目标门播放)+ 冷却
_linkedDoor.PlayTransitionIn();
_linkedDoor.StartCooldown(_cooldown);
_triggered = false;
}
/// <summary>播放此门的淡入反馈。由传送发起方在玩家到达后调用。</summary>
public void PlayTransitionIn() => _transitionIn?.Play();
/// <summary>启动传送冷却,在此期间本门不会再次触发传送。</summary>
public void StartCooldown(float duration) => _cooldownUntil = Time.time + duration;
// ── 辅助 ──────────────────────────────────────────────────────────────
/// <summary>返回传送目标位置。优先使用 _spawnPoint 子节点,否则回退到门中心。</summary>
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<Collider2D>();
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 label = $"{name}\n→ {linkName} | {trigger} | 到达朝向:{facing}{keyInfo}";
UnityEditor.Handles.color = Color.white;
UnityEditor.Handles.Label(labelPos, label);
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8fc461876b6b1e44788334d304144e18
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: