Hearts Fly — Unity VFX Graph

Imran Momin
18 min readJul 25, 2021

Have you ever wanted to make your apps look more spectacular without spending a lot of time and effort on graphics? Here, you’ll learn how to use Unity’s VFX Graph to make stunning visual effects. You’ll use simple, node-based visual logic to control complex simulations, like a flock of flying butterflies. You’ll then see how to adapt the techniques learned to a wide range of other effects you might want to create.

In this tutorial, you’ll learn how to:

  1. Create and navigate a VFX Graph
  2. Design background visual effects
  3. Spawn on-demand visual effects
  4. Control properties of visual effects as they run
  5. Author and use Point Caches

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial. To begin this project, you’ll first need to prepare the needed template and folders.

Starting With the HDRP Template

You must make several important choices when setting up a new project, so take it slowly. First, create a new project using Unity 2020.3.

The next decision is to pick a template. Select High Definition RP. This template has many of the resources and project settings you’ll use in this tutorial. The project’s name and location are up to you.

After everything loads, you’ll find a shiny, gold ball in the SampleScene. There’s some great stuff in that sample, which you’ll get to shortly. But it’s best to organize the project a bit first.

Setting up Project Folders

To manage the files and assets that you’re about to make, create some room in the project Hierarchy. First, create Assets/RW to store everything you need for the project. Next, create subfolders for the following types of assets:

  1. PointCaches: Holds the Point Cache you’ll create.
  2. Scenes: Holds the scene you’ll create.
  3. Scripts: Holds the script you’ll create.
  4. Sprites: Contains the sample art.
  5. VFX: Holds the VFX Graphs you’ll create.

Importing Sprites

Open the starter project from the materials you downloaded earlier. It contains two sprites, butterheart, and butterheartfull, that you need to create these visual effects.

Drag these sprites into the RW/Sprites folder. Once they’re imported, select each sprite and set Texture Type to Sprite (2D and UI). Next, set Read/Write Enable to True and click Apply.

Observing a Box of Butterflies

Unity now provides templates for many common scenarios and some excellent examples of visual effects. These assets are a great starting place. To find some butterflies, you don’t have to go far, because there are some real beauties right there in the SampleScene.

Finding the Butterflies

With the SampleScene open, find and select VFX ▸ ButterFlies in the Hierarchy. Switch to a Scene view and press F to focus on your selection. Do you see them? They’re pretty small, so try zooming in and panning the camera to get a closer look.

Selecting the ButterFlies highlights them and provides access to the Play Controls dialog. Experiment with the controls. You can pause the butterflies in mid-flap and make them zip by increasing the playback Rate.

The Visual Effect itself also contains controls. In the inspector for the ButterFlies, under Properties, you can change the Count, Radius and even Wing Animation Speed. Try it! Just remember to Stop and Play the Visual Effect after changing its properties to see them take effect.

Although these sample butterflies are awesome, you don’t want Unity just giving you cool stuff, right? You want to create that cool stuff yourself. So it’s time to find out how to make these butterflies. After you have a handle on that, you’ll try creating your own effects.

Examining Butterfly Art

The first thing you need to create this effect is art for the wings. Navigate to Assets/Art/Textures/VFX/ButterFlies_8x1 and select the texture to examine it in the inspector. All the butterflies you see in the scene come from this texture. And although all butterflies have two wings, you need only one of each style to create the effect.

Opening the ButterFlies VFX Graph

The second thing you need to create a visual effect is a VFX Graph Asset. In the inspector for the ButterFlies GameObject, look for the General section of the Visual Effect component. Click the Edit button to open the ButterFlies VFX Graph Asset. Maximize the window to get a better view. Some handy controls include MouseScroll to zoom and holding Alt (or Option) (or Middle Mouse Button drag) to pan the graph.

There’s a lot of complexity behind the simple beauty of a butterfly, so it’s worth a stroll through each section to understand how it all works. Before doing that, it’s helpful to understand the basic approach to this kind of animation.

Understanding a Flapping Animation

Like most complex things made using Unity, these butterflies were created by connecting and reusing smaller, simpler things. Complex visual animations are no different. For example, if you wanted to create a character clapping, you could start by making one hand that makes a clapping motion. Then, you could duplicate the arm and mirror it. And voila! A clapping character.

For this butterfly animation, you use the same technique. You start by animating a single wing that flaps. Then, you duplicate it and mirror it. What do you get? A flying butterfly!

