This page explains what the new MMF_Player system is, and how to use it

What is MMF_Player?

Jekyll
The MMF Player inspector

Introduced in FEEL v3.0, the MMF_Player is a new way to play feedbacks. It’s the new version of MMFeedbacks, and the one that should be preferred from 3.0 and onwards.

For a bit of history, when MMFeedbacks were created, Unity didn’t offer a way to serialize polymorphic fields. This changed with Unity 2019.3 and the introduction of SerializeReference, which opened new possibilities for the system.

Here are the main reasons to use MMF_Player over MMFeedbacks :

  • more robust and performant system
  • allows the use of Templates
  • lets you keep runtime changes
  • fancier inspectors
  • add/remove feedbacks easily from code, even at runtime
  • shows help texts when setup is required on feedbacks
  • displays extra info in folded mode
  • has a unified Channel system
  • lets you skip feedbacks to the end
  • lets you customize each feedback’s color

This is an entirely new system, with new classes, new feedbacks and is separate from the old one. But to make transitioning from one to the other as simple as possible, they both function basically the same way, you won’t have to modify your old scripts that were playing Feedbacks, and there’s even a conversion system to go from one to the other. Plus, all the documentation still applies, the only exception being the classes/components names. In time, the old MMFeedbacks system will be deprecated, and only MMF_Player will remain.

You’ll find the MMF_Player in action in the MMF_PlayerDemo scene, and in the FeelTemplates demo scenes.

Naming conventions

Before, there were two main classes : MMFeedbacks, which would let you play a number of MMFeedback (no S) components, and MMFeedback, the individual base class defining individual feedbacks. This naming was admittedly a bit confusing sometimes. So in the new system, we have two main classes : MMF_Player, which lets you play feedbacks, and MMF_Feedback as the individual base class. The MMF_Player plays a list of MMF_Feedback.

And while in the old system feedbacks would typically be named MMFeedbackSomething, they’re now named MMF_Something. This makes for shorter, more readable classes (yes, the underscore is here for readability too).

To recap :

  • MMFeedbacks > MMF_Player
  • MMFeedback > MMF_Feedback
  • MMFeedbackScale > MMF_Scale

If you’re looking at older videos, just keep these in mind, everything else still applies.

Migrating from MMFeedbacks to MMF_Player

Everything’s been put in place so that migrating from the old system to the new be as painless as possible.

Jekyll
The conversion buttons

On every MMFeedbacks, at the bottom of the Settings foldout, you’ll now find two conversion buttons. These are your two main options to convert from an old MMFeedbacks to a new one. Of course you can also recreate one from scratch, or stick to the old system, it’s up to you.

  • Generate MMF_Player : this will create a new MMF_Player component on the same object, and copy feedbacks to it, ready to use. The old MMFeedbacks won’t be touched.
  • Convert to MMF_Player : this will try to replace the current MMFeedbacks with a new MMF_Player with the same settings. Important : if you’re inside a prefab instance, regular conversion won’t work, and you’ll want to add, at the prefab level, an empty MMF_Player that will be the recipient of the conversion. Then you can press the button.

After conversion, you should have something very familiar, but improved!

Jekyll
Before (left) and after (right) the conversion process

Now in your code you may already have something like this :

public MMFeedbacks TargetFeedbacks;

protected virtual void AtSomePoint()
{
    TargetFeedbacks?.PlayFeedbacks();
}

Good news, you don’t need to change anything! MMF_Player inherits from MMFeedbacks, so you can keep your code as is, and drag MMF_Players into your MMFeedbacks inspector slots.

How to migrate from a MMFeedbacks to a MMF_Player

  • in Unity 2019.4.35f1, create a new project using the standard RP
  • from the Package Manager, import Feel v3.0
  • open the FeelDuck demo scene
  • in the Hierarchy, select the FeelDuckJumpStartFeedback
  • in its MMFeedbacks inspector, unfold the Settings foldout towards the top of it
  • at the bottom of that foldout, press the Convert To MMF_Player button
  • a debug log to the console will tell you how it went, and will let you know if any feedbacks couldn’t be converted (that’d be the case for the old “Haptics” one for example, which doesn’t have an equivalent - there are new and better haptic feedbacks now though!)
  • and that’s it, you’re done, press play, the duck will still quack when you start jumping, existing bindings have been preserved

