Step 11: Physics |
In this step we will add the physics simulation.
Let's add the physics service to the game:
… using DigitalRune.Physics; // NEW … namespace MyGame { public class Game1 : Microsoft.Xna.Framework.Game { … private Simulation _simulation; // NEW … protected override void Initialize() { … _animationManager = new AnimationManager(); _services.Register(typeof(IAnimationService), null, _animationManager); _simulation = new Simulation(); // NEW _simulation.ForceEffects.Add(new Gravity()); // NEW _simulation.ForceEffects.Add(new Damping()); // NEW _services.Register(typeof(Simulation), null, _simulation); // NEW Components.Add(new MyGameComponent(this)); base.Initialize(); } protected override void Update(GameTime gameTime) { _inputManager.Update(gameTime.ElapsedGameTime); base.Update(gameTime); _gameObjectManager.Update(gameTime.ElapsedGameTime); _simulation.Update(gameTime.ElapsedGameTime); // NEW _animationManager.Update(gameTime.ElapsedGameTime); _animationManager.ApplyAnimations(); } …
The physics service is represented by the class Simulation. (The physics library was one of our first products. If we redesign the physics library in the future we will probably rename Simulation to IPhysicsService and PhysicsManager to use the same naming convention as the other libraries.)
After we create the simulation, we immediately add two force effects: Gravity and Damping. Unless you make a game in space, you will need these force effects.
So far the ground plane does not have a physical representation. Let's add a rigid body which represents the ground in GroundObject.cs:
… using DigitalRune.Geometry.Shapes; // NEW using DigitalRune.Physics; // NEW … namespace MyGame { public class GroundObject : GameObject { private ModelNode _modelNode; private RigidBody _rigidBody; // NEW protected override void OnLoad() { var game = ServiceLocator.Current.GetInstance<Game>(); var scene = ServiceLocator.Current.GetInstance<IScene>(); var simulation = ServiceLocator.Current.GetInstance<Simulation>(); // NEW _modelNode = game.Content.Load<ModelNode>("Ground/Ground").Clone(); _modelNode.ScaleLocal = new Vector3F(0.5f); scene.Children.Add(_modelNode); _rigidBody = new RigidBody(new PlaneShape(Vector3F.UnitY, 0)) // NEW { // NEW MotionType = MotionType.Static, // NEW }; // NEW simulation.RigidBodies.Add(_rigidBody); } protected override void OnUnload() { _modelNode.Parent.Children.Remove(_modelNode); _modelNode.Dispose(false); _modelNode = null; _rigidBody.Simulation.RigidBodies.Remove(_rigidBody); // NEW _rigidBody = null; // NEW } protected override void OnUpdate(TimeSpan deltaTime) { } } }
Now the ground is represented in the physics simulation as an infinite plane. The MotionType of the ground is Static. This means, that it will never move.
Next, we add a physics object which moves using a new game object: CrateObject.cs
using System; using DigitalRune.Game; using DigitalRune.Geometry; using DigitalRune.Geometry.Shapes; using DigitalRune.Graphics.SceneGraph; using DigitalRune.Mathematics.Algebra; using DigitalRune.Mathematics.Statistics; using DigitalRune.Physics; using Microsoft.Practices.ServiceLocation; using Microsoft.Xna.Framework; namespace MyGame { public class CrateObject : GameObject { private ModelNode _modelNode; private RigidBody _rigidBody; protected override void OnLoad() { var game = ServiceLocator.Current.GetInstance<Game>(); var scene = ServiceLocator.Current.GetInstance<IScene>(); var simulation = ServiceLocator.Current.GetInstance<Simulation>(); _modelNode = game.Content.Load<ModelNode>("MetalGrateBox/MetalGrateBox").Clone(); scene.Children.Add(_modelNode); _rigidBody = new RigidBody(new BoxShape(1, 1, 1)); _rigidBody.Pose = new Pose(new Vector3F(2, 2, -2), RandomHelper.Random.NextQuaternionF()); simulation.RigidBodies.Add(_rigidBody); } protected override void OnUnload() { _modelNode.Parent.Children.Remove(_modelNode); _modelNode.Dispose(false); _modelNode = null; _rigidBody.Simulation.RigidBodies.Remove(_rigidBody); _rigidBody = null; } protected override void OnUpdate(TimeSpan deltaTime) { _modelNode.SetLastPose(true); _modelNode.PoseWorld = _rigidBody.Pose; } } }
The crate object uses the MetalGrateBox.fbx model. It also creates a rigid body which has a box shape. The shape of the box must match size of the graphics model.
The ModelNode is handled by the graphics service. The RigidBody is handled by the physics service. In our case, the model should always have the same pose (position and orientation) as the physics body. To do this, we copy the pose of the rigid body to the pose of the model in OnUpdate.
But before we override the pose of the model, we call SetLastPose(recursive = true). This method stores the current SceneNode.PoseWorld in SceneNode.LastPoseWorld. This is done recursively for the ModelNode and all child nodes. LastPoseWorld may be required later by certain effects, such as motion blur.
In MyGameComponent we create an instance of CrateObject:
… namespace MyGame { public class MyGameComponent : Microsoft.Xna.Framework.GameComponent { … public MyGameComponent(Game game) : base(game) { … gameObjectService.Objects.Add(new GroundObject()); gameObjectService.Objects.Add(new LightsObject()); gameObjectService.Objects.Add(new DudeObject()); gameObjectService.Objects.Add(new CrateObject()); // NEW _myGraphicsScreen.DebugRenderer.DrawText("MyGame"); _myGraphicsScreen.DebugRenderer.DrawAxes(Pose.Identity, 1, false); } …
Current state of the game:
The CrateObject demonstrates an important aspect of our game engine design: