Управление геймплеем
Let's create the LevelManager component to manage game rules, level states and the User Interface. The manager creates a graphical user interface 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.Давайте создадим компонент LevelManager для управления геймплеем, состояниями уровней и пользовательским интерфейсом. Менеджер создает графический пользовательский интерфейс и показывает время, оставшееся до окончания игры. Он также проверяет и показывает количество объектов, которые осталось убрать с игровой площадки.
Step 1. Set Up Timer and Game UIШаг 1. Настройка таймера и пользовательского интерфейса игры#
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 user interface for the game.Нода с назначенным компонентом LevelManager должна присутствовать в мире, чтобы правила вступили в силу. Она будет управлять таймером и обновлять пользовательский интерфейс виджета для игры.
- Create a new C++ component and call it LevelManager.Создайте новый компонент C++ и назовите его LevelManager.
-
Write the following code in an IDE and save the solution. Build and run the solution to generate a property file for the component.Напишите следующий код в среде IDE и сохраните решение. Создайте и запустите решение, чтобы сгенерировать файл свойств для компонента.
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).Обратите внимание, что метод init() компонента LevelManager настроен на вызов во 2-м порядке, чтобы убедиться, что он инициализирован после компонента ObjectGenerator, который создает и привязывает к нему все физические объекты (порядок инициализации ObjectGenerator установлен на 1-й).#pragma once #include <UnigineComponentSystem.h> #include <UnigineWidgets.h> #include <UnigineGame.h> #include <UnigineString.h> 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; };
#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<ObjectText>(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--; }
- Switch back to UnigineEditor and create a new Dummy Node called level_manager and place it somewhere in the world.Переключитесь обратно на UnigineEditor и создайте новую Dummy Node с именем level_manager и поместите ее где-нибудь в мире.
-
Add a LevelManager component to the level_manager node via the Parameters window in the UnigineEditor.Добавьте компонент LevelManager к ноде level_manager через окно Parameters в UnigineEditor.
We created the system GUI via the API from the code. The alternative method is to use UI files.Мы создали системный графический интерфейс с помощью API из кода. Альтернативный метод заключается в использовании файлов пользовательского интерфейса.
Step 2. Detect Physical ObjectsШаг 2. Обнаружение физических объектов#
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 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.Только дочерние ноды level_manager должны быть удалены триггером Kill Zone. Давайте добавим каждый физический объект, который мы создали ранее, в качестве дочернего элемента к ноде level_manager. Чтобы правила функционировали должным образом, вам также необходимо добавить новое условие и вызов метода к компоненту KillZone, который проверяет, есть ли у входящей в зону ноды родительский элемент с подключенным компонентом LevelManager.
-
Open the KillZone component in your IDE, add a levelManager field and replace the content of the Enter callback function according to the following code.Откройте компонент KillZone в вашей IDE, добавьте поле levelManager и замените содержимое функции обратного вызова Enter в соответствии со следующим кодом.
#pragma once #include <UnigineComponentSystem.h> #include <UnigineWorlds.h> //========================== 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 ================================ };
#include "KillZone.h" using namespace Unigine; REGISTER_COMPONENT(KillZone); void KillZone::init() { trigger = checked_ptr_cast<WorldTrigger>(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<LevelManager>(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 ================================ }
-
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.Давайте установим ноду level_manager в качестве родительской ноды для физических объектов. Откройте компонент ObjectGenerator в вашей IDE и замените код в соответствующих файлах следующим. Сохраните код в своей среде разработки, создайте и запустите решение для восстановления файла свойств для компонента и вернитесь к UnigineEditor.
#include <UnigineComponentSystem.h> #include <UnigineGame.h> #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(); };
#include "ObjectGenerator.h" #include <UniginePrimitives.h> 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 ================================ }
-
Select the object_generator node in the World Nodes window and drag the level_manager node to the corresponding field of the ObjectGenerator property.Выберите ноду object_generator в окне World Nodes и перетащите ноду level_manager в соответствующее поле свойства ObjectGenerator.
- Save changes to the world. Go to File->Save World or press Ctrl+S hotkey.Сохраните изменения в мире. Перейдите к File->Save World или нажмите горячую клавишу Ctrl+S.
- Switch to your IDE. Save changes to the solution and then build and run the game to see the game rules in action.Переключитесь на свой IDE. Сохраните изменения в решении, а затем создайте и запустите игру, чтобы увидеть геймплей в действии.