在Unity中用GeneticSharp评估染色体时,如何等待物理模拟完成获取圈速?
解决Unity遗传算法中物理模拟等待的问题
针对你在GeneticSharp框架下无法同步等待物理模拟完成的问题,以下是三种可行的解决方案:
方案一:主线程任务队列+同步阻塞
核心思路是将物理模拟任务提交到Unity主线程队列,后台线程通过信号量等待模拟完成后获取结果(所有Unity API操作必须在主线程执行)。
代码修改步骤:
- 扩展GAController,添加任务队列与同步机制
public class GAController : MonoBehaviour { // 单例供Fitness类访问 public static GAController Instance { get; private set; } public SplineCar Car; public int CarCount; public AIPath AIPath; public float BaseTime; private GeneticAlgorithm _geneticAlgorithm; private Thread _geneticAlgorithmThread; // 模拟任务队列 private ConcurrentQueue<SimulationTask> _simulationTasks = new ConcurrentQueue<SimulationTask>(); // 模拟任务结构体,存储请求与结果 public class SimulationTask { public AIPath.WaypointData[] Waypoints; public AutoResetEvent CompletionEvent; public float ResultLapTime; } private void Awake() { Instance = this; } private void Start() { // 保留原有初始化代码... } private void Update() { // 主线程处理模拟任务 while (_simulationTasks.TryDequeue(out var task)) { // 应用新的路径限速 AIPath.Waypoints = task.Waypoints.ToList(); // 重置车辆到起点状态 Car.ResetToStartingPosition(); // 启动协程执行模拟 StartCoroutine(RunLapAndNotify(task)); } } private IEnumerator RunLapAndNotify(SimulationTask task) { // 假设SplineCar实现了跑圈协程,完成后更新LapTime yield return Car.RunLapCoroutine(); task.ResultLapTime = Car.LapTime; // 通知后台线程任务完成 task.CompletionEvent.Set(); } private void OnDestroy() { // 保留原有销毁代码... } }
- 修改SplineFitness的Evaluate方法
public double Evaluate(IChromosome chromosome) { var genes = chromosome.GetGenes(); // 构建新的路径点数据 var newWaypoints = new AIPath.WaypointData[genes.Length]; for (int i = 0; i < genes.Length; i++) { var pathWaypoint = AIPath.Waypoints[i]; float waypointSpeed = Convert.ToInt32(genes[i].Value, CultureInfo.InvariantCulture); newWaypoints[i] = new AIPath.WaypointData(pathWaypoint.Point, pathWaypoint.OvertakeZoneLeft, pathWaypoint.OvertakeZoneRight, waypointSpeed); } // 创建模拟任务并提交到主线程队列 var task = new GAController.SimulationTask { Waypoints = newWaypoints, CompletionEvent = new AutoResetEvent(false) }; GAController.Instance._simulationTasks.Enqueue(task); // 阻塞等待模拟完成(可添加超时避免死锁) task.CompletionEvent.WaitOne(TimeSpan.FromSeconds(10)); float lapTime = task.ResultLapTime; task.CompletionEvent.Dispose(); ((SplineChromosome)chromosome).LapTime = lapTime; var diffWithBase = BaseTime - lapTime; return diffWithBase < 0 ? 0 : diffWithBase; }
方案二:数值模拟替代真实物理
如果真实物理模拟耗时过高,可基于车辆特性和路径参数构建简化的数值模型,直接计算理论圈速,无需依赖Unity物理引擎,完全在后台线程执行。
示例代码:
private float CalculateTheoreticalLapTime(AIPath.WaypointData[] waypoints) { float totalTime = 0; Vector3 lastPos = waypoints[0].Point; float currentSpeed = 0; for (int i = 1; i < waypoints.Length; i++) { var currentWaypoint = waypoints[i]; float distance = Vector3.Distance(lastPos, currentWaypoint.Point); float maxAllowedSpeed = currentWaypoint.Speed; // 计算加速阶段时间与距离 float acceleration = Car.MaxAcceleration; float timeToAccelerate = (maxAllowedSpeed - currentSpeed) / acceleration; float accelDistance = currentSpeed * timeToAccelerate + 0.5f * acceleration * timeToAccelerate * timeToAccelerate; if (accelDistance < distance) { // 先加速到限速,再匀速行驶剩余距离 totalTime += timeToAccelerate; float remainingDistance = distance - accelDistance; totalTime += remainingDistance / maxAllowedSpeed; currentSpeed = maxAllowedSpeed; } else { // 全程加速,无法达到限速 float time = (-currentSpeed + Mathf.Sqrt(currentSpeed * currentSpeed + 2 * acceleration * distance)) / acceleration; totalTime += time; currentSpeed += acceleration * time; } // 弯道减速逻辑(根据路径曲率调整) float curvature = CalculatePathCurvature(lastPos, currentWaypoint.Point, waypoints[Mathf.Min(i+1, waypoints.Length-1)].Point); float maxCornerSpeed = Car.MaxCornerSpeed / (1 + curvature * 0.1f); if (currentSpeed > maxCornerSpeed) { float deceleration = Car.MaxDeceleration; float timeToDecelerate = (currentSpeed - maxCornerSpeed) / deceleration; totalTime += timeToDecelerate; currentSpeed = maxCornerSpeed; } lastPos = currentWaypoint.Point; } return totalTime; } // 计算三点路径的曲率简化值 private float CalculatePathCurvature(Vector3 a, Vector3 b, Vector3 c) { Vector3 ab = b - a; Vector3 bc = c - b; float crossMagnitude = Vector3.Cross(ab, bc).magnitude; return crossMagnitude / (ab.magnitude * bc.magnitude * ab.magnitude); }
在Evaluate方法中直接调用CalculateTheoreticalLapTime即可,无需等待物理模拟。
方案三:多实例并行物理模拟
若必须使用真实物理,可在Unity中创建多个车辆实例,通过协程并行处理多个模拟任务,减少单线程等待时间。但此方案会增加资源消耗,需根据硬件配置调整实例数量。
核心思路:
- 在GAController中初始化多个SplineCar实例
- 每个模拟任务分配一个空闲车辆实例
- 用协程同时执行多个跑圈模拟,收集结果后返回给后台线程
关键注意事项
- 线程安全:所有Unity API操作必须在主线程执行,禁止在后台线程直接修改GameObject或组件
- 性能优化:遗传算法迭代频繁,优先选择数值模拟方案,真实物理模拟仅在最终验证时使用
- 超时机制:同步等待时需设置超时时间,避免线程无限阻塞
内容的提问来源于stack exchange,提问作者Yona Rutkowski
相关产品推荐
相关产品推荐

