This page has been translated automatically.
Основы UNIGINE
1. Введение
2. Виртуальные миры и работа с ними
3. Подготовка 3D моделей
4. Материалы
5. Камеры и освещение
6. Реализация логики приложения
7. Создание кат-сцен и запись видео
8. Подготовка проекта к релизу
9. Физика
10. Основы оптимизации
12. ПРОЕКТ3: Аркадная гонка по пересеченной местности от 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 приложения, будь то симулятор или игра , вносят всевозможные визуальные эффекты. Для моделирования огня, дыма, взрывов, электрических искр, фонтанов, шлейфов от реактивных двигателей, кильватерного следа, магии и многих других эффектов, без которых сложно себе представить правдоподобную картинку,, в трехмерной графике используются системы частиц.

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 у родительской системы частиц должно покрывать все интервалы Duration и Delay дочерних систем частиц). Итак, чтобы при активации вспышки автоматически появлялся и дым – добавьте 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.В нашем случае все просто, каждая из систем генерирует по одной частице, но нужно, чтобы дым появлялся с небольшой задержкой и длился чуть дольше, для этого установим параметр Delay в группе Emission равным 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 из World Hierarchy в Asset Browser и переименуйте получившийся ассет в 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);
    	// NodeReference для эффектов вспышки выстрела и попадания
    	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)
    {
    	// если не задан NodeReference эффекта вспышки, ничего не делаем
    	if (!muzzleFlashPrefab)
    		return;
    
    	// загружаем NodeReference эффекта выстрела
    	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);
    
    	Unigine::NodeDummyPtr weaponMuzzle;
    	VFXController* vfx; ${HL#}$
    
    	Unigine::PlayerDummyPtr shootingCamera = nullptr;
    	ShootInput *shootInput = nullptr;
    	int damage = 1;
    
    	// маска Intersection чтобы определить, в какие объекты могут попадать пули
    	int mask = ~0;
    
    	// регистрация методов, вызываемых на соответствующих этапах World Logic
    	COMPONENT_INIT(init);
    	COMPONENT_UPDATE(update);
    
    	void shoot();
    
    protected:
    	// объявление методов, вызываемых на соответствующих этапах World Logic
    	void init();
    	void update();
    };

    WeaponController.cpp

    Исходный код (C++)
    #include "WeaponController.h"
    REGISTER_COMPONENT(WeaponController);
    using namespace Unigine;
    using namespace Math;
    
    void WeaponController::shoot()
    {
    ${#HL}$
    	if (weaponMuzzle)
    		vfx->onShoot(weaponMuzzle->getWorldTransform()); ${HL#}$
    
    	// задаем начало отрезка (p0) в позиции камеры и конец (p1) - в точке удаленной на 100 единиц в направлении взгляда камеры
    	Vec3 p0 = shootingCamera->getWorldPosition();
    	Vec3 p1 = shootingCamera->getWorldPosition() + shootingCamera->getWorldDirection() * 100;
    
    	// создаем объект для хранения intersection-нормали
    	WorldIntersectionNormalPtr hitInfo = WorldIntersectionNormal::create();
    	// ищем первый объект, который пересекает отрезок (p0, p1)
    	Unigine::ObjectPtr hitObject = World::getIntersection(p0, p1, mask, hitInfo);
    	// если пересечение найдено
    	if (hitObject)
    	{
    		// отрисовываем нормаль к поверхности в точке попадания при помощи Visualizer
    		Visualizer::renderVector(hitInfo->getPoint(), hitInfo->getPoint() + hitInfo->getNormal(), vec4_red, 0.25f, false, 2.0f);
    ${#HL}$
    		// генерируем в точке попадания NodeReference визуального эффекта
    		vfx->onHit(hitInfo->getPoint(), hitInfo->getNormal(), hitObject); ${HL#}$
    	}
    }
    
    void WeaponController::init()
    {
    	// получаем камеру, которой назначен компонент ShootInput
    	shootingCamera = checked_ptr_cast<Unigine::PlayerDummy>(shooting_camera.get());
    }
    
    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.Сохраните все файлы, в которые мы внесли изменения, а затем соберить и запустите приложение, нажав в IDE Ctrl + F5, чтобы Компонентная система сгенерировала property для связи компонента с нодой. После запуска приложения закройте его и вернитесь в UnigineEditor.

  3. Add the VFXController property to the player Dummy Object.Добавьте компонент VFXController.cs к Dummy Object player.
  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.Выберите Dummy Object player, назначьте ноду muzzle полю Weapon Muzzle в разделе WeaponController.
  6. Assign the player Dummy Object to the Vfx Node field in the WeaponController section.Назначьте Dummy Object player полю Vfx Node в разделе WeaponController.

  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 в поле Hit Prefab раздела VFXController.
  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);

	// регистрация методов, вызываемых на соответствующих этапах World Logic
	COMPONENT_INIT(init);
	COMPONENT_UPDATE(update);

protected:
	float startTime = 0.0f;
	// объявление методов, вызываемых на соответствующих этапах World Logic
	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.Сохраните все файлы, в которые мы внесли изменения, а затем соберить и запустите приложение, нажав в IDE Ctrl + F5, чтобы Компонентная система сгенерировала property для связи компонента с нодой. После запуска приложения закройте его и вернитесь в UnigineEditor.

Add the Lifetime component to the bullet_hit.node and gun_fire.node NodeReferences.Добавьте свойство(property) в шаблоны (NodeReferences) bullet_hit.node и gun_fire.node.

  • 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 секундам.

Последнее обновление: 13.12.2024
Build: ()