Optional Variables - Unity Tips [2020.1+]
Вставка
- Опубліковано 13 бер 2021
- Just a tiny video showcasing a really simple Unity pattern. I find it really useful and thought that maybe you would too.
Thanks for watching!
Sources:
gist.github.com/aarthificial/...
Support me on Patreon:
/ aarthificial
#unity #unitytips #gamedev
This is the kind of thing that makes me love programming. You're not only programming a game, you're programming the tools to program the game.
100%
I didn't even know u could do that in unity
@EDIT I've updated the gist to correctly support prefab overrides.
I have a really exciting topic for my next devlog! Sadly, it will take some time to make so, in the meantime, I wanted to create something less ambitious.
Thanks for watching!
These are real workable "tips and tricks" that I, and im sure plenty of other people, would love to see more of. If you're able to, please create more content like this!
I'll do my best!
Really nice to have small tutorials like that, and more generally giving an idea of how things work in your game. Personally it really helps me to better structure my own ideas and projects. Keep up the good work :)
Thanks, I'm happy to hear that!
This is such a useful function/struct that exists in a couple of scripting/markup langs I use that I've been wishing was in unity
Short, Simple, and Straightforward. This video deserves more views!
This Dude deserves more subscribers and views!
Very nifty struct :)
I'm curious as to how you created an empty PropertyDrawer at 1:31 because not having to write the boiler plate for that would save so much time. Not sure whether that's an inbuilt Unity thing or a unique feature of your IDE. Looks I've got some research to do, great vid Aarth :)
i don't use unity, but i love watching your videos, quality content!
Thank you 🖤
I really like this solution. Easy to use, understand, and works perfect. Nice tip!
that is brilliant! I just finished a project in unity and this definitely would have come in handy
I am hyped for the next devlog!
Just discovered this channel. These are incredibly useful, and you've got a smooth voice. I could see your stuff blowing up, my dude.
Also, after watching the pooling video, I gotta say your animations are so helpful in understanding these topics. Very well done!
Thanks!
I don't use Unity, but I find this really informative nonethelss, great video!
Thanks!
That's a great tip!
will definitely come in handy with my current project. These types of videos are so informative and straight to the point, would be amazing if you gave us more of these useful snippets. Also, can't wait for the upcoming devlog, you have a great game in the making. Best of luck and keep up the great work.
Thanks so much!
Found this from Unity's blog, and I'm stoked! I want to try benchmarking this to see the difference
Thank you! More people should know this stuff, I love that you're sharing it.
Wow these are super cool. Love the mini tutorial on random stuff as well.
Oh dude, I have been looking a week ago, and found nothing good. Awesome Video!
Thanks!!
Good job 👍,
it is actually so much easier now to work with generics thanks to unity team. Previosly such things required a lot of boiler plate code.
However, Imho, they could have done it waaay erlier(and yet they still didn't implement full polemarfism)
Nice and short :>
Expected something like Rust's Options, but this is even cooler!
Great tutorial. Clear, short, and to the point.
Thanks!
Don't use Null check in Update loop. That is one of cool performance trick that I learned. This is a very nice tip 👍👍
Really nice concise video
Thx jacob - will be yoinking this ahaha
Np bro
Interesting
Amazing, love your vids! Which Code Editor are you using?
Thanks! It's Rider
very useful thanks so much for creating this video :D
neat tip, love it
I'm sooo happy that I found your channel, these dev logs are great! Brought back my inspiration to actually make something maybe, but first back to my overcomplicated tooling
You could also add some operator overwrites to the Optional class. For example auto convert to the value.
Btw why did you use the old imgui based editor UI rather than the more modern UIElemens stuff?
I'm glad to hear that!
As far as I know, the default inspector still runs on IMGUI so you can't use UIElements inside of it yet. Although I may be out of the loop on that one
@@aarthificial I think you should be able to do it. The editor uses a messed up way where the whole thing is UIElemens but the automatic fields in it are basically individual imgui containers. At least that is the case for the latest releases 2021.1 I think
This is OP dude
Pro tip: If you add two user-defined implicit conversion operators between T and Optional, you can just write Optional foo = 0.3f;
Or just use a nullable type and use the majestic null coalescing operator for the job, like this:
private Transform? target;
private void Update() {
DoSomethingWith(target ?? default);
// or
target?.position = new Vector3(a,b,c);
// or
target ??= default
}
Thanks for the suggestion!
Have you tested nullable reference types with Unity? I thought they where C# 8+
For anyone coming across this, I don't think the null-coalescing operators work, because they bypass Unity's equality override. It's an unfortunate consequence and they wrote a blog post on how they would probably try a different solution if they could start from scratch
Cool trick!
Was this inspired by the Rust programming language?
Option is a built in type there.
Thanks!
Now as I think of it, it could've been subconsciously inspired by Rust. I haven't used it in quite some time but I was always inspired by the way it handles nulls (by just not having them lmao)
love it :)
Nice ;D
I... don't really understand what you're talking about. But I'm sure I'll run into that problem you solved here, so this video gets saved ^^
You actually can make floats & int's null! All you do is:
int? _integerValue;
float? _floatValue;
The question mark makes it null until a number is put in!
Nullable value types:
1. Don't work for references
2. The Unity inspector doesn't support them
and you can do an implicit operators to automatically convert other values into one as well!
Editor Scripts Are Great Too :D
I’ve never seen anyone or anything say that a comparison to null is expensive. I’m pretty certain that “null” just refers to a special value meaning the variable refers to nothing. Is a null check not just a simple comparison of two values? I would really appreciate a deeper explanation.
Unity is actually a C++ engine and the objects we operate on in C# are just handles for the underlying C++ objects.
The base Object class in C# overrides its Equals method so that if we compare it to null, it checks if that underlying C++ object still exists. This is done because the C++ object might have already been destroyed even though the garbage collector hasn't yet cleaned up its C# representation. So checking for null would return false even though the object is gone.
This check requires an external call from the manage side (C#) to the unmanaged side (C++) which comes with a cost.
In Unity, it's a rule of thumb to avoid these external calls in the hotpath.
@@aarthificial Thank you! Interesting how the majority of youtube channels don't mention this and perform many null checks in Update(), myself included. I've never noticed any performance issues from it, so it surely can't be horribly expensive, but thanks for the clarification.
I don't get it. What happens if you use an optional Transform target, but target is empty in the editor and enabled is true? You'd have to check for null anyways...
It depends on your approach
For example, I never check for null if a field is required for the script to operate. The error thrown is clear enough to inform the designer what field is missing.
In this situation enabling the checkbox is like making the field required
In other words it's more about showing the intent that the field should be empty, rather than protecting the code from all possible values
Hey cool trick! Be careful with using this at scale though because you're trading a single value with a value + a bool which increases memory usage. This will also generate a lot of generic classes that can bloat memory usage and code size.
Thanks!
That's an interesting point. Do you happen to know how much memory a struct definition takes up? I've never faced a situation where the number of types in an application would be a bottleneck
@@aarthificial It varies with Unity versions, whether C# reflection is used with the generic class/struct and other factors so I can't give an actual number. I'm not saying don't use generics at all because they're very useful, but keep in mind that using generics has a runtime cost.
When applying the property drawer anything else that uses the namespace of editor gives me errors, and without it the property won't show up in the inspector. I'd really like to be able to use this to improve workflow, I hope it's an easy fix.
Sorry if it's obvious but did you put the OptionalPropertyDrawer file in "Assets/Editor" ?
If that doesn't work you can try changing the name of the namespace or removing it entirely since it's not required
@@aarthificial I have tried removing the namespace and having it in the right folder, I'm really confused why it doesn't work... I'll attempt it with a new project and see what happens, thank you for the reply by the way!
So I was thinking about this more.. Most null checks in an update loop are to safe guard against the object in question being destroyed..
Can you think of a way that 'enabled' could be set to false upon its GameObject being destroyed? (assuming `T` is a GameObject). I wish Unity provided an OnDestroy event instead of a message
also, I tried replying with the link to the unity blog but it keeps getting removed
Oki, I was able to find the blog. Had no idea I was mentioned there
Anyway, this pattern is more suited for editor-time configuration where things don't really change at runtime. That's why the struct is read-only. You can see at 2:22 that I mostly use it for value types and/or SOs.
When it comes to GameObjects with unpredictable lifespans, probably something different should be used.
Also, totally agree, an OnDestroy event would be great!
@@aarthificial I made an attempt this. Unfortunately, it relies on the user calling Initialize in an awake method, but otherwise it works! Let me know what you think
github.com/RockyGitHub/OptionalT
Interesting idea!
I think you could implement ISerializationCallbackReceiver and use the OnAfterDeserialize() method instead of calling initialize manually.
Also, before adding the OnDestroyInvokeEvent component, I'd check if it already exists cuz there may be a situation where two structs reference the same object
@@aarthificial It appears that you can't access a "gameobject" in that callback, dang! That would have been sweet
I'm super new to C# and unity as I come from a different programming background.
I know that C# 8 already has a nullable type... doesn't this mostly redefined that functionality?
I understand that System.Nullable is not serializable, but isn't there a way to fix that?
Why is comparing to null slower than comparing to "enabled"?
Is there a way to force the language to use the same syntactic sugar of float? instead of Optional like maybe some operator overloading?
1) "C# 8 already has a nullable type... doesn't this mostly redefined that functionality?"
Kinda. In C# 8 only value types can be nullable. Here the main idea is to use this with both references and values.
2) "System.Nullable is not serializable, but isn't there a way to fix that?"
To the best of my knowledge, there is not. Maybe if we could apply field-targeted attributes to it. But nothing I'd know of.
3) "Why is comparing to null slower than comparing to "enabled"?"
Unity is a C++ engine. Any C# object deriving from "Object" is just a reference to the underlying C++ object. Null comparison is overloaded to check if this native object still exists. This requires a transition from managed code (C#) to C++ which is said to be much slower than normal comparison. I don't know exactly why it is but I trust Unity on that one.
4) "Is there a way to force the language to use the same syntactic sugar of float?"
I don't think so, especially since you're talking about a declaration, not an operator. But I'm more than glad to be corrected
@@aarthificial Wow, thanks for taking the time to give such a detailed answer.
I was asking all this since in Swift (i'm coming from an iOS background) the "native" Optional type is an enum similar to how you implemented it but in Swift both value and reference types can be nullable.
I wasn't quite aware of this C++ layer that might slow things down. I did read somewhere that sometime ago unity switched from mono to il2cpp but I haven't really found a detailed explanation of what that means. Most tutorials online just focus on the making of the game. I would love to learn about the intricacies of how unity actually works under the hood.
Anyway, I love your videos! Super inspiring work. You make me wanna make things and learn stuff. That's an incredible impact that you have on people! You just made me become a proud patron :D
Would you know by any chance any quality resources that might tackle some more advanced subjects about unity/C#/shaders (that don't cover how a for loop works)?
Also, where would you think it's the best place to ask unity related questions? Unity forum, stackoverflow, some (your) discord group?
Thanks so much for the support!
Unfortunately, I don't have any particular resources to recommend. Unite talks are definitely worth watching though, especially the ones by Ian Dundore.
I think Unity forum would be the best place.
@@aarthificial There might be a way to add some syntactic sugar. I didnt see you add it in the video, but its been over a year so you may have added it already, you can define custom implicit conversions. That way, when passing it to a function, you wouldn't have do .value. It would also let you store a value into a Optional variable without needing to call a constructor.
This is really cool, but I have to wonder which one adds more runtime overhead:
- Checking for null
- Creating two property wrappers that causes two additional (and separate) stack allocation and de-allocations. After all, property wrappers are just functions, and every function call will do a stack allocation, followed by a jump to the function's code, followed by a stack-deallocation, followed by jumping back to code in the calling function. There's a reason why the performance experts at Unity suggest using fields instead of properties in your release code.
So, if anyone were to take the time to do a performance analysis on this bit of code, I would be interested in reading the results. Besides, in my limited experience in doing performance analysis, I found that calling a function as a method parameter is a bit of a performance killer in the .NET Framework compiler when set to generating release code. I know that Unity rolls out their own compiler, so their complier(s) might be ok with passing a method call as a parameter to another method call.
Sorry if it's obvious but it's not about just checking for null. Unity objects override the equality operator and perform a transition from managed code to C++ (to check if the wrapped object still exists) which is why it's a best practice to not use them in the hot path
@@aarthificial I'm not saying that there isn't additional overhead to checking for null in Unity. I just didn't think I had to say that because you already mentioned it in your video. Besides, it's not that unusual to override equals equals operator in C#. This is an elegant pattern, but I would like to see some metrics that actually show that it's faster. I would like to see the metrics for the Mono builds and the metrics for the IL2CPP builds.
I've seen some weird things with C# over the years that are counter intuitive to what I learned when I studied Computer Science. Last week I learned about the website called leetcode. They were kind enough to provide Runtime and Memory use metrics, so I decided to play around with the code in my solution. When I removed my temp variables and just used reused the input parameters, memory use went up by about 0.7 MB. And, the difference was not linear.
With 2 temp variables, I used 28.1MB. With 1 temp variable, I used 28.7 MB.
With 0 temp variables, I used 28.8 MB.
It doesn't make sense, but adding variables to the stack, decreased my total memory usage.
Sometimes, the thing you expect to execute faster, based on study and knowledge of how code executes, ends up being slower. I don't know if doing a null check is faster or slower, but I would like to know. But, in order to know, I would need to see some metrics to back it up.
@@FranciscoTChavez I suspected that much. I just didn't like how biased your comment was. Without, for example, mentioning the stack allocations that would be caused by the overloaded operator. So I wanted to clarify for anybody reading it
Did they add the functionality to display a class with an generic class in 2020? bc i cant use it like this in 2019.4 or am i doing sth wrong?
Yes, unfortunately, generics require 2020.1+
Have you thought about making a discord server? It would definitely benefit your channel and allow people to talk to you without having to use UA-cam comments
See, my crippling anxiety doesn't like the idea of people talking to me directly. I wanna try to overcome my fear at some point, just not today
@@aarthificial That's completely understandable. I'm always to scared to talk when I join a discord server so someone else has to initiate the conversation lol. But yeah I understand
I see a lot of lofty comments but I would like to point out as a dev myself that these quality of life structs do unnecessarily complicate code. In the distance example, you could (and in my opinion should) have simply said that a value of “0” means “unlimited”, which is pretty standard practice.
The reason the struct is a bit frowned upon in this example is because you cannot as easily use your float in extensions and methods, because it’s a field of a struct. you can’t modify the field, you have to modify the struct now. These are all considerations you need to make when using the Optional struct: it has its uses of course but they cannot be too overhyped lest we regret our choices later.
In these cases a “DisableLimit() => distanceLimit = 0f;” which some code to check for 0 is better practice in my opinion.
Man here I go, I love your videos and the first comment I decide to write is nitpicking your code, I’m sorry I don’t choose to be this way :’)
You also “hide” your float inside a wrapper, making other developers anxious as to “what is going on inside that wrapper?!” Simple is often best, if for its clear communication alone.
It's meant for configuring things via the inspector. Editing a serializable MonoBehaviour field in the code is a really questionable practice.
Still, value types were just an additional benefit, besides the main point of the video.
Have you actually benchmarked if it is faster?
Unity 2020.1+ needed to serialization of generics
Shouldn't you constrain T to something Unity can serialize?
I couldn't find a way to do that
after wach this video im sure the need to learn how to use the editor scripts
C# doesnt have an optional type? Just curious java has one an JS has undefined
Since C# 8 we have nullable references (denoted via "?") which serve a similar purpose but I'm not sure how they would cooperate with Unity.
In terms of a traditional optional container then unfortunately no, there's no default implementation in C#
@@aarthificial Spolier: the won't cooperate. If you check against nullable references you'll get null unity objects that evaluate as non-null.
twitter.com/VehiclePhysics/status/1348641386573271040
@Edy García Oh, yeah, that's for sure. I was thinking more about the nullable aware context and how Unity's serializer would interpret something like:
[SerializeField] private MonoBehaviour? mb;
That is, if they're even gonna support that
For those who tried to use optional variables in lists and encountered problems with indentation, you should reset the indentation to 0 as below:
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel = 0; //add this line
position.x += position.width + 24;