性能优化技巧
本章总结灵泛引擎的性能优化最佳实践。
AOT 编译
启用 AOT
xml
<!-- 在平台 .csproj 中 -->
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>AOT 友好原则
| 避免 | 推荐 |
|---|---|
| 反射 | 显式类型匹配 |
| 动态代码生成 | Source Generator |
dynamic 类型 | 具体类型 |
运行时 JsonSerializer | JsonSerializerContext |
引擎已内置
灵泛引擎的核心代码已经完全 AOT 友好——命令处理器用显式 switch 类型匹配,JSON 序列化用 source generator。
内存优化
避免频繁分配
csharp
// ❌ 每帧分配
public void Update()
{
var list = new List<int>(); // 每帧 new
// ...
}
// ✅ 复用
private readonly List<int> _list = new();
public void Update()
{
_list.Clear();
// ...
}使用 Span / Memory
csharp
// ❌ 分配数组
byte[] data = new byte[256];
// ✅ 栈分配(小数据)
Span<byte> data = stackalloc byte[256];ArrayPool(大数据)
csharp
// 大数组用 ArrayPool
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
// 使用 buffer
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}不可变集合
csharp
// ✅ 不可变数组,遍历快
ImmutableArray<string> names = ImmutableArray.Create("A", "B", "C");
// ✅ 原子更新
ImmutableInterlocked.Update(ref _items, arr => arr.Add("D"));图片优化
纹理大小
| 用途 | 建议尺寸 |
|---|---|
| 背景图 | 1920×1080 |
| 立绘 | 1024×1536 |
| CG | 1920×1080 |
| 图标 | 64×64 / 128×128 |
图片格式
| 格式 | 推荐 | 说明 |
|---|---|---|
| PNG | ✅ | 无损,适合 UI / 立绘 |
| JPG | ✅ | 压缩,适合背景 / CG |
| WebP | ✅ | 更好的压缩率 |
按需加载
灵泛引擎的 AvaloniaAssetAccessor 会按需加载图片。避免在场景定义中引用过多大图。
音频优化
格式选择
| 用途 | 推荐格式 | 理由 |
|---|---|---|
| BGM | MP3 | 压缩,体积小 |
| SE | WAV | 加载快,延迟低 |
| Voice | WAV / MP3 | 根据量决定 |
音频池
频繁播放的 SE 用音频池避免重复加载:
csharp
// 引擎内置音频池,无需手动管理DSL 优化
减少场景内命令
dsl
// ❌ 场景内大量命令
scene "town"
set "a" 1
set "b" 2
set "c" 3
// ... 50 行 set
// ✅ 用 label 组织
scene "town"
call "init_town_vars"
text "小镇" x=50% y=10%
label init_town_vars:
set "a" 1
set "b" 2
set "c" 3
return避免深层嵌套
dsl
// ❌ 深层嵌套
if {a}
if {b}
if {c}
// ...
// ✅ 合并条件
if {a && b && c}
// ...渲染优化
减少 ZIndex 切换
频繁切换 ZIndex 会导致重绘。尽量保持层级稳定。
透明度 vs 可见性
dsl
// ❌ 用透明度隐藏(仍参与渲染)
image "bg.png" opacity=0
// ✅ 用 hide 隐藏(不参与渲染)
hide "bg"合并图片
多个小图标可以合并为一张图集(sprite sheet),减少绘制调用。
状态管理优化
避免高频 _state.Set
csharp
// ❌ 每帧更新
public void Update()
{
_state.Set("fps", currentFps); // 每帧
}
// ✅ 节流更新
private double _fpsUpdateTimer;
public void Update(double delta)
{
_fpsUpdateTimer += delta;
if (_fpsUpdateTimer >= 0.5) // 0.5 秒更新一次
{
_state.Set("fps", currentFps);
_fpsUpdateTimer = 0;
}
}局部变量优先
dsl
// ❌ 用全局变量存临时数据
set "temp.x" 10
set "temp.y" 20
set "result" {temp.x + temp.y}
// ✅ 用局部变量
let "x" 10
let "y" 20
set "result" {_local_x + _local_y}移动端优化
目标帧率
csharp
// 在配置中
options.MobileTargetFps = 60; // 移动端 60 帧足够资源预热
在 loading 场景预热资源:
dsl
label loading:
// 预加载图片
image "Images/chapter1_bg.jpg" x=-100% y=-100% opacity=0
image "Images/hero.png" x=-100% y=-100% opacity=0
// 预加载音频
bgm "Media/bgm_chapter1.mp3" volume=0
// BGM 会在场景切换时自动停止(DefaultAutoStopBgm=true)
navigate "chapter1"内存监控
dsl
debug "Memory: {stats.memory_mb} MB" level=Debug性能分析
内置性能 HUD
csharp
options.ShowPerformanceHud = true;显示 FPS、内存、绘制调用数。
调试日志
dsl
debug "Checkpoint created at {story.progress}" level=Debug
debug "State keys: {state.count}" level=Trace发布优化
去除调试符号
xml
<PropertyGroup>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>裁剪未使用代码
xml
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
</PropertyGroup>单文件发布
xml
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>性能检查清单
- [ ] 启用 AOT 编译
- [ ] 移除调试符号
- [ ] 图片尺寸合理
- [ ] 音频格式合适
- [ ] 避免每帧 _state.Set
- [ ] 局部变量优先
- [ ] 场景内命令精简
- [ ] 移动端帧率适配
- [ ] 资源预热
- [ ] 性能 HUD 检查