XNA Game Engine教程系列#5 – Physics
在这一章的教程中,我们将在游戏引擎中实现物理模拟!我们不会自己编写物理引擎,这对于这个教程来说太复杂了。我们将编写一些类使用XNA平台上的开源物理引擎JigLibX,这意味着我们将能够与Xbox保持兼容。
我已经稍微修改了原来的JigLibX项目使它能更好地配合我们的引擎,并修复了一点小错误。我们将在游戏引擎项目中引用这个项目。下载DLL文件,打开“引用”,右键点击并选择“添加引用”,找到下载的DLL,并选择确定。我们现在有了JigLibX的引用。
现在我们必须将JigLibX整合到引擎中。具体实现如下:
“PhysicsObject”组件:这是用来管理JigLibX的“PhysicsSystem”类的。它将更新物理模拟并处理物理系统的实例。我们需要添加一个“physics”实例到引擎的service container中。这样做的好处是,如果我们不模拟任何物理对象和额外phsyics计算,那么,我们根本无需添加一个“physics”实例到service container中。
“PhysicsObject”组件:这是物理模拟对象的基类。它保存JigLibX的物理模拟物体,其他对象类型将从它继承。这些类型是“Box”,“Capsule”,“Sphere”, “Plane”和“Triangle Mesh”。Triangle Mesh模拟模型中的每一个三角形,这对关卡之类的模型是非常有用的,关卡往往设计复杂,不能用几个“Box”“Sphere”就能模拟。我们还可以很容易地创造新的物理物体类型模拟更复杂的模型而无需自己处理每一个三角形。
“PhysicsActor”组件:这个组件继承自“Actor”,关键的区别在于,它使用PhysicsObject,但用于绘图而不是用户定义的值(如普通的“Actor”类)。为此,它会用PhysicsObject的位置,旋转,缩放和BoundingBox重写I3Dcomponent的属性。
让我们开始吧!添加一个叫做“Physics”的新类。设置如下:
using JigLibX.Collision; using JigLibX.Physics; namespace Innovation { public class Physics : Component { // The physics simulation public PhysicsSystem PhysicsSystem = new PhysicsSystem(); // Whether or not we should update public bool UpdatePhysics = true; public Physics() { // Set up physics system this.PhysicsSystem.EnableFreezing = true; this.PhysicsSystem.SolverType = PhysicsSystem.Solver.Normal; this.PhysicsSystem.CollisionSystem = new CollisionSystemSAP(); } public override void Update() { // Update the physics system if (UpdatePhysics) PhysicsSystem.CurrentPhysicsSystem.Integrate( (float)Engine.GameTime.ElapsedGameTime.TotalSeconds); } } }
这个类将负责处理JigLibX的PhysicsSystem并更新它。如果在简单游戏中我们不想模拟物理效果,例如,我们就不会创建这个类并将计算减小到最低限度。我们将创建的下一个类叫PhysicsObject。模拟对象将从这个类继承。这些子类模拟盒,胶囊,平面,球,三角网格(模拟模型的每个顶点)。以下是这个类的代码:
using JigLibX.Collision; using JigLibX.Collision; using JigLibX.Geometry; using JigLibX.Physics; using Microsoft.Xna.Framework; namespace Innovation { // Provides a base object type for physics simulation public abstract class PhysicsObject : Component, I3DComponent { // Local copy of the mass of the object float mass = 1; // The Body managed by the PhysicsObject public Body Body; // The CollisionSkin managed by the PhysicsObject public CollisionSkin CollisionSkin; // The mass of the PhysicsObject public float Mass { get { return mass; } set { // Set the new value mass = value; // Fix transforms Vector3 com = SetMass(value); if (CollisionSkin != null) CollisionSkin.ApplyLocalTransform( new JigLibX.Math.Transform(-com, Matrix.Identity)); } } // The PhysicsObject's position public Vector3 Position { get { return Body.Position; } set { Body.MoveTo(value, Body.Orientation); } } // The PhysicsObject's rotation as a Matrix public Matrix Rotation { get { return Body.Orientation; } set { Body.MoveTo(Body.Position, value); } } // The PhysicsObject's rotation as a Euler Vector public Vector3 EulerRotation { get { return MathUtil.MatrixToVector3(Rotation); } set { Rotation = MathUtil.Vector3ToMatrix(value); } } // Whether or not the physics object is locked in place public bool Immovable { get { return Body.Immovable; } set { Body.Immovable = value; } } // Returns the PhysicsObject's BoundingBox public BoundingBox BoundingBox { get { if (Body.CollisionSkin != null) return Body.CollisionSkin.WorldBoundingBox; else return new BoundingBox(Position - Vector3.One, Position + Vector3.One); } } // Dummy scale value to satisfy I3DComponent public Vector3 Scale { get { return Vector3.One; } set { } } // The body's velocity public Vector3 Velocity { get { return Body.Velocity; } set { Body.Velocity = value; } } // Constructors public PhysicsObject() : base(){ } public PhysicsObject(GameScreen Parent) : base(Parent) { } // Sets up the body and collision skin protected void InitializeBody() { Body = new Body(); CollisionSkin = new CollisionSkin(Body); Body.CollisionSkin = this.CollisionSkin; Body.EnableBody(); } // Sets the mass of the PhysicsObject public Vector3 SetMass(float mass) { PrimitiveProperties primitiveProperties = new PrimitiveProperties( PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Density, mass); float junk; Vector3 com; Matrix it, itCoM; CollisionSkin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM); Body.BodyInertia = itCoM; Body.Mass = junk; return com; } // Rotates and moves the model relative to the physics object to // better align the model with the object public void OffsetModel(Vector3 PositionOffset, Matrix RotationOffset) { CollisionSkin.ApplyLocalTransform( new JigLibX.Math.Transform(PositionOffset, RotationOffset)); } // Disables physics body and component public override void DisableComponent() { Body.DisableBody(); base.DisableComponent(); } } }
我们要制作的第一个类是box。我们获取方体的长和宽并构成形状。如你所见,大部分工作是由PhysicsObject处理的,大部分代码是构造函数的不同重载以获得更大的灵活性。
using JigLibX.Collision; using JigLibX.Geometry; using JigLibX.Physics; using Microsoft.Xna.Framework; namespace Innovation { // A box shaped physics object public class BoxObject : PhysicsObject { Vector3 sideLengths; // The length of the sides of the box public Vector3 SideLengths { get { return sideLengths; } set { // Set the new value sideLengths = value; // Update the collision skin CollisionSkin.RemoveAllPrimitives(); CollisionSkin.AddPrimitive( new Box(-0.5f * value, Body.Orientation, value), (int)MaterialTable.MaterialID.UserDefined, new MaterialProperties(0.8f, 0.8f, 0.7f)); // Set the mass to itself to fix the local transform // on the CollisionSkin in the set accessor this.Mass = this.Mass; } } // Constructors public BoxObject() : base() { InitializeBody(); SideLengths = Vector3.One; } public BoxObject(Vector3 SideLengths) : base() { SetupSkin(SideLengths, Vector3.Zero, Vector3.Zero); } public BoxObject(Vector3 SideLengths, Vector3 Position, Vector3 Rotation) : base() { SetupSkin(SideLengths, Position, Rotation); } public BoxObject(Vector3 SideLengths, Vector3 Position, Vector3 Rotation, GameScreen Parent) : base(Parent) { SetupSkin(SideLengths, Position, Rotation); } // Sets up the object with the specified parameters void SetupSkin(Vector3 SideLengths, Vector3 Position, Vector3 Rotation) { // Setup the body InitializeBody(); // Set properties this.SideLengths = SideLengths; this.Position = Position; this.EulerRotation = Rotation; } } }
为简洁起见,我没有写出CapsuleObject.cs,PlaneObject.cs,TriangleMeshObject.cs,SphereObject.cs的代码,可见源文件。现在我们有了所有对象类型,我们将创建一个新的Actor处理这些物体,我们将其称之为“PhysicsActor”。与“Actor”类不同的是,这个类接受模型和物理对象而不是位置。大多数的工作仍有actor基类处理,但我们会重写位置,旋转,包围盒和属性以匹配物理对象的属性。为了做到这一点,我们需要将Actor基类中的属性变为“virtual”,它意味着从“Actor”继承的类能够重写这些属性,或取代其功能。在“Actor”类中将position、rotation做如下改动:
// I3DComponent values // I3DComponent values public virtual Vector3 Position { get; set; } public Vector3 EulerRotation { get { return MathUtil.MatrixToVector3(Rotation); } set { Rotation = MathUtil.Vector3ToMatrix(value); } } public virtual Matrix Rotation { get; set; } public virtual Vector3 Scale { get; set; } public virtual BoundingBox BoundingBox { get { return new BoundingBox( Position - (Scale / 2), Position + (Scale / 2) ); } }
现在添加“PhysicsActor”类。如你所见,它所做的就是替换物理对象的位置,旋转等,并做了一些管理工作。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Innovation { // Manages a PhysicsObject and allows the engine to work // with it public class PhysicsActor : Actor { // The object we are managing public PhysicsObject PhysicsObject; // Override the position of the base class to that // of the physics object public override Vector3 Position { get { if (PhysicsObject != null) return PhysicsObject.Position; else return Vector3.Zero; } set { if (PhysicsObject != null) PhysicsObject.Position = value; } } // Override the rotation of the base class to that // of the physics object public override Matrix Rotation { get { if (PhysicsObject != null) return PhysicsObject.Rotation; else return Matrix.Identity; } set { if (PhysicsObject != null) PhysicsObject.Rotation = value; } } // Override the BoundingBox of the base class to that // of the physics object public override BoundingBox BoundingBox { get { if (PhysicsObject != null) return PhysicsObject.BoundingBox; else return new BoundingBox(-Vector3.One, Vector3.One); } } // Constructors public PhysicsActor(Model Model, PhysicsObject PhysicsObject) : base(Model, PhysicsObject.Position) { this.PhysicsObject = PhysicsObject; } public PhysicsActor(Model Model, PhysicsObject PhysicsObject, GameScreen Parent) : base(Model, PhysicsObject.Position, Parent) { this.PhysicsObject = PhysicsObject; } // Override DisableComponent so we can remove the physics // object as well public override void DisableComponent() { this.PhysicsObject.DisableComponent(); base.DisableComponent(); } } }
我们现在实现了基本物理引擎!让我们来测试以一下!。为了展示我们的物理引擎,我们要创建一个Box墙,在几秒后用另一个Box撞击它们。改变load方法,如下面代码所示。除了常规设置,我们也增加了一个物理组件,并创建了一个平面作为地面和一堆盒子。
protected override void LoadContent() { // Setup engine. We do this in the load method // so that we know graphics will be ready for use Engine.SetupEngine(graphics); // Create a new Camera Camera camera = new Camera(); // Setup its position and target camera.Position = new Vector3(3, 3, 5); camera.Target = new Vector3(0, 0, 0); // Add it to the service container Engine.Services.AddService(typeof(Camera), camera); // Setup physics Engine.Services.AddService(typeof(Physics), new Physics()); // Create the plane and make it immovable PhysicsActor plane = new PhysicsActor( Engine.Content.Load<Model>("Content/ig_plane"), new BoxObject(new Vector3(4, .01f, 4))); plane.PhysicsObject.Immovable = true; // Load the model we will use for our boxes Model model = Engine.Content.Load<Model>("Content/ig_box"); // Create the stack of boxes for (int y = 0; y < 3; y++) for (int x = 0; x < 3; x++) { PhysicsActor act = new PhysicsActor(model, new BoxObject( new Vector3(.5f), new Vector3(- .5f + (x * 0.52f), .5f + (y * 0.52f), -1), Vector3.Zero)); act.Scale = new Vector3(.5f); } }
现在创建一个叫做fired的布尔型变量,然后更新的Update方法。该“fired”值将跟踪是否我们射出了方块。两秒钟后我们会射出一个方块击中盒子。
bool fired = false; protected override void Update(GameTime gameTime) { // Update the engine and game Engine.Update(gameTime); if (gameTime.TotalGameTime.TotalSeconds > 2 && !fired) { PhysicsActor act = new PhysicsActor( Engine.Content.Load<Model>("Content/ig_box"), new BoxObject(new Vector3(1), new Vector3(0, .5f, 1), Vector3.Zero)); act.Scale = new Vector3(1); act.PhysicsObject.Mass = 1000; act.PhysicsObject.Velocity = new Vector3(0, 2, -6); fired = true; } base.Update(gameTime); }
运行游戏(F5键),你应该看到一堆箱子被撞倒了!
恭喜,我们已经成功地在游戏引擎中建立了物理系统!将来,我们的组件将能够利用这一物理模拟系统。请务必了解工作原理,你对引擎结构的深入理解是很重要的。
发布时间:2008/12/30 上午7:43:08 阅读次数:7245