Search Unity

How To Optimize a Unity Game

Discussion in 'General Discussion' started by GameDevSA, Oct 24, 2021.

  1. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    252
    I wasn't sure if there was a better spot for this so I've put it here.

    I've written a guide that basically covers everything I've found helpful to optimize my personal Unity projects so far. It's not intended to cover absolutely everything, but it's a good summary of general principles that worked for me.

    You can see the article here: https://stuartspixelgames.com/2018/06/18/how-i-optimised-my-unity-rpg/

    If you know of any other optimization tips, please feel free to add them here or on my blog post as I'd like to keep updating it.
     
    pKallv and BrandyStarbrite like this.
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    I don't have time to read the whole thing yet but I'm confused about one item that you wrote:
    Not sure what the "Event Editor" is. I'm wondering, though, if the texture is just a solid color, why would you need to scale it up? I'm assuming by "scaling it up" you mean up-sampling it. If you do this then how does it save memory?
     
  3. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    When I do a fade to black effect, I take a single black pixel image and stretch it across the entire screen, then fade it in. I'm assuming @GameDevSA was referring to something like that.
     
    GameDevSA and angrypenguin like this.
  4. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    In that case I wouldn't call it scaling-up the texture, but rather the mesh that it's mapped to. That paragraph could use more precise language for clarity.
     
    Joe-Censored likes this.
  5. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    252
    Oops that's a typo, thanks for pointing it out. I merged some parts from an optimization guide I wrote from another game engine, still applicable but different language. Thanks for the catch!

    In terms of scaling up though, my understanding is, if your game is say, 1080 x 1920, and you create an image that is solid black that is also 1080 x 1920, that you're wasting resources because an image that large uses up a lot of space. The game file becomes larger, but also uses more RAM.

    You'd be using less memory by making the image 27 x 48, then scaling it up 40x, for example, or so I understand it. Or yes, even using one pixel and stretching.

    I've updated that part now.
     
    Last edited: Oct 26, 2021
  6. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    252
    That's a fair point if you are working in 3D with meshes, which I don't, I make everything with sprites in 2D so you just scale it. I'll make a change.
     
  7. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    You don't even need a texture. A blank ui canvas image will fill rectangle with its color.
     
    Not_Sure and Joe-Censored like this.
  8. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    Ah, I see. Well, sprites in Unity are also just textured meshes behind-the-scenes, so it's really the same thing either way.
     
  9. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    I wasn't aware of that, thanks!
     
    neginfinity likes this.
  10. Not_Sure

    Not_Sure

    Joined:
    Dec 13, 2011
    Posts:
    3,546
    Just off the cuff:

    -Culling

    -Batching

    -Pooling

    -Limit AI cycles

    -LOD for colliders, art, and scripts

    -Imposters

    -Force the Garbage collector at appropriate times

    -Use rays instead of colliders when possible. even if it’s a projectile, you can do a series of ray casts on a moving point.

    -avoid mesh colliders when you can use primatives

    -use box colliders that have a 0,0,0 rotation (cheapest of the colliders)

    -use 2D colliders when possible. Even if you use 3d models, if it’s on a 2D plane do 2D colliders

    -multithreading

    -I’m really not sure how this is done, but optimize CPU memory. That is, load the CPU with relevant data to avoid having to pull it from the RAM when possible. I’m at a total loss as to what this is called or any tutorials in it, but I know it’s a thing.

    -static methods that have multiple functions

    -use particle emitters when possible

    -use animations instead of manual movement when possible

    -avoid the built in RNG when possible

    -use int instead of float when possible

    -run an asset clean up tool before building

    -use A* method instead of navmesh when possible

    -build the game instead of running it in editor to see actual performance

    -identify your bottleneck between the GPU, CPU, and RAM. It’s usually only one slowing down that is giving you issues

    -pro builder is can be a resource hog

    -stagger processes when possible. As an example I’m making a shotgun. Rather than doing the math for the spread when pulling the trigger, I’m doing the math for each pellet one frame at a time before hand, and when I pull the trigger doing whatever pellets are not done.

    OR another example is rather than having all of my enemies do decisions every frame I’m doing them in chunks taking turns. There’s lots of ways of doing this. Some prefer to give them a clock to do it every so often, rather than every frame. Some prefer to make an array and cycle through it.

    -cache rather than find when possible

    -I’ve had a lot of success with this, if you have a complicated collider do a large simple collider that turns on the more complicated colliders

    -avoid changing variables and GUI elements every frame, and instead only do so when they change

    -avoid instantiate and destroy like the plague (see pooling)
     
    Last edited: Oct 27, 2021
    tmonestudio and GameDevSA like this.
  11. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    252
    How optimal is that?
     
  12. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    252
    Some very good comments there, thanks! Some of that I think I covered but not all of it, I'll go over this in detail when I have more time and might query you about a few things. Cheers!
     
    Not_Sure likes this.
  13. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    It should be optimal.

    If you want the absolutely fastest way to draw a colored square that'll be an unlit shader with a single parameter.
     
  14. Not_Sure

    Not_Sure

    Joined:
    Dec 13, 2011
    Posts:
    3,546
    Sorry, but of all the things that can be optimized, why the focus on this?
     
  15. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    252
    Just clarity.
     
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    It's avoiding cache misses. Search for Overbyte's blog about optimising the game Vessel for info on it. The author also previously gave talks about it for Sony, and the slides for that are publicly available, too. From memory, search for "Pitfalls of Object Oriented Programming", I think that was the name.

    Edit:
    http://overbyte.com.au/index.php/overbyte-blog/entry/optimisation-lesson-3-the-memory-bottleneck

    Edit 2:
    https://m.youtube.com/watch?v=VAT9E-M-PoE
     
    GameDevSA and Not_Sure like this.
  17. Not_Sure

    Not_Sure

    Joined:
    Dec 13, 2011
    Posts:
    3,546
    Oh wow!

    we all forgot a huge one.

    DOTS!
     
  18. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Found some amazing rendering performance improvements for repeating meshes using the Graphics.DrawMesh commands but they are runtime specific and tricky to use and understand.

    I think Unity could make rendering performance optimisation a lot easier with features like this but implemented into the editor/component system as simple to use and profiler detectable scenarios for using them e.g. Unity detects that you use the same mesh multiple times in a scene and recommends adding the DrawMesh component or enabling the DrawMesh toggle in the editor.
     
    Not_Sure likes this.
  19. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Unity already has mesh batching. The DrawMesh stuff is for the many cases which aren't easily covered automatically.
     
    Not_Sure likes this.
  20. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    I can't help but think the real win here isn't the coding or techniques at all, but just load management. You'd want to keep all your game dev stuff simple and even wasteful at times, just whatever makes the program operate correctly.

    This is usually slow, so you'd implement load management, basically the first call of optimising something is... don't do things if you don't have to.

    In a typical game this means LOD affects not just the models, but the scripts as well. Enemies will update less frequently, and animate less frequently the further they are, or if occluded. Any running scripts politely disable when not required, or update with much less frequency.

    Things like this - most people can build a game using Unity's wasteful learn approaches and still ship @ 30 or 60fps on the target platform.

    For VR it'd be tighter, but for everything else, and readers of this thread new to Unity, I really do recommend that you focus on the program behaviour and later, how often that behaviour needs to be processed.

    I think, after all the zen black ops optimisations I've done over the years, this is the biggest win, every time - and the easiest.
     
  21. GimmyDev

    GimmyDev

    Joined:
    Oct 9, 2021
    Posts:
    160
    That's good advice, it's like when I start making low poly games, and realized I could just made whatever UV was convenient to draw with, instead of optimizing it, because there was so much resources I will never run out of it within that project. And by doing so I did learn you could transfer the image of one UV to another, which mean even optimized UV got optimized by having a better workflow, because I could do crazy packed lay out and not care about how I would draw on it or across the seam.
     
    Not_Sure likes this.
  22. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Yeah, you are moving forward and it's the only way to reach the finish line.
     
    Not_Sure likes this.
  23. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Most of the time for Unity projects I've seen the slowness isn't even from the code. It's from wasteful rendering stuff. And that's not because people are silly, it's because rendering works in often unintuitive ways.

    As far as mechanics and logic go, most games simply don't have enough going on to need a lot of optimisaiton in that department. My go to strategy is "avoid doing silly stuff, and then optimise when I hit bottlenecks" and the truth is that most of the time I don't end up having to optimise logic stuff. It's less about being fast everywhere, and just not being particularly slow anywhere instead.

    Of course, the catch is that "silly stuff" is nebulously vague. How do you know what's "silly"? It all comes down to having a decent mental model of what's going on under the hood so that you can evaluate, in super broad terms, "how much work is this probably making the computer do?" Using off-the-shelf stuff is a great example of where you can easily miss "silly" stuff. The blog I linked earlier has a great example to do with the perfectly reasonable use of a sort function.

    You can't always make stuff "not slow" the first time, because there's a limit to how much you can know. But we can get in the habit of just making stuff work, then measuring its performance, and then looking further where we find issues. That'll help you build up those mental models, so you'll be able to do better by default in the future.
     
    Last edited: Oct 29, 2021
    NotaNaN, pk_Holzbaum and GimmyDev like this.
  24. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Heck yeah!

    Two things to consider in Unity on this note.
    • "enabled = false;"
    • Events
    If a Component isn't meant to be doing anything then generally it should be turned off. And learning to use events means that you can have Components know when to turn themselves on to do stuff. So most of your code can be dormant most of the time.

    I don't really consider it an "optimisation", but certainly it could be seen that way compared to the simplistic approach of everything checking what it should do every frame in Update().
     
    Lurking-Ninja and hippocoder like this.
  25. GimmyDev

    GimmyDev

    Joined:
    Oct 9, 2021
    Posts:
    160
    Disable real time shadow and post processing :cool:
     
  26. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Anyone using a more non Unity style of batched based systems a sort of middle ground between DOTS and Monobehaviours?

    Can massively reduce the number of Update() function calls for managed data objects and if you use a SOA (structures of arrays) style of data then you almost have DOTS performance, without the boilerplate and allowing you to keep a more OOP design.

    And it allows you the freedom to use Unity's more powerful graphics features without being tied to the kitchen sink.

    But you are moving away from the Unity way of doing things and the Editors WYSIYG feature set.
     
    tmonestudio likes this.
  27. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Indeed.
    Remenber this old post ? : https://blog.unity.com/technology/1k-update-calls :p
     
  28. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Only the whole point of DOTS and SOA is you take full advantage of your CPU cache and align/chunk the data as opposed to function calls to objects with data all over your RAM that takes about 100 times as long to access.



    If you want your game to be a hundred times slower then use Update().

    However, it's only relevant for games that have multiples of the same thing e.g. enemies, powerups, doors, traps, bombs, gems.

    So it's useless for games that have all unique and singular things.
     
  29. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    I use Update often and recommend everyone does. You want Update for regular dynamic things, and use lists or arrays to loop over for larger quantities. Tools for job. Don't dish out implied bad advice to people. No experts will heed these posts but we have an obligation to newcomers to orient them in a successful way.
     
    Kofiro, angrypenguin and Arowx like this.
  30. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    That's false information and not a good advice.

    If take a game with a single object with a single Update script and it runs at 120 fps, then switching to DOTs absolutely won't make it run at 12000 fps. So using Update() does not make things one hundred time slower.
     
    angrypenguin likes this.
  31. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Good point what we need is a benchmark to compare Update() vs List/Array NativeArrays of structs or SOA and check the performance delta e.g. how many objects before a noticeable speed boost is detected 2,4,8,16,32,64,128,256?

    And then there is Jobification or Multi-threading, an Update() based approach is limited to the single main thread making for a huge potential bottleneck.
    Only from the provided graph you can see that an Update() based system without cache cohesion and relying on the randomness of C# Object memory allocation will be more prone to cache misses and a 100ns trip to ram to get the data.

    120 fps is 8.3 ms or 8,300,000 ns or 8,300 trips to RAM vs 8 million to cache. Or look at it this way if you want to push your game to 240fps/4.15 ms or 360fps/2.0525 ms then maybe every Update() call counts.

    But again maybe we need some super fine resolution benchmarks...

    And remember CPUs seem to have hit a performance GHz limit and the only way around that limit will be increased cache size (great for DOTS and maybe Update()) and more cores (great for DOTS) but not great for a Single threaded Update() approach.

    Or more complex cores allowing for more instructions per clock cycle actually good for DOTS and Update().
     
    Last edited: Oct 29, 2021
  32. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    My game is absolutely not traditional because I'm simulating thousands of enemy entities, alongside temp and a couple other environmental effects, on a 250x250 grid (iterations every ~50 ms). But for me it's DOTS and Windows threading (Tasks usually), and making both of those play nice with the Unity main thread through a static class.

    I've also had some trouble with rendering, mainly lighting because I have dozens of lights that are supposed to be dynamic.

    I honestly have very little idea if I'm doing things the "right" way or not, but I've had to stop more than once and just work on optimization because my CPU usage was through the roof.
     
  33. Actually, yes, well, sort of. The first thing I do is implement this: https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html

    This gives me the ability to run my own update cycles without crossing the C++-C# barrier. Even for my non-MonoBehaviour classes.
    Then when I implement a feature I decide if I want to implement my own IwhateverUpdate or use the MonoBehaviour ones. Depends on the situation. Sometimes you don't have a choice.

    I also use jobs to move things around whenever possible and obviously try to arrange things so I can "jobify" them as much as possible.

    But this is intermediate to advanced topic, I certainly don't recommend doing it for beginners or people who don't know Unity's Scripting environment properly. It is very easy to get lost in this so you need to know what you're doing.
     
    neginfinity and angrypenguin like this.
  34. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Quick benchmark adds more moving cubes with update functions every 1000 frames and logs the total time and average FPS.
    Adds 10 updated objects per cycle and as you can see on my PC 10 Update()'s cost between 0.0020 and 0.0056 ms per frame.

    But remember that ms/fps brackets are very tight in modern games
    30 fps 33.33ms
    60 fps 16.66ms
    90 fps 11.11ms -- 5.55ms or over 990 Updates.
    120 fps 8.33ms -- 2.78ms or about 130 Updates.
    240 fps 4.16ms --

    Simple list of structs called via an update manager

    Surprised by the result even at 10 cubes there is a huge boost in performance overall and much less of a decline in performance as the cube count goes up.

    You need around 600 objects before you get down to similar FPS as 10-30 objects with Updates().

    And this is just an Array of Structs, not a Struct of Arrays (the most cache performant version) using vanilla Unity e.g. Vector3, transform and no Burst, Mathematics, Jobs or DOTS.

    Thought: Maybe I could remove the rendering components so that just the transforms data is changing and take out any rendering overhead.
     
    Last edited: Oct 30, 2021
  35. GimmyDev

    GimmyDev

    Joined:
    Oct 9, 2021
    Posts:
    160
    over engineering is a thing, only do what your game need with the min specs hardware, targeting for overhead to cushion worse case.

    But premature optimization is the root of all fun, I have a problem with over optimizing workflow over performances, ad optimization of performance often makes thing longer and more complex for workflow. I mean if you haven't fill one bottle, don't think about bringing the second one just yet if you won't have a lot of liquid to transport.
     
  36. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    ALL this information is completely irrelevant in situation where Update() is not a bottleneck.

    The graphs don't matter. Bottlenecks do. If they exist in the first place.

    It's been, what, few years, and you're still getting distracted from the bigger picture by small details.
     
    hippocoder likes this.
  37. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I often use a component based approach but that's the default and easy way to do things, however if you have multiple things > 5 with the same Update component you could test the potential performance boost by...

    How easy would it be to convert a component to Batch based...
    1. Add a static list to store each components data.
    2. Rename the Update() function to Process()
    3. Add an Update() function that will run through the list calling Process() on each component.
    ? Can you disable all but one component then call process on them so the Update() method is not triggered.

    Or just write a Manger class and rename the Update() process.

    The thing is with a few minutes work you could see a very good boost in performance which would be ideal for trying to keep your game running at those higher refresh rates/lower processing needs.
     
    Last edited: Oct 30, 2021
  38. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Quite, and you can do it already if you need it. I'm currently working on an oop version of an ecs myself but I'm pretty sure it's a well known pattern. You get some performance boost over Update() but what you can't get is cache coherency which is what ecs solves.
    It doesn't matter that you put your components in arrays, they're still objects and they have to be because multiple things can reference them. The second you put them in struct arrays to get cache friendly, you then need a lookup mechanism and thus a whole ecs architecture.
     
  39. GimmyDev

    GimmyDev

    Joined:
    Oct 9, 2021
    Posts:
    160
    And easy peasy vectorization, that's a 4x boost right there on top
     
  40. sisonja

    sisonja

    Joined:
    Oct 21, 2021
    Posts:
    2
    Great guide. Thx
     
    GameDevSA likes this.
  41. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Or you could just wrap your component data in a

    Code (CSharp):
    1. public struct componentData {
    2.  
    3. // component variables
    4.  
    5. }
    And add into a static list any you have a cache friendly batch based component.

    This is why I'm convinced that Unity could make ECS so much easier to use than the current DOTS Api.
     
  42. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    You mean like ECS already does?
    Like ECS already does?
    I agree ecs is not easy, but what exactly does "easier" look like to you?

    What you're suggesting has some feasability but all you've stated is the simplest part of it which is to use an array of structs. There's a whole bunch of caveats that make that not simply "easy" and which you'll discover the second you try and do it.
    You're being way too simplistic in your argument.
     
  43. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I just think the Unity DOTS API has grown in complexity especially when compared to other aspects of Unity in ease of use.

    Could there not be...
    • [DOTS] compile tags for lists, structs and methods.
    • Or intermediate DOTS e.g. a Batch based API that does all of the IJob/NativeArray/Schedule under the hood.
    • Or just DOTS BASIC where the API is so simple anyone can use it without wading through the very poor documentation (seriously lacking in good examples).
    Or it's a pain to use IMHO.
     
  44. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Honestly, I think arowx's comments are indicative more of the fact Unity hasn't really taught anyone anything and hasn't shown what their vision for programming with ECS will eventually land like. It's changed a few times and has a lot of mystery terms for newcomers which inspire fear.

    So rather than learning something that's a moving target, people tend to want to pull improvements into what they know - basic C# - and there's no shame in that.

    So I expect a lot of these posts or concerns to move to the background once Unity starts moving on DOTS again, with supported editor, supported components, working visual scripting, and so forth. All these things will make it seem a much easier thing than it is at the moment.

    Unity's bread and butter is in rapidly making games. If ECS makes it slower to actually make games, then people will just use classic Unity + any optimisations that work from DOTS land with gameobject pattern, so it's not an all or nothing thing to begin with.

    There's too little information at this point to speculate, but I think I will be benefiting from DOTS regardless of what I aim to make and how.
     
    AcidArrow and GimmyDev like this.
  45. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,572
    How long has Dots/Ecs been in development at this point?

    Because honestly now it occasionally starts resembling microkernel vs monolithic debate that happened in the past.
     
    unity-freestyle likes this.
  46. PutridEx

    PutridEx

    Joined:
    Feb 3, 2021
    Posts:
    1,136
    - If you use unity's occlusion culling umbra, I recommend using Pixel Culling, an asset.
    It's much, much faster and does a better job at culling. Although baking times are longer.

    - Use il2cpp. Free code performance :)

    - The usual suspects: Drawcalls, polys, LODs, materials, so on

    - Profile everything! Essential tool and easy to use
     
  47. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Pixel culling looks reminds me of the open source Blitz3D culling system I wrote using DX7 around 20 years ago. It basically rendered, from each point on the grid, all separate objects in a unique colour. Then scanned the rendered texture for these colours and built up a list of visible objects for that position. It worked perfectly fine! I guess the old ways are often still the best.
     
    PutridEx likes this.
  48. GimmyDev

    GimmyDev

    Joined:
    Oct 9, 2021
    Posts:
    160
    Apparently genshin impact scale on mobile because they didn't optimized on client only, they move pathfinding to server ... that's a trick too
     
    hippocoder likes this.
  49. Not_Sure

    Not_Sure

    Joined:
    Dec 13, 2011
    Posts:
    3,546
    While true, you really want to do everything possible on server-side to keep filthy cheaters and hackers at bay.
     
  50. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    I completely agree with you on that however I don't see any way around it because the complexity is the ecs paradigm itself, not so much the syntax and features. Not that the syntax doesn't get awfully ugly at times too.
    I tried to create the most simple implementation of your static struct arrays and even that is not so simple.
    The interesting thing with this approach is that every component of a given type is in the same array, not fragmented across chunks and archetypes like in ecs. It makes me wonder that there's not some hidden fundamental flaw in this mechanic that I just haven't hit yet. I'm sure I'll find out.
    This naive approach already has a lot of gotchas in it though and you need a more robust solution in practice but it demonstrates an idea.
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. // Base class for struct based components
    5. public abstract class BaseMonoStruct<T> : MonoBehaviour where T : struct
    6. {
    7.     private static List<BaseMonoStruct<T>> _indexes = new List<BaseMonoStruct<T>>();
    8.     private static List<T> _components = new List<T>();
    9.  
    10.     public static List<T> Components { get => _components; }
    11.  
    12.     public T getData()       => _components[_index];
    13.     public void setData(T t) => _components[_index] = t;
    14.  
    15.     protected abstract T getInitialData();
    16.  
    17.     private int _index;
    18.  
    19.     protected virtual void Awake()
    20.     {
    21.         _index = _components.Count;
    22.         _indexes.Add(this);
    23.         _components.Add(getInitialData());
    24.     }
    25.  
    26.  
    27.     void OnDestroy() {
    28.         int lastIndex = _components.Count - 1;
    29.  
    30.         // Last place swap
    31.         if (_index != lastIndex) {
    32.             _components[_index] = _components[lastIndex];
    33.             _indexes[_index] = _indexes[lastIndex];
    34.  
    35.             _indexes[_index]._index = _index;
    36.         }
    37.         _components.RemoveAt(lastIndex);
    38.         _indexes.RemoveAt(lastIndex);
    39.     }
    40.  
    41. }
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public struct SinWaveMotionComponentData {
    4.     public float angle;
    5.     public float radius;
    6.     public float rotationsPerSecond;
    7.     public Transform transform;
    8. }
    9.  
    10.  
    11. // Example implementation of a struct based component
    12. public class SinWaveMotionComponent : BaseMonoStruct<SinWaveMotionComponentData> {
    13.     public float radius;
    14.     public float rotationsPerSecond;
    15.  
    16.     protected override SinWaveMotionComponentData getInitialData() =>
    17.         new SinWaveMotionComponentData {
    18.             angle = 0,
    19.             radius = this.radius,
    20.             rotationsPerSecond = this.rotationsPerSecond,
    21.             transform = this.transform
    22.         };
    23. }
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. // Example implementation of a System processing struct components.
    4. public class SinWaveMotionSystem : MonoBehaviour {
    5.     void Update() {
    6.         float dt = Time.deltaTime;
    7.  
    8.         var components = SinWaveMotionComponent.Components;
    9.  
    10.         for (int i = 0; i < components.Count; i++) {
    11.             var c = components[i];
    12.  
    13.             c.angle += 360 * c.rotationsPerSecond * dt;
    14.             c.angle %= 360;
    15.             float sin = Mathf.Sin(c.angle * Mathf.Deg2Rad);
    16.  
    17.             Vector3 position = c.transform.position;
    18.             position.y = sin * c.radius;
    19.             c.transform.position = position;
    20.  
    21.             components[i] = c;
    22.         }
    23.     }
    24. }
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. // Example of setting struct data from another MonoBehaviour
    4. public class SinWaveMotionSpawnerSystem : MonoBehaviour {
    5.     public SinWaveMotionComponent prefab;
    6.    
    7.     private Vector2 halfScreenSize;
    8.  
    9.     void Start() {
    10.         halfScreenSize.y = Camera.main.orthographicSize;
    11.         halfScreenSize.x = halfScreenSize.y * Camera.main.aspect;
    12.     }
    13.  
    14.     void Update() {
    15.         if( ! Input.GetKeyDown(KeyCode.Space))
    16.             return;
    17.  
    18.         SinWaveMotionComponent comp = Instantiate(prefab);
    19.  
    20.         var compData = comp.getData();
    21.  
    22.         compData.radius = halfScreenSize.y * 0.1f + (Random.value * halfScreenSize.y * 0.8f);
    23.         compData.rotationsPerSecond = 0.1f + Random.value * 0.5f;
    24.         Vector3 pos = compData.transform.position;
    25.         pos.x = -halfScreenSize.x + (Random.value * 2 * halfScreenSize.x);
    26.         compData.transform.position = pos;
    27.  
    28.         comp.setData(compData);
    29.     }
    30. }
    1. Create a 2d sprite gameobject in the scene and add the SinWaveMotionComponent to it and set its properties. Duplicate it a bunch of times.
    2. Add the SinWaveMotionSystem to a gameobject in the scene.
    3. For extra you can also add the spawner to another gameobject in the scene.
     
    Arowx likes this.