Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


9. September 2012

 

In this part of the tutorial we are going to setup our PhsyicsScene, the heart of our physics engine.  A physics engine, in really laymans terms, take a bunch of physics objects, composed of shapes and with defined physical properties like mass, force, elasticity, etc…  and simulates movement in a virtualized world.  You then take the outputs from the physics engine and update your game accordingly.

 

Let’s jump right in to the code:

 

PhysicsScene.cs

 

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.HighLevel.Physics2D;
    
    namespace Pong
    {
        
        public class PongPhysics : PhysicsScene
        {
            // PixelsToMeters
            public const float PtoM = 50.0f;
            private const float BALLRADIUS = 35.0f/2f; 
            private const float PADDLEWIDTH = 125.0f;
            private const float PADDLEHEIGHT = 38.0f;
            private float _screenWidth;
            private float _screenHeight;
            
            
            public enum BODIES { Ball = 0, Player, Ai, LeftBumper, RightBumper };
    
            
            public PongPhysics ()
            {
                _screenWidth = Director.Instance.GL.Context.GetViewport().Width;
                _screenHeight = Director.Instance.GL.Context.GetViewport().Height;
                
                // turn gravity off
                this.InitScene();
                this.Gravity = new Vector2(0.0f,0.0f);
                
                // Set the screen boundaries + 2m or 100pixel
                this.SceneMin = new Vector2(-100f,-100f) / PtoM;
                this.SceneMax = new Vector2(_screenWidth + 100.0f,_screenHeight + 100.0f) / PtoM;
                
                // And turn the bouncy bouncy on
                this.RestitutionCoeff = 1.0f;
                
                this.NumBody = 5; // Ball, 2 paddles, 2 bumpers
                this.NumShape = 3; // One of each of the above
                
                //create the ball physics object
                this.SceneShapes[0] = new PhysicsShape(PongPhysics.BALLRADIUS/PtoM);
                this.SceneBodies[0] = new PhysicsBody(SceneShapes[0],0.1f);
                this.SceneBodies[0].ShapeIndex = 0;
                this.sceneBodies[0].ColFriction = 0.01f;
                this.SceneBodies[0].Position = new Vector2(_screenWidth/2,_screenHeight/2) / PtoM;
                
                
                //Paddle shape
                Vector2 box = new Vector2(PADDLEWIDTH/2f/PtoM,-PADDLEHEIGHT/2f/PtoM);
                this.SceneShapes[1] = new PhysicsShape(box);
                
                //Player paddle
                this.SceneBodies[1] = new PhysicsBody(SceneShapes[1],1.0f);
                this.SceneBodies[1].Position = new Vector2(_screenWidth/2f,0f+PADDLEHEIGHT/2+ 10f) / PtoM;
                this.SceneBodies[1].Rotation = 0;
                this.SceneBodies[1].ShapeIndex = 1;
                
                //Ai paddle
                this.SceneBodies[2] = new PhysicsBody(SceneShapes[1],1.0f);
                float aiX = ((_screenWidth/2f)/PtoM);
                float aiY = (_screenHeight - PADDLEHEIGHT/2 - 10f)/ PtoM;
                this.SceneBodies[2].Position = new Vector2(aiX,aiY);
                this.SceneBodies[2].Rotation = 0;
                this.SceneBodies[2].ShapeIndex = 1;
                
                //Now a shape for left and right bumpers to keep ball on screen
                this.SceneShapes[2] = new PhysicsShape((new Vector2(1.0f,_screenHeight)) / PtoM);
                
                //Left bumper
                this.SceneBodies[3] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
                this.SceneBodies[3].Position = new Vector2(0,_screenHeight/2f) / PtoM;
                this.sceneBodies[3].ShapeIndex = 2;
                this.sceneBodies[3].Rotation = 0;
                this.SceneBodies[3].SetBodyStatic();
                
                //Right bumper
                this.SceneBodies[4] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
                this.SceneBodies[4].Position = new Vector2(_screenWidth,_screenHeight/2f) / PtoM;
                this.sceneBodies[4].ShapeIndex = 2;
                this.sceneBodies[4].Rotation = 0;
                this.SceneBodies[4].SetBodyStatic();
            }
        }
    }
    