That’s why you need only one wing for the art.

Animating a single wing is pretty straightforward. If you’ve animated a door, you’re already familiar with the approach. Pick an edge to be the axis and rotate the door around the axis. Set some limits so the door doesn’t swing too far in one direction or the other, then let it ping-pong between the limits, as needed.

So if you can swing a door in Unity, then you’re halfway to making a butterfly… well, fly!

Understanding the VFX Graph Using the Butterfly Example

Before you dive into the ButterFlies graph, copy it. Find the graph at Assets/Art/Particles/ButterFlies, then duplicate it (Control-D on Windows, Command-D on Mac), name it ButterfliesNotes, and place it in the RW/VFX folder.

This new graph asset doesn’t live in any scene, but you can still edit it. Select the graph asset and select Open in the inspector, or just double-click it.

While you explore the graph, feel free to add some notes directly to the graph as you learn what each block and node does. Add notes by right-clicking and selecting Create Sticky Note.

You can also group nodes into logical sections: Click-drag to select a group, then right-click one of the nodes, select Group Selection, and name the grouping. This is a good way to organize your VFX Graph and make it more maintainable.

Now, you’ll take a look at each of the settings you need to create the butterfly animation. After you have a good grasp of how the butterflies work, you can create your own visual effect.

Creating Particles With the Spawn Block

Start by examining the Spawn Block at the top of the graph. Zoom in to get a better look.

Each time you play, or trigger, the visual effect, it spawns a burst of particles. The number of particles it spawns depends on Count, which you can find on the Blackboard. This variable is an exposed float, which is why you were able to change it before in the SampleScene by adjusting the ButterFlies‘ properties.

This is the basic setup for controlling visual effects in real-time. The VFX Graph needs to use variables, which are exposed via the Blackboard and manipulated via the GameObjects’ Visual Effect component.

Setting an Initial Motion With the Initialize Particle Block

Immediately after spawning, each particle initializes with the settings provided in the Initialize Particle Block. In this example, there’s a limit on the system Capacity of 31. Now, no matter how many times the system triggers or how high you set the Count, no more than 31 butterflies will appear on the screen at a time. The particles are also constrained with a bounding cube of size 4.

Each butterfly spawns in a sphere with a Radius controlled by another exposed float variable in the Blackboard. So again, it’s important to note that it’s possible to create values in Properties that are outside the limitations of the graph. When tweaking values, this is the cause of many head-scratching results.

Next, each butterfly receives an initial direction and a random velocity based on that direction.

Finally, a Tex Index is set to correspond with one of the possible styles provided in the art texture, so each butterfly is a different style.

In most systems, you also set the lifetime of the particle in the Initialize Particle Block. Without that, as is the case here, the particle will live forever or until the system stops.

Now, you’ve had a look at the initial settings for the butterflies. Next, you’ll see how to change them over time.

Changing the Particles With the Update Particle Block

A visual effect that doesn’t change looks unnaturally static. To prevent this, the particle changes a little bit in every frame. You control those changes in the Update Particle Block. Take a look at this block to see how these butterflies have a natural appearance:

  • To keep the butterflies flying near the source, Force is applied, nudging the particle toward the inside of the system.
  • Turbulence adds variety.
  • The Velocity updates with logic that draws on its most recent velocity, so each butterfly can follow a unique flight path.
  • The Scale is adjusted to accommodate the original art dimensions, giving the butterflies a broader wingspan.
  • The Pivot.X creates a pivot point to use. Just like you’d assign which edge of the door should have the hinge.

Moving the Wings With the Output Particle Block

Although there are two Output Particle Blocks in this graph, one is just a mirror of the other. Think “left wing” and “right wing”, each supplied by either common or offset inputs.

Have a look a the settings in each Output Particle Block:

  • The UV Mode is set to Flipbook to take advantage of the variety provided in the wing art texture. Flip Book Size is set to match that texture, which is supplied as the Base Color Map.
  • Orientation is the same for both wings.
  • Position controls the subtle vertical lift associated with the flapping motion. It’s also the same for both wings.
  • Finally, Angle.Y and Angle.X is where things get mirrored between the left and right wings and to actually flap the wings.

Flapping Visual Logic With Wing Animation Speed

