This page has been translated automatically.
Видеоуроки
Интерфейс
Основы
Продвинутый уровень
Подсказки и советы
Основы
Программирование на C#
Рендеринг
Профессиональный уровень (SIM)
Принципы работы
Свойства (properties)
Компонентная Система
Рендер
Физика
Редактор UnigineEditor
Обзор интерфейса
Работа с ассетами
Контроль версий
Настройки и предпочтения
Работа с проектами
Настройка параметров ноды
Setting Up Materials
Настройка свойств
Освещение
Sandworm
Использование инструментов редактора для конкретных задач
Расширение функционала редактора
Встроенные объекты
Ноды (Nodes)
Объекты (Objects)
Эффекты
Декали
Источники света
Geodetics
World-ноды
Звуковые объекты
Объекты поиска пути
Player-ноды
Программирование
Основы
Настройка среды разработки
Примеры использования
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Плагины
Форматы файлов
Материалы и шейдеры
Rebuilding the Engine Tools
Интерфейс пользователя (GUI)
Двойная точность координат
API
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
Работа с контентом
Оптимизация контента
Материалы
Визуальный редактор материалов
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Учебные материалы

Управление геймплеем

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 должна присутствовать в мире, чтобы правила вступили в силу. Она будет управлять таймером и обновлять пользовательский интерфейс виджета для игры.

  1. Create a new C++ component and call it LevelManager.Создайте новый компонент C++ и назовите его 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.Напишите следующий код в среде 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-й).
    LevelManager.h (С++)
    #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;
    };
    LevelManager.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<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--;
    }
  3. 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 и поместите ее где-нибудь в мире.
  4. 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.

  1. 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 в соответствии со следующим кодом.

    KillZone.h (C++)
    #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 ================================
    };
    KillZone.cpp (C++)
    #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 ================================
    }
  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.Давайте установим ноду level_manager в качестве родительской ноды для физических объектов. Откройте компонент ObjectGenerator в вашей IDE и замените код в соответствующих файлах следующим. Сохраните код в своей среде разработки, создайте и запустите решение для восстановления файла свойств для компонента и вернитесь к UnigineEditor.

    ObjectGenerator.h (C++)
    #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();
    };
    ObjectGenerator.cpp (C++)
    #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 ================================
    }
  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.Выберите ноду object_generator в окне World Nodes и перетащите ноду level_manager в соответствующее поле свойства ObjectGenerator.

  4. Save changes to the world. Go to File->Save World or press Ctrl+S hotkey.Сохраните изменения в мире. Перейдите к File->Save World или нажмите горячую клавишу Ctrl+S.
  5. Switch to your IDE. Save changes to the solution and then build and run the game to see the game rules in action.Переключитесь на свой IDE. Сохраните изменения в решении, а затем создайте и запустите игру, чтобы увидеть геймплей в действии.
Последнее обновление: 29.11.2023
Build: ()