How we play effects via code

As with every game, effects are the bread and butter of what makes the gameplay feel satisfying and rewarding. Whether it's an exploding barrel that blasts your opponent away, or a laser beam that's emitted by the trap you just shot. We tried to make the actions in the game as responsive and satisfying and possible and are always happy to improve the existing effects to spice them up!

Playing effects via code is always a bit bothersome, because it's hard to find a clean way to play shared effects. We re-use effects for multiple occasions, for example the explosion effect is shared by the barrel, the mine and the missile.

The same goes with audio: Attaching an audio source to every GameObject and then having to switch clips or something is just messy if you have to repeatedly do it for a lot of objects.

So what we did to solve this is create a handler for all types of effects, whether of the visual or audio type.

Visual Effects

We created an enum for all the effects that can be played. That way, every object that wants to play an effect can just pass that enum to the method that actually plays the effect and be done with it. This also solves the issue of making typos while playing effects, since enums are compiled to integers.

The method that actually plays the effect then fetches the object and instantiates it at the desired position. Additionally, we calculate the maximum lifetime of the effect, based on the particle system information, and destroy it after that time.

But wait - isn't it really inefficient to only load an object once its needed? Couldn't that cause slight delays? Well yes it can! That's why we have a method to preload all the visual effects inside the enum mentioned above. This ensures that all the effects are already in memory and don't have to be loaded from the resources.

But there is one more thing to optimize: Instantiating and destroying objects has a pretty large overhead in Unity. Recycling the effects and re-using them is much more efficient, hence why we implemented a pooling system. This can not only be used for the effects, but for all the objects in the game that are repeatedly instantiated and destroyed, for example the projectiles or the hexes.

Audio Effects

Admittedly, we already saved a huge deal of work with the audio implementation, since we are using the Wwise sound engine. But playing events through it can be a bit messy, especially if you want your audio logic not located inside your objects, but rather have the objects just state what sound effect they want to play, without bothering how to actually play it.

We again created an enum for this case, but this time we decorated the items with attributes containing the identifier of the correlating Wwise event name. So the item Explosion would have an attribute containing the identifier Play_E_Explosion. When the effect manager receives the call to play the explosion effect, it extracts the identifier from its attribute and sends that event name to the sound engine.

Do note that this sound implementation is very basic considering Wwise's capabilities. For more complex sound behaviour (using states, switches and RTPCs) we still need to do the things manually. But it's nice that basic objects, like explosions, can now play all their effects with these two simple lines of code:

Effect.PlayVisual(VisualEffect.Explosion, transform.position);
Effect.PlayAudio(AudioEffect.Explosion, gameObject);

That's all there is from my side. If you have a question regarding other aspects of the effects, or an entirely different technical aspect of the game, don't hesitate to reach out to us!

Tanks for reading this!


Get Tenacious Tanks

Leave a comment

Log in with to leave a comment.