/CSEMS

C# event management system with in-house events and event managers. It can be used for any C# projects, but originally aimed for Unity.

MIT LicenseMIT

C# Event Management System (CSEMS)

C# event management system with in-house events and event managers. It can be used for any C# projects, but originally aimed for Unity.

CSEMS is currently highly unstable and still in evolution.

This document provides information about CSEMS' core classes and also provides examples based on a hexfall (aka Hexic by Alexey Pajitnov) game (user-defined event managers example).

First, we will talk about the core classes, then give examples about the event managers, continue with in-house events, and conclude by showing how you can add your own events and event managers.

Table of Content

  1. Information About Base Classes
    1. Core classes
    2. Observer design pattern based classes
    3. Base classes for event managers
  2. Event Managers
    1. System related event managers
    2. Example user-defined event managers
  3. Events
    1. System event types
    2. Example user-defined events
  4. Event Handlers
  5. An Example: HexfallGameController
    1. Step 1: Creating the events
    2. Step 2: Creating the event handlers
    3. Step 3: Creating the event managers
    4. Step 4: Implementing event handlers

1. Information About Base Classes

Here, you will find some information about the core classes you should be aware of for constructing your own code.

1.1. Core classes

Each class should be either inherited from LB_BaseGameObject, which is inherited from LB_BaseObject, or at least implement LB_IBaseGameObjectFunctionality interface. Otherwise, they cannot receive essential calls, such as LB_Awake(), LB_Start(), LB_Update(), and so on.

If you are familiar with Unity, these methods have the same purpose with MonoBehaviour.Awake(), MonoBehaviour.Start(), and MonoBehaviour.Update().

1.1.1. LB_BaseObject

Base class for any objects in the project. This class includes some common members and properties, such as id, name, etc.

1.1.2. LB_IBaseGameObjectFunctionality

Interface that provides the esential methods for a game object class, such as update, fixed update, destroy, awake, start, and so on.

1.1.3. LB_BaseGameObject

Base class for the classes that will take a part in the game related tasks, but will not need to be inherited from MonoBehaviour of Unity, such as game managers, event managers, and so on.

LB_BaseGameObject implements LB_IBaseGameObjectFunctionality interface and provides the common blue print methods, needed members, and common functionality.

1.2. Observer design pattern based classes

CSEMS is designed based on observer design pattern. Event managers are initiated with Subject class and event handlers are initiated with Observer class.

1.2.1. Subject and Observer classes

Generic base Subject and Observer classes for an event-driven system.

Subject object maintains a list of its dependents, called observers. The observers register with the subject, so they can get notified when a predefined condition, event, or state change occurs. The subject automatically notifies all observers by calling one of their methods (callbacks).

Any number of observers can observe a subject. So, this is one-to-many relationship.

1.3. Base classes for event managers

All event managers (including user-defined ones) are inherited from generic BaseEventManager and automatically register themselves to MainEventManager to receive frame calls (Update(), callOnFrame(), and so on).

MainEventManager is a singleton class and it manages all event managers in the project. Subevent managers operate via MainEventManager.

2. Event Managers

Here, we will talk about event managers provided by CSEMS. They all define their own related events and inherit themselves from BaseEventManager. Event-handling classes, which are the classes listening a particular event, implement Observer interface and register themselves to related event manager.

2.1. System related event managers

ButtonEventManager is a singleton event manager for handling LB_Button events. LB_Button events are in-house events for handling Unity buttons better and easier. We will cover LB_Button events later detailed.

SystemEventManager is a singleton system event manager for handling system related events, such as quitting.

MouseEventManager is a singleton event manager for handling mouse events. This manager works via Unity's Input class. It also provides an in-house mouse hold-and-move event.

TouchEventManager is a singleton event manager for handling touch events. This manager works via Unity's Input class.

2.2. Example user-defined event managers

HexfallUIEventManager is a singleton event manager for managing Hexfall game UI events.

HexfallEventManager is a singleton event manager for Hexfall related event management.

