【Unity】Unity开发中的坑 20250416更新

本文讲述了在Unity开发过程中遇到的两个问题:复用对象时TrailRenderer产生的拖尾问题以及修改Rigidbody2DVelocity不生效的情况,提供了解决方案和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

开个坑记录一下开发以来遇到的一些坑,给大伙避避。本文持续更新!
最后一次更新时间:2024.6.25

1. 复用对象时,Trail Renderer会产生诡异拖尾的情况

在本次项目中,我的子弹对象使用了对象池复用bullet对象,这样考虑到bullet有tail renderer的缘故,每次复用,若正常先显示对象后改变坐标,则会在场景中出现很明显的拖尾效果,这明显不是我们想要的,下图对比正常我们需要的效果,以及出现问题的效果
错误效果
异常效果
正确效果
正常效果
这种错误情况出现的原因主要是,Trail Renderer只要对象坐标进行了移动就会产生拖尾效果,此时应该在修改完坐标后,手动去Clear一下Trail Renderer的轨迹,伪代码如下

Bullet bullet = Pool.Get(); // 从对象池取出对象
bullet.transform.position = muzzle.transform.position; // 修改对象坐标

bullet.GetComponent<TrailRenderer>().Clear(); // 手动清除一下修改坐标后TrailRenderer的遗留轨迹 **这里就是需要新增的地方**

2. 修改Rigidbody2D的Velocity不生效的情况

同样是上面对象池的情况,在取出来一个对象后,需要给对象的Rigidbody赋予一个初速度的情况,由于我本人习惯在初始化完成后,才会将对象的active设为true,所以才会碰上这个bug,我的伪代码如下

Bullet bullet = Pool.Get(); // 从对象池取出对象

bullet.GetComponent<Rigidbody2D>().Velocity = TargetVelocity; // 设置刚体速度
bullet.gameobject.SetActive(true);

这种情况,在对象还未设置active为true之前就将刚体速度设置好,导致的结果就是velocity设置后,刚体的sleep state依旧是asleep,由此velocity就无法成功设置上,就会出现一个奇怪的现象,明明对象activetrue的,但刚体却是asleep状态,相反对象activefalse的时候刚体反而是awake,解决方法就是先将gameobject的active设为true后再对其刚体进行赋值。

Bullet bullet = Pool.Get(); // 从对象池取出对象

bullet.gameobject.SetActive(true);
bullet.GetComponent<Rigidbody2D>().Velocity = TargetVelocity; // 设置速度

3. Vector2.Lerp对速度进行修改后速度无法到达0(浮点数精度的经典bug)

当我们要实现速度以一定加速度实现加减速时,我们通常会使用Lerp来实现,如:

_Velocity = Vector2.Lerp(_Velocity, _TargetVelocity, Acceleration * deltaTime);

当我们设置TargetVelocity为Vector2.zero时,却发现Velocity始终无法变为Vector2.zero,我们打印一下Velocity.x是如下情况:
在这里插入图片描述
这在需要判断速度是否为0时就不能使用简单的Vector2.zero == Velocity来判断,而应该设置一个误差值,当数值小于误差值时判定为Vector2.zero

_Velocity = _Velocity.magnitude < 0.01f ? Vector2.zero : _Velocity; // 其中0.01f为误差值,这个值应该自己设置

当然还有一种情况就是当我们需要知道物体此时的运动方向也可能会用到Velocity,用Velocity的不同分量的正负来判断方向,而遇到这种情况时Velocity分量无法为0,即无法判断物体是否为静止状态,此时需要对Velocity的每一个分量进行单独操作

_Velocity.x = Mathf.Abs(_Velocity.x) < 0.01f ? 0 : _Velocity.x;
_Velocity.y = Mathf.Abs(_Velocity.y) < 0.01f ? 0 : _Velocity.y;

这样就解决分量不为0的情况了,当然由于浮点数精度导致其他类似的情况都可以用这种方法来解决

