This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
基础
专业(SIM)
UnigineEditor
界面概述
资源工作流程
Version Control
设置和首选项
项目开发
调整节点参数
Setting Up Materials
设置属性
照明
Sandworm
使用编辑器工具执行特定任务
如何擴展編輯器功能
嵌入式节点类型
Nodes
Objects
Effects
Decals
光源
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
C++
C#
UnigineScript
统一的Unigine着色器语言 UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
材质和着色器
Rebuilding the Engine Tools
GUI
双精度坐标
应用程序接口
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
Tutorials
注意! 这个版本的文档是过时的,因为它描述了一个较老的SDK版本!请切换到最新SDK版本的文档。
注意! 这个版本的文档描述了一个不再受支持的旧SDK版本!请升级到最新的SDK版本。

使用C++组件系统

The C++ Component System enables you to implement your application's logic via a set of building blocks - components, and assign these blocks to nodes. A logic component integrates a node, a property, and a C++ class containing logic implementation.C++ 组件系统 使您能够通过一组构建块,组件,实现应用程序的逻辑,并分配这些块 到节点。逻辑组件集成了节点、属性和包含逻辑实现的 C++ 类。

This example demonstrates how to:此示例演示如何:

  • Decompose application logic into building blocks.将应用程序逻辑分解为构建块
  • Create your own logic components.创建自己的逻辑组件
  • Implement interaction between your components.实现组件之间的交互

Let's make a simple game to demonstrate how the whole system works.让我们做一个简单的游戏来演示整个系统是如何工作的。

See Also
请参阅#

This case is also provided as an SDK sample.此案例也作为SDK示例提供。

Game Description
游戏介绍#

In the center of the play area, we are going to have an object (Pawn) controlled by the player via the keyboard. It has certain amount of HP and movement parameters (movement and rotation speed).在游戏区域的中心,我们将有一个由玩家通过键盘控制的对象,棋子(Pawn)。 它具有一定数量的HP和运动参数(运动和旋转速度)。

Four corners of the play area are occupied by rotating objects (Spinner) that throw other small objects (Projectile) in all directions while rotating.游戏区域的四个角被旋转对象,旋转器(Spinner)占据,这些旋转器在旋转时向所有方向投掷其他小对象,弹丸(Projectile)。

Each Projectile moves along a straight line in the directon it has been initially thrown by the Spinner. If a Projectile hits a Pawn, the latter takes damage according to the value set for the hitting Projectile (each of them has a random speed and damage value). The pawn is destroyed if the amount of HP drops below zero.每个弹丸在它最初被旋转器抛出的方向上沿着一条直线移动。 如果一个弹丸击中棋子,后者根据为击中的弹丸设置的值(它们中的每一个都有一个随机的速度和伤害值)受到伤害。 如果HP量降至零以下,则棋子将被销毁。

We use boxes for simplicity, but you can easily replace them with any other objects.为了简单起见,我们使用立方体,但您可以将它们替换为任何其他对象。

The basic workflow for implementing application logic using the C++ Component System is given below.下面给出了使用C++组件系统实现应用程序逻辑的基本工作流程。

1. Prepare a Project
1. 准备项目#

Before we can start creating components and implementing our game logic, we should create a new C++ project.在开始创建组件和实现游戏逻辑之前,我们应该创建一个新的C++项目

2. Decompose Application Logic into Building Blocks
2. 将应用程序逻辑分解为构建块#

First of all, we should decompose our application logic in terms of building blocks - components. So, we should define parameters for each component (all these parameters will be stored in a corresponding *.prop file) and decide in which functions of the execution sequence the component's logic will be implemented.首先,我们应该根据构建块(组件)来分解应用程序逻辑。因此,我们应该为每个组件定义参数(所有这些参数将存储在相应的*.prop文件中),并决定在执行序列的哪些函数中实现组件的逻辑。

