---
title: 资源管理最佳实践
description: 学习如何高效管理游戏资源,避免内存泄漏和性能问题
---
# 资源管理最佳实践
## 学习目标
完成本教程后,你将能够:
- 理解资源管理的核心概念和重要性
- 实现自定义资源加载器
- 使用资源句柄管理资源生命周期
- 实现资源预加载和延迟加载
- 选择合适的资源释放策略
- 避免常见的资源管理陷阱
## 前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和 async/await
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[协程系统](/zh-CN/core/coroutine)
## 步骤 1:创建资源类型和加载器
首先,让我们定义游戏中常用的资源类型,并为它们实现加载器。
```csharp
using GFramework.Core.Abstractions.resource;
using System;
using System.IO;
using System.Threading.Tasks;
namespace MyGame.Resources
{
// ===== 资源类型定义 =====
///
/// 纹理资源
///
public class Texture : IDisposable
{
public string Path { get; set; } = string.Empty;
public int Width { get; set; }
public int Height { get; set; }
public byte[]? Data { get; set; }
public void Dispose()
{
Data = null;
Console.WriteLine($"纹理已释放: {Path}");
}
}
///
/// 音频资源
///
public class AudioClip : IDisposable
{
public string Path { get; set; } = string.Empty;
public double Duration { get; set; }
public byte[]? Data { get; set; }
public void Dispose()
{
Data = null;
Console.WriteLine($"音频已释放: {Path}");
}
}
///
/// 配置文件资源
///
public class ConfigData
{
public string Path { get; set; } = string.Empty;
public Dictionary Data { get; set; } = new();
}
// ===== 资源加载器实现 =====
///
/// 纹理加载器
///
public class TextureLoader : IResourceLoader
{
public Texture Load(string path)
{
Console.WriteLine($"同步加载纹理: {path}");
// 模拟加载纹理
Thread.Sleep(100); // 模拟 I/O 延迟
return new Texture
{
Path = path,
Width = 512,
Height = 512,
Data = new byte[512 * 512 * 4] // RGBA
};
}
public async Task LoadAsync(string path)
{
Console.WriteLine($"异步加载纹理: {path}");
// 模拟异步加载
await Task.Delay(100);
return new Texture
{
Path = path,
Width = 512,
Height = 512,
Data = new byte[512 * 512 * 4]
};
}
public void Unload(Texture resource)
{
resource?.Dispose();
}
}
///
/// 音频加载器
///
public class AudioLoader : IResourceLoader
{
public AudioClip Load(string path)
{
Console.WriteLine($"同步加载音频: {path}");
Thread.Sleep(150);
return new AudioClip
{
Path = path,
Duration = 30.0,
Data = new byte[1024 * 1024] // 1MB
};
}
public async Task LoadAsync(string path)
{
Console.WriteLine($"异步加载音频: {path}");
await Task.Delay(150);
return new AudioClip
{
Path = path,
Duration = 30.0,
Data = new byte[1024 * 1024]
};
}
public void Unload(AudioClip resource)
{
resource?.Dispose();
}
}
///
/// 配置文件加载器
///
public class ConfigLoader : IResourceLoader
{
public ConfigData Load(string path)
{
Console.WriteLine($"加载配置: {path}");
// 模拟解析配置文件
return new ConfigData
{
Path = path,
Data = new Dictionary
{
["version"] = "1.0",
["difficulty"] = "normal"
}
};
}
public async Task LoadAsync(string path)
{
await Task.Delay(50);
return Load(path);
}
public void Unload(ConfigData resource)
{
resource.Data.Clear();
Console.WriteLine($"配置已释放: {resource.Path}");
}
}
}
```
**代码说明**:
- 定义了三种常见资源类型:纹理、音频、配置
- 实现 `IResourceLoader` 接口提供加载逻辑
- 同步和异步加载方法分别处理不同场景
- `Unload` 方法负责资源清理
## 步骤 2:注册资源管理器
在架构中注册资源管理器和所有加载器。
```csharp
using GFramework.Core.architecture;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.resource;
using MyGame.Resources;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 创建资源管理器
var resourceManager = new ResourceManager();
// 注册资源加载器
resourceManager.RegisterLoader(new TextureLoader());
resourceManager.RegisterLoader(new AudioLoader());
resourceManager.RegisterLoader(new ConfigLoader());
// 设置释放策略(默认手动释放)
resourceManager.SetReleaseStrategy(new ManualReleaseStrategy());
// 注册到架构
RegisterUtility(resourceManager);
Console.WriteLine("资源管理器初始化完成");
}
}
}
```
**代码说明**:
- 创建 `ResourceManager` 实例
- 为每种资源类型注册对应的加载器
- 设置资源释放策略
- 将资源管理器注册为 Utility
## 步骤 3:实现资源预加载系统
创建一个系统来管理资源的预加载和卸载。
```csharp
using GFramework.Core.system;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.extensions;
using MyGame.Resources;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyGame.Systems
{
///
/// 资源预加载系统
///
public class ResourcePreloadSystem : AbstractSystem
{
// 场景资源配置
private readonly Dictionary> _sceneResources = new()
{
["Menu"] = new List
{
"textures/menu_bg.png",
"textures/button.png",
"audio/menu_bgm.mp3"
},
["Gameplay"] = new List
{
"textures/player.png",
"textures/enemy.png",
"textures/bullet.png",
"audio/game_bgm.mp3",
"audio/shoot.mp3",
"config/level_1.cfg"
},
["GameOver"] = new List
{
"textures/gameover_bg.png",
"audio/gameover.mp3"
}
};
// 当前场景的资源句柄
private readonly List _currentHandles = new();
///
/// 预加载场景资源
///
public async Task PreloadSceneAsync(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
{
Console.WriteLine($"场景 {sceneName} 没有配置资源");
return;
}
Console.WriteLine($"\n=== 开始预加载场景: {sceneName} ===");
var resourceManager = this.GetUtility();
// 并行加载所有资源
var tasks = new List();
foreach (var path in resources)
{
if (path.EndsWith(".png"))
{
tasks.Add(resourceManager.PreloadAsync(path));
}
else if (path.EndsWith(".mp3"))
{
tasks.Add(resourceManager.PreloadAsync(path));
}
else if (path.EndsWith(".cfg"))
{
tasks.Add(resourceManager.PreloadAsync(path));
}
}
await Task.WhenAll(tasks);
Console.WriteLine($"场景 {sceneName} 资源预加载完成\n");
}
///
/// 预加载场景资源(带进度)
///
public async Task PreloadSceneWithProgressAsync(
string sceneName,
Action onProgress)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
return;
Console.WriteLine($"\n=== 开始预加载场景: {sceneName} ===");
var resourceManager = this.GetUtility();
int totalCount = resources.Count;
int loadedCount = 0;
foreach (var path in resources)
{
// 根据扩展名加载不同类型的资源
if (path.EndsWith(".png"))
{
await resourceManager.PreloadAsync(path);
}
else if (path.EndsWith(".mp3"))
{
await resourceManager.PreloadAsync(path);
}
else if (path.EndsWith(".cfg"))
{
await resourceManager.PreloadAsync(path);
}
loadedCount++;
float progress = (float)loadedCount / totalCount;
onProgress?.Invoke(progress);
Console.WriteLine($"加载进度: {progress * 100:F0}% ({loadedCount}/{totalCount})");
}
Console.WriteLine($"场景 {sceneName} 资源预加载完成\n");
}
///
/// 获取场景资源句柄
///
public void AcquireSceneResources(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
return;
Console.WriteLine($"获取场景 {sceneName} 的资源句柄");
var resourceManager = this.GetUtility();
// 清理旧句柄
ReleaseCurrentResources();
// 获取新句柄
foreach (var path in resources)
{
IDisposable? handle = null;
if (path.EndsWith(".png"))
{
handle = resourceManager.GetHandle(path);
}
else if (path.EndsWith(".mp3"))
{
handle = resourceManager.GetHandle(path);
}
else if (path.EndsWith(".cfg"))
{
handle = resourceManager.GetHandle(path);
}
if (handle != null)
{
_currentHandles.Add(handle);
}
}
Console.WriteLine($"已获取 {_currentHandles.Count} 个资源句柄");
}
///
/// 释放当前场景资源
///
public void ReleaseCurrentResources()
{
Console.WriteLine($"释放 {_currentHandles.Count} 个资源句柄");
foreach (var handle in _currentHandles)
{
handle.Dispose();
}
_currentHandles.Clear();
}
///
/// 卸载场景资源
///
public void UnloadSceneResources(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
return;
Console.WriteLine($"\n卸载场景 {sceneName} 的资源");
var resourceManager = this.GetUtility();
foreach (var path in resources)
{
resourceManager.Unload(path);
}
Console.WriteLine($"场景 {sceneName} 资源已卸载\n");
}
///
/// 显示资源状态
///
public void ShowResourceStatus()
{
var resourceManager = this.GetUtility();
Console.WriteLine("\n=== 资源状态 ===");
Console.WriteLine($"已加载资源数: {resourceManager.LoadedResourceCount}");
Console.WriteLine("已加载资源列表:");
foreach (var path in resourceManager.GetLoadedResourcePaths())
{
Console.WriteLine($" - {path}");
}
Console.WriteLine();
}
}
}
```
**代码说明**:
- 使用字典配置每个场景需要的资源
- `PreloadSceneAsync` 并行预加载所有资源
- `PreloadSceneWithProgressAsync` 提供加载进度回调
- `AcquireSceneResources` 获取资源句柄防止被释放
- `ReleaseCurrentResources` 释放不再使用的资源
## 步骤 4:实现资源使用示例
创建一个游戏系统展示如何正确使用资源。
```csharp
using GFramework.Core.system;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.extensions;
using MyGame.Resources;
namespace MyGame.Systems
{
///
/// 游戏系统示例
///
public class GameplaySystem : AbstractSystem
{
private IResourceHandle? _playerTexture;
private IResourceHandle? _bgmClip;
///
/// 初始化游戏
///
public void InitializeGame()
{
Console.WriteLine("\n=== 初始化游戏 ===");
var resourceManager = this.GetUtility();
// 获取玩家纹理句柄
_playerTexture = resourceManager.GetHandle("textures/player.png");
if (_playerTexture?.Resource != null)
{
Console.WriteLine($"玩家纹理已加载: {_playerTexture.Resource.Width}x{_playerTexture.Resource.Height}");
}
// 获取背景音乐句柄
_bgmClip = resourceManager.GetHandle("audio/game_bgm.mp3");
if (_bgmClip?.Resource != null)
{
Console.WriteLine($"背景音乐已加载: {_bgmClip.Resource.Duration}秒");
}
Console.WriteLine("游戏初始化完成\n");
}
///
/// 使用临时资源(使用 using 语句)
///
public void SpawnBullet()
{
var resourceManager = this.GetUtility();
// 使用 using 语句自动管理资源生命周期
using var bulletTexture = resourceManager.GetHandle("textures/bullet.png");
if (bulletTexture?.Resource != null)
{
Console.WriteLine("创建子弹,使用纹理");
// 使用纹理创建子弹...
}
// 离开作用域后自动释放句柄
}
///
/// 播放音效(临时资源)
///
public void PlayShootSound()
{
var resourceManager = this.GetUtility();
using var shootSound = resourceManager.GetHandle("audio/shoot.mp3");
if (shootSound?.Resource != null)
{
Console.WriteLine("播放射击音效");
// 播放音效...
}
}
///
/// 清理游戏资源
///
public void CleanupGame()
{
Console.WriteLine("\n=== 清理游戏资源 ===");
// 释放长期持有的资源句柄
_playerTexture?.Dispose();
_playerTexture = null;
_bgmClip?.Dispose();
_bgmClip = null;
Console.WriteLine("游戏资源已清理\n");
}
}
}
```
**代码说明**:
- 长期使用的资源(玩家纹理、BGM)保存句柄
- 临时资源(子弹纹理、音效)使用 `using` 语句
- 在清理时释放所有持有的句柄
- 展示了正确的资源生命周期管理
## 步骤 5:测试资源管理
编写测试代码验证资源管理功能。
```csharp
using MyGame;
using MyGame.Systems;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== 资源管理最佳实践测试 ===\n");
// 1. 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 2. 获取系统
var preloadSystem = architecture.GetSystem();
var gameplaySystem = architecture.GetSystem();
// 3. 测试场景资源预加载
Console.WriteLine("--- 测试 1: 预加载菜单场景 ---");
await preloadSystem.PreloadSceneWithProgressAsync("Menu", progress =>
{
// 进度回调
});
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 4. 切换到游戏场景
Console.WriteLine("--- 测试 2: 切换到游戏场景 ---");
await preloadSystem.PreloadSceneWithProgressAsync("Gameplay", progress =>
{
// 进度回调
});
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 5. 获取场景资源句柄
Console.WriteLine("--- 测试 3: 获取游戏场景资源句柄 ---");
preloadSystem.AcquireSceneResources("Gameplay");
await Task.Delay(500);
// 6. 初始化游戏
Console.WriteLine("--- 测试 4: 初始化游戏 ---");
gameplaySystem.InitializeGame();
await Task.Delay(500);
// 7. 使用临时资源
Console.WriteLine("--- 测试 5: 使用临时资源 ---");
gameplaySystem.SpawnBullet();
gameplaySystem.PlayShootSound();
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 8. 清理游戏
Console.WriteLine("--- 测试 6: 清理游戏 ---");
gameplaySystem.CleanupGame();
await Task.Delay(500);
// 9. 释放场景资源句柄
Console.WriteLine("--- 测试 7: 释放场景资源句柄 ---");
preloadSystem.ReleaseCurrentResources();
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 10. 卸载旧场景资源
Console.WriteLine("--- 测试 8: 卸载菜单场景资源 ---");
preloadSystem.UnloadSceneResources("Menu");
preloadSystem.ShowResourceStatus();
Console.WriteLine("=== 测试完成 ===");
}
}
```
**代码说明**:
- 测试资源预加载和进度回调
- 测试场景切换时的资源管理
- 测试资源句柄的获取和释放
- 测试临时资源的自动管理
- 验证资源状态和内存清理
## 完整代码
所有代码文件已在上述步骤中提供。项目结构如下:
```
MyGame/
├── Resources/
│ ├── Texture.cs
│ ├── AudioClip.cs
│ ├── ConfigData.cs
│ ├── TextureLoader.cs
│ ├── AudioLoader.cs
│ └── ConfigLoader.cs
├── Systems/
│ ├── ResourcePreloadSystem.cs
│ └── GameplaySystem.cs
├── GameArchitecture.cs
└── Program.cs
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
=== 资源管理最佳实践测试 ===
资源管理器初始化完成
--- 测试 1: 预加载菜单场景 ---
=== 开始预加载场景: Menu ===
异步加载纹理: textures/menu_bg.png
异步加载纹理: textures/button.png
异步加载音频: audio/menu_bgm.mp3
加载进度: 33% (1/3)
加载进度: 67% (2/3)
加载进度: 100% (3/3)
场景 Menu 资源预加载完成
=== 资源状态 ===
已加载资源数: 3
已加载资源列表:
- textures/menu_bg.png
- textures/button.png
- audio/menu_bgm.mp3
--- 测试 2: 切换到游戏场景 ---
=== 开始预加载场景: Gameplay ===
异步加载纹理: textures/player.png
异步加载纹理: textures/enemy.png
异步加载纹理: textures/bullet.png
异步加载音频: audio/game_bgm.mp3
异步加载音频: audio/shoot.mp3
加载配置: config/level_1.cfg
加载进度: 17% (1/6)
加载进度: 33% (2/6)
加载进度: 50% (3/6)
加载进度: 67% (4/6)
加载进度: 83% (5/6)
加载进度: 100% (6/6)
场景 Gameplay 资源预加载完成
=== 资源状态 ===
已加载资源数: 9
--- 测试 3: 获取游戏场景资源句柄 ---
获取场景 Gameplay 的资源句柄
已获取 6 个资源句柄
--- 测试 4: 初始化游戏 ===
=== 初始化游戏 ===
玩家纹理已加载: 512x512
背景音乐已加载: 30秒
游戏初始化完成
--- 测试 5: 使用临时资源 ---
创建子弹,使用纹理
播放射击音效
--- 测试 6: 清理游戏 ---
=== 清理游戏资源 ===
游戏资源已清理
--- 测试 7: 释放场景资源句柄 ---
释放 6 个资源句柄
--- 测试 8: 卸载菜单场景资源 ---
卸载场景 Menu 的资源
纹理已释放: textures/menu_bg.png
纹理已释放: textures/button.png
音频已释放: audio/menu_bgm.mp3
场景 Menu 资源已卸载
=== 资源状态 ===
已加载资源数: 6
=== 测试完成 ===
```
**验证步骤**:
1. 资源预加载正常工作
2. 加载进度正确显示
3. 资源句柄管理正确
4. 临时资源自动释放
5. 资源卸载成功执行
6. 内存正确清理
## 下一步
恭喜!你已经掌握了资源管理的最佳实践。接下来可以学习:
- [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 在协程中加载资源
- [实现状态机](/zh-CN/tutorials/state-machine-tutorial) - 在状态切换时管理资源
- [实现存档系统](/zh-CN/tutorials/save-system) - 保存和加载游戏数据
## 相关文档
- [资源管理系统](/zh-CN/core/resource) - 资源系统详细说明
- [对象池系统](/zh-CN/core/pool) - 结合对象池复用资源
- [协程系统](/zh-CN/core/coroutine) - 异步加载资源
- [System 层](/zh-CN/core/system) - System 详细说明