# Managing Game Rules (CPP) Let's create the **LevelManager** component to manage game rules, level states and the User Interface. The manager creates a [graphical user interface](../../../code/gui/index.md) and shows the time left until the game is over. It also checks and shows the number of objects left to clear away from the Play Area. ## Step 1. Set Up Timer and Game UI A node with the **LevelManager** component assigned should be present in the world for rules to take effect. It will manage the timer and update the [widget](../../../code/gui/ui/ui_widgets.md) user interface for the game. 1. Create a new *C++ component* and call it **LevelManager**. 2. Write the following code in an IDE and save the solution. Build and run the solution to generate a property file for the component. > **Notice:** Take note that the **LevelManager** component's *init()* method is set to be called in the **2nd** order to make sure it is initialized after the **ObjectGenerator** component which creates and parents all physical objects to it (ObjectGenerator's initialization order is set to **1st**).
LevelManager.h | Close ```cpp #pragma once #include #include #include #include class LevelManager : public Unigine::ComponentBase { public: // declare constructor and destructor for our class and define a property name. COMPONENT_DEFINE(LevelManager, ComponentBase) // declare methods to be called at the corresponding stages of the execution sequence COMPONENT_INIT(init, 2); // 2nd initialization order COMPONENT_UPDATE(update); COMPONENT_SHUTDOWN(shutdown); // level timer PROP_PARAM(Float, timer, 100.0f); void decPhysicalObjectsNum(); void setEndGameCallback(Unigine::CallbackBase *callback) { endGameEvent = callback; } protected: void init(); void update(); void shutdown(); void initGUI(); private: Unigine::CallbackBase *endGameEvent = nullptr; bool isCounting = true; int physicalObjectsNum; Unigine::WidgetLabelPtr widget_timer, widget_goal; Unigine::ObjectTextPtr endText; }; ```
LevelManager.cpp | Close ```cpp #include "LevelManager.h" using namespace Unigine; using namespace Math; REGISTER_COMPONENT(LevelManager); void LevelManager::init() { LevelManager::initGUI(); // find the object text node of the widget endText = checked_ptr_cast(Game::getPlayer()->findNode("header_text", 1)); // count dynamic objects in the level physicalObjectsNum = node->getNumChildren(); } void LevelManager::initGUI() { // get a GUI pointer GuiPtr gui = Gui::getCurrent(); // create a label widget and set up its parameters widget_timer = WidgetLabel::create(gui, "Time Left:"); widget_timer->setPosition(10, 10); widget_timer->setFontColor(vec4_red); widget_goal = WidgetLabel::create(gui, "Objects Left: "); widget_goal->setPosition(10, 30); widget_goal->setFontColor(vec4_blue); // add widgets to the GUI gui->addChild(widget_timer, Gui::ALIGN_OVERLAP); gui->addChild(widget_goal, Gui::ALIGN_OVERLAP); } void LevelManager::update() { // decrease the timer if (isCounting) { timer = timer - Game::getIFps(); if (timer <= 0) { //set end game text if (endText) endText->setText("Game Over"); if (endGameEvent) endGameEvent->run(); isCounting = false; } } // show the current time and objects left to clear if (isCounting) { widget_timer->setText(String::format("Time Left: %.2f s", timer.get())); widget_goal->setText(String::format("Objects Left: %d", physicalObjectsNum)); } //hide the widgets on endgame else { widget_timer->setEnabled(false); widget_goal->setEnabled(false); } //win if (physicalObjectsNum <= 0) { if (endText) endText->setText("Success!"); if (endGameEvent) endGameEvent->run(); isCounting = false; } } void LevelManager::shutdown() { widget_timer.deleteLater(); widget_goal.deleteLater(); } void LevelManager::decPhysicalObjectsNum() { physicalObjectsNum--; } ```
3. Switch back to UnigineEditor and create a new *[Dummy Node](../../../objects/nodes/dummy/index.md)* called `level_manager` and place it somewhere in the world. 4. Add a *LevelManager* component to the `level_manager` node via the *Parameters* window in the UnigineEditor. ![](add_new_property_cpp.png) We created the system GUI via [the API from the code](../../../start/quick_start/pqr/index_cpp.md#ui). The alternative method is to use [UI files](../../../code/gui/ui/index.md). ## Step 2. Detect Physical Objects Only the *level_manager*'s children nodes shall be deleted by the **Kill Zone**'s trigger. Let's add each physical object that we [created earlier](../../../start/quick_start/physical_objects/index_cpp.md) as a child to the `level_manager` node. For the rules to function properly you also need to add a new condition and a method call to the **KillZone** component that checks if the entered node has a parent with the **LevelManager** component attached. 1. Open the **KillZone** component in your IDE, add a *levelManager* field and replace the content of the *Enter* [callback](../../../code/fundamentals/callbacks/index_cpp.md) function according to the following code.
KillZone.h | Close ```cpp #pragma once #include #include //========================== NEW - BEGIN =============================== #include "LevelManager.h" //=========================== NEW - END ================================ class KillZone : public Unigine::ComponentBase { public: // declare constructor and destructor for our class and define a property name. COMPONENT_DEFINE(KillZone, ComponentBase) // declare methods to be called at the corresponding stages of the execution sequence COMPONENT_INIT(init); protected: void init(); void enterEventHandler(const Unigine::NodePtr &target); private: // the area into which an object should fall Unigine::WorldTriggerPtr trigger; //========================== NEW - BEGIN =============================== LevelManager *levelManager; //=========================== NEW - END ================================ }; ```
KillZone.cpp | Close ```cpp #include "KillZone.h" using namespace Unigine; REGISTER_COMPONENT(KillZone); void KillZone::init() { trigger = checked_ptr_cast(node); // subscribe for the Enter event (when an object enters the area) if (trigger) trigger->getEventEnter().connect(this, &KillZone::enterEventHandler); } void KillZone::enterEventHandler(const NodePtr &target) { //========================== NEW - BEGIN =============================== levelManager = getComponentInParent(target); // check if the parent node has a LevelManager component attached if (levelManager) { // delete the entered node and decrease the amount of physical objects levelManager->decPhysicalObjectsNum(); target.deleteLater(); } //=========================== NEW - END ================================ } ```
2. Let's set the **level_manager** node as a parent to physical objects. Open the **ObjectGenerator** component in your IDE and replace the code in the corresponding files with the following. Save the code in your IDE, build and run the solution to regenerate a property file for the component and switch back to UnigineEditor.
ObjectGenerator.h | Close ```cpp #include #include #pragma once class ObjectGenerator : public Unigine::ComponentBase { public: COMPONENT_DEFINE(ObjectGenerator, ComponentBase); COMPONENT_INIT(init, 1); // 1st initialization order //========================== NEW - BEGIN =============================== PROP_PARAM(Node, levelManager); //=========================== NEW - END ================================ protected: void init(); }; ```
ObjectGenerator.cpp | Close ```cpp #include "ObjectGenerator.h" #include using namespace Unigine; REGISTER_COMPONENT(ObjectGenerator); void ObjectGenerator::init() { //========================== NEW - BEGIN =============================== if (levelManager) { //=========================== NEW - END ================================ // cube ObjectMeshDynamicPtr box = Primitives::createBox(Math::vec3(1.0f)); //========================== NEW - BEGIN =============================== box->setParent(levelManager); //========================== NEW - END =============================== box->setTriggerInteractionEnabled(1); box->setIntersection(1, 0); box->setIntersectionMask(0x00000080, 0); // check the BulletIntersection bit box->setWorldTransform(translate(Math::Vec3(0.5f, 7.5f, 1.0f))); box->setMaterialFilePath("materials/mesh_base_0.mat", "*"); box->setName("box"); Unigine::BodyRigidPtr bodyBox = BodyRigid::create(box); ShapeBoxPtr shapeBox = ShapeBox::create(bodyBox, Math::vec3(1.0f)); ShapeSphere::create(bodyBox, 0.5f); bodyBox->setShapeBased(0); bodyBox->setMass(2.0f); // sphere ObjectMeshDynamicPtr sphere = Primitives::createSphere(0.5f, 9, 32); //========================== NEW - BEGIN =============================== sphere->setParent(levelManager); //========================== NEW - END =============================== sphere->setTriggerInteractionEnabled(1); sphere->setIntersection(1, 0); sphere->setIntersectionMask(0x00000080, 0); // check the BulletIntersection bit sphere->setWorldTransform(translate(Math::Vec3(4.5f, 5.5f, 1.0f))); sphere->setMaterialFilePath("materials/mesh_base_1.mat", "*"); sphere->setName("sphere"); BodyRigidPtr bodySphere = BodyRigid::create(sphere); ShapeSphere::create(bodySphere, 0.5f); bodySphere->setShapeBased(0); bodySphere->setMass(2.0f); // capsule ObjectMeshDynamicPtr capsule = Primitives::createCapsule(0.5f, 1.0f, 9, 32); //========================== NEW - BEGIN =============================== capsule->setParent(levelManager); //========================== NEW - END =============================== capsule->setTriggerInteractionEnabled(1); capsule->setIntersection(1, 0); capsule->setIntersectionMask(0x00000080, 0); // check the BulletIntersection bit capsule->setWorldTransform(translate(Math::Vec3(4.5f, 0.5f, 3.0f))); capsule->setMaterialFilePath("materials/mesh_base_2.mat", "*"); capsule->setName("capsule"); BodyRigidPtr bodyCapsule = BodyRigid::create(capsule); ShapeCapsule::create(bodyCapsule, 0.5f, 1.0f); bodyCapsule->setShapeBased(0); bodyCapsule->setMass(2.0f); //========================== NEW - BEGIN =============================== } //=========================== NEW - END ================================ } ```
3. Select the **object_generator** node in the *World Nodes* window and drag the `level_manager` node to the corresponding field of the **ObjectGenerator** property. ![](level_manager_cpp.png) 4. Save changes to the world. Go to *File->Save World* or press **Ctrl+S** hotkey. 5. Switch to your IDE. Save changes to the solution and then build and run the game to see the game rules in action.