feat: 添加场景过渡类型支持,优化场景加载逻辑
This commit is contained in:
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;
|
||||
|
||||
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;
|
||||
/// <summary>玩家出生时的朝向(+1 右,-1 左)。</summary>
|
||||
public int FacingDirection => _facingDirection;
|
||||
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,10 +88,12 @@ namespace BaseGames.World
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = new Color(0f, 1f, 0.5f, 0.6f);
|
||||
var col = GetComponent<Collider2D>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user