Once again, let’s take it from the top.

 

// PixelsToMeters
public const float PtoM = 50.0f;
private const float BALLRADIUS = 35.0f/2f; 
private const float PADDLEWIDTH = 125.0f;
private const float PADDLEHEIGHT = 38.0f;
private float _screenWidth;
private float _screenHeight;

Most of these are convenience variables for commonly used dimensions.  The most important concept here is PtoM, which is short hand for Pixels To Meters.  By default, the Physics2D engine measures things in meters and kilograms.  So a velocity of (5,0) is 5 meters per second, and a mass of 1 is 1kg.  This value is used to map from pixels to physics units and back, and is going to be used A LOT.  The value 50.0f was chosen pretty much at random by me.

 

public enum BODIES { Ball = 0, Player, Ai, LeftBumper, RightBumper };

These are just shorthand values for accessing our various physics objects by index value.  Obviously if you changed the order of the PhysicsBodies, you need to update this enum.  This is just to make code that access the physics scene externally a bit more readable and is completely optional.

 

The remaining code is entirely in the constructor, let’s take it in chunks:

_screenWidth = Director.Instance.GL.Context.GetViewport().Width;
_screenHeight = Director.Instance.GL.Context.GetViewport().Height;

// turn gravity off
this.InitScene();
this.Gravity = new Vector2(0.0f,0.0f);

We use the screen width and height a lot, so we cache them in a much shorter variable.  Next we init our PhysicsScene, which simply sets a number of values to defaults and should be called before using the Scene ( calling it later will overwrite all of your settings ).  We then set gravity to (0,0) so there will effectively be none.  By default it’s (0,-9.8), which is 9.8 m/s down the Y axis.

 

// Set the screen boundaries + 2m or 100pixel
this.SceneMin = new Vector2(-100f,-100f) / PtoM;
this.SceneMax = new Vector2(_screenWidth + 100.0f,_screenHeight + 100.0f) / PtoM;
                
// And turn the bouncy bouncy on
this.RestitutionCoeff = 1.0f;
                
this.NumBody = 5; // Ball, 2 paddles, 2 bumpers
this.NumShape = 3; // One of each of the above

By default, the PhysicsScene is from –1000,-1000 to 1000,1000.  Notice how we divided the value by PtoM.  This turns it from pixel coordinates to physics coordinates.  Basically we are setting the scene equal to the size of the screen + 100pixels in all directions.  Once a physics object passes out of this area, it stops updating.

 

Next we set RestituionCoeff, (somewhat short) for Coefficient of Restitution.  Let’s think of it as “bounciness”.  A value of 1.0f means something should rebound at the same force it hit, while a value of 0.0 will mean no bounce at all.  A value over 1.0f will cause a whole lot of bounce.  For some exceptionally odd reason, it is set at the scene level in the Phsyics2D library, instead of at the Body level.  Next we touch on another odd decision in the library, the PhysicsScene contains three pre-allocated arrays for Shapes, Bodies and Joins ( 100 items, 250 items and 150 items in size respectively ).  NumBody and NumShape indicate how many items of each you are going to use.  Make perfectly sure that you get the sizes exactly right or you will have problems.  As you can see, we are going to have 5 SceneBodies and 3 SceneShapes in our scene.

 

//create the ball physics object
this.SceneShapes[0] = new PhysicsShape(PongPhysics.BALLRADIUS/PtoM);
this.SceneBodies[0] = new PhysicsBody(SceneShapes[0],0.1f);
this.SceneBodies[0].ShapeIndex = 0;
this.sceneBodies[0].ColFriction = 0.01f;
this.SceneBodies[0].Position = new Vector2(_screenWidth/2,_screenHeight/2) / PtoM;

