Procedural Magic Circle

A fully procedural, animated magic circle for Godot 4. No textures needed, just put it on a ColorRect or a Sprite2D (square works best).

Everything is adjustable from the inspector:

  • rings: 1-5 concentric rings
  • star_points + star_step: 8/3 = octagram, 5/2 = pentagram, 7/3 = heptagram, 0 = no star
  • ticks: rune-like tick marks around the outer band
  • orbs: small orbs orbiting on the inner ring
  • spin_speed, pulse_speed, glow, line_width, color
  • appear (0 to 1): animate it to “draw” the circle in, nice for spell casting

Uses blend_add, so it looks best on dark backgrounds. For a 3D floor circle, render it in a SubViewport and use the texture on a plane (that is how the preview was made).

Tested in Godot 4.7 with the Compatibility renderer, so it also works in web exports.

Code written with AI assistance, tested by hand in Godot 4.7.
More Godot 4 packs: https://pixeldreamerstudio.itch.io/

Shader code
// Pixel Dreamer — Procedural Magic Circle (Godot 4 · canvas_item · works in Compatibility / web)
// MIT License · https://pixeldreamerstudio.itch.io/
// Put it on a ColorRect or Sprite2D (square). No textures needed.
// Change ring count, star points, tick marks, spin speed and glow from the inspector.
shader_type canvas_item;
render_mode blend_add;

uniform vec4 color : source_color = vec4(0.55, 0.45, 1.0, 1.0);
uniform float glow : hint_range(0.0, 4.0) = 1.0;
uniform int rings : hint_range(1, 5) = 3;
uniform int star_points : hint_range(0, 12) = 8;
uniform int star_step : hint_range(1, 5) = 3; // 8 + 3 = octagram · 5 + 2 = pentagram · 7 + 3 = heptagram
uniform int ticks : hint_range(0, 96) = 48;
uniform int orbs : hint_range(0, 12) = 6;
uniform float line_width : hint_range(0.002, 0.03) = 0.008;
uniform float spin_speed = 0.25;
uniform float pulse_speed = 2.0;
uniform float appear : hint_range(0.0, 1.0) = 1.0; // animate 0 -> 1 to draw the circle in

float ring(float r, float radius, float w) {
	return smoothstep(w, 0.0, abs(r - radius));
}
float seg(vec2 p, vec2 a, vec2 b, float w) {
	vec2 pa = p - a;
	vec2 ba = b - a;
	float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
	return smoothstep(w, 0.0, length(pa - ba * h));
}
vec2 rot(vec2 p, float a) {
	float c = cos(a);
	float s = sin(a);
	return vec2(c * p.x - s * p.y, s * p.x + c * p.y);
}

void fragment() {
	vec2 p = (UV - 0.5) * 2.0;
	float r = length(p);
	float w = line_width;
	float t = TIME;
	float m = 0.0;
	// outer + inner rings, alternating spin direction
	for (int i = 0; i < 5; i++) {
		if (i >= rings) break;
		float rad = 0.92 - float(i) * 0.14;
		m += ring(r, rad, w * (i == 0 ? 1.6 : 1.0));
	}
	// tick marks between ring 0 and ring 1
	if (ticks > 0) {
		vec2 q = rot(p, t * spin_speed);
		float a = atan(q.y, q.x) / TAU + 0.5;
		float k = abs(fract(a * float(ticks)) - 0.5);
		float band = step(0.8, r) * step(r, 0.9);
		m += smoothstep(0.08, 0.0, k) * band;
	}
	// star polygon
	if (star_points > 1) {
		vec2 q = rot(p, -t * spin_speed * 0.6);
		float R = 0.92 - float(rings - 1) * 0.14 - 0.02;
		R = max(R, 0.3);
		for (int i = 0; i < 12; i++) {
			if (i >= star_points) break;
			float a0 = TAU * float(i) / float(star_points) + TAU * 0.25;
			float a1 = TAU * float(i + star_step) / float(star_points) + TAU * 0.25;
			m += seg(q, vec2(cos(a0), sin(a0)) * R, vec2(cos(a1), sin(a1)) * R, w);
		}
	}
	// orbs on the second ring
	if (orbs > 0) {
		vec2 q = rot(p, t * spin_speed * 1.5);
		float a = atan(q.y, q.x);
		float sector = TAU / float(orbs);
		float ca = round(a / sector) * sector;
		vec2 c = vec2(cos(ca), sin(ca)) * 0.78;
		float d = length(q - c);
		m += ring(d, 0.035, w) + smoothstep(0.02, 0.0, d);
	}
	// draw-in mask (sweeps around)
	float ang = atan(p.y, p.x) / TAU + 0.5;
	m *= step(ang, appear * 1.001);
	float pulse = 0.85 + 0.15 * sin(t * pulse_speed);
	float halo = smoothstep(1.0, 0.2, r) * 0.08 * glow;
	vec3 col = color.rgb * (m * glow * pulse + halo);
	col += vec3(1.0) * pow(clamp(m, 0.0, 1.0), 3.0) * 0.12;
	COLOR = vec4(col, clamp(m + halo, 0.0, 1.0) * color.a);
}
Live Preview
Tags
2d, animated, circle, compatibility, Magic, Procedural, rpg, spell
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from pixeldreamerstudio

Related shaders

guest

0 Comments
Oldest
Newest Most Voted