diff --git a/cmd/krampus19/animations.go b/cmd/krampus19/animations.go index 59a1314..ab1c4f9 100644 --- a/cmd/krampus19/animations.go +++ b/cmd/krampus19/animations.go @@ -1,6 +1,8 @@ package main import ( + "math" + "math/rand" "time" "opslag.de/schobers/geom" @@ -50,3 +52,77 @@ func (a *sinkAnimation) Animate(start, now time.Duration) bool { a.e.scr.z = progress * 80 return true } + +type splashAnimation struct { + particles []splashParticle +} + +func newSplashAnimation(pos geom.Point) *splashAnimation { + pos32 := pos.ToF32() + a := &splashAnimation{} + n := rand.Intn(20) + 4 + for i := 0; i < n; i++ { + angle := rand.Float32() * math.Pi * 2 + a.particles = append(a.particles, splashParticle{ + angle: angle, + origin: pos32.Add2D(.45*geom.Cos32(angle), .45*geom.Sin32(angle)), // [-0.45 .. 0.45] + speed: .5*math.Pi + 2*math.Pi*rand.Float32(), // [0.5*Pi .. 2.5*Pi) + height: .2 + .8*rand.Float32(), // [0.2 .. 1.0) + dist: .5 + .5*rand.Float32(), // [0.5 .. 1.0) + }) + } + return a +} + +func (a *splashAnimation) Animate(start, now time.Duration) bool { + const duration = 1670 * time.Millisecond + + progress := float32((now-start)*1000/duration) * .001 + var opaq float32 = 1 + if progress > .6 { + if progress > 1 { + opaq = 0 + } else { + opaq = 2.5 - 2.5*progress + } + } + for i, p := range a.particles { + progress := geom.Min32(math.Pi, progress*p.speed) / math.Pi + a.particles[i].part = particle{ + opaq: opaq, + pos: p.origin.Add2D(progress*p.dist*geom.Cos32(p.angle), progress*p.dist*geom.Sin32(p.angle)), + z: 20 + p.height*-100*geom.Sin32(progress*math.Pi), + } + } + if progress > 1 { + return false + } + return true +} + +type particle struct { + pos geom.PointF32 + z float32 + opaq float32 +} + +func splitParticles(y float32, particles []particle) (behind []particle, front []particle) { + for _, p := range particles { + if p.pos.Y > y { + front = append(front, p) + } else { + behind = append(behind, p) + } + } + return +} + +type splashParticle struct { + origin geom.PointF32 + angle float32 + speed float32 + height float32 + dist float32 + + part particle +} diff --git a/cmd/krampus19/game.go b/cmd/krampus19/game.go index d734246..3b87eb6 100644 --- a/cmd/krampus19/game.go +++ b/cmd/krampus19/game.go @@ -156,7 +156,8 @@ func (g *game) loadAssets() error { "tile_lava_brick.png": "lava_brick", "tile_magma.png": "magma", - "ui.png": "ui", + "particles.png": "particles", + "ui.png": "ui", }) if err != nil { return err @@ -178,7 +179,7 @@ func (g *game) loadAssets() error { log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len()) log.Println("Loading sprites") - err = g.loadSprites("brick", "dragon", "lava_brick", "magma", "ui", "villain") + err = g.loadSprites("brick", "dragon", "lava_brick", "magma", "particles", "ui", "villain") if err != nil { return err } diff --git a/cmd/krampus19/playlevel.go b/cmd/krampus19/playlevel.go index 30854f4..c70113a 100644 --- a/cmd/krampus19/playlevel.go +++ b/cmd/krampus19/playlevel.go @@ -178,8 +178,21 @@ func (l *playLevel) drawSprite(name, partName string, pos entityLoc) { l.ctx.SpriteDrawer.Draw(name, partName, l.posToScreenF32(pos.pos, pos.z), DrawSpriteOptions{Scale: l.scale}) } +func (l *playLevel) drawSpriteAlpha(name, partName string, pos entityLoc, alpha float32) { + a := byte(alpha * 255) + c := allg5.NewColorAlpha(a, a, a, a) + l.ctx.SpriteDrawer.Draw(name, partName, l.posToScreenF32(pos.pos, pos.z), DrawSpriteOptions{Scale: l.scale, Tint: &c}) +} + func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { level := l.state.level + + drawParticles := func(particles []particle) { + for _, p := range particles { + l.drawSpriteAlpha("particles", "splash", entityLoc{p.pos, p.z}, p.opaq) + } + } + for i, t := range level.tiles { pos := geom.Pt(i%level.width, i/level.width) scr := entityLoc{pos.ToF32(), 0} @@ -194,8 +207,11 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { l.drawSprite("magma", "magma", scr) brick := l.state.FindSunkenBrick(pos) if brick != nil { + behind, front := splitParticles(scr.pos.Y, l.state.Particles(pos)) + drawParticles(behind) l.drawSprite("brick", "brick", brick.scr) l.drawSprite("magma", "sunken_overlay", scr) + drawParticles(front) } } } diff --git a/cmd/krampus19/playlevelstate.go b/cmd/krampus19/playlevelstate.go index 37f74c3..f1a5a4a 100644 --- a/cmd/krampus19/playlevelstate.go +++ b/cmd/krampus19/playlevelstate.go @@ -18,6 +18,7 @@ type playLevelState struct { villain *entity bricks entityList sunken entityList + splash map[geom.Point]*splashAnimation steps int complete bool @@ -33,6 +34,20 @@ func (s *playLevelState) Entities() entityList { return entities.Add(s.player).Add(s.villain).AddList(s.bricks) } +func (s *playLevelState) Particles(at geom.Point) []particle { + var particles []particle + for pos, ani := range s.splash { + if pos != at { + continue + } + log.Println("Found particles at", at) + for _, p := range ani.particles { + particles = append(particles, p.part) + } + } + return particles +} + func (s *playLevelState) FindSunkenBrick(pos geom.Point) *entity { return s.sunken.FindEntity(pos) } @@ -50,6 +65,7 @@ func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func( s.level = s.pack.levels[level] s.bricks = nil s.sunken = nil + s.splash = map[geom.Point]*splashAnimation{} for i, e := range s.level.entities { switch e { case entityTypeBrick: @@ -116,6 +132,12 @@ func (s *playLevelState) TryPlayerMove(dir geom.Point, key allg5.Key) { s.bricks = s.bricks.Remove(brickTo) s.sunken = s.sunken.Add(brick) s.ani.Start(s.ctx.Tick, newSinkAnimation(brick)) + + splash := newSplashAnimation(brickTo) + s.splash[brickTo] = splash + s.ani.StartFn(s.ctx.Tick, splash, func() { + delete(s.splash, brickTo) + }) } }) } diff --git a/cmd/krampus19/res/particles.png b/cmd/krampus19/res/particles.png new file mode 100644 index 0000000..61f30e8 Binary files /dev/null and b/cmd/krampus19/res/particles.png differ diff --git a/cmd/krampus19/res/sprites/particles.txt b/cmd/krampus19/res/sprites/particles.txt new file mode 100644 index 0000000..b53d306 --- /dev/null +++ b/cmd/krampus19/res/sprites/particles.txt @@ -0,0 +1,11 @@ +sprite: +texture: particles + +part: +name: splash +sub_texture: 0,0,96,96 +anchor: 48,48 +scale: 6 +:part + +:sprite \ No newline at end of file