linkdoor
This commit is contained in:
@@ -7686,7 +7686,7 @@ MonoBehaviour:
|
||||
_input: {fileID: 0}
|
||||
_formController: {fileID: 0}
|
||||
_modifiers: {fileID: 0}
|
||||
_skillSocket: {fileID: 0}
|
||||
_skillSocket: {fileID: 676050686}
|
||||
_formSkillSets: []
|
||||
--- !u!114 &430284920
|
||||
MonoBehaviour:
|
||||
@@ -7716,7 +7716,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 +7750,6 @@ MonoBehaviour:
|
||||
_groundLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 128
|
||||
_spriteRenderer: {fileID: 0}
|
||||
_dbg_Position:
|
||||
_dbg_VelocityX: 0
|
||||
_dbg_VelocityY: 0
|
||||
@@ -8078,6 +8077,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
_transitionId: default
|
||||
_facingDirection: 1
|
||||
_exitDoor: {fileID: 0}
|
||||
--- !u!4 &442873752
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -19133,7 +19133,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
|
||||
@@ -26350,7 +26350,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
|
||||
|
||||
@@ -499,6 +499,110 @@ 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();
|
||||
|
||||
// ── 门 A ─────────────────────────────────────────────────────────
|
||||
GameObject goA = new GameObject("LinkedDoor_A");
|
||||
Undo.RegisterCreatedObjectUndo(goA, "Place 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);
|
||||
|
||||
SetupSpriteRenderer(goA);
|
||||
GetOrAddComponent<Animator>(goA);
|
||||
AnimancerComponent animancerA = GetOrAddComponent<AnimancerComponent>(goA);
|
||||
|
||||
LinkedDoorTransition doorA = GetOrAddComponent<LinkedDoorTransition>(goA);
|
||||
AssignBool(doorA, "_autoTrigger", true);
|
||||
AssignReference(doorA, "_animancer", animancerA, report);
|
||||
AssignInt(doorA, "_facingDirectionOnArrive", 1); // 朝右走出
|
||||
|
||||
// ── 门 B ─────────────────────────────────────────────────────────
|
||||
GameObject goB = new GameObject("LinkedDoor_B");
|
||||
Undo.RegisterCreatedObjectUndo(goB, "Place 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);
|
||||
|
||||
SetupSpriteRenderer(goB);
|
||||
GetOrAddComponent<Animator>(goB);
|
||||
AnimancerComponent animancerB = GetOrAddComponent<AnimancerComponent>(goB);
|
||||
|
||||
LinkedDoorTransition doorB = GetOrAddComponent<LinkedDoorTransition>(goB);
|
||||
AssignBool(doorB, "_autoTrigger", true);
|
||||
AssignReference(doorB, "_animancer", animancerB, 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("_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);
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Camera Area", priority = 140)]
|
||||
public static void PlaceCameraArea() => PlaceCameraArea("CameraArea");
|
||||
|
||||
|
||||
186
Assets/_Game/Scripts/World/LinkedDoorTransition.cs
Normal file
186
Assets/_Game/Scripts/World/LinkedDoorTransition.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.World
|
||||
{
|
||||
/// <summary>
|
||||
/// 同场景成对门传送组件。两扇门互相引用,玩家进入其中一扇门时被传送到另一扇。
|
||||
/// <para>
|
||||
/// 触发流程:
|
||||
/// <list type="number">
|
||||
/// <item>玩家进入触发器或按交互键 → 播放 <see cref="_openClip"/> 开门动画(本门)</item>
|
||||
/// <item>传送玩家到 <see cref="_linkedDoor"/> 的出生位置,并设定朝向</item>
|
||||
/// <item>目标门播放 <see cref="_enterClip"/> 玩家走出动画</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>不涉及场景加载;适用于同房间内不同小区域之间的传送门、电梯、秘道等。</para>
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class LinkedDoorTransition : MonoBehaviour, IInteractable
|
||||
{
|
||||
[Header("配对")]
|
||||
[Tooltip("与此门配对的另一扇门。玩家进入本门后传送到此目标门的出生点。")]
|
||||
[SerializeField] private LinkedDoorTransition _linkedDoor;
|
||||
|
||||
[Header("出生配置")]
|
||||
[Tooltip("传送后玩家在此门的出生位置偏移(相对于本 GameObject 中心,默认 0)。")]
|
||||
[SerializeField] private Vector2 _spawnOffset = new Vector2(0f, 0f);
|
||||
|
||||
[Tooltip("传送到此门后玩家的朝向(+1 = 朝右,-1 = 朝左)。")]
|
||||
[SerializeField] private int _facingDirectionOnArrive = 1;
|
||||
|
||||
[Header("触发方式")]
|
||||
[Tooltip("true = 玩家进入触发器自动触发;false = 需玩家按交互键。")]
|
||||
[SerializeField] private bool _autoTrigger = false;
|
||||
|
||||
[Header("门动画")]
|
||||
[SerializeField] private AnimancerComponent _animancer;
|
||||
|
||||
[Tooltip("玩家从外侧进入时的开门动画。留空则跳过。")]
|
||||
[SerializeField] private AnimationClip _openClip;
|
||||
|
||||
[Tooltip("玩家从内侧走出时的动画(目标门调用)。留空则跳过。")]
|
||||
[SerializeField] private AnimationClip _enterClip;
|
||||
|
||||
[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 (_animancer != null && _openClip != null)
|
||||
{
|
||||
var state = _animancer.Play(_openClip);
|
||||
yield return state;
|
||||
}
|
||||
|
||||
// 2. 传送玩家到目标门
|
||||
Vector3 destination = _linkedDoor.GetSpawnWorldPosition();
|
||||
player.position = destination;
|
||||
|
||||
// 朝向(通过 localScale X 翻转,或通知 PlayerController)
|
||||
ApplyFacing(player, _linkedDoor._facingDirectionOnArrive);
|
||||
|
||||
// 3. 目标门播放玩家走出动画,并进入冷却
|
||||
_linkedDoor.PlayEnterAnimation();
|
||||
_linkedDoor.StartCooldown(_cooldown);
|
||||
|
||||
_triggered = false;
|
||||
}
|
||||
|
||||
/// <summary>玩家从门内侧走出时的动画。由传送发起方在传送完成后调用。</summary>
|
||||
public void PlayEnterAnimation()
|
||||
{
|
||||
if (_animancer != null && _enterClip != null)
|
||||
_animancer.Play(_enterClip);
|
||||
}
|
||||
|
||||
/// <summary>启动传送冷却,在此期间本门不会再次触发传送。</summary>
|
||||
public void StartCooldown(float duration)
|
||||
{
|
||||
_cooldownUntil = Time.time + duration;
|
||||
}
|
||||
|
||||
// ── 辅助 ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>返回传送目标门的出生世界坐标(含偏移)。</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
var col = GetComponent<Collider2D>();
|
||||
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);
|
||||
}
|
||||
|
||||
// 出生点
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user