【译】FarseerUnity教程-第四部分

发表于2016-03-19
评论0 2.7k浏览

前言: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.OnCollisionbody.OnSeparation)。这就像Physx中的OnCollisionEnterOnCollisionExit,但它是在每个装置中的(一个身体可以有多个装置/形状)。为了控制中间的碰撞步骤,你可以存储接触实例,就当为lastContacts变量准备。

在这一行之后:

lastContacts = new List();

增加这一行:

body.OnCollision += OnCollisionEvent;

现在当一个碰撞发生时,OnCollisionEvent方法会被触发。因为这个方法告知Farseer我们是否想要这次碰撞发生,所以它返回一个布尔值。如果你返回false,物体完全不会碰撞。把“return true;”改成“return false;”然后点击编辑器里的运行看看会发生什么。现在测试一个场景:你不想要两个特殊物体之间发生碰撞(在这个例子中是PLAYERplayerBOX),把CubeB从玩家物体旁移走,然后把OnCollisionEvent方法改成这样:

      private bool OnCollisionEvent(Fixture fixtureA, Fixture fixtureB, Contact contact)

      {

            Body bodyB = fixtureB.Body;

            if(bodyB.UserTag== "Respawn")

                  return false;

            return true;

      }

因为我们之前已经设置了标签,我们可以使用它们来过滤一个碰撞,就像一个简单的碰撞过滤会做的那样。如果你现在点击运行,PLAYERplayerBOX不会发生碰撞因为这个事件手动的把它们过滤掉了。现在想象你想让这个碰撞马上就起作用。联系人类掌握着你要做的所有东西。这个全局引用拥有全局碰撞点和碰撞法向量。把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.FixedArray2Collision.ManifoldPoint>localManifoldPoints = 

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);

            }

      }

如果一切正常,当你把物体放到顶上时重量会改变(下图):

这就是第四部分了!

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引