Search Unity

Assets [WIP] Mecanim Callbacker

Discussion in 'Works In Progress' started by HolyShovel, Mar 10, 2018.

  1. HolyShovel


    Jun 19, 2013

    Hi all!

    We are a small independent team called “Holy Shovel Soft” and today we want to present to you an asset called “Mecanim Callbacker”. This asset was developed for our not yet announced game, but it’s growing up to be an independent product.
    So, what is it? It’s complex and flexible solution for writing code which must work with Mecanim animator. This asset provides seamless, intuitive and useful way of writing code with tons of events and callbacks. Now your code will no longer bloat due to the if-else hell. All you have to do is to subscribe to the particular event of the particular state.
    Asset is currently in beta stage. We plan publish it to asset store with lower cost in near months for open beta.

    Lets talk about base entities and concepts of MC.

    Core component of MC is Controller. This component is the enter point for all API. All entities can be set up from two points: presets (assets which are created in editor) and from code.
    Main entity types:
    Category - consists of states grouped into one entity to work as one whole in all callbacks. For example we create Category with these states: “Base Layer.Idle 1”, “Base Layer.Idle 2”, “Base Layer.Idle 3”, and call it “Idles”. In presets editor it will look like this:
    Lets look closer what we have in category:
    • Name - it’s very simple. Just name of category.
    • Inversed - if set to true, category processes all states not included in list of states.
    • Layer weight tolerance - weight of layer after which states start being processed.
    • States - list of included states.
    And in code we can work with categories in this manner (there are other possible scenarios besides examples below).

    Code (CSharp):
    2. //Get created in editor category by name
    3. //(for example its consist from three states: Idle 1, Idle 2, Idle 3)
    4. controller.Categories["Idles"].BaseEvents.OnTick += sourceController =>
    5. {
    6.     //This code will be called every frame when one of our states active
    7.     //But its called once per frame, all category work as one entity even if we
    8.     Debug.Log("I'm in 'Idles' category.");
    9. };
    11. controller.Categories.Inverse["Idles"].BaseEvents.OnEnd += sourceController =>
    12. {
    13.     //This code will be called every time when we exit from any category except 'Idles'
    14.     Debug.Log("I'm exit from some category.");
    15. };
    17. //We can create Category dynamicly (inversed state list supported)
    18. //...and we can cashe it =)
    19. var walksCategory = controller.Categories.FormStates["BaseLayer.Walk 1", "BaseLayer.Walk2"];
    20. walksCategory.BaseEvents.OnTick += sourceController =>
    21. {
    22.     Debug.Log("So, I'm walking now");
    23. };
    25. //And actualy we can work with group of categories as with one category!
    26. //...and yes, we can use dynamicly created category too, and inversed lists supported
    27. controller.Categories["Idles", walksCategory.Name].BaseEvents.OnTick += sourceController =>
    28. {
    29.     Debug.Log("So, I'm walking now... but maybe in Idle... or in both");
    30. };
    32. //Cool feature its that categories provide transitions events too
    33. walksCategory.TransitionEvents.OnTick += sourceController =>
    34. {
    35.     Debug.Log("I'm in transition from (or to) any state to (or from) state inside walk category.");
    36. };
    38. //It's support direction of transition
    39. walksCategory.TransitionEvents.From.OnStart += sourceController =>
    40. {
    41.     Debug.Log("I'm in start of transition from any state to state inside walk category.");
    42. };
    44. //And also can be used specific states
    45. walksCategory.TransitionEvents.To["BaseLayer.Crouch 1", "BaseLayer.Crouch 2"].OnEnd += sourceController =>
    46. {
    47.     Debug.Log("I'm in end of transition from state inside walk category to 'Crouch 1' or 'Crouch 2' state.");
    48. };
    Group - it’s very similar to Categories. But instead of working as one its call callbacks for every grouped up state. API and Editor UI very similar to Category.

    Curves - very useful tool for creating curves and modifiers for it based on states, layer weights and Mecanim parameters. Curves can be used to directly access values and for mapping it to animator parameter (it’s useful but not necessary unlike standard unity animation curves). How does it work? For example, we have curve called “Test Curve”. In editor it looks like this:

    Some explanation of all of the above stuff:
    • Name - just the name of curve.
    • Default value - constant value which will be returned if no subcurves will be evaluated.
    • Layer blending modes - how value of subcurves must process layer weights.
    • Subcurves - list of evaluating curves.
      • Value curve - main curve.
      • State - in this state our subcurve will be evaluated.
      • Modifiers - list of modifiers
        • Value Curve - modifiers’ main value curve.
        • Interpolation - how modifier must change the resulting value depending to selected source.
        • Modifier mode - type of modifying: override, multiply, etc.
        • Modifier type - type of source: parameter or transition normalized time.
    Code (CSharp):
    2. //Get curve by name
    3. var testCurve = controller.Curves["Test Curve"];
    4. //Get base (not modified) value of curve
    5. var testCurveBaseValue = testCurve.BaseValue;
    6. //Get fully modified value of curve
    7. var testCurveValue = testCurve.Value;
    8. //We also can create curves in runtime
    9. //but its more complex than groups and categories
    10. var newCurvePreset = new CurvePreset
    11. {
    12.     //Set default value
    13.     defaultValue = 10,
    14.     //Set mapping to variable
    15.     mapTo = controller.Variables.Floats["Some float var 1"],
    16.     //Set animator layer blending modes
    17.     layerBlendingModes = new []
    18.     {
    19.         LayerBlendingMode.Additive,
    20.         LayerBlendingMode.Override,
    21.         LayerBlendingMode.Override
    22.     },
    23.     //Set layer weight mapping curves
    24.     layerWeightMapping = new []
    25.     {
    26.         //You can use any animation curve
    27.         new AnimationCurve(),
    28.         new AnimationCurve(),
    29.         new AnimationCurve(),
    30.     },
    31.     //Fill subcurves
    32.     subcurves = new []
    33.     {
    34.         new SubcurvePreset
    35.         {
    36.             statePath = "Base Layer.Idle 1",
    37.             valueCurve = new AnimationCurve()
    38.         },
    39.         new SubcurvePreset
    40.         {
    41.             statePath = "Base Layer.Idle 2",
    42.             valueCurve = new AnimationCurve(),
    43.             //Also you can fill modifiers for subcurve
    44.             modifiers = new []
    45.             {
    46.                 new CurveModifierPreset
    47.                 {
    48.                     type = CurveModifierType.Parameter,
    49.                     mode = CurveModifierMode.Multiply,
    50.                     parameter = controller.Variables.Floats["Some float var 2"],
    51.                     interpolationCurve = new AnimationCurve(),
    52.                     valueCurve = new AnimationCurve()
    53.                 },
    54.             }
    55.         },
    56.     }
    57. };
    59. //And pass preset to create method
    60. controller.Curves.FromPreset(newCurvePreset);
    Events - Self-explanatory. It’s just events depended to state time.
    Basic examples below.
    • Name - again… just the name =)
    • Type - type of event: “One Shot” or “Between”. “One Shot” fires only when we have reached target normalized time of state. “Between” fires between two points and produces start and end callbacks as well.
    • Layer weight tolerance - the same as in categories.
    • Is guaranteed - how the event must handle situation when we don’t fire callback in this loop (e.g. due to lag). If set to false we just ignore this situation; if set to true we get all callbacks (needed count of callbacks).
    • Value - point or range for normalized time where the event must fire.
    • State - state where the event must be processed.
    And some code.

    Code (CSharp):
    2. //For one shot events only OnFire event is called
    3. controller.Events["Some One Shot Event"].OnFire += sourceController => { Debug.Log("Some One Shot Event is fired!");};
    4. var evt1 = controller.Events["Some Between Event"];
    6. //For Between events we can use OnStart and OnEnd too
    7. //and OnFire will be called every frame while we inside range
    8. evt1.OnStart += sourceController => { Debug.Log("Some Between Event start fired!");};
    10. //and we can create event from code
    11. var preset = new EventPreset
    12. {
    13.     isGuaranteed = false,
    14.     type = CallbackerEventType.OneShot,
    15.     statePath = "Base Layer.Crouch 1",
    16.     timePoint = 0.5f,
    17. };
    19. var evt2 = controller.Events.CreateFromPreset(preset);
    20. evt2.OnFire += sourceController => { Debug.Log("Base Layer.Crouch 1 in half time!");};
    Procedures - this is entity which target for handle custom code points. For example - you want execute specific code for category but do it in OnTriggerEnter instead regular update.
    This thing can’t be setted from editor, only in runtime from code.

    Code (CSharp):
    2. //First. We must create procedure. Our have one int argument.
    3. //0-5 arguments supported.
    4. var awesomeProcedure = controller.Procedures.GetProcedure<int>("MyAwesomeProcedure");
    5. //Now we must attach action to some entity which can be "inside"
    6. //like categories, groups, between events
    7. var target = controller.Categories["Idles"];
    8. target.BindProcedure(awesomeProcedure).OnCall += (sourceController, arg1) =>
    9. {
    10.     Debug.LogFormat("MyAwesomeProcedure called in 'Idles' with {0}", arg1);
    11. };
    12. //Its can be binded by name too
    13. controller.Groups["Crouches"].BindProcedure<int>("MyAwesomeProcedure").OnCall += (sourceController, arg1) =>
    14. {
    15.     Debug.LogFormat("MyAwesomeProcedure called in 'Crouches' with {0}", arg1);
    16. };
    17. //And also its can be binded from procedure self
    18. awesomeProcedure.Binder(controller.Events["Some between event"]).OnCall += (sourceController, arg1) =>
    19. {
    20.     Debug.LogFormat("MyAwesomeProcedure called in 'Some between event' with {0}", arg1);
    21. };
    23. //And now we can call our procedure in any place.
    24. awesomeProcedure.Call(10);
    That’s not the complete breakdown, just the main points. If you have any questions, suggestions, requests - speak freely. We are open to new ideas and are glad to answer any of your questions.
    Last edited: Sep 17, 2018
    SugoiDev, ZhavShaw and nuverian like this.
  2. Cartoon-Mania


    Mar 23, 2015
    I have not understood clearly yet. I am uncomfortable with mecanim. It is annoying and frustrating to be honest. So does this asset make it easy to use mecanim? Is it as easy as the old Legacy animation?
  3. HolyShovel


    Jun 19, 2013
    Hi! It all depends on what exactly annoys you in mecanim. But I will try to answer your question the way I understood it.
    So. If we talk about how mecanim designed, how you must fill it (states, blend trees, substate machines) in this point our asset doesnt help you and dont change anything. But if we talk about coding around mecanim - in this way our asset can help you a lot.
    For example - you have character which must shoot when you press fire button. And you have states like "Idle", "crouch", "walk" and "run". You want that character can shoot only in idle and walk states. In usual way you must do something like "Get current state info from animator, compare it to your state list, and execute shoot code". In this simple situation all can be achived in small piece of code. But, any change of states which need to be processed (for example you want add crouch shooting ability to your character) produce change in code... More complex logic (many layers, substates and other complex stuf) can produce more and more "if-else" situation and very complicate the code.
    With our asset all you need its create category with needed states, and write something like this in your code:
    Code (CSharp):
    1. controller.Categories["CanShootCategory"]
    2. .BaseEvents.OnTick += sourceController =>
    3. {
    4.   if(ShootButtonPressed())
    5.   {
    6.     ShootToSomething();
    7.   }
    8. };
    After this you can change this category in preset as you wish, add states from other layers, remove states. No any changes in code needed, and code still will be clean. You can divide the logic into separate pieces which still will be clean and not depend on "what states we use in animator".
  4. Lurking-Ninja


    Jan 20, 2015
    That's nice.

    I only have one question: have you (or are you planning) to run some sort of stress test? How much overhead are you introducing on the top of mecanim?
    What is the ms budget of this? How much garbage will be allocated?
    HolyShovel likes this.
  5. HolyShovel


    Jun 19, 2013
    Yes we plan create some stress tests before launching. Right now we dont have data about "how many ms its eat", but of course its very important moment.
    About GC our asset must produce garbage only on init and deinit stage, not in work cycle. But we have some options for pooling internal instances and when its turned on asset must reduce garbage producing for all work stages.
    Last edited: Mar 10, 2018
  6. nuverian


    Oct 3, 2011
    This sounds very interesting and useful!
    Looking forward to try it :)
    HolyShovel likes this.
  7. HolyShovel


    Jun 19, 2013
    Thanks! We will try impress you! Some secret info... we plan include FlowCanvas integration for free after finish of open beta test. ;)
  8. ZhavShaw


    Aug 12, 2013
    Well, I'm interested. Any release date in mind?
  9. HolyShovel


    Jun 19, 2013
    Thanks for interest. Right now all that I can say its "near future". I still need write some docs, create samples and finish beta test (closed phase). If I dont find critical bugs... it can take something about one month =)

    To all who intrested in try and want help me in beta test. I can give this asset for free to some peoples (two or maybe three) after docs and samples will be done but before publish (and for this peoples its will be free after piublishing). If somebody interesting, let me know. This is my emails and
    Last edited: Mar 12, 2018
  10. HolyShovel


    Jun 19, 2013
    So... In creating some samples I test something about 90 objects with callbacker components on scene (with logic) and same scene without mour components. Great news!
    1. Its not produce GC calls (if somebody find it - its bug, and I fix it).
    2. Its work really nice and fast. Both scenes run in same FPS (Vsync will be turned off) and Callbacker "update cycle" will "eat" something about 10-20ms.
  11. HolyShovel


    Jun 19, 2013
    To all.
    Samples done. Asset tested on some scenarios... but I still need some peoples who whant use it fo "close beta test". Website with docs and api pages still under construction... Its really slow process because my english not so good.
  12. nuverian


    Oct 3, 2011
    That sound awesome! :)
    Looking forward to see what you come up with.
    HolyShovel likes this.
  13. HolyShovel


    Jun 19, 2013

    So... After some frustrating bug fixes we finish with close beta test and submit our asset to assetstore!
    Bad news: docs still in process... Its very dificult process, because our programmer (author of asset and this post) doesn't speak English well enough. But we work on this and hope that docs will be ready in near future. API references will be available on our site when asset will be submitted. Sample scenes ready and uploaded with asset.
    nuverian likes this.
  14. HolyShovel


    Jun 19, 2013
    Last edited: Sep 17, 2018