This page explains how to use the MMEventManager to have your classes communicate without references to each other.

Introduction

Feel is built to be as decoupled as possible: a feedback doesn’t need a reference to a shaker, a sound feedback doesn’t need a reference to the sound manager, and so on. That’s done through events: one class broadcasts an event, and any number of other classes listening for that type of event receive it. You can use that same system in your own code, to have your UI react to your character’s health, your achievements react to enemies dying, or your sound manager react to the game being paused, without any of these classes knowing about each other.

There are two event patterns in Feel:

  • the MMEventManager, described in details on this page, a general purpose, type safe event system, used by MMGameEvents, the MMSoundManager, the MMFader, spring components, the MMPersistenceManager, achievements, MMF Players, and more
  • static delegate events, a lighter pattern used by shakers. You can learn more about them, and how to create your own, in the custom shaker section

From your side, both work the same way: you trigger them with a one liner, from anywhere, and whatever listens to them will react.

Controlling Feel via events

While Feel lets you do a lot from the editor only, via the MMF Player and its 150+ feedbacks, you can also do pretty much everything from code directly. For example, you can create a MMF Player from code, or modify any value on a player.

But sometimes you just want to add a bit of juice with a single line, and setting up a MMF Player would be overkill. Thanks to its decoupled architecture, you can skip the player entirely, and trigger the same events feedbacks use, directly from your code. A good example of that would be the MMSoundManager, that you can control through feedbacks, or just via one liner events. Here’s how you’d pause the UI sound track:

MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.PauseTrack, MMSoundManager.MMSoundManagerTracks.UI);

Most shakers can also be controlled via events. To find out how, open the shaker class, which usually also defines its associated event, or look at the corresponding feedback class, which will call that same event when played. Here are some examples:

// triggering a simple camera shake, this event will be processed by a MMCameraShaker or MMCinemachineCameraShaker
MMCameraShakeEvent.Trigger(duration: 1f, amplitude: 5f, frequency: 20f, amplitudeX: 5f, amplitudeY: 5f, amplitudeZ: 5f, infinite: false, channelData: new MMChannelData(MMChannelModes.Int, 0, null), false);

// triggers a fade out over 0.5s and an ease-in overhead curve, this event will be processed by a MMFader
MMFadeEvent.Trigger(0.5f, 0f, MMTween.MMTweenCurve.EaseInOverhead, 0, false, Vector3.zero);

// triggers a reverb shake event, shaking the value of the reverb from 0 to 1 over half a second, along a specified animation curve
MMAudioFilterReverbShakeEvent.Trigger(someAnimationCurve, 0.5f, 0f, 1f);

And if what you want is to move or interpolate something, without a MMF Player, check out tweens.

MMGameEvent

The simplest way to get started is with MMGameEvent, a general purpose event that comes with MMTools. It’s made of a name, and a few optional parameters (an int, a Vector2, a Vector3, a bool and a string).

Triggering one from anywhere in your code is a one liner:

MMGameEvent.Trigger("GameOver");
MMGameEvent.Trigger("ScoreChanged", intParameter: 150);

You can also trigger one from a MMF Player, using the MMGameEvent feedback.

Listening without code

Add a MMGameEventListener component to any object, set its EventName, and bind whatever you want to its OnMMGameEvent UnityEvent. Every time a MMGameEvent of that name gets triggered, it’ll be invoked.

Listening via code

To listen for an event from any class, there are 3 things to do:

  1. declare that your class implements the MMEventListener interface for that type of event (you can implement it for as many event types as you want)
  2. start listening on enable, and stop listening on disable
  3. implement the interface’s OnMMEvent method, which will be called every time an event of that type is triggered
using MoreMountains.Tools;
using UnityEngine;

public class GameOverScreen : MonoBehaviour, MMEventListener<MMGameEvent>
{
	public virtual void OnMMEvent(MMGameEvent gameEvent)
	{
		if (gameEvent.EventName == "GameOver")
		{
			// display the game over screen
		}
	}

	protected virtual void OnEnable()
	{
		this.MMEventStartListening<MMGameEvent>();
	}

	protected virtual void OnDisable()
	{
		this.MMEventStopListening<MMGameEvent>();
	}
}

Always make sure you stop listening when your object gets disabled or destroyed, otherwise the event manager will keep a reference to it.

Creating your own events

String based events are convenient, but for anything a bit more structured, you’ll want your own event types. Any struct can be an event. By convention, MoreMountains events come with a static Trigger method, which makes them easy to fire from anywhere, and reuse a single static instance to avoid allocations:

using MoreMountains.Tools;
using UnityEngine;

public struct HealthChangeEvent
{
	public GameObject Target;
	public float NewHealth;
	public float MaxHealth;

	static HealthChangeEvent e;

	public static void Trigger(GameObject target, float newHealth, float maxHealth)
	{
		e.Target = target;
		e.NewHealth = newHealth;
		e.MaxHealth = maxHealth;
		MMEventManager.TriggerEvent(e);
	}
}

Then, from your health class:

HealthChangeEvent.Trigger(this.gameObject, CurrentHealth, MaxHealth);

And from any class that wants to react to it, like a health bar:

public class HealthBarUpdater : MonoBehaviour, MMEventListener<HealthChangeEvent>
{
	public MMProgressBar TargetBar;

	public void OnMMEvent(HealthChangeEvent healthEvent)
	{
		TargetBar.UpdateBar(healthEvent.NewHealth, 0f, healthEvent.MaxHealth);
	}

	protected virtual void OnEnable() => this.MMEventStartListening<HealthChangeEvent>();
	protected virtual void OnDisable() => this.MMEventStopListening<HealthChangeEvent>();
}

A class can listen for as many event types as it wants, simply implement MMEventListener once per type:

public class AchievementRules : MonoBehaviour, MMEventListener<MMGameEvent>, MMEventListener<HealthChangeEvent>

Good to know

  • Events are dispatched synchronously: when you call Trigger, all listeners’ OnMMEvent methods run immediately, before the next line of your code.
  • Triggering an event nobody listens to is perfectly fine, nothing happens. This is what makes it safe to, for example, trigger sound events in a scene without a sound manager.
  • The event manager’s listeners list gets reset when entering play mode, so it works with Enter Play Mode Options (domain reload disabled).
  • Since events are structs, you can’t inherit from them, but you can add an enum to your event to describe different situations (for example a MMSoundManagerTrackEvent has a type that can be Mute, Pause, SetVolume, etc).