↓ Ir para o conteúdo principal
0

Procedural Art: Pure-Code 2D Sprites, SDF Primitives, Material Ramps & VFX Guide #

This skill provides mathematical, graphical, and software architecture patterns for generating high-quality 2D sprites, tilesets, Signed Distance Field (SDF) shapes, particle systems, and visual effects (VFX) purely in code—without relying on external .png, .jpg, or .svg asset files.

💡 TIP

Reference Implementation:


1. Core Architectural Principles & Zero-Asset Strategy #

Procedural art generates graphical textures in memory at startup (Boot state) or renders vector/particle effects dynamically during game frames:

  • Zero Disk I/O: Eliminates asset loading delays, missing file errors, and external bundle dependencies.
  • Signed Distance Field (SDF) Rasterization: Composes organic characters, weapons, and tiles mathematically using SDFCircle, SDFBox, SDFCapsule, and SDFSmoothMin.
  • Strict Premultiplied Alpha Invariant: Enforces $R, G, B \le A$ on every color.RGBA pixel via PremultiplyRGBA(r, g, b, a).

2. “Retro-HD” Visual Style, Premultiplied Alpha & 4-Step Material Ramps #

Target the aesthetic richness of classic 16-bit/32-bit console art (SNES, Sega Saturn, Neo Geo) combined with modern 32-bit truecolor shading.

2.1 Premultiplied Alpha Invariant (PremultiplyRGBA) #

In Go’s image/color and Ebitengine (image.NewRGBA / ebiten.NewImageFromImage), color.RGBA values are alpha-premultiplied ($R \le A, G \le A, B \le A$). Writing non-premultiplied values like color.RGBA{255, 255, 255, alpha} or color.RGBA{200, 30, 10, 0} causes glowing halos and blending corruption. Always construct translucent colors via PremultiplyRGBA:

go
func PremultiplyRGBA(r, g, b, a uint8) color.RGBA {
	alpha := uint16(a)
	return color.RGBA{
		R: uint8((uint16(r)*alpha + 127) / 255),
		G: uint8((uint16(g)*alpha + 127) / 255),
		B: uint8((uint16(b)*alpha + 127) / 255),
		A: a,
	}
}

2.2 4-Step Material Ramp Structure (MaterialRamp) #

