Skip to content

Custom Effect Types

Shi Johnson-Bey edited this page Feb 21, 2024 · 6 revisions

How to create custom effect functions for Social Events

TDRS comes preloaded with an array of readily-available effect functions. This guide is for folks who might need to augment the built-in set with additional functions. A majority of users should not need to do this. This process requires creating new IEffect and IEffectFactory classes. This page takes users though a tutorial of this process. We use the built-in AddAgentTrait function as an example.

Step 0: Understanding the structure of an effect

Before we get started, let's review how effects are specified by a game designer and subsequently instantiated at runtime by the game engine. Effects are called as a single string value containing the name of the effect function followed by some number of parameter values, each separated by one or more spaces. When we need to instantiate and effect, we take this string, divide it into its parts and pass these parts to the effect factory responsible for constructing effects of the given type.

Structure of an effect

Step 1: Create a new effect class

Effects are classes that implement the IEffect interface which requires a class to define an Apply() method. This method is called when we want to apply the side-effects of the effect function. TDRS does not enforce a specific constructor format or set of class fields. Those are left to the discretion of the end-user.

Below is the implementation of the AddAgentTrait effect. It adds a trait to an agent for a specified amount of time and overrides the trait description to say why the agent has the given trait. The inclusion of the ToString() method is optional. User's may find it helpful for debugging.

// AddAgentTrait.cs

public class AddAgentTrait : IEffect
{
    private Agent m_agent;
    private string m_traitID;
    private int m_duration;
    private string m_descriptionOverride;

    public AddAgentTrait(
        Agent agent,
        string traitID,
        int duration,
        string descriptionOverride
    )
    {
        m_agent = agent;
        m_traitID = traitID;
        m_duration = duration;
        m_descriptionOverride = descriptionOverride;
    }

    public void Apply()
    {
        m_agent.AddTrait(m_traitID, m_duration, m_descriptionOverride);
    }

    public override string ToString()
    {
        return $"AddAgentTrait {m_agent.UID} {m_traitID} {(m_duration == -1 ? "" : m_duration)}";
    }
}

Step 2: Create a custom factory class

Next, we need to create a new factory class that will create instances of the AddAgentTrait effect when it is called within social events. Our factory must implement the IEffectFactory interface that requires we provide implementations for the EventName property and the CreateInstance() method. The code below is defines the factory for the AddAgentTrait effect. When defining your own factories, please remember:

  1. The EffectName property should return the name of the effect that we want this factory to correspond to. For example, if someone uses the effect AddAgentTrait ?owner dubious 5, then the factory with an EventName of "AddAgentTrait" will be selected to instantiate the effect.

  2. The CreateInstance() method is responsible for creating a specific effect instance. It accepts two parameters: a binding context and the arguments to the effect call as an array of strings. The context contains binding information to help convert variables in the effect call, in this case ?owner, to instances of social agents within the game.

  3. Use the System.ArgumentException class when something goes wrong in the factory. All the parameters are passed as strings. So the factory must parse any numerical or boolean values. So, when things go wrong, you need to provide adequate error messaging to help track down the issue. ArgumentException is caught by the TDRS' internal code and re-raised with additional information to help with debugging.

// AddAgentTraitFactory.cs

public class AddAgentTraitFactory : IEffectFactory
{
    public string EffectName => "AddAgentTrait";

    public IEffect CreateInstance(
        EffectContext ctx,
        params string[] args
    )
    {
        if (args.Length < 2)
        {
            string argStr = string.Join(" ", args);

            throw new System.ArgumentException(
                $"Incorrect number of arguments for 'AddAgentTrait {argStr}'. "
                + $"Expected at least 2 but was {args.Length}."
            );
        }

        string agentVar = args[0];
        string traitID = args[1];
        int duration = -1;

        if (!ctx.Engine.HasAgent(ctx.Bindings[agentVar].ToString()))
        {
            throw new System.ArgumentException(
                $"No Agent found with ID: {ctx.Bindings[agentVar]}"
            );
        }

        if (args.Length >= 3)
        {
            if (!int.TryParse(args[2], out var value))
            {
                throw new System.ArgumentException(
                    $"Expected integer as 3rd argument but was '{args[2]}'"
                );
            }
            duration = value;
        }

        return new AddAgentTrait(
            ctx.Engine.GetAgent(ctx.Bindings[agentVar].ToString()),
            traitID,
            duration,
            ctx.Description
        );
    }
}

Step 3: Create a plugin class to load your effects factories.

The last step before you can use your event is to add an instance of your effect factory to the EffectLibrary instance within the SocialEngine.

Effect factories are loaded when the SocialEngineController.Initialize() method is called. This call will fire an OnRegisterEffectFactories event with the current SocialEngine state as a parameter.

The steps below create a "plugin" class that you can add all your custom factories to.

  1. Create a new C# Script and name it "CustomEffectFactories"
  2. Open the file in your code editor of choice
  3. Add a public void HandleRegisterFactories(SocialEngine engine) Method to the class (sample code provided below).
  4. Inside the Start method add HandleRegisterFactories as a subscriber to the OnRegisterEffectFactories event (see code below).
  5. Inside the HandleRegisterFactoriesMethod, add your custom effect factory to the EffectLibrary. Here we have added the AddAgentTraitFactory.
  6. Add the CustomEffectFactories script as a component on the GameObject that has your SocialEngineController.
using UnityEngine;

public class CustomEffectFactories : MonoBehaviour
{
    void Start()
    {
        SocialEngineController.OnRegisterEffectFactories +=
            HandlerRegisterFactories;
    }

    public void HandleRegisterFactories(SocialEngine engine)
    {
        engine.EffectLibrary.AddEffectFactory(
            new AddAgentTraitFactory()
        );

        // Add additional custom effect factories below.
    }
}

Step 4: Use the effect

Add your new effect to a new or existing social event!