OPTIMIZE your Unity game using these performance tips | Tutorial

Ойындар

Show your Support & Get Exclusive Benefits on Patreon (Including Access to this tutorial Source Files + Code) - / sasquatchbgames
Join our Discord Community! - / discord
--
As my game (Samurado) continues to get bigger and bigger, performance and optimization is something I've been getting more concerned about. So I went on a deep dive to figure out as much as I could about performance, and these are some of the best tips I use/have come across, some are specifically for 2D games, but some are just overall good rules of thumb for any project!
I hope you enjoy!
--
Timestamps:
00:00 - Intro
00:07 - Sprite Atlas and Frame Debugger
01:30 - Tick System
03:57 - Setting up Your Own Benchmark Tests
06:50 - Hashing Strings
08:20 - General Tips
Unity optimization and performance E-Book:
unity.com/resources/optimize-...
---
In need of more Unity Assets? Using our affiliate link is a great way to support us. We get a small cut that helps keep us up and running: assetstore.unity.com/?aid=110...
---
Looking for some awesome Gamedev merch? - sasquatchbgames.myspreadshop....
---
Who We Are-------------------------------------
If you're new to our channel, we're Brandon & Nikki from Sasquatch B Studios. We sold our house to start our game studio, and work full time on building our business and making our game, Veil of Maia.
Don't forget to Subscribe for NEW game dev videos every Monday & Thursday!
Wishlist Samurado!
store.steampowered.com/app/23...
Follow us on Twitter for regular updates!
/ sasquatchbgames
#unitytutorial #unity2d #unity3d

