Jump to content
  • Advertisement
  • 08/07/17 05:55 AM

    Unity
    How We Optimized Ragdoll Death Animation in Unity

    General and Gameplay Programming

    tatar1nro

     

    b487e1e23144495daf3602ad0ff4f85b.png

    Hello, everyone.  We are a small indie game studio called Drunken Monday.

    We’ve recently made a game in Unity3d where you run around an arena, rotate around yourself with the huge axe and try to hit other players. Good smash - good kill.

    We used ragdoll animation based on physics to make the deaths look more realistic. And everything was good in the beginning...

    However, when the number of the characters and calculations started growing, the game began working slowly and lagging on older phones. Disabling all physics calculation gave us 50-60 fps and absolute smoothness, but we didn't want to give up the cool ragdoll deaths of the characters.
     
    One of the solutions was to force the animators to build a pack of death animations. But we had a great idea to record a few ragdoll deaths directly in Unity and then just show the desired animation? Deaths will turn out to be diverse, there will no need to occupy animators, and the most important - everything will be fast, beautiful and realistic.

    What we get:

    0be4fedf8343481d8f1c74d0b9d8725b.gif

    Implementation

    The animation clip in the Unity is presented by the AnimationClip class, which contains an array of AnimationCurve. AnimationCurve defines the curve for the changes of one particular property of a particular object, for example, the localPosition.x. Values change in the timeline are described by a number of Keyframe structures.

    84c5a004b39744839d2a941890c823c0.png

    The idea is simple: for the each property of the each character object we create an AnimationCurve and store the values of this property on the curve for the each frame. The generated AnimationClip is exported through  AssetDatabase.CreateAsset at the end.

    Let's create the class AnimationRecorderItem to track the each character object. All properties of the monitored object will be described through the dictionary, where the keys are the name of the properties and the values are the animation curves.

    Properties = new Dictionary<string, AnimationCurve> ();
     
    Properties.Add ( "localPosition.x", new AnimationCurve () );
    Properties.Add ( "localPosition.y", new AnimationCurve () );
    Properties.Add ( "localPosition.z", new AnimationCurve () );
     
    Properties.Add ( "localRotation.x", new AnimationCurve () );
    Properties.Add ( "localRotation.y", new AnimationCurve () );
    Properties.Add ( "localRotation.z", new AnimationCurve () );
    Properties.Add ( "localRotation.w", new AnimationCurve () );
     
    Properties.Add ( "localScale.x", new AnimationCurve () );
    Properties.Add ( "localScale.y", new AnimationCurve () );
    Properties.Add ( "localScale.z", new AnimationCurve () );

    For all of the object properties in each frame will be set their current values:

    Properties["localPosition.x"].AddKey (new Keyframe (time, _animObj.localPosition.x, 0.0f, 0.0f));
    Properties["localPosition.y"].AddKey (new Keyframe (time, _animObj.localPosition.y, 0.0f, 0.0f));
    Properties["localPosition.z"].AddKey (new Keyframe (time, _animObj.localPosition.z, 0.0f, 0.0f));
     
    Properties["localRotation.x"].AddKey (new Keyframe (time, _animObj.localRotation.x, 0.0f, 0.0f));
    Properties["localRotation.y"].AddKey (new Keyframe (time, _animObj.localRotation.y, 0.0f, 0.0f));
    Properties["localRotation.z"].AddKey (new Keyframe (time, _animObj.localRotation.z, 0.0f, 0.0f));
    Properties["localRotation.w"].AddKey (new Keyframe (time, _animObj.localRotation.w, 0.0f, 0.0f));
     
    Properties["localScale.x"].AddKey (new Keyframe (time, _animObj.localScale.x, 0.0f, 0.0f));
    Properties["localScale.y"].AddKey (new Keyframe (time, _animObj.localScale.y, 0.0f, 0.0f));
    Properties["localScale.z"].AddKey (new Keyframe (time, _animObj.localScale.z, 0.0f, 0.0f));


    But if you record all values for the each frame for the each property of the each object, the output file of the animation will turn out to be too large. Let's introduce the conditions for limiting the minimum changes in comparison with the previous frame. If the object has moved, increased and turned just a little bit, we will not record these changes.

    Completed class: AnimationRecorderItem.cs

    Also, we have to create a manager class AnimationRecorder.

    This script should be executed through the all children of the animated object and create an instance of AnimationRecorder for each of them.
    And also immediately generate and remember relativePath under which it will be saved in AnimationClip.

    According to the documentation,  relativePath is generated as follows:

    Quote

    Path to the game object this curve applies to. The relativePath is formatted similar to a pathname, e.g. "root/spine/leftArm". If relativePath is empty it refers to the game object the animation clip is attached to.


    The code will look like:

    private List<AnimationRecorderItem> _recorders;
     
    void Start ()
    {
        Configurate ();
    }
     
    void Configurate ()
    {
        _recorders = new List<AnimationRecorderItem> ();
     
        var allTransforms = gameObject.GetComponentsInChildren< Transform > ();
        for ( int i = 0; i < allTransforms.Length; ++i )
        {
            string path = CreateRelativePathForObject ( transform, allTransforms [ i ] );
            _recorders.Add( new AnimationRecorderItem ( path, allTransforms [ i ] ) );
        }
    }
     
    private string CreateRelativePathForObject ( Transform root, Transform target )
    {
        if ( target == root )
        {
            return string.Empty;
        }
     
        string name = target.name;
        Transform bufferTransform = target;
     
        while ( bufferTransform.parent != root )
        {
            name = string.Format ( "{0}/{1}", bufferTransform.parent.name, name );
            bufferTransform = bufferTransform.parent;
        }
        return name;
    }

     

    To calculate current animation time and record the properties values for the each frame:

    private float _recordingTimer;
    private bool _recording = false;
     
    void Update ()
    {
     
        if ( _recording )
        {
            for ( int i = 0; i < _recorders.Count; ++i )
            {
                _recorders [ i ].AddFrame ( _recordingTimer );
            }
            _recordingTimer += Time.deltaTime;
        }
    }

     

    But the  Update function is called quite often and recording the animation every frame is pretty redundant, so we limit the record. 30 fps should be enough for everyone. 
    We will start recording by tapping on Spacebar.

    private const float CAPTURING_INTERVAL = 1.0f / 30.0f;
     
    private float _lastCapturedTime;
    private float _recordingTimer;
    private bool _recording = false;
     
    void Update ()
    {
        if ( Input.GetKeyDown ( KeyCode.Space ) && !_recording )
        {
            StartRecording ();
            return;
        }
     
        if ( _recording )
        {
            if (_recordingTimer==0.0f||_recordingTimer-_lastCapturedTime>=CAPTURING_INTERVAL)
            {
                for ( int i = 0; i < _recorders.Count; ++i )
                {
                    _recorders [ i ].AddFrame ( _recordingTimer );
                }
                _lastCapturedTime = _recordingTimer;
            }
            _recordingTimer += Time.deltaTime;
        }
    }
     
    public void StartRecording ()
    {
        Debug.Log ( "AnimationRecorder recording started" );
        _recording = true;
    }


    Let’s implement an animation export. We will create the AnimationClip instance and fill it with the collected values.

    private void ExportAnimationClip ()
    {
        AnimationClip clip = new AnimationClip ();
        for ( int i = 0; i < _recorders.Count; ++i )
        {
            Dictionary<string,AnimationCurve> propertiles = _recorders [ i ].Properties;
            for ( int j = 0; j < propertiles.Count; ++j )
            {
                string name = _recorders [ i ].PropertyName;
                string propery = propertiles.ElementAt ( j ).Key;
                var curve = propertiles.ElementAt ( j ).Value;
                clip.SetCurve ( name, typeof(Transform), propery, curve );
            }
        }
        clip.EnsureQuaternionContinuity ();
     
        string path = "Assets/" + gameObject.name + ".anim";
        AssetDatabase.CreateAsset ( clip, path );
        Debug.Log ( "AnimationRecorder saved to = " + path );
    }


    Completed class AnimationRecorder.cs

    Finally, we will create the  AnimationRecorderRagdollHelper helper class, which function will stop the Animator on the animated object, turn on all collisions, give the object acceleration and start recording our animation. The end of the animation recording will be completed by ourselves. 
    To avoid some artefacts due to scene loading and the initialization of various objects, the script will start working with the specified delay.
     

    Completed class AnimationRecorderRagdollHelper.cs
      
    That's all, we add AnimationRecorderRagdollHelper on our character, set the impact force, then start the scene - and watch how the character cheerfully flies around the scene.

    When the cold corpse freezes on the ground - press Spacebar. 

    e3abe0da93de49199428861d8900c607.gif

     

    The script will export our animation to the root of the project.

    001d0ca5d99d44638bcf44758fb23f37.png

     

    RecklessFickleBarracuda.gif


    https://gfycat.com/RecklessFickleBarracuda

    We recorded 4-5 animations for each character in this way and choose between them randomly when the character dies.
     
    P. S. Or not quite randomly.
    Our game is multiplayer, physics is calculated on the server and the vector of impact comes to us. So we select the necessary animation based on the vector which comes to us from the server, simply looking for the closest vector which the animation was recorded.
     
    Links:
    Project on GitHub

    " rel="external nofollow">A video of the game on YouTube with some deaths

    Slash Arena: Online ( Facebook )

    Slash Arena: Online ( Steam )

     



      Report Article


