You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Unity中用GeneticSharp评估染色体时,如何等待物理模拟完成获取圈速?

解决Unity遗传算法中物理模拟等待的问题

针对你在GeneticSharp框架下无法同步等待物理模拟完成的问题,以下是三种可行的解决方案:


方案一:主线程任务队列+同步阻塞

核心思路是将物理模拟任务提交到Unity主线程队列,后台线程通过信号量等待模拟完成后获取结果(所有Unity API操作必须在主线程执行)。

代码修改步骤:

  1. 扩展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()
    {
        // 保留原有销毁代码...
    }
}
  1. 修改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中创建多个车辆实例,通过协程并行处理多个模拟任务,减少单线程等待时间。但此方案会增加资源消耗,需根据硬件配置调整实例数量。

核心思路:

  1. 在GAController中初始化多个SplineCar实例
  2. 每个模拟任务分配一个空闲车辆实例
  3. 用协程同时执行多个跑圈模拟,收集结果后返回给后台线程

关键注意事项

  • 线程安全:所有Unity API操作必须在主线程执行,禁止在后台线程直接修改GameObject或组件
  • 性能优化:遗传算法迭代频繁,优先选择数值模拟方案,真实物理模拟仅在最终验证时使用
  • 超时机制:同步等待时需设置超时时间,避免线程无限阻塞

内容的提问来源于stack exchange,提问作者Yona Rutkowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 05:02:08