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

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/fps/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/fps/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")] // <-- this line is generated automatically for a new component
    public class VFXController : Component
    {
    	// NodeReference for flash and hit effects of a shot
    	[ParameterFile(Filter = ".node")]
    	public string hitPrefab = null;
    
    	[ParameterFile(Filter = ".node")]
    	public string muzzleFlashPrefab = null;
    
    	public void OnShoot(Mat4 transform)
    	{
    		// if no flash effect NodeReference is specified, do nothing
    		if (string.IsNullOrEmpty(muzzleFlashPrefab))
    			return;
    
    		// load NodeReference shot effect
    		Node muzzleFlashVFX = World.LoadNode(muzzleFlashPrefab);
    		// set the flash position to the specified coordinates of the gun's muzzle
    		muzzleFlashVFX.WorldTransform = transform;
    	}
    
    	public void OnHit(Vec3 hitPoint, vec3 hitNormal, Unigine.Object hitObject)
    	{
    		// if the node of the hit effect isn't specified, do nothing
    		if (string.IsNullOrEmpty(hitPrefab))
    			return;
    
    		// load the hit effect node from the file
    		Node hitVFX = World.LoadNode(hitPrefab);
    		// set the uploaded node to the specified hit point and deploy it in the direction of normal vector
    		hitVFX.Parent = hitObject;
    		hitVFX.WorldPosition = hitPoint;
    		hitVFX.SetWorldDirection(hitNormal, vec3.UP, MathLib.AXIS.Y);
    	}
    }
  2. Modify the WeaponController component in order to use logic of VFXController.

    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")] // <-- this line is generated automatically for a new component
    public class WeaponController : Component
    {
    	public PlayerDummy shootingCamera = null;
    	public ShootInput shootInput = null;
    ${#HL}$	public NodeDummy weaponMuzzle = null;
    	public VFXController vfx = null; ${HL#}$
    	public int damage = 1;
    	// Intersection mask to define which objects bullets can hit
    	[ParameterMask(MaskType = ParameterMaskAttribute.TYPE.INTERSECTION)]
    	public int mask = ~0;
    
    	public void Shoot()
    	{
    ${#HL}$		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 intersection-normal storage object
    		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}$			// render 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")] // <-- this line 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-12-13
Build: ()