User Feedback


I've got a feeling you'll see a pretty big performance increase if you use a custom data structure rather than a dictionary. Dictionary string lookups are really slow, and I don't see any reason why it's necessary here as opposed to simply having a class with those AnimationCurves as a set of fields rather than values in dictionaries.

Share this comment


Link to comment
Share on other sites


Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
  • Game Developer Survey

    completed-task.png

    We are looking for qualified game developers to participate in a 10-minute online survey. Qualified participants will be offered a $15 incentive for your time and insights. Click here to start!

    Take me to the survey!

  • Advertisement
  • Latest Featured Articles

  • Featured Blogs

  • Advertisement
  • Popular Now

  • Similar Content

    • By XenahE
      Hi there, 
      I am new to this forum and wanted to say hi first of all. 
      Secondly, I am in the process of creating a vertical 2d shooter using the assets I am creating on photoshop and illustrator. I want to put a scrolling parallax in the background to give the illusion that the player is moving forward (similar to Skyforce Anniversary) but I am not sure how best to create the graphics in terms of the document size etc. I have used a scrolling parallax before using a 3D game object in unity and wrapping a background to it. This works fine for a repeating background but I would like mine to change throughout the level. 
      Has anyone got any experience with doing something like this?
      Thanks in advance 
      Xe
    • By RoKabium Games
      The red hued resources you can find in SAMA is mostly on the pink and brown side of red, but the Quarky is as bright and deep red as it comes!
    • By Ruben Torres
      [This post was originally posted with its original formatting at The Gamedev Guru's Blog]
      If you've been following me, you will probably know my interest in Unity Addressables. That is for a reason.

      Unity Addressables is a powerful Unity package that upgrades the way you and I have been tackling some of the most important challenges in Game Development: efficient, pain-free content management.
      When managing your game assets, it's hard to keep good standards that prevent our project from becoming a disgusting pile of mess. A big issue there is the coupling between the different responsibilities of our asset management systems.
      The way we store the assets in our project has way too much to do with the method we load them, and later use them.
      For instance, you may decide to store an innocent sprite in the Resources folder. This, in turn, will force Unity to build the player in a way that that sprite is put into special archive files. And the fact that it was put there, will corner you into loading it through the Resources API.
      Things get messy quicker than you can realize!
      One choice, multiple long-term consequences.
      A good system will prevent you and me from easily making sloppy mistakes like that. A great system will be that, and also easy to learn and use.
      With Unity Addressables, we separate the asset management concerns. Our goal is to remain flexible and to keep our project maintainable.
      Here are 3 proven ways Unity Addressables will help you and your games:

      1. Reduce Your Game's Memory Pressure
      When you publish your game, you'll be required on most platforms to specify the minimum hardware specifications your players must meet to buy and play your game.
      The math is easy here: the more hardware power you demand, the fewer will buy your game. Or, seen from another perspective, the better memory management you do, the higher the amount of content and fun you can offer in your game.
      Unity Addressables helps you in this regard enormously!
      To give you a brief idea, converting this kind of code:
      public class CharacterCustomization : MonoBehaviour { [SerializeField] private List<Material> _armorVariations; [SerializeField] private MeshRenderer _armorRenderer; public void ChangeArmorVariation(int variationId) { _armorRenderer.material = _armorVariations[variationId]; } } Into this other one:
      using UnityEngine.AddressableAssets; public class CharacterCustomizationV2 : MonoBehaviour { [SerializeField] private List<AssetReference> _armorVariations; [SerializeField] private MeshRenderer _armorRenderer; public IEnumerator ChangeArmorVariation(int variationId) { var loadProcess = _armorVariations[variationId].LoadAssetAsync(); yield return loadProcess; _armorRenderer.material = loadProcess.Result; } } Will bring you these results:

      Easy gains I'd say.
      -> Read more on Unity Addressables for better memory management in Unity Addressables: It's Never Too Big to Fit (opens in a new tab)
       

      2. Sell Your Next DLC - Quick and Easy
      The fact that Unity Addessables gives you full control over how, when and where to store and load your game assets is incredibly useful for implementing and selling Downloadable Content.
      Even if you are not thinking of releasing DLCs any time soon, just by using Unity Addressables in your project, you will have done already a big chunk of the work ahead.
      Other approaches for selling DLCs, such as Asset Bundles, are a very deprecated way of doing the same thing but at a much higher cost. Maintaining a well-functioning Asset Bundle pipeline is painfully time-consuming and requires a high degree of expensive expertise.
      There are many ways you can approach implementing DLCs in Unity, but for starters, this is a good starting point:
       
      public class DlcManager : MonoBehaviour { // ... public IEnumerator TryDownloadDlc() { if (_hasBoughtDlc && _dlcDownloaded == false) { var operationHandle = Addressables.DownloadDependenciesAsync("DLC-Content"); while (operationHandle.IsDone == false) { _progressText.text = $"{operationHandle.PercentComplete * 100.0f} %"; yield return null; } } } } You get the idea.
      Why would you say no to selling more entertainment for your players at a fraction of the cost?

      3. Reduce Your Iteration Times
      Using Unity Addressables will reduce the time wasted waiting in several areas.
      Tell me, how frustrating is it to be blocked for half a minute after pressing the Unity play button? And it only gets worse if you deploy your build on another platform, such as mobile or WebGL. This all starts adding minutes and minutes to your iteration times. It gets old so quickly.
      I don't like waiting either.
      But do you know what I like? Unity Addressables, my long-awaited hero. This is how Addressables will help you:
      A) Reduced Build Size
      Your game has a lot of content, I get it. Gamers love enjoying content. Developers love creating content.
      That doesn't mean, however, that every single asset you produced has to be included in the build your players will install. In fact, you should remove as much as possible.
      Players want to start playing ASAP. And they're not happy when your game steals 2GB of their data plan and 30 minutes of their gaming time. They'll just keep downloading Candy Crush kind of games that install well under 50MB.
      One strategy is to include only the assets needed to run your game up to the main menu. Then, you can progressively download the rest of your content in the background, starting, of course, downloading the first level of your game.
      It's also neat to realize that your deployment times during development will be much faster. You'll be able to iterate more times each day; this benefit quickly adds up in the long term.

      Unity Addressables - Reduced Build Sizes
      B) Reduced Load Times
      We, both as game developers and as players, hate waiting. Waiting takes us out of the zone and before you realize it, it is time to go to bed.
      Unity is working hard towards reducing the time it takes us to start playing our games, both in the Unity Editor and in the games we distribute.
      But not hard enough.
      Things look promising in the future, but not without side effects. Avoiding domain reloads in Unity 2019.3 looks promising, but as of today that's still in beta and not everyone can profit from it.
      In the mean-time, we can do better than just being frustrated.
      Let's say you're working on a medieval game. Several months ago, you implemented armor types for your game. You did a pretty damn good job and generated over 100MB of content.
      At some point, it was time to move on and right now you're working on something else, let's say sword fighting.
      Realize that, every time you press the play button to work on your features, you are loading an insane amount of data coming from all the already developed features, and loading this data takes a massive amount of time. You press play to test your sword fighting animations, and you spend 5 seconds waiting due to loading the armor features you implemented.
      The time wasted in loading is mostly spent on I/O (Input/Output), because memory bandwidth is expensive. And, on top of that, your CPU has to process it. You, as a developer, pay this time penalty while developing in the Unity Editor. But your players pay it as well in the games you are distributing.
      Knowing how big of a deal this can be, let's ask ourselves: which shortcuts can we take here?
      It turns out that Unity Addressables can help us here in two ways.
      1. Unity Addressables will reduce your Players' Loading Times
      We can alleviate some of our players' pain.
      Keeping indirect references to our assets instead of direct references will drastically improve your loading times.
      By using indirect references (AssetReference), Unity will not load everything at once but only what you tell it to. And more importantly, you have direct control over when that happens.
      2. Unity Addressables will reduce your Unity Editor Iteration Times
      How much do you know about the play mode script in the Unity Addressables Window? The play mode script defines how the Unity Editor should load the content marked as Addressable.
      With Packed Play Mode selected, Unity will directly load your pre-built addressable assets with little to no processing overhead, effectively reducing your Unity Editor iteration times
      Just do not forget to build the player content for this to work

      Unity Addressables - Build Player Content
      What if you applied these strategies to your most demanding content?
      4. Extra: Are You There Yet?
      It is true. Unity Addressables is very helpful. But this package will help only those who want to be helped.
      After reading how Addressables will help you producing better and selling more to your players, you probably want to start with it right away. However, starting in this new unknown area may be challenging.
      To make the most of your chance, answer these questions first:
      Where are you standing right now? Are you just starting, or are your skills production-ready? When to use indirect references, when to use direct references? What's the bigger picture? What is your next logical step? → Take this short quiz now to test your answers ← (opens in a new tab)

    • By Ruben Torres
      [This article was originally posted at The Gamedev Guru's Blog]
      I normally do not share my past struggles, but this topic deserves it. What to do when you lose hope with Unity UI?
      I still remember that weekend, about five years ago...
      I spent the entire weekend worried about all the technical debt I was accumulating over the months in one of my projects. I had done things too quickly, without really understanding them. Clearly enough, that had to stop working at some point.
      The thing is, I owed my clients results but I had no idea how to bring them. That morning though, I said to myself: Rubén, this is enough. Stop the excuses. You are becoming a professional today.
      So, I decided to gather every single resource I could find about user interfaces in Unity, because that was the biggest hurdle I was dealing with for several weeks. I read forums, best practices, Unite videos, read all blogs I could find on that topic and did my own research.

      Did you ever experience this feeling of slowly getting overwhelmed by technical debt? It accumulates over time and someday you either give up or decide to crush it. In my case, it was the later.
      You probably heard that optimizing UI in Unity is challenging. Or, if lucky, you know it from first-hand experience. Yes, Unity UI is a powerful tool and I love it. However, its mightiness can quickly transform into a deadly weapon if it falls into inexperienced hands. And so were my hands back then.
      If I had to confess anything to you today, it would be this: I wished I had had a solid foundation on Unity UI before I designed the UI of several games.
      It is SO easy to do it wrong, SO easy to get frustrated. In my opinion, there's something even easier than that. That is, for your client to ask you to do over-hours to get your shit together.
      In my experience in the professional sector, companies do not want developers who design poorly optimizable interfaces and leave the tuning task to experienced programmers. They rather want developers who create visually stunning interfaces, but also performant.
      And even in the indie development scene, where would you choose to spend your time on? Bringing fun to your players, or spending hours clicking through the profiler to find bottlenecks?
      In my opinion: no matter your role, you should understand basic Unity UI design principles. But this takes time and project experience. You might not have any of these.
      Hopefully, I can help you there.
      Today, I will give you one of my most powerful tools:
      The Guru's UI Development Diagram
      As usual, I'd like to start with the Level 1 Developer construct
       
      Level 1 Unity UI Developer: UpdateBatches & Layout Spikes in Unity UI
      Level 1 Unity UI Developer: free-style UI
      This is where we all start: free-style design, free-style results.
      I used to import sprites with whatever settings. Then, I would add them as images everywhere with no predefined criteria. And so I ended up with an unstructured hierarchy full of components that I didn't need. I used the wrong systems for the wrong reasons.
      Overlapping UI elements, a motherload of batches, profiler spikes. Those are all common.
      Do I have anything against this?
      Partially. I think it is great to play around and to make mistakes. Breaking and repairing help learning.
      But if you want to do this for a living, at some point you might want to level up your skills and embrace professionalism.
      This becomes unacceptable in the games industry, especially in Virtual Reality. If you do this in VR you will turn players into patients.
      Such a chaotic Canvas hierarchy structure could look like this:

      Level 1 Unity UI Developer: Unstructured Unity UI Hierarchy
      You do a lot of nesting, you use auto-layout components everywhere and there is a lack of rules.
      At some point, someone will ask you to optimize your UI. That could be your players, your boss or even your own pride.
      But you might not be ready for this. You might be caught off-guard having other tasks on your plate. And UI development is daunting at the beginning.
      The reason for its complexity lies in the amount of factors it involves. Leaving the visual appeal aside, you can say that the way you work with Unity UI will have a big impact in three critical hardware pieces: the CPU, the GPU and the developer theirself.
      If you are lost, it's good, because...
      I am about to show you something cool
      Let's level up as a developer!
       
      Level 2 Unity UI Developer: The Guru's UI Development Diagram
      Level 2 Unity UI Developer: The Guru's UI Development Diagram
      Here we are with a Venn Diagram. I decided to call it The Guru's UI Development Diagram.
      There we see three big chunks related to Unity UI Optimization: CPU, GPU, and Developer. Those are the main resources you will have to spare when developing UI.
      I want to give you a short overview before we start digging into the topics.
      You pay the CPU toll mainly when generating and submitting batches of UI. You can think of them as UI components, such as images. The more of those you have, the more strain you will put in your processor.
      The turn for the GPU. You pay the GPU resources mostly in concept of overdraw. This means, stacking layers of graphics on top of each other.
      Lastly, the developer's pain is paid in time. You spend time every time you design or maintain your UI. Unity offers you some tools to make it easier for you, e.g. auto-layout helpers.
      The cost of the three components rely mostly on you, but they sometimes counteract each other. It's very challenging to perform in every aspect, unless you are very experienced and you have the right tools.
      Eventually, you will get there.
      I would like you to have a closer look at the attached Guru's UI Development Diagram.
      You might be confused. That's alright because we are going to cover each section separately. You will get to understand all of it.
      For now, you only have to understand one thing: in the end, it will be up to you to find the balance between the three variables in your game.
      Before we start with each component, have a look at these general rules:
      The easier you want the UI design workflow to be, the less performant your UI will be. If you want more GPU performance, you will have to focus on finely tweaking graphical elements and avoid element stacking. For more CPU performance, you will need to reduce the amount of graphical elements present in the hierarchy. Let's move on, I can't wait to show you the CPU side of Unity UI Optimization!
       
      Level 2 Unity UI Developer: The CPU
      Level 2 Unity UI Developer: The CPU
      In my opinion, this is very simple.
      The golden rule is:
      Every time you add a UI component in your scene, you are adding CPU overhead
      Each component increases the CPU cost for the following reasons:
      RectTransform calculation: this is pretty much free in simple canvas hierarchies. However, your CPU will have a harder time if you are using auto-layout components to ease your UI design workflow, such as vertical layout groups and such. The issue with those is that most RectTransforms present in the hierarchy will now depend on each other, so the calculation becomes more complex. Vertex generation: the GPU understands vertices, thus those must be provided by the CPU. Vertices must be generated based on the entire canvas hierarchy of the involved RectTransform elements. The vertices depend as well on the specific type of UI element you added (images, texts). Under this category, we also include the cost for the CPU to add other per-vertex attributes such as colors and UVs. Dynamic batching: because draw calls are expensive, Unity does its best to dynamically batch as many of them as possible.
      Think of this as combining a large number of small groups of vertices into a single, huge group of vertices. Because... would you rather go to the supermarket ten times to bring an apple each, or go just once and grab them all together?
      Batching is, however, an expensive operation for the CPU which increases with the amount of UI elements you have. Draw call emission: finally, the generated batches must be sent to the GPU. These graphical processing units are very picky with the data formats they accept, so the CPU has to make extra work to pack them in a way that they like. This is expensive, yes. But luckily for us, Unity is kind of smart and caches the canvas so these expensive processes do not happen in every frame.
      Just as I brought you good news, I will give you the bad ones: this cache is super easy to break.
      Changing almost any attribute of any UI element will break the cache of your canvas. If that happens, we go through most of the process again. These changes, by the way, trigger what is called a canvas rebuild.
      I'll give you some examples of the breaking changes: scaling, positioning, rotation, color, image swapping, animations and more.
      After exposing the basics, I want to conclude with three practical observations:
      The more elements you have in your Unity Canvas, the more CPU overhead you will have on each canvas rebuild. Avoid any kind of changes in UI to avoid canvas rebuilds. If needed, then put dynamic elements in different canvases. Canvases are either static or dynamic. Design accordingly.  
      Level 2 Unity UI Developer: The GPU
      Level 2 Unity UI Developer: The GPU
      Now is the turn for the friendly Graphical Processing Unit, don't you think?
      The GPU is the piece of hardware that makes us fall in love with games. We owe them so much. What about showing it some affection? Well, we can...
      By treating it well.
      But guess what? UI can make the GPU pretty upset. Especially mobile GPUs, where it is relatively easy to be memory-bound. To put it simply:
      In mobile, there is a massive reason UI is expensive: overdraw!
      Overdraw happens when we render the same pixel multiple times per frame. And this is soooo easy to achieve with Unity.
      Every time to add a Unity UI Image, you are commanding your GPU to draw a full rectangle, rendering every single pixel inside that rectangle. And it does not matter if the pixel you are drawing is transparent, it will cost you still.
      That means, I advise you to be careful with the transparent regions of your sprites!
      This has some implications for you. If you add 10 full-screen images, then you get 10x overdraw. As my grandmother used to say, WTAIWYG! (What You Add Is What You Get).
      Stacking of layers on top of each other will slowly enrage your GPU. It will add milliseconds to it, which is a crime in VR games where your budget could very well be under 13ms.
      I do not want to get too technical in this post... but I can't just help it! Some day you will be having a delicious coffee break conversation with your colleagues and you might feel tempted to drop a few intellectual lines. If that's the case, say this one aloud:
      UI Overdraw is expensive in mobile because you devour the precious memory bandwidth of the device and this is further worsened by the alpha blending happening internally
      You'll make some jaws drop.
      The key lesson is easy: avoid stacking UI elements on top of each other and you will be fine 
      GPU cost mainly comes from stacking graphical layers on top of each other. Avoid these. Reduce the space UI takes in screen to reduce GPU cost. Implement sprite atlasing to improve CPU and GPU performance.  
      PRO TIP #1: Sprite Atlasing (opens in a new tab)
      PRO TIP #2: Opaque UI Shading (opens in a new tab; Advanced)
       
      Level 2 Unity UI Developer: The Developer
      Level 2 Unity UI Developer: The Developer!
      I am afraid we often under-estimate the effort required to design, implement and especially maintain optimized user interfaces in Unity.
      Surely, Unity improved in this area during the last few years and now it is easier and faster than ever to create those. But this comes at a cost.
      In general, the more Unity UI extras you use, the worse your game will perform.
      The auto-layout helpers are especially expensive. Unfortunately, they are the most helpful. For instance, I find Vertical Layout Groups especially handy when designing responsive UI. These neat scripts help you organize your elements in a manner that is structured and therefore visually appealing.
      The problem I see with those is that they incur on a big CPU cost whenever there are canvas rebuilds!
      You might be able to get away with UI helpers if your UI is static 99% of the time, but the frame it isn't, you will notice it.
      We love working with helpers, but we don't want to ruin our performance. Here's one trick you can try: keep the auto-layout helpers around while designing but disable them all just before saving your scene. By doing this, they will be deactivated for run-time players, but they already did their work for you!
      I don't want to disappoint you, so just remember that this auto-layout disabling secret will not work if your UI content is dynamic.
      At the end of the day, the more tasks you delegate to Unity, the more work your hardware will have to do.  Find your own balance here! You can start using those helpers but be ready to remove them if you have to.
      While optimizing, do the biggest gains for the buck first. Only advance in the ladder of diminishing returns if the profiler tells you that you have to.
      For instance, creating sprite atlases is easy and will bring you good fortune. Tweaking graphics to avoid transparent regions, however, is more time-consuming. Lastly, creating your own UI shaders might bring you big gains but at a big cost.
      Remember, saving a few microseconds in your game might not be worth if it costs you weeks of your life-time and your application does not need it. This should not be taken as an excuse to never care; optimizations in the future are more expensive than in the present.
      Profile before you optimize Keep profiling Go for the biggest gains for the buck first  
      Level 3 Unity UI Developer: The Guru's UI Development Diagram
      Level 3 Developer: The Sweet Spots
      From the Venn Diagram, you might see several sweet spots where you could find yourself in: Fine-Grained UI Development, Coarse-Grained UI Development, Massive PP and Balance.
      They sounded quite obscure to me at the beginning, but then, with time, it all started to make sense to me. Experience brought understanding.
      What I still hold true is that, the more tools you know, the better. This is the main way I am able to recognize the subtle patterns in all problems. And, when you recognize the heart of the challenge, you know which tool to take out of your toolbox.
      Nothing will help your team more than having a developer possessing both a high-level overview of UI and an eye for detail.
      A Level 3 developer would confidently answer important practical questions that only come through experience. I can think of a few that any client could ask you:
      Here's a UI architecture for you. How performant is it? What are 3 alternatives I have to optimize this user interface? Can you show me numerical proof?
        You see, I prepared a few Unity examples for you.
      The examples you will have access to illustrate each of the UI optimization methods I propose.
      Not only that, I analyzed them all so you can feel safe the information is accurate.
      Because... do you know what comes after speaking out of our gut feelings? Speaking with certainty, based on real experience.
      So, are you serious about becoming a Level 3 Unity UI Developer?
      ---> Grab Now the Level 3 Guide from my Blog Post.
    • By Ruben Torres
      [This article was originally posted in The Gamedev Guru's Blog]
      You are leading a team of a handful of programmers and artists to port a good-looking PS4 VR game to Oculus Quest. You have six months to complete it. Well? What's your first move? Let's bring Unity Addressables to the table.
      You do realize you have to tackle quite many challenging tasks at once. I bet some might be personally more concerning than others, depending on your expertise level in each area. If you had to pick one that robbed your sacred sleep the most, which one would it be?
      My initial guess would be this: above 70% of the readers would say CPU/GPU performance is the biggest concern when porting a title to Quest. And to that I say: you can very well be right. Performance is one of the toughest areas to improve on in a VR game. For optimizations of this kind, you require in-depth knowledge about the product, which is a time intensive process. Sometimes you even cannot just optimize further, which usually ends in dropping expensive gameplay or graphics features. And not meeting people's expectations is a dangerous place to be.
      Performance, performance, performance.. The word might let some chills go down your spine. What can you expect in this regard from the Quest platform? How does it perform? The thing is, if you have had some development experience in it, you will probably know that in spite of being mobile hardware, it is astonishingly powerful.
      But Ruben! Don't give me that crap. I tell you my phone slows down significantly the time I open a second browser tab. How dare you say mobile platforms can be so performant?
      I read your mind, didn't I? The massive difference lies on Quest's active cooling system, which gives it a huge boost on its attainable CPU/GPU hardware levels that no other mobile platform can offer. It is a powerful fan that will prevent your hair from gathering dust and avoid melting the CPU together with your face (the GoT crown scene comes to mind).
      Additionally and on the side of the Quest software, the more specialized OS is better optimized for virtual reality rendering (surprise) than the generic Android variant. Mobile hardware has been catching up with standalone platforms so quickly in the last few years.
      But, at the same time, I cannot deny that our task of constantly rendering at 72 fps will prove to be challenging, especially for VR ports coming from high-end platforms. To be more precise, when we talk about the Oculus Quest, you have to picture yourself a Snapdragon 835 with a screen, a battery, four cameras and a fan.
      What could look like a disadvantage can actually be thought of as an edge. This mobile platform is a well researched piece of s*** hardware. One can say there are a myriad of known tricks you can pull off to quickly reduce the CPU and GPU load up to an acceptable point. If it is of your interest, you will be able to read about it in upcoming posts. As of now, we will take performance out of the equation for this post.
      What might catch your attention in our challenge is that, compared to the PS4, there is a single hardware characteristic literally halved on the Quest: the RAM capacity. That's right, we go from 8 to 4GB RAM. This is an approximation since, in both platforms, the operating system does not allow you to use it all so it can keep track of a few subsystems for the ecosystem to work. On the Quest you will be able to roughly use up to 2.2 GB of RAM before things get messy.
      Ruben, what do you exactly mean by messy? The thing is, proper memory handling is crucial for your game. This is so because you have two constraints:
      Hard memory constraint: if you go above a certain threshold, the OS will just kill your game Soft memory constraint: above another certain limit, the OS might decide to kill your game when your user minimizes your game, takes the headset off or you go out of the Oculus Guardian area Obviously, you do not want any of the two to happen in your game. Can you picture an angry player who just lost his last two hours of gameplay? Yes, they will go to your store and nothing pretty will come out of their mouth.

      Disgruntled Player - RUN!
      The thing is, the guaranteed availability of 2.2GB of RAM is not much, honestly. It's usually not a problem for new projects where you track your stats constantly from the beginning, but it definitely is an issue for a port to a severely downgraded hardware.
      If you dealt with similar ports in the past, you will quickly realize how extremely challenging it can become to decrease your game's RAM budget by half. It grandly depends on how well the game architecture was prepared for such a change, but in most cases this will temporally transform you into a tear-producing machine.
      The most popular strategies to reduce memory pressure include tweaking asset compression settings, optimizing scripts, reducing shader variants, etc.. Depending on the specifics of your project, tweaking texture import settings is your first go-to solution, but if you need to you can also resort to compressing meshes, animations and audio. The problem is that those techniques are usually complex in nature and will have a ceiling limit.
      Not all platforms support the same import settings; targeting several devices will dramatically increase your build pipeline overhead, not to mention QA, art, design and programming complexity. Does this Android device support ASTC, or just ETC2 (if at all)? Oh, we also want to build 64 bit builds, but also keep the players on the 32 bit versions. How many split-APKs should we create, and worse, manage and test, for each update we do on the game? You want to make your life easier, so you should not rely exclusively on these techniques.
      We would like to go further. As usual, we want to keep things as simple as possible (TM), especially if you are doing a port. Redesigning the entire game for performance's sake is a worse option than just not porting it. For that matter and in today’s topic, I will offer you one of the biggest gains for the buck: I will show you how to halve a project's memory budget in matter of hours. Boom!
      Wouldn't that be awesome?
      Go ahead, go ahead... ask me: is it really possible for me? The answer is: it depends on your starting point, but in my experience, YES. Unity Addressables can be of a great value here. The catch? You must be willing to put in the work and to master the process. But this workflow will earn you the title of employee of the month. 
      If you are interested, keep reading.
      In this post you and I will go through the journey of moving from a traditional asset management to an addressables-based asset management system. To illustrate the process,  we will port a simplified old-school project to the new era of Unity Addressables.
      Now, you might ask me: why don't you just show me the real-life work you did?
      In a non-competitive world I would just show you all the material I created. In the real world though, that is likely to get me canned. And worse, busted.
      What I will do instead is to offer you my guidance so you and I iterate on project that absolutely represents the difficulties you could find tomorrow in your next project. And we will do so by first welcoming Unity's Addressables to our family of suggested packages.
      In this blog post I will get you started in Addressables ASAP so you can implement your own Unity Addressables system in a matter of minutes.
       

      Unity Addressables: Why?
      Time to pay attention to this very important section. Our goal is to diagnose easy memory gains and implement them fast. There are several ways to do this, but one of the most powerful yet simplest methods to pick the low-hanging fruits by loading the initial scene and opening up the profiler. Why this?
      Because an unoptimized game architecture can frequently be diagnosed at any point during gameplay, so the quickest way to check this is by profiling the initial scenes. The reason for this is the common over-usage of singleton-like scripts containing references to every single asset in the project just in case you need it.
      In order words, in many games there is usually an almighty script causing an asset reference hell. This component keeps each asset loaded at all times independently from whether it's used or not at that time point.
      How bad is this?
      It depends. If your game is likely to be constrained by memory capacity, it is a very risky solution, as your game will not scale with the amount of assets you add (e.g. think of future DLCs). If you target heterogeneous devices, such as Android, you don't have a single memory budget; each device will offer a different one, so you settle for the worst case. The OS can decide to kill your app at any point if our user decides to answer a quick message in Facebook. Then the user comes back and surprise, all their game is gone for good.
      How fun is that?
      Zero. Unless you are overworked and sleep deprived, situation which might grant you a desperation laugh.
      To complicate matters further, if later on you decide (or someone decides for you) to port your game to another less powerful platform while keeping cross-play working, good luck. You don't want to get caught in the middle of this technical challenge.
      On the other side, is there a situation where this traditional asset management solution works just fine? The answer is yes. If you are developing it for a homogeneous platform such as PS4 and most requirements are set from the beginning, the benefits of global objects can potentially outweigh the extra added complexity of a better memory management system.
      Because let's face it: a plain, good old global object containing everything you need is a simple solution, if it works well enough for you. It will simplify your code and will also preload all your referenced assets.
      In any case, the traditional memory management approach is not acceptable for developers seeking to push the boundaries of the hardware. And as you are reading this, I take you want to level up your skills. So it is time for just doing that.
      Greet Unity Addressables.
       
      Requirements for our Unity Addressables project
      If you are planning on just reading this blog entry, your screen will suffice. Otherwise, if you want to do this along with me, you will need:
      Functional hands Overclocked brain Unity 2019.2.0f1 or similar The level 1 project from GitHub (download zip or through command line) Willingness to get your hands dirty with Unity Addressables The git repository will contain three commits, one per skill level-up section in the blog (unless I messed up at some point, case in which I will commit a fix).
       
      Download the project in ZIP format directly from GitHub
       

      Level 1 developer: Traditional asset management
      We are starting here with the simplest asset management method here. In our case, this entails having a list of direct references to skybox materials in our component.
      If you are doing this with me, the setup is a simple 3 step process:
      Download the project from git Open the project in Unity Hit the damn play button! Good, good. You can click a few buttons and change the skybox. This is so original... and boring. I take it, no Unity Addressables yet.
      In a short moment you and I will see why we need to endure this short-lasting boredom.
      To start with, how is our project structured? It pivots around two main systems. On the one side, we have our Manager game object. This component is the main script holding the references to the skybox materials and switches between them based on UI events. Easy enough.
      using UnityEngine; public class Manager : MonoBehaviour { [SerializeField] private Material[] _skyboxMaterials; public void SetSkybox(int skyboxIndex) { RenderSettings.skybox = _skyboxMaterials[skyboxIndex]; } } The Manager offers the UI system a function to apply a specific material to the scene settings through the usage of the RenderSettings API.
      Secondly, we have our CanvasSkyboxSelector. This game object contains a canvas component, rendering a a collection of vertically-distributed buttons. Each button, when clicked, will invoke the aforementioned Manager function to swap the rendered skybox based on the button id. Put in another way, each button's OnClick event calls the SetSkybox function on the Manager. Isn't that simple?
      Unity Addressables - Scene Hierarchy
      Once we're done daydreaming how immersive it was to play X2, it's time to get our hands dirty. Let's launch the multisensory experience and open the profiler (ctrl/cmd + 7; or Window - Analysis - Profiler). I take you are familiar with this tool, otherwise you know what to do with the top record button. After a few seconds of recording, stop it and see the metrics for yourself: CPU, memory, etc.. Anything of interest?

      Performance is pretty good, which is nothing surprising considering the scope of the project. You could literally turn this project into a VR experience and I assure you that your users will not fill any of the bile buckets that so many players filled when playing Eve: Valkyrie.

      In our case, we will be focusing on the memory section. The simple view mode will display something as depicted below:
      Level 1 Asset Management - Simple Memory Profiling
      The numbers on the texture size look pretty oversized for just displaying a single skybox at any given time, don't you agree? Surprise incoming: this is the pattern you will find in many unoptimized projects you are likely to suffer lead. But heck, in this case it's just a collection of skyboxes. In others, it will be more about characters, planets, sounds, music. You name it, I have it.
      If dealing with many assets falls under your responsibility, well, then I am glad you are reading this article. I will help you transitioning to scalable solution.
      Time for magic. Let's switch the memory profiler to detail mode. Have a look!
      Level 1 Asset Management - Detailed Memory Profiling
      Holy crap, what happened there? All skybox textures are loaded in memory, but only one is displayed at any given time. You see what we did? This rookie architecture produced the whoooooooping figure of 400mb.
      This is definitely a no-go, considering this is just a small piece of a future game. Addressing this very problem is the foundation for our next section.
      Come with me!
      Summary:
      Traditional asset management entails direct references Therefore you keep all objects loaded at all times Your project will not scale this way  
      Level 2 developer: Unity Addressables workflow
      In video- games you start at level 1, which is great, but once you know the gameplay rules it is time to leave the safe city walls in our quest to level up. That is exactly what this section is about.
      Grab the level 2 project now.
      As we previously saw in the profiler, we have all skyboxes loaded in memory even though only one is actively used. That is not a scalable solution, as at some point you will be limited on the amount of different variations of assets you can offer to your players. An advice? Don't limit the fun of your players.
      Here, let me help you. Take my shovel so we can dig the so needed tunnel to escape the prison of traditional asset management. Let's add a new fancy tool to our toolbox: the API of Unity Addressables.
      The first thing we need to do is to install the Addressables package. For that, go to Window → Package Manager, as shown below:

      Unity Package Manager - Unity Addressables
      Once installed, it's time to mark the materials as addressables. Select them and activate the addressables flag in the inspector window.
      Level 2 Asset Management (Unity Addressables)
      What this will do is to ask Unity politely to include those materials and their texture dependencies in the addressables database. This database will be used during our builds to pack the assets in chunks that can easily be loaded at any point during in our game.
      I'll show you something cool now. Open Window → Asset Management → Addressables. Guess what's that? It's our database screaming to go live!
      Level 2 Asset Management (Unity Addressables) - Main Window
      My dear reader: that was the easy part. Now comes the fun part.
      I want you to pay a visit to an old friend of ours from the previous section: Sir Manager. If you check it, you will notice it is still holding direct references to our assets! We don't want that.
      We are teaching our manager to use indirect references instead - i.e. AssetReference (in Unreal Engine you might know them as soft references).
      Let us do just that, let's beautify our component:
      using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; public class Manager : MonoBehaviour { [SerializeField] private List<AssetReference> _skyboxMaterials; private AsyncOperationHandle _currentSkyboxMaterialOperationHandle; public void SetSkybox(int skyboxIndex) { StartCoroutine(SetSkyboxInternal(skyboxIndex)); } private IEnumerator SetSkyboxInternal(int skyboxIndex) { if (_currentSkyboxMaterialOperationHandle.IsValid()) { Addressables.Release(_currentSkyboxMaterialOperationHandle); } var skyboxMaterialReference = _skyboxMaterials[skyboxIndex]; _currentSkyboxMaterialOperationHandle = skyboxMaterialReference.LoadAssetAsync(); yield return _currentSkyboxMaterialOperationHandle; RenderSettings.skybox = _currentSkyboxMaterialOperationHandle.Result; } } What happens here is the following:
      A major change happens in line 7, where we hold a list of indirect references (AssetReference) instead of direct material references. This change is key, because these materials will NOT be loaded automatically just by being referenced. Their loading will have to be made explicit. Afterwards, please reassign the field in the editor. Line 13: since we are now in an asynchronous workflow, we favor the use of a coroutine. We simply start a new coroutine that will handle the skybox material change. We check in lines 18-20 whether we have an existing handle to a skybox material and, if so, we release the skybox we were previously rendering.  Every time we do such a load operation with the Addressables API, we receive a handle we should store for future operations. A handle is just a data structure containing data relevant to the management of a specific addressable asset. We resolve specific addressable reference to a skybox material in line 23 and then you call its LoadAssetAsync function, over which you can yield (line 25) so we wait for this operation to finish before proceeding further. Thanks to the usage of generics, there's no need for sucky casts. Neat! Finally, once the material and its dependencies have been loaded, we proceed to change the skybox of the scene in line 26. The material will be offered in the Result field that belongs to the handle we used to load it. Level 2 Asset Management (Unity Addressables) - AssetReference list
      Keep in mind: this code is not production-ready. Do not use it when programming an airplane. I decided to favor simplicity over robustness to keep the matter simple enough.
      Enough with explanations. It is time you and I saw this in action.
      If you would be so kind to perform the following steps:
      In the addressables window, cook the content (build player content) Then make a build on a platform of your choice Run it and connect the (memory) profiler to it. Protect your jaw from dropping.
      Level 2 (Unity Addressables) - Build Player Content
        Level 2 Asset Management (Unity Addressables) - Memory Profiler
          Isn't asset cooking delicious?
      I like happy profilers. And what you saw is the happiest profiler the world has ever seen. A satisfied profiler will mean several things. For one, it means happy players playing your game in a Nokia 3210. It also means happy producers. And as of you, it means a happy wallet.
      This is the power of the Addressables system.
      Addressables which comes with little overhead on the team. On the one side, programmers will have to support asynchronous workflows (easy-peasy with Coroutines). Also, designers will have to learn the possibilities of the system, e.g. addressable groups, and gather experience to make intelligent decisions. Finally, IT will be delighted to set up an infrastructure to deliver the assets over the network, if you opt to host them online.
      I have to congratulate you. Let me tell you what we have accomplished:
      Appropriate memory management. Faster initial loading times. Faster install times, reduced in-store app size. Higher device compatibility. Asynchronous architecture. Opening the door of storing this content online → decoupling data from code. I would be proud of such a gain. It's for sure a good return on investment.
      Oh, and make sure to mention your experience with Addressables in job interviews.
       
      INTERMEDIATE: Instancing and reference counting. Read on it my blog post for information on this topic.
      OPTIONAL: Alternative loading strategies. Read on it my blog post for information on this topic.
       
      Summary:
      Addressables-based asset management scales just well Addressables introduces asynchronous behavior Do not forget to cook content on changes or you'll give your game a pinkish tint!  

      Level 3 Asset Management (??) - Content Network Delivery
        Level 3 Asset Management (??) - Content Network Delivery
      In the previous section, we achieved the biggest bang for the buck. We leveled up our skills by moving from a traditional asset management system to an addressables-based workflow. This is a huge win for your project, as a small time investment gave your project the room to better scale in assets while keeping your memory usage low. That accomplishment indeed made you step up to level 2, congrats! However, one question is yet to answer:
      Is that it?
      No. We barely scratched the surface of Addressables, there are further ways to improve your project with this game-changer package.
      Of course, you do not have to memorize all the details regarding Addressables, but I highly suggest you to have an overview of them because down the road you are likely to encounter further challenges and you will be thankful to have read a bit further. That's why I prepared an extra short guide for you.
      There you will learn about the following aspects:
      The Addressables window: the details matter Addressables profiling: don't leak a (memory) leak ruin your day Network delivery: reduce the time-to-play user experience Build pipeline integration Practical strategies: speed up your workflow, trash your 10-minute coffee breaks  
      And more importantly, answer questions such as:
      What is the hidden meaning behind Send Profiler Events? How useful is the AddressableAssetSettings API? How do I integrate this all with the BuildPlayerWindow API? What's the difference between Fast Mode, Virtual Mode and Packed Mode? In order to grab the level 3 guide check out my blog post
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!