For our small game, we are going to use one component for each type of object. Thus, we need 3 components:对于我们的小游戏,我们将为每种类型的对象使用一个组件。 因此,我们需要3组件:

  • Pawn with the following parameters:具有以下参数的Pawn:

    • name - name of the Pawnname 棋子的名称
    • moving speed - how fast the Pawn movesmoving speed 棋子移动的速度有多快
    • rotation speed - how fast the Pawn rotatesrotation speed 棋子旋转的速度有多快
    • health - HP count for the Pawnhealth 棋子的HP计数

    We are going to initialize the Pawn, do something with it each frame, and report a message, when the Pawn dies. Therefore, this logic will be implemented inside the init(), update(), and shutdown() methods.我们将初始化棋子,每帧对它做一些事情,并在棋子死亡时报告一条消息。 因此,该逻辑将在 init(), update()shutdown() 方法内部实现。

  • Spinner with the following parameters:具有以下参数的Spinner:

    • rotation speed - how fast the Spinner rotatesrotation speed 旋转器旋转的速度有多快
    • acceleration - how fast Spinner's rotation rate increasesacceleration 旋转器旋转速率增加的速度有多快
    • node to be used as a projectile要用作弹丸的节点
    • minimum spawn delay最小生成延迟
    • maximum spawn delay最大生成延迟

    We are going to initialize a Spinner and do something with it each frame. Therefore, this logic will go to the init() and update().我们将初始化旋转器,并在每个帧中使用它做一些事情。 因此,该逻辑将转到init()update()

  • Projectile with the following parameters:具有以下参数的Projectile:

    • speed - how fast the Projectile movesspeed 弹丸移动的速度有多快
    • lifetime - how long the Projectile liveslifetime 弹丸寿命多长
    • damage - how much damage the Projectile causes to the Pawn it hitsdamage 弹丸对其击中的棋子造成多少伤害

    As for the projectile, it will be spawned and initialized by the Spinner. The only thing we are going to do with it, is checking for a hit and controlling the life time left every frame. All of this goes to update().至于弹丸,它将由旋转器产生和初始化。 我们要用它做的唯一的事情就是检查一个命中并控制每一帧留下的生命时间。 所有这些都转到update()

3. Create a C++ Class for Each Component
3. 为每个组件创建一个C++类#

