From b6d19e8a8c3317b4f5d3b885e7bf27b6ffbd6115 Mon Sep 17 00:00:00 2001
From: Ding Yuntian <1491671119@qq.com>
Date: Thu, 26 Feb 2026 15:42:27 +0800
Subject: [PATCH] =?UTF-8?q?Timeline=E5=B7=A5=E5=85=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../AssetGroups/ScriptalObjects.asset | 5 -
.../AssetGroups/石头维修相关资源.asset | 9 +-
Assets/Scenes/网格测试.unity | 10 +-
.../Framework/ResourceKit/ResourceKit.cs | 27 +++-
.../TimelineKit/DirectorHandler.cs | 123 +++++++++++++++---
.../TimelineKit/TimelineCenter.cs | 113 +++++++++++-----
6 files changed, 225 insertions(+), 62 deletions(-)
diff --git a/Assets/AddressableAssetsData/AssetGroups/ScriptalObjects.asset b/Assets/AddressableAssetsData/AssetGroups/ScriptalObjects.asset
index 24e7c7f71..de53018c8 100644
--- a/Assets/AddressableAssetsData/AssetGroups/ScriptalObjects.asset
+++ b/Assets/AddressableAssetsData/AssetGroups/ScriptalObjects.asset
@@ -33,11 +33,6 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- - m_GUID: 9a92289d23953c445ba2d385f1698f30
- m_Address: BlockPuzzleValidtor
- m_ReadOnly: 0
- m_SerializedLabels: []
- FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
diff --git a/Assets/AddressableAssetsData/AssetGroups/石头维修相关资源.asset b/Assets/AddressableAssetsData/AssetGroups/石头维修相关资源.asset
index 06edba259..84d19f384 100644
--- a/Assets/AddressableAssetsData/AssetGroups/石头维修相关资源.asset
+++ b/Assets/AddressableAssetsData/AssetGroups/石头维修相关资源.asset
@@ -23,7 +23,7 @@ MonoBehaviour:
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3bd393f446c1ad24bb37d23570d508fd
- m_Address: "Timeline/\u5361\u6263\u89E3\u9501"
+ m_Address: "Timeline/\u7EF4\u4FEE\u9762\u677F/\u5361\u6263\u89E3\u9501"
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
@@ -43,7 +43,12 @@ MonoBehaviour:
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 03e5e9a1437535042ae48b2ba034dd93
- m_Address: BlockShape
+ m_Address: BlockPuzzle/BlockShape
+ m_ReadOnly: 0
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: 9a92289d23953c445ba2d385f1698f30
+ m_Address: BlockPuzzleValidtor
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
diff --git a/Assets/Scenes/网格测试.unity b/Assets/Scenes/网格测试.unity
index 8bf882013..b30b90b08 100644
--- a/Assets/Scenes/网格测试.unity
+++ b/Assets/Scenes/网格测试.unity
@@ -5353,7 +5353,15 @@ MonoBehaviour:
m_EditorClassIdentifier:
timelineName: "\u7EF4\u4FEE\u9762\u677F"
director: {fileID: 1492619800}
- timelineList: []
+ timelineList:
+ - m_AssetGUID: a541678fe3c38c645b11bb878371e602
+ m_SubObjectName:
+ m_SubObjectType:
+ m_EditorAssetChanged: 0
+ - m_AssetGUID: 3bd393f446c1ad24bb37d23570d508fd
+ m_SubObjectName:
+ m_SubObjectType:
+ m_EditorAssetChanged: 0
--- !u!1 &1532984430
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs b/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs
index 01f27ab84..3da8bec7a 100644
--- a/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs
+++ b/Assets/Scripts/Framework/ResourceKit/ResourceKit.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
@@ -94,6 +94,31 @@ namespace AibisDream.Framework
};
}
+ ///
+ /// 异步加载 Addressable 资源,callback 接收完整 handle。调用方需在适当时机调用 Release 释放。
+ ///
+ public static void LoadAssetAsyncWithHandle(string key, Action> callback) where T : UnityEngine.Object
+ {
+ var handle = Addressables.LoadAssetAsync(key);
+ handle.Completed += operation =>
+ {
+ if (operation.Status != AsyncOperationStatus.Succeeded)
+ {
+ Debug.Log($"资源{key}不存在");
+ }
+ callback(operation);
+ };
+ }
+
+ ///
+ /// 释放 Addressable 加载的 handle
+ ///
+ public static void Release(AsyncOperationHandle handle)
+ {
+ if (handle.IsValid())
+ Addressables.Release(handle);
+ }
+
#if UNITY_EDITOR
public static bool GetAssetAddressableKey(T asset, out string addressableKey) where T : UnityEngine.Object
{
diff --git a/Assets/Scripts/SceneManagement/TimelineKit/DirectorHandler.cs b/Assets/Scripts/SceneManagement/TimelineKit/DirectorHandler.cs
index 962fcda19..423dfed25 100644
--- a/Assets/Scripts/SceneManagement/TimelineKit/DirectorHandler.cs
+++ b/Assets/Scripts/SceneManagement/TimelineKit/DirectorHandler.cs
@@ -1,21 +1,22 @@
using System;
using System.Collections;
-using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
-using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+using AibisDream.Framework;
namespace AibisDream
{
public class DirectorHandler : MonoBehaviour
{
+ private const string TimelinePrefix = "Timeline/";
+
public string timelineName;
[SerializeField] private PlayableDirector director;
- [Header("多条Timeline")]
- [SerializeField] private AssetReference[] timelineList;
-
- private Dictionary _timelineDict = new();
+
private Action _onCompleteCallback;
+ private AsyncOperationHandle _addressableHandle;
+ private PlayableAsset _savedPlayableAsset;
#if UNITY_EDITOR
///
@@ -36,9 +37,6 @@ namespace AibisDream
{
director = GetComponent();
}
-
- // timelineList转字典
-
}
private void Start()
@@ -54,6 +52,21 @@ namespace AibisDream
{
director.stopped -= OnDirectorStopped;
}
+
+ ReleaseAddressableHandle();
+ }
+
+ private void ReleaseAddressableHandle()
+ {
+ if (!_addressableHandle.IsValid()) return;
+
+ if (director != null && director.playableAsset == _addressableHandle.Result)
+ {
+ director.playableAsset = _savedPlayableAsset;
+ }
+
+ ResourceKit.Release(_addressableHandle);
+ _addressableHandle = default;
}
///
@@ -103,7 +116,7 @@ namespace AibisDream
}
director.Play();
-
+
// 等待播放完成
while (director.state == PlayState.Playing)
{
@@ -111,18 +124,92 @@ namespace AibisDream
}
}
+ ///
+ /// 从 Addressables 加载 PlayableAsset 并播放(加载时自动加 "Timeline/" 前缀)
+ ///
+ public void PlayFromAddressable(string addressableKey, Action onComplete = null)
+ {
+ StartCoroutine(PlayFromAddressableCoroutine(addressableKey, onComplete));
+ }
+
+ ///
+ /// 从 Addressables 加载 PlayableAsset 并播放(协程版本,可等待播完)
+ ///
+ public IEnumerator PlayFromAddressableAsync(string addressableKey)
+ {
+ yield return PlayFromAddressableCoroutine(addressableKey, null);
+ }
+
+ private IEnumerator PlayFromAddressableCoroutine(string addressableKey, Action onComplete)
+ {
+ if (director == null)
+ {
+ Debug.LogError($"DirectorHandler {timelineName}: PlayableDirector is null");
+ yield break;
+ }
+
+ if (string.IsNullOrEmpty(addressableKey))
+ {
+ Debug.LogError($"DirectorHandler {timelineName}: addressableKey is null or empty");
+ yield break;
+ }
+
+ // 播放新 Addressable 前,先释放上一次
+ ReleaseAddressableHandle();
+
+ var loadKey = TimelinePrefix + addressableKey;
+ PlayableAsset loadedAsset = null;
+ AsyncOperationHandle loadedHandle = default;
+ var loadDone = false;
+
+ ResourceKit.LoadAssetAsyncWithHandle(loadKey, handle =>
+ {
+ loadedHandle = handle;
+ if (handle.Status == AsyncOperationStatus.Succeeded)
+ loadedAsset = handle.Result;
+ loadDone = true;
+ });
+
+ yield return new WaitUntil(() => loadDone);
+
+ if (loadedAsset == null)
+ {
+ Debug.LogError($"DirectorHandler {timelineName}: Failed to load PlayableAsset '{loadKey}'");
+ yield break;
+ }
+
+ director.stopped -= OnDirectorStopped;
+ if (director.state == PlayState.Playing)
+ {
+ director.Stop();
+ }
+
+ _savedPlayableAsset = director.playableAsset;
+ director.playableAsset = loadedAsset;
+ _addressableHandle = loadedHandle;
+
+ _onCompleteCallback = onComplete;
+ if (onComplete != null)
+ {
+ director.stopped += OnDirectorStopped;
+ }
+
+ director.Play();
+ }
+
///
/// 停止播放
///
public void Stop()
{
if (director == null) return;
-
+
// 先取消事件订阅,避免 Stop() 触发回调
director.stopped -= OnDirectorStopped;
-
+
director.Stop();
_onCompleteCallback = null;
+ ReleaseAddressableHandle();
}
///
@@ -157,15 +244,16 @@ namespace AibisDream
public void Reset()
{
if (director == null) return;
-
+
// 先取消事件订阅,避免 Stop() 触发回调
director.stopped -= OnDirectorStopped;
-
+
director.Stop();
director.time = 0;
director.Evaluate();
-
+
_onCompleteCallback = null;
+ ReleaseAddressableHandle();
}
///
@@ -221,10 +309,11 @@ namespace AibisDream
{
// 立即取消订阅,避免重复触发
director.stopped -= OnDirectorStopped;
-
+
var callback = _onCompleteCallback;
_onCompleteCallback = null;
-
+
+ ReleaseAddressableHandle();
callback?.Invoke();
}
diff --git a/Assets/Scripts/SceneManagement/TimelineKit/TimelineCenter.cs b/Assets/Scripts/SceneManagement/TimelineKit/TimelineCenter.cs
index b118c3eec..24de6c6c7 100644
--- a/Assets/Scripts/SceneManagement/TimelineKit/TimelineCenter.cs
+++ b/Assets/Scripts/SceneManagement/TimelineKit/TimelineCenter.cs
@@ -34,84 +34,121 @@ namespace AibisDream
_timelineDict.Remove(directorHandler.timelineName);
}
+ ///
+ /// 解析 timelineName,返回 (directorName, addressableKey 或 null)
+ /// 格式:仅 DirectorName 或 DirectorName/AddressableKey
+ ///
+ private static (string directorName, string addressableKey) ParseTimelineName(string timelineName)
+ {
+ var idx = timelineName.IndexOf('/');
+ if (idx < 0)
+ return (timelineName, null);
+ return (timelineName.Substring(0, idx), timelineName.Substring(idx + 1));
+ }
+
// -------------------------- 播放控制 --------------------------
///
- /// 播放 Timeline(带完成回调的重载)
+ /// 播放 Timeline。timelineName 支持两种格式:仅 DirectorName(播放自带)或 DirectorName/AddressableKey(从 Addressables 加载播放)
///
- public void PlayTimeline(string timelineName, Action onComplete = null)
+ public void PlayTimeline(string timelineName)
{
- if (!_timelineDict.TryGetValue(timelineName, out var directorHandler))
+ var (directorName, addressableKey) = ParseTimelineName(timelineName);
+
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {timelineName} not found");
+ Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
return;
}
-
+
directorHandler.gameObject.SetActive(true);
- directorHandler.Play(onComplete);
+
+ if (addressableKey == null)
+ {
+ directorHandler.Play();
+ }
+ else
+ {
+ directorHandler.PlayFromAddressable(addressableKey);
+ }
}
public IEnumerator PlayTimelineAsync(string name)
{
- if (!_timelineDict.TryGetValue(name, out var directorHandler))
+ var (directorName, addressableKey) = ParseTimelineName(name);
+
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {name} not found");
+ Debug.LogError($"Timeline {name} not found (directorName: {directorName})");
yield break;
}
directorHandler.gameObject.SetActive(true);
- yield return directorHandler.PlayAsync();
+
+ if (addressableKey == null)
+ {
+ yield return directorHandler.PlayAsync();
+ }
+ else
+ {
+ yield return directorHandler.PlayFromAddressableAsync(addressableKey);
+ }
}
public void StopTimeline(string timelineName)
{
- if (!_timelineDict.TryGetValue(timelineName, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(timelineName);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {timelineName} not found");
+ Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
return;
}
-
+
directorHandler.Stop();
}
public void PauseTimeline(string timelineName)
{
- if (!_timelineDict.TryGetValue(timelineName, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(timelineName);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {timelineName} not found");
+ Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
return;
}
-
+
directorHandler.Pause();
}
public void ResumeTimeline(string timelineName)
{
- if (!_timelineDict.TryGetValue(timelineName, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(timelineName);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {timelineName} not found");
+ Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
return;
}
-
+
directorHandler.Resume();
}
public void ResetTimeline(string timelineName)
{
- if (!_timelineDict.TryGetValue(timelineName, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(timelineName);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {timelineName} not found");
+ Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
return;
}
directorHandler.Reset();
}
-
+
public void HideTimeline(string timelineName)
{
- if (!_timelineDict.TryGetValue(timelineName, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(timelineName);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {timelineName} not found");
+ Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
return;
}
@@ -122,23 +159,25 @@ namespace AibisDream
public double GetPlaybackTime(string name)
{
- if (!_timelineDict.TryGetValue(name, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(name);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {name} not found");
+ Debug.LogError($"Timeline {name} not found (directorName: {directorName})");
return 0;
}
-
+
return directorHandler.GetPlaybackTime();
}
public double GetDuration(string name)
{
- if (!_timelineDict.TryGetValue(name, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(name);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {name} not found");
+ Debug.LogError($"Timeline {name} not found (directorName: {directorName})");
return 0;
}
-
+
return directorHandler.GetDuration();
}
@@ -148,23 +187,25 @@ namespace AibisDream
public void SetPlaybackSpeed(string name, float speed)
{
- if (!_timelineDict.TryGetValue(name, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(name);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {name} not found");
+ Debug.LogError($"Timeline {name} not found (directorName: {directorName})");
return;
}
-
+
directorHandler.SetPlaybackSpeed(speed);
}
public void SeekToTime(string name, double time)
{
- if (!_timelineDict.TryGetValue(name, out var directorHandler))
+ var (directorName, _) = ParseTimelineName(name);
+ if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
{
- Debug.LogError($"Timeline {name} not found");
+ Debug.LogError($"Timeline {name} not found (directorName: {directorName})");
return;
}
-
+
directorHandler.SeekToTime(time);
}
@@ -203,7 +244,7 @@ namespace AibisDream
public interface ITimelineCenter
{
- void PlayTimeline(string timelineName, Action onComplete = null);
+ void PlayTimeline(string timelineName);
IEnumerator PlayTimelineAsync(string name);
void StopTimeline(string timelineName);
void PauseTimeline(string timelineName);