Creating an Arena Shield Shader for my Circle Battle Game

5 min read
Circle with Fresnel effect and a

The Result

Here is what I ended up with:

The Process

I was hunting for a nice shader to spice up my arena boundary for Circle Pusher®. Browsing through Godot Shaders brought me to this page.

That looked promising:

GIF showcasing the shader found on GodotShader.com

One issue became clear immediately, I was looking at a 3D spatial shader and Circle Pusher® is a 2D top down game. But I didn’t worry to much about it, I wanted to try out some viewport shenanigans after watching Raffa’s talks about them anyway.

So I did the good old copy paste and tried to set everything up in a new 3D Scene.

Easy enough just add a sphere, material and drop the shader there, voila a sphere with Fresnel.

Screenshot from the Godot Editor showing a Sphere with Fresnel.

One important detail for later: The sphere is position with the center in the center of the scene and the camera is pointing at the circle along on the Z-axis.

Drawing of a circle showing the Z-axis in the center of the circle the Y going up and the X to the side.

Now, the interesting part is to make it wobble, and ideally, make it wobble where the little battle circles hit the border. Out of the box, the shader uses two curves to animate the impact and ripple effect. I peeked at the demo project and translated it to GDScript. With that, it wobbled at a fixed point 🎉—a good start.

Getting my bearings

Now, how exactly do I get the position of the circle translated from the 2D space to the 3D space of the shield?

It took me a while, and I went through some heavy design discussions with my friend Chat G..

But all that translate or ray-cast stuff seemed a bit much for this case. So, a good night of sleep brought me this simple solution:

Drawing of two circles with a label on top: 2D over the left one and 3D over the right one. An arrow extends from the center of each circle pointing towards the outer boundary. A 0 is placed on top of the arrow at the center of both circles, and a 1 is positioned at the end of the arrow. Beneath the arrow, the distance is indicated as 0px from the left circle's center and 977px at the end of the arrow. For the right circle, the measurements are 0.0m at the center and 0.5m at the end of the arrow.

If you can make it 0 to 1 make it 0 to 1 😄

So here is the plan:

  1. Get the distance to the circle from the center of the arena
    • Translate that to a 0 to 1 range
  2. Get the direction to the circle from the center of the arena
  3. Send this to the 3D scene
  4. Translate the 0 to 1 range to the sphere’s 3D space
  5. Profit 💰

As mentioned before, it is important that the sphere is positioned correctly in the 3D scene. This setup allows me to just ignore the Z-axis and always set it to 0.

I am pretty happy with that. What I don’t like is the stiff animation and the fact that it always looks the same. But whatever, I can tweak that later. 👍

Time to make it work with multiple circles! 🎉

How many circles? Yes!

Things get a bit blurry from here. I had some discussions again with my friend Chat G., sadly we kept talking past each other. So, I don’t know exactly how I ended up with that idea.

I started throwing out everything I didn’t want from the shader and started fiddling until I ended up with this.

void vertex() {
	float displacement = 0.0;
	// Calculate the vertex position in world space
	vec3 vertex_world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;

	for (int i = 0; i < _circle_global_positions.length(); i++) {
		vec3 circle_global_position = _circle_global_positions[i];

		// Calculate distance to circle
		float vertex_distance_to_circle = clamp(distance(vertex_world_position, circle_global_position), 0.0, 1.0) ;
		vec3 vertex_direction_to_circle = direction(vertex_world_position, circle_global_position);
		float vertex_dot_to_circle = dot(NORMAL * -1.0, vertex_direction_to_circle);

		displacement += (smoothstep(0.1, 0.0, vertex_distance_to_circle) * 0.025) * vertex_dot_to_circle;
	}

	VERTEX += NORMAL * (displacement + _shield_size);
}

That’s it, pretty simple, and I’m sure it could be trimmed down or optimized. But with this, I actually solved two issues at once.

  • Multiple circles can interact with the shield surface.
  • There is no fixed animation anymore.

Quick explanation

  • For each circle:
    • Get the distance from the current vertex to the circle.
    • Get the direction from the current vertex to the circle.
    • Get the dot product between the current vertex normal and the direction to the circle.
    • Then smoothstep between 0.1 and 0.0 based on the distance to the circle. The distance is clamped between 0 and 1 so the effect only happens if the vertex is close enough.
    • Then multiply this by a small value, 0.025, to tune down the displacement.
    • Multiply it by the dot product calculated above - this will cause the displacement to reverse if the circles are outside the shield, resulting in a nice wobbly effect.
  • Then just multiply the cumulative displacement with the vertex normal and add that to the vertex.

Here is the final product again:

That’s it!

If you have any questions, you can find me on the Godot Modding Discord and ping me in #dev or on Twitter @KANAjetzt 👍


Kai / KANA

I’m Kai, a software engineer based in Germany. Currently, my focus is on building games, plugins, and addons for and with Godot. Apart from that, I'm web deving since 2018.