This page covers some of MMTools' most useful helpers: observables, singletons, debug helpers, controlled randomness, loot tables, cooldowns and more.

Introduction

On top of its bigger systems, MMTools contains a lot of small helpers, the kind of classes you end up rewriting in every project. This page covers the most useful ones. They’re all in the MoreMountains.Tools namespace.

MMObservable

MMObservable wraps a value, and notifies anyone interested whenever it changes. Declare your property as a MMObservable:

public class Character : MonoBehaviour
{
	public MMObservable<float> Speed;

	protected virtual void Update()
	{
		Speed.Value = ComputeSpeed(); // listeners only get notified if the value actually changed
	}
}

Then, from any other class, subscribe to its changes, and don’t forget to unsubscribe:

public class SpeedDisplay : MonoBehaviour
{
	public Character TargetCharacter;

	protected virtual void OnEnable()
	{
		TargetCharacter.Speed.OnValueChangedTo += OnSpeedChange;
	}

	protected virtual void OnDisable()
	{
		TargetCharacter.Speed.OnValueChangedTo -= OnSpeedChange;
	}

	protected virtual void OnSpeedChange(float newSpeed)
	{
		// update your display
	}
}

Three callbacks are available, pick the one that fits: OnValueChanged (no parameter), OnValueChangedTo (the new value), and OnValueChangedFromTo (the previous and new values). Note that MMObservable is a struct, and isn’t serialized, so it won’t show in the inspector, and you should always access it through its owner’s field (as above), not through a local copy. You’ll find an example of it in the MMObservable demo scene.

Singletons

MMTools comes with base classes to turn any component into a singleton, in MMTools/Core/MMSingletons. Simply extend the one you need:

public class GameManager : MMSingleton<GameManager>
{
	public int Coins;
}

// then, from anywhere :
GameManager.Instance.Coins += 10;
  • MMSingleton: a regular singleton. If you access Instance and there’s none in the scene, one gets created automatically. Use HasInstance or TryGetInstance() if you just want to check, without creating one.
  • MMPersistentSingleton: same, but it survives scene loads (DontDestroyOnLoad), and destroys any newer duplicate it finds.
  • MMPersistentHumbleSingleton: survives scene loads too, but the other way around: a newer instance replaces older ones.

If you override Awake in your singleton, make sure you call base.Awake().

MMDebug

MMDebug is a static class packed with debugging helpers:

// a log with the current time and frame count, and an optional color
MMDebug.DebugLogTime("Player spawned", "#FFC400");

// displays a label and a value on screen, updated every frame, no UI setup needed
MMDebug.DebugOnScreen("Speed", currentSpeed);

// draws an arrow in the scene view
MMDebug.DebugDrawArrow(transform.position, transform.forward, Color.red);

You’ll also find methods to draw gizmo cubes, points, rectangles and arrows, and to get system info. MMDebug’s logs and debug draws can be toggled globally, via MMDebug.SetDebugLogsEnabled(bool) and MMDebug.SetDebugDrawEnabled(bool), or by adding a MMDebugController component to your scene. And if you need a full runtime console, check out the MMDebugMenu.

MMShufflebag

Pure randomness can feel unfair (the same item three times in a row, the rare item never showing up). A shuffle bag gives you controlled randomness: you put items in the bag, with a quantity for each, and pick them at random, without putting them back. Once the bag has been emptied, it starts over.

MMShufflebag<string> bag = new MMShufflebag<string>(10);
bag.Add("Common", 7);
bag.Add("Rare", 2);
bag.Add("Legendary", 1);

string result = bag.Pick(); // over 10 picks, you're guaranteed exactly 7 commons, 2 rares and 1 legendary

Loot tables

MMLootTable picks objects at random out of a weighted list, where each entry has a weight (the higher, the more likely), and its chance percentage gets computed for you. It’s available for game objects, strings and floats, as serializable classes you can declare in your own components:

public MMLootTableGameObject LootTable;

public virtual void DropLoot()
{
	GameObject loot = LootTable.GetLoot()?.Loot;
	if (loot != null)
	{
		Instantiate(loot, transform.position, Quaternion.identity);
	}
}

If you prefer storing your loot definitions as assets, to share them between enemies for example, create a Loot Definition (Create > MoreMountains > Loot Definition), a ScriptableObject containing a game object loot table, and call GetLoot() on it. You can also create your own loot types by extending MMLoot and MMLootTable.

MMCooldown

MMCooldown handles a consumable resource over time, like a sprint, a dash, a shield or a jetpack: it gets consumed while in use, pauses once empty, then refills. Declare it as a field in your class, initialize it once, update it every frame, and start and stop it as needed:

public MMCooldown SprintCooldown;

protected virtual void Start()
{
	SprintCooldown.Initialization();
}

protected virtual void Update()
{
	SprintCooldown.Update();
	if (SprintButtonPressed && SprintCooldown.Ready())
	{
		SprintCooldown.Start();
	}
	if (SprintButtonReleased)
	{
		SprintCooldown.Stop();
	}
}

From its inspector, you can set its consumption duration, the pause to apply once empty, its refill duration, and whether a new Start can interrupt a refill. Its Progress property (between 0 and 1) is perfect to drive a MMProgressBar, and you can subscribe to its OnStateChange delegate to react when it switches between Idle, Consuming, Stopped and Refilling.

Data structures

  • MMSerializableDictionary: Unity can’t serialize dictionaries natively. Extend MMSerializableDictionary with your own key and value types (see its class comments for the exact constructors to declare), and you’ll get a dictionary that survives serialization.
  • MMCircularList: a list with a CurrentIndex that loops around when incremented past its end or decremented past its start, with Current, NextIndex and PreviousIndex accessors. Perfect for carousels and weapon wheels.

Extensions and static helpers

MMTools also comes with many extension methods (Animator, arrays, lists, transforms, vectors, colors, layer masks, rect transforms, etc) and static helper classes (MMMaths, MMColors, MMGeometry, MMScene, MMString, MMTime, MMFade…). Here are a few examples:

MMAnimatorExtensions.UpdateAnimatorBoolIfExists(_animator, "Grounded", isGrounded); // no error if the parameter doesn't exist
transform.MMDestroyAllChildren();
Transform weapon = transform.MMFindDeepChildBreadthFirst("WeaponAttachment");
myList.MMShuffle();
Vector3 flattened = velocity.MMSetY(0f);
float remapped = MMMaths.Remap(value, 0f, 1f, 10f, 20f);
bool inLayerMask = layerMask.MMContains(collider.gameObject);

Don’t hesitate to browse MMTools/Core/MMExtensions and MMTools/Core/MMHelpers, or the API documentation, there’s a lot more in there.