Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Created July 22, 2017 11:08
Show Gist options
  • Save peterhellberg/b63c0afa93cfbcc02cdab96be87f1de0 to your computer and use it in GitHub Desktop.
Save peterhellberg/b63c0afa93cfbcc02cdab96be87f1de0 to your computer and use it in GitHub Desktop.
pixel-particles.go
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"math"
"math/rand"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
const (
w, h = 512, 512
fw, fh = float64(w), float64(h)
)
func flip() float64 {
if rand.Float64() > 0.5 {
return 1.0
}
return -1.0
}
func run() {
win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
Bounds: pixel.R(0, 0, fw, fh),
Undecorated: true,
})
if err != nil {
panic(err)
}
rand.Seed(time.Now().UnixNano())
canvas := pixelgl.NewCanvas(win.Bounds())
particles := []*particle{}
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
buffer := image.NewRGBA(image.Rect(0, 0, w, h))
win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
if win.JustPressed(pixelgl.KeyC) {
particles = nil
draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src)
}
fx, fy := win.MousePosition().XY()
x, y := int(fx), int(fy)
fmt.Println("particles:", len(particles))
if win.Pressed(pixelgl.MouseButtonLeft) {
c := color.RGBA{255, uint8(y % 255), uint8(x % 255), 255}
angle := 90.0 + rand.Float64()*80.0*flip()
speed := 30.0 + dt
life := 3.0
particles = append(particles,
newParticle(fx, fy, angle, speed, life, c),
)
}
for _, p := range particles {
p.update(dt)
x, y := p.XY()
buffer.Set(x, y, p.color)
buffer.Set(x-1, y, p.color)
buffer.Set(x+1, y, p.color)
buffer.Set(x, y-1, p.color)
buffer.Set(x, y+1, p.color)
}
aliveParticles := []*particle{}
for _, p := range particles {
if p.life > 0 {
aliveParticles = append(aliveParticles, p)
}
}
particles = aliveParticles
win.Clear(color.RGBA{0, 0, 0, 255})
canvas.SetPixels(buffer.Pix)
canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
win.Update()
}
}
func main() {
pixelgl.Run(run)
}
type particle struct {
position pixel.Vec
velocity pixel.Vec
color color.RGBA
life float64
}
func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle {
angleInRadians := angle * math.Pi / 180
return &particle{
position: pixel.Vec{x, y},
velocity: pixel.Vec{
X: speed * math.Cos(angleInRadians),
Y: -speed * math.Sin(angleInRadians),
},
life: life,
color: c,
}
}
func (p *particle) XY() (int, int) {
return int(p.position.X), int(p.position.Y)
}
func (p *particle) update(dt float64) {
p.life -= dt
if p.life > 0 {
p.position.X += p.velocity.X * dt
p.position.Y += p.velocity.Y * dt
if p.life > 1.5 {
if rand.Float64() < 0.4+dt {
p.color.R -= 1
p.color.G -= 1
p.color.B -= 1
}
} else {
p.color.R -= 1
p.color.G -= 1
p.color.B -= 1
}
}
}
@peterhellberg
Copy link
Author

pixel-particles

@peterhellberg
Copy link
Author

Not clearing the buffer between frames can produce some pretty interesting results.

@peterhellberg
Copy link
Author

A good article on the subject of particle systems: http://buildnewgames.com/particle-systems/

@peterhellberg
Copy link
Author

peterhellberg commented Jul 22, 2017

pixel-fireworks

package main

import (
	"image"
	"image/color"
	"image/draw"
	"math"
	"math/rand"
	"time"

	"github.com/faiface/pixel"
	"github.com/faiface/pixel/pixelgl"
)

const (
	w, h   = 512, 512
	fw, fh = float64(w), float64(h)
)

func flip() float64 {
	if rand.Float64() > 0.5 {
		return 1.0
	}

	return -1.0
}