There’s one more variable in the Blackboard, called Wing Animation Speed. Each butterfly starts with a default flapping speed between 20 and 30. The Wing Animation Speed is then multiplied by the random starting value to get the actual speed.

The graph uses the age of the particle, which constantly increases, and controls the rate of increase with the modified Wing Animation Speed. The ever-increasing number is then converted to its Sine value, which ping-pongs between -1 and 1. These numbers control the bottom and top of the flap.

That sine value is used in two ways. First, it drives the small up-and-down bucking motion of the butterfly via Add Position. Second, it drives the large variation in the angle of the wing, the flap itself, via Set Angle.Y. This flap input is inversed for the second wing to create the mirrored effect.

Now that you understand a working example of a butterfly effect, it’s time to create something new.

Creating the Scene

Create a new scene using the Basic Indoors (HDRP) scene template. Call it MakingHeartsFly and save it in RW/Scenes.

In the Hierarchy, find the default Plane GameObject and delete it.

Adjusting the Camera Settings

Select the Main Camera and set its Position to (X:0, Y:1, Z:-6.5) and its Rotation to (X:-30, Y:0, Z:0). In the Background Type drop-down, select Color. Then, choose a soothing Background Color like (R:200, G:50, B:100, A:255, Intensity:0).

Sky and Fog Volume Settings

Now, you’re going to make a romantic setting for your flying hearts.

Select Sky and Fog Volume to change its settings. In the Visual Environment, set the Type to None and the Ambient Mode to Static. For the Exposure, set the Mode to Fixed and set the Fixed Exposure to -1. Make sure you enable both of those checkboxes.

Next, remove the Fog Override. Finally, add a Vignette Override and set its Mode to Procedural and its Intensity to 0.4. Be sure to enable each.

Scene Light Settings

For your next step, you’ll add some mood lighting.

Select the default Spot Light and rename it HeartLight. Next, set its Position to (X:-0.75, Y:3, Z:-5). Then, change the Light Type to Point and the Mode to Realtime.

Change the Shape Radius to 1. Finally, for Intensity, select Ev 100 in the drop-down and set the Intensity to 11. Yes, you can laugh if you want.

VFX GameObject

Now that the mood is set, it’s time to settle the butterflies!

In the VFX folder, find the ButterfliesNotes graph and duplicate it. Name the new graph asset BoxOfHearts.

In the Hierarchy, add a Visual Effects ▸ Visual Effect GameObject and call it BoxOfHearts. Next, set its Position to (X:0, Y:2, Z:-5). Then, set the Asset Template to the BoxOfHearts VFX Graph asset found in the VFX folder.

Try toggling the HeartLight on and off to see the impact of the light on the particles, and make sure it’s positioned near the top left of the screen. To see it better, try toggling the Volumetrics for the light as well.

Now that you have a new Visual Effect GameObject connected to a new VFX Graph Asset, it’s time for those butterflies to metamorphose into something new: a box of hearts!

Replacing the Wing Art

First, you’ll replace the butterflies with something more your style: butterhearts, pretty winged hearts.

In the Hierarchy, select the BoxOfHearts. Then, in the inspector, click Edit for the BoxOfHearts graph asset. To better see changes with each step, work from the bottom to the top of the graph.

Setting the Output Block

In the Output Block for both the left and right wings, change the Uv Mode to Default and Use Alpha Clipping to False. For Base Color Map, replace the default butterfly art by assigning the butterheart from the RW/Sprites folder.

Update Block

In the Update Block, remove the Turbulence and Force block and related node inputs. Next, add a Tile/Warp Positions Block and set the Size to (X:3, Y:3, Z:3).

Setting the Initialize Block

In the Initialize Block, you have several changes to make. Start by removing the Tex Index Block and related node inputs. Then, do the following:

  1. Boost the Capacity to 75
  2. Add a Set Color Block. Instead of choosing a single, boring color for all the particles, you’ll use a Gradient and some simple logic to add a lot of variety.
  3. Open the Blackboard, add a new Gradient variable and call it Wing Color. Then, set Exposed to True so you can edit the gradient in the scene.
  4. Drag the Wing Color variable from the Blackboard to somewhere close to the Set Color Block, but don’t connect them yet. To add variety, you first need to apply some randomness.
  5. Add a Sample Gradient Node and connect the Wing Color variable to the input and the output to the Set Color Block.
  6. Add a Random Number Node with (Min:0, Max:1) and Constant set to false. Then, connect that to the Time input. Now, each particle will select a random color from the gradient you provide in the GameObject.

