编程快速参考
介绍#
在接下来的章节中,你会发现如何开始使用UNIGINE编程3D应用程序和视频游戏的基本信息。本常见问题解答包括以下几章:
基本场景对象#
对于 UNIGINE 而言,节点 是一种基本类型,所有类型的场景对象都继承自该类型。 其中一些在视觉上出现:Objects、Decals 和 Effects,它们 都有 surfaces 来表示它们的几何体(网格),而其他的(Light Sources、玩家等)是不可见的。
每个节点有一个变换矩阵的编码位置,旋转,世界上节点的规模。
所有添加到场景中的场景对象,无论其类型,都称为节点。
附加信息:
坐标系统#
Unigine中的3D空间用右手笛卡尔坐标系表示:X和Y轴形成一个水平面,Z轴指向上方 . 当 exporting 从 3D 编辑器中导出动画时,Y 被视为正向。
积极的旋转角度设置逆时针旋转。它对应于右手法则:如果你设置的右手拇指沿轴,其他手指包裹将显示旋转的方向。
附加信息:
日志和打印消息到控制台#
打印消息日志文件和控制台监控整体进度的执行应用程序和报告错误,可以用于调试。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);
附加信息:强
保存和加载一个世界#
有些应用程序管理单个世界,而其他应用程序则需要管理多个世界。在任何情况下,知道如何保存当前世界和加载其他世界是非常有用的。为了解决这个任务,我们应该使用World类,它被设计成一个单例。
#include <UnigineWorld.h>
using namespace Unigine;
/* .. */
// loading world from the my_world.world file
World::loadWorld("my_world");
我们也可以通过 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");
附加信息:强
关闭应用程序#
任何应用程序都需要在某个时刻关闭。要关闭您的应用程序,您应该使用Engine类。
要关闭应用程序,需要使用以下代码:
using namespace Unigine;
/* .. */
// closing the application
Engine::get()->quit();
在运行时创建和删除节点#
在运行时创建和删除节点几乎和在编辑器中一样简单。基本的动作集如下:
- 创建。我们应该创建一个节点为节点的类型声明一个智能指针我们要创建和调用相应的类的构造函数在必要时提供施工参数。
- 删除>
// creating a node of the NodeType named nodename
<NodeType>Ptr nodename = <NodeType>::create(<construction_parameters>);
// removing the node
nodename.deleteLater();
现在让我们以从资源加载网格为例,说明使用简单静态网格 (ObjectMeshStatic) 创建和删除节点的过程。
int AppWorldLogic::init()
{
//create an ObjectMeshStatic node from an fbx model imported to the data/fbx/ folder using the Editor
ObjectMeshStaticPtr my_object = ObjectMeshStatic::create("fbx/model.fbx/model.mesh");
// removing the node
my_object.deleteLater();
return 1;
}
附加信息:强
- 您可以创建原语通过代码使用Primitives类。
- 有关管理世界节点的更多信息,请参阅Node类的方法。
创建和设置一个相机#
一个 camera是一个对世界的视口,没有它你实际上将看不到任何东西。相机在UNIGINE使用players管理。当你添加一个新的播放器时,它会创建一个camera,并指定控件mask ,该相机的后期处理材质。
为了设置一个新球员活跃我们应该使用Game设计成一个单例类。
以下代码说明了 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;
}
附加信息:强
创建和设置光源#
照明是每个场景定义颜色和你的对象的最终外观的基础。UNIGINE中的灯的创建方式与所有节点相同。
让我们考虑建立一个世界光源为例:
#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;
}
附加信息:强
在运行时创建、应用和删除素材#
材质分配给节点的特定表面决定了节点的渲染方式。 他们实现 shaders 并控制 options、states、参数和不同类型的纹理 用于在 渲染通道 期间渲染节点。 为了管理材质,我们使用以下两个类:
可以使用下面的代码来创建一个新的材质继承了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;
}
附加信息:强
- 有关通过API创建和编辑素材的更多信息,请参阅Material类文章。
- 管理加载材质通过API的更多信息,参见Materials类文章。
- 有关材质文件格式的详细信息,请参阅 材质文件 部分。
- 有关物料参数的更多信息,请参见%UNIGINE_SDK_BROWSER_INSTALLATION_FOLDER%/sdks/%CURRENT_SDK%/data/core/materials/default/文件夹中的物料文件。
管理现有的场景对象#
不是世界上所有的内容是在运行时创建的,所以我们应该能够操作节点已经存在。我们怎么得到现有对象的指针来管理他们吗?这就是World类再次发挥作用。基本上,有两种方法我们可以得到一个指向某个节点World类的使用方法:
- getNodeByName()方法——当我们知道节点名时
- getNodeByID()方法——当我们知道节点的ID
这些方法返回一个NodePtr值,这是一个指向基类的指针,但是为了与某个对象执行操作(例如 ObjectMeshDynamicPtr)我们需要执行 的向下类型转换(即转换从pointer-to-base pointer-to-derived)。
有时你可能还需要执行 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;
有以下方法让一个组件从一个节点:
// 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);
执行基本的转换(移动、旋转、规模)#
每个节点都有一个转换矩阵,它对节点在世界中的位置、旋转和规模进行编码。如果一个节点被添加为另一个节点的子节点,则它有一个与其父节点相关的转换矩阵。这就是为什么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);
附加信息:强
- 有关矩阵变换的更多信息,请参阅矩阵变换文章。
Framerate-independent使游戏过程#
由于我们的应用程序的帧速率可能会有所不同(即 AppWorldLogic::update() 方法会或多或少地被调用 )取决于硬件,我们应该做一些事情来确保某些动作在相同的时间段内执行,而不管帧速率如何(例如每秒更改一次等)。 为了使您的游戏帧速率独立,您可以使用以下方法返回的缩放乘数(完成最后一帧所需的时间,以秒为单位):
- Engine::getIfps()返回应用程序的反FPS值。
- Game::getIfps()返回缩放的反FPS值。这个类是用来当你想加快,放慢或暂停渲染,物理或游戏逻辑。
要更改转换,您可以使用以下代码:
// 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;
}
/* .. */
执行一些变化一旦在一定的时间内你可以使用下面的代码:
#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;
}
/* .. */
附加信息:强
管理十字路口#
intersection 在3D应用中被广泛使用。在UNIGINE中有三种主要的交叉口类型:
- World intersection - objects 和 nodes 的交集。
- Physics intersection - 一个具有形状和碰撞对象的交集。
- Game intersection - 与 障碍 等寻路节点的交集。
但是有一些条件来检测与表面的相交:
- 表面是启用的。
- 该表面已指定了一种材质。
每个表面Intersection标志启用。
您可以使用Object.setIntersection()函数将该标志设置为对象的表面。
下面的代码说明了几种使用世界交点的方法:
- 找出与边界框相交的所有结点
- 找到所有节点分割的边界范围
- 找出与边界截锥相交的所有结点
- 找到第一个对象分割的射线
#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;
}
附加信息:强
- 有关交叉口的更多信息,请参阅 Intersections 文章。
- 有关通过API管理World交集的更多信息,请参阅World类文章。
- 有关通过API管理Game交集的更多信息,请参阅Game类文章。
- 有关管理的更多信息Physics路口通过API,看到Physics类文章。
获取和管理用户输入#
大多数应用程序被设计成与用户交互。在UNIGINE中,你可以使用以下类来管理用户输入:
下面的代码演示了如何使用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;
}
下面的代码演示了如何使用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;
}
下面的代码演示了如何使用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;
}
附加信息:强
- 有关使用Input类管理用户输入的更多信息,请参阅Input class文章。
- 有关使用Controls类管理用户输入的更多信息,请参阅Controls class文章。
- 更多关于使用ControlsApp类管理用户输入的信息,看到ControlsApp class文章。
创建用户界面#
在 UNIGINE 中,图形用户界面 (GUI) 由添加到其中的不同类型的 widgets 组成。 基本上,有两种创建 GUI 的方法:
- 通过添加小部件到系统GUI (Unigine用户界面),呈现在应用程序窗口的顶部。
- 通过将小部件添加到位于世界中的 GUI Object。 在这种情况下,可以应用任何后处理过滤器。
要向系统GUI添加元素,您应该使用Gui类。
有两种方法来创建GUI布局:
- 直接从代码中通过 GUI 相关类
- 通过使用 User interface (UI)文件
下面的代码演示了如何添加一个标签和一个滑块系统界面:
#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;
}
为了使用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(const WidgetPtr &button)
{
/* .. */
}
/// method to be called when button2 is clicked
int AppWorldLogic::onButton2Clicked(const WidgetPtr &button)
{
/* .. */
}
/// method to be called when delete button is clicked
int AppWorldLogic::onButtonDelClicked(const WidgetPtr &button)
{
/* .. */
}
/// method to be called when slider position is changed
int AppWorldLogic::onSliderChanged(const WidgetPtr &slider)
{
/* .. */
}
int AppWorldLogic::init()
{
/* .. */
// getting a GUI pointer
gui = Gui::getCurrent();
// subscbribing for the clicked event with the onButton1Clicked function as a handler
buttonwidget1->getEventClicked().connect(onButton1Clicked);
// subscbribing for the clicked event with the onButton2Clicked function as a handler
buttonwidget2->getEventClicked().connect(this, &AppWorldLogic::onButton2Clicked);
buttonwidget1->getEventClicked().connect(this, &AppWorldLogic::onButtonDelClicked);
// setting AppWorldLogic::onSliderChanged method as a changed event handler for a widget_slider
widget_slider->getEventChanged().connect(this, &AppWorldLogic::onSliderChanged);
/* .. */
return 1;
}
附加信息:强
- 有关UI文件的更多信息,请参阅 UI文件文章。
- 就API的更多信息,参见GUI-related classes部分。
- 有关Gui类的更多信息,请参阅Gui class文章。
- 有关处理事件的更多信息,请参阅 事件处理 文章。
播放声音和音乐#
声源#
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();
环境资源#
要播放环境背景音乐,创建一个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();
附加信息:强
- 有关定向声音的更多信息,请参见SoundSource类文章。
- 有关环境声的更多信息,请参见AmbientSource类文章。
设置物理#
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));
捕获节点与世界触发器#
World trigger任何节点时触发回调(对撞机)内部或外部。任何类型的触发器可以检测到一个节点的边界框。触发器对所有节点(默认行为)。
World Trigger的回调函数实际上只在调用下一个引擎函数时执行:也就是说,在updatePhysics()(在当前帧中)之前或在update()(在下一帧中)之前——不管前面是什么。
您可以订阅Enter 和 Leave事件,并在节点进入或离开>World Trigger时执行处理函数。回调函数必须接收 Node作为它的第一个参数。
// implement the enter event handler
void AppWorldLogic::enter_event_handler(const 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 event handler to be executed when a node enters the world trigger
trigger->getEventEnter().connect(this, &AppWorldLogic::enter_event_handler);
// add the leave event handler to be executed when a node leaves the world trigger
trigger->getEventLeave().connect(this, &AppWorldLogic::leave_event_handler);
return 1;
}