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 Resolution • Particles
Demonstrations: Rigid Bodies • Particle 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.
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.
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.
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.
Our draw function is straightforward. We clear the canvas, setup our camera, draw our background first and then all of our GameObjects.
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.
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.
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.
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.
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.
Our _createParticle()
function takes in an X and Y location for where to spawn the new particle. We do these tasks...
- Create the ParticleGameObject with the texture, screen coordinates, life value and initial color value. In this example, we set the life to a random range from 30 - 230 frames before it expires (don't forget, the game engine runs at 60 frames per second so a particle can last from 0.5 seconds to almost 4 seconds). The initial color is red.
- Set up an initial size from 5.5 to 6 units in world coordinate space
- Setup the final color at red = 3.5-4.5, blue = 0.4 - 0.5 and green = 0.3 - 0.4. The red is oversaturated and will give a very bright flamelike effect. The alpha component will end at 0.6 so it will appear to become translucent.
- Setup the velocity of the particle. The X component will be -10 to +10 units. The Y component will be 0 - 10. By default, the engine implements a drag on these velocities of 5% per update. It also increases the downward Y velocity to simulate gravitational acceleration.
- Setup the size delta. In this case, the particles will reduce in size by 2% every update.
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