mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
docs(guidelines): 更新错误处理和移动端优化最佳实践
- 添加完整的错误处理最佳实践指南,涵盖Result<T>和Option<T>使用方式 - 补充移动端性能优化策略,包括纹理压缩、对象池、内存管理和电池优化 - 更新单元测试教程中的相关文档链接 - 完善错误处理层次结构和测试示例代码 - 增加移动端UI优化和平台适配最佳实践
This commit is contained in:
parent
519e3a480b
commit
8554f01423
@ -1301,3 +1301,385 @@ public class CircuitBreaker
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 选择合适的错误处理方式
|
||||||
|
|
||||||
|
根据错误类型选择处理方式:
|
||||||
|
|
||||||
|
| 错误类型 | 处理方式 | 示例 |
|
||||||
|
|-----------|-----------------|-------------|
|
||||||
|
| 可预期的业务错误 | Result<T> | 用户输入验证、权限检查 |
|
||||||
|
| 可能不存在的值 | Option<T> | 查找操作、配置读取 |
|
||||||
|
| 不可预期的系统错误 | Exception | 文件 IO、网络错误 |
|
||||||
|
| 不可恢复的错误 | Exception + 日志 | 初始化失败、资源耗尽 |
|
||||||
|
|
||||||
|
### 2. 错误处理的层次结构
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 底层:使用 Result 处理业务错误
|
||||||
|
public class InventoryRepository
|
||||||
|
{
|
||||||
|
public Result<Item> GetItem(string itemId)
|
||||||
|
{
|
||||||
|
var item = _items.FirstOrDefault(i => i.Id == itemId);
|
||||||
|
return item != null
|
||||||
|
? Result<Item>.Success(item)
|
||||||
|
: Result<Item>.Failure("物品不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中层:组合多个操作
|
||||||
|
public class InventorySystem : AbstractSystem
|
||||||
|
{
|
||||||
|
public Result<Item> UseItem(string itemId)
|
||||||
|
{
|
||||||
|
return _repository.GetItem(itemId)
|
||||||
|
.Bind(item => ValidateItemUsage(item))
|
||||||
|
.Bind(item => ConsumeItem(item))
|
||||||
|
.OnSuccess(item => this.SendEvent(new ItemUsedEvent { Item = item }))
|
||||||
|
.OnFailure(ex => _logger.Warn($"使用物品失败: {ex.Message}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上层:处理用户交互
|
||||||
|
public class InventoryController : IController
|
||||||
|
{
|
||||||
|
public void OnUseItemButtonClicked(string itemId)
|
||||||
|
{
|
||||||
|
var result = _inventorySystem.UseItem(itemId);
|
||||||
|
|
||||||
|
result.Match(
|
||||||
|
succ: item => ShowSuccessMessage($"使用了 {item.Name}"),
|
||||||
|
fail: ex => ShowErrorMessage(ex.Message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 避免过度使用异常
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ 避免:用异常处理正常流程
|
||||||
|
public Player GetPlayerById(string playerId)
|
||||||
|
{
|
||||||
|
var player = _players.FirstOrDefault(p => p.Id == playerId);
|
||||||
|
if (player == null)
|
||||||
|
throw new PlayerNotFoundException(playerId); // 不应该用异常
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 好的做法:使用 Option
|
||||||
|
public Option<Player> GetPlayerById(string playerId)
|
||||||
|
{
|
||||||
|
var player = _players.FirstOrDefault(p => p.Id == playerId);
|
||||||
|
return player != null
|
||||||
|
? Option<Player>.Some(player)
|
||||||
|
: Option<Player>.None;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 提供有意义的错误消息
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ 避免:模糊的错误消息
|
||||||
|
return Result<Item>.Failure("错误");
|
||||||
|
return Result<Item>.Failure("操作失败");
|
||||||
|
|
||||||
|
// ✅ 好的做法:具体的错误消息
|
||||||
|
return Result<Item>.Failure("物品不存在");
|
||||||
|
return Result<Item>.Failure($"背包空间不足,需要 {required} 格,当前 {available} 格");
|
||||||
|
return Result<Item>.Failure($"物品 {itemName} 不可交易");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 记录错误上下文
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ 避免:缺少上下文
|
||||||
|
_logger.Error("保存失败", ex);
|
||||||
|
|
||||||
|
// ✅ 好的做法:包含完整上下文
|
||||||
|
_logger.Log(
|
||||||
|
LogLevel.Error,
|
||||||
|
"保存玩家数据失败",
|
||||||
|
ex,
|
||||||
|
("playerId", playerId),
|
||||||
|
("saveSlot", saveSlot),
|
||||||
|
("dataSize", data.Length),
|
||||||
|
("timestamp", DateTime.UtcNow)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 优雅降级
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ✅ 好的做法:提供降级方案
|
||||||
|
public async Task<Texture> LoadTextureAsync(string path)
|
||||||
|
{
|
||||||
|
// 1. 尝试加载指定纹理
|
||||||
|
var result = await TryLoadTextureAsync(path);
|
||||||
|
if (result.IsSuccess)
|
||||||
|
return result.Match(succ: t => t, fail: _ => null);
|
||||||
|
|
||||||
|
// 2. 尝试加载备用纹理
|
||||||
|
_logger.Warn($"加载纹理失败: {path},使用备用纹理");
|
||||||
|
var fallbackResult = await TryLoadTextureAsync(_fallbackTexturePath);
|
||||||
|
if (fallbackResult.IsSuccess)
|
||||||
|
return fallbackResult.Match(succ: t => t, fail: _ => null);
|
||||||
|
|
||||||
|
// 3. 使用默认纹理
|
||||||
|
_logger.Error("所有纹理加载失败,使用默认纹理");
|
||||||
|
return _defaultTexture;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 测试错误处理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[TestFixture]
|
||||||
|
public class InventorySystemTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void UseItem_ItemNotFound_ShouldReturnFailure()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var system = new InventorySystem();
|
||||||
|
var invalidItemId = "invalid_item";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = system.UseItem(invalidItemId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception.Message, Contains.Substring("物品不存在"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void UseItem_ItemNotUsable_ShouldReturnFailure()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var system = new InventorySystem();
|
||||||
|
var item = new Item { Id = "item1", IsUsable = false };
|
||||||
|
system.AddItem(item);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = system.UseItem("item1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception.Message, Contains.Substring("不可使用"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void UseItem_ValidItem_ShouldReturnSuccess()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var system = new InventorySystem();
|
||||||
|
var item = new Item { Id = "item1", IsUsable = true };
|
||||||
|
system.AddItem(item);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = system.UseItem("item1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 何时使用 Result,何时使用 Option?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
|
||||||
|
- 使用 **Result<T>** 当操作可能失败,需要返回错误信息时
|
||||||
|
- 使用 **Option<T>** 当值可能不存在,但不需要错误信息时
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用 Result:需要知道为什么失败
|
||||||
|
public Result<User> RegisterUser(string username, string password)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(username))
|
||||||
|
return Result<User>.Failure("用户名不能为空");
|
||||||
|
|
||||||
|
if (password.Length < 8)
|
||||||
|
return Result<User>.Failure("密码长度至少为 8 个字符");
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Option:只需要知道是否存在
|
||||||
|
public Option<User> FindUserById(string userId)
|
||||||
|
{
|
||||||
|
var user = _users.FirstOrDefault(u => u.Id == userId);
|
||||||
|
return user != null ? Option<User>.Some(user) : Option<User>.None;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2: 如何处理异步操作中的错误?
|
||||||
|
|
||||||
|
**A:** 使用 Result 的异步扩展方法:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task<Result<PlayerData>> LoadPlayerDataAsync(string playerId)
|
||||||
|
{
|
||||||
|
return await ResultExtensions.TryAsync(async () =>
|
||||||
|
{
|
||||||
|
var data = await _httpClient.GetStringAsync($"/api/players/{playerId}");
|
||||||
|
return JsonSerializer.Deserialize<PlayerData>(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链式异步操作
|
||||||
|
public async Task<Result<Player>> LoadAndValidatePlayerAsync(string playerId)
|
||||||
|
{
|
||||||
|
var result = await LoadPlayerDataAsync(playerId);
|
||||||
|
return await result.BindAsync(async data =>
|
||||||
|
{
|
||||||
|
var isValid = await ValidatePlayerDataAsync(data);
|
||||||
|
return isValid
|
||||||
|
? Result<Player>.Success(CreatePlayer(data))
|
||||||
|
: Result<Player>.Failure("玩家数据验证失败");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3: 如何在 Command 和 Query 中处理错误?
|
||||||
|
|
||||||
|
**A:** Command 和 Query 可以返回 Result:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Command 返回 Result
|
||||||
|
public class SaveGameCommand : AbstractCommand<Result<SaveData>>
|
||||||
|
{
|
||||||
|
protected override Result<SaveData> OnDo()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = CollectSaveData();
|
||||||
|
var saveSystem = this.GetSystem<SaveSystem>();
|
||||||
|
return saveSystem.SaveGame(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.GetUtility<ILogger>().Error("保存游戏失败", ex);
|
||||||
|
return Result<SaveData>.Failure(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query 返回 Option
|
||||||
|
public class GetPlayerQuery : AbstractQuery<Option<Player>>
|
||||||
|
{
|
||||||
|
public string PlayerId { get; set; }
|
||||||
|
|
||||||
|
protected override Option<Player> OnDo()
|
||||||
|
{
|
||||||
|
var playerSystem = this.GetSystem<PlayerSystem>();
|
||||||
|
return playerSystem.FindPlayerById(PlayerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 如何处理多个可能失败的操作?
|
||||||
|
|
||||||
|
**A:** 使用 Result 的链式操作:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public Result<Trade> ExecuteTrade(string sellerId, string buyerId, string itemId, int price)
|
||||||
|
{
|
||||||
|
return ValidateSeller(sellerId)
|
||||||
|
.Bind(_ => ValidateBuyer(buyerId))
|
||||||
|
.Bind(_ => ValidateItem(itemId))
|
||||||
|
.Bind(_ => ValidatePrice(price))
|
||||||
|
.Bind(_ => TransferItem(sellerId, buyerId, itemId))
|
||||||
|
.Bind(_ => TransferCurrency(buyerId, sellerId, price))
|
||||||
|
.Map(_ => CreateTradeRecord(sellerId, buyerId, itemId, price));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q5: 如何避免 Result 嵌套过深?
|
||||||
|
|
||||||
|
**A:** 使用 Bind 扁平化嵌套:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ 避免:嵌套过深
|
||||||
|
public Result<string> ProcessData(string input)
|
||||||
|
{
|
||||||
|
var result1 = Step1(input);
|
||||||
|
if (result1.IsSuccess)
|
||||||
|
{
|
||||||
|
var result2 = Step2(result1.Match(succ: v => v, fail: _ => ""));
|
||||||
|
if (result2.IsSuccess)
|
||||||
|
{
|
||||||
|
var result3 = Step3(result2.Match(succ: v => v, fail: _ => ""));
|
||||||
|
return result3;
|
||||||
|
}
|
||||||
|
return Result<string>.Failure(result2.Exception);
|
||||||
|
}
|
||||||
|
return Result<string>.Failure(result1.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 好的做法:使用 Bind 扁平化
|
||||||
|
public Result<string> ProcessData(string input)
|
||||||
|
{
|
||||||
|
return Step1(input)
|
||||||
|
.Bind(Step2)
|
||||||
|
.Bind(Step3);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q6: 如何在 UI 层处理错误?
|
||||||
|
|
||||||
|
**A:** 将错误转换为用户友好的消息:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class UIController : IController
|
||||||
|
{
|
||||||
|
private readonly ErrorMessageService _errorMessageService;
|
||||||
|
|
||||||
|
public void OnSaveButtonClicked()
|
||||||
|
{
|
||||||
|
var result = this.SendCommand(new SaveGameCommand());
|
||||||
|
|
||||||
|
result.Match(
|
||||||
|
succ: data => {
|
||||||
|
ShowSuccessToast("游戏保存成功");
|
||||||
|
},
|
||||||
|
fail: ex => {
|
||||||
|
var userMessage = _errorMessageService.GetUserFriendlyMessage(ex);
|
||||||
|
ShowErrorDialog("保存失败", userMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
良好的错误处理是构建健壮应用的基础。遵循本指南的最佳实践:
|
||||||
|
|
||||||
|
- ✅ 使用 **Result<T>** 处理可预期的业务错误
|
||||||
|
- ✅ 使用 **Option<T>** 处理可能不存在的值
|
||||||
|
- ✅ 使用 **异常** 处理不可预期的系统错误
|
||||||
|
- ✅ 记录详细的 **日志** 信息
|
||||||
|
- ✅ 提供友好的 **用户反馈**
|
||||||
|
- ✅ 实现 **错误恢复** 和降级策略
|
||||||
|
- ✅ 编写 **测试** 验证错误处理逻辑
|
||||||
|
|
||||||
|
通过这些实践,你将构建出更加稳定、可维护、用户友好的游戏应用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**相关文档**:
|
||||||
|
|
||||||
|
- [架构模式最佳实践](./architecture-patterns.md)
|
||||||
|
- [扩展方法使用指南](../core/extensions.md)
|
||||||
|
- [日志系统](../core/logging.md)
|
||||||
|
|
||||||
|
**文档版本**: 1.0.0
|
||||||
|
**更新日期**: 2026-03-07
|
||||||
|
|||||||
@ -382,3 +382,549 @@ public enum ResourcePriority
|
|||||||
|
|
||||||
### 2. 纹理压缩和优化
|
### 2. 纹理压缩和优化
|
||||||
|
|
||||||
|
使用合适的纹理格式和压缩:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class TextureOptimizer
|
||||||
|
{
|
||||||
|
public static TextureSettings GetOptimalSettings(string platform)
|
||||||
|
{
|
||||||
|
return platform switch
|
||||||
|
{
|
||||||
|
"iOS" => new TextureSettings
|
||||||
|
{
|
||||||
|
Format = TextureFormat.PVRTC_RGB4,
|
||||||
|
MaxSize = 2048,
|
||||||
|
MipmapEnabled = true,
|
||||||
|
Compression = TextureCompression.High
|
||||||
|
},
|
||||||
|
"Android" => new TextureSettings
|
||||||
|
{
|
||||||
|
Format = TextureFormat.ETC2_RGB,
|
||||||
|
MaxSize = 2048,
|
||||||
|
MipmapEnabled = true,
|
||||||
|
Compression = TextureCompression.High
|
||||||
|
},
|
||||||
|
_ => new TextureSettings
|
||||||
|
{
|
||||||
|
Format = TextureFormat.RGB24,
|
||||||
|
MaxSize = 4096,
|
||||||
|
MipmapEnabled = true,
|
||||||
|
Compression = TextureCompression.Normal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 对象池优化
|
||||||
|
|
||||||
|
针对移动平台优化对象池,限制池大小并定期清理:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MobileObjectPool<T> : AbstractObjectPoolSystem<string, T>
|
||||||
|
where T : IPoolableObject
|
||||||
|
{
|
||||||
|
private const int MaxPoolSize = 50;
|
||||||
|
|
||||||
|
public new void Release(string key, T obj)
|
||||||
|
{
|
||||||
|
if (Pools.TryGetValue(key, out var pool) && pool.Count >= MaxPoolSize)
|
||||||
|
{
|
||||||
|
obj.OnPoolDestroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
base.Release(key, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override T Create(string key)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 避免内存泄漏
|
||||||
|
|
||||||
|
确保正确释放资源和取消事件订阅:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class LeakFreeSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private IResourceHandle<Texture>? _textureHandle;
|
||||||
|
private IUnRegister? _eventUnregister;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
_eventUnregister = this.RegisterEvent<GameEvent>(OnGameEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
// 释放资源句柄
|
||||||
|
_textureHandle?.Dispose();
|
||||||
|
_textureHandle = null;
|
||||||
|
|
||||||
|
// 取消事件订阅
|
||||||
|
_eventUnregister?.UnRegister();
|
||||||
|
_eventUnregister = null;
|
||||||
|
|
||||||
|
base.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGameEvent(GameEvent e)
|
||||||
|
{
|
||||||
|
// 处理事件
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
### 1. CPU 优化
|
||||||
|
|
||||||
|
减少 CPU 计算负担:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CPUOptimizer : AbstractSystem
|
||||||
|
{
|
||||||
|
private const int UpdateInterval = 5; // 每 5 帧更新一次
|
||||||
|
private int _frameCount;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
this.RegisterEvent<GameUpdateEvent>(OnUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdate(GameUpdateEvent e)
|
||||||
|
{
|
||||||
|
_frameCount++;
|
||||||
|
|
||||||
|
// 降低更新频率
|
||||||
|
if (_frameCount % UpdateInterval == 0)
|
||||||
|
{
|
||||||
|
UpdateNonCriticalSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键系统每帧更新
|
||||||
|
UpdateCriticalSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCriticalSystems()
|
||||||
|
{
|
||||||
|
// 玩家输入、物理等关键系统
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateNonCriticalSystems()
|
||||||
|
{
|
||||||
|
// AI、动画等非关键系统
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 批量处理
|
||||||
|
|
||||||
|
使用批量操作减少函数调用:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class BatchProcessor : AbstractSystem
|
||||||
|
{
|
||||||
|
private readonly List<Entity> _entitiesToUpdate = new();
|
||||||
|
|
||||||
|
public void ProcessEntities()
|
||||||
|
{
|
||||||
|
// 批量处理,减少函数调用开销
|
||||||
|
for (int i = 0; i < _entitiesToUpdate.Count; i++)
|
||||||
|
{
|
||||||
|
var entity = _entitiesToUpdate[i];
|
||||||
|
entity.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 缓存计算结果
|
||||||
|
|
||||||
|
避免重复计算:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CachedCalculator
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, float> _cache = new();
|
||||||
|
|
||||||
|
public float GetDistance(Vector3 a, Vector3 b)
|
||||||
|
{
|
||||||
|
var key = $"{a}_{b}";
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(key, out var distance))
|
||||||
|
{
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
distance = Vector3.Distance(a, b);
|
||||||
|
_cache[key] = distance;
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearCache()
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 电池优化
|
||||||
|
|
||||||
|
### 1. 动态帧率调整
|
||||||
|
|
||||||
|
根据场景复杂度调整帧率:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class DynamicFrameRateSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private int _targetFrameRate = 60;
|
||||||
|
|
||||||
|
public void AdjustFrameRate(SceneComplexity complexity)
|
||||||
|
{
|
||||||
|
_targetFrameRate = complexity switch
|
||||||
|
{
|
||||||
|
SceneComplexity.Low => 60,
|
||||||
|
SceneComplexity.Medium => 45,
|
||||||
|
SceneComplexity.High => 30,
|
||||||
|
_ => 60
|
||||||
|
};
|
||||||
|
|
||||||
|
Application.targetFrameRate = _targetFrameRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SceneComplexity
|
||||||
|
{
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 后台优化
|
||||||
|
|
||||||
|
应用进入后台时降低性能消耗:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class BackgroundOptimizer : AbstractSystem
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
Application.focusChanged += OnFocusChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFocusChanged(bool hasFocus)
|
||||||
|
{
|
||||||
|
if (hasFocus)
|
||||||
|
{
|
||||||
|
OnApplicationForeground();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnApplicationBackground();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationBackground()
|
||||||
|
{
|
||||||
|
// 降低帧率
|
||||||
|
Application.targetFrameRate = 10;
|
||||||
|
|
||||||
|
// 暂停音频
|
||||||
|
AudioListener.pause = true;
|
||||||
|
|
||||||
|
// 暂停非关键系统
|
||||||
|
PauseNonCriticalSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationForeground()
|
||||||
|
{
|
||||||
|
// 恢复帧率
|
||||||
|
Application.targetFrameRate = 60;
|
||||||
|
|
||||||
|
// 恢复音频
|
||||||
|
AudioListener.pause = false;
|
||||||
|
|
||||||
|
// 恢复系统
|
||||||
|
ResumeNonCriticalSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PauseNonCriticalSystems()
|
||||||
|
{
|
||||||
|
SendEvent(new PauseNonCriticalSystemsEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResumeNonCriticalSystems()
|
||||||
|
{
|
||||||
|
SendEvent(new ResumeNonCriticalSystemsEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
Application.focusChanged -= OnFocusChanged;
|
||||||
|
base.OnDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI 优化
|
||||||
|
|
||||||
|
### 1. 触摸优化
|
||||||
|
|
||||||
|
优化触摸输入处理:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class TouchInputSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private const float TouchThreshold = 10f; // 最小移动距离
|
||||||
|
private Vector2 _lastTouchPosition;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
this.RegisterEvent<TouchEvent>(OnTouch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTouch(TouchEvent e)
|
||||||
|
{
|
||||||
|
switch (e.Phase)
|
||||||
|
{
|
||||||
|
case TouchPhase.Began:
|
||||||
|
_lastTouchPosition = e.Position;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TouchPhase.Moved:
|
||||||
|
var delta = e.Position - _lastTouchPosition;
|
||||||
|
if (delta.magnitude > TouchThreshold)
|
||||||
|
{
|
||||||
|
ProcessTouchMove(delta);
|
||||||
|
_lastTouchPosition = e.Position;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TouchPhase.Ended:
|
||||||
|
ProcessTouchEnd(e.Position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessTouchMove(Vector2 delta)
|
||||||
|
{
|
||||||
|
SendEvent(new TouchMoveEvent { Delta = delta });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessTouchEnd(Vector2 position)
|
||||||
|
{
|
||||||
|
SendEvent(new TouchEndEvent { Position = position });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. UI 元素池化
|
||||||
|
|
||||||
|
复用 UI 元素减少创建开销:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class UIElementPool : AbstractObjectPoolSystem<string, UIElement>
|
||||||
|
{
|
||||||
|
protected override UIElement Create(string key)
|
||||||
|
{
|
||||||
|
return new UIElement(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UIElement GetButton()
|
||||||
|
{
|
||||||
|
return Acquire("button");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReturnButton(UIElement button)
|
||||||
|
{
|
||||||
|
Release("button", button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UIElement : IPoolableObject
|
||||||
|
{
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public UIElement(string type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAcquire()
|
||||||
|
{
|
||||||
|
// 激活 UI 元素
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRelease()
|
||||||
|
{
|
||||||
|
// 重置状态
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPoolDestroy()
|
||||||
|
{
|
||||||
|
// 清理资源
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 平台适配
|
||||||
|
|
||||||
|
### iOS 优化
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class iOSOptimizer
|
||||||
|
{
|
||||||
|
public static void ApplyOptimizations()
|
||||||
|
{
|
||||||
|
// 使用 Metal 渲染
|
||||||
|
SystemInfo.graphicsDeviceType = GraphicsDeviceType.Metal;
|
||||||
|
|
||||||
|
// 优化纹理格式
|
||||||
|
QualitySettings.masterTextureLimit = 0;
|
||||||
|
|
||||||
|
// 启用多线程渲染
|
||||||
|
PlayerSettings.MTRendering = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android 优化
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class AndroidOptimizer
|
||||||
|
{
|
||||||
|
public static void ApplyOptimizations()
|
||||||
|
{
|
||||||
|
// 使用 Vulkan 或 OpenGL ES 3
|
||||||
|
SystemInfo.graphicsDeviceType = GraphicsDeviceType.Vulkan;
|
||||||
|
|
||||||
|
// 优化纹理格式
|
||||||
|
QualitySettings.masterTextureLimit = 0;
|
||||||
|
|
||||||
|
// 启用多线程渲染
|
||||||
|
PlayerSettings.MTRendering = true;
|
||||||
|
|
||||||
|
// 优化 GC
|
||||||
|
GarbageCollector.GCMode = GarbageCollector.Mode.Disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 资源管理
|
||||||
|
|
||||||
|
- 使用资源优先级系统
|
||||||
|
- 及时卸载不用的资源
|
||||||
|
- 使用资源压缩
|
||||||
|
- 实现资源预加载
|
||||||
|
|
||||||
|
### 2. 内存管理
|
||||||
|
|
||||||
|
- 监控内存使用
|
||||||
|
- 限制对象池大小
|
||||||
|
- 避免内存泄漏
|
||||||
|
- 定期执行 GC
|
||||||
|
|
||||||
|
### 3. 性能优化
|
||||||
|
|
||||||
|
- 降低更新频率
|
||||||
|
- 使用批量处理
|
||||||
|
- 缓存计算结果
|
||||||
|
- 优化算法复杂度
|
||||||
|
|
||||||
|
### 4. 电池优化
|
||||||
|
|
||||||
|
- 动态调整帧率
|
||||||
|
- 后台降低性能
|
||||||
|
- 减少渲染开销
|
||||||
|
- 优化网络请求
|
||||||
|
|
||||||
|
### 5. UI 优化
|
||||||
|
|
||||||
|
- 优化触摸处理
|
||||||
|
- 池化 UI 元素
|
||||||
|
- 减少 UI 层级
|
||||||
|
- 使用异步加载
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:如何监控移动设备的内存使用?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
使用 `GC.GetTotalMemory()` 监控托管内存,结合平台 API 监控总内存:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var managedMemory = GC.GetTotalMemory(false);
|
||||||
|
Console.WriteLine($"托管内存: {managedMemory / 1024 / 1024}MB");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何优化移动游戏的启动时间?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- 延迟加载非关键资源
|
||||||
|
- 使用异步初始化
|
||||||
|
- 减少启动时的计算
|
||||||
|
- 优化资源包大小
|
||||||
|
|
||||||
|
### 问题:如何处理不同设备的性能差异?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
实现设备性能分级系统:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public enum DevicePerformance
|
||||||
|
{
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DeviceProfiler
|
||||||
|
{
|
||||||
|
public static DevicePerformance GetDevicePerformance()
|
||||||
|
{
|
||||||
|
var memory = SystemInfo.systemMemorySize;
|
||||||
|
var processorCount = SystemInfo.processorCount;
|
||||||
|
|
||||||
|
if (memory < 2048 || processorCount < 4)
|
||||||
|
return DevicePerformance.Low;
|
||||||
|
else if (memory < 4096 || processorCount < 6)
|
||||||
|
return DevicePerformance.Medium;
|
||||||
|
else
|
||||||
|
return DevicePerformance.High;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何优化移动游戏的网络性能?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- 使用数据压缩
|
||||||
|
- 批量发送请求
|
||||||
|
- 实现请求队列
|
||||||
|
- 处理网络中断
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [资源管理系统](/zh-CN/core/resource) - 资源管理详细说明
|
||||||
|
- [对象池系统](/zh-CN/core/pool) - 对象池优化
|
||||||
|
- [协程系统](/zh-CN/core/coroutine) - 异步操作优化
|
||||||
|
- [架构模式最佳实践](/zh-CN/best-practices/architecture-patterns) - 架构设计
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: 1.0.0
|
||||||
|
**更新日期**: 2026-03-07
|
||||||
|
|
||||||
|
|||||||
@ -1143,5 +1143,5 @@ public void CalculateBonus_Should_Return_Double(int input, int expected)
|
|||||||
|
|
||||||
- [NUnit 官方文档](https://docs.nunit.org/)
|
- [NUnit 官方文档](https://docs.nunit.org/)
|
||||||
- [Moq 快速入门](https://github.com/moq/moq4/wiki/Quickstart)
|
- [Moq 快速入门](https://github.com/moq/moq4/wiki/Quickstart)
|
||||||
- [测试驱动开发实践](/zh-CN/best-practices/tdd)
|
- [架构设计模式](/zh-CN/best-practices/architecture-patterns)
|
||||||
- [持续集成配置](/zh-CN/best-practices/ci-cd)
|
- [性能优化最佳实践](/zh-CN/best-practices/performance)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user