GTCS Game Engine:
Tutorial 4: Rigid Bodies & Particle Systems

Tutorial 3 <-- Tutorial 4 --> Tutorial 5
Main tutorial guide page


Introduction

As we have already mentioned, GameObjects allow us to control the behavior of the elements in our game as well as the interactions between them. In this tutorial, we look at using rigid bodies to apply physics to our GameObjects and creating particles.

Covered Topics: Collision ResolutionParticles

Demonstrations: Rigid BodiesParticle System

Complete source code for all tutorials can be downloaded from here.


Collision Resolution

In tutorial 3, we looked at detecting collisions between game elements. With per-pixel collision detection, we checked to determine if one element overlaps another. While this is very useful, for most games we need more than to just detect collisions. We need to look at collision resolution where the engine will detect a collision through overlap and "undo" the overlap before it is drawn to the screen.

To do this, we will apply an "invisible shape" over our GameObject where physics calculations will occur to simulate objects having mass and not being able to pass through each other. The game engine supports the use of rectangles and circles as rigid shapes. While these shapes are not as precise as the per-pixel collision detection we saw previously, for determining locations of objects for collision resolution, these shapes work very well. By setting a virtual mass to these shapes, the engine will calculate what happens when these objects collide.

We create a new rigid bodies object, set its size and apply it to a GameObject using the setPhysicsComponent() function. To simulate the physics of the game, we call the engine's physics processing functions such as gEngine.Physics.processObjObj()for resolving the physics interactions between two bodies.

Using Rigid Bodies

In the next example, we will create two renderables, a minion and a platform. We will place the platform in the center of our scene and allow keys to control movement of the minion. Using rigid bodies, we will make it so the minion cannot pass through the platform.

You can see the running example here. The WASD keys move the minion around the screen. The 'C' key will show/hide the rigid rectangles for the GameObjects so you can view the locations of our "rigid" boundaries.

Figure 4-1: GameObjects Using Rigid Bodies

First, we declare our instance variables and constants. Next, we load in our assets. In this example, we load in graphics for the minion (as part of our minion spritesheet from previous examples), the platform and a background image (to see something more interesting than a grey background). We also write our parallel unload calls.

Though rigid bodies are an abstract representation for behavior, there is an area of effect associated with them called the bounding region. For demonstration purposes, we create a variable, this.mShowBounds, to allow us to control drawing the bounding region.

function MyGameScene() {
	this.mCamera = null;
	this.mBg = null;
	this.mMinionRenderable = null;
	this.mMinionObject = null;
	this.mPlatformRenderable = null;
	this.mPlatformObject = null;
	this.mShowBounds = false;	// we use this to control drawing bounds
	
	this.kBG = "assets/bg.png";
	this.kTexture = "assets/minion_spritesheet.png";
	this.kPlatform = "assets/platform.png";
}
gEngine.Core.inheritPrototype(MyGameScene, Scene);

MyGameScene.prototype.loadScene = function () {
	gEngine.Textures.loadTexture(this.kBG);
	gEngine.Textures.loadTexture(this.kPlatform);
	gEngine.Textures.loadTexture(this.kTexture);
};

MyGameScene.prototype.unloadScene = function () {
	gEngine.Textures.unloadTexture(this.kBG);
	gEngine.Textures.unloadTexture(this.kPlatform);
	gEngine.Textures.unloadTexture(this.kTexture);
};
      		

Code snippet 4-1: Loading Assets

In the initialization function, we create our camera as we have done before. We create a background renderable, a minion renderable from the spritessheet and we create a platform renderable. We embed the minion and platform renderables into GameObjects. We do not need to embed the background renderable as it will not be interacting with any other game element.

