【Unity学习笔记01:2D物理系统(一)】


记录一下这段时间学习和实践Unity的心得体会,单纯的Manual翻译就不放上来了,主要记录一下官方手册未提及或不够清楚,或容易误解混淆的知识点。

2D物理系统学习笔记(一)

在学习和实践过程中主要遇见了以下几个方面的问题。

两大组件:Collider2D和Rigidbody2D各自承担的功能以及之间的联系区别

Collider2D组件做的事:

在这里插入图片描述

  1. 定义碰撞体积,包括位置(offset属性)、形状、大小、物理材质(决定摩擦力、弹力)等等;
  2. IsTrigger属性,定义碰撞事件集合;
  3. Used By Effector属性,定义碰撞体是否受Effector2D组件影响,关于场效应器因为没怎么用到也没怎么研究(都用OnTriggerStay自己写逻辑实现功能了2333),所以暂不讨论

Rigidbody2D组件做的事:

在这里插入图片描述

  1. 通过组件内的各种设置为关联的碰撞体模拟一个物理实体:模拟方式、质量、线阻力、角阻力、位移方向冻结、位移是否插值等等。注意一个刚体组件会关联本物体和所有子物体上的Collider2D组件所代表的碰撞体(取最近距离)
  2. 根据组件内的各种设置参加物理运算,用运算结果覆写Transform里的位置、旋转等参数。理解这一点很重要,这解释了为什么不能通过Transform组件对带有刚体组件的物体进行移动控制:那样会导致物理运算得出的物体移动又再被覆写而失真(比如本应碰撞后弹开,结果却穿过)
  3. 性能相关的其他设置:碰撞检测间隔、睡眠模式等;

Tip:Collider2D和Rigidbody2D这两个组件应该总是成对出现。事实上挂载Collider2D而未挂载Rigidbody2D的对象会被物理系统默认为Static类型的刚体去参加物理运算;而没有碰撞体积的刚体不会对物理运算产生任何影响(一个有质量的点?),因此也没有使用刚体的意义。

两种碰撞事件集合

Unity的定义了两种碰撞事件集合:OnTrigger事件集合,OnCollision以及事件集合。这里不再重复事件集合里Enter、Exit、Stay事件的定义和触发时机,官方手册定义得很清楚,主要记录一下物体A和物体B碰撞时事件集的触发条件:

触发OnTrigger的条件:
  1. A和B都拥有Collider2D组件;
  2. A和B至少一个被标记为触发器,即IsTrigger设为true,两个都为true也是一样的效果;
  3. A和B至少一个拥有动态/运动学刚体组件;
  4. A和B所属的Layer在碰撞矩阵中设置为可互相产生碰撞,这个在ProjectSetting->Physic2D中进行设置;
触发OnCollision的条件:
  1. A和B都拥有Collider2D组件;
  2. A和B都不能被标记为触发器,即IsTrigger都设为false;
  3. A和B至少一个拥有动态/开启了Use Full Kinematic Contacts选项的运动学刚体组件;
  4. A和B所属的Layer在碰撞矩阵中设置为可互相产生碰撞,这个在ProjectSetting->Physic2D中进行设置;
“实际物理碰撞模拟事件”,也就是两个碰撞的物体是否会彼此弹开而不是彼此穿过,该类事件的触发条件:
  1. A和B都拥有Collider2D组件;
  2. A和B都不能被标记为触发器,即IsTrigger都设为false;
  3. A和B至少一个拥有动态刚体组件;
  4. A和B所属的Layer在碰撞矩阵中设置为可互相产生碰撞,这个在ProjectSetting->Physic2D中进行设置;
    Tip:官方手册中“collides with”这个词的意思其实是触发OnCollision事件集而非实际物理碰撞模拟。例如一个开启了Use Full Kinematic Contacts选项的运动学刚体跟Static组件碰撞时会触发OnCollision事件集,但不会像动态刚体那样被挡住推回,而是会径直穿过,感觉这是官方手册没有讲清楚的一个点。

Trigger还是Collision:

  1. Trigger更方便、更节省性能。OnTrigger事件的输入参数Collider2D other是另一个碰撞体的引用,因此可以用来处理**“敌人碰撞到子弹后扣血”**这种相对简单的功能;
  2. Collision更精确、更消耗性能。OnCollision事件的输入参数Collision2D other是物理系统对该次碰撞的运算结果,包括碰撞点、碰撞法线等更详细的信息,因此可以用来处理**“子弹碰撞到敌人后以精确的位置和方向播放击中特效”**这种更复杂的功能;

