I have been working on the apartment for absolutely forever! I have begun to approach this room as a vertical slice for the game. This post is about some of the features I added this week: simulated cool air mist and fluorescent light flicker.

Trigger Zibra smoke & fire simulations using adventure creator events

Realtime smoke simulations for some fridge breeze effects

As I was already using Zibra fluids in the game for realtime smoke & fire simulation, I wanted to integrate them into the Adventure Creator action list system so they could be enabled and disabled on command. I had already written the actions to enable and disable the liquid emitters, so duplicating them to act on smoke & fire emitters was a breeze.

Realtime smoke & fire simulation rendering settings to match the scene lighting
Compositing the smoke to the scenes is very easy with the Absorption color, Scattering color and Shadow Absorption color settings. Lights can also used to affect it.

The first use case for this new event was the fridge. The fridge door has been animated for a long time, and I begun to feel it would benefit from some cool air flowing out of the door. I imagined if I would add an SDF collider on the fridge door, it would create some pretty nice effects on the realtime smoke & fire simulation when the door is shut.

Realtime smoke & fire simulation SDF collision primitives wireframe view
For the colliders for the smoke sim, I used simple boxes and a capsule for the place where the player character would be standing in. One of the colliders is parented to the fridge door.

After implementing the effect, I was clearly right! Opening and closing the fridge door is insanely satisfying now. While I was adding the VFX, I also added proper sounds for opening and closing the fridge and a loop for the hum sound when the door is open. These sounds were triggered directly in the action list, instead of animation events.

Adventure Creator action list for enabling and disabling the realtime smoke & fire simulation effects on command
Custom Zibra smoke & fire integration for Adventure Creator
Action_DisableZibraSmokeEmitter.cs
/*
 *
 *	Adventure Creator
 *	by Chris Burton, 2013-2023
 *	
 *	"ActionTemplate.cs"
 * 
 *	This is a blank action template.
 * 
 */

using UnityEngine;
using System.Collections.Generic;
using com.zibra.smoke_and_fire.DataStructures;
using com.zibra.smoke_and_fire.Manipulators;
using com.zibra.common.Utilities;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace AC
{

	[System.Serializable]
	public class Action_DisableZibraSmokeEmitter : Action
	{
		
		// Declare properties here
		public override ActionCategory Category { get { return ActionCategory.Object; }}
		public override string Title { get { return "Disable Zibra smoke & fire Emitter"; }}
		public override string Description { get { return "Disables a zibra smoke & fire emitter."; }}


		// Declare variables here
		public ZibraSmokeAndFireEmitter _ZibraEmitter;

		public override float Run ()
		{
			/* 
			 * This function is called when the action is performed.
			 * 
			 * The float to return is the time that the game
			 * should wait before moving on to the next action.
			 * Return 0f to make the action instantenous.
			 * 
			 * For actions that take longer than one frame,
			 * you can return "defaultPauseTime" to make the game
			 * re-run this function a short time later. You can
			 * use the isRunning boolean to check if the action is
			 * being run for the first time, eg: 
			 */

			_ZibraEmitter.enabled = false;

			if (!isRunning)
			{
				isRunning = true;
				return defaultPauseTime;
			}
			else
			{
				isRunning = false;
				return 0f;
			}
		}


		public override void Skip ()
		{
			/*
			 * This function is called when the Action is skipped, as a
			 * result of the player invoking the "EndCutscene" input.
			 * 
			 * It should perform the instructions of the Action instantly -
			 * regardless of whether or not the Action itself has been run
			 * normally yet.  If this method is left blank, then skipping
			 * the Action will have no effect.  If this method is removed,
			 * or if the Run() method call is left below, then skipping the
			 * Action will cause it to run itself as normal.
			 */

			 Run ();
		}


#if UNITY_EDITOR

		public override void ShowGUI ()
		{
			// Action-specific Inspector GUI code here
			_ZibraEmitter = (ZibraSmokeAndFireEmitter) EditorGUILayout.ObjectField ("Emitter to enable:", _ZibraEmitter, typeof (ZibraSmokeAndFireEmitter), true);
		}
		

		public override string SetLabel ()
		{
			// (Optional) Return a string used to describe the specific action's job.
			
			return string.Empty;
		}

#endif

	}

}
Action_ActivateZibraSmokeEmitter
/*
 *
 *	Adventure Creator
 *	by Chris Burton, 2013-2023
 *	
 *	"ActionTemplate.cs"
 * 
 *	This is a blank action template.
 * 
 */

