Dev Log

Jeremy Wolf Jeremy Wolf

Stats in Unity - The Way I Do it

The goal of this post is to show how I did stats and hopefully give you ideas or at least a starting point to build your own system - your needs are probably different than mine.

A WIP, but the buildings need stats

All games need stats in some shape or form. Maybe it’s just the speed of the player or maybe it’s full-blown RPG levels stats for players, enemies, NPCs, weapons, and armor.

For my current personal project, I knew I was going to need stats on the buildings as well as the enemy units.

A good way to do this is to create a non-monobehaviour stats class that has a public field for every stat. Then all classes that need stats can have a public stats field and you’re good to go. For a lot of uses, this might be more than enough.

Making the stats class a non-monobehavior class it can help enforce some good composition over inheritance type of structure.

While this is good, I wanted something more. The goal of this post is to show how I did stats and hopefully give you ideas or at least a starting point to build your own system - it’s likely that your needs are a bit different than mine.

I wanted something generic. I wanted different units to have different types of stats. I wanted a quick and easy way to get values of stats - without creating all kinds of functions or properties to access individual stats. And lastly, I wanted a stat system that could work with an upgrade system with similar ease and adaptability.

My implementation of an upgrade system will get shared in a follow-up post.

My stats. Easy to use. Easy to Create

Stats in a Collection

Having my stats in a collection (a dictionary in my case, but lists also work) means I can easily add stat types and adjust stat values for a given type of unit. While I very much have a love-hate relationship with assigning values in the inspector - this is a win in my book all day every day.

This was a crucial piece of the puzzle given that not all units will have the same types of stats - farmer and defensive towers have very different functions and so they need different stats!

Using Scriptable Objects

I choose to use a scriptable object (SO) as the container for my stats. This keeps my data separate from the logic that uses the data. Which I like.

It also means that each stats container is an asset and can be shared with any object that needs access to it. It works project-wide.

For example, every “tower” has access to the same stats object. If the UI needs to display stats - they access the same SO. This reduces the need to duplicate information and more importantly reduces possible errors or needs to keep “everything up to date” - the UI and units always have the same values.

Upgrades can also be easy. Apply an upgrade to the stats object and every tower gets it. No need to hunt through the scene for objects of a certain type to apply the upgrade. Additionally, if I apply an upgrade in Level 1, that upgrade can easily transfer over to Level 2 as it can be applied to the SO. Pretty handy.

Important Note: Scriptable objects can be used to transfer data from one scene to another. BUT! They are not a save system and changes in an SO will not persist when leaving play mode - just like changes to a monobehaviour.

As a slight tangent, I also like that the SO, at some level defines the characteristics of the object. For example, in my project I want players to be able to choose a leader at the start of a new game or even at the start of a new level - the leaders effectively act as a global upgrade. By having each leader’s stats on a SO, I can simply drop the leader’s SO into whatever script is handling the leader’s logic and the effect is a change in leadership - a strategy pattern-like effect. The same effect can be had with stats - depending on your exact implementation.

Quick Stat Look-Up

Putting my stats in a dictionary with an enum for the key and the value holding the stat value makes for a quick and easy method to access a given stat. No need to create properties. All I need is one public function that takes in the stat type and returns the value. This continues to work if I add or remove a stat type making my system a little less brittle.

This approach does mean that a stat could be requested and that it isn’t in the dictionary. If this is the case, I return zero and send an error to the console. Ideally, this isn’t happening, but with this implementation, nothing breaks, and as the developer, I get a message letting me know that I either asked for the wrong stat or I haven’t created a stat type for a given unit. Again, nice and clean.

Changing Stat Values

Similarly, if you need to change a stat on the fly - a potential path for an upgrade system - a single public function can again be used. Once again remembering that changes to the SO won’t persist out of play mode.

In this case, I chose to return a negative value if the stat couldn’t be found… Would zero have been better? Maybe. Depends on your use case. I chose negative as things like hit points can be zero without something being wrong.

Potential Issues

For those who are paying attention, there are at least two potential issues with this system.

The Dictionary

You may have noticed that my scriptable object is actually a “SerializedScriptableObject” which isn’t a class built into Unity. Instead, it’s part of Odin Inspector and it allows the serialization and display of dictionaries in the Unity inspector. Without this class, you can’t see the dictionary in the inspector and you can’t add stats in the inspector… It’s a potential problem. There are at least two workarounds - short of buying Odin.