Here, making all event managers singleton is a design choice and you do not need to define your event managers as singleton.

3. Events

All events are inherited from LB_Event class and define their own event type and define other specialities if needed. Now, we will talk about system and user-defined events.

3.1 System event types

3.1.1. LB_Event

LB_Event is the base event class both for system and user-defined events. It defines parent event information (system, touch, mouse, user-defined, and so on), event type (mouse down, touch up, etc.), and an object type member in case you need to hold extra information.

3.1.2. LB_ButtonEvent

LB_ButtonEvent is an in-house button event for handling click, button down, and button up events. LB_Button script is added to the buttons in the project and related callbacks (click, button down, and button up) are assigned via Unity editor. These callbacks automatically creates related events and register them to ButtonEventManager.

3.1.3. LB_SystemEvent

LB_SystemEvent is used for handling system related actions, such as quit request, processing quit request and so on.

3.1.4. LB_MouseEvent

LB_MouseEvent is an in-house mouse event used for handling mouse down, mouse up, and mouse hold and move events. It also provides information about mouse x and y positions in pixel coordinates.

3.1.5. LB_TouchEvent

LB_TouchEvent is an in-house touch event used for handling touch down, touch up, and touch and move events. It also provides information about touch x and y positions in pixel coordinates.

3.2. Example user-defined events

3.2.1. HexfallUIEvent

HexfallUIEvent provides events such as main menu, go game preparation, go in game, and so on. These events are used to change between game menus.

3.2.2. HexfallEvent

HexfallEvent provides events such as start game, quit game, score, generate bomb, generate hex, and so on. These events are used to handle actions in the game, mostly by various game controllers.

4. Event Handlers

Event handlers are interfaces based on generic LB_Observer interface mentioned in Subject and Observer classes section. Each event handler inherits LB_Observer by defining its event type. Each class aiming to handle one or more events implements HandleEvent(EventType e) method provides by the related event handler.

For example, LB_IButtonHandler is inherited from LB_Observer interface with LB_ButtonEvent event. A class aiming to handle LB_Button events needs to use LB_IButtonHandler interface and to implement HandleEvent(LB_ButtonEvent e) method defined by LB_IButtonHandler interface.

You can find more examples in the list below:

  • LB_IButtonEventHandler inherites LB_Observer with LB_ButtonEvent, provides void HandleEvent(LB_ButtonEvent e) method
  • LB_ISystemEventHandler inherites LB_Observer with LB_SystemEvent, provides void HandleEvent(LB_SystemEvent e) method
  • LB_IMouseEventHandler inherites LB_Observer with LB_MouseEvent, provides void HandleEvent(LB_MouseEvent e) method
  • LB_ITouchEventHandler inherites LB_Observer with LB_TouchEvent, provides void HandleEvent(LB_TouchEvent e) method
  • IHexfallUIEventHandler inherites LB_Observer with HexfallUIEvent, provides void HandleEvent(HexfallUIEvent e)method
  • IHexfallEventHandler inherites LB_Observer with HexfallEvent, provides void HandleEvent(HexfallEvent e) method

5. An Example: HexfallGameController

Let us assume we need a game controller that needs to handle mouse and touch events. Here, we will show how to create these events, event managers, and related event handlers.

For constructing such system;

  1. We need to define the related events based on LB_Event,
  2. Its event handler based on LB_Observer,
  3. Its event manager based on BaseEventManager,
  4. and implement the related HandleEvent(EventType e) methods in the game controller.

5.1. Step 1: Creating the events

5.1.1. Creating mouse and touch events

Let's assume we want to design a mouse event providing mouse down, mouse up, and mouse hold and move events along mouse x and y positions in pixel coordinates. We can achieve this goal by the following steps:

  • Inherit from LB_Event
  • Define event types and event type field
  • Define other specifications needed
  • Define constructors

These are the general steps we need to follow whenever we need to introduce a new user-defined event.

When we follow the general steps, we get a mouse event class as below:

