Создание управляемого персонажа
Давайте создадим робота, который может перемещаться по игровой зоне и сталкиваться с объектами или стенами. Наш персонаж будет летать над полом и поворачиваться лицом к курсору.
Шаг 1. Включение физики для модели робота#
Робот со сложной 3D-моделью будет представлять игрового персонажа в игре. Мы уже импортировали Node Reference со Skinned Mesh, анимацию полета и материалы для робота.
Робот должен уметь перемещаться по игровой площадке и сталкиваться со статическими и физическими объектами. Для этого он должен иметь физическое тело и коллизионную форму, приблизительно соответствующую его контурам.
-
Поместите импортированную ноду programming_quick_start\character\robot\robot.node на пол внутри игровой зоны, перетащив ее из Asset Browser непосредственно в Editor Viewport.
-
При выбранной ноде robot щелкните Edit в разделе Reference окна Parameters. Откроется Node Reference, и текущий выбор должен быть сфокусирован на ноде robot ObjectMeshSkinned. Теперь переключитесь на вкладку Physics окна Parameters и назначьте тело Rigid выбранному ObjectMeshSkinned.
- Установите для параметра LDamping значение 5.0, чтобы убедиться, что робот со временем будет терять скорость.
-
Прокрутите вниз до раздела Shapes и добавьте форму Capsule к телу.
Форма капсулы будет использоваться в качестве приблизительного объема для столкновений с другими объектами в мире.
Шаг 2. Настройка элементов управления#
Мы приложим линейный импульс к телу, чтобы переместить робота с помощью клавиш клавиатуры WASD. Движение робота будет определяться в соответствии с ориентацией камеры. Кроме того, давайте ограничим вращение и вертикальное перемещение, основанные на физике, чтобы избежать нежелательного поведения при управлении.
Чтобы сориентировать робота лицом к курсору, мы будем использовать один из типов пересечений, называемый World Intersection. Он проведет линию от положения курсора до пола, чтобы получить точку пересечения, которая будет использоваться в качестве ориентира для вращения робота. Вы можете прочитать больше об управлении различными пересечениями здесь.
Лучший способ управлять вводом с клавиатуры и мыши — это использовать класс Input. Это позволяет вам проверить состояние кнопок и получить текущие координаты мыши. Альтернативные методы обработки входных данных описаны здесь.
Давайте используем Компонентную систему C++ для реализации этой логики. Мы создадим компонент C++ и назначим его на ноду робота в мире.
- Чтобы начать писать код, мы должны открыть наш проект в IDE. Перейдите к SDK Browser и выберите Open Code IDE.
-
В среде IDE создайте новый класс C++ (файлы *.h и *.cpp) и назовите его PlayerController. Убедитесь, что он наследуется от класса Unigine::ComponentBase.
Скопируйте приведенный ниже код и вставьте его в соответствующие файлы в вашем проекте и сохраните их в вашей IDE.
Рекомендуется использовать Visual Studio в качестве IDE по умолчанию.#pragma once #include <UnigineComponentSystem.h> #include <UnigineGame.h> #include <UnigineControls.h> #include <UnigineStreams.h> #include <UniginePlayers.h> #include <UnigineWorld.h> #include <UnigineConsole.h> #include <UnigineMathLib.h> #include <UnigineRender.h> class PlayerController : public Unigine::ComponentBase { public: // declare constructor and destructor for our class and define a property name. COMPONENT_DEFINE(PlayerController, ComponentBase) // declare methods to be called at the corresponding stages of the execution sequence COMPONENT_INIT(init); COMPONENT_UPDATE_PHYSICS(updatePhysics); protected: void init(); void updatePhysics(); private: void move(const Unigine::Math::vec3& direction); Unigine::BodyRigidPtr rigid; Unigine::PlayerPtr player; // a WorldIntersection object to store the information about the intersection Unigine::WorldIntersectionPtr intersection = Unigine::WorldIntersection::create(); Unigine::Math::Vec3 pos; };
#include "PlayerController.h" using namespace Unigine; using namespace Math; REGISTER_COMPONENT(PlayerController); void PlayerController::init() { player = Game::getPlayer(); if (node) { rigid = node->getObjectBodyRigid(); if (rigid) { rigid->setAngularScale(vec3(0.0f, 0.0f, 0.0f)); // restricting the rotation rigid->setLinearScale(vec3(1.0f, 1.0f, 0.0f)); // restricting Z movement rigid->setMaxLinearVelocity(8.0f); // clamping the max linear velocity } } } void PlayerController::updatePhysics() { if (!Console::isActive()) // do not process input if the console is shown { // check if W key is pressed if (Input::isKeyPressed(Input::KEY::KEY_W)) move(player->getWorldDirection(MathLib::AXIS::AXIS_Y)); // move forward // check if S key is pressed if (Input::isKeyPressed(Input::KEY::KEY_S)) move(player->getWorldDirection(MathLib::AXIS::AXIS_NY)); // move backward // check if A key is pressed if (Input::isKeyPressed(Input::KEY::KEY_A)) move(player->getWorldDirection(MathLib::AXIS::AXIS_NX)); // move left // check if D key is pressed if (Input::isKeyPressed(Input::KEY::KEY_D)) move(player->getWorldDirection(MathLib::AXIS::AXIS_X)); // move right // finding the positions of the cursor and the point moved 100 units away in the camera forward direction ivec2 mouse = Input::getMousePosition(); Vec3 p0 = player->getWorldPosition(); Vec3 p1 = p0 + Vec3(player->getDirectionFromMainWindow(mouse.x, mouse.y)) * 100; // casting a ray from p0 to p1 to find the first intersected object ObjectPtr obj = World::getIntersection(p0, p1, 1, intersection); // the first bit of the intersection mask is set to 1, the rest are 0s // finding the intersection position, creating a transformation matrix to face this position and setting the transform matrix for the body preserving current angular and linear velocities if (obj && rigid) { pos = intersection->getPoint(); pos.z = rigid->getTransform().getTranslate().z; // project the position vector to the Body Rigid pivot plane Mat4 transform = Math::setTo(rigid->getTransform().getTranslate(), pos, vec3_up, AXIS::AXIS_Y); rigid->setPreserveTransform(transform); // turn the character's body } } } // moving the rigid body with linear impulse in the specified direction void PlayerController::move(const Unigine::Math::vec3& direction) { // direction is a normalized camera axis vector if (rigid) // direction is a normalized camera axis vector rigid->addLinearImpulse(direction); }
-
Чтобы использовать компоненты C++, нужно инициализировать систему компонентов. Измените метод init() класса AppSystemLogic, как показано ниже (файл AppSystemLogic.cpp). Также включите автоматическую распаковку NodeReference для удобства.
#include <UnigineComponentSystem.h> using namespace Unigine; int AppSystemLogic::init() { // initialize ComponentSystem and register all components ComponentSystem::get()->initialize(); // unpack node references to find child nodes by name World::setUnpackNodeReferences(true); return 1; }
- Соберите и запустите проект с помощью вашей IDE (нажмите Ctrl + F5 в Visual Studio), система компонентов сгенерирует файл свойств для компонента.
-
Переключитесь обратно в UnigineEditor и выберите ноду robot (ObjectMeshSkinned) в окне World Nodes. Затем щелкните Add New Property в окне Parameters и назначьте вновь созданное свойство на PlayerController.
Убедитесь в том, что назначаете свойство именно ноде ObjectMeshSkinned, находящейся внутри NodeReference!
Шаг 3. Завершение и запуск#
- Отключите обнаружение пересечений для каждой поверхности ноды робота, чтобы игнорировать пересечения с собственными поверхностями робота, так как мы не хотим, чтобы они учитывались при реализации вращения нашего робота.
-
Для каждого объекта стены в мире перейдите в окно Parameters и найдите раздел Surfaces на вкладке Node. Выберите поверхность box меша и откройте окно Intersection Mask. Снимите флажок с первого бита Intersection Mask 0, чтобы стены не влияли на поворот персонажа игрока.
- Чтобы курсор всегда был виден, перейдите в раздел Windows->Settings->Controls, измените режим Mouse Handle на User. Вы также можете управлять курсором с помощью API.
- Сохраните изменения мира и робота с помощью File->Save World (или нажмите горячую клавишу Ctrl+S).
Соберите и запустите игру в своей среде разработки, чтобы опробовать управление роботом.