Fix #1

Use a list instead of a dictionary. You would need to create a custom class that has fields for the stat type and the stat value. Then you would need to alter the GetStat() and ChangeStat() functions to iterate through the list and find the result.

A bit messier, but not too bad. If you are concerned about the performance of the list vs a dictionary, while there is definitely a difference, the extra time to iterate through a list of 5, 10, or 20 stat types is marginal at best for most use cases.

Fix #2

But if you insist on using a dictionary, the second fix would be to use your list to populate a dictionary at runtime and then use that dictionary during play mode. This could be done in an initialized function or the first time that a stat is requested. A bit messier, but definitely doable.

The Scriptable Object

Having every unit of a type share stats is a good thing. Unless of course, a stat needs to be for the individual instance. Something like hit points or health. In those cases, we have a problem and need to work around it. So let me propose a couple of solutions.

Fix #1

Have each unit instantiate a copy of the SO when the unit is created. This makes the original SO just a template and each object will have its own copy. This breaks the “every unit of a type shares the same stats” idea, but it means that every unit of a type starts with the same stats.

This effectively means that the SO tracks max values or starting values while the object itself tracks the current value of the stat.

This is the method I have used in my project to prevent all units of a type from sharing health, but unfortunately, it will likely break my upgrade system moving forward. So….

Fix #2

Or you could create an additional dictionary (or list) of stats on the SO that should be copied onto the instance. Then functions such as a DoDamage() that change the value of a local or instance stat simply change the local value instead of changing the value on the SO.

This is likely my preferred solution moving forward as the SO still defines all stats for the object while individual objects have control of their instanced stats.

Read More
Jeremy Wolf Jeremy Wolf

State of UI in Unity - UI Toolkit

UI Toolkit. It’s free. It’s built into the Unity game engine. It promises way more functionality than UGUI. Sounds perfect, right?

Well. Maybe. Turns out it just isn’t that simple.

So let’s talk about UI Toolkit.

A Bit of History

There is a huge elephant in the room when it comes to UI Toolkit. That being the development timeline.

UI Toolkit, or rather UI elements, was announced sometime in 2017 (I believe) with a roadmap released early in 2018 and the first release with some functionality came in Unity 2019.1. This was all leading to the goal of having parity with UGUI with the release of Unity 2020.3.

While UI Toolkit has come a long way - it’s still not able to fully replace UGUI. The latest official release from Unity says UI Toolkit is in maintenance mode through the Unity 2022 cycle and no new features will be added until the release of Unity 2023.

Based on the recent Unity release pattern, this means we likely won’t be seeing a feature-complete UI Toolkit until sometime in the calendar year 2024. That’s 6-7 years from announcement to being feature complete. 7 years ago I was still pretty fast on a mountain bike…

The Pro’s

UI Toolkit offers far better control of UI layout than UGUI. Many of the layout features that I find so attractive about Nova UI are either built into UI Toolkit or are on the roadmap. On top of that, UI Toolkit can be used to create custom editor windows, which neither UGUI nor Nova UI can or will ever do.

Plus! And this is no small thing, UI Toolkit allows styles or themes to be defined and reused. It’s no secret if you’ve watched any of my recent streams, I find color choice and visual design really really hard. With UGUI if you want to tweak the color of a button background, you either make prefabs, which can work sometimes, or you have to change each and every button manually. I hated this workflow so much that I built a janky - but effective - asset to let me define UGUI styles. While Nova is working on themes, they aren’t ready or available for developers just yet.

UI Toolkit also promises far better performance than UGUI - much of it is done behind the scenes with little effort from the developer. With UGUI if you change an element of a canvas the entire canvas has to be redrawn - which isn’t an issue with simple UI but can become a significant issue with more complex designs when you are trying to eke out every little bit of performance.

Despite big significant differences in how to create the UI, to Unity’s credit, much of the programming with UI Toolkit should feel familiar to folks comfortable working with C# and UGUI. While there will certainly be some nuance and naming differences, programming the interactivity of UI Toolkit should not be a major hurdle.

And of course the last big win for UI Toolkit? It’s free! For a lot of folks that right there is all the reason to ignore Nova UI and give Unity’s newest solution a serious go.

Stolen from a Unity Video On UI Toolkit

The Con’s

The biggest question about UI Toolkit is will it ever be done? Will it be feature complete? Will it truly have parity with UGUI? How much will change in the process? Will those changes break your project?

