This page has been translated automatically.
Unigine Basics
1. Introduction
2. Managing Virtual Worlds
3. Preparing 3D Models
4. Materials
5. Cameras and Lighting
6. Implementing Application Logic
7. Making Cutscenes and Recording Videos
8. Preparing Your Project for Release
9. Physics
10. Optimization Basics
12. PROJECT3: Third-Person Cross-Country Arcade Racing Game
13. PROJECT4: VR Application With Simple Interaction
注意! 这个版本的文档是过时的,因为它描述了一个较老的SDK版本!请切换到最新SDK版本的文档。
注意! 这个版本的文档描述了一个不再受支持的旧SDK版本!请升级到最新的SDK版本。

Adding Visual Effects

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.

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:

  • Emitter – the source that emits particles according to the values set in the Emitter parameters.
  • 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.
  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.
  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.
  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.
  5. Now you only need to replace the material — in the Surface Material group assign the data/particles/materials/gun_flash_0_mat.mat material.

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.
  2. Assign the data/particles/materials/gun_smoke_gray_mat.mat material to it.
  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.

  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.
  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).

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.

In the component code below, the OnHit() and OnShoot() methods implement this logic.

  1. Create the VFXController.cs component and copy the code below.

    VFXController.cs

    Source code (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    #region Math Variables
    #if UNIGINE_DOUBLE
    using Vec3 = Unigine.dvec3;
    using Mat4 = Unigine.dmat4;
    #else
    using Vec3 = Unigine.vec3;
    using Mat4 = Unigine.mat4;
    #endif
    #endregion
    [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- identifier is generated automatically for a new component
    public class VFXController : Component
    {
        // NodeReference for hit and muzzle flash visualization
        [ParameterFile(Filter = ".node")]
        public string hitPrefab = null;
    
        [ParameterFile(Filter = ".node")]
        public string muzzleFlashPrefab = null;
    
        public void OnShoot(Mat4 transform)
        {
       	 // if the flash effect NodeReference is not specified, we do nothing
       	 if (string.IsNullOrEmpty(muzzleFlashPrefab))
       		 return;
    
       	 // load the NodeReference for muzzle flash visualization
       	 Node muzzleFlashVFX = World.LoadNode(muzzleFlashPrefab);
       	 // set the muzzle flash node transformation to the pistol muzzle coordinates
       	 muzzleFlashVFX.WorldTransform = transform;
        }
    
        public void OnHit(Vec3 hitPoint, vec3 hitNormal, Unigine.Object hitObject)
        {
       	 // if no hit node is specified, do nothing
       	 if (string.IsNullOrEmpty(hitPrefab))
       		 return;
    
       	 // load the node for hit visualization from the file
       	 Node hitVFX = World.LoadNode(hitPrefab);
       	 // place the loaded node in the hit point and set its direction according to the hit normal vector
       	 hitVFX.Parent = hitObject;
       	 hitVFX.WorldPosition = hitPoint;
       	 hitVFX.SetWorldDirection(hitNormal, vec3.UP, MathLib.AXIS.Y);
        }
    }
  2. Modify the WeaponController.cs component in order to use logic of VFXController.cs.

    WeaponController.cs

    Source code (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    #region Math Variables
    #if UNIGINE_DOUBLE
    using Vec3 = Unigine.dvec3;
    #else
    using Vec3 = Unigine.vec3;
    #endif
    #endregion
    
    [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- identifier is generated automatically for a new component
    public class WeaponController : Component
    {
        public PlayerDummy shootingCamera = null;
        public ShootInput shootInput = null;
        public NodeDummy weaponMuzzle = null;
        ${#HL}$ public VFXController vfx = null; ${HL#}$
        public int damage = 1;
    
        // Intersection mask to sort out which objects can be hit by a bullet
        [ParameterMask(MaskType = ParameterMaskAttribute.TYPE.INTERSECTION)]
        public int mask = ~0;
    
        public void Shoot()
        {
    ${#HL}$ // visualize the shoot effect
       	 if (weaponMuzzle)
       		 vfx.OnShoot(weaponMuzzle.WorldTransform); ${HL#}$
       	 // set the line starting point (p0) in the camera position 
                // and end point (p1) in the point 100 units away in the camera view direction
       	 Vec3 p0 = shootingCamera.WorldPosition;
       	 Vec3 p1 = shootingCamera.WorldPosition + shootingCamera.GetWorldDirection()  * 100;
    
       	 // create an object to store the intersection normal
       	 WorldIntersectionNormal hitInfo = new WorldIntersectionNormal();
       	 // get the first object intersected by the (p0,p1) line
       	 Unigine.Object hitObject = World.GetIntersection(p0, p1, mask, hitInfo);
    
       	 // if the intersection is found
       	 if (hitObject)
       	 {
       		 // render the intersection normal to the surface in the hit point using Visualizer
       		 Visualizer.RenderVector(hitInfo.Point, hitInfo.Point + hitInfo.Normal, vec4.RED, 0.25f, false, 2.0f);
    ${#HL}$ 	 // visualize the hit effect in the intersection point
       		 vfx.OnHit(hitInfo.Point, hitInfo.Normal, hitObject); ${HL#}$
       	 }
        }
    
        private void Update()
        {
       	 // handle user input: check if the fire button is pressed
       	 if (shootInput.IsShooting())
       		 Shoot();
        }
    }
  3. Add the VFXController.cs component to the player Dummy Node.
  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.

  5. Select the player Dummy Node, assign the muzzle node to the Weapon Muzzle field in the WeaponController section.
  6. Assign the player Dummy Node to the Vfx field in the WeaponController section.

  7. Add the data/fps/bullet/bullet_hit.node asset to the Hit Prefab field of the VFXController section.
  8. Add the gun_fire.node asset that we prepared earlier to the Muzzle Flash Prefab field.

  9. Now you can press Play and test the shooting visual effects.

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.cs 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.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- identifier is generated automatically for a new component
public class Lifetime : Component
{
    [ShowInEditor][Parameter(Tooltip = Object's lifetime (in seconds))]
    private float lifeTime = 1.0f;

    private float startTime = 0.0f;

    void Init()
    {
   	 // remember initialization time of an object
   	 startTime = Game.Time;
    }

    void Update()
    {
   	 // wait until the lifetime ends and delete the object
   	 if (Game.Time - startTime > lifeTime)
   		 node.DeleteLater();
    }
}

This component is already added to the bullet_hit.node and gun_fire.node NodeReferences:

  • For the bullet_hit.node, the Life Time parameter is set to 1 second.

  • For the gun_fire.node, the Life Time parameter is set to 5 seconds.

Last update: 2024-04-04
Build: ()