Search Unity

Mesh Animator - Highly efficient animated crowds, fully animated GPU instancing, and more!

Discussion in 'Assets and Asset Store' started by jschieck, Dec 7, 2014.

  1. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Thanks for the heads up! I'll get in touch with Unity support to get it fixed.

    1.5.5 has minor optimizations and fixes for the Nintendo Switch.
     
    wood333 likes this.
  2. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    I'll test on that version and see what the issue is. Is it giving you errors?
     
  3. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    To get smoother animations you need to increase the bake FPS. So if you're running your application at 60 FPS, and want it to update every frame, you need to bake at 60.

    With a simple mesh like that, you will start to see better performance gains with greater numbers (like 5k-10k and beyond) . 1000 meshes isn't that many when comparing the two methodologies.
     
  4. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Well yes you'll have smoother animations with the default animator component because it's animating bones along animation curves. Think of mesh animator like a flip book of animation frames that are being swapped.

    Even older Android devices should be able to handle 1000 without much issue especially the mesh is as simple as you said. Your bottleneck will more likely be rendering.
     
  5. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Hey everyone! I've put together another short demo video showing the performance gain from using Mesh Animator and plan to make a short tutorial video to go with it soon. Enjoy!

     
    AthrunVLokiz and ftejada like this.
  6. blackbird

    blackbird

    Joined:
    Aug 9, 2011
    Posts:
    521
    what is included in the package
     
  7. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    It includes the source code (obviously), and several examples for performance comparison testing. Full documentation can be found here.
     
    blackbird likes this.
  8. blackbird

    blackbird

    Joined:
    Aug 9, 2011
    Posts:
    521
    the zombie example is no where to be found , which the main reason why i bought it
     
  9. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Yes I am still working on building a suitable example to include in the asset.
     
  10. blackbird

    blackbird

    Joined:
    Aug 9, 2011
    Posts:
    521
    i see , i thought it was included , how long will take you to deliver it ?
     
  11. witcher101

    witcher101

    Joined:
    Sep 9, 2015
    Posts:
    470
    Does this work with webgl??
     
  12. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Yes, though no multi-threading support for crossfading as webgl doesn't support threads.
     
  13. Mateusz_Wenkly

    Mateusz_Wenkly

    Joined:
    Sep 18, 2018
    Posts:
    1
    Hello. I just noticed that when you use frame skip and expose transform, the transforms which you expose do not move as smooth as the animation. Is there any workaround for this?
     
  14. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    I will need to make a fix for this and will PM you the update.

    EDIT: @Mateusz_Wenkly PM'd you a fix. Everyone else this is fixed in version 1.5.8 which is currently under review.
     
    Last edited: Dec 20, 2018
  15. blackbird

    blackbird

    Joined:
    Aug 9, 2011
    Posts:
    521
    still no zombie example ? if you don't plan to release it , could you refund me please
     
  16. Archie747

    Archie747

    Joined:
    Jan 7, 2015
    Posts:
    14
    Hi, Im interested in this mesh animator asset
    I want to implement this in my android & ios RTS game,
    so, can it work on mobile if I have around 30 different characters?
    or how many maximum different characters will work with this asset?

    thanks
     
    Last edited: Dec 23, 2018
  17. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    The more characters you have, the greater amount of memory is required. If they are very simple meshes, and have only 1 or 2 animations, it might be okay. The system stores each frame of animation as a separate mesh, so memory requirements can grow quite rapidly. You can estimate how much you would need by putting one of your characters in an empty scene, then open the profiler in the memory section, switch to Detailed, and take a snapshot. Examine how much memory is required for your mesh.

    upload_2018-12-28_12-25-48.png

    So in this case 0.5MB is used by 2 references, so about 290KB per frame of animation for that mesh when baked when Mesh Animator.
    upload_2018-12-28_12-27-13.png
    (each frame of animation takes up 290 KB of memory)

    So in your case it would be something like: number of animation total frames * 30 characters * memory requirement for each mesh. That should give you a rough estimate of what would be required to use this system.
     

    Attached Files:

  18. Archie747

    Archie747

    Joined:
    Jan 7, 2015
    Posts:
    14
    Great thanks!..
    forgot to ask, what is the requirement for mobile, does the asset will works with OpenGLES 2.0?
    or what is the minimum requirement for mobile, thank you..
     
  19. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    There are no special requirements as far as Graphics API, unless you are attempting to use GPU instancing. MeshAnimator itself doesn't use any custom shaders or materials.
     
  20. HenryChinaski

    HenryChinaski

    Joined:
    Jul 9, 2013
    Posts:
    79
    I hope this question was not answered before (I looked through the thread).
    I use MeshAnimator in a project due to the aesthetics provided by small compression accuracy.
    I noticed, that if I use the Mecanim + MeshAnimator component, there is no blending / fading between the single animations for idle, walk etc. I saw the "Crossfade" bool on the component, but it doesn't seem to do anything.
    What am I missing? Is this expected behavior?
     
  21. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    The default blend time when calling the Crossfade method blend time is only 0.1 seconds, it's possible that if you're using the MecanimMeshAnimatorController, it uses that. You might want to try editing that script and pass in your own desired crossfade duration so that it can be observed better.
     
    JBR-games likes this.
  22. HenryChinaski

    HenryChinaski

    Joined:
    Jul 9, 2013
    Posts:
    79
    That totally fixed it. Thanks!
     
    JBR-games likes this.
  23. Ardinius

    Ardinius

    Joined:
    Oct 4, 2015
    Posts:
    46
    I haven't played with Mesh Animator in a while so please forgive me, just comming back to it.
    Is there anyway to override how shadows are handled?
    As I still want the Units to receive and cast shadows when near the camera, but with so many Units my framerate tanks with the Quality settings of shadows set to Ultra, its ok with Very High but then the rest of the scene suffers.
    I was wondering if there was a way to over ride shadow settings for Mesh Animator objects so they have their own distance?

    Or if not is their a hook in for LOD, so if model in a certain LOD it changes shadow casting to off?

    Or another method perhaps?

    great asset.
     
  24. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    What you could do using Unity's built in LOD system, use the same mesh animator prefab at each level, but on the ones further away just turn of shadows on those MeshRenderer's. That will accomplish what you're trying to do.
     
    JBR-games likes this.
  25. Black_Raptor

    Black_Raptor

    Joined:
    Nov 3, 2014
    Posts:
    94
    Hi !

    I would like to know before purchase this asset if he's compatible with unity 2018.3+.
    You said :
    "• Can be controlled using Mecanim, no need to re-write existing code or controllers, just attach a script."
    So it's work with any animator ? Blendtree is supported ?
     
  26. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Hey,

    Yes, 2018.3+ is supported. Blendtree's are not supported as the animations are prebaked and cannot be blended together.
    The mecanim controller script will take the active state and try and play it on the newly baked prefab, but since mecanim isn't really needed, it's probably best to write your own controller to avoid the unnecessary overhead and get better performance.
     
  27. Black_Raptor

    Black_Raptor

    Joined:
    Nov 3, 2014
    Posts:
    94
    And the zombie example is still not included ?
     
  28. HenryChinaski

    HenryChinaski

    Joined:
    Jul 9, 2013
    Posts:
    79
    Hey,

    unfortunately, I have another problem.
    I use the MecanimMeshAnimatorController component and changed the crossfade time to a reasonable amount if you want to blend between idle, walking etc.
    Now, in the exact frames where the crossfading takes place, the material of the model becomes darker and overall noticeably changes in a way. I am not really sure, but I believe it has something to do with the normals recalculating. I already checked the "instant normals calculation" checkbox of the respective animations, but it did not change anything.
    Has it maybe something to do with me using a compression accuracy of 20?
    Or am I missing something else?
     
  29. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    I cannot include the zombie demo in my asset because it uses a paid versions of several other assets. The zombie controller itself isn't anything special, it just uses Unity's navigation system and some simple logic. Here's the controller for it:
    Code (CSharp):
    1. using FSG.MeshAnimator;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.AI;
    6.  
    7. public class ZombieController : MonoBehaviour
    8. {
    9.     private static WaitForSeconds s_tick;
    10.     private static WaitForSeconds s_attackTick;
    11.     private static WaitForSeconds s_deathTick;
    12.     private static Vector3 s_playerPosition;
    13.     private static bool s_isFirst = false;
    14.     [RuntimeInitializeOnLoadMethod]
    15.     private static void Init()
    16.     {
    17.         Application.targetFrameRate = 60;
    18.         s_tick = new WaitForSeconds(0.25f);
    19.         s_attackTick = new WaitForSeconds(2f);
    20.         s_deathTick = new WaitForSeconds(5f);
    21.     }
    22.  
    23.     private enum State
    24.     {
    25.         Idle,
    26.         Dead,
    27.         Wandering,
    28.         TargetPursue,
    29.         Attack,
    30.         Hit
    31.     }
    32.     [SerializeField]
    33.     private float m_agroDistance = 5f;
    34.     [SerializeField]
    35.     private float m_crawlDistance = 5f;
    36.     [SerializeField]
    37.     private float m_attackRange = 0.5f;
    38.     [SerializeField]
    39.     private float m_walkSpeed = 0.5f;
    40.     [SerializeField]
    41.     private float m_runSpeed = 5f;
    42.     [SerializeField]
    43.     private int m_health = 5;
    44.     [SerializeField]
    45.     private GameObject m_animatorPrefab;
    46.     [SerializeField]
    47.     private GameObject m_impact;
    48.  
    49.     private NavMeshAgent m_agent;
    50.     private State m_state;
    51.     private MeshAnimator m_animator;
    52.     private int m_currentHealth;
    53.     private float m_agroSqr;
    54.     private float m_crawlSqr;
    55.     private float m_attackSqr;
    56.  
    57.     protected virtual void Awake()
    58.     {
    59.         m_agent = GetComponent<NavMeshAgent>();
    60.         m_agent.avoidancePriority = Random.Range(-1000, 1000);
    61.         m_agent.obstacleAvoidanceType = ObstacleAvoidanceType.NoObstacleAvoidance;
    62.         m_agroSqr = m_agroDistance * m_agroDistance;
    63.         m_crawlSqr = m_crawlDistance * m_crawlDistance;
    64.         m_attackSqr = m_attackSqr * m_attackSqr;
    65.         if (!s_isFirst)
    66.         {
    67.             s_isFirst = true;
    68.             Recycler.Pool(m_animatorPrefab, 1000);
    69.             FindObjectOfType<ZombieSpawner>().StartCoroutine(UpdateStatics());
    70.         }
    71.     }
    72.     private void OnEnable()
    73.     {
    74.         Recycler.Instantiate(m_animatorPrefab, transform.position, transform.rotation, transform);
    75.         m_animator = GetComponentInChildren<MeshAnimator>();
    76.         if (m_animator != null)
    77.         {
    78.             m_animator.PrepopulateCrossfadePool(1000);
    79.         }
    80.         m_currentHealth = m_health;
    81.         StartCoroutine(Tick());
    82.     }
    83.     private IEnumerator UpdateStatics()
    84.     {
    85.         Transform player = GameObject.FindGameObjectWithTag("Player").transform;
    86.         var tick = new WaitForSeconds(0.1f);
    87.         while (player != null)
    88.         {
    89.             s_playerPosition = player.position;
    90.             yield return tick;
    91.         }
    92.     }
    93.     private IEnumerator Tick()
    94.     {
    95.         m_state = State.Idle;
    96.         var m_collider = GetComponent<Collider>();
    97.         m_agent.obstacleAvoidanceType = ObstacleAvoidanceType.NoObstacleAvoidance;
    98.         while (enabled)
    99.         {
    100.             yield return s_tick;
    101.             m_collider.enabled = m_state != State.Dead;
    102.             switch (m_state)
    103.             {
    104.                 case State.Idle:
    105.                     {
    106.                         while (Random.value > 0.25f && m_state == State.Idle)
    107.                         {
    108.                             StopAgent();
    109.                             PlayAnimation("idle");
    110.                             yield return s_tick;
    111.                         }
    112.                         m_agent.enabled = true;
    113.                         Vector3 pos = transform.position + new Vector3(Random.Range(-20f, 20f), 0, Random.Range(-20f, 20f));
    114.                         NavMeshHit hit;
    115.                         NavMesh.SamplePosition(pos, out hit, 1000, -1);
    116.                         m_agent.SetDestination(hit.position);
    117.                         m_state = State.Wandering;
    118.                         break;
    119.                     }
    120.                 case State.Wandering:
    121.                     {
    122.                         m_agent.speed = m_walkSpeed;
    123.                         while (m_agent.hasPath && m_state == State.Wandering)
    124.                         {
    125.                             if (m_agent.velocity.magnitude < 0.1f)
    126.                             {
    127.                                 PlayAnimation("idle");
    128.                             }
    129.                             else
    130.                             {
    131.                                 PlayAnimation("walk");
    132.                             }
    133.                             if (DistanceToPlayer() < m_agroSqr)
    134.                             {
    135.                                 m_agent.SetDestination(s_playerPosition);
    136.                                 m_state = State.TargetPursue;
    137.                                 break;
    138.                             }
    139.                             yield return s_tick;
    140.                         }
    141.                         if (m_state == State.Wandering)
    142.                             m_state = State.Idle;
    143.                         break;
    144.                     }
    145.                 case State.TargetPursue:
    146.                     {
    147.                         m_agent.speed = m_runSpeed;
    148.                         bool crawlOrRun = Random.value > 0.5f;
    149.                         string anim = crawlOrRun ? "crawl" : "run";
    150.                         m_agent.obstacleAvoidanceType = ObstacleAvoidanceType.LowQualityObstacleAvoidance;
    151.                         while (m_agent.hasPath && m_state == State.TargetPursue)
    152.                         {
    153.                             float distanceToPlayer = DistanceToPlayer();
    154.                             if (distanceToPlayer > m_agroSqr + 4)
    155.                             {
    156.                                 m_state = State.Idle;
    157.                                 break;
    158.                             }
    159.                             PlayAnimation(anim, 0.1f);
    160.                             m_agent.SetDestination(s_playerPosition);
    161.                             if (distanceToPlayer < m_attackSqr)
    162.                             {
    163.                                 m_state = State.Attack;
    164.                                 break;
    165.                             }
    166.                             yield return s_tick;
    167.                         }
    168.                         if (m_state == State.TargetPursue)
    169.                             m_state = State.Idle;
    170.                         m_agent.obstacleAvoidanceType = ObstacleAvoidanceType.NoObstacleAvoidance;
    171.                         break;
    172.                     }
    173.                 case State.Attack:
    174.                     {
    175.                         StopAgent();
    176.                         PlayAnimation("attack");
    177.                         transform.LookAt(s_playerPosition);
    178.                         yield return s_attackTick;
    179.                         m_state = State.Idle;
    180.                         break;
    181.                     }
    182.                 case State.Dead:
    183.                     {
    184.                         yield return s_deathTick;
    185.                         Cleanup();
    186.                         break;
    187.                     }
    188.                 case State.Hit:
    189.                     {
    190.                         StopAgent();
    191.                         yield return s_tick;
    192.                         m_agent.enabled = true;
    193.                         m_agent.SetDestination(s_playerPosition);
    194.                         m_state = State.TargetPursue;
    195.                         break;
    196.                     }
    197.             }
    198.         }
    199.     }
    200.     private float DistanceToPlayer()
    201.     {
    202.         Vector3 pos = transform.position;
    203.         if (s_playerPosition.y - pos.y > 5)
    204.             return float.MaxValue;
    205.         return (transform.position - s_playerPosition).sqrMagnitude;
    206.     }
    207.     private void Damage(Vector3 point)
    208.     {
    209.         m_currentHealth--;
    210.         if (m_currentHealth <= 0)
    211.         {
    212.             StopAgent();
    213.             GetComponent<Collider>().enabled = false;
    214.             m_state = State.Dead;
    215.             string anim = Random.value > 0.5f ? "death1" : "death2";
    216.             PlayAnimation(anim);
    217.         }
    218.         else
    219.         {
    220.             StopAgent();
    221.             transform.LookAt(s_playerPosition);
    222.             m_state = State.Hit;
    223.             PlayAnimation("hit");
    224.         }
    225.         Recycler.Instantiate(m_impact, point);
    226.     }
    227.     private void StopAgent()
    228.     {
    229.         if (m_agent.enabled)
    230.         {
    231.             m_agent.isStopped = true;
    232.             m_agent.enabled = false;
    233.         }
    234.     }
    235.     protected virtual void Cleanup()
    236.     {
    237.         if (m_animator != null)
    238.             Recycler.Recycle(m_animator.gameObject);
    239.         Recycler.Recycle(gameObject);
    240.     }
    241.     protected virtual void PlayAnimation(string animation)
    242.     {
    243.         m_animator.Crossfade(animation, 0.15f);
    244.     }
    245. }
     
  30. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Yes it is most likely a normal calculation issue. On the MeshAnimator component itself, have you tried turning off recalculateCrossfadeNormals?
     
  31. HenryChinaski

    HenryChinaski

    Joined:
    Jul 9, 2013
    Posts:
    79
    That did the trick! Didn't know about this option!
    Thanks!
     
  32. mdotstrange

    mdotstrange

    Joined:
    Sep 23, 2013
    Posts:
    211
    Hello- great asset- works well and its easy to use :)

    I read the docs but still have some questions-

    Let's say I have a model I'm using with 800 verts- if I'm using MeshAnimator with an accuracy of 0.1- will that use up less memory than the full 800 vert model would? Or is the memory usage based on the base model that we use to generate the animations?

    Let's say my animation is 8 seconds long- but I skip every 3 frames and run it at 15fps- Is it going to use less memory than the full 8 second clip?

    The reason I ask is because the doc's state "The best use of Mesh Animator is with low-poly models with short animations"

    So would a long clip that is optimized using frame skipping and lowering the frame rate be considered a "short animation"?

    If I have a model I plan to instantiate several times but each with a different idle animation- what would be more performant- to use one mesh animator model with several animations on it? or a separate mesh animator model- each one with one animation on it?

    Thank you.
     
    Last edited: Jun 29, 2019
  33. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Runtime memory is determined by your original mesh, so however much memory that takes, is how much each frame will take.

    Frame skip and compression accuracy only effect the size of the baked files.

    So if you want to use less memory, reduce vertex counts or if you can shorten animations as much as possible.

    Using a single mesh animator with multiple animations will be slightly more performant, but have no real effect on memory usage.
     
    JBR-games and mdotstrange like this.
  34. mdotstrange

    mdotstrange

    Joined:
    Sep 23, 2013
    Posts:
    211
    Thank you for the quick answers.
     
  35. p_hergott

    p_hergott

    Joined:
    May 7, 2018
    Posts:
    179
    Just real quick. Using synty models. Explosives rpg animations. For making a army of ... x things. Like goblins for example. That have scripts and colliders. Having, 10 animations each. Would this work? And be easy to set up?
     
  36. Carterryan1990

    Carterryan1990

    Joined:
    Dec 29, 2016
    Posts:
    76
    I did so by simply looking into the unity docs! All of the scripts are there already smh
     
  37. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Sorry you feel this way. But there's 100s of satisfied customers that have used my tool in production games and projects. I'd be happy to issue you a refund.
     
  38. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    357
    Yes it should work well with those models. The tool simply replaces the animator and the skinned mesh renderer so all of you colliders and scripts will continue to work.
     
  39. Carterryan1990

    Carterryan1990

    Joined:
    Dec 29, 2016
    Posts:
    76
    You know what that was rude of me I’m sorry. But I still believe there are far easier and more efficient solutions as stated I was able to do so and have 1 million characters with 200 FPS... When I used your system which I was never able to use it in release because the quality was just not there I wasnt able to notice a big difference between unitys and yours.. Anyways good luck no refund needed. If you like I can share how I did mine and maybe you can rebuild yours on it...
     
    Last edited: Aug 6, 2019
  40. p_hergott

    p_hergott

    Joined:
    May 7, 2018
    Posts:
    179
    Well i played with it for a few minutes. Just using defaults. I was able to run 500 characters running around the screen. So thats pretty cool.
     
  41. p_hergott

    p_hergott

    Joined:
    May 7, 2018
    Posts:
    179
    Thats cool and all. But most assets dont help ppl achieve the impossible or anything, they typically help simplify, and reduce production times. I personally wouldnt know where to start on replicating this, could i learn, sure. Like a model, i could spend a month in blender making a character, but id rather spend a few bucks and just get one. Like dont get me wrong 74k characters, is pretty sweet.