This commit is contained in:
2026-05-19 15:30:27 +08:00
parent ee0f659c97
commit 0559d2cd36
3 changed files with 295 additions and 5 deletions

View File

@@ -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

View File

@@ -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("_facingDirectionOnArriveA→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");

View 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());
}
}
}
}