【译】FarseerUnity教程-第四部分
前言:Farseer是Box2D的C#实现,而FarseerUnity是Farseer的Unity版本插件,适用于4.X版本,在资源商店可以免费下载。翻译出处: http://www.catsinthesky.com/blog/article/2012/04/9/farseer-physics-box2d-and-unity-原文作者:Gabriel Ochsenhofer
这个部分的教程涵盖了碰撞事件,又叫做触发器。不像碰撞过滤,触发器总是出现在游戏中。它们是游戏逻辑中必不可少的,从存盘点到致命的子弹。因为触发器能够以多种方式被用在所有东西上,我会讲到更多它后面的程序逻辑(而不是像之前的部分一样依赖于已经做好的组件)。下载unity包,创建一个新工程然后导入所有东西。基本场景是在Tutorials/Part 4/CollisionEventsBase中(下图)。
现在FarseerUnity也在身体和装置实例中存储标签信息。你能在你的触发逻辑中使用它。我们会把PLAYER的标签改成“玩家”(下图),把playerBOX的标签改成“重生”(下下图)。
现在在Tutorials/Part4/CollisionEventsBase文件夹中创建一个名为“TestCollisionEventsCp”的C#脚本。现在把如下代码写在里面:
using UnityEngine;
using System.Collections.Generic;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics;
using FVector2 =Microsoft.Xna.Framework.FVector2;
public class TestCollisionEventsCp : MonoBehaviour
{
}
把这个脚本附给PLAYER物体(下图)。
现在把代码改成下面这个基本结构:
using UnityEngine;
using System.Collections.Generic;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics;
using FVector2 = Microsoft.Xna.Framework.FVector2;
public class TestCollisionEventsCp : MonoBehaviour
{
private Body body;
private List lastContacts;
void Start()
{
body =GetComponent().PhysicsBody;
lastContacts = new List();
}
void Update()
{
}
private bool OnCollisionEvent(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
return true;
}
}
当一个碰撞发生和结束时Farseer物理引擎使用默认的C#事件来调度(body.OnCollision和body.OnSeparation)。这就像Physx中的OnCollisionEnter和OnCollisionExit,但它是在每个装置中的(一个身体可以有多个装置/形状)。为了控制中间的碰撞步骤,你可以存储接触实例,就当为lastContacts变量准备。
在这一行之后:
lastContacts = new List();
增加这一行:
body.OnCollision += OnCollisionEvent;
现在当一个碰撞发生时,OnCollisionEvent方法会被触发。因为这个方法告知Farseer我们是否想要这次碰撞发生,所以它返回一个布尔值。如果你返回false,物体完全不会碰撞。把“return true;”改成“return false;”然后点击编辑器里的运行看看会发生什么。现在测试一个场景:你不想要两个特殊物体之间发生碰撞(在这个例子中是PLAYER和playerBOX),把CubeB从玩家物体旁移走,然后把OnCollisionEvent方法改成这样:
private bool OnCollisionEvent(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
Body bodyB = fixtureB.Body;
if(bodyB.UserTag== "Respawn")
return false;
return true;
}
因为我们之前已经设置了标签,我们可以使用它们来过滤一个碰撞,就像一个简单的碰撞过滤会做的那样。如果你现在点击运行,PLAYER和playerBOX不会发生碰撞因为这个事件手动的把它们过滤掉了。现在想象你想让这个碰撞马上就起作用。联系人类掌握着你要做的所有东西。这个全局引用拥有全局碰撞点和碰撞法向量。把OnCollisionEvent改成这样:
private bool OnCollisionEvent(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
Body bodyB = fixtureB.Body;
if(bodyB.UserTag== "Respawn")
{
FVector2 normal;
FarseerPhysics.Common.FixedArray2contactPoints;
contact.GetWorldManifold(out normal, out contactPoints);
bodyB.ApplyLinearImpulse(normal *-55f);
body.ApplyLinearImpulse(normal * 55f);
}
return true;
}
在上面的代码中,这个法向量变量持有全局碰撞法向量,所以当我们使用它增加一个力以后,物体就会往反方向飞出去。全局联系点还能干很多事,比如在一个点播放3D立体声,设想playerBox是个能回收的物体,你也可以这么做:
private bool OnCollisionEvent(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
Body bodyB = fixtureB.Body;
if(bodyB.UserTag== "Respawn")
{
// remove BodyB controller
Destroy(bodyB.UserFSBodyComponent.gameObject);
// add points, stats, anythingelse
// here
// don't return a collision
return false;
}
return true;
}
和检查玩家是否能跳:没代码。现在让我们做一个更复杂的例子来试试联系人类能提供什么,就像获得一个物体的全部重量(就像缩放)。因为联系人也存储了法向量和切向冲量,这很容易做。首先,创建一个3D文字物体来显示重量(下图)。
现在把这个物体做成PLAYER的子物体(下图)。
然后再TestCollisionEventsCp类中增加一个全局变量(复制下面代码):
public TextMesh LinkedTextMesh;
现在把3D文字物体实例拖到LinkedTextMesh参数上(下图):
现在把TextCollisionEventsCp的代码改成这样:
using UnityEngine;
using System.Collections.Generic;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics;
using FVector2 = Microsoft.Xna.Framework.FVector2;
public class TestCollisionEventsCp : MonoBehaviour
{
public TextMesh LinkedTextMesh;
private Body body;
private List lastContacts;
void Start()
{
body = GetComponent().PhysicsBody;
lastContacts = new List();
body.OnCollision += OnCollisionEvent;
}
void Update()
{
float weight =body.Mass;
GetWeight(ref weight);
LinkedTextMesh.text = weight.ToString("#0.00") + "Kg";
}
private void GetWeight(ref float weight)
{
if(lastContacts.Count< 1)
return;
float ownWeight =weight;
weight = 0f;
foreach(ContactlastContact in lastContacts)
{
bool isTouching =lastContact.IsTouching();
if(isTouching)
{
FarseerPhysics.Common.FixedArray2
lastContact.Manifold.Points;
// gravity =9.8f (hard coded here just for testing purposes)
//Time.fixedDeltaTime is the FPE timeStep
weight +=
(1f * (localManifoldPoints[0].NormalImpulse/Time.fixedDeltaTime)/ 9.8f);
weight +=
(1f * (localManifoldPoints[1].NormalImpulse/Time.fixedDeltaTime)/ 9.8f);
}
}
// remove inactive contacts
for(int i = 0; i< lastContacts.Count; i++)
{
if(!lastContacts[i].IsTouching())
{
lastContacts.RemoveAt(i);
i = Mathf.Max(0, i - 1);
}
}
// calc weight
weight -= ownWeight;
weight *= 0.5f;
weight += ownWeight;
}
private bool OnCollisionEvent(Fixture fixtureA, Fixture fixtureB, Contact contact)
{
if(!lastContacts.Contains(contact))
lastContacts.Add(contact);
return true;
}
}
就像你看到的,本地副本点包含冲量数据。它能用来计算总重量,冲力,破坏力等等。要查看全局副本点和法向量,增加它:
void OnDrawGizmos()
{
if(lastContacts == null)
return;
foreach(ContactlastContact in lastContacts)
{
if(!lastContact.IsTouching())
return;
FarseerPhysics.Common.FixedArray2contactPoints;
FVector2 normal;
lastContact.GetWorldManifold(out normal, out contactPoints);
Vector3 p0 =FSHelper.FVector2ToVector3(contactPoints[0]);
Vector3 p1 =FSHelper.FVector2ToVector3(contactPoints[1]);
Vector3 pn =FSHelper.FVector2ToVector3(normal);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(p0, 0.15f);
Gizmos.DrawLine(p0, p0+ pn * 2f);
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(p1, 0.15f);
Gizmos.DrawLine(p1, p1+ pn * 2f);
}
}
如果一切正常,当你把物体放到顶上时重量会改变(下图):
这就是第四部分了!