diff --git a/Assets/_Game/Scripts/Core/DeathRespawnService.cs b/Assets/_Game/Scripts/Core/DeathRespawnService.cs
index 24baf42..e05d9f5 100644
--- a/Assets/_Game/Scripts/Core/DeathRespawnService.cs
+++ b/Assets/_Game/Scripts/Core/DeathRespawnService.cs
@@ -56,6 +56,7 @@ namespace BaseGames.Core
{
SceneName = sm?.LastCheckpointScene,
EntryTransitionId = sm?.LastCheckpointSpawnId,
+ TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
IsRespawn = true,
});
diff --git a/Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs b/Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs
index cfb8a9f..ab52775 100644
--- a/Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs
+++ b/Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs
@@ -12,7 +12,10 @@ namespace BaseGames.Core.Events
public string EntryId;
/// 玩家出生点 Transition ID(具体过渡门 ID,可为 null)
public string EntryTransitionId;
- /// 是否显示加载画面
+ /// 过渡类型,决定 的演出行为(淡出时长、加载画面等)。
+ /// 默认 ,向后兼容旧请求。
+ public TransitionType TransitionType;
+ /// 是否显示加载画面(由 TransitionType 自动推导,通常无需手动设置)。
public bool ShowLoadingScreen;
/// 死亡复活时为 true,不执行正常过渡动画
public bool IsRespawn;
diff --git a/Assets/_Game/Scripts/Core/Events/TransitionType.cs b/Assets/_Game/Scripts/Core/Events/TransitionType.cs
new file mode 100644
index 0000000..223b780
--- /dev/null
+++ b/Assets/_Game/Scripts/Core/Events/TransitionType.cs
@@ -0,0 +1,16 @@
+namespace BaseGames.Core.Events
+{
+ ///
+ /// 场景过渡类型,决定 的演出行为。
+ ///
+ public enum TransitionType
+ {
+ /// 同区域相邻房间切换。极短淡出(≈0.05 s),无加载画面,相机硬切。
+ /// 适用于走廊边界、隐藏通道等玩家感知连续的场景边界。
+ Room,
+
+ /// 跨大区域切换。完整淡出,显示加载画面。
+ /// 适用于地图间传送、返回标题、大区域入口等有明显空间跳跃感的切换。
+ Scene,
+ }
+}
diff --git a/Assets/_Game/Scripts/Core/Events/TransitionType.cs.meta b/Assets/_Game/Scripts/Core/Events/TransitionType.cs.meta
new file mode 100644
index 0000000..c142fb3
--- /dev/null
+++ b/Assets/_Game/Scripts/Core/Events/TransitionType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7a9a71d44467e124ea9e7cda40603f30
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Game/Scripts/Core/SceneService.cs b/Assets/_Game/Scripts/Core/SceneService.cs
index c91a876..98baa3d 100644
--- a/Assets/_Game/Scripts/Core/SceneService.cs
+++ b/Assets/_Game/Scripts/Core/SceneService.cs
@@ -19,7 +19,11 @@ namespace BaseGames.Core
}
///
- /// 场景管理服务。
+ /// 场景管理服务。根据 执行不同演出:
+ ///
+ /// - Room:极短淡出(),无加载画面。
+ /// - Scene:完整淡出(),显示加载画面。
+ ///
///
[DefaultExecutionOrder(-900)]
public class SceneService : MonoBehaviour, ISceneService
@@ -32,7 +36,13 @@ namespace BaseGames.Core
[SerializeField] private VoidEventChannelSO _onFadeOutRequest;
[SerializeField] private SceneLoader _sceneLoader;
- [SerializeField] private float _fadeDuration = 0.3f;
+
+ [Header("淡出时长")]
+ [Tooltip("Room 过渡:极短淡出,用于相邻房间边界切换(推荐 0.05 s)。")]
+ [SerializeField] private float _roomFadeDuration = 0.05f;
+
+ [Tooltip("Scene 过渡:完整淡出,用于大区域/地图间切换(推荐 0.4 s)。")]
+ [SerializeField] private float _sceneFadeDuration = 0.4f;
private readonly CompositeDisposable _subscriptions = new();
@@ -48,8 +58,13 @@ namespace BaseGames.Core
public IEnumerator LoadSceneCoroutine(SceneLoadRequest request)
{
+ float fadeDuration = request.TransitionType == TransitionType.Scene
+ ? _sceneFadeDuration
+ : _roomFadeDuration;
+
_onFadeOutRequest?.Raise();
- yield return new WaitForSeconds(_fadeDuration);
+ if (fadeDuration > 0f)
+ yield return new WaitForSeconds(fadeDuration);
if (_sceneLoader != null)
yield return StartCoroutine(_sceneLoader.LoadSceneCoroutine(request));
@@ -71,6 +86,7 @@ namespace BaseGames.Core
{
SceneName = AddressKeys.SceneMainMenu,
EntryTransitionId = null,
+ TransitionType = TransitionType.Scene,
ShowLoadingScreen = false,
IsRespawn = false
});
diff --git a/Assets/_Game/Scripts/Quest/ChallengeRoomTrigger.cs b/Assets/_Game/Scripts/Quest/ChallengeRoomTrigger.cs
index d287ff7..74e4cd6 100644
--- a/Assets/_Game/Scripts/Quest/ChallengeRoomTrigger.cs
+++ b/Assets/_Game/Scripts/Quest/ChallengeRoomTrigger.cs
@@ -30,6 +30,7 @@ namespace BaseGames.Challenge
{
SceneName = _challengeSceneName,
EntryTransitionId = string.Empty,
+ TransitionType = TransitionType.Scene,
ShowLoadingScreen = false,
IsRespawn = false,
});
diff --git a/Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs b/Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs
index 0547257..2524802 100644
--- a/Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs
+++ b/Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs
@@ -111,10 +111,11 @@ namespace BaseGames.Support.AntiSoftlock
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
- SceneName = scene,
- EntryId = spawn,
+ SceneName = scene,
+ EntryId = spawn,
+ TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
- IsRespawn = true,
+ IsRespawn = true,
});
}
diff --git a/Assets/_Game/Scripts/UI/Menus/PauseMenuController.cs b/Assets/_Game/Scripts/UI/Menus/PauseMenuController.cs
index 9970d4e..48c4527 100644
--- a/Assets/_Game/Scripts/UI/Menus/PauseMenuController.cs
+++ b/Assets/_Game/Scripts/UI/Menus/PauseMenuController.cs
@@ -51,7 +51,8 @@ namespace BaseGames.UI
_uiManager.CloseTopPanel();
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
- SceneName = "MainMenu",
+ SceneName = "MainMenu",
+ TransitionType = TransitionType.Scene,
ShowLoadingScreen = true
});
}
diff --git a/Assets/_Game/Scripts/World/DoorTransition.cs b/Assets/_Game/Scripts/World/DoorTransition.cs
new file mode 100644
index 0000000..98cdd45
--- /dev/null
+++ b/Assets/_Game/Scripts/World/DoorTransition.cs
@@ -0,0 +1,142 @@
+using System.Collections;
+using Animancer;
+using BaseGames.Core.Events;
+using UnityEngine;
+
+namespace BaseGames.World
+{
+ ///
+ /// 门过渡组件。挂载在有物理门对象的 GameObject 上。
+ ///
+ /// 触发流程:
+ ///
+ /// - 玩家进入触发器或按交互键 → 播放 开门动画
+ /// - 动画完成后发出 ,类型由 决定
+ /// - 目标场景出生点侧的 被 引用,
+ /// 由外部调用 播放玩家进入门的过渡动画
+ ///
+ ///
+ ///
+ /// 若不需要门动画,直接使用 即可。
+ ///
+ ///
+ [RequireComponent(typeof(Collider2D))]
+ public class DoorTransition : MonoBehaviour, IInteractable
+ {
+ [Header("目标")]
+ [SerializeField] private string _targetSceneAddress;
+ [SerializeField] private string _targetTransitionId;
+
+ [Header("过渡类型")]
+ [Tooltip("Room:极短淡出,无加载画面。\nScene:完整淡出 + 加载画面。")]
+ [SerializeField] private TransitionType _transitionType = TransitionType.Room;
+
+ [Header("触发方式")]
+ [Tooltip("true = 玩家进入触发器自动触发;false = 需要玩家按交互键。")]
+ [SerializeField] private bool _autoTrigger = false;
+
+ [Header("门动画")]
+ [SerializeField] private AnimancerComponent _animancer;
+
+ [Tooltip("玩家从外侧进入时播放的开门动画(出口侧)。留空则跳过动画直接过渡。")]
+ [SerializeField] private AnimationClip _openClip;
+
+ [Tooltip("玩家从内侧走出时播放的动画(入口侧,由 PlayerSpawnPoint 在玩家出生后触发)。留空则跳过。")]
+ [SerializeField] private AnimationClip _enterClip;
+
+ [Header("钥匙物品校验")]
+ [SerializeField] private bool _requiresKeyItem;
+ [SerializeField] private string _requiredItemId;
+
+ [Header("事件频道")]
+ [SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
+
+ [Header("世界状态")]
+ [SerializeField] private WorldStateRegistry _worldState;
+
+ private bool _triggered;
+
+ // ── IInteractable ─────────────────────────────────────────────────────
+
+ public bool CanInteract => !_autoTrigger;
+ public string InteractPrompt => "进入";
+
+ public void Interact(Transform player) => TryTrigger();
+
+ public void OnPlayerEnterRange(Transform player) { }
+ public void OnPlayerExitRange() { }
+
+ // ── 触发 ──────────────────────────────────────────────────────────────
+
+ private void OnTriggerEnter2D(Collider2D other)
+ {
+ if (!_autoTrigger) return;
+ if (!other.CompareTag("Player")) return;
+ TryTrigger();
+ }
+
+ private void TryTrigger()
+ {
+ if (_triggered) return;
+ if (_requiresKeyItem && !HasItem(_requiredItemId)) return;
+ _triggered = true;
+ StartCoroutine(OpenAndTransition());
+ }
+
+ private void OnDisable() => _triggered = false;
+
+ // ── 动画流程 ──────────────────────────────────────────────────────────
+
+ private IEnumerator OpenAndTransition()
+ {
+ if (_animancer != null && _openClip != null)
+ {
+ var state = _animancer.Play(_openClip);
+ yield return state; // 等待动画完成
+ }
+
+ _onSceneLoadRequest?.Raise(new SceneLoadRequest
+ {
+ SceneName = _targetSceneAddress,
+ EntryTransitionId = _targetTransitionId,
+ TransitionType = _transitionType,
+ ShowLoadingScreen = _transitionType == TransitionType.Scene,
+ IsRespawn = false,
+ });
+ }
+
+ ///
+ /// 玩家从门内侧走出时的动画(由出生点侧的 在玩家出生后调用)。
+ /// 若未配置 则静默跳过。
+ ///
+ public void PlayEnterAnimation()
+ {
+ if (_animancer != null && _enterClip != null)
+ _animancer.Play(_enterClip);
+ }
+
+ // ── 辅助 ──────────────────────────────────────────────────────────────
+
+ private bool HasItem(string itemId)
+ {
+ if (string.IsNullOrEmpty(itemId)) return true;
+ if (_worldState == null)
+ {
+ Debug.LogWarning($"[DoorTransition] WorldStateRegistry 未配置,钥匙 {itemId} 检查跳过");
+ return false;
+ }
+ return _worldState.IsCollected(itemId);
+ }
+
+ private void OnDrawGizmos()
+ {
+ var col = GetComponent();
+ if (col == null) return;
+ // 紫色区分门过渡
+ Gizmos.color = new Color(0.7f, 0.3f, 1f, 0.7f);
+ Gizmos.DrawWireCube(transform.position, col.bounds.size);
+ Gizmos.color = new Color(0.7f, 0.3f, 1f, 0.3f);
+ Gizmos.DrawCube(transform.position, col.bounds.size);
+ }
+ }
+}
diff --git a/Assets/_Game/Scripts/World/DoorTransition.cs.meta b/Assets/_Game/Scripts/World/DoorTransition.cs.meta
new file mode 100644
index 0000000..6678d53
--- /dev/null
+++ b/Assets/_Game/Scripts/World/DoorTransition.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8a0d07af1ccd0284889188cf32516e1b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Game/Scripts/World/PlayerSpawnPoint.cs b/Assets/_Game/Scripts/World/PlayerSpawnPoint.cs
index 985f7f1..e187b47 100644
--- a/Assets/_Game/Scripts/World/PlayerSpawnPoint.cs
+++ b/Assets/_Game/Scripts/World/PlayerSpawnPoint.cs
@@ -12,16 +12,36 @@ namespace BaseGames.World
[Tooltip("+1 = 朝右出生,-1 = 朝左出生")]
[SerializeField] private int _facingDirection = 1;
- public string TransitionId => _transitionId;
- public Vector2 SpawnPosition => transform.position;
+ [Tooltip("(可选)此出生点对应的出口门。\n" +
+ "玩家在此出生后,调用 TriggerExitDoor() 播放门的进入动画(玩家从门里走出)。\n" +
+ "仅在使用 DoorTransition 的场景中需要配置。")]
+ [SerializeField] private DoorTransition _exitDoor;
+
+ public string TransitionId => _transitionId;
+ public Vector2 SpawnPosition => transform.position;
/// 玩家出生时的朝向(+1 右,-1 左)。
- public int FacingDirection => _facingDirection;
+ public int FacingDirection => _facingDirection;
+ /// 此出生点关联的出口门(可为 null)。
+ public DoorTransition ExitDoor => _exitDoor;
+
+ ///
+ /// 触发出口门的进入动画(玩家从门内侧走出)。
+ /// 无关联门时静默跳过。由负责放置玩家的系统在玩家落点后调用。
+ ///
+ public void TriggerExitDoor() => _exitDoor?.PlayEnterAnimation();
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, 0.3f);
Gizmos.DrawLine(transform.position, transform.position + Vector3.up * 0.5f);
+
+ // 门关联指示线
+ if (_exitDoor != null)
+ {
+ Gizmos.color = new Color(0.7f, 0.3f, 1f, 0.6f);
+ Gizmos.DrawLine(transform.position, _exitDoor.transform.position);
+ }
}
}
}
diff --git a/Assets/_Game/Scripts/World/RoomTransition.cs b/Assets/_Game/Scripts/World/RoomTransition.cs
index e096418..5359447 100644
--- a/Assets/_Game/Scripts/World/RoomTransition.cs
+++ b/Assets/_Game/Scripts/World/RoomTransition.cs
@@ -4,8 +4,15 @@ using UnityEngine;
namespace BaseGames.World
{
///
- /// 房间传送点。玩家进入触发器或按交互键时,广播 ,
- /// 由 SceneLoader 监听并执行 Additive 场景加载/卸载。
+ /// 房间/场景过渡触发器。玩家进入触发器或按交互键时,广播 。
+ ///
+ /// 通过 控制过渡演出:
+ ///
+ /// - Room:极短淡出,无加载画面,适合相邻房间边界。
+ /// - Scene:完整淡出 + 加载画面,适合大区域切换。
+ ///
+ /// 如需门动画,请改用 组件,它会在动画完成后内部触发本组件。
+ ///
///
[RequireComponent(typeof(Collider2D))]
public class RoomTransition : MonoBehaviour, IInteractable
@@ -17,6 +24,11 @@ namespace BaseGames.World
[SerializeField] private string _targetSceneAddress; // Addressable key(目标场景)
[SerializeField] private string _targetTransitionId; // 目标房间出生点 ID
+ [Header("过渡类型")]
+ [Tooltip("Room:极短淡出,无加载画面,相邻房间边界专用。\n" +
+ "Scene:完整淡出 + 加载画面,大区域/地图间切换专用。")]
+ [SerializeField] private TransitionType _transitionType = TransitionType.Room;
+
[Header("触发方式")]
[SerializeField] private bool _autoTrigger = true; // true = 玩家进入触发器自动触发
@@ -32,7 +44,7 @@ namespace BaseGames.World
// ── IInteractable ─────────────────────────────────────────────────────
public bool CanInteract => !_autoTrigger;
- public string InteractPrompt => "前往下一区域";
+ public string InteractPrompt => _transitionType == TransitionType.Scene ? "前往下一区域" : "进入";
public void Interact(Transform player) => RequestTransition();
@@ -47,7 +59,8 @@ namespace BaseGames.World
RequestTransition();
}
- private void RequestTransition()
+ /// 触发过渡。可由触发器、交互系统或 调用。
+ public void RequestTransition()
{
if (_requiresKeyItem && !HasItem(_requiredItemId)) return;
@@ -55,7 +68,8 @@ namespace BaseGames.World
{
SceneName = _targetSceneAddress,
EntryTransitionId = _targetTransitionId,
- ShowLoadingScreen = true,
+ TransitionType = _transitionType,
+ ShowLoadingScreen = _transitionType == TransitionType.Scene,
IsRespawn = false,
});
}
@@ -66,7 +80,7 @@ namespace BaseGames.World
if (string.IsNullOrEmpty(itemId)) return true;
if (_worldState == null)
{
- Debug.LogWarning($"[RoomTransition] WorldStateRegistry 未配置,销 {itemId} 检查跳过");
+ Debug.LogWarning($"[RoomTransition] WorldStateRegistry 未配置,钥匙 {itemId} 检查跳过");
return false;
}
return _worldState.IsCollected(itemId);
@@ -74,10 +88,12 @@ namespace BaseGames.World
private void OnDrawGizmos()
{
- Gizmos.color = new Color(0f, 1f, 0.5f, 0.6f);
var col = GetComponent();
- if (col != null)
- Gizmos.DrawWireCube(transform.position, col.bounds.size);
+ if (col == null) return;
+ Gizmos.color = _transitionType == TransitionType.Scene
+ ? new Color(1f, 0.6f, 0f, 0.6f) // 橙色:大区域切换
+ : new Color(0f, 1f, 0.5f, 0.6f); // 绿色:房间切换
+ Gizmos.DrawWireCube(transform.position, col.bounds.size);
}
}
}