feat: 添加场景过渡类型支持,优化场景加载逻辑
This commit is contained in:
@@ -56,6 +56,7 @@ namespace BaseGames.Core
|
||||
{
|
||||
SceneName = sm?.LastCheckpointScene,
|
||||
EntryTransitionId = sm?.LastCheckpointSpawnId,
|
||||
TransitionType = TransitionType.Scene,
|
||||
ShowLoadingScreen = true,
|
||||
IsRespawn = true,
|
||||
});
|
||||
|
||||
@@ -12,7 +12,10 @@ namespace BaseGames.Core.Events
|
||||
public string EntryId;
|
||||
/// <summary>玩家出生点 Transition ID(具体过渡门 ID,可为 null)</summary>
|
||||
public string EntryTransitionId;
|
||||
/// <summary>是否显示加载画面</summary>
|
||||
/// <summary>过渡类型,决定 <see cref="BaseGames.Core.SceneService"/> 的演出行为(淡出时长、加载画面等)。
|
||||
/// 默认 <see cref="TransitionType.Room"/>,向后兼容旧请求。</summary>
|
||||
public TransitionType TransitionType;
|
||||
/// <summary>是否显示加载画面(由 TransitionType 自动推导,通常无需手动设置)。</summary>
|
||||
public bool ShowLoadingScreen;
|
||||
/// <summary>死亡复活时为 true,不执行正常过渡动画</summary>
|
||||
public bool IsRespawn;
|
||||
|
||||
16
Assets/_Game/Scripts/Core/Events/TransitionType.cs
Normal file
16
Assets/_Game/Scripts/Core/Events/TransitionType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 场景过渡类型,决定 <see cref="BaseGames.Core.SceneService"/> 的演出行为。
|
||||
/// </summary>
|
||||
public enum TransitionType
|
||||
{
|
||||
/// <summary>同区域相邻房间切换。极短淡出(≈0.05 s),无加载画面,相机硬切。
|
||||
/// 适用于走廊边界、隐藏通道等玩家感知连续的场景边界。</summary>
|
||||
Room,
|
||||
|
||||
/// <summary>跨大区域切换。完整淡出,显示加载画面。
|
||||
/// 适用于地图间传送、返回标题、大区域入口等有明显空间跳跃感的切换。</summary>
|
||||
Scene,
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/TransitionType.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/TransitionType.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a9a71d44467e124ea9e7cda40603f30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -19,7 +19,11 @@ namespace BaseGames.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景管理服务。
|
||||
/// 场景管理服务。根据 <see cref="SceneLoadRequest.TransitionType"/> 执行不同演出:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Room</b>:极短淡出(<see cref="_roomFadeDuration"/>),无加载画面。</item>
|
||||
/// <item><b>Scene</b>:完整淡出(<see cref="_sceneFadeDuration"/>),显示加载画面。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[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
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace BaseGames.Challenge
|
||||
{
|
||||
SceneName = _challengeSceneName,
|
||||
EntryTransitionId = string.Empty,
|
||||
TransitionType = TransitionType.Scene,
|
||||
ShowLoadingScreen = false,
|
||||
IsRespawn = false,
|
||||
});
|
||||
|
||||
@@ -113,6 +113,7 @@ namespace BaseGames.Support.AntiSoftlock
|
||||
{
|
||||
SceneName = scene,
|
||||
EntryId = spawn,
|
||||
TransitionType = TransitionType.Scene,
|
||||
ShowLoadingScreen = true,
|
||||
IsRespawn = true,
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace BaseGames.UI
|
||||
_onSceneLoadRequest?.Raise(new SceneLoadRequest
|
||||
{
|
||||
SceneName = "MainMenu",
|
||||
TransitionType = TransitionType.Scene,
|
||||
ShowLoadingScreen = true
|
||||
});
|
||||
}
|
||||
|
||||
142
Assets/_Game/Scripts/World/DoorTransition.cs
Normal file
142
Assets/_Game/Scripts/World/DoorTransition.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using BaseGames.Core.Events;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.World
|
||||
{
|
||||
/// <summary>
|
||||
/// 门过渡组件。挂载在有物理门对象的 GameObject 上。
|
||||
/// <para>
|
||||
/// 触发流程:
|
||||
/// <list type="number">
|
||||
/// <item>玩家进入触发器或按交互键 → 播放 <see cref="_openClip"/> 开门动画</item>
|
||||
/// <item>动画完成后发出 <see cref="SceneLoadRequest"/>,类型由 <see cref="_transitionType"/> 决定</item>
|
||||
/// <item>目标场景出生点侧的 <see cref="DoorTransition"/> 被 <see cref="PlayerSpawnPoint"/> 引用,
|
||||
/// 由外部调用 <see cref="PlayEnterAnimation"/> 播放玩家进入门的过渡动画</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 若不需要门动画,直接使用 <see cref="RoomTransition"/> 即可。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[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,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 玩家从门内侧走出时的动画(由出生点侧的 <see cref="PlayerSpawnPoint"/> 在玩家出生后调用)。
|
||||
/// 若未配置 <see cref="_enterClip"/> 则静默跳过。
|
||||
/// </summary>
|
||||
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<Collider2D>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/World/DoorTransition.cs.meta
Normal file
11
Assets/_Game/Scripts/World/DoorTransition.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a0d07af1ccd0284889188cf32516e1b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -12,16 +12,36 @@ namespace BaseGames.World
|
||||
[Tooltip("+1 = 朝右出生,-1 = 朝左出生")]
|
||||
[SerializeField] private int _facingDirection = 1;
|
||||
|
||||
[Tooltip("(可选)此出生点对应的出口门。\n" +
|
||||
"玩家在此出生后,调用 TriggerExitDoor() 播放门的进入动画(玩家从门里走出)。\n" +
|
||||
"仅在使用 DoorTransition 的场景中需要配置。")]
|
||||
[SerializeField] private DoorTransition _exitDoor;
|
||||
|
||||
public string TransitionId => _transitionId;
|
||||
public Vector2 SpawnPosition => transform.position;
|
||||
/// <summary>玩家出生时的朝向(+1 右,-1 左)。</summary>
|
||||
public int FacingDirection => _facingDirection;
|
||||
/// <summary>此出生点关联的出口门(可为 null)。</summary>
|
||||
public DoorTransition ExitDoor => _exitDoor;
|
||||
|
||||
/// <summary>
|
||||
/// 触发出口门的进入动画(玩家从门内侧走出)。
|
||||
/// 无关联门时静默跳过。由负责放置玩家的系统在玩家落点后调用。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,15 @@ using UnityEngine;
|
||||
namespace BaseGames.World
|
||||
{
|
||||
/// <summary>
|
||||
/// 房间传送点。玩家进入触发器或按交互键时,广播 <see cref="SceneLoadRequest"/>,
|
||||
/// 由 SceneLoader 监听并执行 Additive 场景加载/卸载。
|
||||
/// 房间/场景过渡触发器。玩家进入触发器或按交互键时,广播 <see cref="SceneLoadRequest"/>。
|
||||
/// <para>
|
||||
/// 通过 <see cref="_transitionType"/> 控制过渡演出:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Room</b>:极短淡出,无加载画面,适合相邻房间边界。</item>
|
||||
/// <item><b>Scene</b>:完整淡出 + 加载画面,适合大区域切换。</item>
|
||||
/// </list>
|
||||
/// 如需门动画,请改用 <see cref="DoorTransition"/> 组件,它会在动画完成后内部触发本组件。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[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()
|
||||
/// <summary>触发过渡。可由触发器、交互系统或 <see cref="DoorTransition"/> 调用。</summary>
|
||||
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,9 +88,11 @@ namespace BaseGames.World
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = new Color(0f, 1f, 0.5f, 0.6f);
|
||||
var col = GetComponent<Collider2D>();
|
||||
if (col != null)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user