Trigger/Action is an organizational strategy that loosely couples an event like pressing a key or an object entering a trigger to an action like spawning a monster. This makes your code more flexible and friendlier to make changes to.
People often code Unity behaviours like this:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof(MonsterSpawner))]
public class MonsterTrap : MonoBehaviour {
private MonsterSpawner monsterSpawner;
private void Start() {
monsterSpawner = GetComponent<MonsterSpawner>();
}
private void OnTriggerEnter(Collider other) {
monsterSpawner.Spawn();
}
}
The problem with this class, simple as it is, is that the effect, spawning a
monster, is coupled too tightly to the cause, something entering the trigger.
When you decide in the future that you actually need a second trigger by a nearby
door, you'll have to split this class into two classes. This kind of rewriting
feels sucky because it is sucky. The class is doing too much.
My solution is to always break the causes (triggers) into separate classes from the effects (actions). Triggers can be anything really, like pressing a key, or a GameObject's Start() callback firing.
Let's say we were to build this MonsterTrap into a prefab using Trigger/Action.
MonsterTrap (GameObject)
- BoxCollider
- EnterColliderTrigger
- SpawnGameObjectAction
The great thing about this is that we can easily modify it to add in another trigger:
MonsterTrap (GameObject)
- Door Trigger (GameObject)
- BoxCollider
- EnterColliderTrigger
- Window Trigger (GameObject)
- BoxCollider
- EnterColliderTrigger
- Spawner (GameObject)
- SpawnGameObjectAction
Then we discover the monster will be spawned each time the player wanders through the box collider, so let's put the OnceAction in front of the spawn action.
MonsterTrap (GameObject)
- Door Trigger (GameObject)
- BoxCollider
- EnterColliderTrigger
- Window Trigger (GameObject)
- BoxCollider
- EnterColliderTrigger
- Spawner (GameObject)
- OnceAction
- SpawnGameObjectAction
Now let's add a scary sound when it spawns:
MonsterTrap (GameObject)
- Door Trigger (GameObject)
- BoxCollider
- EnterColliderTrigger
- Window Trigger (GameObject)
- BoxCollider
- EnterColliderTrigger
- Spawner (GameObject)
- OnceAction
- SeriesAction
- PlaySoundAction
- SpawnGameObjectAction
The point here is that when triggers are loosely coupled to actions and all actions have the same interface, actions become composable. You write an action once and re-use it in a bunch of places. This feels better because it's working at a cleaner level of abstaction.
Drop the TriggerAction folder inside Assets/Plugins (Create Plugins if it doesn't exist.)
When a button is pressed, fires off the attached action.
When an object enters the trigger, fires off the associated action. Remember that you can create custom layers in Unity3d that only react when a particular gameObject (like the player) enters.
Fires the action immediately when the gameObject fires its Start() callback.
Utility action that will call another action, but will not fire again until an amount of time has passed. Super useful for controlling player attacks.
Simply prints to the log. Useful for figuring out if a complex chain is working correctly.
Utility action that when called will wait a specified time, then fire another action. Useful for timing things like destroying a crossbow bolt 10 seconds after it's been spawned.
Utility action that hides the cursor.
Utility action that will call another action, but just once, then it silently ignores all action calls.
Plays the attached AudioSource.
Crossfade queues up the specified animation.
Utility action that will fire the attached action a given percentage of the time. This is useful for introducing a little randomness into your game.
Repositions the named GameObject somewhere randomly between the specified MinPosition and MaxPosition.
Deletes the named gameobjects.
Utility action that runs the specified other actions in turn.
Sets a target GameObject to active or disabled depending on the setToActive setting.
When the action is fired and the user is holding down shift, fires one action, otherwise fires another action.
Spawns a specified gameObject. Options include:
attachToSelf
-- specifies if this gameObject should be parent or notisTemporary
-- indicates the spawned object should be deletedlifespan
-- indicates how long the object should live
These last two settings are useful for one-shot particle emitters.
Utility action that alternates between firing one action or another. Useful for enabling and disabling menus.
Shows the cursor. Useful for enabling the cursor for menus after disabling it in game.
First, check out the source to the provided triggers and actions. They're not complicated at all.
Build most of your custom trigger classes by inheriting from TriggerBase. Call Trigger() when you want to fire off the associated action.
Build most of your custom actions by inheriting from ActionBase. Simply override the Act() method in your new class with your payload code and you're good to go.
I hope you find this a useful way to organize your code. I used these triggers and actions in my game, Piggyback Dungeon Ryder, and felt that the benefits of organizing my code this way far outweighed the drawbacks -- which to be fair are mainly the overhead of managing prefabs.
Feel free to contact me if you have any questions, comments, potential contract work, or want to contribute anything back. I love pull requests.
Cheers,
Mike Judge
@mikelovesrobots on Twitter