Introduction

TypeSafe is a tool for reflecting changes made in the Unity Editor to your code. By scanning the project and generating static classes from your Resources, Layers, Tags and Scenes you no longer have to rely on hard-coded ‘naked-strings’ littered around your codebase. This has a number of benefits that improve the robustness of your code:

  • Any changes made in the Editor that might introduce bugs in your code will cause compiler errors. (e.g. renaming a resource, which would break a hard-coded path at runtime, shows up as a compiler error immediately)
  • Misspelling layers, scenes, etc is no longer possible.
  • It is possible to iterate over the resources in your project. More on that later.

Resources

In Unity, a Resource is any asset that is contained within a folder called Resources. Usually to load such a resource you would pass a path into Resources.Load, for example:

Traditional Method

var prefab = Resources.Load<PlayerBehaviour>("Player/MainPrefab");
var player = (PlayerBehaviour)Instantiate(prefab);

This has a number of drawbacks.

  • If the path to the resource changes, or is typed incorrectly, this code will fail at runtime.
  • The type of the resource may change in the future and cause this code to fail at runtime.

TypeSafe is designed to relocate this kind of error to compile-time instead of runtime, ensuring that you find out about bugs as soon as they are introduced.

TypeSafe Method

// If MainPrefab is missing, this will be a compile-error instead of a runtime error.

var player = Instantiate(SRResources.Player.MainPrefab.Load());

Resource Wrapper

Each resource in your project is wrapped in a simple Resource object. This object contains the path to the resource, a cached copy of the resource once it has been loaded, and helper methods for asynchronous loading. Usually you won’t need any of this, as the object implicitly converts to the type of the resource when assigned to a field.

For example, a Material resource will be wrapped in a Resource<Material> object. When this wrapper object is assigned to a renderer.sharedMaterial field it will automatically load the resource and assign the result to the field.

// Assign alternate material. Resource is loaded and cached automatically.

player.GetComponent<Renderer>().material = SRResources.Materials.PlayerAlt;

The resource wrapper object automatically loads and caches the resource when assigned to a field. After the object is first assigned, any subsequent uses will return the cached object instead of loading it again. You don’t need to cache a copy in your scripts like you do with the traditional Resources.Load method.

Class Overview

public class Resource<TResource> : IResource where TResource : Object
{
	// Type of resource pointed to.

	public Type Type { get; }

	// Name of the resource (ie when calling unityObject.name)

	public string Name { get; }

	// Path string that is passed to Resources.Load(...) to load this resource object.

	public string Path { get;}

	// True if the resource has been loaded before.

	public bool IsLoaded { get; }

	// Get the object for this resource. If the resource is not loaded, this will block until it is.

	// Subsequent calls to this method will return the cached reference to the resource.

	public TResource Load();

	// Returns a ResourceRequest object that can be used to yield in a coroutine. 

	public ResourceRequest LoadAsync();
	
	// This is the magic. When this object is assigned to a field matching the Resource type

	// it will automatically call Load() and return the result.

	public static implicit operator TResource(Resource<TResource> resource);
	
	// Unload this resource.

	public void Unload();
}

Folders

The static class for resources generated by TypeSafe (named SRResources by default, but this can be customized) has nested classes for each folder. For example, a resource located at Assets/Resources/Materials/Player/PlayerAlt can be accessed by SRResources.Materials.Player.PlayerAlt.

Each folder has methods for getting a list of the contents, contents of a certain type, and matching recursive variants (which include resources in sub-folders).

Class Overview

// Return a list of all resources in this folder (does not include sub-folders)

// This method has a very low performance cost, so don't worry about caching the result.

public static IList<TypeSafe.IResource> GetContents();    
	
// Return a list of all resources in this folder and all sub-folders.

// The result of this method is cached, so subsequent calls will have very low performance cost.

public static IList<TypeSafe.IResource> GetContentsRecursive();

// Return a list of all resources in this folder of type TResource (does not include sub-folders)

// You should cache the result of this method if used often.

public static List<TypeSafe.Resource<TResource>> GetContents<TResource>();

// Return a list of all resources in this folder of type TResource, including subfolders.

// You should cache the result of this method if used often.

public static List<TypeSafe.Resource<TResource>> GetContentsRecursive<TResource>();
	
// Calls Unload() on all resources in this folder.

public static void UnloadAll();

// Calls Unload() on all resources in this folder and all sub-folders.

// You should cache the result of this method if used often.

public static void UnloadAllRecursive();
	

Prefabs

TypeSafe handles prefabs as a special case. Instead of being wrapped in the generic Resource object, they are wrapped in a specialized PrefabResource object. This provides some syntax sugar for instantiating to save you some time.

Class Overview

public class PrefabResource : Resource<GameObject>
{
	// Instantiate a new instance of this prefab and return the result.

	public GameObject Instantiate();

	// Instantiate a new instance of this prefab at position and rotation and return the result.

