What to do when the puck is hidden behind the rink?

One of the problems with a side-top-down view is that sometimes the puck is in a position where it is not visible from the camera. It can be when it's behind a group of players, behind a goal or just hidden by the rink itself

So to tackle this problem there are a few different ways to handle this:

  1. Show some kind of UI indicator of where the puck is
  2. Change camera angle
  3. Make puck render through objects

I don't want to change the camera angle since that will affect the controls of the game and most likely confuse the human players. A UI indicator is very messy and covers up even more of the screen, so I decided to go with option 3.

Task: Make puck render through objects

This is a bit tricky and requires some shader work

My initial idea was just to change the geometry rendering layer and render the puck above everything else. However this turned out to be a really bad idea as the sense of depth was completely lost. Making it look like the puck was flying when on ground.

So instead I started writing a shader that produces a sillouette for the puck when something is in the way. I also added a bit of transparency to it so we don’t get the “puck flying” feeling again.

PUCK OFF Normal State - Puck is visible
PUCK OFF Puck is hidden by rink, but visible as a transparent white sillouette

Result

The end result was pretty good, this is what it looks like in game:

PUCK OFF Gameplay

Shader

This is a two-pass surface shader In the first pass we render the sillouette transparent without culling any faces and depthtest set to GEqual. This will make it render not just the surface but also the “shape” of the object. GEqual makes sure that we only render the sillouette if it is behind something else. We also disable lighting when doing this.

In the second pass we just render the puck as normal, resetting culling, depthtest and lighting.

The neat thing about this is that we can change the sillouette color and transparency directly in Unity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Shader "Custom/Standard-Color-Sillouette" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SillouetteColor ("SillouetteColor", Color) = (1,1,1,0.2)
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

    CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        fixed4 _SillouetteColor;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }

        void surf_sillouette (Input IN, inout SurfaceOutputStandard o) {
            o.Albedo = _SillouetteColor.rgb;
            o.Metallic = 0;
            o.Smoothness = 0;
            o.Alpha = _SillouetteColor.a;
        }

    ENDCG

    SubShader {
        Tags {  
            "Queue"="Transparent" 
            "RenderType"="Transparent" 
        }

        Cull Off
        ZWrite Off
        ZTest Always
        ZTest GEqual
        Blend DstColor SrcColor
        Lighting Off

        CGPROGRAM
        #pragma surface surf_sillouette Standard alpha
        #pragma target 3.0
        ENDCG

        Cull Back
        ZWrite On
        ZTest LEqual
        Blend SrcAlpha OneMinusSrcAlpha
        Lighting On

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0
        ENDCG
    }
    
    FallBack "Diffuse"
}