Setting the Output Block Inspector

To make the colors pop, there’s one more setting to change. First, arrange your windows so you can see both the inspector panel and the graph panel. Next, select one of the Output Blocks and look at the inspector, which reveals a hidden trove of extra settings. Then, find the Color Mode drop-down and select Base Color and Emissive, which makes full use of the HDR colors. Repeat this step with the Output Block of the other wing.

Save the graph and close the window to return to the Game view.

Setting the Visual Effect Properties

Select the BoxOfHearts in the Hierarchy and set new Properties in the inspector. Check each property to enable it. Next, increase the Count to 75 and set the Radius to 2.

Click the Gradient to open the Gradient Editor. Setting a gradient is an art, so take some time to experiment and tune the results to your liking. These example values are set to create a broad mix of darker and lighter shades, with a few highlights of ultra-bright color.

Set the Mode to Blend and create four pins for color on the bottom of the Gradient bar at Location 15, 70, 90, 95. Use these color values for each pin:

  • 15%: (R:191, G:0, B:29, Intensity:1).
  • 70%: (R:191, G:29, B:48, Intensity:3).
  • 90%: (R:191, G:29, B:48, Intensity:3).
  • 95%: (R:191, G:10, B:29, Intensity:10).

Toggle BoxOfHearts on and off to refresh the Visual Effect with your new property values.

Return to the Game view to bask in the glow of your new BoxOfHearts.

Toggle HeartLight on and off to see the impact the light has on the color of the nearby butterhearts.

Experiment with the Exposure settings in Sky and Fog Volume to see the impact across all the butterhearts.

When you’re done experimenting, return the values to their defaults, and save the scene and the project. Your background visual effect is complete. But wouldn’t it be cool to have the effect spawn when the user performs an action? Now, it’s time to create an on-demand visual effect.

Controlling the Launch

First, create another VFX Graph asset, starting with a copy of the BoxOfHearts graph and calling it HeartOfHearts. Save it to the RW/VFX folder.

In the Hierarchy, add a Visual Effects ▸ Visual Effect GameObject and call it HeartOfHearts. Set its Position to (X:0, Y:2, Z:-5). Then, set the Asset Template to the HeartOfHearts VFX Graph asset found in the VFX folder.

Unlike the BoxOfHearts graph, you want to control when and where the HeartOfHearts graph spawns butterhearts. To control the visual effect, you need two things. First, you need a script that receives and processes input. Second, you need control variables in the VFX Graph asset that receive and apply changes to the graph.

Creating a Control Script

Inside RW/Scripts, create a new C# script named HeartManager. Next, attach that script to the HeartOfHearts GameObject.

Open the HeartManager script in your code editor, then add this declaration to the script, above the class declaration:

using UnityEngine.VFX; //for VFX graph

This lets you access VisualEffect features.

Script References
Next, just inside the class declaration, add these variables:

//1
private VisualEffect visualEffect;
//2
private VFXEventAttribute eventAttribute;
//3
private bool heartsAreFlying;
//4
private int flyingBoolID;

Here’s what this code does. It:

  1. Creates a private reference to VisualEffect, which you can use throughout the script.
  2. Declares the attribute, VFXEventAttribute, to use in the script.
  3. Creates a private variable to change the state of the visual effect.
  4. Declares a private variable, flyingBoolID, that allows the script to access the exposed variable in the graph’s Blackboard.

Initializing the Script
Next, you’ll use Awake to set up the VisualEffect component:

void Awake()
{
//1
visualEffect = GetComponent<VisualEffect>();
eventAttribute = visualEffect.CreateVFXEventAttribute();
//2
flyingBoolID = Shader.PropertyToID("Flying");
//3
heartsAreFlying = false;
//4
visualEffect.SetBool(flyingBoolID, false);
//5
visualEffect.Stop();
}

In this code, you:

  1. Cache the reference to the attached VisualEffect and the required VFXEventAttribute to access it.
  2. Configure flyingBoolID to the name, you’ll assign, in a bit, to the matching variable in the Blackboard.
  3. Initialize the variable heartsAreFlying to false so the butterhearts are resting to start
  4. Initialize the matching variable flyingBoolID in the Blackboard to the same.
  5. Stop the visual effect from playing at the start of the scene.

