Управление геймплеем
Давайте создадим компонент LevelManager для управления геймплеем, состояниями уровней и пользовательским интерфейсом. Менеджер создает графический пользовательский интерфейс и показывает время, оставшееся до окончания игры. Он также проверяет и показывает количество объектов, которые осталось убрать с игровой площадки.
Шаг 1. Настройка таймера и пользовательского интерфейса игры#
Нода с назначенным компонентом LevelManager должна присутствовать в мире, чтобы правила вступили в силу. Она будет управлять таймером и обновлять пользовательский интерфейс виджета для игры.
- Создайте новый компонент C++ и назовите его LevelManager.
-
Напишите следующий код в среде IDE и сохраните решение. Создайте и запустите решение, чтобы сгенерировать файл свойств для компонента.
Обратите внимание, что метод 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--; }
- Переключитесь обратно на UnigineEditor и создайте новую Dummy Node с именем level_manager и поместите ее где-нибудь в мире.
-
Добавьте компонент LevelManager к ноде level_manager через окно Parameters в UnigineEditor.
Мы создали системный графический интерфейс с помощью API из кода. Альтернативный метод заключается в использовании файлов пользовательского интерфейса.
Шаг 2. Обнаружение физических объектов#
Только дочерние ноды level_manager должны быть удалены триггером Kill Zone. Давайте добавим каждый физический объект, который мы создали ранее, в качестве дочернего элемента к ноде level_manager. Чтобы правила функционировали должным образом, вам также необходимо добавить новое условие и вызов метода к компоненту KillZone, который проверяет, есть ли у входящей в зону ноды родительский элемент с подключенным компонентом LevelManager.
-
Откройте компонент 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 ================================ }
-
Давайте установим ноду 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->setMaterialPath("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->setMaterialPath("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->setMaterialPath("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 ================================ }
-
Выберите ноду object_generator в окне World Nodes и перетащите ноду level_manager в соответствующее поле свойства ObjectGenerator.
- Сохраните изменения в мире. Перейдите к File->Save World или нажмите горячую клавишу Ctrl+S.
- Переключитесь на свой IDE. Сохраните изменения в решении, а затем создайте и запустите игру, чтобы увидеть геймплей в действии.