Unity2D和3D的物理引擎是基于PhyX的,内置的碰撞检测也是基于PhyX的。PhyX可以模拟很真实的物理效果。但是对于游戏来说太过真实的物理效果反而看起来很假,游戏需要的是可配置性的“物理效果”,例如按帧或者时间线的方式来编辑产生类似物理的效果 ,所以目前大量的游戏几乎都是不使用物理引擎的。在Unity中可以关闭物理效果,只用它的碰撞功能,或者整理的碰撞功能都自己编写代码来完成
Collider 2D 任何的碰撞现象都有两个载体,一个是发起碰撞的,另一个是接受碰撞的,所以我们首先要明确哪些物体是可以接受碰撞的。Collider2D并不依赖Sprite组件,就好比一个空气墙
capture Collider一般用于主角,其他形状用于场景或者动态阻挡等。composite Collider表示将Box Collider和Polygon Collider的外形合并,
Tilemap Collider 2D专用于tile,它经常与composite Collider合并使用,来消除无用的tile碰撞边界
Rigidbody 2d 表示2D刚体组件,表示当前物体启动物理引擎。如果希望操控的角色被其他碰撞体组件挡住,就必须使用刚体组件
Body Type介绍:
Dynamic:表示动态刚体,完全模拟物理效果,碰到Collider 2D会被挡住,碰到任意Rigidbody 2D都会产生物理效果。它在空中会根据重力自动落下来,它的效率是最低的,仅适合给主角使用
Kinematic:它只能和选中Dynamic复选框的Rigidbody发生碰撞效果。如果需要碰撞事件,比如OnCollisionEnter2D
,或者Kinematic与Kinematic碰撞,两者必须要有一个选中USE Full Kinematic Contacts复选框。Kinematic与Static碰撞,Kinematic必须选中USE Full Kinematic Contacts复选框,否则碰撞事件也没有了。Kinematic适合做主角被攻击的碰撞检测。比如主角被别的物体击飞,发生击飞的物体可以设置Kinematic。因为主角已经是Dynamic了,可以正常触发碰撞效果,如果使用Kinematic效率比Dynamic好
Static:只能和Dynamic发生碰撞物理效果,和Kinematic只能发生碰撞事件(需要保证Kinematic必须勾选USE Full Kinematic Contact复选框)它的效率是最高的
**如果需要移动或者旋转带Rigidbody 2D的组件时,不能直接修改它的Transform.position
,而是要使用Rigidbody.position
和Rigidbody.rotation
**否则无法发生正确物理现象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Tilemaps;public class Script_06_10 : MonoBehaviour { public SpriteRenderer heroRenderer; public RigidBody2D heroRigidbody2D; void Update () { if (Input.GetKey(KeyCode.W)){ Run(Vector3.up); }else if (Input.GetKey(KeyCode.S)){ Run(Vector3.down); }else if (Input.GetKey(KeyCode.A)){ Run(Vector3.left,true ); }else if (Input.GetKey(KeyCode.D)){ Run(Vector3.right,false ); } } void Run (Vector2 position,bool flipx = false ) { heroRenderer.flipx = flipx; heroRigidbody2D.position += (position * 0.1f ); } }
碰撞事件 碰撞事件和碰撞效果是两个不同的概念。
碰撞事件表示Collider2D被RIgidbody2D碰撞后发生的事件,碰撞事件会被碰撞者和碰撞者同时接收到。
碰撞效果指的是碰撞事件触发的效果,比如主角被空气墙挡住
主角碰到墙,给主角或者墙任意一方绑定脚本都可以收到事件。如果是在主角这里监听碰到什么东西,代码可以这样写
1 2 3 4 5 6 7 8 9 10 11 12 private void OnCollisionEnter2D (Collision2D collision ) { Debug.LogFormat("开始碰到:{0}" , collision.collider.name); } private void OnCollisionStay2D (Collision2D collision ) { Debug.LogFormat("持续碰到:{0}" , collision.collider.name); } private void OnCollisionExit2D (Collision2D collision ) { Debug.LogFormat("结束碰到:{0}" , collision.collider.name); }
游戏中需要监听碰撞的事件可能比较多,并非一定要将其写在监听它的脚本中,可以将它抛出去,这样就可以在与它有关的地方统一处理。
首先新建CollisionPublisher
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using UnityEngine;using UnityEngine.Events;public class CollisionEvent : UnityEvent <GameObject , GameObject > { }public class CollisionPublisher : MonoBehaviour { public static CollisionEvent onCollisionEnter2D = new CollisionEvent(); public static CollisionEvent onCollisionStay2D = new CollisionEvent(); public static CollisionEvent onCollisionExit2D = new CollisionEvent(); private void OnCollisionEnter2D (Collision2D collision ) { onCollisionEnter2D.Invoke(gameObject, collision.collider.gameObject); } private void OnCollisionStay2D (Collision2D collision ) { onCollisionStay2D.Invoke(gameObject, collision.collider.gameObject); } private void OnCollisionExit2D (Collision2D collision ) { onCollisionExit2D.Invoke(gameObject, collision.collider.gameObject); } }
将这个脚本挂载在某个具有Collider的对象上(发布者)
新建两个Square,全部挂载Box Collider和Rigidbody,其中一个是dynamic,一个是static,将CollisionListener
脚本和下面的RigidbodyMove
脚本全都挂载在Dynamic的Suqare上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class RigidbodyMove : MonoBehaviour { private Rigidbody2D rb; public float force = 10f ; void Start () { rb = gameObject.GetComponent<Rigidbody2D>(); CollisionListener.onCollisionEnter2D.AddListener((g1, g2) => { Debug.LogFormat("{0}开始碰撞{1}" , g1.name, g2.name); }); CollisionListener.onCollisionStay2D.AddListener((g1, g2) => { Debug.LogFormat("{0}正在接触{1}" , g1.name, g2.name); }); CollisionListener.onCollisionExit2D.AddListener((g1, g2) => { Debug.LogFormat("{0}结束碰撞{1}" , g1.name, g2.name); }); } private void FixedUpdate () { float horizontalmove = Input.GetAxis("Horizontal" ); rb.velocity = new Vector2(horizontalmove * force*Time.deltaTime, rb.velocity.y); float verticlemove = Input.GetAxis("Vertical" ); rb.velocity = new Vector2(rb.velocity.x,verticlemove*force*Time.deltaTime); } }
将匿名方法加进去之后,碰撞就会执行匿名方法
碰撞方向 碰撞通常会有4个方向,跳起来脑袋碰到房顶,掉下去脚碰到地面,还有就是左右两边的碰撞了。
Unity2D目前并没有提供方法来判断方向,但是提供了碰撞发生的坐标点,这样就可以计算碰撞方向了。
将上面的Square挂载的Box Collider改为Circle Collider,并在RigidbodyMove
脚本中添加下面几行代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void OnCollisionStay2D (Collision2D collision ) { foreach (var contact in collision.contacts) { Debug.DrawLine(contact.point, transform.position, new Color(0f , 211f , 187f ),5f ); var direction = transform.InverseTransformPoint(contact.point); Debug.Log(direction); if (direction.x > 0f ) { print("右碰撞" ); } if (direction.x < 0f ) { print("左碰撞" ); } if (direction.y > 0f ) { print("上碰撞" ); } if (direction.y < 0f ) { print("下碰撞" ); } } }
在playing模式下,打开gizmos就可以在game窗口中看到Debug.DrawLine
画出的线
触发器Publisher 将Collider的is trigger勾选后,就变成了没有物理阻挡的触发器(Trigger)
新建TriggerPublisher
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using UnityEngine;using UnityEngine.Events;public class TriggerEvent : UnityEvent <GameObject > { }public class TriggerPublisher : MonoBehaviour { public static TriggerEvent onCollisionEnter2D = new TriggerEvent(); public static TriggerEvent onCollisionStay2D = new TriggerEvent(); public static TriggerEvent onCollisionExit2D = new TriggerEvent(); private void OnTriggerEnter2D (Collider2D collision ) { onCollisionEnter2D.Invoke(gameObject); } private void OnTriggerStay2D (Collider2D collision ) { onCollisionStay2D.Invoke(gameObject); } private void OnTriggerExit2D (Collider2D collision ) { onCollisionExit2D.Invoke(gameObject); } }
Effectors 2D 具有特殊物理效果的Collider 2D对象,Effector需要在Collider的基础上勾选used by effector才可以使用
Platform Effector 2D:一种特殊的地面,称为单向平台,可以从下面跳上去。
Surface Effector 2D:像传送带一样带摩擦地缓慢移动(如果在主角移动代码中设定了Velocity,此效果器会失效)
Point Effector 2D:圆形引力场效果器,需要对应的Collider设置为Trigger,可以制作吸引或爆炸效果
Buoyancy Effector 2D:浮力效果器,模拟水中的浮力效果。
Area Effector 2D:区域力,例如物体从空中掉下来,进入某个区域互相弹跳的效果。(类似Point Effector,但是在碰撞区域内部起效)
优化 如果碰撞效果必须通过物理引擎,那么必须在RIgidbody 2D中选中Dynamic复选框了,这样功能虽然是最全面的,但是效率也是最低的。
另一种做法是不依赖物理引擎。没有碰撞效果(例如被墙挡住行走),仅选中运动学Kinematic,那么只能监听到OnCollisionEnter2D()
和OnTriggerEnter2D()
一类的碰撞事件,无法自动处理类似被墙挡住的效果。
最后就是完全自己实现一套碰撞物理。更加灵活富有效率
计算区域 获取SpriteRenderer中4个点的世界绝对坐标,这样就可以判断相交、重合和计算距离等,这种方法类似于UGUI的RectTransform.GetWorldCorners
新建DrawSpriteRegin
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using UnityEngine;public class DrawSpriteRegin : MonoBehaviour { public SpriteRenderer spriteRenderer; private void Update () { Vector3 min = spriteRenderer.bounds.min; Vector3 max = spriteRenderer.bounds.max; Debug.DrawLine(min, new Vector3(max.x, min.y, 0f ), Color.red); Debug.DrawLine(new Vector3(max.x, min.y, 0f ), max, Color.red); Debug.DrawLine(max, new Vector3(min.x, max.y, 0f ), Color.red); Debug.DrawLine(new Vector3(min.x, max.y, 0f ), min, Color.red); } }
挂载后,将spriteRenderer拉取进来,运行就可以看到边框
一个sprite的原点默认在中心点,也就是transform.position相对整个图片的坐标。这个原点可以在Sprite Editor中编辑,很多游戏会将这个点放在脚底板的位置上。防止角色高低不同。