1 Requirements

Tutorial 3 - Loading Levels

2 Introduction

Ok, now we can create static worlds. But as this is kinda boring, we need some motion and some physics. We will start with an introduction on how to use the physics system of the engine. As this is a quite complex and propbably one of the most difficult parts of the engine, this tutorial is quite long and difficult to follow.
In this tutorial, we will create four completely different boxes (you’ll need different templates for them) and a terrain. One box will be an unmovable object, one a movable object. One will be simply a ghost you can walk through and the last one the character box that can move and collide with the other boxes. The files that come along with the tutorial are a working sample of this setting. As we can’t move objects yet, the CharacterBox will fall from the sky onto another one lying on the ground. Modify the level.xml to get the different boxes on the ground. The next tutorial will cover the relevant parts for creating a keyboard controlled entity.

3 Extending Template

First of all, we have to modify our template. Currently, we add a StaticStateComponent to the objects. Such a component only defines an object in the world, that will never interact with other objects. There’s no collision, no movement etc. We will now change this to a PhysicalStateComponent that allows you to modify the physical properties of the object. One important thing to keep in mind: Whilst a StaticStateComponent only describes a position in the world, a PhysicalStateComponent that is marked as STATIC defines a physical entity that is just unmovable (like a wall).

 
1<Component template="PhysicalState"> 
2    <Attribute name="pos">0.0 0.0 0.0</Attribute> 
3    <Attribute name="rot">1.0 0.0 0.0 0.0</Attribute> 
4    <Attribute name="scale">1.0 1.0 1.0</Attribute> 
5    <Attribute name="shapeType">1</Attribute> 
6    <Attribute name="box">1 1 1</Attribute> 
7    <Attribute name="mass">10.0</Attribute> 
8    <Attribute name="friction">1.0</Attribute> 
9    <Attribute name="collisionGroup">5 1 9</Attribute> 
10    <Attribute name="shatterInterest">4</Attribute> 
11    <Attribute name="scale">0.0 0.0 0.0</Attribute> 
12    <Attribute name="syncPrio">2</Attribute> 
13    <Attribute name="compound">0</Attribute> 
14</Component>
Listing 1: PhysicalStateComponent

Listing 1 shows the code necessary for addings physics. We will go through it step by step.
We define a position, rotation and scale (9.1). Then comes the definition of a collision shape:

4 Collisionshape

You have to differentiate two shapes of an object. The mesh as defined in .mesh files. This defines the real shape of an object (or at least a highly accurate approximation). This is used in the graphic to render the object as realistic as possible. And second, the collision shape. This defines the shape used in physical calculations. Whilst a graphic card is optimized to handle complex objects, there is no physic card. Thus, the collision shape should be simple. Again, it is impossible to define simple. You have to try it. But most of the time, even one basic shape like box, sphere or cylinder is enough. Therefore, we currently only support these basic shapes and complex ones generated out of .mesh files being stored in .bullet files. You create the collision shape using the shapeType parameter. Here you define the type of the collision shape (PLANE, BOX, FILE, SPHERE). See 8 for explanation of the values to be set for different shape attributes. In our template we create a box in line 6. Our cube will weight 10 kg and has the size 1 meter x 1 meter x 1 meter.
You should now be able to run the game and see objects falling down that are not already on top of the terrain. If something is not working as expected, a good idea is to turn on Debug Drawing. This will add white lines to indicate the border of the collision shapes. This is for example helpful to see whether the shape matches the mesh. But if your level is large, you will see a huge performance drop as this is a non optimized mode and causes high traffic in the engine. To turn the DebugDrawer on, call at anytime:

1i6e::api::EngineController::GetSingleton().setDebugdrawer(1);
Listing 2: enabling debug drawer

5 Collision Flags