MyGameScene.prototype.initialize = function () {
	// Setup the camera and background color
	this.mCamera = new Camera(
		vec2.fromValues(50, 40),	// position of the camera
		100,						// width of camera
		[0, 0, 500, 400]			// viewport (orgX, orgY, width, height)
	);
	this.mCamera.setBackgroundColor([0.8, 0.8, 0.8, 1]);
   
	// create a renderable for our background
	this.mBg = new SpriteAnimateRenderable(this.kBG);
	this.mBg.setElementPixelPositions(0, 1024, 0, 1024);
	this.mBg.getXform().setSize(100, 80);
	this.mBg.getXform().setPosition(50, 40);
    
	// create a renderable for our minion object
	this.mMinionRenderable = new SpriteAnimateRenderable(this.kTexture);
	this.mMinionRenderable.setElementPixelPositions(130, 310, 0, 180);
    
	// create a renderable for our platform object
	this.mPlatformRenderable = new TextureRenderable(this.kPlatform);
    
	// embed the minion renderable in a GameObject
	this.mMinionObject = new GameObject(this.mMinionRenderable);
	this.mMinionObject.getXform().setSize(16, 16);
	this.mMinionObject.getXform().setPosition(30, 50);
    
	// emded the platform renderable in a GameObject
	this.mPlatformObject = new GameObject(this.mPlatformRenderable);
	this.mPlatformObject.getXform().setSize(30, 3.75);
	this.mPlatformObject.getXform().setPosition(50, 20)
    
	// ... Continued in next snippet ... 
    

Code snippet 4-2: Creating Renderables and GameObjects

We continue our initialization by creating our rigid bodies. We create a rigid rectangle with the GameObject's transform and a size for the rigid body. Notice, for the platform, we set a mass of zero. This makes it so that collision with other bodies will not transfer motion (momentum) to this body.

	// create a rigid body physics component for the minion
	var r = new RigidRectangle(this.mMinionObject.getXform(), 11, 15);
	r.setColor([0, 1, 0, 1]);		// set a bounding rectangle color
	r.setDrawBounds(this.mShowBounds);
	this.mMinionObject.setPhysicsComponent(r);

	// create a rigid body physics component for the platform
	r = new RigidRectangle(this.mPlatformObject.getXform(), 30, 3);
	r.setMass(0);				// ensures no movement!
	r.setColor([1, 0.2, 0.2, 1]);		// set a bounding rectangle color
	r.setDrawBounds(this.mShowBounds);
	this.mPlatformObject.setPhysicsComponent(r);
    
	gEngine.DefaultResources.setGlobalAmbientIntensity(3);
};

Code snippet 4-3: Creating Rigid Bodies

Our draw function is straightforward. We clear the canvas, setup our camera, draw our background first and then all of our GameObjects.

MyGameScene.prototype.draw = function () {
	// Clear the screen
	gEngine.Core.clearCanvas([0.8, 0.8, 0.8, 1.0]);
	
	this.mCamera.setupViewProjection();
    
	this.mBg.draw(this.mCamera);
	this.mPlatformObject.draw(this.mCamera);
	this.mMinionObject.draw(this.mCamera);
};

Code snippet 4-4: Draw Function

In the update function, we move our minion around based on the WASD keys. We also have the ability to toggle the viewing of our rigid body bounding regions by pressing the C key. Then we call gEngine.PhysicsprocessObjObj() to check for and resolve collisions between GameObjects.

MyGameScene.prototype.update = function () {
	// minion control WASD	
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.A)) {
		this.mMinionObject.getXform().incXPosBy(-0.5);
	}

	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.D)) {
		this.mMinionObject.getXform().incXPosBy(0.5);
	}
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.W)) {
		this.mMinionObject.getXform().incYPosBy(0.5);
	}

	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.S)) {
		this.mMinionObject.getXform().incYPosBy(-0.5);
	}
	
	// toggle the drawing of the bounding regions
	if (gEngine.Input.isKeyClicked(gEngine.Input.keys.C)) {
		if (this.mShowBounds) {
			this.mMinionObject.getPhysicsComponent().setDrawBounds(false);
			this.mPlatformObject.getPhysicsComponent().setDrawBounds(false);
		}
		else {
			this.mMinionObject.getPhysicsComponent().setDrawBounds(true);
			this.mPlatformObject.getPhysicsComponent().setDrawBounds(true);
		}
		this.mShowBounds = !this.mShowBounds;
	}
	
	// resolve collisions between our two GameObjects
	gEngine.Physics.processObjObj(this.mPlatformObject, this.mMinionObject);
};

Code snippet 4-5: Update Function

A call to gEngine.Physics.processObjObj() checks our GameObject with another object and will resolve collisions between them. If we were dealing with more than two objects that can possible cross-collide (like balls on a billiard table) that need to be resolved simulataneously with respect to eachother, we need to have a means of handling collisions on a bigger scale. For this, we create a set or sets of game objects and use gEngine.Physics.processObjSet() or gEngine.Physics.processSetSet() to resolve collisions with one call. The ObjSet call would handle a single object against a set of objects while SetSet would check the objects in a set against the objects in another set.


Particles

Particles are small objects that emit from a point. Over time, the particle will move further away from the emitter and change in size, color and/or opacity until it eventually dissipates. Emitters can be used to simulate smoke, explosions and rain. In the game engine, particles are implemented by another type of game object, ParticleGameObject. Since we could be handling hundreds of particle objects, we will group them in a ParticleGameObjectSet to ease the task of tracking and manipulating them.