Пікірлер: 66

  • @sasquatchbgames
    @sasquatchbgames3 ай бұрын

    Hey guys! Another easy optimization tip that some of you may not know about: Build your game using IL2CPP (instead of Mono, in the Player Settings) Mono is better for iteration because the build time is faster, but IL2CPP runs faster, so you always want to use that option on any demos/vertical slices/final builds

  • @gameboardgames

    @gameboardgames

    3 ай бұрын

    These are super useful tips for an intermediate Unity guy such as myself, thanks muchly!

  • @ggwp8618

    @ggwp8618

    3 ай бұрын

    IL2CPP is also irreversible. Your code can easily be decompiled on mono. But not in il2cpp

  • @RobLang
    @RobLang3 ай бұрын

    Tips are good but I would recommend always starting any video on optimisation with the process of setting a frame rate budget on a target system, measure frame rate and fix where there are issues. You can have a thousand draw calls reduced to 2 but the player won't ever see the change from 200FPS to 300FPS.

  • @adventuretuna

    @adventuretuna

    3 ай бұрын

    Should also start with a baseline performance so that when he's done optimizing, he can quantify the effectiveness of his work.

  • @rickiousproductions
    @rickiousproductions3 ай бұрын

    Never realised you could pack your own sprite atlas in unity!!! Cheers dude 👍

  • @sealsharp
    @sealsharp3 ай бұрын

    Nice one Brandon! 1) 02:55 There's rarely a need to define custom delegates. System.Action and System.Func are fine. 2) The tick-system is a nice way to reduce overall CPU load, however, it still executes all of them in the same frame, which may lead to microstutters. If you try this to spread calculations over time, it requires time slicing. 3) Bonus tipp: One common waste of performance and unneccesary memory allocations i see in Unity tutorials are coroutines. Not just because the coroutines themselves add allocations, but so do the closures that happen hidden from the developer. There's a place for coroutines, but coroutines require a reason to be used.

  • @Dragoncro0wn

    @Dragoncro0wn

    3 ай бұрын

    Wouldn't a coroutine be better in that tick implementation? Id like to learn more about when a coroutine should be used since i am using them heavily in my game. Maybe i shouldve been using them less

  • @sealsharp

    @sealsharp

    3 ай бұрын

    @@Dragoncro0wn A coroutine is a tool to have a linear sequence of code run over multiple frames. A sequence that starts and ends. By using a coroutine, you can avoid a stateful Update() which is why it's used in tutorials so much. The way the tick-system is used here is for something that happens continuously and it's already using a local state ( the isFacing and distance code in the if()-conditions). So a coroutine in this case is not the right solution, because this is not a sequence. The most common mis-use of coroutines is when multiple coroutines run at the same time and just accidentally work by overwriting each other. And example is when you got grass and you want it to wobble when the player or an enemy touches it. You could make a coroutine that starts on collide and does the wobble for a few seconds. In this case, when the players re-collides while the wobble is still active, a second coroutine is started, both still overwriting the same value. When enemies collide with the grass, more coroutines would be started, overwriting the same value. Now scale that up, multiple pieces of grass, multiple enemies, and now you have dozens if not hundreds of coroutines all existing at the same time, mostly overwriting the same value. A very resonably use of a coroutine is switching levels with asynchronous loading. A sequence of... * sychronously loading the loading scene * unloading the last scene * starting the asynchronous loading of the next scene * updating the load-percentage while loading lasts * waiting for the end of the asynchronous loading * activating the newly loaded next scene * unloading the loading scene ...can be nicely done in a coroutine. Now add a savesystem with saving, loading, autosave to the mix and it's simple where to put the code because it's a sequence. It's visually clear, it's easy to debug. It shows something that logically is a sequence as a sequence in code and that's what coroutines are worth it for. So the question for coroutines is: * how often is my coroutine created? * do they overwrite each other? * is it really a sequence with a start and an end? * will it be running from start to end? If you think about how to cancel a coroutine, it's a symptom that another way of doing this would be preferable.

  • @veeper

    @veeper

    3 ай бұрын

    @@sealsharp What's wrong with using a coroutine for something that happens continuously and uses the local state? Is there a concrete reason why you think it's wrong? It seems to me that it would simplify the code a lot and would achieve the same thing that Tick() did, just without the "executes all of them in the same frame" part?

  • @sealsharp

    @sealsharp

    3 ай бұрын

    @@veeper Hey veeper! 1) "Whats wrong with..." Wrong is maybe the wrong word. But it's a tradeoff between memory pressure and convenience. Example: This coroutine executes the logic every 0.2 seconds. void Coroutine() // started in Start() { while() { return yield new WaitForSeconds(0.2); DoLogic(); } } Example: This Update() executes the logic every 0.2 seconds. float lastTime; void Update() { if(Time.time >= lastTime + 0.2) { lastTime = Time.time; DoLogic(); } } Both are functually the same as Brandons 0.2s Ticker. Brandons ticker is slightly better performing than my Update method because it does the time-check only once for all instances. The Coroutine performs minimally worse because it has a per instance time comparison and it has overhead from the allocation of the coroutine and the allocations from the yield object, though this could be improved by yield-caching. So it is up to you to decide if you like to sacrifice a little bit of performance and create a little bit of memory pressure for writing it the way you think looks nicer. If we are honest, we often sacrifice a little bit of performance for minimally nicer looking code. Garbage collection however is a problematic issue in Unity. Has been for a long time and replacing the classic Unity GC with a new one ( mono to CoreCLR ) is something Unity has been working on for some time, and it's probably the most drastic technological change in the history of the engine. 2) "without the executes all of them in the same frame" It will not solve this. If you have 1000 objects, all created at scene-load, all executing Start() and starting their coroutine in the same frame, then all of their yield new WaitForSeconds(0.2) expressions in the coroutine function will be evaluated equally and at the same frame. Coroutines are executed on the main thread right after Update() as seen in the unity documentation page "Order of execution for event functions". Hope this answers your question!

  • @veeper

    @veeper

    3 ай бұрын

    @@sealsharp yes, thank you!

  • @Lazzarus7
    @Lazzarus73 ай бұрын

    Great tips, love this type of videos. A lot of youtubers don't explain debugging and optimization, very important concepts. Thank you

  • @gregbradburn
    @gregbradburn3 ай бұрын

    Great stuff Brandon! A couple more tips: You can limit how often something runs inside Update by using a modulus operator on the frameCount: for example: if (Time.frameCount % 2 == 0) will only be true every other frame. You could limit it to every 10th frame by if (Time.frameCount % 10 == 0)... The other tip, is in addition to using compression, you can reduce the MaxSize of your textures. Like you said, play around with those settings to see if there's any noticeable difference. Also take advantage of the input quality settings for audio clips because you drag that quality slider waaaaaay down before you start to notice any difference, significantly reducing the size of audio files in your game.

  • @sealsharp

    @sealsharp

    3 ай бұрын

    The modulus version mentioned by Greg here can be combined with a static instance counter for very simple time-slicing.

  • @ragerungames
    @ragerungames3 ай бұрын

    Great tips! Thank you for making this video!

  • @LabitokuDev
    @LabitokuDev3 ай бұрын

    Damn this truly hits the nail ! Last week I was trying to optimize some application at work and after a few different ideas, I just thought about a ticker to fix issues related to multithreading and UI, which was effectively a working solution. As you said, sometimes, bits of code don't have to run as many times as possible in order to keep the application running as fast as possible. Moreover, I find it really convenient to manage the "scope" of a running thread. By handling how many times it can run its code each second, I feel like I have a better grasp on how much performance some logic can take. This kind of content explained and demonstrated in such ways is so valuable, thanks for these !

  • @leonard4
    @leonard43 ай бұрын

    Been using Unity for 10yrs, never played with Sprite atlases. I have a survivor style game with 50 to 100 enemies at a time. Each enemy has a canvas and a scroll view with debuffs. I added all of the debuff sprites to an atlas and the performance gain is significant. Thanks!

  • @adassus
    @adassus3 ай бұрын

    some really nice tips in here. I really appreciate your efforts!

  • @beatrageGameSalad
    @beatrageGameSalad3 ай бұрын

    This is a freaking GOLDEN prize video for every developer in the world! Thanks guys! 😎

  • @alexeyprosin6343
    @alexeyprosin63432 ай бұрын

    This is from Unity's blog. Was posted in 2022. >You should aim to profile a development build of your game, rather than profiling it from within the Unity Editor. There are two reasons for this: 1. The data on performance and memory usage from standalone development builds is much more accurate compared to results from profiling a game in-Editor. This is due to the Profiler window recording data from the Editor itself, which can skew the results. 2. Some performance problems will only appear when the game is running on its target hardware or operating systems, which you’ll miss if you profile exclusively in-Editor.

  • @xown7078
    @xown7078Ай бұрын

    Thanks for your video

  • @gamedevblueprint
    @gamedevblueprint3 ай бұрын

    And again an another amazing video 😀🙏

  • @bgoldpanda7265
    @bgoldpanda72653 ай бұрын

    I’ve become a fan of you ever since I watched the SO state machine video. That completely changed the way I programmed. I’ve noticed a great improvement in your content between that video and this one. Great work! I don’t know if you have heard this but you should have also mentioned that premature optimization is the root of all evil. If the benefits you gain are tiny from making the changes and it makes your code less readable, then don’t make the changes. Only make changes when you have bottle necks or it is easy to write code in the new format you are teaching.

  • @mracipayam
    @mracipayam3 ай бұрын

    Great tips! you should make more optimizing videos.

  • @hefferwolff3578
    @hefferwolff3578Ай бұрын

    This was great!

  • @ArcanePengi
    @ArcanePengi3 ай бұрын

    This is going to save my game 🙏

  • @Soundy777
    @Soundy7773 ай бұрын

    Great Vid & Great Tips!

  • @Cherry-jc8bo
    @Cherry-jc8bo3 ай бұрын

    Thanks a lot man ❤ for the tips love it❤

  • @KIRA-kz8pn
    @KIRA-kz8pn3 ай бұрын

    i used to pack my sprite manually by taking them and importing into photoshop, this gonna save a lot of time, thanks

  • @aaaalord
    @aaaalord3 ай бұрын

    Thank you for your tips

  • @vazzius
    @vazzius3 ай бұрын

    One note is that the Sprite Atlas will take care of compression when you setup one, so in this case you don't need to do the importer settings setup for each individual sprite.

  • @Dragoncro0wn
    @Dragoncro0wn3 ай бұрын

    Instead of a ticker in update why not use a coroutine that has a while true loop and waitforseconds for whatever time you need. Then you have more control on when this coroutine runs as well

  • @publicmmi
    @publicmmi3 ай бұрын

    Nice video :) I guess there are many approaches for the ticker problem. Mine is this: public class MonoBehaviourTicker : MonoBehaviour { public static float tickTimer = 0.2f; // 10 fps default private float _tickTime = 0f; private Action _callback; // Update is called once per frame protected virtual void Update() { _tickTime += Time.deltaTime; if (_tickTime >= tickTimer) { _callback?.Invoke(); _tickTime = 0f; } } public void Init(float rate, Action callback) { tickTimer = rate; _tickTime = 0f; _callback = callback; } } Every class which needs to update "regulated" can derive from 'MonoBehaviourTicker' and in Awake call the Init and in Update() it needs to call "base.Update()" first.

  • @halivudestevez2
    @halivudestevez23 ай бұрын

    let me put here my little tip: in unity editor, the FPS may go down if you let the Scene window on while running the game. So: close Scene window on Play mode :)

  • @cadafinn
    @cadafinn3 ай бұрын

    Gracias, maestro gracias a ti estoy aprendiendo como usar unity 2D para mi proyecto.

  • @mirandaart3012
    @mirandaart30123 ай бұрын

    how is it anytime i think of how to do something, there's a video on it xD nice work!

  • @icaria2674
    @icaria26743 ай бұрын

    For your tick system, its better to break the event callbacks into pools. If your updates are actually taking more that 16ms, putting them all into a single event mean you have one slow frame every 200ms instead of all frames being slow. From a player experience point of view I am not sure that's better because 5 hiccoughs a second isn't great. Better is to distribute those callbacks across multiple frames. Let's say your total update time is 30ms. If you call them all once every 160ms, you end up with one 30ms frame every 10. If you split them into 10 pools and cycle through them you can spend 3ms every frame instead which will look much better.

  • @kobi665
    @kobi66521 күн бұрын

    so, there's a faster sqrt based on the quake 3 algorithm you can look it up, but in my benchmarks it is about 10-20% faster than magnitude

  • @bhuvneetsaggu4011
    @bhuvneetsaggu40113 ай бұрын

    Thanks for the tips, but for reducing update.. Can we do Start () RepeatingInvoke("dumbUpdate", 0.2)

  • @EROSNERdesign
    @EROSNERdesign3 ай бұрын

    why do I keep getting this error " It looks like another Unity instance is running with this project open. Multiple Unity instances cannot open the same project."

  • @GoeTeeks
    @GoeTeeks3 ай бұрын

    09:25 You should look into Power of Two texture sizes for Unity.

  • @sealsharp

    @sealsharp

    3 ай бұрын

    And dividable by 4 if you can't do Power of Two. Crunch compression requires it.

  • @halivudestevez2
    @halivudestevez23 ай бұрын

    I was afraid of using atlas so much, but is it so easy like you showed it? No need to slice sprites after putting them onto 1 asset? I will take a look at it.

  • @sealsharp

    @sealsharp

    3 ай бұрын

    You may be confusing to ways to do a sprite atlas: * If you create one from individual images in Unity like in the video, Unity already knows the sizes of the individual assets from the source image. * If you create one from a spritesheet, an single file image that contains multiple assets on it, then you need to mark which parts of the image are individual sprites.

  • @Coco-gg5vp
    @Coco-gg5vp3 ай бұрын

    First

  • @Diablokiller999
    @Diablokiller9993 ай бұрын

    9:05 Wait, empty functions would get removed by the compiler in C/C++ anyway. Didn't know C# is so bad o.O

  • @sealsharp

    @sealsharp

    3 ай бұрын

    The issue is not the compiler, remember that the C# compiler is just a preprocess for the JIT. The method stays because it could be required by derived classes that may potentially be loaded or created at runtime. There's no overhead except for a few bytes in the assembly until the use happens, but in this case, Unity links itself to instances of empty methods. Unity uses a custom version of the mono-runtime, so we can't say if it's a problem with mono-reflection or how Unity use it. In C# the possibilty to check for an empty method body exists since 2005.

  • @Diablokiller999

    @Diablokiller999

    3 ай бұрын

    @@sealsharp Thought that actually is the issue since C# isn't pre compiled like C/C++/Rust and thus can run into such pitfalls. Afaik the C-Compiler knows if a method isn't even used by derived classes because the preprocessor should tell him anyway. At least you get warnings for those events If I remember correctly - don't write unused methods that often ;)

  • @sealsharp

    @sealsharp

    3 ай бұрын

    @@Diablokiller999 dotnet had "that's empty, remove that!" warnings for years now and in my long time working with it, Unity is the one case I remember where empty methods are anything worse than wasted space in a text file.

  • @regys9521
    @regys95213 ай бұрын

    Gold tips

  • @martinchya2546
    @martinchya25463 ай бұрын

    The stuff in this video are true. But the problem is that.. they doesnt really matter THAT much in most typical scenarios. Reducing 4 draw calls to 1 draw call wont change that much even on 10 year mobile phone (esp since URP handles draw calls a lot better). Hashing string for anim is nice.. if you actually do 100 000 of these in one frame, are you? Benchmarking is okay, but dont benchmark Vector2.Distance() call, benchmark things that you really know that could influence framerate (for example line of sight calculation, path finding, ai finding best move, etc.). I am not saying that topics covered in this video are wrong, because they are not. But in real world scenario they doesnt really matter as much as many think will do. Dont waste your time by profiling and optimizing every single block of code - just MAKE YOUR GAME. You are using Unity after all, it comes with its own overhead because its general purpose engine, not thing tailored for your specific use case. Good, you will save 0.000003 seconds by optimizing your linq into foreach or using dictionary instead of array, good. But by this time, Unity will calculate hundred of matrix transformations every frame, perform UI raycasts, split world into aabb boxes for collision checking, run physics tick, render 6 cameras for every light to render your nice soft shadows, render your view 12 times to make bloom happen, generate shadows for your floor because you forgot to disable shadow casting on them, etc. It just doesnt matter that much. My few perf tips: - dont use realtime shadows on mobile, they are performance killer - generally unless its impossible for you, always use baked lights instead of realtime - always profile to find performance issues on target device (if you have beefy computer or phone, buy or find some weaker one and profile on weaker as well) - be careful only in Update() functions or other real time stuff - time slice more complex actions (you dont need to pathfind 60 times per second, once per 0.25s is probably enough) - dont be too obsessive about allocations, they are fine even on mobile, only avoid alloc per frame or when you deal with huge amounts of entities (bullet hells) (also remember to tick incremental gc because its not enabled by default) I'll tell you my story, I am updating a "fog of war" texture pixel by pixel, the texture is 256x256 monochrome texture. I am taking 5x5 tiles around player and doing some simple raycast for every of those 25 tiles to determine what player see. The raycast is modified boehm line alghoritm. So its 5x5x~10 iterations every player step. I was terrified that it will kill my performance... and yeah it took a huge amount of 0.0004 seconds on my 7 year old phone. Optimize real thing, dont optimize just for sake of optimizing.

  • @Hietakissa

    @Hietakissa

    3 ай бұрын

    You suggest that because we use Unity that does more complex things behind the scenes than we do, optimizing our code would be useless, but I think that's a very backwards way of looking at things.

  • @martinchya2546

    @martinchya2546

    3 ай бұрын

    @@Hietakissa no, I suggest to optimize real problems instead of measuring whether caching transform will get you a net performance gain of 0.0000000000001 s ;)

  • @Hietakissa

    @Hietakissa

    3 ай бұрын

    @@martinchya2546 caching transform isn't even comparable to the things gone over in the video. Sure, doing them once per frame won't bring any noticeable performance improvements, but the same goes with most optimization methods, they are still all good to keep in mind for when they are applicable

  • @DeepCommaAI

    @DeepCommaAI

    3 ай бұрын

    i agree with you, many people waste their time optimizing things that dont matter very much in the end

  • @Sim2322
    @Sim23223 ай бұрын

    Wow, I just got given some 'optimization tips' by a guy who uses a float to iterate through a loop 🤣 Also, simply doing benchmark tests in the editor without using the frame debugger is just plain useless and the results mean absolutely nothing, especially when measured in milliseconds and going 'tHeRe iS a 30 mILliSeCoNdS iNCreAse iN pErForMAncE'.

  • @DeepCommaAI

    @DeepCommaAI

    3 ай бұрын

    looking forward to your KZread video

  • @Sim2322

    @Sim2322

    3 ай бұрын

    @@DeepCommaAI 🤣 Since I have an actual job in the industry and YT channels are for wannabe devs, you'll wait a long time, bud

  • @DeepCommaAI

    @DeepCommaAI

    3 ай бұрын

    @@Sim2322 looking forward to your game

  • @Hietakissa

    @Hietakissa

    3 ай бұрын

    It's not useless, it can still be compared to itself. Also where is this loop you're talking about?

Келесі