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

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.
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
)
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),
}
}
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
}
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}
}
This runs every frame to advance the animation:
func (g *Game) Update() error {
g.time += 0.05 // Increment time for animation
return nil
}
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)
}
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)
}
}
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)
In the plasma() function, try:
In Update(), adjust the time increment:
g.time += 0.1 // Faster animation
g.time += 0.02 // Slower animation
Save your code as plasma.go and run:
go run plasma.go
The plasma effect works by:
Congratulations! You now know how to: