add smoke example
This commit is contained in:
parent
2ef17e7a95
commit
ce083a9a9f
|
@ -0,0 +1,8 @@
|
||||||
|
# Smoke
|
||||||
|
|
||||||
|
This example implements a smoke particle effect using sprites. It uses a spritesheet with a CSV
|
||||||
|
description.
|
||||||
|
|
||||||
|
The art in the spritesheet comes from [Kenney](https://kenney.nl/).
|
||||||
|
|
||||||
|
![Screenshot](screenshot.png)
|
|
@ -0,0 +1,25 @@
|
||||||
|
1543,1146,362,336
|
||||||
|
396,0,398,364
|
||||||
|
761,1535,386,342
|
||||||
|
795,794,351,367
|
||||||
|
394,1163,386,364
|
||||||
|
1120,1163,377,348
|
||||||
|
795,0,368,407
|
||||||
|
0,0,395,397
|
||||||
|
1164,0,378,415
|
||||||
|
781,1163,338,360
|
||||||
|
1543,0,372,370
|
||||||
|
1148,1535,393,327
|
||||||
|
387,1535,373,364
|
||||||
|
396,365,371,388
|
||||||
|
0,758,378,404
|
||||||
|
379,758,378,371
|
||||||
|
1543,774,360,371
|
||||||
|
1543,1483,350,398
|
||||||
|
0,398,382,359
|
||||||
|
1164,416,356,382
|
||||||
|
1164,799,369,350
|
||||||
|
0,1535,386,394
|
||||||
|
795,408,366,385
|
||||||
|
1543,371,367,402
|
||||||
|
0,1163,393,371
|
|
Binary file not shown.
After Width: | Height: | Size: 3.0 MiB |
|
@ -0,0 +1,240 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "image/png"
|
||||||
|
|
||||||
|
"github.com/faiface/pixel"
|
||||||
|
"github.com/faiface/pixel/pixelgl"
|
||||||
|
"golang.org/x/image/colornames"
|
||||||
|
)
|
||||||
|
|
||||||
|
type particle struct {
|
||||||
|
Sprite *pixel.Sprite
|
||||||
|
Pos pixel.Vec
|
||||||
|
Rot, Scale float64
|
||||||
|
Mask pixel.RGBA
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type particles struct {
|
||||||
|
Generate func() *particle
|
||||||
|
Update func(dt float64, p *particle) bool
|
||||||
|
SpawnAvg, SpawnDist float64
|
||||||
|
|
||||||
|
parts list.List
|
||||||
|
spawnTime float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *particles) UpdateAll(dt float64) {
|
||||||
|
p.spawnTime -= dt
|
||||||
|
for p.spawnTime <= 0 {
|
||||||
|
p.parts.PushFront(p.Generate())
|
||||||
|
p.spawnTime += math.Max(0, p.SpawnAvg+rand.NormFloat64()*p.SpawnDist)
|
||||||
|
}
|
||||||
|
|
||||||
|
for e := p.parts.Front(); e != nil; e = e.Next() {
|
||||||
|
part := e.Value.(*particle)
|
||||||
|
if !p.Update(dt, part) {
|
||||||
|
defer p.parts.Remove(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *particles) DrawAll(t pixel.Target) {
|
||||||
|
for e := p.parts.Front(); e != nil; e = e.Next() {
|
||||||
|
part := e.Value.(*particle)
|
||||||
|
|
||||||
|
part.Sprite.SetMatrix(pixel.IM.
|
||||||
|
Scaled(0, part.Scale).
|
||||||
|
Rotated(0, part.Rot).
|
||||||
|
Moved(part.Pos),
|
||||||
|
)
|
||||||
|
part.Sprite.SetColorMask(part.Mask)
|
||||||
|
part.Sprite.Draw(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type smokeData struct {
|
||||||
|
Vel pixel.Vec
|
||||||
|
Time float64
|
||||||
|
Life float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type smokeSystem struct {
|
||||||
|
Sheet pixel.Picture
|
||||||
|
Rects []pixel.Rect
|
||||||
|
Orig pixel.Vec
|
||||||
|
|
||||||
|
VelBasis []pixel.Vec
|
||||||
|
VelDist float64
|
||||||
|
|
||||||
|
LifeAvg, LifeDist float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *smokeSystem) Generate() *particle {
|
||||||
|
sd := new(smokeData)
|
||||||
|
for _, base := range ss.VelBasis {
|
||||||
|
c := math.Max(0, 1+rand.NormFloat64()*ss.VelDist)
|
||||||
|
sd.Vel += base.Scaled(c)
|
||||||
|
}
|
||||||
|
sd.Vel = sd.Vel.Scaled(1 / float64(len(ss.VelBasis)))
|
||||||
|
sd.Life = math.Max(0, ss.LifeAvg+rand.NormFloat64()*ss.LifeDist)
|
||||||
|
|
||||||
|
p := new(particle)
|
||||||
|
p.Data = sd
|
||||||
|
|
||||||
|
p.Pos = ss.Orig
|
||||||
|
p.Scale = 1
|
||||||
|
p.Mask = pixel.Alpha(1)
|
||||||
|
p.Sprite = pixel.NewSprite(ss.Sheet, ss.Rects[rand.Intn(len(ss.Rects))])
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *smokeSystem) Update(dt float64, p *particle) bool {
|
||||||
|
sd := p.Data.(*smokeData)
|
||||||
|
sd.Time += dt
|
||||||
|
|
||||||
|
frac := sd.Time / sd.Life
|
||||||
|
|
||||||
|
p.Pos += sd.Vel.Scaled(dt)
|
||||||
|
p.Scale = 0.5 + frac*1.5
|
||||||
|
|
||||||
|
const (
|
||||||
|
fadeIn = 0.2
|
||||||
|
fadeOut = 0.4
|
||||||
|
)
|
||||||
|
if frac < fadeIn {
|
||||||
|
p.Mask = pixel.Alpha(math.Pow(frac/fadeIn, 0.75))
|
||||||
|
} else if frac >= fadeOut {
|
||||||
|
p.Mask = pixel.Alpha(math.Pow(1-(frac-fadeOut)/(1-fadeOut), 1.5))
|
||||||
|
} else {
|
||||||
|
p.Mask = pixel.Alpha(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sd.Time < sd.Life
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSpriteSheet(sheetPath, descriptionPath string) (sheet pixel.Picture, rects []pixel.Rect, err error) {
|
||||||
|
sheetFile, err := os.Open(sheetPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer sheetFile.Close()
|
||||||
|
|
||||||
|
sheetImg, _, err := image.Decode(sheetFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet = pixel.PictureDataFromImage(sheetImg)
|
||||||
|
|
||||||
|
descriptionFile, err := os.Open(descriptionPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer descriptionFile.Close()
|
||||||
|
|
||||||
|
description := csv.NewReader(descriptionFile)
|
||||||
|
for {
|
||||||
|
record, err := description.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x, _ := strconv.ParseFloat(record[0], 64)
|
||||||
|
y, _ := strconv.ParseFloat(record[1], 64)
|
||||||
|
w, _ := strconv.ParseFloat(record[2], 64)
|
||||||
|
h, _ := strconv.ParseFloat(record[3], 64)
|
||||||
|
|
||||||
|
y = sheet.Bounds().H() - y - h
|
||||||
|
|
||||||
|
rects = append(rects, pixel.R(x, y, x+w, y+h))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheet, rects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
|
sheet, rects, err := loadSpriteSheet("blackSmoke.png", "blackSmoke.csv")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := pixelgl.WindowConfig{
|
||||||
|
Title: "Smoke",
|
||||||
|
Bounds: pixel.R(0, 0, 1024, 768),
|
||||||
|
Resizable: true,
|
||||||
|
VSync: true,
|
||||||
|
}
|
||||||
|
win, err := pixelgl.NewWindow(cfg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := &smokeSystem{
|
||||||
|
Rects: rects,
|
||||||
|
Orig: 0,
|
||||||
|
VelBasis: []pixel.Vec{pixel.V(-100, 100), pixel.V(100, 100), pixel.V(0, 100)},
|
||||||
|
VelDist: 0.1,
|
||||||
|
LifeAvg: 7,
|
||||||
|
LifeDist: 0.5,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &particles{
|
||||||
|
Generate: ss.Generate,
|
||||||
|
Update: ss.Update,
|
||||||
|
SpawnAvg: 0.3,
|
||||||
|
SpawnDist: 0.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := pixel.NewBatch(&pixel.TrianglesData{}, sheet)
|
||||||
|
|
||||||
|
var (
|
||||||
|
second = time.Tick(time.Second)
|
||||||
|
frames = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
last := time.Now()
|
||||||
|
for !win.Closed() {
|
||||||
|
dt := time.Since(last).Seconds()
|
||||||
|
last = time.Now()
|
||||||
|
|
||||||
|
p.UpdateAll(dt)
|
||||||
|
|
||||||
|
win.Clear(colornames.Aliceblue)
|
||||||
|
win.SetMatrix(pixel.IM.Moved(win.Bounds().Center() - pixel.Y(win.Bounds().H()/2)))
|
||||||
|
|
||||||
|
batch.Clear()
|
||||||
|
p.DrawAll(batch)
|
||||||
|
batch.Draw(win)
|
||||||
|
|
||||||
|
win.Update()
|
||||||
|
|
||||||
|
frames++
|
||||||
|
select {
|
||||||
|
case <-second:
|
||||||
|
win.SetTitle(fmt.Sprintf("%s | FPS: %d", cfg.Title, frames))
|
||||||
|
frames = 0
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pixelgl.Run(run)
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 386 KiB |
Loading…
Reference in New Issue