About gamedev...

Pixel-Perfect selection using shaders

Object selection in Sky Tycoon is not handled in the traditional Unity way using colliders and OnMouse or IPointer events.

Instead, to achieve pixel-perfect selection with minimal performance impact, the game uses a custom solution based on rendering selectable objects to a RenderTexture and getting selection info from it during each frame.

In theory

I first came across the idea of performing object selection via shader output when playing The Sims 4 recently.

Check out this clip of the game where the selection alternates between sink and counter. If there were colliders — even mesh colliders — involved, you’d expect selection to not work properly when moving into the inset area of the sink.

To perform pixel-perfect selection, Sky Tycoon assigns a unique id to each object placed in the scene. Converted to a color, this id is rendered each frame to a `RenderTexture` using Replaced Shaders (https://docs.unity3d.com/Manual/SL-ShaderReplacement.html). Using input coordinates like mouse position, the game can then decide which color (and in turn unique id) is currently hovered.

Converting id to color

In order to render the unique object ids, the game has to convert its internal id format from a uint to Color. Reading the selection id back from the RenderTexture, the same process has to be performed in reverse.

This could be done inside the replacement shader, but since the conversion from id to color only needs to happen once during object initialization, the game stores the converted color inside a MaterialPropertyBlock, which can be accessed from within shaders using

fixed4 c = UNITY_ACCESS_INSTANCED_PROP(_SelectionColor);

Converting from uint to Color is an interesting subject. Unity’s Color allows for storage of four byte-sized components, namely RGB and A. Conveniently, the default uint type in C# stores it’s values in a 32-bit format, which in turn can be stored in exactly four bytes.

This method is akin to float packing (http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/), but does not suffer from precision issues when rendered to an `ARGB32` target.

To convert a uint (or any 32-bit data structure for that matter) to a Color, you can get the four bytes it’s represented by and simply push them into Unity’s COlor32 struct, which already allows for assignment by byte values and is implicitly convertible to Color. Getting the separate bytes of a data type can be done using .NET’s System.BitConverter.

public static Color32 UIntToColor(uint number) {
  var intBytes = BitConverter.GetBytes(number);
 
  return new Color32(intBytes[0], intBytes[1], intBytes[2], intBytes[3]);
}

Similarly, converting from a Color value back to a uint again utilizes the BitConverter class:

public static uint ColorToUInt(Color32 color) {
  return BitConverter.ToUInt32(color.ToByteArray(), 0);
}

To get a color’s RGBA channels as a byte array, I wrote a simple extension method for the Color32 struct:

public static byte[] ToByteArray(this Color32 color) {
  return new[] {color.r, color.g, color.b, color.a};
}

Rendering to a texture

Each frame, the previously stored Color of each selectable object is rendered using Replaced shaders to a separate RenderTexture.

To adhere to the pixel-perfect requirement, the game sets some additional properties on the RenderTexture upon creation.

selectionTexture = new RenderTexture(Screen.width, Screen.height, 0) {
  antiAliasing = 1,
  filterMode = FilterMode.Point,
  autoGenerateMips = false,
  depth = 24
};

Since there should be no anti-aliasing or filtering performed on the texture, both properties are turned off (or set to point filtering respectively). There’s no need for MipMaps, since the game only uses the texture in it’s full resolution anyway.

It is important to declare a depth buffer (https://en.wikipedia.org/wiki/Z-buffering) for the texture, since selectables in front of other selectables should be preferred when hovering over them.

Not writing any depth information can result in the following unwanted behaviour, where objects behind the foremost object are mistaken for the currently selected object when in reality the foremost object should be focused.

Additionally, the camera that renders to this texture should have it’s clear color set to RGBA(0, 0, 0, 0) since that equals no selection. The clear mode itself is set to Solid Color.

Here’s a comparison of what the game’s main camera sees and what’s rendered to the selection texture:

Image for post

Note that I’ve increased the contrast of the selection texture, since it would otherwise be barely visible due to the alpha channel being used to encode values as well (very low ones in this case).

The shader that renders the selection colors themselves is very simple. As mentioned before, since Sky Tycoon uses Instancing for selectables like objects, actors, etc., the shader can access the selection color via a MaterialPropertyBlock. All the shader does then is render that color to the screen, overriding any previously rendered fragment. The shader has Z-Writing enabled by default, so it always renders the foremost object’s selection id.

Shader “Custom/Selection Id” {
  SubShader {
    Tags { “RenderType”=”Transparent” }
    Pass {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
 
      #include “UnityCG.cginc”
      UNITY_INSTANCING_CBUFFER_START(Props)
        UNITY_DEFINE_INSTANCED_PROP(fixed4, _SelectionColor)
      UNITY_INSTANCING_CBUFFER_END
 
      float4 vert (float4 vertex : POSITION) : SV_POSITION {
        return UnityObjectToClipPos(vertex);
      }
 
      fixed4 frag () : SV_Target {
        return UNITY_ACCESS_INSTANCED_PROP(_SelectionColor);
      }
      ENDCG
    }
  }
}

Determining selection

The resulting RenderTexture is checked every frame for the color under the current mouse cursor position. If it results in something other than ‘no selection’ (RGBA(0, 0, 0, 0)), the game can then convert the color back to an id and look it up. If it is valid (e.g. exists in the game world), we then decide what to do with that information.

Usually, we display some sort of indicator to tell the user there’s an action to perform on this object. When left-clicking, we change the currently selected object to the one under the mouse cursor and tell the object about it. It is then individually able to decide which actions to take (open a dialog, etc.)

This is what the final selection behaviour looks like:

Conclusion

As you saw in this article, shaders are not only useful for end-user graphics computations, but can also be used to provide more complex game logic. Using shaders to perform pixel-perfect object selection is an interesting approach to a problem that would normally result in performance loss when too many objects are placed in your scenes.

I hope that this article shed some light into some of the finer inner workings of Sky Tycoon. If you have any questions regarding the article or Sky Tycoon in general, feel free to leave a comment or reach out to me on Twitter @CelestialStatic.

0 comments

Game Configuration via ScriptableObjects

Any game or application in general benefits from being configurable over hard-coding configuration values. Say I want to provide a value of the maximum health points of my main character. I could simply hard-code this:

private const float MaxHealth = 100f;

which leads to a recompilation of the Assembly the field if defined in every time I want to change the value. A better approach might be to serialize the field on a character behaviour:

public class CharacterBehaviour : MonoBehaviour {

    public float MaxHealth => _maxHealth;

    [SerializeField]
    private float _maxHealth = 100f;

}

Note how I restricted write access to the field by exposing it as a get-only property on the public-facing side while declaring the field itself private. Unity’s inspector still honors [SerializeField] attributes on private fields as well. This way, I’ll be able to change the value from within the inspector, but won’t be able to overwrite the value once my game is running.

This is a valid approach that works great for dynamic attributes of behaviours like current health points, names, etc. Changing the value also doesn’t incur a recompile anymore as it is serialized from within the inspector.But what about global game configuration? If I want to configure every character to have a maximum health. Providing the value on the character behaviour as seen above does not only unnecessarily increase the size of my serialized component because ideally I’d only need to provide the value once, it also leaves room for error because if I want to someday change the maximum health of all my characters, I’ll have to go through every character behaviour in my scene/assets and change it manually.

If I wanted to provide a different maximum health value for every character in my game, I’d be fine with putting the field on the character behaviour. Global configs that don’t change for many behaviours don’t belong there though.

I could of course simply create a config GameObject that provides these values and use it from my scripts via `GameObject.Find`. This would also mean that I’d have to include the config objects in every scene that needs to load it. Also, using GameObjects that provide no value to the active scene and simply act as containers is frowned upon because it incurs a performance overhead that is better reserved for essential objects.

ScriptableObjects to the rescue

In addition to `MonoBehaviour` Unity provides a second serialized base class called `ScriptableObject`. These are classes that can be instantiated similar to prefabs as assets within your project view, seperate from any active scene.

namespace CelestialStatic.Unity.Settings {

    public abstract class UnitySettings : ScriptableObject {
    }

}

Creating an abstract base class that all settings will derive from allows me to load all of them from Resources later on and add base functionality.

namespace CelestialStatic.Unity.Settings {
    [CreateAssetMenu(fileName = "CharacterSettings", menuName = EditorConstants.SettingsMenuBase + "/Character")]
    public class CharacterSettings : UnitySettings {

        public float MaxHealth => _maxHealth;

        [SerializeField]
        private float _maxHealth = 100f;

    }
}

It’s always good to move your classes into namespaces, avoiding name conflicts between your own classes and third-party libraries and packages you import from the asset store.The `CreateAssetMenu` allows me to add the character settings asset to the context menu when right-clicking inside the project view. I’m using a constant `EditorConstants.SettingsMenuBase` because I want all of my settings grouped under the same base string `Celestial Static Games/Settings`.

„Create“ context menu

Loading configuration during runtime

Now that I can store my configuration files, I still need to provide an easy way to load them during runtime.When working on a larger game or codebase, you inevitably come across the concept of Dependency Injection. Any class requiring a reference on e.g. a world manager or UI window can declare the dependency on itself and as long as the required instances have been registered with the injection container, they will be automatically injected upon creation of new instances of the class.On all my recent projects, I’ve chosen to go with Zenject (now Extenject after some legal disputes) as my preferred framework. It’s very easy to get set up and has a minimal performance overhead. If you haven’t checked out the concept or any of the frameworks available so far, I’d highly recommend you doing so. Its worth the investment even for small projects.To register all configuration files with my DI container, I have to load them from my Resources folder and register them with their corresponding types.

namespace CelestialStatic.Unity.Settings {

    public class SettingsInstaller : MonoInstaller {

        public override void InstallBindings() {
            BindSettings();
        }

        private void BindSettings() {
            foreach (var setting in Resources.LoadAll<UnitySettings>("Settings")) {
Container.Bind(setting.GetType()).FromInstance(setting).AsSingle().NonLazy();
            }
        }
    }

}

Most of this class is boilerplate code specific to Zenject, but the code inside `BindSettings()` is applicable to any framework (or even no framework at all).Thanks to the magic of dependency injection all I have to do to access my configuration classes during runtime is declaring them in a constructor or injectable method:

public class CharacterBehaviour : MonoBehaviour {

    [Inject]
    public void Initialize(CharacterSettings characterSettings) {
        // Access settings here...
        var maxHealth = characterSettings.MaxHealth;
    }

}

For any new type of configuration all I have to do is create a new class containing the fields I require and configuring the asset from within Unity’s inspector.

I hope you found this tip useful and can see the benefits of providing a clean architecture for your configuration files.

0 comments