动态、运动学还是Static:

  1. 动态最复杂也最消耗性能,一般用于玩家单位,虽然官方手册推荐使用AddForce函数实现移动,但直接修改velocity或调用Rigidbody.MovePosition也不会出错(“实际物理碰撞模拟事件”能正常触发);
  2. 运动学只能直接修改velocity或调用Rigidbody.MovePosition来控制移动,因其被物理系统视为质量无穷大,所以无法受力,只能推开其他碰撞体而无法被推开。故此运动学刚体和运动学刚体、Static刚体间是无法触发“实际物理碰撞模拟事件”的(会互相穿过)。未开启Use Full Kinematic Contacts的运动学刚体只会在与动态刚体碰撞的时候触发OnCollision事件,开启后与其他运动学或Static刚体也能触发OnCollision事件,但开启与否都不会触发“实际物理碰撞模拟事件”;
  3. Static最节省性能,默认无穷大质量因此不会因物理因素而移动(直接修改还是能移动的),因此常用于地图边界。但在单位是运动学刚体的时候会面临一个问题,运动学刚体和Static刚体间没法触发“实际物理碰撞模拟事件”。在这类需求下经常用冻结所有移动和旋转的动态刚体替代Static刚体以触发“实际物理碰撞模拟事件”,让敌人走不出地图范围。

动态刚体的移动控制

超级麻烦,由于Mass和LinearDrag参数的存在,简单定义一个速度,需要各种换算后才能作为AddForce的输入参数,而结果仅仅只是实现了一个逼真的加减速效果,LinearDrag的实际生效公式官方手册还没说……所以我最后是弃用了这玩意,直接用加速度修改velocity去了。

新手很容易遇到的一个坑:“OnTrigger2DEnter触发两次、触发多次、被Deactive后仍然触发、Destroy后仍然触发”:

在学习的时候遇到了这个问题。后来在Unity社区一查发现有一堆相似的问题,时间跨度都好几年了,答案也是五花八门(不得不吐槽一下现在的互联网环境,随着时间的增长,网上遗留了好多错误或者过时的信息,现在想快速查找一个问题的答案真的越来越难了,所有互联网产品都在追求不带脑子网上冲浪的体验……),后来自己想明白了。这里记录一下正确的答案,事实上在社区里已经发过一次Discussion了。

public bool test = false;
void OnTriggerEnter2D(Collider2D other)
{
    if (test == true && (gameObject.activeSelf == false || _collider2D.enabled == false))
    {
        Debug.Log("Error");
    }

    gameObject.SetActive(false);
    _collider2D.enabled = false;
    test = true;
    Destroy(gameObject);
}

上面代码的使用情景是一个弹幕游戏Demo,大量的子弹时刻都在和大量的敌人碰撞,在运行时偶尔会打印出Error。看起来像是一个active为false,或者碰撞体被disable,甚至是已经被Destroy调用的物体仍然会触发事件,当时想了很多种可能性,比如SetActive会推迟到下一次Update才会真正执行之类的(Unity不可能有这么误导人的机制)。其实这里真正的原因是碰撞事件的触发执行机制:在一次FixedUpdate周期内,Unity先进行所有的物理运算,再生成所有的碰撞事件,最后再发送给所有碰撞相关的物体触发监听事件,这一切都是在同一个物理帧内完成的。也就是说如果在同一个物理帧内,子弹A同时和敌人B、敌人C碰撞,A会获得两个OnTriggerEnter2D事件回调,假设先触发和B的回调,此回调中子弹肯定会在造成伤害后关掉自己,但之后和C的Trigger事件仍然会正常触发。因此正确的做法应该是在每个事件监听的最前面都手动判断一下“自己还能不能触发碰撞”,类似下面这样:

public bool alive = false;
void OnTriggerEnter2D(Collider2D other)
{
    if (!alive)
    {
        return;
    }

    alive= false;
    gameObject.SetActive(false);
}

入行好几年了,这才第一次在CSDN上发自己的学习笔记,希望能坚持下去,提升自己,此外要是还能对他人产生一些帮助就太好了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值