pixel-examples/community/amidakuji/glossary/explosive.go

199 lines
5.8 KiB
Go

package glossary
import (
"image/color"
"math/rand"
"sync"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
)
// -------------------------------------------------------------------------
// explosive.go
// - Original idea: "github.com/faiface/pixel-examples/community/bouncing"
// --------------------------------------------------------------------
// Explosions is an imdraw and a manager of all particles.
type Explosions struct {
imd *imdraw.IMDraw
mutex sync.Mutex // It is unsafe to access any refd; ptrd object without a critical section.
//
*colorPicker
width float64
height float64
particles []*particle
precision int
}
// NewExplosions is a constructor.
// The 3rd argument colors can be nil. Then it will use its default value of a color set.
func NewExplosions(width, height float64, colors []color.Color, precision int) *Explosions {
return &Explosions{
nil, sync.Mutex{},
newColorPicker(colors),
width, height, nil,
precision,
}
}
// SetBound of particles. All particles bounce when they meet this bound.
func (e *Explosions) SetBound(width, height float64) {
e.width = width
e.height = height
}
// IsExploding determines whether this Explosions is about to be updated or not.
// Pass lock by value warning from (e Explosions) should be ignored,
// because an Explosions here is just passed as a read only argument.
func (e Explosions) IsExploding() bool {
e.mutex.Lock()
defer e.mutex.Unlock()
return e.particles != nil
}
// Draw guarantees the thread safety, though it's not a necessary condition.
// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
func (e *Explosions) Draw(t pixel.Target) {
e.mutex.Lock()
defer e.mutex.Unlock()
if e.imd == nil || len(e.particles) <= 0 { // isInvisible set to true.
return // An empty image is drawn.
}
e.imd.Draw(t)
}
// Update animates an Explosions. An Explosions is drawn on an imdraw.
func (e *Explosions) Update(dt float64) {
e.mutex.Lock()
defer e.mutex.Unlock()
// physics
aliveParticles := []*particle{}
for _, particle := range e.particles {
particle.update(dt, e.width, e.height)
if particle.life > 0 {
aliveParticles = append(aliveParticles, particle)
}
}
e.particles = aliveParticles
// imdraw (a state machine)
if e.imd == nil { // lazy creation
e.imd = imdraw.New(nil)
e.imd.EndShape = imdraw.RoundEndShape
e.imd.Precision = e.precision
}
imd := e.imd
imd.Clear()
// draw
for _, particle := range e.particles {
imd.Color = particle.color
imd.Push(particle.pos)
imd.Circle(16*particle.life, 0)
}
}
// ExplodeAt generates an explosion at given point.
func (e *Explosions) ExplodeAt(pos, vel pixel.Vec) {
e.mutex.Lock()
defer e.mutex.Unlock()
e.next()
e.particles = append(e.particles,
newParticleAt(pos, vel.Rotated(1).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(2).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(3).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(4).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(5).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(6).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(7).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(8).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(9).Scaled(rand.Float64()), e.here()),
newParticleAt(pos, vel.Rotated(10).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(20).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(30).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(40).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(50).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(60).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(70).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(80).Scaled(rand.Float64()+1), e.here()),
newParticleAt(pos, vel.Rotated(90).Scaled(rand.Float64()+1), e.here()),
)
}
// --------------------------------------------------------------------
type particle struct {
pos pixel.Vec
vel pixel.Vec
color color.RGBA
life float64
}
func newParticleAt(pos, vel pixel.Vec, color color.RGBA) *particle {
color.A = 5
return &particle{pos, vel, color, rand.Float64() * 1.5}
}
func (p *particle) update(dt, width, height float64) {
p.pos = p.pos.Add(p.vel)
p.life -= 3 * dt
switch {
case p.pos.Y < 0 || p.pos.Y >= height:
p.vel.Y *= (-10 * dt)
case p.pos.X < 0 || p.pos.X >= width:
p.vel.X *= (-10 * dt)
}
}
// --------------------------------------------------------------------
type colorPicker struct {
colors []color.RGBA
index int
}
func newColorPicker(_colors []color.Color) *colorPicker {
if _colors == nil {
_colors = []color.Color{
color.RGBA{190, 38, 51, 255},
color.RGBA{224, 111, 139, 255},
color.RGBA{73, 60, 43, 255},
color.RGBA{164, 100, 34, 255},
color.RGBA{235, 137, 49, 255},
color.RGBA{247, 226, 107, 255},
color.RGBA{47, 72, 78, 255},
color.RGBA{68, 137, 26, 255},
color.RGBA{163, 206, 39, 255},
color.RGBA{0, 87, 132, 255},
color.RGBA{49, 162, 242, 255},
color.RGBA{178, 220, 239, 255},
}
}
colors := []color.RGBA{}
for _, v := range _colors {
if c, ok := v.(color.RGBA); ok {
colors = append(colors, c)
}
}
return &colorPicker{colors, 0}
}
func (colorPicker *colorPicker) next() color.RGBA {
if colorPicker.index++; colorPicker.index >= len(colorPicker.colors) {
colorPicker.index = 0
}
return colorPicker.colors[colorPicker.index]
}
func (colorPicker *colorPicker) here() color.RGBA {
return colorPicker.colors[colorPicker.index]
}