First we create a new PhysicsShape at position 0 of the SceneShapes array.  This is going to be for our ball, and is created by specifying the radius of the sphere ( again, adjusted from pixels to physics coordinates ).  We then create a PhysicsBody using the PhysicsShape.  The 0.1f represents the mass, in this case 1/10th kg.  ShapeIndex is the index within the SceneShapes array that this SceneBody uses ( and yes, is overwhelmingly redundant, another design decision I don’t understand).  As we will see shortly, multiple Bodies can use the same Shape.  ColFriction is Collision Friction which you can read more about here.  Finally we position the ball within the physics world to match it’s location in the GameEngine2D world ( we will see shortly ), again, converted using PtoM.

 

//Paddle shape
Vector2 box = new Vector2(PADDLEWIDTH/2f/PtoM,-PADDLEHEIGHT/2f/PtoM);
this.SceneShapes[1] = new PhysicsShape(box);

//Player paddle
this.SceneBodies[1] = new PhysicsBody(SceneShapes[1],1.0f);
this.SceneBodies[1].Position = new Vector2(_screenWidth/2f,0f+PADDLEHEIGHT/2+ 10f) / PtoM;
this.SceneBodies[1].Rotation = 0;
this.SceneBodies[1].ShapeIndex = 1;

//Ai paddle
this.SceneBodies[2] = new PhysicsBody(SceneShapes[1],1.0f);
float aiX = ((_screenWidth/2f)/PtoM);
float aiY = (_screenHeight - PADDLEHEIGHT/2 - 10f)/ PtoM;
this.SceneBodies[2].Position = new Vector2(aiX,aiY);
this.SceneBodies[2].Rotation = 0;
this.SceneBodies[2].ShapeIndex = 1;

 

This code sets up the player and ai paddle physics bodies, this time sharing a single PhysicsShape.  Instead of passing a radius to this one, we pass a pair of vectors representing width an height, relative to the center point ( which coincidentally is explained nowhere and learned from trial and error ).  The only other difference is for the AI paddle, I saved the X and Y values to temporary variables, aiX and aiY.  Why?  Because there is an annoying bug in Expression Evaluator, that simply could not handle _screenWidth/2f.  It would treat both numbers an ints, and was showing ( in watch windows and Expression Evaluator ) the wrong values.  In fact, Expression Evaluation thinks 3.0f/2.0f = 1!   This particular (display) bug cost me much of my sanity!

 

//Now a shape for left and right bumpers to keep ball on screen
this.SceneShapes[2] = new PhysicsShape((new Vector2(1.0f,_screenHeight)) / PtoM);

//Left bumper
this.SceneBodies[3] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
this.SceneBodies[3].Position = new Vector2(0,_screenHeight/2f) / PtoM;
this.sceneBodies[3].ShapeIndex = 2;
this.sceneBodies[3].Rotation = 0;
this.SceneBodies[3].SetBodyStatic();

//Right bumper
this.SceneBodies[4] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
this.SceneBodies[4].Position = new Vector2(_screenWidth,_screenHeight/2f) / PtoM;
this.sceneBodies[4].ShapeIndex = 2;
this.sceneBodies[4].Rotation = 0;
this.SceneBodies[4].SetBodyStatic();

This code simply creates a pair of “bumpers” for the left and right side of the screen.  Something for the ball to ricochet off of.  The only thing of note is SetBodyStatic(), which tells the physics engine the object isn’t going to move, saving some valuable calculation time.  There are 3 modes in Physics2D, Dynamic ( what are ball and paddles are ), which will be fully calculated by the engine, Kinematic, which respond to direct application of motion, but not environmental effects ( like gravity ) as well as Static, which are completely stationary.  We are sticking with Dynamic because we are going to control paddle movement through the application of Force, something Kinematic doesn’t support.

 

We now have a physics scene, in the next part we will create the actual GameEngine2D scene, GameScene.

 

Programming , , ,

blog comments powered by Disqus

Month List

Popular Comments