Now we have defined our collision shape. But we need some more properties for the physic to work: The collision flags. They define how objects will interact on collision. To create a powerful and flexible yet easy to use system, the collision properties are two basic values: A value defining the general overall physical properties of the object and the flags used to drop many collision events and only get the ones needed (You don’t want to get informed whenever a tree collides with the terrain it is placed on). Line 9 sets the collision flags. The first parameter is either NONE or a combination of STATIC, GHOST and TRIGGER. They are explained at the end of this chapter. Here we define a static object that shall be notified on collisions.
There are two more values in this line. They are basically integers, but think of them as bit sets: Each of the 32 bits can be either set (1) or not set (0). The first of these parameters (second in total) is the type of the object. Pass an integer with exactly one bit set. The bit which is set describes the group the object belongs to. For the second type parameter (third in total), pass the types of objects for which this object shall get notifies. Let’s look at our example: We have about 4 different types of objects: A character box, the terrain (nobody cares about collision with terrain in most cases), the ghost box and the other two boxes we can crash into. We now assign each group an integer with only one bit set: character 1, terrain 2, ghost 4, others 8. These are the values we pass as the second parameter. For the third parameter, we use the combined (with logical or (|), same as sum (+) in this case) values of all groups we want to get notified on collision. The terrain gets 0 as it doesn’t care about any collision. The character gets 9 (= 8 + 1): We want to get informed when colliding with a solid box (8) as well as other character boxes (1). The solid boxes get 0 as well and the ghost box gets 1, because it wants to know whenever a character box moves through it. Note that our character box won’t notice the collision with the ghost. As these are many magic numbers within your code, consider using enums or similar constructs to make it more readable.

5.1 STATIC

This object is unmovable from physical actions. Thus if an object collides with a static object, only the moving object will be pushed back whilst the static object remains unchanged. This is a good flag for walls, trees etc. Note: You can still move this object explicitly with a call to setPosition() but not through applying forces.

5.2 GHOST

Objects defined as ghost never interact with other objects. Thus, they are similar to objects with a StaticStateComponent. But differently, they notice collision if another object passes through. They can be used to create special effects if an objects enters an area. You’ll probably want to set the TRIGGER flag as well.

5.3 TRIGGER

Normally, objects will just collide and bounce away, but the game doesn’t now, there was a collision. If you need to react to a collision, set the TRIGGER flag. Additionally, you will need to add a ShatterComponent to the object and specify whether you want to be notified in every frame the objects collide or only in the first or the last frame of the collision. Whenever the object with the TRIGGER flag notices a collision, the shatter() method from the ShatterComponent will be invoked. The one and only parameter in this function is a pointer to other GameObject. We will work on this notifies later on in this tutorial.

6 Shatter Interest

Last thing to do here: Set the shatter interest. Sometimes you want to get informed as soon as a collision occures (a character enters our ghost box) whilst sometimes you want to get informed as long as the collision is present (as long as our character collides with a solid box, he will loose health). That’s what shatter interest is for. In line 10 you see the definition as it is used for our ghost box. Possible values are START, END and ALWAYS as well as START | END for entering and leaving.
Now, that was quite little code but an enormous amount of concepts concerning the physic. Luckily, we covered nearly everything related to collisions. Thus there is nothing more you have to know. But we have to talk about two remaining minor things for our boxes: Reacting to collisions being detected and moving the box (at least in a very stupid way).

7 Collision Handling

As already mentioned, we need a ShatterComponent for each trigger object that will eventually receive collision events. There is a ShatterComponent defined in i6engine/api/components/ShatterComponent.h. There are some more things you have to do for receiving collision events, but this component does all this stuff for you. You only have to derive from this class and implement a method called void shatter(const i6engine::api::GOPtr & other). In listing 3 you can see the simplest shatter component subclass. The GameObject other is the object that collided with the current GameObject. For our example, we can use two different subclasses: ShatterBox for the character box and ShatterGhost for the ghost box. The plane and the solid boxes don’t need such a component as they are not interested in collisions.


 
1#ifndef SH1 
2#define SH1 
3 
4#include <iostream> 
5 
6#include "api/components/ShatterComponent.h" 
7 
8class Shatter1 : public i6e::api::ShatterComponent { 
9public: 
10    Shatter1(int64_t id, const attributeMap & params) : ShatterComponent(id, params) { 
11    } 
12 
13    void shatter(const i6engine::api::GOPtr & other) { 
14        std::cout << "Shatter1" << std::endl; 
15    } 
16}; 
17 
18#endif
Listing 3: ShatterComponent

8 Shapes

If not other stated, all parameters are optional. If not set, a common default values is used.

8.1 All Shapes

These parameters are possible for all shapes:

8.2 PLANE

Defines an infinite large plane.

8.3 BOX

Defines a simple box

8.4 FILE

This is a special shape. It uses a complex shape read from a .bullet file.

8.5 SPHERE

Defines a simple sphere.

9 Important notes

9.1 Scaling

For the physic to work correctly, you have to keep the sizes small. Our unit size is one meter, meaning, if you pass a size value of 4, your object is 4 meters large. There is no fix value you shouldn’t exceed, but for a first value, try not to create objects larger than 1000 units. Same goes for weights. A value of 1 is 1 kilogramm and objects shouldn’t exceed 1000 kg.

10 What’s next?

The next tutorial will add some movement control to our character box so we have real interaction in our game.