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] }