For each of our components, we should derive a new C++ class from the ComponentBase class. Therefore, we should do the following in the header file:对于我们的每个组件,我们应该从ComponentBase类派生一个新的C++类。 因此,我们应该在头文件中执行以下操作:

  • Declare a component class. The name of the class is used as the name of the property (the corresponding *.prop file is automatically saved in your project's data folder).声明一个组件类。 类的名称用作属性的名称(相应的*.prop文件会自动保存在项目的data文件夹中)。

    To declare a class, use the following:要声明类,请使用以下内容:

    源代码 (C++)
    COMPONENT_DEFINE(my_property_name, Unigine::ComponentBase);
  • Declare all parameters defined above with their default values (if any).声明上面定义的所有参数及其默认值(如果有的话)。

    To declare a parameter we can use the following macros (optional parameters are enclosed in square brackets []):要声明参数,我们可以使用以下宏(可选参数用方括号[]括起来):

    源代码 (C++)
    PROP_PARAM(type, name[, default_value, title, tooltip, group]);
    PROP_STRUCT(type, name);
    PROP_ARRAY(type, name);
    PROP_ARRAY_STRUCT(type, name);
  • Declare which methods we are going to use to implement our logic, and during which stages of the execution sequence to call them:声明我们将使用哪些方法来实现我们的逻辑,以及在执行序列的哪些阶段调用它们:

    源代码 (C++)
    public:
    	COMPONENT_INIT(my_init);
    	COMPONENT_UPDATE(my_update);
    	COMPONENT_SHUTDOWN(my_shutdown);
    // ...
    protected:
    	void my_init();
    	void my_update();
    	void my_shutdown();
  • Declare all necessary auxiliary parameters and functions.声明所有必要的辅助参数和函数。

Thus, for our Pawn, Spinner, and Projectile classes, we will have the following declarations:因此,对于我们的Pawn, SpinnerProjectile类,我们将有以下声明:

Pawn.h

源代码 (C++)
#pragma once
#include <UnigineGame.h>
#include <UnigineControls.h>

// include the header file of the Component System
#include <UnigineComponentSystem.h>

using namespace Unigine;
using namespace Math;

// derive our class from the ComponentBase
class Pawn : public ComponentBase
{
public:
	// declare constructor and destructor for our class and define a property name. The Pawn.prop file containing all parameters listed below will be saved in your project's data folder
	COMPONENT_DEFINE(Pawn, ComponentBase)
		// declare methods to be called at the corresponding stages of the execution sequence
		COMPONENT_INIT(init);
	COMPONENT_UPDATE(update);
	COMPONENT_SHUTDOWN(shutdown);

	// parameters
	PROP_PARAM(String, name, "Pawn1");		// Pawn's name
	PROP_PARAM(Int, health, 200);			// health points
	PROP_PARAM(Float, move_speed, 4.0f);	// move speed (meters/s)
	PROP_PARAM(Float, turn_speed, 90.0f);	// turn speed (deg/s)

	// methods
	void hit(int damage);	// decrease Pawn's HP 

protected:
	// world main loop overrides
	void init();
	void update();
	void shutdown();

private:
	// auxiliary parameters and functions
	ControlsPtr controls;
	PlayerPtr player;

	float damage_effect_timer = 0;
	Mat4 default_model_view;

	void show_damage_effect();
};

Spinner.h

源代码 (C++)
#pragma once
#include <UnigineMaterial.h>
#include <UnigineComponentSystem.h>

using namespace Unigine;
using namespace Math;

class Spinner : public ComponentBase
{
public:
	// declare constructor and destructor for the Spinner class
	COMPONENT_DEFINE(Spinner, ComponentBase);

	// declare methods to be called at the corresponding stages of the execution sequence
	COMPONENT_INIT(init);
	COMPONENT_UPDATE(update);

	// parameters
	PROP_PARAM(Float, turn_speed, 30.0f);
	PROP_PARAM(Float, acceleration, 5.0f);

	PROP_PARAM(Node, spawn_node);
	PROP_PARAM(Float, min_spawn_delay, 1.0f);
	PROP_PARAM(Float, max_spawn_delay, 4.0f);

protected:
	// world main loop
	void init();
	void update();

private:
	float start_turn_speed = 0;
	float color_offset = 0;
	float time_to_spawn = 0;
	MaterialPtr material;

	// converter from HSV to RGB color model
	vec3 hsv2rgb(float h, float s, float v);
};

Projectile.h

源代码 (C++)
#pragma once
#include <UnigineMaterial.h>
#include <UnigineComponentSystem.h>

using namespace Unigine;

class Projectile : public ComponentBase
{
public:
	// declare constructor and destructor for the Projectile class
	COMPONENT(Projectile, ComponentBase);

	// declare methods to be called at the corresponding stages of the execution sequence
	COMPONENT_UPDATE(update);
	PROP_NAME("Projectile");

	// parameters
	PROP_PARAM(Float, speed, 5.0f);
	PROP_PARAM(Float, lifetime, 5.0f);	// life time of the projectile (declaration with a default value)
	PROP_PARAM(Int, damage);			// damage caused by the projectile (declaration with no default value)

	// methods
	void setMaterial(const MaterialPtr &mat);

protected:
	// world main loop
	void update();
};

4. Implement Each Component's Logic
4. 实现每个组件的逻辑#

After making necessary declarations, we should implement logic for all our components. Let's do it in the corresponding *.cpp files.在进行必要的声明后,我们应该为所有组件实现逻辑。 让我们在相应的*.cpp文件中进行。

In each of these files, we should add the following macro to ensure automatic registration of the corresponding component by the Component System:在每个文件中,我们应该添加以下宏,以确保组件系统自动注册相应的组件:

源代码 (C++)
REGISTER_COMPONENT ( your_component_name );
警告
Do not put this macro to header (*.h) files, otherwise your project won't be built!不要将此宏放到头文件(*.h)中,否则您的项目将无法构建!

Pawn's Logic
典当的逻辑#

The Pawn's logic is divided into the following elements:Pawn的逻辑分为以下元素:

  • Initialization - here we set necessary parameters, and the Pawn reports its name:初始化:这里我们设置必要的参数,Pawn报告其名称:

    源代码 (C++)
    Log::message("PAWN: INIT \"%s\"\n", name.get());
    注意
    You can access parameters of your component via: <parameter_name>.get()您可以通过 <parameter_name>.get() 访问组件的参数
  • Main loop - here we implement the player's keyboard control.主循环:这里我们实现玩家的键盘控制。

    注意

    To access the node from the component, we can simply use node, e.g. to get the current node's direction we can write:要从组件访问节点,我们就可以使用node,例如获取我们可以写入的当前节点的方向:

    源代码 (C++)
    Vec3 direction = node->getWorldTransform().getColumn3(1);
  • Shutdown - here we implement actions to be performed when a Pawn dies. We'll just print a message to the console.关闭:这里我们实现当棋子死亡时要执行的操作。 我们只需打印一条消息到控制台。
  • Auxiliary - a method to be called when the pawn is hit, and some visual effects.辅助:当棋子被击中时调用的方法,以及一些视觉效果。

Implementation of the Pawn's logic is given below:Pawn逻辑的实现如下:

Pawn.cpp

源代码 (C++)
#include "Pawn.h"
#include <UnigineConsole.h>
#include <UnigineRender.h>
REGISTER_COMPONENT(Pawn);		// macro for component registration by the Component System
#define DAMAGE_EFFECT_TIME 0.5f

void Pawn::init()
{
	player = Game::getPlayer();
	controls = player->getControls();

	default_model_view = player->getCamera()->getModelview();
	damage_effect_timer = 0;
	show_damage_effect();

	Log::message("PAWN: INIT \"%s\"\n", name.get());
}

void Pawn::update()
{
	// get delta time between frames
	float ifps = Game::getIFps();

	// show damage effect
	if (damage_effect_timer > 0)
	{
		damage_effect_timer = Math::clamp(damage_effect_timer - ifps, 0.0f, DAMAGE_EFFECT_TIME);
		show_damage_effect();
	}

	// if console is opened we disable any controls
	if (Console::isActive())
		return;

	// get the direction vector of the mesh from the second column (y axis) of the transformation matrix
	Vec3 direction = node->getWorldTransform().getColumn3(1);

	// checking controls states and changing pawn position and rotation
	if (controls->getState(Controls::STATE_FORWARD) || controls->getState(Controls::STATE_TURN_UP))
	{
		node->setWorldPosition(node->getWorldPosition() + direction * move_speed * ifps);
	}

	if (controls->getState(Controls::STATE_BACKWARD) || controls->getState(Controls::STATE_TURN_DOWN))
	{
		node->setWorldPosition(node->getWorldPosition() - direction * move_speed * ifps);
	}

	if (controls->getState(Controls::STATE_MOVE_LEFT) || controls->getState(Controls::STATE_TURN_LEFT))
	{
		node->setWorldRotation(node->getWorldRotation() * quat(0.0f, 0.0f, turn_speed * ifps));
	}

	if (controls->getState(Controls::STATE_MOVE_RIGHT) || controls->getState(Controls::STATE_TURN_RIGHT))
	{
		node->setWorldRotation(node->getWorldRotation() * quat(0.0f, 0.0f, -turn_speed * ifps));
	}
}

void Pawn::shutdown()
{
	Log::message("PAWN: DEAD!\n");
}

// method to be called when the Pawn is hit by a Projectile
void Pawn::hit(int damage)
{
	// take damage
	health = health - damage;

	// show effect
	damage_effect_timer = DAMAGE_EFFECT_TIME;
	show_damage_effect();

	// destroy
	if (health <= 0)
		node.deleteLater();

	Log::message("PAWN: damage taken (%d) - HP left (%d)\n", damage, health.get());
}

// auxiliary method implementing visual damage effect
void Pawn::show_damage_effect()
{
	float strength = damage_effect_timer / DAMAGE_EFFECT_TIME;
	Render::setFadeColor(vec4(0.5f, 0, 0, saturate(strength - 0.5f)));
	player->getCamera()->setModelview(default_model_view * Mat4(
		rotateX(Game::getRandomFloat(-5, 5) * strength) *
		rotateY(Game::getRandomFloat(-5, 5) * strength)));
}

Projectile's Logic
弹丸的逻辑#

The Projectile's logic is simpler - we just have to perform a check each frame whether we hit the Pawn or not. This means that we have to access the Pawn component from the Projectile component.弹丸的逻辑更简单-我们只需要每帧执行一个检查是否击中棋子。 这意味着我们必须从Projectile组件访问Pawn组件。

注意

To access certain component on a certain node (e.g. the one that was intersected in our case) we can write the following:要访问某个节点上的某个组件(例如在我们的例子中相交的那个),我们可以编写以下内容:

源代码 (C++)
// get the component assigned to a node by type "MyComponent"
MyComponent *my_component = getComponent<MyComponent>(some_node);

// access some method of MyComponent
my_component->someMyComponentMethod();

The Projectile has a limited life time, so we should destroy the node when its life time is expired.弹丸的生命周期是有限的,所以我们应该在弹丸生命周期结束时销毁该节点。

注意
To destroy a node, to which a component is added, simply call: node.deleteLater(). The node will be destroyed with all its properties and components.要销毁添加组件的节点,只需调用 node.deleteLater()。该节点连同其所有属性和组件将被销毁。

Implementation of the Projectile's logic is given below:下文给出了弹丸逻辑的实现:

Projectile.cppProjectile.cpp

源代码 (C++)
#include "Projectile.h"
#include "Pawn.h"
#include "Spinner.h"
#include <UnigineGame.h>
#include <UnigineWorld.h>

REGISTER_COMPONENT(Projectile);		// macro for component registration by the Component System

void Projectile::update()
{
	// get delta time between frames
	float ifps = Game::getIFps();

	// get the direction vector of the mesh from the second column (y axis) of the transformation matrix
	Vec3 direction = node->getWorldTransform().getColumn3(1);

	// move forward
	node->setWorldPosition(node->getWorldPosition() + direction * speed * ifps);

	// lifetime
	lifetime = lifetime - ifps;
	if (lifetime < 0)
	{
		// destroy current node with its properties and components

		node.deleteLater();
		return;
	}

	// check the intersection with nodes
	VectorStack<NodePtr> nodes; // VectorStack is much faster than Vector, but has some limits
	World::getIntersection(node->getWorldBoundBox(), nodes);
	if (nodes.size() > 1) // (because the current node is also in this list)
	{
		for (int i = 0; i < nodes.size(); i++)
		{
			Pawn *pawn = getComponent<Pawn>(nodes[i]);
			if (pawn)
			{
				// hit the player!
				pawn->hit(damage);

				// ...and destroy current node

				node.deleteLater();
				return;
			}
		}
	}
}

void Projectile::setMaterial(const MaterialPtr &mat)
{
	checked_ptr_cast<Object>(node)->setMaterial(mat, "*");
}

Spinner's Logic
旋转器的逻辑#

The Spinner's logic is divided into the following elements:旋转器的逻辑分为以下元素:

  • Initialization - here we set necessary parameters to be used in the main loop.初始化:这里我们设置必要的参数在主循环中使用。
  • Main loop - here we rotate our Spinner and spawn nodes with Projectile components. We also set some parameters of the Projectile.主循环:在这里,我们旋转我们的旋转器并生成分配了弹丸组件的节点。 我们还设置了弹丸的一些参数。

    There are 3 ways to change variables of another component:种方法可以更改另一个组件的变量:

    1. Directly via component (fast, easy)直接通过组件(快速,简单)

      源代码 (C++)
      component->int_parameter = component->int_parameter + 1;
    2. Via node's property (slower, more awkward)通过节点的属性(比较慢,比较不方便的)

      源代码 (C++)
      for (int i = 0; i < node->getNumProperties(); i++)
      {
      	PropertyPtr prop = node->getProperty(i);
      	if (prop && (!strcmp(prop->getName(), "my_prop_name") || prop->isParent("my_prop_name")))
      		prop->setParameterInt(prop->findParameter("int_parameter"), 5);
      }
    3. Via component's property通过组件的属性

      源代码 (C++)
      PropertyPtr prop = component->getProperty();
      prop->setParameterInt(prop->findParameter("int_parameter"), 5);
  • Auxiliary - color conversion function.辅助:颜色转换功能。

Implementation of the Spinner's logic is given below:下面给出了旋转器逻辑的实现:

Spinner.cpp

源代码 (C++)
#include "Spinner.h"
#include "Projectile.h"
#include <UnigineGame.h>
#include <UnigineEditor.h>

REGISTER_COMPONENT(Spinner);		// macro for component registration by the Component System

void Spinner::init()
{
	// get current material (from the first surface)
	ObjectPtr obj = checked_ptr_cast<Object>(node);

	if (obj && obj->getNumSurfaces())
		material = obj->getMaterialInherit(0);

	// init randoms
	time_to_spawn = Game::getRandomFloat(min_spawn_delay, max_spawn_delay);
	color_offset = Game::getRandomFloat(0, 360.0f);
	start_turn_speed = turn_speed;
}

void Spinner::update()
{
	// rotate spinner
	float ifps = Game::getIFps();
	turn_speed = turn_speed + acceleration * ifps;
	node->setRotation(node->getRotation() * quat(0, 0, turn_speed * ifps));

	// change color
	int id = material->findParameter("albedo_color");
	if (id != -1)
	{
		float hue = Math::mod(Game::getTime() * 60.0f + color_offset, 360.0f);
		material->setParameterFloat4(id, vec4(hsv2rgb(hue, 1, 1), 1.0f));
	}

	// spawn projectiles
	time_to_spawn -= ifps;
	if (time_to_spawn < 0 && spawn_node)
	{
		// reset timer and increase difficulty
		time_to_spawn = Game::getRandomFloat(min_spawn_delay, max_spawn_delay) / (turn_speed / start_turn_speed);

		// create node
		NodePtr spawned = spawn_node->clone();
		spawned->setEnabled(1);
		spawned->setWorldTransform(node->getWorldTransform());

		// don't destroy it on quitting the current scope
		// note: if you plan to add some components to this object
		// you have to use release() method and transfer ownership of this
		// object to the Editor

		// create component
		Projectile *proj_component = addComponent<Projectile>(spawned);

		// there are three ways to change variables inside another component:
		// 1) direct change via component (fast, easy)
		proj_component->speed = Game::getRandomFloat(proj_component->speed * 0.5f, proj_component->speed * 1.5f);

		// 2) change via property of the node (more slow, more awkward)
		for (int i = 0; i < spawned->getNumProperties(); i++)
		{
			PropertyPtr prop = spawned->getProperty(i);
			if (prop && (!strcmp(prop->getName(), "Projectile") || prop->isParent("Projectile")))
				prop->getParameterPtr("damage")->setValueInt(Game::getRandomInt(75, 100));
		}

		// 3) change via property of the component
		PropertyPtr proj_property = proj_component->getProperty();
		proj_property->getParameterPtr("lifetime")->setValueFloat(Game::getRandomFloat(5.0f, 10.0f));

		// call public method of another component
		proj_component->setMaterial(material);
	}
}

// color conversion H - [0, 360), S,V - [0, 1]
vec3 Spinner::hsv2rgb(float h, float s, float v)
{
	float p, q, t, fract;

	h /= 60.0f;
	fract = h - Math::floor(h);

	p = v * (1.0f - s);
	q = v * (1.0f - s * fract);
	t = v * (1.0f - s * (1.0f - fract));

	if (0.0f <= h && h < 1.0f) return vec3(v, t, p);
	else if (1.0f <= h && h < 2.0f) return vec3(q, v, p);
	else if (2.0f <= h && h < 3.0f) return vec3(p, v, t);
	else if (3.0f <= h && h < 4.0f) return vec3(p, q, v);
	else if (4.0f <= h && h < 5.0f) return vec3(t, p, v);
	else if (5.0f <= h && h < 6.0f) return vec3(v, p, q);
	else return vec3(0, 0, 0);
}

5. Register Components in the Component System
5. 在组件系统中注册组件#

Now we have all our game logic implemented in the corresponding components: Pawn, Spinner, and Projectile. There is one more thing to be done before we can start using them. We should register our components in the Component System.现在我们已经在相应的组件中:Pawn, SpinnerProjectile,实现了所有的游戏逻辑。 在我们开始使用它们之前,还有一件事要做。 我们应该在组件系统中注册我们的组件。

All components having the REGISTER_COMPONENT macro declared in their implementation files (*.cpp), are registered automatically by the Component System upon its initialization, just add a single line to the AppSystemLogic::init() method:所有在其实现文件(*.cpp)中声明REGISTER_COMPONENT宏的组件,在初始化时由组件系统自动注册,只需在AppSystemLogic::init()方法中添加一行即可:

源代码 (C++)
#include <UnigineComponentSystem.h>

/* ... */

int AppSystemLogic::init()
{
	/* ... */
	
	// initialize ComponentSystem and register all components
	Unigine::ComponentSystem::get()->initialize();
	
	/* ... */
}

Voila! All our components are registered at once!瞧!我们所有的组件都是一次性注册的!

6. Add Components to Nodes
6. 向节点添加组件#

As we implemented our game logic in the components and registered them in the System, we can actually start using them. There are two ways to add a logic component to a node:当我们在组件中实现我们的游戏逻辑并在系统中注册它们时,我们实际上可以开始使用它们。 有两种方法可以将逻辑组件添加到节点:

  • By simply assigning the corresponding property to it via UnigineEditor or code:通过 UnigineEditor 或代码为其分配相应的属性:

    源代码 (C++)
    object1->addProperty("MyComponentProperty");
    object2->setProperty(0, "MyComponentProperty");
  • By calling the corresponding method of the Component System:调用组件系统的相应方法:

    源代码 (C++)
    ComponentSystem::get()->addComponent<MyComponent>(object);

Here is the resulting code for our game including registration of components as well as adding them to nodes:这是我们游戏的总代码,包括组件的注册以及将它们添加到节点:

game.cpp

源代码 (C++)
#include <UnigineEngine.h>
#include <UnigineLights.h>
#include <UnigineWidgets.h>

#include <UnigineComponentSystem.h>
#include "Spinner.h"
#include "Projectile.h"
#include "Pawn.h"

using namespace Unigine;
using namespace Math;
//////////////////////////////////////////////////////////////////////////
// System logic class
//////////////////////////////////////////////////////////////////////////

class AppSystemLogic : public SystemLogic
{
public:
	AppSystemLogic() {}
	virtual ~AppSystemLogic() {}
	virtual int init()
	{
		// initialize ComponentSystem and register all components
		Unigine::ComponentSystem::get()->initialize();

		// run in background
		Engine::get()->setBackgroundUpdate(Engine::BACKGROUND_UPDATE_RENDER_NON_MINIMIZED);

		// load world
		World::loadWorld("cs");

		return 1;
	}
};

//////////////////////////////////////////////////////////////////////////
// World logic class
//////////////////////////////////////////////////////////////////////////

class AppWorldLogic : public WorldLogic
{
public:
	AppWorldLogic() {}
	virtual ~AppWorldLogic() {}
	virtual int init()
	{
		// create static camera
		player = PlayerDummy::create();

		player->setPosition(Vec3(17.0f));
		player->setDirection(vec3(-1.0f), vec3(0.0f, 0.0f, 1.0f));
		Game::setPlayer(player);

		// create light
		sun = LightWorld::create(vec4_one);

		sun->setName("Sun");
		sun->setWorldRotation(Math::quat(45.0f, 30.0f, 300.0f));

		// Note: objects are added to the world when created

		// create objects
		ObjectMeshDynamicPtr obj[4];
		obj[0] = create_box(translate(Vec3(-16.0f, 0.0f, 0.0f)), vec3(1.0f));
		obj[1] = create_box(translate(Vec3(16.0f, 0.0f, 0.0f)), vec3(1.0f));
		obj[2] = create_box(translate(Vec3(0.0f, -16.0f, 0.0f)), vec3(1.0f));
		obj[3] = create_box(translate(Vec3(0.0f, 16.0f, 0.0f)), vec3(1.0f));

		// there are two ways to create components on nodes:
		// 1) via component system
		ComponentSystem::get()->addComponent<Spinner>(obj[0]);
		ComponentSystem::get()->addComponent<Spinner>(obj[1]);

		// 2) via property
		obj[2]->addProperty("Spinner");
		obj[3]->setProperty(0, "Spinner");

		// set up spinners (set "spawn_node" variable)
		ObjectMeshDynamicPtr projectile_obj = create_box(Mat4_identity, vec3(0.15f));
		projectile_obj->setEnabled(0);
		for (int i = 0; i < 4; i++)
			ComponentSystem::get()->getComponent<Spinner>(obj[i])->spawn_node = projectile_obj;

		// create player
		ObjectMeshDynamicPtr my_pawn_object = create_box(translate(Vec3(1.0f, 1.0f, 0.0f)), vec3(1.3f, 1.3f, 0.3f));
		my_pawn = ComponentSystem::get()->addComponent<Pawn>(my_pawn_object);
		my_pawn->setDestroyCallback(MakeCallback(this, &AppWorldLogic::my_pawn_destroyed));
		time = 0;

		// create info label
		label = WidgetLabel::create(Gui::getCurrent());
		label->setPosition(10, 10);
		label->setFontSize(24);
		label->setFontOutline(1);
		Gui::getCurrent()->addChild(label, Gui::ALIGN_OVERLAP);

		return 1;
	}

	virtual int update()
	{
		// increase time while player is alive
		if (my_pawn)
			time += Game::getIFps();

		// show game info
		label->setText(String::format(
			"Player:\n"
			"Health Points: %d\n"
			"Time: %.1f sec\n"
			"\n"
			"Statisics:\n"
			"Components: %d",
			(my_pawn ? my_pawn->health.get() : 0),
			time,
			ComponentSystem::get()->getNumComponents()).get());

		return 1;
	}

private:
	Pawn* my_pawn{ nullptr };	// Pawn player
	float time = 0;
	WidgetLabelPtr label;
	PlayerDummyPtr player;
	LightWorldPtr sun;

	void my_pawn_destroyed()
	{
		my_pawn = nullptr;
	}
	// method creating a box
	ObjectMeshDynamicPtr create_box(const Mat4 &transform, const vec3 &size)
	{
		MeshPtr mesh = Mesh::create();
		mesh->addBoxSurface("box", size);

		ObjectMeshDynamicPtr object = ObjectMeshDynamic::create(1);
		object->setMesh(mesh);
		object->setWorldTransform(transform);

		return object;
	}
};

//////////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////////

int main(int argc, char **argv)
{
	// init engine
	Unigine::Engine::InitParameters init_params;
	init_params.window_title = "UNIGINE Engine: Component System Usage Example (C++)";
	Unigine::EnginePtr engine(init_params, argc, argv);

	// enter main loop
	AppWorldLogic world_logic;
	AppSystemLogic system_logic;
	engine->main(&system_logic, &world_logic, NULL);

	return 0;
}
最新更新: 2024-08-16
Build: ()