Unity异步程序化地形生成:如何保证世代生成顺序执行?
解决Unity世代程序化地形生成的顺序异步执行问题
针对你遇到的「前一世代未完成就启动新世代导致顺序混乱」的问题,核心是要实现异步任务的串行执行——每个新世代的生成任务必须等待前一世代完全结束后才能启动。以下是几种实用的实现方案:
方案一:异步任务链(Task Chaining)
维护一个记录当前/上一世代生成任务的变量,每次新请求生成时,先等待上一个任务完成,再启动新的异步任务。这种方式逻辑简洁,适合单线程触发生成的场景。
using System.Threading.Tasks; using UnityEngine; public class TerrainGenerationManager : MonoBehaviour { // 初始化为已完成的任务,确保第一次调用无需等待 private Task _currentGenerationTask = Task.CompletedTask; // 调用此方法触发下一世代生成 public async Task GenerateNextGeneration(int generationNumber) { // 等待上一世代的生成任务完成 await _currentGenerationTask; // 将当前世代的生成任务赋值给变量,供下一次调用等待 _currentGenerationTask = Task.Run(() => { // 这里执行耗时的地形生成逻辑(纯计算,避免Unity主线程API) GenerateSector(generationNumber); // 存储当前世代结果,作为下一世代的输入 SaveGenerationData(generationNumber); }); // 可选:等待当前世代完成后再返回(调用方可以选择await此方法) await _currentGenerationTask; } private void GenerateSector(int generation) { // 你的程序化地形生成逻辑(噪声、网格计算等) Debug.Log($"正在生成世代 {generation}"); } private void SaveGenerationData(int generation) { // 存储当前世代数据到内存/磁盘 Debug.Log($"已保存世代 {generation} 数据"); } }
方案二:异步信号量(SemaphoreSlim)
使用信号量控制并发数为1,强制所有生成任务排队执行。这种方式适合多入口触发生成的场景(比如UI按钮、游戏逻辑同时请求生成),能自动处理排队逻辑。
using System.Threading; using System.Threading.Tasks; using UnityEngine; public class TerrainGenerationManager : MonoBehaviour { // 初始信号量计数设为1,同一时间仅允许一个任务执行 private readonly SemaphoreSlim _generationSemaphore = new SemaphoreSlim(1, 1); public async Task GenerateNextGeneration(int generationNumber) { // 等待获取信号量(自动排队,前一个任务释放后才会执行) await _generationSemaphore.WaitAsync(); try { // 后台执行耗时生成逻辑 await Task.Run(() => { GenerateSector(generationNumber); SaveGenerationData(generationNumber); }); } finally { // 无论是否异常,都释放信号量,避免死锁 _generationSemaphore.Release(); } } // 省略GenerateSector、SaveGenerationData方法(同方案一) }
方案三:自定义任务队列
维护一个请求队列,启动一个后台循环任务,持续从队列中取出请求并顺序执行。这种方式适合批量提交生成请求的场景,能清晰控制任务的排队与执行顺序。
using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; public class TerrainGenerationManager : MonoBehaviour { private readonly Queue<int> _generationQueue = new Queue<int>(); private bool _isProcessingQueue = false; // 调用此方法将生成请求加入队列 public void EnqueueGeneration(int generationNumber) { lock (_generationQueue) { _generationQueue.Enqueue(generationNumber); } // 如果当前未在处理队列,启动处理循环 if (!_isProcessingQueue) { _ = ProcessGenerationQueueAsync(); } } private async Task ProcessGenerationQueueAsync() { _isProcessingQueue = true; try { while (true) { int currentGeneration; lock (_generationQueue) { if (_generationQueue.Count == 0) break; currentGeneration = _generationQueue.Dequeue(); } // 顺序执行生成任务 await Task.Run(() => { GenerateSector(currentGeneration); SaveGenerationData(currentGeneration); }); } } finally { _isProcessingQueue = false; } } // 省略GenerateSector、SaveGenerationData方法(同方案一) }
Unity专属注意事项
- 地形生成的纯计算逻辑(比如噪声生成、网格顶点计算)可以放心放在后台线程,但如果涉及Unity的主线程API(比如创建GameObject、修改场景对象),需要在生成完成后切回主线程处理。可以自行实现
UnityMainThreadDispatcher,或者通过await Task.Run(...)后在主线程执行后续操作。 - 若需要支持任务取消,可以在
Task.Run时传入CancellationToken,并在生成逻辑中定期检查取消状态,避免无效计算。
内容的提问来源于stack exchange,提问作者arcadeperfect
相关产品推荐
相关产品推荐

