Crafting Principles

Here are the 3 principles we use to craft any component in this package:

  1. I interface my purpose - So everyone know how to interact with me.

  2. I can seek for information - So you don’t need to know me. Just leave an open door and i’ll get what i need.

  3. You can directly ask me - In case you prefer to be specific about me.

Before we dive into our principles, have in mind that our components may have any of these traits:

  • Performer - Meaning it exists to perform some kind of action.

  • Handler - Meaning it exists to handle another component.

  • Provider - Meaning it exists to provide some kind of information.

A component can have multiple traits and a trait represents a purpose for that component to live by.

Ok. Lets get to the principles already.

The “I interface my purpose” principle

We ensure components communication by setting interfaces wich expose the traits.

For example, take our PlatformerJump component. You can attach it to some object, configure it and you will be able to benefit from it. If at any time you’d need a reference to our PlatformerJump component, you would do something like the following:

PlatformerJump jump = GetComponent<PlatformerJump>();

Ok. Now, the PlatformerJump component must have a handler among the other components of its GameObject. Imagine you have a component wich is responsible for handling the actions of the GameObject it is attached to. And since a jump can be considered an action, this particular component would handle the PlatformerJump component and tell it when to perform and stop jumps.

So, for example, let’s say we have a PlayableCharacterActions component and there we get the reference to the PlatformerJump component:

public class PlayableCharacterActions : MonoBehaviour
{
  public PlatformerJump jump;

  void Start()
  {
    jump = GetComponent<PlatformerJump>();
  }
}

Awesome! From there on we would be able to perform jump stuff by calling the PlatformerJump correct methods. But I consider you should be free to write your own component to handle / perform jumps . Actually, any component you want to write in order to perform anything while interacting with our components, i find that you should be able to do it and still benefit from our components wich you see fit as a solution for your game.

Because of that, PlayableCharacterActions wouldn’t have a reference to the PlatformerJump component. Instead it would declare itself as a “dynamic jump handler” and then allow the PlatformerJump component to find it and listen to events that state what it should do. So… for the sake of a use case example, I present to you the IPlatformerJumpHandler interface.

Take a look:

namespace HandyTools.CharacterController.Abilities
{
    /// <summary>
    /// Any component that implements this interface will be able to handle platformer jumps
    /// by invoking its methods..
    /// </summary>
    public interface IPlatformerJumpHandler
    {
        UnityEvent SendJumpRequest { get; }
        UnityEvent SendJumpStop { get; }
    }
}

This interface forces whatever implements it to give access to 2 Unity Events. One to request a jump to be performed and other to demand a jump to be stoped. So as a Handler whenever you want a jump to be performed use the SendJumpRequest.Invoke() approach. Same goes for when you want to stop a jump with SendJumpStop.Invoke().

public class PlayableCharacterActions : MonoBehaviour, IPlatformerJumpHandler
{

  public UnityEvent sendJumpRequestEvent; // declare it as field so you can benefit from it on inspector
  public UnityEvent sendJumpStopEvent;

  public UnityEvent SendJumpRequest => sendJumpRequestEvent; // Dispose as getter to respect IPlatformerJumpHandler interface
  public UnityEvent SendJumpStop => sendJumpStopEvent;

  void Update() {

    if (JumpShouldBePerformed)
    {
      sendJumpRequestEvent.Invoke();
    }

    if (JumpShouldBeStoped)
    {
      sendJumpStopEvent.Invoke();
    }

  }

}

That allows us to, while crafting a “dynamic jump performer” component, find any IPlatformerJumpHandler among its GameObject components and listen to its events. Then, every time a SendJumpRequest is invoked by its handler, the component will receive it on its callback and handle if it should jump or not.

public class MyJumpPerformer : MonoBehaviour
{

  IPlatformerJumpHandler jumpHandler;

  void Start()
  {
    jumpHandler = GetComponent<IPlatformerJumpHandler>();
    jumpHandler?.SendJumpRequest.AddListener(Request); // Listen to Jump Requests
    jumpHandler?.SendJumpStop.AddListener(Stop); // Listen to Stop demands
  }

  void OnDisable()
  {
    jumpHandler?.SendJumpRequest.RemoveListener(Request); // Stop listening to Jump Requests
    jumpHandler?.SendJumpStop.RemoveListener(Stop); // Stop listening to Stop demands
  }

  void Perform()
  {
    // Performs jump every physics update
  }

  void Request()
  {
    // Evaluate if jump can be perfomed and start it if so
  }

  void Stop()
  {
    // Stop any jump in progress
  }
}

Well… we can easily see now how to benefit from the unity’s input system . When jump button is pressed: SendJumpRequest.Invoke(). When jump button is released: SendJumpStop.Invoke().

Note

If you are not familiar with Unity Events you can click here and learn a bit more.

So, if you want to build your own handler for our PlatformerJump component, just implement the IPlatformerJumpHandler interface and mark “Seek For Handler” option in the PlatformerJump component inspector window. Make sure to attach both components to the same GameObject.

We use this strategy to all traits presented here. For another example you will find interfaces like IPlatformerGroundingProvider so you can listen for updates on the GameObject being or not considered grounded. Even another example is the IDashPerformer wich will allow you to find components that perform dynamic dashes and listen for when the dash occur.

This is it for this first and most important principle. It is the “i interface my purpose” principle that glue all components together and allow you to grow your components from our work.

The “I can seek for information” principle

As seen in the previous principle, the PlatformerJump can seek for its handler if you mark the inspector’s option. Well… it is not the only one.

All components that need some kind of information will have the option to seek for a component to feed that piece of intel under the “Seekers” section of its inspector window.

Todo: Seeker section image

For example, the DynamicDash needs to know about the grounding status of the GameObject it is attached to. In order to receive updates about grounding it can seek for a “grounding provider” wich will be a component that implements the IPlatformerGroundingProvider interface. This way you can use our GoundingChecker component or implement your own component that provides information about its GameObject grounding status. All you will need to do is to make sure it implements the IPlatformerGroundingProvider interface and the DynamicDash will be able to find it and listen to the event that provides grounding information.

The “you can directly ask me” principle

Lastly, althoug I find it better to components be crafted “event drivenly” I like to leave open doors for a direct approach. And that is the reason for the “Seekers” section of our components on the inspector window to be completly optional. They CAN seek for information but if you prefer you can directly feed the information they need and it will be documented on that component’s page what method you can use to achieve that.

For example, the PlatformerJump component have an UpdateGrounding(bool newGrounding) method wich is exactly the method used as callback when the “seek for grounding provider” option is enabled. So, when creating you own provider, if the “interfancing and eventing” approach is not for you, just call the feeding method and pass in the argument your logic came up with.

bool grounded = MyWayToDetectGrounding();
jump.UpdateGrounding(grounded);

And with that we end the principles. Now you can start looking at specific components documentation pages and understand how to interact and use them.