func run() {
	win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
		Bounds:      pixel.R(0, 0, fw, fh),
		Undecorated: true,
	})
	if err != nil {
		panic(err)
	}

	rand.Seed(time.Now().UnixNano())

	canvas := pixelgl.NewCanvas(win.Bounds())

	particles := []*particle{}

	last := time.Now()

	var total float64

	explode := func(fx, fy float64) {
		if len(particles) > 500 {
			return
		}

		x := int(fx)
		y := int(fy)

		n := 30 + (6 * rand.Intn(10))

		c := color.RGBA{255, uint8(int(total) + y%255), uint8((int(total) ^ x) % 255), 0}

		for i := 0; i < n; i++ {
			angle := 90.0 + rand.Float64()*180.0*flip()
			speed := (2.0 * float64(n)) + (float64(i) * rand.Float64())
			life := 0.1 + (1.3 * rand.Float64())

			particles = append(particles,
				newParticle(fx, fy, angle, speed, life, c),
			)
		}
	}

	go func() {
		for {
			time.Sleep(2700 * time.Millisecond)
			explode(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2))
		}
	}()

	for !win.Closed() {
		dt := time.Since(last).Seconds()
		last = time.Now()

		total += dt

		buffer := image.NewRGBA(image.Rect(0, 0, w, h))

		win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))

		if win.JustPressed(pixelgl.KeyC) {
			particles = nil
			draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src)
		}

		if win.Pressed(pixelgl.MouseButtonLeft) {
			explode(win.MousePosition().XY())
		}

		if win.JustPressed(pixelgl.KeyS) {
			explode(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2))
		}

		for _, p := range particles {
			p.update(dt)

			x := int(p.position.X)
			y := int(p.position.Y)

			buffer.Set(x, y, p.color)
			buffer.Set(x-1, y, p.color)
			buffer.Set(x+1, y, p.color)
			buffer.Set(x, y-1, p.color)
			buffer.Set(x, y+1, p.color)
		}

		aliveParticles := []*particle{}

		for _, p := range particles {
			if p.life > 0 {
				aliveParticles = append(aliveParticles, p)
			}
		}

		particles = aliveParticles

		win.Clear(color.RGBA{0, 0, 0, 255})

		canvas.SetPixels(buffer.Pix)

		canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))

		win.Update()
	}
}

func main() {
	pixelgl.Run(run)
}

type particle struct {
	angle    float64
	speed    float64
	position pixel.Vec
	velocity pixel.Vec
	color    color.RGBA
	life     float64
}

func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle {
	angleInRadians := angle * math.Pi / 180

	return &particle{
		angle:    angle,
		speed:    speed,
		position: pixel.Vec{x, y},
		velocity: pixel.Vec{
			X: speed * math.Cos(angleInRadians),
			Y: -speed * math.Sin(angleInRadians),
		},
		life:  life,
		color: c,
	}
}

func (p *particle) update(dt float64) {
	p.life -= dt

	if p.life > 0 {
		p.position.X += p.velocity.X * dt
		p.position.Y += p.velocity.Y * dt

		if p.life > 1.0 {
			if rand.Float64() < 0.6 {
				p.color.G -= 1
				p.color.B -= 1
			}
		} else {
			p.color.R -= 2
			p.color.G -= 1
			p.color.B -= 1
		}
	}
}

@peterhellberg
Copy link
Author

pixel-snow

package main

import (
	"image"
	"image/color"
	"image/draw"
	"math"
	"math/rand"
	"time"

	"github.com/faiface/pixel"
	"github.com/faiface/pixel/pixelgl"
)

const (
	w, h   = 512, 256
	fw, fh = float64(w), float64(h)
)

func run() {
	win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
		Bounds:      pixel.R(0, 0, fw, fh),
		Undecorated: true,
	})
	if err != nil {
		panic(err)
	}

	rand.Seed(time.Now().UnixNano())

	canvas := pixelgl.NewCanvas(win.Bounds())

	particles := []*particle{}

	last := time.Now()

	var total float64

	angle := 90.0

	white := color.RGBA{255, 255, 255, 255}

	fall := func(fx, fy float64) {
		speed := 32.0 + (32.0 * rand.Float64())
		life := 16.0 + (1.6 * rand.Float64())

		particles = append(particles,
			newParticle(fx, fy, angle, speed, life, white),
		)
	}

	go func() {
		for {
			time.Sleep(40 * time.Millisecond)
			fall(rand.Float64()*fw, fh-(rand.Float64()*fh)/2+(fh/2))
		}
	}()

	for !win.Closed() {
		dt := time.Since(last).Seconds()
		last = time.Now()

		total += dt

		buffer := image.NewRGBA(image.Rect(0, 0, w, h))

		win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))

		if win.JustPressed(pixelgl.KeyC) {
			particles = nil
			draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src)
		}

		if win.Pressed(pixelgl.KeyS) {
			fall(rand.Float64()*fw, fh-(rand.Float64()*fh)/2+(fh/2))
		}

		if win.JustPressed(pixelgl.KeyLeft) {
			angle += 4
		}

		if win.JustPressed(pixelgl.KeyRight) {
			angle -= 4
		}

		for _, p := range particles {
			p.update(dt)

			x := int(p.position.X)
			y := int(p.position.Y)

			buffer.Set(x, y, p.color)
			buffer.Set(x-1, y, p.color)
			buffer.Set(x+1, y, p.color)
			buffer.Set(x, y-1, p.color)
			buffer.Set(x, y+1, p.color)
		}

		aliveParticles := []*particle{}

		for _, p := range particles {
			if p.life > 0 {
				aliveParticles = append(aliveParticles, p)
			}
		}

		particles = aliveParticles

		win.Clear(color.RGBA{0, 0, 0, 255})

		canvas.SetPixels(buffer.Pix)

		canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))

		win.Update()
	}
}