using UnityEngine;
using System.Collections.Generic;
using com.zibra.smoke_and_fire.DataStructures;
using com.zibra.smoke_and_fire.Manipulators;
using com.zibra.common.Utilities;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace AC
{

	[System.Serializable]
	public class Action_ActivateZibraSmokeEmitter : Action
	{
		
		// Declare properties here
		public override ActionCategory Category { get { return ActionCategory.Object; }}
		public override string Title { get { return "Activate Zibra Smoke & Fire Emitter"; }}
		public override string Description { get { return "Activates, or disables a zibra smoke & fire emitter."; }}


		// Declare variables here
		public ZibraSmokeAndFireEmitter _ZibraEmitter;

		public override float Run ()
		{
			/* 
			 * This function is called when the action is performed.
			 * 
			 * The float to return is the time that the game
			 * should wait before moving on to the next action.
			 * Return 0f to make the action instantenous.
			 * 
			 * For actions that take longer than one frame,
			 * you can return "defaultPauseTime" to make the game
			 * re-run this function a short time later. You can
			 * use the isRunning boolean to check if the action is
			 * being run for the first time, eg: 
			 */

			_ZibraEmitter.enabled = true;

			if (!isRunning)
			{
				isRunning = true;
				return defaultPauseTime;
			}
			else
			{
				isRunning = false;
				return 0f;
			}
		}


		public override void Skip ()
		{
			/*
			 * This function is called when the Action is skipped, as a
			 * result of the player invoking the "EndCutscene" input.
			 * 
			 * It should perform the instructions of the Action instantly -
			 * regardless of whether or not the Action itself has been run
			 * normally yet.  If this method is left blank, then skipping
			 * the Action will have no effect.  If this method is removed,
			 * or if the Run() method call is left below, then skipping the
			 * Action will cause it to run itself as normal.
			 */

			 Run ();
		}


#if UNITY_EDITOR

		public override void ShowGUI ()
		{
			// Action-specific Inspector GUI code here
			_ZibraEmitter = (ZibraSmokeAndFireEmitter) EditorGUILayout.ObjectField ("Emitter to enable:", _ZibraEmitter, typeof (ZibraSmokeAndFireEmitter), true);
		}
		

		public override string SetLabel ()
		{
			// (Optional) Return a string used to describe the specific action's job.
			
			return string.Empty;
		}

#endif

	}

}

There is really no reason other than laziness for these realtime smoke & fire simulation scripts not to be bundled into one action with a boolean for setting the emitter state. I am going to do that change at some point for sure.

Random animation clip player & play audio from animation events.

A video of the flickering light in action

As these scenes are pretty static, I am always thinking of new ideas to add motion to them. For the kitchen one pretty obvious low hanging fruit was to add flicker to the fluorescent lighting.

To make the light flicker somewhat random, but still controlled for these interesting bursts of flicker, I created a number of animation clips with different flickers and one completely without.

Flickering light random animation controller
The fluorescent light animator setup

I duplicated the idle animation a number of times in the animator to make it more likely for the light not to flicker.

Flickering light animation timeline
Flickering animation keyframes

The animations themselves are very simple. I animated the light intensity so you could see the flicker on the shaded meshes. In sync with the animated light, I enable and disable a shadow caster object that forces the background painting shadowed version to show up when the light is off. This made the light seem to affect both, the character and the AI generated 2D background.

Hand painted shadow pass on the kitchen sink for the flickering light
The normal and the shadowed version of the kitchen wall

Because I could not, using shadow masking, make the light flicker smoothly, I did not paint the shadowed version of the tile-wall too dark. When the wall was fully shadowed the effect was too jarring. A sort of half-shaded version worked best in the end and sells the flicker effect quite well.

This a similar effect to my UV animated neon lights, but instead of a shader trick animated with code, this is a more practical approach of using key frame animation to apply shadow to the background to hide lighting.

PlayClipsInRandomOrder
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayClipsInRandomOrder : MonoBehaviour
{

    private AnimationClip[] clips;
    private Animator animator;

    private void Awake()
    {
        // Get the animator component
        animator = GetComponent<Animator>();

        // Get all available clips
        clips = animator.runtimeAnimatorController.animationClips;
    }

    private void Start()
    {
        StartCoroutine(PlayRandomly());
    }

    private IEnumerator PlayRandomly()
    {
        while (true)
        {
            var randInd = Random.Range(0, clips.Length);

            var randClip = clips[randInd];

            animator.Play(randClip.name);

            // Wait until animation finished than pick the next one
            yield return new WaitForSeconds(randClip.length);
        }
    }
}

You can also see the audio playback events on the event track in the animation in the above screenshot. They are timed to trigger the audio playback a little bit before the visual effect kicks in.

Animation event for playing audio
The animation event controls

For the fluorescent sounds, I downloaded a longer clip of just someone turning an old bulb on / off. I cut it in pieces in Adobe Audition. Creating the script for the event for the audio playback in super simple. Here is the source code for my solution.

PlaySFXFromAnimationEvent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using AC;

public class PlaySFXFromAnimationEvent : MonoBehaviour
{
    public AudioSource _audioSource;

    public void PlayAudio(AudioClip _SoundEffect)
    {
        if (_SoundEffect)
        {
            Debug.Log(_SoundEffect.name);
            if (_audioSource)
            {
                _audioSource.PlayOneShot(_SoundEffect, Options.GetSFXVolume());
            }
            else
            {
                Debug.LogWarning("Audio source not set");
            }
        }
        else
        {
            Debug.LogWarning("Sound effect missing from animation event");
        }
    }
}

Spatial audio “mixing” for a 2 room setup

Once I had put in the fluorescent light audio in, you could clearly hear it in the other room as well. This was clearly not ideal. I did not want to make a complicated trigger setup for the sounds, so I decided to use the spatial audio setup for that. With a custom Rolloff I could precisely control the volume of the sound based on camera position. As there were only 2 camera positions in the scene, this was very precise!

Spatial audio settings for turning audio sources on / off based on camera position in the scene
Settings for fluorescent light sound to not play when camera is in the other room

In the screenshot you can see the camera positions (as small camera icons) and the audio range radiuses that run trough the camera positions. The smaller radius is the min range, where the audio is heard at full volume, and the larger radius is the max range, out of which the audio can nor be heard at all.

For the door, I did not completely mute the audio in the other room, but I left it so that you can hear some of the knocking sounds in the kitchen as well, in case the player decides to explore the apartment before answering the door.

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts


Archive


Social Links