You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity无尽跑酷游戏路径生成器运行数分钟后失效问题排查

问题:Unity无尽跑酷游戏路径复用代码数分钟后失效

你正在开发一款类似《神庙逃亡》的无尽跑酷游戏,这是你用Unity制作的第一款游戏,而且三周前才开始学习C#(这还是你的第一门编程语言)。你写了一段代码,当玩家穿过触发碰撞器时,会把相机后方的路径移到玩家前方——代码起初运行正常,但数分钟后就失效了。咱们来一步步解决这个问题:

你的原始代码

public List<GameObject> paths; 
public float offset = 50; // Removes Path behind the Camera or Player and moves it infront.
public void movePath() { 
    GameObject movePath = paths[0]; 
    paths.Remove(movePath); 
    float newZPos = paths[paths.Count - 1].transform.position.z + offset; 
    movePath.transform.position = new Vector3(0, 0, newZPos); 
    paths.Add(movePath); 
}

问题根源:浮点数精度丢失

作为新手能写出这样的循环复用逻辑已经很厉害了!不过这里的核心问题是float类型的浮点数精度丢失

你每次计算新路径的Z坐标时,都是基于前一个路径的Z值累加offset。但float类型的数值在多次累加后,会因为二进制存储的特性出现微小的精度误差——这些误差会随着次数增多慢慢放大,数分钟后,路径的实际位置会和预期位置偏差越来越大,最终导致触发逻辑失效(比如碰撞器没被正确触发,或者路径位置混乱到系统无法识别)。

另外还有个潜在的小风险:如果paths列表意外为空(比如初始化错误),paths[0]或者paths[paths.Count -1]会直接抛出空引用错误,但你说的是数分钟后失效,所以主要问题还是浮点数精度。

解决方案:避免累加,用索引计算准确位置

咱们换一种计算方式,不要用浮点数累加,而是基于路径的初始索引来计算绝对位置,彻底避免精度丢失。这里给你两种方案,选你觉得容易理解的就行:

方案一:直接计算索引(简单易上手)

using System.Collections.Generic;
using UnityEngine;

public class PathManager : MonoBehaviour
{
    [SerializeField] private List<GameObject> paths; // 用SerializeField让列表在Inspector可见,更安全
    [SerializeField] private float offset = 50;
    private float _baseZ; // 记录第一个路径的初始Z位置作为基准

    void Start()
    {
        if(paths.Count == 0)
        {
            Debug.LogError("请在Inspector中添加路径对象!");
            return;
        }
        // 记录初始基准Z值(第一个路径的初始位置)
        _baseZ = paths[0].transform.position.z;
    }

    public void movePath()
    {
        if(paths.Count == 0) return; // 安全判断,防止空列表报错

        GameObject movePath = paths[0];
        paths.Remove(movePath);

        // 通过当前最后一个路径的Z值反推索引,再计算新位置
        float lastPathZ = paths[paths.Count - 1].transform.position.z;
        int lastIndex = Mathf.RoundToInt((lastPathZ - _baseZ) / offset);
        float newZPos = _baseZ + (lastIndex + 1) * offset;

        movePath.transform.position = new Vector3(0, 0, newZPos);
        paths.Add(movePath);

        // 可选:打印日志观察数值,方便排查
        Debug.Log($"移动后的路径Z值:{newZPos}");
    }
}

方案二:用字典记录初始索引(更严谨)

如果担心计算索引时的误差,可以提前用字典记录每个路径的初始索引,彻底避免计算偏差:

using System.Collections.Generic;
using UnityEngine;

public class PathManager : MonoBehaviour
{
    [SerializeField] private List<GameObject> paths;
    [SerializeField] private float offset = 50;
    private float _baseZ;
    private Dictionary<GameObject, int> _pathInitialIndices; // 记录每个路径的初始索引

    void Start()
    {
        if(paths.Count == 0)
        {
            Debug.LogError("请在Inspector中添加路径对象!");
            return;
        }

        _baseZ = paths[0].transform.position.z;
        _pathInitialIndices = new Dictionary<GameObject, int>();
        // 初始化每个路径的初始索引
        for(int i = 0; i < paths.Count; i++)
        {
            _pathInitialIndices.Add(paths[i], i);
        }
    }

    public void movePath()
    {
        if(paths.Count == 0 || !_pathInitialIndices.ContainsKey(paths[paths.Count -1])) return;

        GameObject movePath = paths[0];
        paths.Remove(movePath);

        // 获取当前最后一个路径的初始索引,加列表长度保证位置持续向前
        int lastInitialIndex = _pathInitialIndices[paths[paths.Count -1]];
        int newIndex = lastInitialIndex + paths.Count;
        float newZPos = _baseZ + newIndex * offset;

        movePath.transform.position = new Vector3(0, 0, newZPos);
        paths.Add(movePath);
    }
}

新手友好小提示

  • paths列表加[SerializeField]标签,这样即使设为private也能在Unity Inspector里赋值,比public更安全;
  • 每次修改代码后,在Unity编辑器的Scene视图里检查路径的Z坐标,看看是不是始终准确;
  • Debug.Log打印关键数值,能帮你快速观察数值变化,排查问题。

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

火山引擎 最新活动