This page has been translated automatically.
UNIGINE 基础课程
1. 简介
2. 虚拟世界管理
3. 3D模型准备
4. 材质
5. 摄像机和光照系统
6. 实现应用程序逻辑
7. 制作过场动画与动画序列
8. 准备发布项目
9. 物理系统
10. 优化基础
12. 项目3:第三人称越野街机赛车游戏。简介
13. 项目4:带有简单交互的 VR 应用程序

添加视觉效果

Brief Overview of Particle Systems
粒子系统概述#

Visual effects play an important role in enhancing the realism of the images generated by 3D applications, be it a simulator or a game. To simulate various phenomena such as fire, smoke, explosions, electric sparks, fountains, jet engine plumes, wake waves, magic and many others, particle systems are extensively used in 3D graphics. These effects are crucial in creating believable images that captivate the audience.在3D应用程序(无论是模拟器还是游戏)中,视觉效果对于提升画面真实感起着至关重要的作用。粒子系统被广泛应用于3D图形中,用于模拟各种现象,如火焰、烟雾、爆炸、电火花、喷泉、发动机尾焰、尾流、魔法效果等。这些效果对于创造引人入胜的逼真画面至关重要。

Particles can be material points, elementary sprites, triangles, small polygons, or even geometric shapes. Typically, the system releases particles at random points within a predetermined volume, such as a sphere, cylinder, or cone, or from a single point in various directions. The system determines the lifetime of a particle, and destroys it when that time runs out.粒子可以是质点、基本精灵图、三角形、小型多边形,甚至是几何形状。通常,系统会在预定体积(如球体、圆柱体或圆锥体)内的随机点,或从单一点沿不同方向发射粒子。系统会设定粒子的生命周期,并在时间耗尽时销毁它。

Each particle in the system dynamically changes its coordinates based on a specific algorithm, such as the law of universal gravitation. Additionally, particles are not static and may alter both their position and shape over time.系统中的每个粒子都会根据特定算法(如万有引力定律)动态改变坐标。此外,粒子并非静态存在,其位置和形状都可能随时间变化。

Fire and smoke simulated by using particles systems使用粒子系统模拟的火焰与烟雾效果

A particle system in UNIGINE consists of three main entities:UNIGINE中的粒子系统由三个主要实体组成:

  • Emitter – the source that emits particles according to the values set in the Emitter parameters.发射器:根据Emitter参数设定的值发射粒子的源头
  • Particles themselves, which are emitted according to the predefined behavior after emission.粒子本身:按照预定义的发射后行为规则运动
  • Additional physical effects applied to the particles that affect their behavior.附加物理效果:影响粒子行为的额外物理作用

To create the desired effects in our game, we will need a flash and smoke effect for when a shot is fired, as well as a bullet impact effect that leaves a trace on the hit surface. Both effects will use the particle system, and the latter effect will also include a decal projection to simulate the impact. We'll start with creating a simplified version of the flash and smoke effect manually before using the ready-made assets.要在游戏中创建所需的效果,我们需要实现射击时的闪光和烟雾效果,以及子弹命中表面后留下痕迹的碰撞效果。这两个效果都将使用粒子系统实现,且后者还会包含用于模拟撞击的贴花投影。我们将先手动创建简化版的闪光和烟雾效果,然后再使用现成的资源。

Creating a Simple Effect Template
创建简单效果模板#

The shot effect will have two components — flash and smoke, and we'll use a separate particle system for each. Let's start with the flash:射击效果将包含两个组成部分:闪光和烟雾,我们将为每个部分使用独立的粒子系统。先从闪光开始:

  1. Let's add a new particle system to the scene (Create -> Particle System -> Particles) and call it flash.向场景添加新的粒子系统(Create -> Particle System -> Particles)并命名为flash
  2. The created system emits particles continuously, but for the flash we need to do it once, so set the Spawn Rate to 0. Now we need to turn off the node in the World Hierarchy and turn it on to see what the flash will look like.创建的系统会持续发射粒子,但闪光只需发射一次,因此将Spawn Rate(生成速率)设为0。现在我们需要在World Hierarchy(世界层级)中关闭再开启该节点来查看闪光效果。
  3. We don't want our single particle imitating the flash to move anywhere after it appears, we only need it increase in size. The particle behavior after emission is controlled by the Behavior After Emission group parameters. There we will set Velocity to 0 so that the particle does not move anywhere, and the Increase In Radius value — to 1.5.我们不希望模拟闪光的单个粒子在出现后移动,只需它逐渐扩大。粒子发射后的行为由Behavior After Emission(发射后行为)参数组控制。将Velocity(速度)设为0使粒子保持静止,Increase In Radius(半径增长)值设为1.5。
  4. Let's check what the effect looks like — turn off and on the flash node. It's already better, but it's too slow for a flash, so we need to speed up the effect. To do this, reduce Life Time to 0.1.检查效果——关闭再开启flash节点。效果有所改善但闪光速度过慢,需缩短Life Time(生命周期)至0.1来加速效果。
  5. Now you only need to replace the material — in the Surface Material group assign the data/fps/particles/materials/gun_flash_0_mat.mat material.最后替换材质:在Surface Material(表面材质组)中指定data/fps/particles/materials/gun_flash_0_mat.mat材质。