There are two big and commonly used UI features that UI Toolkit doesn’t have (yet). First is world space UI. If you don’t need it. Not a big deal. The second incomplete feature is UI animation. Some is supported but not all. Is this a problem? Maybe? Depends on your project.

Data binding with UI Toolkit, is less than awesome. Finding objects by name? Using strings? This doesn’t feel sustainable or scalable or at the very least it’s just not a pleasant way to work. Even the devs have commented about it and are planning to revamp it with a more generic solution. What exactly that means? We’ll have to see.

With any choice of system, you need to look carefully at what it can do, and what it can’t do, and compare that to what you need to do.

The Toss Up’s

The workflow and design of UI Toolkit largely reflect web design and workflow. Is that a pro? Is that a con? That depends on your experience and maybe your willingness to learn a new approach. For me and I suspect many others this is the deciding factor. UI Toolkit feels TOTALLY different than the UGUI or Nova workflow. The pattern of gameObjects, components, and prefabs is replaced with USS, UXML, and UI documents.

Also, the UI is also no longer really part of the scene - in the sense that you can’t see it in the scene view and it’s really in its own container. The UI elements are NOT scene objects. Again, it’s different which isn’t good or bad but it is really different.

For some, these are the exact reasons to go with UI Toolkit. For others, they’re the perfect reasons to stay with UGUI or Nova UI.

The sceneview (left) vs the GameView (right)

An Experiment

The results. Can you which result came from which UI tool?

I felt like if I was going to make a second video talking about UI options in Unity I really needed to have SOME comparison of the tools. So I set out to make the “same” ui with UGUI, UI Toolkit, and Nova UI. I wanted to create a slider that changed the alpha of an image and buttons that would collapse text. Nothing too fancy, but functions or similar to functions used in a lot of projects.

I spend about a bit longer with UGUI (18:02) than with Nova UI (17:39) and as expected, due to my lack of knowledge and experience, far longer with UI Toolkit (56:16). Those times are based on recording the process with each tool. You can see the final results in the video to the right.

In all cases, default or included assets were used. No formatting of colors, textures, or fonts was intentionally done.

I KNOW that it is possible to make this UI with all three components. The point was not to say one tool is better than another. The point was just to experience each tool and the process of using each tool. That said, for me, with my experience, my brain, and my understanding it was clear that one tool, in particular, is easily the best choice in terms of the workflow and the quality of the final result.

Let me explain more…

My Thoughts on UI Toolkit

I have zero experience with modern web development or design. I’d like to think I can learn and adapt to a new system, but I can’t explain just how foreign UI Toolkit felt to me. Sure I could drag and drop things into the UI Builder and mess around with values in the inspector, but after spending a few hours (more than just the testing above) with it I had way more questions than answers. I was playing and experimenting with Unity’s Dragon Crashers project - I had examples in front of me but I still very much struggled to see how it all worked and connected.

For example, there is a nice-looking slider in the library. It works. There are lots of great options and adjustments in the inspector. But for the life of me, I could not figure out how to scale it on the vertical axis. The horizontal axis, no problem, but make it thicker? Nope.

Video of a UI Toolkit slider at 30 fps…

I did some googling and found the same question online with no posted answer. Now clearly there is an answer. There is a way to do it. But it’s a way that I couldn’t figure out.

And then there’s the UI builder window. There’s no way to sugarcoat it, the performance of UI builder was horrible. I don’t know how else to say it. With just a few elements it’s a non-issue. But load up the main menu of Dragon Crashers and slide a few values around and the editor becomes nearly unusable. You can see the lag in the video to the right. I saw similar results in my simple “experiment” use case too.

Just to make sure I wasn’t crazy or being overly critical I opened up the profiler to do some testing. Sure enough, there’s a HUGE lag spike while dragging a value in the UI Builder inspector. This isn’t a deal breaker, but it sure makes the tool harder to use.

UI Builder lag

Strings? Really? Why?

Then I ran into my active disdain for strings. I will freely admit that when I saw that strings were being used to get references to UI elements I was really put off.

Why? Why do it that way? Why lose strongly typed references? Maybe this is how web development is done and folks are used to it. But this feels like a step backward.

The dev team agrees or at least sees it as an area to improve, so they are looking into “more generic” solutions, but right now those solutions don’t exist and who knows when or if they will materialize.