4. 单例对象运行后消失(2025.4.16更新)

实现了一个单例脚本

// Script is created from iCan_qi
using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : class
{
    private static T _Instance;
    public static T Instance
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = FindAnyObjectByType(typeof(T)) as T;
            }
            return _Instance;
        }
    }

    public bool DontDestroy = false;
    protected virtual void Awake()
    {
        if (_Instance != null)
        {
            Destroy(this.gameObject);
            return;
        }
        _Instance = this as T;

        if (DontDestroy)
        {
            DontDestroyOnLoad(gameObject);
        }
    }

    protected virtual void OnDestroy()
    {
        if (ReferenceEquals(this, _Instance))
        {
            _Instance = null;
        }
    }
}

写了一个管理场景内2D精灵sorting order的管理器,发现每次一运行,这个场景内的管理器就会被删除,原因是sorting order的脚本在OnEnable中向管理器注册了自己的SpriteRenderer和Transform,但为了让对象在editor状态下也能根据对象的移动而修改sorting order,导致在编辑器状态下就会创建一个管理器单例,而运行后判断为已存在管理器就会将场景内的管理器移除,且编辑器状态下的单例也会将自己移除,场景内就不存在这个管理器了,下面是管理器和sorting order的脚本:

// Script is created from iCan_qi
using System.Collections.Generic;
using UnityEngine;

public struct RendererSortingOrder
{
    public bool IsActive;
    public SpriteRenderer Renderer;
    public Transform Transform;
}

public class SortingOrderByYManager : Singleton<SortingOrderByYManager>
{
    private List<RendererSortingOrder> _ListSort = new List<RendererSortingOrder>();

    private Stack<int> _StackEmptyIndex = new Stack<int>();

    void LateUpdate()
    {
        HandleSortingOrder();
    }

    void HandleSortingOrder()
    {
        for (int i = 0; i < _ListSort.Count; i++)
        {
            if (!_ListSort[i].IsActive)
            {
                continue;
            }
            _ListSort[i].Renderer.sortingOrder = -(int)(_ListSort[i].Transform.position.y * 100f);
        }
    }

    public int AddRendererSortingOrder(SpriteRenderer renderer, Transform transform)
    {
        RendererSortingOrder rendererSortingOrder = new RendererSortingOrder
        {
            IsActive = true,
            Renderer = renderer,
            Transform = transform
        };

        int index = -1;
        if (_StackEmptyIndex.Count > 0)
        {
            index = _StackEmptyIndex.Pop();
            _ListSort[index] = rendererSortingOrder;
        }
        else
        {
            index = _ListSort.Count;
            _ListSort.Add(rendererSortingOrder);
        }

        return index;
    }

    public void RemoveRendererSortingOrder(int index)
    {
        if (index < 0 || index >= _ListSort.Count)
        {
            return;
        }
        _ListSort[index] = default;
        _StackEmptyIndex.Push(index);
        return;
    }
}
// Script is created from iCan_qi
using UnityEngine;
// [ExecuteInEditMode] 罪魁祸首
public class SortingOrderByY : MonoBehaviour
{
    public int sortingOrder = 0;
    public bool IsStatic = true;
    private int index = -1;

    private void OnEnable()
    {
        SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
        sortingOrder = -(int)(transform.position.y * 100);
        spriteRenderer.sortingOrder = sortingOrder;
        if (!IsStatic)
        {
            if (SortingOrderByYManager.Instance == null)
            {
                index = -1;
                return;
            }
            index = SortingOrderByYManager.Instance.AddRendererSortingOrder(spriteRenderer, transform);
        }
    }

    private void OnDisable()
    {
        if (IsStatic)
        {
            return;
        }

        SortingOrderByYManager.Instance?.RemoveRendererSortingOrder(index);
    }
}

只要让脚本不在编辑器状态下运行就能解决这个问题了。

未完待续

后续不定期更新文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值