主题
视频播放
项目视频可用于开场动画、教学、剧情和界面背景。普通 UI 优先使用 GameUI.Control.Primitive.Video:控件负责异步加载、画面缩放和播放器释放。需要把视频帧接到自定义 Canvas 或 3D 材质时,使用 GameGraph.ResourceSystem.VideoPlayer。两者的播放逻辑都仅在客户端执行。
资源准备
将视频放到项目的 res/ 下,例如 res/video/intro.mp4,代码和数据字段填写 video/intro.mp4,不包含 res/ 前缀。视频属于引擎资源,即使显示在 UI 中,也不放在 UI 图片目录或 user_files。这些接口只接受项目相对路径,不接受网址、绝对路径或 .. 路径段。
建议从 MP4、H.264 视频和 AAC 音频开始。实际支持的容器与编码由目标平台客户端的解码能力决定,扩展名不是兼容性保证;发布前应在目标设备上验证。普通文件、混淆资源和包内资源通过项目资源系统查找。包内资源需要先提取,较大的包内视频可能增加加载开始阶段的耗时。
无音轨或音频输出不可用时,播放器使用计时时钟推进画面,不要求系统存在可用音频设备。音频设备可用时优先使用音频时钟保持同步;播放中失去音频输出时从当前位置切换到计时时钟,不自动重新打开音频设备。
请使用包含视频播放与项目资源寻址支持的客户端或编辑器。仅更新 SDK 或依赖库不会更新客户端解码器。
在界面中播放
以下代码放在客户端的游戏开始逻辑中。先订阅事件,再设置 Source,这样可以接收到路径错误等立即失败的情况。
csharp
#if CLIENT
using GameUI.Control.Primitive;
using GameUI.Control.Enum;
var video = new Video
{
Width = 640,
Height = 360,
AutoPlay = true,
IsLooping = false,
Volume = 0.5f,
Stretch = VideoStretch.Uniform,
};
video.OnFailed += (_, _) =>
Game.Logger.LogWarning("视频加载失败:{Error}", video.LastError);
video.OnEnded += (_, _) =>
Game.Logger.LogInformation("视频播放结束");
video.AddToRoot();
video.Source = "video/intro.mp4";
// 界面永久关闭时调用,或由所属父控件的 Destroy 级联销毁。
// video.Destroy();
#endifVideo 是基础显示控件,不自带播放按钮、进度条或全屏界面。可使用 TextButton、进度控件和普通布局组合这些功能。它继承 Canvas,视频画面遵循控件的布局、透明度和裁剪;视频帧本身不作为独立的 UI 树节点出现。
控件销毁或当前对局结束时会释放播放器及其视频纹理。普通界面关闭仍应调用 Destroy(),以便立即释放;RemoveFromParent() 只摘除界面节点,默认隐藏暂停策略会暂停播放但保留资源,方便重新挂载。
配置与默认值
也可以使用流式构建。UI.Video() 与 UI.Video(width, height) 返回视频控件;通用布局扩展继续适用,视频专用扩展包括 AutoPlay、Loop、Muted、Volume、PlaybackRate、PauseWhenHidden、Stretch、Source 和事件订阅。Source 放在选项与订阅之后:
csharp
using GameUI.Control.Extensions;
var video = UI.Video(640, 360)
.AutoPlay()
.Loop()
.Muted()
.Stretch(VideoStretch.Uniform)
.PauseWhenHidden()
.OnVideoFailed((sender, _) =>
{
if (sender is Video failed)
Game.Logger.LogWarning("视频加载失败:{Error}", failed.LastError);
})
.Source("video/intro.mp4");
video.AddToRoot();| 属性 | 默认值 | 含义 |
|---|---|---|
Source | 空字符串 | 项目视频路径;清空会卸载当前视频 |
Background | 白色 #FFFFFFFF | Canvas 输出乘色;保留视频原色,不是留边底色 |
AutoPlay | false | 新视频加载完成后是否播放;设置它不会立即播放已经打开的视频 |
IsLooping | false | 到达末尾后循环 |
IsMuted | false | 静音,保留音量设置 |
Volume | 1 | 音量,限制在 0~1 |
PlaybackRate | 1 | 播放速度,限制在 0.25~4;音调处理取决于平台解码后端,不保证保调变速 |
PauseWhenHidden | true | 控件隐藏、父级隐藏或脱离界面树时暂停 |
Stretch | Uniform | 画面如何适配控件尺寸 |
VideoStretch.Uniform 等比完整显示并居中,剩余空间透明;UniformToFill 等比填满并居中裁剪;Fill 拉伸到完整区域,可能改变画面比例。
Video 沿用 Canvas 的颜色行为:Background 会乘到整张画布纹理上,默认白色保留视频原色。不要将视频控件的 Background 设为黑色来制作留边,否则视频也会被染黑。需要黑色或其他颜色的留边时,将视频放进相同尺寸的 Panel,在外层 Panel 上设置背景色,视频本身保持默认白色。
关闭自动播放时,加载完成后显示首帧,不启动音频。AutoPlay 在设置新 Source 或调用 Reload() 开始加载时读取;之后要改变本次播放请求,使用 Play()、Pause() 或 Stop()。
隐藏暂停策略在逻辑更新时检查。重新显示只恢复由该策略暂停的播放;用户主动调用 Pause() 或 Stop() 后,重新显示不会擅自恢复。需要视频隐藏时仍继续播放,可将 PauseWhenHidden 设为 false。
控制播放
csharp
video.Play(); // 开始或恢复;正在加载时记住播放请求
video.Pause(); // 暂停;也可取消加载期间的自动播放请求
video.Seek(3.5f); // 定位到第 3.5 秒;加载期间会在打开后应用
video.Stop(); // 停止并归零
video.Reload(); // 重新加载同一个 Source,重新应用 AutoPlay
video.Source = "video/next.mp4"; // 释放旧视频并异步加载新视频
video.Source = ""; // 卸载视频,控件保留反复赋予同一个 Source 不会重新加载,使用 Reload() 显式重载。切换资源或销毁控件会取消旧播放器的加载,旧加载结果不会覆盖新视频。
状态与事件
State 使用 VideoViewState,覆盖 Empty、Loading、Ready、Playing、Paused、Stopped、Ended 和 Failed。可读取 IsLoading、IsReady、IsPlaying、CurrentTime、Duration、VideoWidth 和 VideoHeight;时间单位均为秒。
OnOpened:视频及纹理就绪,在可选自动播放之前触发。OnEnded:进入播放结束状态时触发一次,循环边界不触发。OnFailed:加载失败,原因见LastError;修正路径后可重新赋值或调用Reload()。OnStateChanged:控件状态改变。事件中可以切换资源或销毁控件。
Video.CurrentTime 在 Ended 时保留为 Duration,便于进度条保持在末尾。VideoPlayer 底层对象的状态更简单,不能用“进度归零”推断视频是否结束,应判断其 VideoPlaybackState.Ended。
数据定义与 UI 创作
控件对应 GameDataControlVideo,继承已有 Canvas 数据配置。它提供 Source、AutoPlay、IsLooping、IsMuted、Volume、PlaybackRate、PauseWhenHidden 和 Stretch。这些字段可空:未设置时保留控件默认值,空的 Source 则表示卸载。应用数据时先配置播放参数,最后设置视频源。
使用更新后的 GameSystemUI 视频控件模板创建控件,配置尺寸和 Source 即可。模板显式写入白色 Background 和上述播放参数,保证数据编辑器显示值与运行时默认值一致;默认不自动播放,避免刚插入界面时立即发出声音。控件实现由 GameUI 提供;数据编辑、运行时预览和保存沿用现有控件流程。
视频音频
带音轨的视频默认同时播放声音和画面,无需将音轨拆成独立声音资源。使用同一个控件的 Play()、Pause()、Stop()、Seek() 和 PlaybackRate 控制音画;更换视频源、销毁控件或结束对局时会同时释放音频和视频资源。
音量和静音
Volume控制这一个视频,IsMuted只静音而不修改音量值或停止画面。- 视频属于
Effect音量分类,同时受游戏Master主音量影响。最终音量为视频自身音量、Master 音量、Effect 音量的乘积,再受系统音量控制。 - 例如视频
Volume=0.5、Master 为 0.8、Effect 为 0.5,视频获得 0.2 的音量系数。任意一层为 0 都会静音。 - 不要为了播放一个视频调用全局
AudioManager.SetMode或强行启动被用户关闭的音频设备。音频初始化和设备选择由客户端管理;没有可用输出时仍允许画面继续播放。
平台行为
Windows 和 Android 的视频音轨由平台解码器解码后进入引擎音频混音。音频输出有效时以音频采样时钟推进画面;无输出或没有音轨时使用计时时钟。
iOS 使用系统播放器统一播放声音和画面,由系统播放器维护音画时间。引擎只获取当前视频帧,并将视频音量、静音和游戏音量设置同步给播放器。进入后台会暂停;回到前台时根据播放器是否仍有效选择恢复或重新加载。用户在后台主动停止或切换视频后,不会恢复原视频的播放请求。
系统播放器在片尾重播或恢复失效后端时可能异步重建。因此首次加载以外也可能短暂没有可用画面;循环播放不保证无缝衔接。播放器保留完整片尾,不会为循环提前裁掉视频尾部。
多语言旁白、动态配乐或空间化声音可以选择独立音频,再将视频 IsMuted 设为 true。这是项目主动选择的组合方式;两个独立播放器同时调用 Play 并不保证严格同步。普通剧情和教学视频应优先使用文件内嵌音轨。
每帧调用与开销
视频解码、音频和 GPU 纹理上传在原生层进行,像素数据不经过 C# / WASM。稳定播放时,每个控件在一次逻辑更新中查询一次状态和一次播放时间;可见画布每次渲染发送清帧、尺寸查询和纹理绘制三个调用,绘制参数是句柄与源/目标矩形。另有一次原生到 WASM 的 Canvas 渲染回调。若逻辑更新与渲染均为 60 次/秒,约为每控件 300 次轻量出站调用和 60 次入站回调;加载、暂停/恢复和窗口尺寸变化时有额外调用。
这不是“零开销”:Canvas 会额外渲染到自身纹理,并处理缩放和裁剪。少量视频通常应优先关注解码分辨率、帧率和设备解码能力;大量同时播放的视频仍需在目标设备上测量 CPU/GPU 和内存开销。上述数量按代码路径估算,不是性能基准测试结果。
底层播放器与纹理
直接使用 VideoPlayer 时,需要自己管理生命周期和画面承载。Load(path) 同步加载,失败返回 null;路径格式非法时抛出参数异常。首次异步加载可使用 Create():
csharp
#if CLIENT
using GameGraph.ResourceSystem;
using GameCore.Timers;
using var player = VideoPlayer.Create();
if (player is null || !player.BeginLoadAsync("video/intro.mp4"))
return;
while (player.IsAsyncLoading)
await new Delay(16);
if (!player.IsReady)
return;
using var texture = player.Texture;
if (texture is null)
return;
// 可将 texture 交给 Canvas.DrawTexture 或材质的贴图槽。
player.Play();
// 在这里等待项目需要的播放时长或播放结束,并保持播放器存活。
while (player.State != VideoPlaybackState.Ended)
await new Delay(16);
// 离开作用域前,应解除 Canvas 事件或材质对该纹理的使用。
#endif上述示例展示异步加载与生命周期,不会单独创建可见界面。Canvas 绘制时需要在它的 OnRender 回调中调用 DrawTexture;也可使用 GameUI.RuntimeTexture.Register 将纹理注册为 RT: 图片名,给普通控件的 Image 使用。后者在结束时必须 Unregister。3D 材质可以引用同一视频纹理,播放器须覆盖所有消费者的使用时长。
播放器会由引擎自动推进,无需每帧主动调用 Update()。暂停或停止并不释放资源;独立播放器最终需要 Dispose()。注销纹理、解除绘制回调或移除 UI 后,仍须释放自己持有的播放器和纹理包装对象。不要依赖关闭 PIE 或垃圾回收替代显式清理。
相关内容:项目资源路径、GameGraph 总览。
可运行示例
code_sample/src/VideoPlaybackSample/ 提供视频播放与内嵌音频测试,游戏模式为 VideoPlaybackSample。附带的 res/video/sample-sync.mp4 每秒发出红色闪光和短音,可直观检查声音、画面、暂停恢复与跳转。界面提供循环、倍速、音量、静音、三种画面适配、隐藏暂停和销毁重建操作。
示例默认不自动播放,也不修改游戏全局音量或初始化音频设备。打开示例目录内的 README.md 查看测试模式设置与验收步骤;generate_clip.py 仅用于重新制作随附测试片,正常运行不需要 Python 或 FFmpeg。