Tuesday, March 10, 2015

Unity: Plug and Play Conditions and Actions

In this article, I want to briefly describe a fairly simple, flexible, and modular technique that I have been using in my Unity projects to check conditions in games, and perform actions of all kinds. This article assumes you understand the concepts of inheritance and polymorphism.

Say you are making an RPG game, and you want to create an epic weapon with some awesome effect on it. For instance, lets make a weapon called "The Injured Warrior's Sword of Raining Night Coins." This sword will have a 10% chance when you hit an enemy with it to make a coin-raining particle system around you, play a money-falling sound effect, and give your character 30 to 50 gold coins. However, we only want this effect to be able to happen at night time when your character is below 50% health.

Creating this awesome weapon or any weapon like it is fairly easy as long as you have things set up with an action/condition paradigm in mind.

In this article, actions are scripts that inherit from an abstract Action class that contains an abstract function called DoAction(). Similarly, conditions are scripts that inherit from an abstract Condition class that contains an abstract function called CheckCondition().

To accomplish this ambitious goal, create a game object within the sword's hierarchy. On this game object, attach a script that listens for some event on the weapon that fires whenever the weapon hits something. This can be accomplished through C# events for example. When the event occurs, have the script call CheckCondition() on all Condition components attached to the game object. If all of them return true, then have the script call DoAction() on all of the Action components attached to the game object.

Assuming you already had all of the following action and condition scripts written:
  1. Add ChanceCondition script. Set it's chance to 0.1.
  2. Add a TimeOfDayCondition script. Set its range from 6pm to 6am.
  3. Add a PlayerHealthPercentageCondition script. Set its threshold to Less than 50%.
  4. Create a good looking coin-raining particle system prefab.
  5. Add an InstantiatePrefabAction script, and drag in the reference to the particle system prefab.
  6. Add a PlaySoundAction script, and drag in a reference to your desired money falling sound.
  7. Add a GivePlayerRandomMoney script, and set the range from 30 to 50.

It is done! You're awesome sword with all kinds of crazy effects and conditions took little more than dragging in a few select condition and action scripts, and setting their parameters. As you can see, this technique of writing common game condition checks and actions, each in their own small script can be incredibly powerful, and it can be used in all kinds of places throughout your project.


I have started to use this paradigm very heavily in Salt, and it has worked out quite well. For instance, I use conditions for what fish you catch, what loot you receive from killing a pirate, whether or not you can place an object on a boat, if a certain night-time exclusive creature is allowed to spawn, and so forth. I use actions for all kinds of things too, such as effects on weapons, special events that happen when you touch things, stuff that happens during boss fights, etc. 

Every time I need some new thing to happen in the game, I create a new action script that accomplishes what I need. I try to make the new scripts as flexible as I can so that I can re-use the same script later for different things. Same for conditions. Each time I need to check some game state, I create a new condition script.

I have slowly built up a large library of both action and condition scripts over time. With this library, implementing new items, effects, and content in general is very often just plug and play. This allows me to throw together very complicated functionality in very little time.

This kind of system also makes non-coding developers very happy.

Code

Here is the Condition abstract class that I use in Salt.


using UnityEngine;
using System.Collections;
using System;

public abstract class Condition : MonoBehaviour {

 public event Action<bool> conditionCheckedEvent;

 public bool CheckCondition () {
  return CheckCondition(null);
 }

 public bool CheckCondition(System.Object o) {
  bool returnVal = OnCheckCondition(o);
  if (conditionCheckedEvent != null) {
   conditionCheckedEvent(returnVal);
  }
  return returnVal;
 }

 public abstract bool OnCheckCondition (System.Object o);

}

As for actions, I actually use an interface called IAction, and have new action scripts implement that interface. You may choose to make it a class and use inheritance like with the conditions. Either way will work fine. Here is my IAction interface declaration and an example action:

using UnityEngine;
using System.Collections;

public interface IAction {
 bool DoAction(System.Object o);
}

using UnityEngine;
using System.Collections;

public class InstantiatePrefabAction : MonoBehaviour, IAction {

    public GameObject prefab;
    public Transform prefabParent;
    public bool setParentToThis = false;

    public bool DoAction(object o)
    {
        if (prefab == null)
        {
            Debug.LogError(name + " instantiate prefab action requires reference to prefab.", this);
        }
        GameObject newGO = Instantiate(prefab, transform.position, transform.rotation) as GameObject;
        newGO.transform.parent = prefabParent;
        if (newGO.transform.parent == null)
        {
            if (setParentToThis)
            {
                newGO.transform.parent = transform;
            }
        }
        return true;
    }
}

I originally gave the DoAction() function a bool return type because I figured I might sometimes need to know when an action failed, but I have yet to use it for anything. You can give it a void return type if you don't want to be stuck with having to return a bool value for every action you create.

No comments:

Post a Comment