创建控制字符
Let's create a playable robot that can move around the Play Area and collide with objects or wall obstacles. Our character will fly above the floor and rotate to face the cursor.让我们创建一个可玩的机器人,它可以在游戏区域内移动,并且 与物体或墙壁障碍物发生碰撞。我们的角色将飞到地板上方,并旋转到面向光标的位置。
Step 1. Engage Physics for Robot's Model步骤1。参与机器人模型的物理#
The robot with a complex 3D model will represent the playable character in the game. We have already imported the Node Reference with the Skinned Mesh, the flying animation, and materials for the robot.具有复杂的 3D模型的机器人将代表游戏中的可玩角色。我们已经导入了带有Skinned Mesh的Node Reference, 飞翔的动画,以及机器人的材质。
The robot must be able to move around the Play Area and collide with static and physical objects. In order to do so, it should have a physical body and a collision shape approximating its volume.机器人必须能够在游戏区域内移动,并与静态物体和物理物体发生碰撞。为了做到这一点,它应该有一个物理的 body和一个碰撞形状来接近它的体积。
-
Place the imported programming_quick_start\character\robot\robot.node on the floor inside the Play Area by dragging it from the Asset Browser directly to the Editor Viewport. 将导入的programming_quick_start\character\robot\robot.node从Asset Browser直接拖到Editor Viewport,放置在Play Area的地板上。
-
With the robot node selected click Edit in the Reference section of the Parameters window. The Node Reference shall open and the current selection shall focus on the robot ObjectMeshSkinned node. Now switch to the Physics tab of the Parameters window and assign a Rigid body to the selected ObjectMeshSkinned.选中了robot节点后,单击Parameters窗口的Reference部分中的Edit。Node Reference将打开,当前的选择将聚焦于robot ObjectMeshSkinned节点。现在切换到Parameters窗口的Physics选项卡,并为选定的ObjectMeshSkinned分配Rigid主体。
- Set LDamping parameter to 5.0 to make sure that the robot will lose speed over time.将LDamping参数设置为 5.0,确保机器人会随着时间的推移而失去速度。
-
Scroll down to the Shapes section and add a Capsule shape to the body.向下滚动到Shapes部分,并向body添加一个Capsule shape。
The capsule shape will be used as an approximation volume for collisions with other objects in the world.胶囊形状将被用作与世界上其他物体碰撞的近似体积。
Step 2. Set Up Controls步骤2。设置控制#
We will apply a linear impulse to the body to move the robot with keyboard WASD keys. The robot's motion will be determined according to the camera's orientation. Also, let's restrict the physics-based rotation and vertical movement to avoid unwanted control behavior.我们将一个线性脉冲对身体移动机器人键盘 WASD 键。机器人的运动将决定根据相机的方向。同时,我们限制了基于物理旋转和垂直运动,以避免不必要的控制行为。
To orient the robot to face the cursor, we will use one of the intersection types called World Intersection. It will trace a line from the cursor position to the floor to get an intersection point that will be used as a reference for the robot's rotation. You can read more about managing various intersections here.为了使机器人朝向光标,我们将使用交集类型中的一个,称为World Intersection。它将从光标位置到地板的一条线,以得到一个交点,作为机器人旋转的参考点。您可以阅读更多关于管理各种交叉口在这里。
The best way to manage keyboard and mouse inputs, is to use the Input class. It enables you to check the state of the buttons and get the current mouse coordinates. Alternative methods for input handling are described here.管理键盘和鼠标输入的最佳方法是使用Input类。它使您能够检查按钮的状态并获得当前鼠标坐标。输入处理的替代方法在这里描述 。
Let's use the C++ Component System to implement this logic. We will create a C++ component and assign it to the robot's node in the world.让我们使用 C++ 组件系统 来实现这个逻辑。 我们将 创建一个 C++ 组件 并将其分配给机器人在世界中的节点。
- To start writing code we should open our project in an IDE. Go to SDK Browser and choose Open Code IDE.要开始编写代码,我们应该在IDE中打开我们的项目。转到SDK Browser并选择Open Code IDE。
-
In an IDE create a new C++ class (*.h and *.cpp files) and call it PlayerController. Make sure it inherits from the Unigine::ComponentBase class.在IDE创建一个新的c++类(*.h和*.cpp文件)和PlayerController调用它。确保它从Unigine::ComponentBase类继承。
Copy the code below and paste it to the corresponding files in your project and save them in your IDE.复制下面的代码并将其粘贴到项目中的相应文件中,并将其保存到IDE中。
It is recommended to use Visual Studio as a default 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); }
-
Before you can use C++ components you should initialize the Component System. Modify the init() method of the AppSystemLogic class as shown below (AppSystemLogic.cpp file). Also enable automatic NodeReference unpacking for convenience.在你使用c++组件之前,你应该初始化组件系统。修改AppSystemLogic类的init()方法,如下所示(AppSystemLogic.cpp文件)。为了方便,也可以启用自动NodeReference unpacking。
#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; }
- Build and run the project via your IDE (press Ctrl + F5 in Visual Studio), the Component System will generate a property-file for the component.通过您的IDE(在Visual Studio中按Ctrl + F5)构建和运行项目,组件系统将为组件生成一个属性文件。
-
Switch back to UnigineEditor and select the robot node (ObjectMeshSkinned) in the World Nodes window. Then click Add New Property in the Parameters window and assign the newly generated PlayerController property.切换回UnigineEditor并选择robot节点(ObjectMeshSkinned) World Nodes窗口。然后单击Add New Property Parameters窗口和分配新生成PlayerController财产。
Make sure you assign the property to the ObjectMeshSkinned node inside the NodeReference!确保将属性分配给 NodeReference 内的 ObjectMeshSkinned 节点!
Step 3. Finalize and Run步骤3。完成并运行#
- Turn off the Intersection detection for every surface of the robot node to ignore intersections with the robot's own surfaces, as we do not want it in our robot's rotation implementation.关闭机器人节点每个表面的 Intersection 检测以忽略与机器人自身表面的交叉点,因为我们不希望它在机器人的旋转实现中出现。
-
For every wall object in the world, go to the Parameters window and find the Surfaces section in the Node tab. Select the box surface of the mesh and open an Intersection Mask window. Uncheck the first Intersection Mask 0 bit to make sure that walls will not affect the player's character turning.对于,世界中每个 wall对象,转到Parameters窗口,并在Node选项卡中找到Surfaces部分。选择网格的box表面并打开一个Intersection Mask窗口。取消第一个Intersection Mask 0位,以确保墙壁不会影响玩家的角色转弯。
- To make the cursor always visible, go to Windows->Settings->Controls section, change the Mouse Handle mode to User. You can also control the cursor via API.要使光标始终可见,请转到 Windows->Settings->Controls 部分,将 Mouse Handle 模式更改为 User。 您还可以通过 API 控制光标。
- Save changes to the world and the robot via File->Save World (or press Ctrl+S hotkey).通过File->Save World(或按Ctrl+S热键)保存世界和机器人的更改。
Build and run the game in your IDE to try out the robot's controls.在你的IDE中构建并运行游戏,以尝试机器人的控制。