Keeping runtime changes

With the MMF_Player, it’s now way easier to keep your runtime changes, and speed up your workflow. There are two main ways you can do that :

  • press the Keep Playmode Changes button at the bottom of your MMF_Player inspector. Once you exit play mode, your changes will be saved.
  • use the Copy All button at any time during runtime, and then after exiting play mode, use the Replace All button to replace all your feedbacks with the values you’ve copied
Jekyll
The Keep Playmode Changes button

Both of these will keep all changes on your MMF_Player, including added/removed feedbacks, or changes to any values inside your feedbacks. You can also copy each feedback individually if you prefer, by clicking on the 3 little dots on the right of each feedback.

Note that to avoid potential mistakes, the “Keep Playmode Changes” mode will auto disable itself every time you exit play mode. If you’d like to change that behaviour (some people like to live dangerously), you can do so by editing the MMF_PlayerConfiguration settings (look for “MMF_PlayerConfiguration” in your Project, uncheck AutoDisableKeepPlaymodeChanges in its inspector).

Templates

Because it’s now so easy to copy feedbacks at any time, and from a Player to another, you can easily create “templates” that you’ll reuse in different parts of your projects.

There are multiple ways to do that. The recommended one would be to simply use Unity’s presets system. Creating a new preset is very simple :

  • select a MMF_Player that already contains a list of feedbacks you’d like to use as your preset
  • press the preset button (see image below), a popup will open, then click on the “save current to” button, located in the bottom left of the popup.
  • pick some place in your project to save your preset, and give it a name
  • now, from any MMF_Player, you can click on that preset button, and recall any of the ones you’ve saved, and it’ll instantly replace your MMF_Player’s entire list of feedbacks. If you do that by mistake remember you can ctrl+Z to go back to its previous state
Jekyll
The presets button

You can also use prefabs to “store” a stack of feedbacks on a MMF_Player, and copy from there. You’ll find examples of such prefabs throughout demos. Here we’re just using regular prefabs as storage space for our templates. You could use these as regular prefabs of course, but maybe you want to create a brand new feedback player and not start from scratch. You can simply select a template in your Project, copy all its feedbacks using the Copy All button, create a new MMF_Player anywhere you want it, and paste them. The system will let you know, via little warnings, of all the places you need to bind things for the feedbacks to work correctly. This can be a real time saver in some workflows!

Quality of Life

Jekyll
Various assists in the inspector

The MMF_Player comes with a number of quality of life improvements, that should help making their setup safer and easier. As shown in the image above, when a feedback is missing some binding, a little warning icon will display in the list, and if you expand the feedback, a text zone will let you know what to do to fix it. You’ll also find that same warning icon in the section of the feedback where the fix needs to happen.

Feedbacks now have inspector groups separating their various sections, including a common “Feedback Settings” section you’ll find on all feedbacks. You can fold/unfold each section, and there’s a setting in the configuration scriptable object you can tweak to have all these groups folded or expanded by default.

When folded, feedbacks will now display extra info (their target, their channel, etc) when relevant, next to their duration. This can be helpful to differentiate between feedbacks of the same type. And from the Feedbacks Settings section you can now define a unique background color on a per-feedback basis.

Modifying feedback values at runtime

If you’ve been using MMFeedbacks, you’ll find the MMF_Player API is very similar, by design. The main differences reside in the way you interact with feedbacks from external scripts, either to modify values in them, or to add/remove them.

To modify a value, while previously you would have used GetComponent, you can now use the GetFeedbackOfType or GetFeedbacksOfType methods :

// will return the first MMF_Scale found on that MMF_Player
MMF_Scale scaleFeedback = MyTargetMMFPlayer.GetFeedbackOfType<MMF_Scale>();

// will return a list of all MMF_Scale found on that MMF_Player
List<MMF_Scale> scaleFeedbacks = MyTargetMMFPlayer.GetFeedbacksOfType<MMF_Scale>();

// will return the first MMF_Scale found on that MMF_Player whose label matches "MyCustomLabel"
MMF_Scale scaleFeedback = MyTargetMMFPlayer.GetFeedbackOfType<MMF_Scale>("MyCustomLabel");

