feat(timeline): 添加 wait_timeline 以支持黑屏起播
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
---
|
||||
name: narrative-timeline-flow
|
||||
description: >-
|
||||
叙事 Timeline:aseprite/静图改 Timeline、Addressable 登记、黑屏防穿帮、替换 show_full_screen。
|
||||
Use when 加叙事 timeline、aseprite 转 timeline、英里初登场、CG 演出、替换 show_full_screen、
|
||||
hide_full_screen、黑屏穿帮、fade_out 时机、wait_timeline、Day1 begin / Day2 night 天桥 CG、
|
||||
或用户提到 TimelineKit 叙事接入。
|
||||
---
|
||||
|
||||
# 叙事 Timeline 接入(Aseprite / 静图 → Yarn)
|
||||
|
||||
API 细节见 [`Assets/Scripts/SceneManagement/TimelineKit/README_TimelineKit.md`](../../Assets/Scripts/SceneManagement/TimelineKit/README_TimelineKit.md)。本 skill 只记**本仓库约定与踩坑**。
|
||||
|
||||
## 1. 何时用哪种 Yarn 命令
|
||||
|
||||
| 场景 | 命令 |
|
||||
|------|------|
|
||||
| 播完再往下走 | `<<play_timeline 名>>`(阻塞) |
|
||||
| 黑屏下先起播 / 与对白并行 / Loop CG | `<<start_timeline 名>>` |
|
||||
| 已 `start_timeline`,要等播完 | `<<wait_timeline 名>>`(不重头播) |
|
||||
| 关掉整棵 Director GO | `<<hide_timeline 名>>` |
|
||||
|
||||
名称与场景里 `DirectorHandler.timelineName` **完全一致**(叙事多用中文单名,内嵌 Playable)。
|
||||
|
||||
## 2. 资产与 Addressable
|
||||
|
||||
1. Aseprite → `Assets/GameContent/Scene_{场景}/animationRaw/`(或现有 `AnimationRaw/`)
|
||||
2. Timeline → `GameContent/Scene_{场景}/Timeline/{名}.playable`
|
||||
3. Addressable Group = 对应场景线(如 `Scene_ClinicOut`、`Scene_Bridge`)
|
||||
4. Address = `Timeline/{名}`(必须带 `Timeline/` 前缀)
|
||||
5. 叙事 Timeline **一般不**加 `TimelineAssetRef`(维修玩法才用)
|
||||
|
||||
参考范本:`医生出地铁`、`day2夕阳医生看佩佩侧脸`、`英里初登场`。
|
||||
|
||||
## 3. 场景 GO 约定
|
||||
|
||||
- 根节点挂 `PlayableDirector`(`Play On Awake = false`)+ `DirectorHandler`
|
||||
- **根节点默认 Active**:`Start` 里要注册到 `TimelineCenter`;关掉根节点会注册失败
|
||||
- **带 Sprite 的子物体(如 `Visual`)默认 Inactive**;用 ActivationTrack 或播放时再显
|
||||
- `hide_timeline` = 根 `SetActive(false)`;之后再播时 `PlayTimeline` 会重新打开
|
||||
- 全屏 CG 常需调位置(例:天桥侧脸根节点 `Y = -5`);PPU / scale 按相机再调
|
||||
|
||||
## 4. 黑屏防穿帮(必做)
|
||||
|
||||
**错误**:`fade_out` 揭开场景 → 再 `play_timeline`(先露底再播 CG)。
|
||||
|
||||
**正确**(切场景后的 intro CG):
|
||||
|
||||
```yarn
|
||||
<<fade_in>>
|
||||
<<load_scene Scene/ClinicOut>>
|
||||
<<init_actor ...>>
|
||||
<<start_timeline 英里初登场>>
|
||||
<<wait 0.2>>
|
||||
<<fade_out>>
|
||||
<<wait_timeline 英里初登场>>
|
||||
<<hide_timeline 英里初登场>>
|
||||
<<show_actor ...>>
|
||||
<<play_timeline 医生出地铁>>
|
||||
```
|
||||
|
||||
要点:黑屏内起播并稳住首帧 → 再结束黑屏 → 再等播完。CG 盖住初登场时,角色可延后到 `hide_timeline` 后再 `show_actor`。
|
||||
|
||||
## 5. 替换 `show_full_screen` 静图
|
||||
|
||||
常驻 / Loop 演出(对白期间一直播):
|
||||
|
||||
```yarn
|
||||
<<start_timeline day2夕阳医生看佩佩侧脸>>
|
||||
<<fade_out 1>>
|
||||
# ... 对白 ...
|
||||
<<fade_in 1>>
|
||||
<<hide_timeline day2夕阳医生看佩佩侧脸>>
|
||||
<<fade_out 1>>
|
||||
```
|
||||
|
||||
不要再用 `show_full_screen` / `hide_full_screen` 播同一段。
|
||||
|
||||
## 6. 其它坑
|
||||
|
||||
- `<<load_scene>>` **只需场景名**。旧脚本第二参数(如 `1`)已兼容忽略,**新代码不要写**。
|
||||
- 改 `.yarn`:**不要动**已有 `#line:`;新行不加 `#line:`。
|
||||
- 只改运行时实际绑定的 Yarn(如 Day1_begin → FP,不是 Fiction 草稿)。
|
||||
- Aseprite 多 tag:选主演出 clip;Loop CG 用循环 clip + Director `Loop` 或长轨;一次性 intro 用有限时长 + `wait_timeline`。
|
||||
|
||||
## 7. 落地检查清单
|
||||
|
||||
- [ ] Timeline 在正确 `GameContent/Scene_* /Timeline/`
|
||||
- [ ] Addressable:`Timeline/{名}` + 正确 Group
|
||||
- [ ] `timelineName` 与 Yarn 一致;`Play On Awake = false`
|
||||
- [ ] Visual 默认关;进 Play 不会提前露 CG
|
||||
- [ ] 切场景 intro:黑屏内 `start` → 短 wait → `fade_out` → `wait_timeline`
|
||||
- [ ] 静图替换:`start` / `hide` 成对
|
||||
- [ ] 跑一次确认无先露场景、无 `load_scene` 参数错误
|
||||
@@ -266,6 +266,22 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待当前播放结束(不 Stop、不重头播放)。用于 start_timeline 后再等播完。
|
||||
/// </summary>
|
||||
public IEnumerator WaitUntilStoppedAsync()
|
||||
{
|
||||
if (director == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (director.state == PlayState.Playing)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 Addressables 加载 PlayableAsset 并播放(加载时自动加 "Timeline/" 前缀)
|
||||
/// </summary>
|
||||
|
||||
@@ -155,6 +155,18 @@ TimelineYarnCommand 提供以下 Yarn 命令,可在 `.yarn` 文件中直接使
|
||||
<<hide_timeline 维修面板>>
|
||||
```
|
||||
|
||||
### start_timeline / wait_timeline
|
||||
|
||||
`start_timeline` 起播但**不等待**;`wait_timeline` 等待**已经在播**的 Timeline 结束(不重头播)。叙事切场景时常用:黑屏下 `start` → 短 wait 稳住首帧 → `fade_out` → `wait_timeline`。完整约定见项目 Skill [`.cursor/skills/narrative-timeline-flow/SKILL.md`](../../../../.cursor/skills/narrative-timeline-flow/SKILL.md)。
|
||||
|
||||
```yarn
|
||||
<<start_timeline 英里初登场>>
|
||||
<<wait 0.2>>
|
||||
<<fade_out>>
|
||||
<<wait_timeline 英里初登场>>
|
||||
<<hide_timeline 英里初登场>>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 代码调用示例
|
||||
|
||||
@@ -147,6 +147,21 @@ namespace AibisDream
|
||||
directorHandler.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待已在播放的 Timeline 结束(不重新从头播放)。
|
||||
/// </summary>
|
||||
public IEnumerator WaitTimelineAsync(string timelineName)
|
||||
{
|
||||
var (directorName, _) = ParseTimelineName(timelineName);
|
||||
if (!_timelineDict.TryGetValue(directorName, out var directorHandler))
|
||||
{
|
||||
Debug.LogError($"Timeline {timelineName} not found (directorName: {directorName})");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return directorHandler.WaitUntilStoppedAsync();
|
||||
}
|
||||
|
||||
#region 状态查询
|
||||
|
||||
public double GetPlaybackTime(string name)
|
||||
|
||||
@@ -20,6 +20,15 @@ namespace AibisDream
|
||||
TimelineCenter.Instance.PlayTimeline(timelineName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待已在播放的 Timeline 结束(配合 start_timeline:可先在黑屏下起播,fade_out 后再等播完)。
|
||||
/// </summary>
|
||||
[YarnCommand("wait_timeline")]
|
||||
public static IEnumerator WaitTimeline(string timelineName)
|
||||
{
|
||||
yield return TimelineCenter.Instance.WaitTimelineAsync(timelineName);
|
||||
}
|
||||
|
||||
[YarnCommand("reset_timeline")]
|
||||
public static void ResetTimeline(string timelineName)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user