	public GameObject Instantiate(Vector3 position, Quaternion rotation);
}

Unloading Resources

Resource folders have an UnloadAll and an UnloadAllRecursive method, that calls Unload() on each resource wrapper in the folder or folder & sub-folders respectively.

The resource wrapper holds the cached reference to the resource as a Weak Reference, which means that it does not prevent Unity’s Resources.UnloadUnusedResources() method from working correctly on TypeSafe-loaded resources.

Layers and Tags

Both Layers and Sorting Layers in TypeSafe are wrapped in a struct which contains the layer ID and name. The Layer wrapper also contains the bitmask for the layer.

Layers

TypeSafe makes it simple to access and use layers. By using the SRLayers class to reference layers you can be certain that if your layer configuration changes later you’ll discover any problems immediately. The Layer object implicitly converts to an integer, so can be assigned to a gameObject’s layer field automatically.

gameObject.layer = SRLayers.Characters;

Class Overview

public class Layer
{
	// Name of the sorting layer, as might be passed to LayerMask.NameToLayer

	public string name { get; }

	// Layer number, as assigned to <c>gameObject.layer</c>

	public int index { get; }

	// Layer mask, as might be passed to Physics.Raycast

	public int mask { get; }
}

The SRLayers object also has an All property that returns a list of Layer objects containing all the layers in your project.

SortingLayers

By default, SortingLayers are accessed through the SRSortingLayers class. The SortingLayer object implicitly converts to an integer or string, so can be assigned to a renderer’s sortingLayerID/sortingLayerName fields automatically.

GetComponent<Renderer>().sortingLayerID = SRSortingLayers.Characters;

Class Overview

public class SortingLayer
{
	// Name of the sorting layer, as assigned to Renderer.sortingLayerName

	public string name { get; }

	//Unique ID, as assigned to Renderer.sortingLayerID<

	public int id { get; }
}

The SRSortingLayers object also has an All property that returns a list of SortingLayer objects containing all the sorting layers in your project.

Tags

Tags are accessed via the SRTags class (by default. See Customize). It contains string properties generated from your tags, and an All property that returns a list of strings with all your tags.

Scenes

Scenes are accessed via the SRScenes class (by default. See Customize). This class has properties that return a Scene wrapper object, that exposes the scene name and index. You can pass this object directly to Application.LoadScene(...) or use one of the helper methods. Only Scenes listed in the Build Settings window will be included.

Application.LoadLevel(SRScenes.MainLevel);

SRScenes.MainLevel.Load();

Class Overview

// Name of the scene, as passed to Application.LoadLevel(string)

public string name { get; }

// Scene index, as passed to Application.LoadLevel(int)

public int index { get; }

// Calls Application.LoadLevel(...)

public void Load();

// Calls Application.LoadLevelAdditive(...)

public void LoadAdditive();

// Calls Application.LoadLevelAdditiveAsync(...)

public AsyncOperation LoadAdditiveAsync();

// Calls Application.LoadLevelAsync(...)

public AsyncOperation LoadAsync();

Input

Your Input axes (as defined in the Unity Input Manager) are accessed via the SRInput class (by default. See Customize). This class has properties that return an InputAxis wrapper object, that exposes the input name and state.

Class Overview

public class InputAxis
{
	// Name of the input axis, as used by Input.GetAxis(string)

	public string Name { get; }

	// Equivalent to calling Input.GetAxis(Name);

	public float Value { get; }
	
	// Equivalent to calling Input.GetAxisRaw(Name);

	public float RawValue { get; }
		
	// Equivalent to calling Input.GetButton(Name);

	public bool IsPressed { get; }	
	
	// Equivalent to calling Input.GetButtonDown(Name);

	public bool Down { get; }	
	
	// Equivalent to calling Input.GetButtonUp(Name);

	public bool Up { get; }
}

Audio Mixers

In the settings window, drag Audio Mixer assets to the Audio Mixers section on the Assets tab. The parameters and snapshots of mixers in this list will be available in the SRAudioMixers class (by default). Parameters will be under SRAudioMixers.__MixerName__.Parameters, and snapshots will be under SRAudioMixers.__MixerName__.Snapshots

Animators

In the settings window, drag Animator Controller assets to the Animator section on the Assets tab. The parameters and layers of animators in this list will be available in the SRAnimators class (by default).

Parameters

Parameters will be under SRAnimators.__AnimatorName__.Parameters. Parameters are integers, and correspond to the equavelent call to Animator.StringToHash.

// TypeSafe

animator.SetFloat(SRAnimators.SomeAnimator.Parameters.SomeParameter, 0.5f);

// Original

animator.SetFloat("SomeParameter", 0.5f);
// or 

animator.SetFloat(_someParameterHash, 0.5f); // _someParameterHash populated elsewhere by Animator.StringToHash.

Layers

Layers are under SRAnimators.__AnimatorName__.Layers. Layers are stored as an integer, and correspond to the equavelent call to Animator.GetLayerIndex.