// will return a list of all the MMF_Scale found on that MMF_Player whose label matches "MyCustomLabel"
List<MMF_Scale> scaleFeedbacks = MyTargetMMFPlayer.GetFeedbacksOfType<MMF_Scale>("MyCustomLabel");

Adding and removing feedbacks at runtime

It’s now very easy to add and remove feedbacks from your own scripts at runtime.

There are two main ways you can add feedbacks.

For all examples above, we’ll assume we have a reference to an existing MMF_Player, for example it could have been declared like so :

public MMF_Player MyTargetMMFPlayer;

The first way you can add feedbacks is very close to how you’d add a component, simply by specifying the type of the feedback you’d like to add. This returns the MMF_Feedback you’ve added so you could also keep interacting with it afterwards :

MyTargetMMFPlayer.AddFeedback(typeof(MMF_Scale));

The second signature of that method will let you create and optionally customize a feedback. Here for example we’re creating a new scale feedback, and setting its label and duration to custom values, and adding them to our target MMF_Player :

MMF_Scale scale = new MMF_Scale();
scale.Label = "MyCustomLabel";
scale.AnimateScaleDuration = 15f;
MyTargetMMFPlayer.AddFeedback(scale);

And of course you can remove feedbacks, simply by specifying their index in the list, here we’re removing the second feedback in the list :

MyTargetMMFPlayer.RemoveFeedback(1);

Creating a player and its feedbacks at runtime

Adding and removing feedbacks at runtime is pretty cool, but Feel also lets you create entire players dynamically. To demonstrate this, let’s try to crate a MMF Player that will scale a number of cubes in sequence, with a bit of pause in between each of them.

To do so, let’s first create a new empty scene, and add an empty game object to it, and let’s rename that object to “DynamicCreator”. Then, let’s create a cube and parent it to our DynamicCreator object. Now let’s duplicate that cube (as many times as you want, still parented to our empty), and move them around a bit, your scene should look like this :

Jekyll
The presets button

Now create a new class somewhere in your project, and name it DynamicPlayerCreator. Open it in your IDE of choice, and replace its contents with this :

using MoreMountains.Feedbacks;
using MoreMountains.Tools;
using UnityEngine;

/// <summary>
/// A test class that will automatically generate a MMF Player, containing as many scale feedbacks as this object has children.
/// To test it :
/// - create a new scene, and a new object in it. Add this component to it.
/// - create a bunch of cubes, position them where you want, then parent them to this object.
/// - press play in the editor, then press the "Create" button on this component's inspector
/// </summary>
public class DynamicPlayerCreator : MonoBehaviour
{
	// an inspector test button to trigger the Create method
	[MMInspectorButton("Create")] public bool CreateBtn;

	private void Create()
	{
		// we create a MMF Player
		MMF_Player newPlayer = this.gameObject.AddComponent<MMF_Player>();
		// we're going to play this MMF Player right after having created it, we'll initialize it manually.
		// we do this to avoid its Initialization method to run on its Start, which would be after we've played it
		newPlayer.InitializationMode = MMFeedbacks.InitializationModes.Script;

		if (transform.childCount == 0)
		{
			Debug.LogWarning("[DynamicCreation test class] to test this class, create a number of cubes, position them randomly, then parent them to this object.");
			return;
		}

		// for each child transform
		for (int i = 0; i < transform.childCount; i++)
		{
			Transform childTransform = transform.GetChild(i);

			// we create a new scale feedback, tweak its target, and add it to the player
			MMF_Scale newScaleFeedback = new MMF_Scale();
			newScaleFeedback.Label = "Scale child " + i;
			newScaleFeedback.AnimateScaleTarget = childTransform;
			newPlayer.AddFeedback(newScaleFeedback);

			// we create a pause feedback, define its pause duration, and add it to the player
			MMF_Pause newPauseFeedback = new MMF_Pause();
			newPauseFeedback.Label = "Pause " + i;
			newPauseFeedback.PauseDuration = 0.2f;
			newPlayer.AddFeedback(newPauseFeedback);
		}

		// we initialize and play our player
		newPlayer.Initialization();
		newPlayer.PlayFeedbacks();
	}
}

