Item Affix System in Unity C# with the Decorator Pattern

Ever wanted to do:

var awesomeNewHat = new OfProgramming( new Sagacious( new WizardHat() ) );
awesomeNewHat.Use();

Well, I used to, that’s why I implemented an affix system with the decorator pattern. Code available here – https://github.com/abeldantas/item-affix-decorators

> Puts on Sagacious Wizard Hat of Programming

An affix system is the sort of thing every loot based RPG needs to have satisfying item names and effects.

Here, have some hats:Unity WebGL Player | item-affix-decorators


The decorators are the affixes, an item is a component, and the Wizard Hat in this case is a concrete item.

The decorator pattern can model a lot of fun stuff in games.
Making a skill system should be fun too, where the decorating order factors into what effects you get.

You shall seek and always find with this generic seeker type pattern

Imagine you have a bunch of popup templates that you want to use across a codebase.

You have a bunch of prefabs for those popups.

You can use the logic I’m about to describe to do stuff like:

Popup.Find<YourPizzaIsReadyPopup>().Show()

Disclaimer: Don’t overuse this or you’ll have spaghetti code.

Ok, let’s go, I called this the seeker pattern, because it helps you find stuff.
Have a better name? Cool, keep it to yourself.

public static class SeekableExtensions
{
    public static bool IsOfSeekableType<T>( this Seekable seekable ) where T : Seekable
    {
        return seekable.GetType() == typeof(T);
    }
}
 
public interface Seekable
{
    void Seek();
}
 
public class UnassumingBystander : Seekable
{
    public void Seek()
    {
        Debug.Log( "Oh, you found me..." );
    }
}
 
internal class SeekableNotFoundException : Exception
{
}
 
// A wizard is a singleton
public class Wizard
{
    List<Seekable> seekables;
 
    T GetSeekable<T>() where T : Seekable
    {
        foreach ( var arcanizer in seekables )
        {
            if ( arcanizer.IsOfSeekableType<T>() )
            {
                return (T)arcanizer;
            }
        }
        throw new SeekableNotFoundException();
    }
 
 
    void DoStuff()
    {
        GetSeekable<UnassumingBystander>().Seek();
    }
}

Transform Extension Methods

Cannot modify a value type return value of `UnityEngine.Transform.position'. 
Consider storing the value in a temporary variable

No.

This is what you get when you try to directly set a transform x position, like this:

transform.position.x = 0f;

Create a class called ‘TransformExtensions’:

using UnityEngine;

public static class TransformExtensions
{
    public static void SetX( this Transform transform, float x )
    {
        transform.position = new Vector3(x,transform.position.y, transform.position.z);
    }
}

And instead of:

transform.position.x = 0f;

Do:

transform.SetX( 0f );

Coroutines that change value by reference

IEnumerator ChangeFloatBetween( float duration, float from, float to, Action callback, Action post )
{
    float value;
    var elapsedTime = 0f;
    while ( elapsedTime < duration )
    {
        elapsedTime += Time.deltaTime;
        value = Mathf.Lerp( from, to, elapsedTime / duration );
        yield return new WaitForEndOfFrame();
        callback( value );
    }
    value = to;
    callback( value );
    if ( post != null )
    {
        post();
    }
}

IEnumerator ChangeFloatBetween( float duration, float from, float to, Action callback )
{
    yield return ChangeFloatBetween( duration, from, to, callback, null );
}

This method can be used to change values of an image’s alpha for example, like this:

public void FadeIn( float duration = 3f )
{
   const float @from = 0f;
   const float to = 1f;
   foreach ( var material in MeshRenderer.materials )
   {
      var materialCopy = material;
      StartCoroutine( 
         ChangeFloatBetween( duration, from, to, ( alpha ) =>
            {
               materialCopy.color = new Color( 
                  materialCopy.color.r, 
                  materialCopy.color.g, 
                  materialCopy.color.b,
                  alpha );
            }) );
   }
}

– – –

We might want to do something after the increment is finished.
Like we do here, when we want to animate health UI after you drink a potion:

public void IncrementHP( float duration, int currentHP, int hpIncrement, Text hpText )
{
    StartCoroutine(
        ChangeFloatBetween( duration, currentHP, currentHP + hpIncrement, ( risingHP ) =>
        {
            hpText.text = Math.Ceiling( risingHP );
        }, () =>
        {
            Debug.Log("Finished incrementing HP");
        } ) );
}

– – –

We can also of course use this to change the position or rotation of a game object, example:

public void MoveThing( GameObject thing, float duration, float xMovementStep)
{
   var initialX = thing.transform.position.x;
    StartCoroutine(
        ChangeFloatBetween( duration, initialX , initialX + movementStep, ( risingX ) =>
        {
            thing.transform.SetX(movementStep);
        } ) );
}

In this example we’re using the SetX extension method, that I’ve covered that in my ‘Transform Extension Methods’ post.