// TypeSafe

animator.SetLayerWeight(SRAnimators.SomeAnimator.Layers.SomeLayer, 0.5f);

// Original

animator.SetLayerWeight(animator.GetLayerIndex("SomeLayer"), 0.5f);

Customize

Automatic Rebuild

TypeSafe automatically regenerates the classes whenever it detects a change in your project that might impact your code. This can be disabled if you want to only trigger scans manually. If no changes are detected it won’t trigger a script refresh.

The Automatic Rebuild Delay setting forces a minimum build time. This is helpful when you are rearranging resources as triggering a script refresh when you are busy rearranging can be frustrating. If another change is detected during a scan the scan is cancelled and re-queued.

Naming Scheme

The class naming scheme can be adjusted from the Settings panel. The namespace, prefix and suffix of the class can be changed.

  • The namespace is the C# namespace that the generated classes will be contained in. By default this is empty and the classes are in the root namespace.
  • The prefix is a string of characters added to the beginning of the class name (e.g., the default prefix SR is added to Resources to make SRResources.
  • The suffix is the same as above, but added to the end of the class name.

The settings panel will provide a preview of your changes and ensure that the generated names don’t conflict with any built-in Unity classes or your own code.

Limitations

Conflicting Resources

Two resources cannot share the same path and name. If TypeSafe detects this, one will be discarded and an error printed to the console (clicking on this error will take you to the conflicting resources).

Nested Folder Names

Nested folders cannot have the same name as the parent folder. If TypeSafe detects this it will prefix the offending name with an underscore to prevent compiler errors and print a warning to the console. The same applies to resource names.

Unicode

TypeSafe allows most Unicode letter characters in resource names, except for non-ASCII numbers. This is due to a bug with Unity’s Mono compiler that incorrectly handles these characters.

Reserved Names

TypeSafe reserves a number of names that are used internally, or are part of the API. These names cannot be used for resources, layers or any other item. TypeSafe will ignore any resources with these names. A truncated list of these names is below:

  • All
  • None
  • GetContents
  • GetContentsRecursive

API

TypeSafeApi

The TypeSafeApi static class has a few exposed methods for invoking the TypeSafe compile process via script.

Member Name Description
QueueRefresh() Queue a new scan/compile process. Will do nothing and print a warning if IsBusy is true.
Cancel() If a TypeSafe scan is currently in progress, cancel it.
IsBusy True if TypeSafe is currently scanning or compiling.
FormatTypeName(string) Format a string with the class prefix/suffix specified in the Settings window. Useful when using Custom Data Sources

TypeSafeApi is located in the TypeSafe.Editor namespace.

Custom Data Source

TypeSafe supports integrating into your own code to provide custom generated classes based on your own data. This is accomplished by creating a class that implements the ITypeSafeDataSource interface.

namespace TypeSafe.Editor 
{
	public interface ITypeSafeDataSource
	{
		TypeSafeDataUnit GetTypeSafeDataUnit();
	}
}

This interface has a single method, GetTypeSafeDataUnit(), that returns a TypeSafeDataUnit object. Any class that implements this interface will be automatically instantated and this method invoked during the TypeSafe scan process.

TypeSafeDataUnit

Property Name Description
ClassName The name of the generated class. You can use TypeSafeApi.FormatTypeName(…) to conform to the naming scheme specified in the Settings window.
FileName Used to create the generated file name ({0}.Generated.cs, placed in the TypeSafe usr folder). Uses ClassName by default.
DataType System.Type used for each data entry in the generated class. See type limitations.
Data Collection of TypeSafeDataEntry objects used to populate the generated class.
NestedUnits Collection of TypeSafeDataUnit objects. These will be added as nested classes inside this data unit.
EnableAllProperty Setting to true will enable the generation of a property named ‘All’ that returns a list of all entries in the data set.

TypeSafeDataEntry

Property Name Description
PropertyName        String used as property name of the generated entry. Will be filtered through reserved name and C# compliance checks.
Parameters The array of parameters passed to the data type constructor. If the data type is a primitive or string, this should have one entry of the exact same type.

Type Limitations

The type of generated fields must fulfil one of the following criteria:

  • Is a primitive (int, uint, byte, etc)
  • Is a string
  • Is a class with a public constuctor, with parameters matching the objects passed into the TypeSafeDataEntry.Parameters array.

FAQ

What’s up with global::?

In all code generated by TypeSafe classes are referenced by their absolute path, starting with global::. This makes the code a bit bulky, but it ensures that there are no naming conflicts with existing code in your project. Users finding that a class in their project is interfering with code in an asset is a problem we’ve had reported in the past, so we took steps to ensure that this didn’t happen with TypeSafe. This is the same reason why we use a .NET assembly for all non-generated code.

Contact

If you have a problem which isn’t answered by this documentation or the FAQ, or you find an error in these docs, feel free to open an issue on the GitHub repository or post in the Unity Forum Thread.