Now add this newly created component to your DynamicPlayerCreator object. Press play, then in the DynamicPlayerCreator inspector, press the Create button. This will execute the Create method above, which creates a MMF Player, adds and sets up a scale feedback and a pause feedback for each of your cubes, then initializes the player and plays it. You should see your little cubes bump one after the other. This same logic (minus the Initialization and PlayFeedbacks calls at the end) can be used to automatically setup feedbacks outside of play mode too.

Executing code once all feedbacks are done playing

The MMF Player lets you listen for events, including OnComplete, that you can use to run code once the player is complete. Alternatively, you can use Unity Events as hooks for that, you’ll find them under the Settings foldout of your player. And lastly, you can asynchronously wait for it to finish, like so :

using System.Collections;
using System.Collections.Generic;
using MoreMountains.Feedbacks;
using MoreMountains.Tools;
using UnityEngine;

public class AwaitTest : MonoBehaviour
{
	public MMF_Player FirstPlayer;
	public MMF_Player SecondPlayer;

	[MMInspectorButton("Test")]
	public bool TestButton;

	public async void Test()
	{
		await FirstPlayer?.PlayFeedbacksTask(this.transform.position);
		SecondPlayer?.PlayFeedbacks();
	}
}

Converting old MMFeedback scripts to the new system

In the MMFeedbacks system, if you wanted to create a custom feedback script, you’d create a new class that inherits from MMFeedback. Now with the MMF_Player system, you create a new class that inherits from MMF_Feedback. If you have some old feedbacks you’d like to convert, the process is fairly simple, and there are only a handful of differences to be aware of :

  • base class
  • initialization method signature
  • coroutines
  • extra editor overrides
  • inspector groups

Usually you’ll want to start by duplicating your old class, and rename it. All new feedbacks match the MMF_Something pattern for their name, but it’s up to you, and can be anything you want.

Once you have that new class, we need to start by changing the class we inherit from, so for an example MMFeedbackTest class, we would change our class declaration like so :

public class MMFeedbackTest : MMFeedback

// would become :

public class MMF_Test : MMF_Feedback

If you were overriding the CustomInitialization method, you’ll need to change its signature, like so :

protected override void CustomInitialization(GameObject owner)

// would become :

protected override void CustomInitialization(MMF_Player owner)

You may have had coroutines in your old feedbacks, and there was nothing special about them, as feedbacks were monobehaviours. The new feedbacks aren’t, so if you want to run a coroutine, you’ll need a monobehaviour support. You can use the host MMF_Player for that, which you can access via the Owner property. So coroutines can be started like this :

StartCoroutine(DoSomethingCo());

// would become :

Owner.StartCoroutine(DoSomethingCo());

The MMF_Feedback also lets you specify extra editor overrides. Usually you’ll want to place them in the same #if UNITY_EDITOR block as the FeedbackColor override. There are three of them, all of them optional :

// if your feedback requires some form of setup, and you'd like to display a warning if that requirement isn't met,
// you can override EvaluateRequiresSetup to have it return such a condition
public override bool EvaluateRequiresSetup() { return (MyTargetObject == null); }

// if you define a required setup like on the line above, you'll want to override the RequiresSetupText property,
// to specify what text should appear in the warning message in the inspector
public override string RequiresSetupText => "This feedback requires that you set a MyTargetObject into its slot below.";

// if you want to display some extra info in the right part of your feedback's header,
// you can do so with the property below. Here we want to display the name of
// our MyTargetObject, so we check it's not null and return that.
// But it could be any other value you prefer.
public override string RequiredTargetText => MyTargetObject != null ? MyTargetObject.name : "";  

The MMF_Player also lets you define inspector groups, which group properties into foldable sections in the inspector.

[MMFInspectorGroup("MySectionTitle", true, 12, true)]

The first argument is the name of the section, the second determines whether or not this attribute is valid for all fields until the next (usually you’ll just want to leave that to true), the 3rd defines the color (it’s an int, max value 139, full list is in the MMFeedbacksColors class), and the last parameter is optional. You’ll want to set it to true for all sections where you’d want a warning displayed if setup is required (missing game object bound, etc). If EvaluateRequiresSetup is true, then that section will receive a little warning icon.

And that’s all there is to know to create new feedbacks! Of course you can look at existing feedbacks for references, you’ll find both the old and new versions in the asset.