// Step 1: Inherit from LB_Event
public class LB_MouseEvent : LB_Event
{
  // Step 2: Define event types
  public enum MOUSE_EVENTS : uint
  {
     MOUSE_DOWN, MOUSE_UP, MOUSE_HOLD_MOVE
  }

  // Step 2: Define event type field
  public MOUSE_EVENTS EventType
  {
     get { return (MOUSE_EVENTS)EVENT_TYPE; }
     set { EVENT_TYPE = (uint)value; }
  }

   // Step 3: Define other specifications
   public float MOUSE_POS_X;
   public float MOUSE_POS_Y;

  // Step 4: Define constructors
  public LB_MouseEvent() : base(LB_EVENTS.MOUSE_EVENT)
  {
     MOUSE_POS_X = -1;
     MOUSE_POS_Y = -1;
  }

  public LB_MouseEvent(
		        MOUSE_EVENTS eventType,
		        float mousePosX, float mousePosY,
		        string extraInfo = ""
	               )
     : base(LB_EVENTS.MOUSE_EVENT, (uint)eventType, extraInfo)
  {
     MOUSE_POS_X = mousePosX;
     MOUSE_POS_Y = mousePosY;
  }
}

We would define touch event as in the same way. You can refer to other event examples provided if you need more detail.

5.2. Step 2: Creating the event handlers

Since, we now have our events, we can define our event handlers. Event handlers are interfaces based on generic LB_Observer interface. Each event handler defines itself by an associated event and provides an event handler method via LB_Observer interface.

5.2.1. Creating mouse and touch event handlers

Let's consider mouse and touch event handlers based on what we've covered so far:

// Interface for mouse events
public interface LB_IMouseEventHandler : LB_Observer<LB_MouseEvent>
{ }

// Interface for touch events
public interface LB_ITouchEventHandler : LB_Observer<LB_TouchEvent>
{ }

C'est tout! Defining event handlers in CSEMS is very straightforward.

5.3. Step 3: Creating the event managers

Event managers are basically the subjects in observer pattern. CSEMS encapsulates the subject class via BaseEventManager. You can still create your own subject classes based on LB_Subject if you need for different purposes, such as a messaging system, but for creating an event manager that will work in CSEMS, you need to inherit your user-defined event manager from BaseEventManager.

Generic BaseEventManager manager requires two generic types; an EventType based on LB_Event and an EventHandlerType based on LB_Observer. Thus, when we create a mouse event manager MouseEventManager, we will inherit it from BaseEventManager as public class MouseEventManager : BaseEventManager<LB_MouseEvent, LB_IMouseEventHadler>.

Let's consider an easier example this time since MouseEventManager is a bit complicated. Let's assume we have our HexfallEvent and IHexfallEventHandler, and we want to write a HexfallEventManager. We could write this class as below:

// Singleton event manager for Hexfall related event management
public class HexfallEventManager : BaseEventManager<HexfallEvent, IHexfallEventHandler>
{
	// Thread-safe without using locks, not quite lazy
	private static readonly HexfallEventManager instance = new HexfallEventManager();
   
	static HexfallEventManager() { }

	private HexfallEventManager() { }
	
	public static HexfallEventManager Instance
    {
        get
        {
            return instance;
        }
    }  
}

The singleton is a design choice and you do not need to define your event manager as singleton. Also, if you do not need some special requirements, i.e. MouseEventManager, you also do not need to override any methods. BaseEventManager provides you all functionality you need for an event-driven system, such as pushing a new event, notifying listeners, registering to MainEventManager, and so on.

5.4. Step 4: Implementing event handlers

Let's go back to our mouse and input events and their event handlers along their event managers. In this part, we will focus on implementing a game controller that receives mouse and touch events.

There are two steps to achieve this functionality:

  1. Implementing related event handlers
  2. Registering to the related event managers

For being able to implement the related event handlers, we need to inherit from the related event handlers. In our case, these are LB_IMouseEventHandler and LB_ITouchEventHandler. So, we will define our HexfallGameController class as public class HexfallGameController : LB_BaseGameObject, LB_IMouseEventHandler, LB_ITouchEventHandler.