The flash is ready, now let's add some smoke. It's done as follows:闪光效果完成,现在添加烟雾效果。操作如下:

  1. Clone the first particle system and name this copy smoke.克隆第一个粒子系统并将副本命名为smoke
  2. Assign the data/fps/particles/materials/gun_smoke_gray_mat.mat material to it.为其指定data/fps/particles/materials/gun_smoke_gray_mat.mat材质。
  3. To synchronize several particle systems, we need to combine them into a hierarchy (the Duration value of the parent particle system must be big enough to enclose Duration and Delay intervals of all child particle systems). So, to make smoke automatically appear when flash is activated, add smoke as a child node for flash.要同步多个粒子系统,需将它们组织为层级结构(父粒子系统的Duration(持续时间)需足够长以包含所有子系统的DurationDelay)。因此将smoke添加为flash的子节点,实现闪光触发时自动生成烟雾。

  4. In our case everything is simple, each of the systems generates one particle, but we need to make the smoke appear with a slight delay and last a little longer. For this, we'll set the Delay parameter in the Emission group equal to 0.1, and Life Time — to 0.2. For Increase In Radius, set the value to 0.3.本案例中每个系统只生成一个粒子,但需使烟雾略微延迟出现且持续时间更长。为此在Emission(发射)组中将Delay(延迟)参数设为0.1,Life Time(生命周期)设为0.2。Increase In Radius(半径增长)值设为0.3。
  5. The last step is to drag the flash node from World Hierarchy to Asset Browser and rename the resulting asset to gun_fire.node. Now it is ready to be used for the fire effect (the node itself can be removed from the scene).最后将flash节点从世界层级拖拽到资源浏览器,重命名生成资源为gun_fire.node。现在该资源可用于开火效果(场景中的原节点可删除)。

Implementing the Muzzle Flash and Hit Effect
实现枪口闪光与命中效果#

Visual effects for shooting can be implemented in a separate component. You can get information about the hit point and spawn a NodeReference representing the hit effect at this point oriented along the hit normal. For the muzzle flash, you can attach a NodeDummy to the muzzle of the pistol, and spawn a muzzle flash NodeReference at this position.射击的视觉效果可以在独立组件中实现。你可以获取命中点的信息,并在该位置生成一个代表命中效果的节点引用(NodeReference),使其沿命中法线方向对齐。对于枪口闪光,可以在手枪枪口附加一个虚拟节点(NodeDummy),并在该位置生成枪口闪光的节点引用(NodeReference)。

