🌊 Plasma Effect

Learn how to create mesmerizing animated plasma effects in Go with Ebiten

What is a Plasma Effect?

A plasma effect is a classic demoscene visual effect that creates flowing, organic patterns using mathematical functions. It combines multiple sine waves to generate smooth, colorful animations that look like liquid plasma flowing across the screen.

📚 What you'll learn:

Step-by-Step Implementation

1Set Up the Basic Structure

Here is the full code if you want to skip the steps below: plasma.go

First, create the main package and import the necessary libraries:

package main

import (
    "image/color"
    "math"
    "github.com/hajimehoshi/ebiten/v2"
)

const (
    screenWidth  = 640
    screenHeight = 480
)
💡 Tip: Start with a reasonable screen size like 640x480. You can always change it later!

2Create the Game Structure

Ebiten requires a game struct that implements the Game interface:

type Game struct {
    time   float64        // Animation time counter
    pixels []byte         // Pixel buffer for the screen
}

func NewGame() *Game {
    return &Game{
        pixels: make([]byte, screenWidth*screenHeight*4),
    }
}
🔍 Why multiply by 4? Each pixel needs 4 bytes: Red, Green, Blue, and Alpha (RGBA format).

3The Plasma Function - The Heart of the Effect

This is where the magic happens! The plasma function combines multiple sine waves to create organic patterns:

func plasma(x, y, t float64) float64 {
    // Wave 1: Horizontal wave
    v := math.Sin(x*0.04 + t)
    
    // Wave 2: Vertical wave (different speed)
    v += math.Sin(y*0.03 + t*1.3)
    
    // Wave 3: Diagonal wave
    v += math.Sin((x+y)*0.03 + t*0.7)
    
    // Wave 4: Circular wave from center
    v += math.Sin(math.Sqrt(x*x+y*y)*0.02 + t*1.5)
    
    // Average the waves (divide by number of waves)
    return v / 4.0
}
📊 Understanding the parameters:

4Converting Values to Colors

Transform the plasma values (-1 to 1) into beautiful RGB colors:

func getColor(value float64) color.RGBA {
    // Normalize from [-1, 1] to [0, 1]
    normalized := (value + 1) / 2
    
    // Use sine waves with phase shifts for RGB
    r := uint8(math.Sin(normalized*math.Pi*2) * 127 + 128)
    g := uint8(math.Sin(normalized*math.Pi*2+2*math.Pi/3) * 127 + 128)
    b := uint8(math.Sin(normalized*math.Pi*2+4*math.Pi/3) * 127 + 128)
    
    return color.RGBA{r, g, b, 255}
}
🎨 Color Theory: The phase shifts (2π/3 and 4π/3) create complementary colors. Try different values to get different color schemes!

5The Update Function

This runs every frame to advance the animation:

func (g *Game) Update() error {
    g.time += 0.05  // Increment time for animation
    return nil
}
⚠️ Performance Note: Smaller time increments = slower animation. Adjust this value to control speed!

6The Draw Function - Rendering the Magic

This is where we generate and display the plasma effect:

func (g *Game) Draw(screen *ebiten.Image) {
    // Loop through every pixel on the screen
    for y := 0; y < screenHeight; y++ {
        for x := 0; x < screenWidth; x++ {
            // Center the coordinates (0,0 at center of screen)
            cx := float64(x - screenWidth/2)
            cy := float64(y - screenHeight/2)
            
            // Calculate plasma value for this pixel
            value := plasma(cx, cy, g.time)
            
            // Convert to color
            c := getColor(value)
            
            // Write to pixel buffer (RGBA format)
            idx := (y*screenWidth + x) * 4
            g.pixels[idx] = c.R
            g.pixels[idx+1] = c.G
            g.pixels[idx+2] = c.B
            g.pixels[idx+3] = c.A
        }
    }
    
    // Push all pixels to screen at once
    screen.WritePixels(g.pixels)
}
🎯 Why center coordinates? Centering makes the circular waves symmetric and creates more pleasing patterns.

7Layout and Main Functions

Complete the implementation with these required functions:

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    return screenWidth, screenHeight
}

func main() {
    ebiten.SetWindowSize(screenWidth, screenHeight)
    ebiten.SetWindowTitle("Plasma Effect")
    ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
    
    game := NewGame()
    
    if err := ebiten.RunGame(game); err != nil {
        panic(err)
    }
}

🎨 Customization Ideas

Change Colors

Modify the phase shifts in getColor():

// For warmer colors
g := uint8(math.Sin(normalized*math.Pi*2+math.Pi/2) * 127 + 128)

// For cooler colors  
b := uint8(math.Sin(normalized*math.Pi*2+math.Pi) * 127 + 128)

Adjust Wave Patterns

In the plasma() function, try:

Speed Control

In Update(), adjust the time increment:

g.time += 0.1  // Faster animation
g.time += 0.02 // Slower animation

🚀 Running Your Plasma Effect

Save your code as plasma.go and run:

go run plasma.go
🎉 Success! You should see a window with flowing, colorful plasma patterns. Press the window close button to exit.

📚 How It Works - The Math Behind It

The plasma effect works by:

  1. Layering sine waves - Each wave contributes to the final pattern
  2. Using coordinates as input - Position determines the wave phase
  3. Adding time - Makes the waves move and flow
  4. Averaging results - Keeps values in a predictable range
  5. Mapping to colors - Converts numbers to visual output

💪 Challenges to Try

🎓 What You Learned

Congratulations! You now know how to: