1 Requirements

Tutorial 4 - Physics

2 Introduction

As by now, you can create objects and give them basic functionality with predefined components. But although we’ll provide more and more of these components, you’ll have to write your own components that match your individual needs. In this tutorial we will create a component to control the character using the W-A-D keys.

3 Basic Component

A minimal component is shown in listing 1. First of all, you have to derive from i6engine::api::Component. This will allow the engine to use your component. You then have to implement the functions Init(), Tick(), synchronize() and getComponentOptions(). For initialising your component you can do whatever you want, but to be compatible with future engine versions as well as the creation using xml files, we recommend the following: Write a (private) constructor taking an id and a string map. Pass both parameters to the base class constructor and use the values provided in the string map to initialise all internal values. Next, set some values for objFamilyID and objComponentID. These values are of no special interest for the engine, you can use whatever you want. If you later want to call some functions in your component, using the getGOC(uint32˙t) method of a game object will return a pointer to the component with given family id. The main idea behind this two values is: Use different ComponentID values for every component, but the same FamilyID for components that have the same interface and cover the same aspect but implement different functionality (e.g. several CameraComponents with different algorithm for following the object). Thus you can easily apply changes on the current camera without knowing the exact type of camera currently attached. If you derive from a predefined component, don’t change the familyID unless you really know what you are doing. Finally, within Init(), you can do the last initializations. In the Init method you can access the GameObject if needed. Furthermore, you can now safely send messages (we will cover Messages during a later tutorial). Make sure that after returning from Init(), the component need to be ready for calls to other functions.
As the constructor is private, we need an additional public function creating the component. In the sample Component, the function createC will do this. It basically does nothing more than passing the arguments to the constructor.
Now, this is enough for the basic component. We will look at the three most important ways to implement functionality in Components.

 
1#ifndef PMC 
2#define PMC 
3 
4#include "i6engine/api/FrontendMessageTypes.h" 
5#include "i6engine/api/KeyCodes.h" 
6#include "i6engine/api/components/Component.h" 
7#include "i6engine/api/configs/InputConfig.h" 
8#include "i6engine/api/facades/InputFacade.h" 
9#include "i6engine/api/facades/MessagingFacade.h" 
10#include "i6engine/api/facades/MessageSubscriberFacade.h" 
11#include "i6engine/api/facades/ObjectFacade.h" 
12 
13class MovementComponent : public i6e::api::Component, public i6e::api::MessageSubscriberFacade { 
14public: 
15    static i6e::utils::sharedPtr<i6e::api::Component, i6e::api::Component> createC(const int id, const i6e::api::attributeMap & params) { 
16        return i6e::utils::make_shared<MovementComponent, i6e::api::Component>(id, params); 
17    } 
18 
19    ~MovementComponent() { 
20        removeTicker(); 
21        ISIXE_UNREGISTERMESSAGETYPE(i6e::api::messages::InputMessageType); 
22    } 
23 
24    void Init() { 
25        ISIXE_REGISTERMESSAGETYPE(i6e::api::messages::InputMessageType, MovementComponent::Action, this); 
26        addTicker(); 
27    } 
28 
29    void Tick() { 
30        i6e::api::MessageSubscriberFacade::processMessages(); 
31    } 
32 
33    void Action(const i6e::api::GameMessage::Ptr msg) { 
34    } 
35 
36    i6e::api::attributeMap synchronize() const { 
37        return i6e::api::attributeMap(); 
38    } 
39 
40    std::vector<i6e::api::componentOptions> getComponentOptions() { 
41        return {}; 
42    } 
43 
44private: 
45    MovementComponent(const int id, const i6e::api::attributeMap & params) : Component(id) { 
46        Component::_objFamilyID = 100; 
47        Component::_objComponentID = 100; 
48    } 
49}; 
50 
51#endif
Listing 1: Basic Component

3.1 Providing an API

This is simple. Just create some functions that are called from the game or other components. There is nothing special to keep in mind here.

3.2 Continous actions

If you want a component that implements some continous functions, there is the Tick() function that you already know from the Application class. It will be called in each frame. But to reduce overhead, only components that are registered will get their Tick method invoked. To register a component, see listing 3.2. But don’t forget to unregister in the destructor or better in the Finalize() method using listing 3.2.

1addTicker();
1removeTicker();

3.3 Receiving methods

The engine is highly based on messages to seperate the different aspects of a game. Whenever an event occures, a message is sent and all parts being subscribed for this type of messages will receive it. The messages we will use to react to keyboard events are Input messages. They are sent by the Input system whenever the user presses or releases a key. To receive such messages, we need a function parsing the messages. In our sample, this is the function Action. The name doesn’t matter, but the other parts of the signature do. To be able to receive messages, call the ISIXE˙REGISTERMESSAGETYPE macro providing the type of messages you want to receive as well as the function that should be called for all messages and the pointer to the object with the function (here this). Don’t forget to unregister in the destructor. Now we can receive messages, but to get the Action method invoked, call i6engine::api::MessageSubscriberFacade::processMessages();. All messages will be stored in a buffer until you do this call. Then for each message, the registered method will be called. Thus a convenient position for this call is the Tick() function (don’t forget to register the Tick() method). This was a bit complicated but it reduces the performance drop with a huge amount of components and ensures synchronisation: You can call every API function in facades as well in objects and components without thinking of race conditions.

4 Movement

Finally we get to the funny part. You should now be able to write your own components and thus extend the functionality of objects as much as you need. We will now use this knowledge to write a component that allows the user to control the corresponding game object. In listing 2, you can see a simple implementation for basic movement (shortend to the two functions Tick and Action, to reduce size). Feel free to tweak it until it matches your needs.
As mentioned above, we will use messages sent by the engine. Thus we need the full initialising with registering for methods and messages. Furthermore, as we can currently only get informed on key presses and releases, we need the three flags isForward, isLeft and isRight that indicate whether the corresponding button is currently pressed.
In the Tick() method, we will set the current movement speed as well as rotating the object if needed. These three blocks demonstrate a basic usage of other components being attached to the same game object: Get a pointer to the object, retrieve the other component using the familyID string and call a function modifing the component (you should always check the pointer received from the cast whether it is a null pointer. That indicates, the object doesn’t have such a component). On moving, we just set a linear velocity of 10ms-. But we have to rotate it by the current rotation of the object. Otherwise the object will move in z direction defined by the world, not the object itself. For rotating, we rotate the object (multiplying two rotation quaternions result in a quaternion defining a rotation by the first quaternion followed by the rotation of the second quaternion). The second parameter in nearly every function of the PhysicalStateComponent indicate the priority of the change. To reduce strange behaviour caused by different calls to these functions from different threads, the change will be stored until the next frame of the object thread. Then the changes requested by the highest priority call will be accepted and propagated to the rest of the engine. A value of 0 is reserved for the physic. Thus, if you want to modify something, use a value higher than 0.
We now come to the last part: modifying the flags. First of all, we have to check the subtype of the message. We registered for all input messages. Thus we will receive mouse events as well, but we don’t need them. Such a subtype is part of every message. Whilst the main type indicates mostly the sender or receiver of a message, the subtype specifies more precisely what the message is about. The third separation is the Method. It can be Create, Update or Delete. They mainly come into account when the message is about entities, e.g. creating new objects, updating the camera, etc. You can access the content of a message using the getContent() method. But this will only give you the base class of all message contents. Using your knowledge of the type, subtype and method, you can then cast the content pointer to the real content class. The name of the class is mostly a concatenation of the three values, in our example Input˙Keyboard˙Update. Consult the documentation about all messages and their content interpretation. Now that you have the casted pointer, you can access all values set by the sender. Here we need the pressed state and the key being pressed. Last thing we have to do is evaluating the key and the action and toggle the corresponding flag. On releasing the forward button, we additionally set the velocity to 0 to stop the character immediately.

 
1    void Tick() { 
2        api::MessageSubscriberFacade::processMessages(); 
3        if (isForward) { 
4            api::GOPtr go = getOwnerGO(); 
5            utils::sharedPtr<api::PhysicalStateComponent, api::Component> psc = go->getGOC<api::PhysicalStateComponent>(api::components::ComponentTypes::PhysicalStateComponent); 
6            Quaternion q = psc->getRotation(); 
7            psc->setLinearVelocity((q * Vec3(0, 0, 10) * q.inverse()).toVector(), 2); 
8        } 
9        if (isLeft) { 
10            api::GOPtr go = getOwnerGO(); 
11            utils::sharedPtr<api::PhysicalStateComponent, api::Component> psc = go->getGOC<api::PhysicalStateComponent>(api::components::ComponentTypes::PhysicalStateComponent); 
12            Quaternion quat(Vec3(0, 1, 0), 3.1415926 / 64); 
13            quat = quat / quat.length(); 
14            psc->applyRotation(quat); 
15        } 
16        if (isRight) { 
17            api::GOPtr go = getOwnerGO(); 
18            utils::sharedPtr<api::PhysicalStateComponent, api::Component> psc = go->getGOC<api::PhysicalStateComponent>(api::components::ComponentTypes::PhysicalStateComponent); 
19            Quaternion quat(Vec3(0, 1, 0), - 3.1415926 / 64); 
20            psc->applyRotation(quat); 
21        } 
22    } 
23 
24    void Action(const api::GameMessage::Ptr msg) { 
25        std::string type = msg->getSubtype(); 
26 
27        if (type != api::keyboard::KeyKeyboard) { 
28            return; 
29        } 
30        api::KeyState pressed = dynamic_cast<api::Input_Keyboard_Update *>(msg->getContent())->pressed; 
31        api::KeyCode button = static_cast<api::KeyCode>(static_cast<api::Input_Keyboard_Update *>(msg->getContent())->code); 
32 
33        if (pressed == api::KeyState::KEY_PRESSED && button == api::KeyCode::KC_W) { 
34            isForward = true; 
35        } else if (pressed == api::KeyState::KEY_RELEASED && button == api::KeyCode::KC_W) { 
36            isForward = false; 
37            api::GOPtr go = getOwnerGO(); 
38            utils::sharedPtr<api::PhysicalStateComponent, api::Component> psc = go->getGOC<api::PhysicalStateComponent>(api::components::ComponentTypes::PhysicalStateComponent); 
39            psc->setLinearVelocity(Vec3(0, 0, 0), 2); 
40        } else if (pressed == api::KeyState::KEY_PRESSED && button == api::KeyCode::KC_A) { 
41            isLeft = true; 
42        } else if (pressed == api::KeyState::KEY_RELEASED && button == api::KeyCode::KC_A) { 
43            isLeft = false; 
44        } else if (pressed == api::KeyState::KEY_PRESSED && button == api::KeyCode::KC_D) { 
45            isRight = true; 
46        } else if (pressed == api::KeyState::KEY_RELEASED && button == api::KeyCode::KC_D) { 
47            isRight = false; 
48        } 
49    }
Listing 2: Complete Component

5 Conclusion

You just learned how to write your own component. Now you should be able to implement nearly everything you need. The last things we will cover in the basic tutorials are: Sending messages (and defining your own message types), creating and using a GUI and the concept of the network (it’s not much more than setting some flags correctly). All tutorials beyond this point are just explanation of the api that you don’t implement functions by yourself that are already implemented as well as demos for different commonly used functions that are still too specific to be implemented in the engine directly.