In this next example, we are going to create a particle emitter. The particles will emit from our current mouse location and will fall as if under the influence of gravity. We retain all of our code from before and add particle functionality.

You can view this example here for a demonstration. The WASD keys move the minion around the screen. The 'Z' key will emit particles from the current mouse location. The 'C' key will show/hide the rigid rectangles for the GameObjects.

Figure 4-2: Generating Particles

In our constructor, we identify the texture and load/unload it. We also allocate a new ParticleGameObjectSet object. We can do this here as opposed to the initialize function because it's allocation does not depend on any loaded resources. The set will store all of our particles so that we can treat them as one object rather than dozens or hundreds.

function MyGameScene() {
... Same code as in the previous example ...
	this.kParticleTexture = "assets/particle.png";
    
	this.mAllParticles = new ParticleGameObjectSet();
};
gEngine.Core.inheritPrototype(MyGameScene, Scene);

MyGameScene.prototype.loadScene = function () {
... Same code as in the previous example ...
	gEngine.Textures.loadTexture(this.kParticleTexture);
};

MyGameScene.prototype.unloadScene = function () {
... Same code as in the previous example ...
	gEngine.Textures.unloadTexture(this.kParticleTexture);
};

Code snippet 4-6: Constructor, Load and Unload

Our initialization does not need any special code for handling particles. Unlike other renderables, particle renderables will be created and destroyed on the fly during the running of the game.

We now set our particle set to draw. All objects inside will draw with a single call.

MyGameScene.prototype.draw = function () {
	// Clear the screen
	gEngine.Core.clearCanvas([0.8, 0.8, 0.8, 1.0]);
	
	this.mCamera.setupViewProjection();
    
	this.mBg.draw(this.mCamera);
	this.mPlatformObject.draw(this.mCamera);
	this.mGameObject.draw(this.mCamera);
	this.mAllParticles.draw(this.mCamera);
};

Code snippet 4-7: Drawing Particles

In our update function, we update all particles and check to see if the Z key is pressed. When pressed, we get the mouse location and spawn a particle at that location using the _createParticle() private method. We get the ParticleGameObject and store it in our set.

MyGameScene.prototype.update = function () {
... Same code as in the previous example ...
	// update particle information
	this.mAllParticles.update();
    
	// create particles when Z key is pressed
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.Z)) {
		if (this.mCamera.isMouseInViewport()) {
			var p = this._createParticle(this.mCamera.mouseWCX(),
				this.mCamera.mouseWCY());
			this.mAllParticles.addToSet(p);
		}
	}
    
	// resolve collisions between our two GameObjects
	gEngine.Physics.processObjObj(this.mPlatformObject, this.mMinionObject);
    
	// check for and resolve collisions against our other GameObjects
	gEngine.Particle.processObjSet(this.mMinionObject, this.mAllParticles);
	gEngine.Particle.processObjSet(this.mPlatformObject, this.mAllParticles);
};

Code snippet 4-8: Updating Particles

Our _createParticle() function takes in an X and Y location for where to spawn the new particle. We do these tasks...

MyGameScene.prototype._createParticle = function(atX, atY) {
	var life = 30 + Math.random() * 200;
	var p = new ParticleGameObject(this.kParticleTexture, atX, atY, life);
	p.getRenderable().setColor([1, 0, 0, 1]);
    
	// size of the particle
	var r = 5.5 + Math.random() * 0.5;
	p.getXform().setSize(r, r);
    
	// final color
	var fr = 3.5 + Math.random();
	var fg = 0.4 + 0.1 * Math.random();
	var fb = 0.3 + 0.1 * Math.random();
	p.setFinalColor([fr, fg, fb, 0.6]);
    
	// velocity on the particle
	var fx = 10 - 20 * Math.random();
	var fy = 10 * Math.random();
	p.getPhysicsComponent().setVelocity([fx, fy]);
    
	// size delta
	p.setSizeDelta(0.98);
    
	return p;
};

Code snippet 4-9: Creating Particles


Conclusion

Rigid bodies have a many uses in games. They can be used to simulate floor and walls so that GameObjects can be constrained in their movement. They can also be used to make interactions between GameObjects more realistic. Particles give us impressive visual affects. Aside from explosions, we could simulate smoke or weather affects with particles.

In tutorial 5, we will take a look at more visual affects by illuminating renderables with custom lighting.


Tutorial 3 <-- Tutorial 4 --> Tutorial 5
Main tutorial guide page

2/11/2016 - David Watson, Proofread by Adedayo Odesile & Jeb Pavleas