func main() {
	pixelgl.Run(run)
}

type particle struct {
	position pixel.Vec
	velocity pixel.Vec
	color    color.RGBA
	life     float64
}

func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle {
	angleInRadians := angle * math.Pi / 180

	return &particle{
		position: pixel.Vec{x, y},
		velocity: pixel.Vec{
			X: speed * math.Cos(angleInRadians),
			Y: -speed * math.Sin(angleInRadians),
		},
		life:  life,
		color: c,
	}
}

func (p *particle) update(dt float64) {
	p.life -= dt * 3

	if p.life > 0 {
		p.position.X += p.velocity.X * dt
		p.position.Y += p.velocity.Y * dt

		if p.life < 10 && p.color.R > 0 {
			if rand.Float64() < 0.3 {
				p.color.R -= 1
				p.color.G -= 1
				p.color.B -= 1
			}
		}
	}
}

@peterhellberg
Copy link
Author

falling-red

package main

import (
	"image"
	"image/color"
	"image/draw"
	"math"
	"math/rand"
	"time"

	"github.com/faiface/pixel"
	"github.com/faiface/pixel/pixelgl"
)

const (
	w, h   = 512, 512
	fw, fh = float64(w), float64(h)
)

func run() {
	win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
		Bounds:      pixel.R(0, 0, fw, fh),
		Undecorated: true,
	})
	if err != nil {
		panic(err)
	}

	rand.Seed(time.Now().UnixNano())

	canvas := pixelgl.NewCanvas(win.Bounds())

	particles := []*particle{}

	last := time.Now()

	var total float64

	angle := 90.0

	white := color.RGBA{255, 255, 255, 255}

	fall := func(fx, fy float64) {
		speed := 25.0 + (25.0 * rand.Float64())
		life := 10.1 + (1.3 * rand.Float64())

		particles = append(particles,
			newParticle(fx, fy, angle, speed, life, white),
		)
	}

	go func() {
		for {
			time.Sleep(100 * time.Millisecond)
			fall(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2))
		}
	}()

	for !win.Closed() {
		dt := time.Since(last).Seconds()
		last = time.Now()

		total += dt

		buffer := image.NewRGBA(image.Rect(0, 0, w, h))

		win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))

		if win.JustPressed(pixelgl.KeyC) {
			particles = nil
			draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src)
		}

		if win.Pressed(pixelgl.MouseButtonLeft) {
			fall(win.MousePosition().XY())
		}

		if win.JustPressed(pixelgl.KeyS) {
			fall(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2))
		}

		for _, p := range particles {
			p.update(dt)

			x := int(p.position.X)
			y := int(p.position.Y)

			buffer.Set(x, y, p.color)
			buffer.Set(x-1, y, p.color)
			buffer.Set(x+1, y, p.color)
			buffer.Set(x, y-1, p.color)
			buffer.Set(x, y+1, p.color)
		}

		aliveParticles := []*particle{}

		for _, p := range particles {
			if p.life > 0 {
				aliveParticles = append(aliveParticles, p)
			}
		}

		particles = aliveParticles

		win.Clear(color.RGBA{0, 0, 0, 255})

		canvas.SetPixels(buffer.Pix)

		canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))

		win.Update()
	}
}

func main() {
	pixelgl.Run(run)
}

type particle struct {
	angle    float64
	speed    float64
	position pixel.Vec
	velocity pixel.Vec
	color    color.RGBA
	life     float64
}

func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle {
	angleInRadians := angle * math.Pi / 180

	return &particle{
		angle:    angle,
		speed:    speed,
		position: pixel.Vec{x, y},
		velocity: pixel.Vec{
			X: speed * math.Cos(angleInRadians),
			Y: -speed * math.Sin(angleInRadians),
		},
		life:  life,
		color: c,
	}
}

func (p *particle) update(dt float64) {
	p.life -= dt

	if p.life > 0 {
		p.position.X += p.velocity.X * dt
		p.position.Y += p.velocity.Y * dt

		if p.life > 1.0 {
			if rand.Float64() < 0.6 {
				p.color.G -= 1
				p.color.B -= 1
			}
		} else {
			p.color.R -= 2
			p.color.G -= 1
			p.color.B -= 1
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment