In my last post, I introduced my recent journey into Unity's Scriptable Objects. Since then I have been working to convert the tree objects and the tile engine to use them in place of Singleton classes that store tree and tile data in static lists. I have now completed parts of this refactoring and am quite impressed with the new workflow, the added benefits, and the surprising performance boost I have seen moving to scriptable objects. From here onward, I will abbreviate the term Scriptable Object to simply (SO) for brevity.
The Refactoring Process
I decided to refactor my tree game objects first since the tree code was less work than the tile engine. I started by creating an abstract base class which derived from (SO). All my other (SO) classes derive from my base class. I used this base class to store properties and fields that were common to all the (SO) classes I would eventually create. The class needed to be abstract because I had no intention of instantiating this base (SO) class by itself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public abstract class STBaseObject : ScriptableObject { public string Id { get; set; } public string Name; public string FriendlyName; public STBaseType BaseType; public GameObject DefaultPrefab; public GameObject InventoryPrefab; public float AdjustedXPos; public float AdjustedYPos; public int HighestMapLevel; public int LowestMapLevel; [TextArea(1, 20)] public string Description; } |
The next (SO) class was my STTreeObject class. This class would hold fields and properties that were unique to my trees. Up to this point, all my tree data was stored in a public class that held the properties for my trees. This class was accessed by another class which existed to simply to instantiate the list of trees at runtime. That class was instantiated once as a public property on one of my Singleton classes, which also was only instantiated once. What I am describing is a Singleton data storage mechanism. Getting back to the (SO) alternative, have a look at the top portion of the new class showing the CreateAssetMenu attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [CreateAssetMenu(fileName = "TreeObject", menuName = "SurviveAndThrive/Flora/TreeObject", order = 1)] public class STTreeObject : STBaseObject { public int AssetId; public GameObject Sapling; public GameObject Ripe; public string FellingSound; public float FellSoundTimer; public STFloraAssetType AssetType; public STFloraAssetDistributionType DistributionMethod; public STFloraAssetAgeType MinimumAge; public STFloraAssetAgeType MaximumAge; public STFloraAssetAgeType MaturityAge; public STFloraAssetGrowthRateType GrowthRate; public STFloraAssetGrowthRateType FlowerRate; public STFloraAssetGrowthRateType SeedRate; |
The class attribute is important because it allowed me to create a (SO) file in the Unity Editor by creating a menu item for it under Unity's Assets => Create menu. Notice the fileName is the name of the (SO). The menuName is the location within the menu structure where the (SO) will appear.
Scriptable Object Menu |
A New TreeObject |
The New Workflow
There is much to be appreciated in the screenshot above. For one, it is a lot easier to keep track of my tree data using the Project and Inspector tabs in Unity as opposed to scrolling down 1600+ lines of code in the class I was using to store this data before. In a glance, I can see how many trees I have and I can compare the property settings of each one to the others. Plus, by selecting a tree (SO) and locking the inspector, I can navigate to my prefabs and drag-and-drop them onto the (SO). This means I no longer have to load those prefabs from the disk each time I need to instantiate a new tree in the game. Yes, it is tedious to transfer all the information from the class to the (SO) files, but it is no less work than using the classes.
The Added Benefits
This brings me to the benefits I have noticed using (SO) files instead of static classes. Like I mentioned earlier, I was able to switch from storing and using strings in the tree class which held paths and filenames to using hard references in the (SO) file for things like the DefaultPrefab, InventoryPrefab, and Sapling and Ripe variants. Another amazing, and I do mean AMAZING benefit over static class storage is the fact that all of the tree data can be altered at runtime. For example, if I need to increase the number of logs that are produced from chopping down a particular type of tree, all I have to do is change the value in the inspector. The next time I chop down that type of tree, it will produce the altered count of logs automatically. This is perhaps the best overall benefit I have seen to using (SO) over other methods of storage of static data.
The Performance Boost
Once I had converted to using (SO) for the trees, I proceeded to build out the tile engine to use (SO) as well. There is a nice method in my MapManager class which creates the preview map in the game. I chose that as the first step to refactoring the tile engine. What follows are comparisons between the pre-scriptable-object version of the game and the current using the new (SO) system. This test involved loading an existing map template and rendering the preview of it. The map is 1 MILLION tiles, being 1000 x 1000 tiles in size, rendered on a single Unity tilemap. Notice in the Console the Start and Stop times. These times captured how long it took to generate the preview map using the old and new systems.
Pre-SO-Test 35 Seconds |
SO Test 23 Seconds |
Pre-SO-Test |
SO Test |
Pre-SO Chunk Load Times |
Post-SO Chunk Load Times |
Incredible work on this. Can't wait to see it all come together!
ReplyDeleteme too bro :3
Delete