Adding Action Functions
With initialization complete, create a function to trigger the visual effect:

private void SpawnHearts()
{
visualEffect.Play();
}

Next, create a function to toggle the intensity of the visual effect:

private void ToggleHeartFlight()
{
//1
heartsAreFlying = !heartsAreFlying;
//2
visualEffect.SetBool(flyingBoolID, heartsAreFlying);
}

In this code, you:

  1. Reverse the private Boolean used to track the state of the visual effect.
  2. Apply the updated state to the matching variable in the graph’s Blackboard.

These are all the functions you need. Now, you just need a way to call the functions.

Tracking Input
For this tutorial, an easy way to call the functions is to track key inputs with Update:

void Update()
{
//1
if (Input.GetKeyDown(KeyCode.Space))
{
SpawnHearts();
}
//2
if (Input.GetKeyDown(KeyCode.F))
{
ToggleHeartFlight();
}
}

In this code, you listen for the user to:

  1. Press Space, then call SpawnHearts.
  2. Press F, then call ToggleHeartFlight.

You’ve now finished the script. Save it and return it to the editor.

Now that you can track inputs when playing the scene, it’s time to use that input to control the VFX Graph.

Adding Controls to the VFX Graph

First, select HeartOfHearts in the Hierarchy and click Edit to open the HeartOfHearts graph in the inspector. Make the window full screen so you have plenty of room to work.

In the Initialize Particle Block, increase the Capacity to 10,000. So many butterhearts shouldn’t fly forever, so add a Set Lifetime Random Block with (A:3, B:10).

Then, for a graceful exit, add a Set Alpha over Life Block to the Output Particle Block for both wings and set the animation curve to your liking.

To control whether the butterheart is flying or resting, add a new Boolean variable to the Blackboard and call it Flying. This variable modifies both the Wing Animation Speed and the particle velocity, so drag it onto the graph twice, close to each node it will modify.

To modify Wing Animation Speed, Flying supplies a Branch Node to choose whether to use the full or modified version of the Wing Animation Speed.

To modify the particle velocity in the Update Block, Flying supplies a Branch Node to choose whether to use the full or reduced velocity calculation. Because velocity is used elsewhere in the graph, avoid setting the velocity to zero to avoid division errors.

[TODO: Eric — when I ran through the tutorial using the Unity template in the latest 2020, the sample graph at the above point was slightly different. Is the screenshot above enough for the reader to see the changes required, now that they’ve been introduced to all the elements? Or do we need to explain further?]

Save the graph and return to the Game view.

Select HeartOfHearts in the Hierarchy and set up its Properties in the inspector. Check each property to enable it. Set the Count to 500. Copy the gradient you used for BoxOfHearts.

Save the scene, click Play and test your new controls.

Next, you’ll play with the shape your butterhearts use when they spawn.

Customizing Spawn Shapes

Spawning from a sphere works, but it would be more interesting to use a Point Cache to control where particles spawn. In this case, you’ll make them appear in the shape of a heart.

To create a Point Cache, navigate to Window ▸ Visual Effects ▸ Utilities ▸ Point Cache Bake Tool. Set the Bake Mode to Texture, then select butterheartfull from the RW/Sprites folder. Set the Threshold to 0.9. Then, save the new file to RW/PointCaches and name it butterheartfull.

Open the HeartOfHearts graph and add a Point Cache Node. Then, assign the butterheartfull asset from the PointCaches folder.

In the Initialize Particle Block, replace the Add Position (Sphere) Block with a Set Position from Map Block. Then, connect the Point Cache Node to the Attribute Map input and set the Value Scale to (X:2, Y:2, Z:1).

Note: Make sure the Set Position from Map block is at the top of the Initialize Particle block. Otherwise, you may see a warning in the console, and no butterhearts on screen!

Because the graph is no longer uses Radius, remove it from the Blackboard.

In the Update Particle Block, also remove the Tile/Warp Positions Block.

Save the graph, then return to the Game view.

Make sure you’ve enabled the HeartLight and the BoxOfHearts.

Now, save the scene and the project.

Click Play and experiment with the input keys to trigger new flocks of butterhearts and toggle their active state. You’re now making hearts fly with Unity VFX Graph!

--

--

Imran Momin

A VR/AR developer, who enjoys making games and developing interactive environments using Unity’s XR integration toolkit for Oculus quest and HTC vive devices.