Useful Extension Methods (in Unity / C#)

In my previous article, we took a detailed look at how and when to use extension methods. In this companion article, I’ll share several useful extension methods you can use in your own projects for free.

Extension methods can add convenient functionality to untouchable classes. They can also help keep your codebase clean and organized.

Used frivolously, however, extension methods can do quite the opposite. They can break object oriented design principles, obfuscate your code and contribute to coding nightmares.

It’s super important that you familiarize yourself with the pitfalls and best practices, so please read part 1 before moving on. Seriously, read it now. These examples will wait.

 

GameObject Extension Methods

Since we’re working with Unity, let’s begin with the built-in GameObject class.

Rather than showing you each method right from the start, I’ve given you the entire class. This provides you with a more complete picture. I’ll cover details of each method immediately following the code.

Notice that I’ve included the class within the namespace containing the declaration of the class we’re extending. Also note that the name of the class reinforces the idea that only methods extending GameObjects belong. Organizational best practices like these were covered in more detail in part 1.

These particular methods leverage recursion. Recursion is beyond the scope of this post but it’s worth your research time if you’re unfamiliar with the concept.

 


SetLayersRecursively()

This method sets the layer of the calling GameObject and all its children.

This comes in handy in a variety of situations but is especially useful when dealing with UI plugins like NGUI, EZ-GUI and 2DToolkit. All plugins make certain assumptions about how you’ll use them but UI plugins usually take this to an extreme. There are times when you may need to work around them.

For example, when NGUI objects are moved or instantiated, NGUI automatically changes their layers to whichever is used by the UIPanel they are parented under in the object hierarchy. If no UIPanel is found as NGUI traverses up the object list, the layers are set to the one used by the UIRoot instead. This could cause entire sections of your UI to disappear unexpectedly. It’s up to you to repair the damage.

Side note: you can sometimes avoid this particular NGUI anomaly by adding a UIPanel to your prefab.

This method is overloaded to give you the option of passing in the layer’s string value instead.

 


SetCollidersEnabledRecursively()

This enables or disables the Collider Components of the calling GameObject and its children.

This is useful when toggling on/off collision for a group of objects. When a level trigger is activated, for example, it may become disabled and yet must remain in the scene visually. If the trigger has several moving parts, such as puzzle in a Zelda-style game, then you may need to disable interaction with the object’s entire hierarchy.

Of course, this is only one way to disable collision on a group of objects. You may, for example, simply remove the colliders instead.

 


SetRenderersEnabledRecursively()

This enables or disables the Renderer Components of the calling GameObject and all its children.

This is useful for toggling on/off the visuals of a group of objects. This can be used to highlight portions of a level object until its interacted with, or hide things while the game is paused (so the player can’t “freeze-frame” while they come up with a cheating strategy).

 

Transform Extension Methods

For completeness and convenience, I’ve also extended the Transform class with some of the same methods.

These methods simply call the ones I just showed you. Unity exposes the collider and renderer properties to both the GameObject and Transform classes, so it makes sense to have access to these methods from both classes.

Notice that I haven’t included SetLayersRecursively here. I omitted that method because Unity does not expose the layer property directly to the Transform class. I wanted to keep things consistent.

Anyway, that method can still be accessed by the Transform class through its gameObject property.

 

IList Extension Methods

This time we’re extending an interface instead of a class. If you started this series from the beginning, this should look familiar.

 


Shuffle()

This randomizes the values of an array, generic list or any other IList.

Note that you’ll have to include the System.Collections.Generic namespace to use this method with built-in arrays. Since that’s where IList is defined, and it keeps my organization consistent, I’m at peace with this small limitation.

If you always define your extension methods within the same namespace as the extended type, this kind of limitation should be extremely rare. However, if you find yourself using built-in arrays often, you can remove this minor annoyance by replacing the “namespace” block with a “using” statement.

 

IDictionary Extension Methods

Now here’s an interesting set of methods. These will give you several options for merging dictionaries.

The code gets a bit heavy, but you need not worry about the details. Just read the comments in the MergeRecursively() method before moving on.

These methods can be particularly useful when dealing with data-driven software. Usually, some JSON or XML data is read into the game at runtime and deserialized into dictionaries for access. Before the game can use these dictionaries, it is sometimes necessary to merge them together. This forms the complete or relevant data set.

The basic benefit of data-driven design is that it permits developers to tweak things in the game without requiring the players to download an updated version of the entire program. With this type of design, data is usually pulled into the game from a back-end database or content management system. However, it can also be just as useful to read data from disk to avoid the need to recompile the program whenever changes are made to things like game settings and level data.

There are countless reasons your game may need to load data dynamically. For this discussion, the cases we’re interested are only those in which you start with useful, yet potentially incomplete or outdated data. This data is then merged with additional data to form the most recent, complete or relevant data set.

At first it may seem silly to combine data, but this approach does permit some interesting possibilities.

Once your data sets are read into the game, and deserialized into dictionaries, merging them is dead simple with these extension methods.

 


Merge()

This will merge the contents of a dictionary parameter into the dictionary calling the method. Any new keys will be added and duplicates will be skipped.

The optional parameter, “mergeSubDicitonaryLevel”, will cause the method to merge sub dictionaries instead of skipping over the duplicate. In order to be merged, sub dictionaries will need to share the same key-value types as the original dictionary. Additionally, they will only be merged for the specified number of nested levels. A value of 0 will not merge any sub dictionaries.

So when would you need to merge data sets? Actually, that possibility comes in handy more than you might first think.

For the purpose of explaining this particular method, lets assume that you’re working with some base set of data. Additionally, data exists to represent variations of that base. To build these variants into the game, each variants’ data is supplemented with the shared base data to form the complete picture. I know that sounds strange, but stick with me.

One example of this could be when your game has groups of monsters sharing similar looks, stats and behaviors. Each monster is dynamically added into the game based on some complete data set which describes it.

Since monsters of some given type share similar qualities, much of the data describing them won’t change with each variation. In other words, we have the opportunity to extract the similarities into a data set describing the base norm for that monster type.

Variants of each monster type can then be described in terms of differences from their base (level-2 strength, add lightning alignment, etc). The base data is merged into the variant data to build the full data set describing that particular monster.

Combining base and variant data to build the full data set for a given monster is just one convenient way to make groups of monsters share some commonalities, yet also bare independent differences. At the same time, you’re eliminating the need for quite a bit of repeated data.

This would allow you to tweak monsters on a global scale, while still being able to fine-tune each monster independently. Plus, it permits you to “juice” your monster types, save time, and get more “bang for your buck”.

This is a highly game-specific example, but the data-to-game relationship usually is. Your mileage may vary.

Merge() is the appropriate choice when variant data is supplemented with common base data to fill in any gaps…

 


MergeOverwrite()

This will merge the contents of a dictionary parameter into the dictionary calling the method. Any new keys will be added and duplicates will be overwritten.

The optional parameter, “mergeSubDictionaryLevel”, functions in the same way as before, except that the recursive calls will adopt the “overwrite” preference when duplicates are found.

Maybe your game ships with some default data used for offline play. When the game goes online, the game can patch that data and update it on disk.

As game developers, we’re always interested in keeping our bandwidth and server loads at a minimum. Therefor it’d be wise to only store the information which varies in these patches, not the entire data set. This way our patch size is kept extremely small.

Of course, you’ll also need to store the last patch version so your update process can include all missed patches.

MergeOverwrite() is the appropriate choice when new data replaces any duplicates in the current set…

 


MergeRecursively()

This method does the work for Merge() and MergeOverwrite(). Since most of their functionality is shared, this keeps the code dryNotice that the method is “private”, making the method inaccessible outside of this class.

We could have made this method public and completely omitted Merge() and MergeOverwrite() instead, but then we’d be dealing with a lot of parameters in our calling code. That wouldn’t be as readable or convenient.

 

Dictionary Extension Methods

The method shown here can come in handy when multiple developers are working on the same project and may not adhere to the same naming conventions. Perhaps your back-end and front end developers prefer different conventions, for example.

 


ToLowerCaseKeyed()

Returns a copy of the dictionary with lower case keys.

Note that this method returns a copy rather than removing old keys and replacing them with lowercase keys. This is largely a matter of style and code readability. If memory use is a concern, you may prefer an in-place method in which you avoid the copy.

You may have use for a ToUpperCaseKeyed() method, but I haven’t needed one. I’ll leave that to you to write.

 

Where to go from here?

Well, get coding and I’m sure you’ll find the need for an extension method before you know it.

The following resources may also prove useful to you. You’ll have to exercise caution when adopting the code from these external sites. Since anyone can add to these sites, the code may come from novices or developers unconcerned with best practices.

ExtensionMethod.net: A huge collection of extension methods from all over the net.

Stack Overflow’s “Favorite Extension Methods” thread: A huge forum where many developers have posted their favorite extension methods.

Very soon I’ll be writing a post about how to leverage an extension method, along with some other trickery, to convert to and from a string value and an enum. If that post doesn’t become part of this series, I’ll be sure to add a link to it here. Stay tuned.

 

Closing Words

If you found this article helpful, please leave me a comment, share this post, or use the donate button to buy me a coffee.

A few seconds of your time will make my entire day!

– John Hutchinson

 

Leave a Reply

Your email address will not be published. Required fields are marked *

2 × 4 =