In the component code below, the onHit() and onShoot() methods implement this logic.在下面的组件代码中,onHit()onShoot()方法实现了这一逻辑。

  1. Create the VFXController component and copy the code below.创建VFXController组件并复制以下代码:

    VFXController.h

    源代码 (C++)
    #pragma once
    #include <UnigineComponentSystem.h>
    class VFXController :
        public Unigine::ComponentBase
    {
    public:
    	COMPONENT_DEFINE(VFXController, Unigine::ComponentBase);
    	// // 用于射击闪光和命中效果的节点引用
    	PROP_PARAM(File, hitPrefab, NULL);
    	PROP_PARAM(File, muzzleFlashPrefab, NULL);
    
    	// 射击事件处理器
    	void onShoot(Unigine::Math::Mat4 transform);
    	// 命中事件处理器
    	void onHit(Unigine::Math::Vec3 hitPoint, Unigine::Math::vec3 hitNormal, Unigine::ObjectPtr hitObject);
    };

    VFXController.cpp

    源代码 (C++)
    #include "VFXController.h"
    REGISTER_COMPONENT(VFXController);
    using namespace Unigine;
    using namespace Math;
    
    void VFXController::onShoot(Unigine::Math::Mat4 transform)
    {
    	// 如果未指定闪光效果节点,则不执行操作
    	if (!muzzleFlashPrefab)
    		return;
    
    	// 加载闪光效果节点
    	NodePtr muzzleFlashVFX = World::loadNode(FileSystem::guidToPath(FileSystem::getGUID(muzzleFlashPrefab.getRaw())));
    	// 设置闪光位置为枪口指定坐标
    	muzzleFlashVFX->setWorldTransform(transform);
    }
    
    void VFXController::onHit(Unigine::Math::Vec3 hitPoint, Unigine::Math::vec3 hitNormal, Unigine::ObjectPtr hitObject)
    {
    	// 如果未指定命中效果节点,则不执行操作
    	if (!hitPrefab)
    		return;
    
    	// 加载命中效果节点
    	NodePtr hitVFX = World::loadNode(FileSystem::guidToPath(FileSystem::getGUID(hitPrefab.getRaw())));
    	// 设置节点到命中点并沿法线方向对齐
    	hitVFX->setParent(hitObject);
    	hitVFX->setWorldPosition(hitPoint);
    	hitVFX->setWorldDirection(hitNormal, vec3_up, Math::AXIS_Y);
    }
  2. Modify the WeaponController component in order to use logic of VFXController.修改WeaponController组件以使用VFXController的逻辑。

    WeaponController.h

    源代码 (C++)
    #pragma once
    #include <UnigineComponentSystem.h>
    #include <UnigineVisualizer.h>
    #include "ShootInput.h"
    
    ${#HL}$ #include "VFXController.h" ${HL#}$
    class WeaponController :
    	public Unigine::ComponentBase
    {
    public:
    	COMPONENT_DEFINE(WeaponController, Unigine::ComponentBase);
    
    	PROP_PARAM(Node, shooting_camera, nullptr);
    	PROP_PARAM(Node, shoot_input_node, nullptr);
    
    ${#HL}$
    	PROP_PARAM(Node, weapon_muzzle, nullptr);
    	PROP_PARAM(Node, vfx_node, nullptr);
    
    	VFXController* vfx; ${HL#}$
    
    	Unigine::PlayerDummyPtr shootingCamera = nullptr;
    	ShootInput *shootInput = nullptr;
    	int damage = 1;
    
    	// 定义子弹可命中物体的碰撞检测遮罩
    	int mask = ~0;
    
    	// 声明在程序执行流程各阶段调用的方法
    	COMPONENT_INIT(init);
    	COMPONENT_UPDATE(update);
    
    	void shoot();
    
    protected:
    	// 世界主循环重写方法
    	void init();
    	void update();
    };

    WeaponController.cpp

    源代码 (C++)
    #include "WeaponController.h"
    REGISTER_COMPONENT(WeaponController);
    using namespace Unigine;
    using namespace Math;
    
    void WeaponController::shoot()
    {
    ${#HL}$
    	if (weapon_muzzle)
    		vfx->onShoot(weapon_muzzle->getWorldTransform()); ${HL#}$
    
    	// 设置射线起点(p0)为摄像机位置,终点(p1)为摄像机前方100单位处
    	Vec3 p0 = shootingCamera->getWorldPosition();
    	Vec3 p1 = shootingCamera->getWorldPosition() + (Vec3)shootingCamera->getWorldDirection() * 100;
    
    	// 创建碰撞法线存储对象
    	WorldIntersectionNormalPtr hitInfo = WorldIntersectionNormal::create();
    	// 获取(p0,p1)射线第一个碰撞物体
    	Unigine::ObjectPtr hitObject = World::getIntersection(p0, p1, mask, hitInfo);
    	// 如果检测到碰撞
    	if (hitObject)
    	{
    		// 使用可视化工具在碰撞点渲染表面法线
    		Visualizer::renderVector(hitInfo->getPoint(), hitInfo->getPoint() + (Vec3)hitInfo->getNormal(), vec4_red, 0.25f, false, 2.0f);
    ${#HL}$
    		// 在碰撞点生成命中特效
    		vfx->onHit(hitInfo->getPoint(), hitInfo->getNormal(), hitObject); ${HL#}$
    	}
    }
    
    void WeaponController::init()
    {
    	// 获取已分配ShootInput组件的摄像机对象
    	shootingCamera = checked_ptr_cast<Unigine::PlayerDummy>(shooting_camera.get());
    
    	// 从'shoot_input_node'节点获取ShootInput组件实例
    	shootInput = ComponentSystem::get()->getComponent<ShootInput>(shoot_input_node.get());
    
    ${#HL}$
    	// 从'vfx_node'节点获取VFXController组件实例
    	vfx = Unigine::ComponentSystem::get()->getComponent<VFXController>(vfx_node.get()); ${HL#}$
    }
    
    void WeaponController::update()
    {
    	// 处理用户输入:检查"开火"按钮是否被按下
    	if (shootInput->isShooting())
    		shoot();
    }

    Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System update properties used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.保存所有修改过的文件,然后按Ctrl + F5编译并运行应用程序,使组件系统更新用于将组件分配给节点的属性。运行后关闭应用程序并切换回UnigineEditor。

  3. Add the VFXController property to the player Dummy Object.VFXController组件添加到playerDummy Object
  4. Now add a point where the shoot effect will be visualized. Create a NodeDummy, call it muzzle, make it a child of the pistol Skinned Mesh, and place it near the end of the weapon muzzle.现在添加射击效果的显示点。创建一个NodeDummy,命名为muzzle,使其成为pistol (Skinned Mesh)的子对象,并将其放置在武器枪口末端附近。

  5. Select the player Dummy Object, assign the muzzle node to the Weapon Muzzle field in the WeaponController section.选中player Dummy Object,在WeaponController部分的Weapon Muzzle字段中分配muzzle节点。
  6. Assign the player Dummy Object to the Vfx Node field in the WeaponController section.在WeaponController部分的Vfx Node字段中分配player Dummy Object

  7. Add the data/fps/bullet/bullet_hit.node asset to the Hit Prefab field of the VFXController section.data/fps/bullet/bullet_hit.node资源添加到VFXController部分的Hit Prefab字段。
  8. Add the gun_fire.node asset that we prepared earlier to the Muzzle Flash Prefab field.将我们之前准备的gun_fire.node资源添加到Muzzle Flash Prefab字段。

  9. Now you can press Play and test the shooting visual effects.现在你可以按Play键测试射击视觉效果。

VFX Lifetime
视觉效果生命周期管理#

With each shot, a new node is generated to indicate where the bullet hit. You can fire quite a lot of shots during the game. And it could be times more if we had a machine gun! We need to be mindful of performance and avoid unnecessary resource usage. Therefore, we should regularly delete nodes that are no longer critical for gameplay, such as the trace effect on walls after some time.每次射击都会生成新节点来标记子弹命中点。游戏中可能产生大量射击效果,若是机枪射击则更甚。我们必须关注性能表现,避免不必要的资源消耗。因此需要定期删除那些对游戏进程不再关键的效果节点,比如墙面上的弹痕在经过一段时间后应当消失。

To control the duration of visual effects, you can add the Lifetime component that will allow you to define a time interval for the node during which it will live and after which it will be deleted. Here's the code for this component:通过添加Lifetime组件,你可以为节点定义存活时间间隔,到期后自动删除节点。以下是该组件的实现代码:

LifeTime.h

源代码 (C++)
#pragma once
#include <UnigineComponentSystem.h>
class LifeTime :
	public Unigine::ComponentBase
{
public:
	COMPONENT_DEFINE(LifeTime, Unigine::ComponentBase);
	
	// 生命周期时长
	PROP_PARAM(Float, lifeTime, 1.0f);

	// 声明在程序执行流程各阶段调用的方法
	COMPONENT_INIT(init);
	COMPONENT_UPDATE(update);

protected:
	float startTime = 0.0f;
	// 世界主循环重写方法
	void init();
	void update();
};

LifeTime.cpp

源代码 (C++)
#include "LifeTime.h"
#include <UnigineGame.h>
REGISTER_COMPONENT(LifeTime);
using namespace Unigine;

void LifeTime::init()
{
	// 记录对象初始化时间
	startTime = Game::getTime();
}
void LifeTime::update()
{
	// 等待生命周期结束后删除对象
	if (Game::getTime() - startTime > lifeTime)
		node.deleteLater();
}

Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System update properties used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.保存所有修改过的文件,然后按下Ctrl + F5编译并运行应用程序,使组件系统更新用于将组件分配给节点的属性。运行完成后关闭应用程序并切换回UnigineEditor。

Add the Lifetime component to the bullet_hit.node and gun_fire.node NodeReferences.将Lifetime组件添加到bullet_hit.nodegun_fire.node这两个NodeReferences上。

  • For the bullet_hit.node, the Life Time parameter is set to 1 second.对于bullet_hit.node,将Life Time参数设置为1

  • For the gun_fire.node, the Life Time parameter is set to 5 seconds.对于gun_fire.node,将Life Time参数设置为5

本页面上的信息适用于 UNIGINE 2.20 SDK.

最新更新: 2025-06-20
Build: ()