编程快速参考
Introduction介绍#
In the next chapters you will find the basic information on how to start programming 3D applications and videogames with UNIGINE. This FAQ consists of the following chapters:在接下来的章节中,你会发现如何开始使用UNIGINE编程3D应用程序和视频游戏的基本信息。本常见问题解答包括以下几章:
- Basic Scene Objects基本场景物体
- Coordinate System坐标系
- Logging and Printing Messages to Console日志和打印消息到控制台
- Saving and Loading a World保存和加载World
- Closing the Application关闭应用程序
- Creating and Deleting Nodes at Runtime运行时创建/删除节点
- Creating and Setting Up a Camera创建和设置摄像机
- Creating and Setting up Light Sources创建和设置光源
- Creating, Applying and Deleting Materials at Runtime在运行时创建、应用和删除物料
- Managing Existing Scene Objects管理现有场景对象
- Performing Basic Transformations (Move, Rotate, Scale)执行基本转换(移动,旋转,缩放)
- Making the Game Process Framerate-independent使游戏过程帧率独立
- Managing Intersections管理间隙
- Getting and Managing User Inputs获取和管理用户输入
- Creating User Interface Creating用户界面
- Playing Sound and Music播放声音和音乐
- Setting Up Physics设置物理
- Catching Nodes with World Triggers使用World Triggers捕获节点
Basic Scene Objects基本场景对象#
In terms of UNIGINE, node is a basic type from which all types of scene objects are inherited. Some of them appear visually: Objects, Decals, and Effects they all have surfaces to represent their geometry (mesh), while others (Light Sources, Players, etc.) are invisible.对于 UNIGINE 而言,节点 是一种基本类型,所有类型的场景对象都继承自该类型。 其中一些在视觉上出现:Objects、Decals 和 Effects,它们 都有 surfaces 来表示它们的几何体(网格),而其他的(Light Sources、玩家等)是不可见的。
Each node has a transformation matrix, which encodes position, rotation, and scale of the node in the world.每个节点有一个变换矩阵的编码位置,旋转,世界上节点的规模。
All scene objects added to the scene regardless of their type are called nodes.所有添加到场景中的场景对象,无论其类型,都称为节点。
附加信息:
- For more information on UNIGINE node types, see Built-in Node Types section.有关 UNIGINE 节点类型的更多信息,请参阅 内置节点类型 部分。
- For more information on managing nodes via API, see Node-Related Classes section.有关通过API管理节点的更多信息,请参见节点相关类小节。
Coordinate System坐标系统#
The 3D space in UNIGINE is represented by the right-handed Cartesian coordinate system: X and Y axes form a horizontal plane, Z axis points up. When exporting an animation from 3D editors, Y is considered a forward direction.Unigine中的3D空间用右手笛卡尔坐标系表示:X和Y轴形成一个水平面,Z轴指向上方 . 当 exporting 从 3D 编辑器中导出动画时,Y 被视为正向。
Positive rotation angle sets the rotation counterclockwise. It corresponds to the right-hand rule: if you set right hand thumb along the axis, other fingers wrapped will show rotation direction.积极的旋转角度设置逆时针旋转。它对应于右手法则:如果你设置的右手拇指沿轴,其他手指包裹将显示旋转的方向。
附加信息:
- For more information on UNIGINE node types, see Built-in Node Types section.有关 UNIGINE 节点类型的更多信息,请参阅 内置节点类型 部分。
- For more information on managing nodes via API, see Node-Related Classes section.有关通过API管理节点的更多信息,请参见节点相关类小节。
Logging and Printing Messages to Console日志和打印消息到控制台#
Printing messages to the log file and console helps to monitor overall progress of execution of your application and report errors which can be used in debugging. Log class makes it possible to print formatted string messages to the log file and the console. The code below demonstrates how to print various types of messages:打印消息日志文件和控制台监控整体进度的执行应用程序和报告错误,可以用于调试。Log类可以打印格式化字符串消息日志文件和控制台。下面的代码演示了如何打印各种类型的消息:
using namespace Unigine;
// auxiliary variables for messages
const char* file_name = "file.txt";
int ID = 10;
// reporting an error message
Log::error("Loading mesh: can't open \"%s\" file\n", file_name);
// reporting a message
Log::message("-> Added %d UI elements.\n", ID);
// reporting a warning message
Log::warning("ID of the \"%s\" file: %d.\n", file_name, ID);
// reporting a fatal error message to the log file and closing the application
Log::fatal("FATAL ERROR reading \"%s\" file!\n", file_name);
Additional information:附加信息:强
Saving and Loading a World保存和加载一个世界#
Some applications manage a single world, while other require several worlds to be managed. In any case, it is very useful to know how to save our current world and load some other. In order to solve this task, we should use the World class, which is designed as a singleton.有些应用程序管理单个世界,而其他应用程序则需要管理多个世界。在任何情况下,知道如何保存当前世界和加载其他世界是非常有用的。为了解决这个任务,我们应该使用World类,它被设计成一个单例。
#include <UnigineWorld.h>
using namespace Unigine;
/* .. */
// loading world from the my_world.world file
World::loadWorld("my_world");
We can also do the same via the console by using the Console class, which is also designed as a singleton.我们也可以通过 console,通过使用Console类来实现同样的功能,它也被设计为单例。
#include <UnigineConsole.h>
using namespace Unigine;
/* .. */
// saving current world to the my_world.world file
Console::run("world_save my_world");
// loading world from the my_world.world file
Console::run("world_load my_world");
Additional information:附加信息:强
- For more information on managing worlds via API, see the World class article.管理世界通过API的更多信息,请参阅World类文章。
- For more information on the console and available commands, see Console article.有关控制台和可用命令的更多信息,请参阅 console 文章。
- For more information on managing the console via API, see Console class article.管理控制台通过API的更多信息,参见Console类文章。
- For more information on managing world nodes that are to be saved via API, see the methods of the Node class.有关管理将通过API保存的世界节点的更多信息,请参阅Node类的方法。
Closing the Application关闭应用程序#
Any application needs to be closed at some moment. To close your application you should use the Engine class.任何应用程序都需要在某个时刻关闭。要关闭您的应用程序,您应该使用Engine类。
To close the application the following code is to be used:要关闭应用程序,需要使用以下代码:
using namespace Unigine;
/* .. */
// closing the application
Engine::get()->quit();
Creating and Deleting Nodes at Runtime在运行时创建和删除节点#
Nodes can be created and deleted at runtime almost as easy as in the Editor. The basic set of actions is as follows:在运行时创建和删除节点几乎和在编辑器中一样简单。基本的动作集如下:
- Creation. To create a node we should declare a smart pointer for the type of the node we are going to create and call a constructor of the corresponding class providing construction parameters if necessary.创建。我们应该创建一个节点为节点的类型声明一个智能指针我们要创建和调用相应的类的构造函数在必要时提供施工参数。
- Deletion. To delete a node we simply call the deleteLater() method for the node we are going to remove.删除>
// creating a node of the NodeType named nodename
<NodeType>Ptr nodename = <NodeType>::create(<construction_parameters>);
// removing the node
nodename.deleteLater();
Now let us illustrate the process of creating and deleting a node using a simple static mesh (ObjectMeshStatic) by loading a mesh from an asset as an example.现在让我们以从资源加载网格为例,说明使用简单静态网格 (ObjectMeshStatic) 创建和删除节点的过程。
#include <UnigineObjects.h>
using namespace Unigine;
/* .. */
int AppWorldLogic::init()
{
// first, we're creating a mesh instance as it is required to call an ObjectMeshStatic constructor
MeshPtr mesh = Mesh::create();
// loading a mesh from an fbx model imported to the data/fbx/ folder using the Editor
mesh->load("fbx/model.fbx/model.mesh");
// creating a box surface with a given size to the mesh
mesh->addBoxSurface("box_surface", Math::vec3(0.5f, 0.5f, 0.5f));
//create an ObjectMeshStatic node
ObjectMeshStaticPtr my_object = ObjectMeshStatic::create(mesh);
// removing the node
my_object.deleteLater();
// clearing the mesh
mesh.clear();
return 1;
}
Additional information:附加信息:强
- You can create primitives via code using the Primitives class.您可以创建原语通过代码使用Primitives类。
- For more information on managing world nodes, see the methods of the Node class.有关管理世界节点的更多信息,请参阅Node类的方法。
Creating and Setting Up a Camera创建和设置一个相机#
A camera is a viewport to the world, without it you actually won't be able to see anything. Cameras in UNIGINE are managed using players. When you add a new player, it creates a camera and specifies controls, masks, postprocess materials for this camera.一个 camera是一个对世界的视口,没有它你实际上将看不到任何东西。相机在UNIGINE使用players管理。当你添加一个新的播放器时,它会创建一个camera,并指定控件mask ,该相机的后期处理材质。
In order to set a new player as active one we should use the Game class which is designed as a singleton.为了设置一个新球员活跃我们应该使用Game设计成一个单例类。
The following code illustrates creation of a PlayerSpectator and setting it as the active game camera.以下代码说明了 PlayerSpectator 的创建并将其设置为活动游戏摄像机。
#include <UnigineGame.h>
using namespace Unigine;
/* ... */
int AppWorldLogic::init()
{
// creating a new PlayerSpectator instance
PlayerSpectatorPtr playerSpectator = PlayerSpectator::create();
// setting necessary parameters: FOV, ZNear, ZFar, view direction vector and position.
playerSpectator->setFov(90.0f);
playerSpectator->setZNear(0.1f);
playerSpectator->setZFar(10000.0f);
playerSpectator->setViewDirection(Math::vec3(0.0f, 1.0f, 0.0f));
playerSpectator->setWorldPosition(Math::Vec3(-1.6f, -1.7f, 1.7f));
// setting the player as a default one via the Game singleton instance
Game::setPlayer(playerSpectator);
return 1;
}
Additional information:附加信息:强
Creating and Setting up Light Sources创建和设置光源#
Lighting is the basis of every scene defining colors and final look of your objects. Lights in UNIGINE are created the same way as all nodes.照明是每个场景定义颜色和你的对象的最终外观的基础。UNIGINE中的灯的创建方式与所有节点相同。
Let us consider creation of a world light source as an example:让我们考虑建立一个世界光源为例:
#include <UnigineLights.h>
using namespace Unigine;
int AppWorldLogic::init()
{
// creating a world light source and setting its color to white
LightWorldPtr sun = LightWorld::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f));
// setting light source's parameters (intensity, disable angle, scattering type, name and rotation)
sun->setName("Sun");
sun->setDisableAngle(90.0f);
sun->setIntensity(1.0f);
sun->setScattering(LightWorld::SCATTERING::SCATTERING_SUN);
sun->setWorldRotation(Math::quat(86.0f, 30.0f, 300.0f));
return 1;
}
Additional information:附加信息:强
- For more information on light sources, see the Light sources article.有关光源的更多信息,请参阅 光源 文章。
- For more information on light sources API, see the Lights-Related Classes article.有关光源API的更多信息,请参阅灯相关类文章。
Creating, Applying and Deleting Materials at Runtime在运行时创建、应用和删除素材#
Materials assigned to particular surfaces of a node determine how the node is to be rendered. They implement the shaders and control what options, states, parameters of different types and textures are used to render the node during the rendering passes. To manage materials we use the following two classes:材质分配给节点的特定表面决定了节点的渲染方式。 他们实现 shaders 并控制 options、states、参数和不同类型的纹理 用于在 渲染通道 期间渲染节点。 为了管理材质,我们使用以下两个类:
- Materials class which represents an interface for managing loaded materials.Materials类代表一个接口来管理材质加载。
- Material class which is used to manage each individual material.Material类,用于管理每个单独的材质。
The following code can be used to create a new material inherited from the mesh_base material.可以使用下面的代码来创建一个新的材质继承了mesh_base材质。
#include <UnigineMaterials.h>
#include <UnigineObjects.h>
using namespace Unigine;
/* .. */
int AppWorldLogic::init()
{
// creating a box (ObjectMeshDynamic node)
MeshPtr mesh = Mesh::create();
mesh->addBoxSurface("box_surface", Math::vec3(1.5f, 1.5f, 1.5f));
ObjectMeshDynamicPtr my_mesh = ObjectMeshDynamic::create(mesh);
// getting the base mesh_base material to inherit from
MaterialPtr mesh_base = Materials::findManualMaterial("Unigine::mesh_base");
// inherit a new child material from it
MaterialPtr my_mesh_base = mesh_base->inherit();
// save it to "materials/my_mesh_base0.mat"
my_mesh_base->createMaterialFile("materials/my_mesh_base0.mat");
// setting the albedo color of the material to red
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(255, 0, 0, 255));
// assigning a "my_mesh_base0.mat" material to the surface 0 of the my_mesh ObjectMeshDynamic node
my_mesh->setMaterialPath("materials/my_mesh_base0.mat", 0);
// assigning a "my_mesh_base0.mat" material to all surfaces of the my_mesh ObjectMeshDynamic node
my_mesh->setMaterialPath("materials/my_mesh_base0.mat", "*");
}
int AppWorldLogic::shutdown()
{
// deleting the material "materials/my_mesh_base0.mat"
Materials::removeMaterial(Materials::findMaterialByPath("materials/my_mesh_base0.mat")->getGUID());
return 1;
}
Additional information:附加信息:强
- For more information on creating and editing materials via API, see the Material class article.有关通过API创建和编辑素材的更多信息,请参阅Material类文章。
- For more information on managing loaded materials via API, see the Materials class article.管理加载材质通过API的更多信息,参见Materials类文章。
- For more information on materials files formats, see the Materials Files section.有关材质文件格式的详细信息,请参阅 材质文件 部分。
- For more information on materials parameters, see the materials files in the %UNIGINE_SDK_BROWSER_INSTALLATION_FOLDER%/sdks/%CURRENT_SDK%/data/core/materials/default/ folder.有关物料参数的更多信息,请参见%UNIGINE_SDK_BROWSER_INSTALLATION_FOLDER%/sdks/%CURRENT_SDK%/data/core/materials/default/文件夹中的物料文件。
Managing Existing Scene Objects管理现有的场景对象#
Not all content in the world is created at runtime, so we should be able to operate with nodes that already exist. How do we get pointers to existing objects in order to manage them? This is where the World class comes into play again. Basically, there are two ways we can get a pointer to a certain node using the methods of the World class:不是世界上所有的内容是在运行时创建的,所以我们应该能够操作节点已经存在。我们怎么得到现有对象的指针来管理他们吗?这就是World类再次发挥作用。基本上,有两种方法我们可以得到一个指向某个节点World类的使用方法:
- getNodeByName() method - when we know node's namegetNodeByName()方法——当我们知道节点名时
- getNodeByID() method - when we know the node's IDgetNodeByID()方法——当我们知道节点的ID
These methods return a NodePtr value, which is a pointer to the base class, but in order to perform operations with a certain object (e.g. ObjectMeshDynamicPtr) we need to perform downcasting (i.e. convert from a pointer-to-base to a pointer-to-derived).这些方法返回一个NodePtr值,这是一个指向基类的指针,但是为了与某个对象执行操作(例如 ObjectMeshDynamicPtr)我们需要执行 的向下类型转换(即转换从pointer-to-base pointer-to-derived)。
Sometimes you may also need to perform upcasting (i.e. convert from a pointer-to-derived to a pointer-to-base), in this case you can use the derived class itself. The code below demonstrates the points described above.有时你可能还需要执行 upcasting(即从指针到派生类转换为指针到基类),在这种情况下,你可以使用派生类本身。下面的代码演示了上面描述的要点。
#include <UnigineWorld.h>
using namespace Unigine;
/* .. */
// find a pointer to node by a given name
NodePtr baseptr = World::getNodeByName("my_meshdynamic");
// cast a pointer-to-derived from pointer-to-base with automatic type checking
ObjectMeshDynamicPtr derivedptr = checked_ptr_cast<ObjectMeshDynamic>(baseptr);
// static cast (pointer-to-derived from pointer-to-base)
ObjectMeshDynamicPtr derivedptr = static_ptr_cast<ObjectMeshDynamic>(World::getNodeByName("my_meshdynamic"));
// upcast to the pointer to the Object class which is a base class for ObjectMeshDynamic
ObjectPtr object = derivedptr;
// upcast to the pointer to the Node class which is a base class for all scene objects
NodePtr node = derivedptr;
There are the following ways to get a component from a node:有以下方法让一个组件从一个节点:
// get the component assigned to a node by type "MyComponent"
MyComponent* my_component = ComponentBase::getComponent<MyComponent>(node);
// // do the same by using the function of the Component System
MyComponent* my_component = ComponentSystem::get()->getComponent<MyComponent>(color_zone);
Additional information:附加信息:强
- For more information on working managing smart pointers, see the Working with Smart Pointers article.有关管理智能指针的更多信息,请参阅使用智能指针一文。
- For more information see the World class article.有关更多信息,请参阅World类文章。
Performing Basic Transformations (Move, Rotate, Scale)执行基本的转换(移动、旋转、规模)#
Every node has a transformation matrix, which encodes position, rotation, and scale of the node in the world. If a node is added as a child of another node, it has a transformation matrix that is related to its parent node. That is why the Node class has different functions: getTransform(), setTransform() and getWorldTransform(), setWorldTransform() that operate with local and world transformation matrices respectively. The following code illustrates how to perform basic node transformations:每个节点都有一个转换矩阵,它对节点在世界中的位置、旋转和规模进行编码。如果一个节点被添加为另一个节点的子节点,则它有一个与其父节点相关的转换矩阵。这就是为什么Node类有不同的函数:getTransform(), setTransform()和getWorldTransform(), setWorldTransform(),它们分别与局部和世界变换矩阵进行操作。下面的代码演示了如何执行基本的节点转换:
// injecting Unigine namespace to the global namespace
using namespace Unigine;
using namespace Unigine::Math;
// move the node by X, Y, Z units along the corresponding axes
node->setWorldPosition(node->getWorldPosition() + Vec3(X, Y, Z));
// move the node by one unit along the Y axis
node->worldTranslate(0.0f, 1.0f, 0.0f);
// rotate the node around the axis (X, Y, Z) by the Alpha angle
node->setWorldRotation(node->getWorldRotation() * quat(Vec3(X, Y, Z), Alpha));
// rotate the node around X, Y, and Z axes by the corresponding angle (angle_X, angle_Y, angle_Z)
node->setWorldRotation(node->getWorldRotation() * quat(angle_X, angle_Y, angle_Z));
// rotate the node by 45 degrees along the Z axis
node->worldRotate(0.0f, 0.0f, 45.0f);
// orient the node using a direction vector and a vector pointing upwards
node->setWorldDirection(vec3(0.5f, 0.5f, 0.0f), vec3_up, AXIS_Y);
// setting node scale to Scale_X, Scale_Y, Scale_Z along the corresponding axes
node->setWorldScale(vec3(Scale_X, Scale_Y, Scale_Z));
// setting new transformation matrix to scale the node 2 times along all axes, rotate it by 45 degrees around the Z-axis and move it by 1 unit along all axes
Mat4 transform = Mat4(translate(vec3(1.0f, 1.0f, 1.0f)) * rotate(quat(0.0f, 0.0f, 1.0f, 45.0f)) * scale(vec3(2.0f)));
// setting node transformation matrix relative to its parent
node->setTransform(transform);
// setting node transformation matrix relative to the world origin
node->setWorldTransform(transform);
Additional information:附加信息:强
- For more information on matrix transformations, see the Matrix Transformations article.有关矩阵变换的更多信息,请参阅矩阵变换文章。
Making the Game Process Framerate-independentFramerate-independent使游戏过程#
As the frame rate of our application may vary (i.e. the AppWorldLogic::update() method will be called more or less frequently) depending on hardware, we should do something to ensure that certain actions are performed at the same time periods regardless of the frame rate (e.g. change something once per second etc). To make your game frame rate independent you can use a scaling multiplier (the time in seconds it took to complete the last frame) returned by the following methods:由于我们的应用程序的帧速率可能会有所不同(即 AppWorldLogic::update() 方法会或多或少地被调用 )取决于硬件,我们应该做一些事情来确保某些动作在相同的时间段内执行,而不管帧速率如何(例如每秒更改一次等)。 为了使您的游戏帧速率独立,您可以使用以下方法返回的缩放乘数(完成最后一帧所需的时间,以秒为单位):
- Engine::getIfps() returns the inverse FPS value for your application.Engine::getIfps()返回应用程序的反FPS值。
- Game::getIfps() returns the scaled inverse FPS value. This class is to be used when you want to speed up, slow down or pause rendering, physics or game logic.Game::getIfps()返回缩放的反FPS值。这个类是用来当你想加快,放慢或暂停渲染,物理或游戏逻辑。
To change the transformations you can use the following code:要更改转换,您可以使用以下代码:
// AppWorldLogic.cpp
#include <UnigineGame.h>
// injecting Unigine namespace to the global namespace
using namespace Unigine;
/* .. */
int AppWorldLogic::update() {
// getting an inverse FPS value (the time in seconds it took to complete the last frame)
float ifps = Game::getIFps();
// moving the node up by 0.3 units every second instead of every frame
node->worldTranslate(Math::Vec3(0.0f, 0.0f, 0.3f * ifps));
return 1;
}
/* .. */
To perform some changes once in a certain period of time you can use the following code:执行一些变化一旦在一定的时间内你可以使用下面的代码:
#include <UnigineGame.h>
// injecting Unigine namespace to the global namespace
using namespace Unigine;
// AppWorldLogic.cpp
const float INTERVAL_DURATION = 5; // interval duration
float elapsed_time = INTERVAL_DURATION; // current time left to make changes
/* .. */
int AppWorldLogic::update() {
// getting an inverse FPS value (the time in seconds it took to complete the last frame)
float ifps = Game::getIFps();
// checking if it's time to make changes
if (elapsed_time < 0.0f)
{
/* .. DO SOME CHANGES .. */
// resetting elapsed time counter
elapsed_time = INTERVAL_DURATION;
}
// decreasing elapsed time counter
elapsed_time -= ifps;
return 1;
}
/* .. */
Additional information:附加信息:强
Managing Intersections管理十字路口#
Intersections are widely used in 3D applications. In UNIGINE there are three main types of intersections: intersection 在3D应用中被广泛使用。在UNIGINE中有三种主要的交叉口类型:
- World intersection - an intersection with objects and nodes.World intersection - objects 和 nodes 的交集。
- Physics intersection - an intersection with shapes and collision objects.Physics intersection - 一个具有形状和碰撞对象的交集。
- Game intersection - an intersection with pathfinding nodes like obstacles.Game intersection - 与 障碍 等寻路节点的交集。
But there are some conditions to detect intersections with the surface: 但是有一些条件来检测与表面的相交:
- The surface is enabled.表面是启用的。
- The surface has a material assigned.该表面已指定了一种材质。
Per-surface Intersection flag is enabled.每个表面Intersection标志启用。
You can set this flag to the object's surface by using the Object.setIntersection() function.您可以使用Object.setIntersection()函数将该标志设置为对象的表面。
The code below illustrates several ways of using world intersections:下面的代码说明了几种使用世界交点的方法:
- to find all nodes intersected by a bounding box找出与边界框相交的所有结点
- to find all nodes intersected by a bounding sphere找到所有节点分割的边界范围
- to find all nodes intersected by a bounding frustum找出与边界截锥相交的所有结点
- to find the first object intersected by a ray找到第一个对象分割的射线
#include <UnigineGame.h>
#include <UnigineWorld.h>
// injecting Unigine namespace to the global namespace
using namespace Unigine;
int listNodes(Vector<Ptr<Node>>& nodes, const char* intersection_with)
{
Log::message("Total number of nodes intersecting a %s is: %i \n", intersection_with, nodes.size());
for (int i = 0; i < nodes.size(); i++)
{
Log::message("Intersected node: %s \n", nodes.get(i)->getName());
}
// clearing the list of nodes
nodes.clear();
return 1;
}
int AppWorldLogic::update()
{
// getting a player pointer
PlayerPtr player = Game::getPlayer();
// creating a vector to store intersected nodes
Vector<Ptr<Node>> nodes;
//-------------------------- FINDING INTERSECTIONS WITH A BOUNDING BOX -------------------------
// initializing a bounding box with a size of 3 units, located at the World's origin
WorldBoundBox boundBox(Math::Vec3(0.0f), Math::Vec3(3.0f));
// finding nodes intersecting a bounding box and listing them if any
if (World::getIntersection(boundBox, nodes))
listNodes(nodes, "bounding box");
//------------------------- FINDING INTERSECTIONS WITH A BOUNDING SPHERE ------------------------
// initializing a bounding sphere with a radius of 3 units, located at the World's origin
WorldBoundSphere boundSphere(Math::Vec3(0.0f), 3.0f);
// finding nodes intersecting a bounding sphere and listing them if any
if (World::getIntersection(boundSphere, nodes))
listNodes(nodes, "bounding sphere");
//------------------------- FINDING INTERSECTIONS WITH A BOUNDING FRUSTUM -----------------------
// initializing a bounding frustum with a frustum of the player's camera
WorldBoundFrustum boundFrustum(player->getCamera()->getProjection(), player->getCamera()->getModelview());
// finding ObjectMeshStaticNodes intersecting a bounding frustum and listing them if any
if (World::getIntersection(boundFrustum, Node::OBJECT_MESH_STATIC, nodes))
listNodes(nodes, "bounding frustum");
//---------------- FINDING THE FIRST OBJECT INTERSECTED BY A RAY CAST FROM P0 to P1 --------------
// initializing points of the ray from player's position in the direction pointed by the mouse cursor
Math::ivec2 mouse = Input::getMousePosition();
Math::Vec3 p0 = player->getWorldPosition();
Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromMainWindow(mouse.x, mouse.y)) * 100;
//creating a WorldIntersection object to store the information about the intersection
WorldIntersectionPtr intersection = WorldIntersection::create();
// casting a ray from p0 to p1 to find the first intersected object
ObjectPtr obj = World::getIntersection(p0, p1, 1, intersection);
// print the name of the first intersected object and coordinates of intersection point if any
if (obj)
{
Math::Vec3 p = intersection->getPoint();
Log::message("The first object intersected by the ray at point (%f, %f, %f) is: %s \n ", p.x, p.y, p.z, obj->getName());
}
return 1;
}
Additional information:附加信息:强
- For more information on intersections, see the Intersections article.有关交叉口的更多信息,请参阅 Intersections 文章。
- For more information on managing World intersections via API, see the World class article.有关通过API管理World交集的更多信息,请参阅World类文章。
- For more information on managing Game intersections via API, see the Game class article.有关通过API管理Game交集的更多信息,请参阅Game类文章。
- For more information on managing Physics intersections via API, see the Physics class article.有关管理的更多信息Physics路口通过API,看到Physics类文章。
Getting and Managing User Inputs获取和管理用户输入#
The majority of applications are designed to interact with the user. In UNIGINE you can manage user inputs using the following classes:大多数应用程序被设计成与用户交互。在UNIGINE中,你可以使用以下类来管理用户输入:
The following code illustrates how to use Input class to get mouse coordinates in case if a right mouse button was clicked and to close the application if "q" key was pressed (ignoring this key if the console is opened):下面的代码演示了如何使用Input类获取鼠标坐标,以防如果鼠标右键点击关闭应用程序如果"q"键被按下(忽略这个关键如果控制台打开):
#include <UnigineInput.h>
#include <UnigineConsole.h>
int AppWorldLogic::update()
{
// if right mouse button is clicked
if (Input::isMouseButtonDown(Input::MOUSE_BUTTON_RIGHT))
{
Math::ivec2 mouse = Input::getMousePosition();
// report mouse cursor coordinates to the console
Log::message("Right mouse button was clicked at (%d, %d)\n", mouse.x, mouse.y);
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (Input::isKeyDown(Input::KEY_Q) && !Console::isActive())
{
Engine::get()->quit();
}
return 1;
}
The following code illustrates how to use Controls class to handle keyboard input:下面的代码演示了如何使用Controls类来处理键盘输入:
#include <UnigineGame.h>
int AppWorldLogic::update()
{
// getting current controls
ControlsPtr controls = Game::getPlayer()->getControls();
// checking controls states and reporting which buttons were pressed
if (controls->clearState(Controls::STATE_FORWARD) || controls->clearState(Controls::STATE_TURN_UP))
{
Log::message("FORWARD or UP key pressed\n");
}
else if (controls->clearState(Controls::STATE_BACKWARD) || controls->clearState(Controls::STATE_TURN_DOWN))
{
Log::message("BACKWARD or DOWN key pressed\n");
}
else if (controls->clearState(Controls::STATE_MOVE_LEFT) || controls->clearState(Controls::STATE_TURN_LEFT))
{
Log::message("MOVE_LEFT or TURN_LEFT key pressed\n");
}
else if (controls->clearState(Controls::STATE_MOVE_RIGHT) || controls->clearState(Controls::STATE_TURN_RIGHT))
{
Log::message("MOVE_RIGHT or TURN_RIGHT key pressed\n");
}
return 1;
}
The following code illustrates how to use ControlsApp class to map keys and buttons to states and then to handle user input:下面的代码演示了如何使用ControlsApp类将键和按钮映射到状态,然后处理用户输入:
#include <UnigineGame.h>
int AppWorldLogic::init()
{
// remapping states to other keys and buttons
ControlsApp::setStateKey(Controls::STATE_FORWARD, Input::KEY_PGUP);
ControlsApp::setStateKey(Controls::STATE_BACKWARD, Input::KEY_PGDOWN);
ControlsApp::setStateKey(Controls::STATE_MOVE_LEFT, Input::KEY_L);
ControlsApp::setStateKey(Controls::STATE_MOVE_RIGHT, Input::KEY_R);
ControlsApp::setStateMouseButton(Controls::STATE_JUMP, Input::MOUSE_BUTTON_LEFT);
return 1;
}
int AppWorldLogic::update()
{
if (ControlsApp::clearState(Controls::STATE_FORWARD))
{
Log::message("FORWARD key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_BACKWARD))
{
Log::message("BACKWARD key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_MOVE_LEFT))
{
Log::message("MOVE_LEFT key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_MOVE_RIGHT))
{
Log::message("MOVE_RIGHT key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_JUMP))
{
Log::message("JUMP button pressed\n");
}
return 1;
}
Additional information:附加信息:强
- For more information on managing user inputs using Input class, see the Input class article.有关使用Input类管理用户输入的更多信息,请参阅Input class文章。
- For more information on managing user inputs using Controls class, see the Controls class article.有关使用Controls类管理用户输入的更多信息,请参阅Controls class文章。
- For more information on managing user inputs using ControlsApp class, see the ControlsApp class article.更多关于使用ControlsApp类管理用户输入的信息,看到ControlsApp class文章。
Creating User Interface创建用户界面#
In UNIGINE a Graphical User Interface (GUI) is composed of different types of widgets added to it. Basically, there are two ways of creating GUI:在 UNIGINE 中,图形用户界面 (GUI) 由添加到其中的不同类型的 widgets 组成。 基本上,有两种创建 GUI 的方法:
- By adding widgets to the system GUI (Unigine user interface) that is rendered on top of application window.通过添加小部件到系统GUI (Unigine用户界面),呈现在应用程序窗口的顶部。
- By adding widgets to a GUI object positioned in the world. In this case, any postprocessing filter can be applied.通过将小部件添加到位于世界中的 GUI Object。 在这种情况下,可以应用任何后处理过滤器。
To add elements to the system GUI you should use the Gui class.要向系统GUI添加元素,您应该使用Gui类。
There are 2 ways to create the GUI layout:有两种方法来创建GUI布局:
- Directly from the code via GUI-related classes直接从代码中通过 GUI 相关类
- By using User interface (UI) files通过使用 User interface (UI)文件
The following code demonstrates how to add a label and a slider to the system GUI:下面的代码演示了如何添加一个标签和一个滑块系统界面:
#include <UnigineUserInterface.h>
// injecting Unigine namespace to the global namespace
using namespace Unigine;
GuiPtr gui;
int AppWorldLogic::init()
{
// getting a GUI pointer
gui = Gui::getCurrent();
// creating a label widget and setting up its parameters
WidgetLabelPtr widget_label = WidgetLabel::create(gui, "Label text:");
widget_label->setToolTip("This is my label!");
widget_label->arrange();
widget_label->setPosition(10, 10);
// creating a slider widget and setting up its parameters
WidgetSliderPtr widget_slider = WidgetSlider::create(gui, 0, 360, 90);
widget_slider->setToolTip("This is my slider!");
widget_slider->arrange();
widget_slider->setPosition(100, 10);
gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
return 1;
}
In order to use GUI elements we must specify handlers for various events (click, change, etc.). The following code demonstrates how to set event handlers:为了使用GUI元素,我们必须为各种事件(单击、更改等)指定处理程序。下面的代码演示如何设置事件处理程序
#include <UnigineUserInterface.h>
// injecting Unigine namespace to the global namespace
using namespace Unigine;
GuiPtr gui;
/// function to be called when button1 is clicked
int onButton1Clicked()
{
/* .. */
}
/// method to be called when button2 is clicked
int AppWorldLogic::onButton2Clicked()
{
/* .. */
}
/// method to be called when delete button is clicked
int AppWorldLogic::onButtonDelClicked()
{
/* .. */
}
/// method to be called when slider position is changed
int AppWorldLogic::onSliderChanged()
{
/* .. */
}
int AppWorldLogic::init()
{
/* .. */
// getting a GUI pointer
gui = Gui::getCurrent();
// setting onButton1Clicked function as a clicked event handler for a buttonwidget1 using a CallbackBase variable
Unigine::CallbackBase* button1_clicked_callback_function = MakeCallback(onButton1Clicked);
buttonwidget1->addCallback(Gui::CLICKED, button1_clicked_callback_function);
// setting AppWorldLogic::onButton2Clicked method as a clicked event handler for a buttonwidget2 using a CallbackBase variable
Unigine::CallbackBase* button2_clicked_callback_method = MakeCallback(this, &AppWorldLogic::onButton2Clicked);
buttonwidget2->addCallback(Gui::CLICKED, button2_clicked_callback_method);
buttonwidget1->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonDelClicked));
// setting AppWorldLogic::onSliderChanged method as a changed event handler for a widget_slider
widget_slider->addCallback(Gui::CHANGED, MakeCallback(this, &AppWorldLogic::onSliderChanged));
/* .. */
return 1;
}
Additional information:附加信息:强
- For more information on UI files, see the UI files article.有关UI文件的更多信息,请参阅 UI文件文章。
- For more information on GUI-related API, see the GUI-related classes section.就API的更多信息,参见GUI-related classes部分。
- For more information on the Gui class, see the Gui class article.有关Gui类的更多信息,请参阅Gui class文章。
- For more information on handling events, see the Event Handling Callbacks article.有关处理事件的更多信息,请参阅 事件处理回调 文章。
Playing Sound and Music播放声音和音乐#
声源#
The SoundSource class is used to create directional sound sources. To create a sound source, create an instance of the SoundSource class and specify all required settings:SoundSource 类用于创建定向声源。 要创建声源,请创建 SoundSource 类的实例并指定所有必需的设置:
// create a new sound source using the given sound sample file
SoundSourcePtr sound = SoundSource::create("sound.mp3");
// disable sound muffling when being occluded
sound->setOcclusion(0);
// set the distance at which the sound gets clear
sound->setMinDistance(10.0f);
// set the distance at which the sound becomes out of audible range
sound->setMaxDistance(100.0f);
// set the gain that result in attenuation of 6 dB
sound->setGain(0.5f);
// loop the sound
sound->setLoop(1);
// start playing the sound sample
sound->play();
环境资源#
To play ambient background music create an instance of the AmbientSource class, specify all required parameters and enable it. Make sure to import the sound asset to the project.要播放环境背景音乐,创建一个AmbientSource类的实例,指定所有必需的参数并启用它。确保将声音资源导入到项目中。
// create a player so that an ambient sound source is played
PlayerSpectatorPtr player = PlayerSpectator::create();
player->setPosition(Vec3(0.0f, -3.401f, 1.5f));
player->setViewDirection(vec3(0.0f, 1.0f, -0.4f));
Game::setPlayer(player);
// create the ambient sound source
AmbientSourcePtr sound = AmbientSource::create("sound.mp3");
// set necessary sound settings
sound->setGain(0.5f);
sound->setPitch(1.0f);
sound->setLoop(1);
sound->play();
Additional information:附加信息:强
- For more information on directional sound, see the SoundSource class article.有关定向声音的更多信息,请参见SoundSource类文章。
- For more information on ambient sound, see the AmbientSource class article.有关环境声的更多信息,请参见AmbientSource类文章。
Setting Up Physics设置物理#
An object should have a body and a shape to be affected by gravity and to collide with other physical objects:object 应该有一个 body 和一个受重力影响的 shape 并与其他物理对象碰撞:
// cube
ObjectMeshStaticPtr box = ObjectMeshStatic::create("core/meshes/box.mesh");
// create a body and a shape based on the mesh
BodyRigidPtr bodyBox = BodyRigid::create(box);
ShapeBoxPtr shapeBox = ShapeBox::create(bodyBox, Math::vec3(1.0f));
Catching Nodes with World Triggers捕获节点与世界触发器#
A World trigger fires callbacks when any nodes (colliders or not) get inside or outside of them. The trigger can detect a node of any type by its bounding box. The trigger reacts to all nodes (default behavior).World trigger任何节点时触发回调(对撞机)内部或外部。任何类型的触发器可以检测到一个节点的边界框。触发器对所有节点(默认行为)。
The callback function of World Trigger is actually executed only when the next engine function is called: that is, before updatePhysics() (in the current frame) or before update() (in the next frame) - whatever comes first.World Trigger的回调函数实际上只在调用下一个引擎函数时执行:也就是说,在updatePhysics()(在当前帧中)之前或在update()(在下一帧中)之前——不管前面是什么。
The methods addEnterCallback() and addLeaveCallback() add callback functions to be fired when a node enters or leaves the World Trigger. A callback function must receive a Node as its first argument.方法addEnterCallback()和addLeaveCallback()添加回调函数,当节点进入或离开World Trigger时触发。回调函数必须接收 Node作为它的第一个参数。
// implement the enter callback
void AppWorldLogic::enter_callback(NodePtr node)
{
Log::message("\nA node named %s has entered the trigger\n", node->getName());
}
WorldTriggerPtr trigger;
int AppWorldLogic::init()
{
// create a world trigger node
trigger = WorldTrigger::create(Math::vec3(3.0f));
// add the enter callback to be fired when a node enters the world trigger
trigger->addEnterCallback(MakeCallback(this, &AppWorldLogic::enter_callback));
// add the leave callback to be fired when a node leaves the world trigger
trigger->addLeaveCallback(MakeCallback(this, &AppWorldLogic::leave_callback));
return 1;
}