After having the event handlers, implementing them is straightforward and not any different than implementing any interface method. Let's assume, we have the following actions depending on the events we receiver:

  • MouseDown or TouchDown: Take input
  • MouseUp or TouchUp: Handle hexagon selection
  • MouseHoldAndMove or TouchMove: Rotate selected hexagon

so, we would implement the event handlers as below:

public void HandleEvent(LB_MouseEvent e)
{
   if (e.EventType == LB_MouseEvent.MOUSE_EVENTS.MOUSE_DOWN)
   {
   	game.TakeInput();
   }
   else if (e.EventType == LB_MouseEvent.MOUSE_EVENTS.MOUSE_UP)
   {
   	game.HandleHexagonSelection();
   }
   else if (e.EventType == LB_MouseEvent.MOUSE_EVENTS.MOUSE_HOLD_MOVE)
   {
   	game.HandleHexagonRotation();
   }
}

public void HandleEvent(LB_TouchEvent e)
{
   if (e.EventType == LB_TouchEvent.LB_TOUCH_EVENTS.TOUCH_DOWN)
   {
   	game.TakeInput();
   }
   else if (e.EventType == LB_TouchEvent.LB_TOUCH_EVENTS.TOUCH_UP)
   {
   	game.HandleHexagonSelection();
   }
   else if (e.EventType == LB_TouchEvent.LB_TOUCH_EVENTS.TOUCH_MOVE)
   {
   	game.HandleHexagonRotation();
   }
}

Now, we are at the final step to integrate CSEMS fully. It is time to register our class, HexfallGameController, to the related event managers. This step is inevitable and if you do not register your class to the related event managers, you cannot receive any event calls. It is like expecting phone calls without having a phone number.

It is better to handle this registeration after being sure the core system is ready. Since our event managers are singleton, we can handle this in LB_Awake() method having the same order and logic as Unity's Monobehaviour.Awake(). If the current event managers were not singletons than it would be better to handle the registeration process in LB_Start() the equivalent of Monobehaviour.Star().

Here is the full code of HexfallGameController:


public class HexfallGameController : LB_BaseGameObject, LB_IMouseEventHandler, LB_ITouchEventHandler
{
	HexfallGame game;

	public override void LB_Awake()
	{
#if (UNITY_EDITOR || UNITY_STANDALONE)
		MouseEventManager.Instance.Attach(this.SystemId, this);
#elif (UNITY_IOS || UNITY_ANDROID)
		TouchEventManager.Instance.Attach(this.SystemId, this);
#endif

		game = new HexfallGame();
		game.LB_Awake();

	}

	public override void LB_Start()
	{
		game.LB_Start();
	}

	public override void LB_OnDestroy()
	{
		game.LB_OnDestroy();
	}

	public void HandleEvent(LB_MouseEvent e)
	{
		if (e.EventType == LB_MouseEvent.MOUSE_EVENTS.MOUSE_DOWN)
		{
			game.TakeInput();
		}
		else if (e.EventType == LB_MouseEvent.MOUSE_EVENTS.MOUSE_UP)
		{
			game.HandleHexagonSelection();
		}
		else if (e.EventType == LB_MouseEvent.MOUSE_EVENTS.MOUSE_HOLD_MOVE)
		{
			game.HandleHexagonRotation();
		}
	}

	public void HandleEvent(LB_TouchEvent e)
	{
			if (e.EventType == LB_TouchEvent.LB_TOUCH_EVENTS.TOUCH_DOWN)
		{
			game.TakeInput();
		}
		else if (e.EventType == LB_TouchEvent.LB_TOUCH_EVENTS.TOUCH_UP)
		{
			game.HandleHexagonSelection();
		}
		else if (e.EventType == LB_TouchEvent.LB_TOUCH_EVENTS.TOUCH_MOVE)
		{
			game.HandleHexagonRotation();
		}
	}
}