So Should You Use It?

In my mind, trying to decide if UI Toolkit is the right solution comes down to a handful of questions (and a lovely flow chart).

  1. Do you know and like web design workflows?

  2. Do you need world space UI?

  3. Do you need complete control of UI animations?

  4. Are you okay with the UI system changing?

Final Thoughts

Options are good to have. I see UGUI, UI Toolkit, and Nova UI each as viable UI solutions depending on the needs of the project and the skill set of the developers. Each has shortcomings. None of them are perfect.

UI Toolkit is in this weird alpha/beta mode where it’s been released, but not feature-complete and has potential breaking changes coming in the future. Which means much of the tutorial information out there is outdated. It also doesn’t give content creators a good incentive to make more than just introductory content. This makes it harder for the average user to get up to speed. Unity keeps doing this and feels so counterproductive!

But here’s the best part of the situation. All three of these solutions can be tried for free. Nova has a free version while UGUI and UI Toolkit are shipping with recent versions of Unity. So my advice? Try them. Play with them. Do an experiment like I did. Find the right tool for you and your project. I have my UI solution. I love it. But that doesn’t mean it’s the right solution for everyone.

Read More
Jeremy Wolf Jeremy Wolf

Knowing When A Coroutine Finishes

Did you know that a coroutine can yield until another coroutine finishes? I didn’t. Let’s talk about it and why it’s useful.

Backstory

A few weeks back, I was working with some 3rd party code that heavily used coroutines. In one place it had a “chain” of coroutines that each called another coroutine sometimes multiple coroutines and this went 5-6 coroutines deep.

First. Yuck.

Second. Holy cow.

For what I was working on, I needed to know when the process (all the coroutines) had finished. The “usual” and often suggested method on the interwebs is to add a class-wide boolean to track when the coroutine is complete - you set it to true when you start and set it to false when you finish.

I’ve never liked this approach, but sometimes it’s good enough.

In the case that I was working with, the boolean approach just wasn’t practical or at least was going to be extra icky. And I definitely wasn’t going to add a boolean parameter to each coroutine and try to pass it through… No chance.

So I got to wondering if there was a better way. It turns out there is. And somehow I’d missed it up until now.

Ironically, if I’d looked closer at the coroutines in the 3rd party code I would have seen the solution in action. Oops… You win some you lose some.

A Better Way

So this may sound like just passing the buck. BUT! You can have a coroutine yield until another coroutine is finished.

(Seriously, how did I not know about this?)

This means in my case, a cluster of sequential coroutines, I can create yet another coroutine and have it yield until the cluster finishes it’s business. So if the coroutines exit early and DON’T make it to the end of the chain - for some reason. I’ll still know that the coroutines are done. I may not know why, but I know it’s done. And that’s a hugely useful thing!

Plus!

We can throw in a function to call when the coroutines have done their business. Frankly, this is often what we really want to do - wait until the coroutine is done and then run some other code.

In my opinion, this works and it works well. But I think it also opens the doors to better code structure (assuming it’s your code and you can change it).

Better Still!

A chain of coroutines makes it hard to debug and in my opinion and makes harder it than necessary to follow the flow of the code. So with what we know now, or at least with what I know now, we could restructure the code and make it easier to read.

So instead of one coroutine calling the next coroutine, I can call them all, sequentially, from within a single wrapping or master coroutine. This avoids the hard to follow “chain” AND it avoids a huge monolithic coroutine (i.e. just making it all one coroutine).

In fact, I used this exact approach in my current personal project where I had several actions that needed to happen sequentially, but also over a controlled period of time. Launching a missile requires a lot of moving pieces - it is actually rocket science!

Launching an ICBM from a missile SILO


Next Level

Coroutine with callback

But we can go one step further!

If you’ve spent any time on my channel or discord you know I love actions and events. I rarely pass up a chance to use them and this is no different!

Rather than have a set function called when the coroutines finish, we can pass in an action that will act as a callback.

Meaning the same coroutine can run, but with a different reaction when it’s finished. This can be super useful if the coroutine is public (yes, coroutines can be started from other classes) or if it is somehow getting invoked by different objects or for different reasons.

I generally don’t love making coroutines public, not sure why, just don’t. But it’s easy enough to add a public function.

Passing in a callback function

Either way, passing in an action (i.e. function) is easy and makes for very useful code and potentially reusable code.

Read More

Older Posts