Every material in a procedural sprite (steel, crimson, flesh, gold, foliage) uses a structured 4-step shading ramp ([4]color.RGBA) paired with a dark 1-pixel silhouette outline (#0F0C1C):

  1. Step 0 — Deep Shadow ([0]): Primary hue shifted toward cool blue/violet (illumination < 0.25).
  2. Step 1 — Base Tone ([1]): Core saturated material color (0.25 <= illumination < 0.65).
  3. Step 2 — Light Highlight ([2]): Primary hue shifted toward warm yellow/orange (0.65 <= illumination < 0.90).
  4. Step 3 — High Specular ([3]): Crisp specular edge or rim highlight (illumination >= 0.90).

3. 2D Signed Distance Field (SDF) Primitives & Smooth Blending #

Instead of drawing blocky raw rectangles, sculpt multi-part sprites at startup by evaluating 2D Signed Distance Fields per pixel $(p_x, p_y)$ and blending limbs/armor plates with polynomial smooth minimum (SDFSmoothMin):

  • Circle (SDFCircle): $d = |(p_x - c_x, p_y - c_y)|_2 - r$
  • Rounded Box (SDFBox): Box centered at $(c_x, c_y)$ with half-extents $(h_w, h_h)$ and corner radius $r$.
  • Line Capsule (SDFCapsule): Swept sphere of radius $r$ along segment $\overline{AB}$ (ideal for limbs, blades, beams, and pauldrons).
  • Smooth Union (SDFSmoothMin): Blends two distance fields $d_1, d_2$ with smoothing factor $k > 0$: $$h = \frac{\max(k - |d_1 - d_2|, 0)}{k}, \quad \text{smin}(d_1, d_2, k) = \min(d_1, d_2) - \frac{1}{4} h^2 k$$

4. Transformation Mathematics & Order of Operations (CRITICAL) #

In 2D graphics programming (ebiten.GeoM), matrix multiplication is non-commutative. Applying operations in the wrong order causes sprites to orbit wildly or scale off-screen.

4.1 Exact Sequence: Pivot -> Scale -> Rotate -> World Translation #

$$M_{\text{correct}} = T(X + O_x, Y + O_y) \cdot R(\theta) \cdot S(S_x, S_y) \cdot T(-O_x, -O_y)$$
  1. m.Translate(-OriginX, -OriginY) — Move pivot to $(0, 0)$.
  2. m.Scale(ScaleX, ScaleY) — Scale relative to pivot.
  3. m.Rotate(Rotation) — Rotate around pivot.
  4. m.Translate(OriginX+X, OriginY+Y) — Translate to world position.

5. Sub-Frame Interpolation & Non-Linear Easing #

Use delta time ($dt$) and non-linear easing functions to model physical weight, momentum, and elasticity:

  • Linear (EaseLinear): $f(t) = t$
  • Ease-In Quadratic (EaseInQuad): $f(t) = t^2$ (accelerating under gravity)
  • Ease-Out Quadratic (EaseOutQuad): $f(t) = t(2 - t)$ (decelerating friction slide)
  • Ease-InOut Cubic (EaseInOutCubic): Smooth S-curve for breathing idle and hover loops
  • Elastic Overshoot (EaseElasticOut): $f(t) = 2^{-10t} \sin\left(\frac{(t - 0.075) \cdot 2\pi}{0.3}\right) + 1$ (springy attacks and UI pop-ups)

6. Retro-HD Sprite Sheet Architecture & Frame Requirements #

Animation StateRequired Frames / DirKeyframes & Pose ProgressionEasing / Timing Guidelines
Idle / Breathing4–8 framesSubtle chest rise, shoulder dip, weapon idle shimmer.Smooth EaseInOutCubic (1.2s–1.8s cycle).
Walk / Run Cycle8–12 framesContact $\rightarrow$ Recoil $\rightarrow$ Passing $\rightarrow$ High Point.Continuous loop (0.6s–0.9s cycle).
Attack / Strike6–10 framesWind-up $\rightarrow$ Fast Strike/Impact $\rightarrow$ Follow-through $\rightarrow$ Recovery.Fast EaseInQuad to impact, EaseOutQuad recovery.
Hurt / Hit Recoil3–5 framesSharp backward tilt, hit-flash, recovery.High speed (0.15s–0.25s total).
Death / Collapse6–10 framesStagger $\rightarrow$ Knees buckle $\rightarrow$ Ground impact $\rightarrow$ Settle.Heavy EaseInQuad drop, non-looping final frame.

7. Zero-Allocation Particle Systems & VFX #

High-performance visual effects (explosions, thrusters, magic trails, sparks) require both a pre-allocated []Particle pool and a reusable ebiten.DrawImageOptions struct field (ps.drawOp) so Draw() never allocates on the heap:

go
func (ps *ParticleSystem) Draw(screen *ebiten.Image) {
	for i := range ps.pool {
		p := &ps.pool[i]
		if !p.Active {
			continue
		}
		progress := p.Age / p.Life
		size := p.StartSize + progress*(p.EndSize-p.StartSize)
		curColor := LerpColor(p.StartColor, p.EndColor, progress)

		ps.drawOp.GeoM.Reset()
		ps.drawOp.GeoM.Translate(-8, -8)
		ps.drawOp.GeoM.Scale(size, size)
		ps.drawOp.GeoM.Translate(p.X, p.Y)

		ps.drawOp.ColorScale.Reset()
		ps.drawOp.ColorScale.ScaleWithColor(curColor)

		if p.Additive {
			ps.drawOp.Blend = ebiten.BlendLighter
		} else {
			ps.drawOp.Blend = ebiten.BlendSourceOver
		}

		screen.DrawImage(ps.texture, &ps.drawOp)
	}
}

8. Mandatory Quality Directives for AI Art Generation #

  1. Premultiplied Alpha Enforcement: Every translucent color.RGBA pixel written to image.RGBA or interpolated in particle systems MUST use PremultiplyRGBA ($R, G, B \le A$).
  2. Zero Allocations in Draw(): Pre-render all procedural *ebiten.Image textures at boot and reuse ebiten.DrawImageOptions (Reset()) inside Draw().
  3. SDF + 4-Step Material Ramp Composition: Combine SDFCircle, SDFBox, SDFCapsule, and SDFSmoothMin with MaterialRamp ([4]color.RGBA) surface normal illumination and 1px dark outlines (ApplyPixelOutline).
  4. Correct Matrix Transformation Order: Always execute Translate(-pivot) -> Scale -> Rotate -> Translate(+pivot + pos).

📚 Progressive Disclosure & References #

  • Procedural Art Driver: references/art.go — Premultiplied RGBA helpers, 4-step MaterialRamp shading, SDFCircle/SDFBox/SDFCapsule/SDFSmoothMin primitives, zero-allocation ParticleSystem, and character sprite rasterization.
  • Verification Test Suite: references/art_test.go — Unit tests verifying PremultiplyRGBA, SDF geometry, 4-step material ramps, matrix transformation order, and particle pool lifecycle.