change how anim works

This commit is contained in:
Josh Deprez 2021-08-23 20:28:49 +10:00
parent 4eaf48f380
commit e2e937f409
9 changed files with 165 additions and 156 deletions

View file

@ -1,42 +1,47 @@
package engine package engine
// Anim is an "instance" of an AnimDef: an animation being displayed, // Ensure Anim satisfies Animer.
// together with the current state. var _ Animer = &Anim{}
type Anim struct {
Def *AnimDef
Index int
Ticks int
}
func (a *Anim) CurrentFrame() int { return a.Def.Frames[a.Index].Frame }
func (a *Anim) Reset() { a.Index, a.Ticks = 0, 0 }
// Update increments the tick count and advances the frame if necessary.
func (a *Anim) Update() error {
a.Ticks++
if a.Def.OneShot && a.Index == len(a.Def.Frames)-1 {
// on the last frame of a one shot so remain on final frame
return nil
}
if a.Ticks >= a.Def.Frames[a.Index].Duration {
a.Ticks = 0
a.Index++
}
if !a.Def.OneShot && a.Index >= len(a.Def.Frames) {
a.Index = 0
}
return nil
}
// AnimDef describes an animation (sequence of frames and timings).
type AnimDef struct {
Frames []AnimFrame
OneShot bool
}
// AnimFrame describes a frame in an animation. // AnimFrame describes a frame in an animation.
type AnimFrame struct { type AnimFrame struct {
Frame int // show this frame Frame int // show this frame
Duration int // for this long, in ticks Duration int // for this long, in ticks
} }
// Anim is n animation being displayed, together with the current state.
type Anim struct {
Frames []AnimFrame
OneShot bool
Index int
Ticks int
}
// Copy makes a shallow copy of the anim.
func (a *Anim) Copy() *Anim {
a2 := *a
return &a2
}
// CurrentFrame returns the frame number for the current index.
func (a *Anim) CurrentFrame() int { return a.Frames[a.Index].Frame }
// Reset resets both Index and Ticks to 0.
func (a *Anim) Reset() { a.Index, a.Ticks = 0, 0 }
// Update increments the tick count and advances the frame if necessary.
func (a *Anim) Update() error {
a.Ticks++
if a.OneShot && a.Index == len(a.Frames)-1 {
// on the last frame of a one shot so remain on final frame
return nil
}
if a.Ticks >= a.Frames[a.Index].Duration {
a.Ticks = 0
a.Index++
}
if !a.OneShot && a.Index >= len(a.Frames) {
a.Index = 0
}
return nil
}

48
engine/animref.go Normal file
View file

@ -0,0 +1,48 @@
package engine
import (
"encoding/gob"
"io/fs"
)
var (
animCache = make(map[assetKey]Anim)
_ Animer = &AnimRef{}
_ Loader = &AnimRef{}
)
func init() {
gob.Register(AnimRef{})
}
// AnimRef manages an Anim using a premade AnimDef from the cache.
type AnimRef struct {
Path string
anim Anim
}
func (r *AnimRef) Load(assets fs.FS) error {
// Fast path: set r.anim to a copy
anim, found := animCache[assetKey{assets, r.Path}]
if found {
r.anim = anim
return nil
}
// Slow path: load from gobz file
if err := loadGobz(&r.anim, assets, r.Path); err != nil {
return err
}
animCache[assetKey{assets, r.Path}] = r.anim
return nil
}
// CurrentFrame returns the value of CurrentFrame from r.anim.
func (r *AnimRef) CurrentFrame() int { return r.anim.CurrentFrame() }
// Reset calls Reset on r.anim.
func (r *AnimRef) Reset() { r.anim.Reset() }
// Update calls Update on r.anim.
func (r *AnimRef) Update() error { return r.anim.Update() }

View file

@ -1,28 +1,22 @@
package engine package engine
import ( import (
"compress/gzip"
"encoding/gob" "encoding/gob"
"image" "image"
"io/fs" "io/fs"
"log"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
var ( var (
// AnimDefs are easier to write as Go expressions -
// so just set this.
// TODO: put in Game
AnimDefs map[string]*AnimDef
imageCache = make(map[assetKey]*ebiten.Image) imageCache = make(map[assetKey]*ebiten.Image)
// Ensure ImageRef satisfies interfaces. // Ensure types satisfy interfaces.
_ Loader = &ImageRef{} _ Loader = &ImageRef{}
) )
func init() { func init() {
gob.Register(AnimRef{})
gob.Register(ImageRef{}) gob.Register(ImageRef{})
} }
@ -31,24 +25,17 @@ type assetKey struct {
path string path string
} }
// AnimRef manages an Anim using a premade AnimDef from the cache. func loadGobz(dst interface{}, assets fs.FS, path string) error {
type AnimRef struct { f, err := assets.Open(path)
Key string if err != nil {
return err
anim *Anim
}
func (r *AnimRef) Anim() *Anim {
if r.anim != nil {
return r.anim
} }
ad := AnimDefs[r.Key] defer f.Close()
if ad == nil { gz, err := gzip.NewReader(f)
log.Fatalf("Unknown AnimDef %q", r.Key) if err != nil {
return nil return err
} }
r.anim = &Anim{Def: ad} return gob.NewDecoder(gz).Decode(dst)
return r.anim
} }
// ImageRef loads images from the AssetFS into *ebiten.Image form. // ImageRef loads images from the AssetFS into *ebiten.Image form.

View file

@ -7,6 +7,14 @@ import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
// Animer components have a current frame index.
type Animer interface {
Updater
CurrentFrame() int
Reset()
}
// Bounder components have a bounding rectangle. // Bounder components have a bounding rectangle.
type Bounder interface { type Bounder interface {
BoundingRect() image.Rectangle BoundingRect() image.Rectangle

View file

@ -1,7 +1,6 @@
package engine package engine
import ( import (
"compress/gzip"
"encoding/gob" "encoding/gob"
"image" "image"
"io/fs" "io/fs"
@ -36,18 +35,8 @@ type SceneRef struct {
// Load loads the scene from the file. // Load loads the scene from the file.
func (r *SceneRef) Load(assets fs.FS) error { func (r *SceneRef) Load(assets fs.FS) error {
f, err := assets.Open(r.Path)
if err != nil {
return err
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
return err
}
sc := new(Scene) sc := new(Scene)
if err := gob.NewDecoder(gz).Decode(sc); err != nil { if err := loadGobz(sc, assets, r.Path); err != nil {
return err return err
} }
r.scene = sc r.scene = sc

View file

@ -141,9 +141,7 @@ func (s StaticTile) TileIndex() int { return int(s) }
// AnimatedTile uses an Anim to choose a tile index. // AnimatedTile uses an Anim to choose a tile index.
type AnimatedTile struct { type AnimatedTile struct {
AnimRef Animer
} }
func (a *AnimatedTile) TileIndex() int { return a.Anim().CurrentFrame() } func (a *AnimatedTile) TileIndex() int { return a.CurrentFrame() }
func (a *AnimatedTile) Update() error { return a.Anim().Update() }

View file

@ -1,65 +0,0 @@
package game
import "drjosh.dev/gurgle/engine"
func init() {
engine.AnimDefs = map[string]*engine.AnimDef{
"green_tiles": {
Frames: []engine.AnimFrame{
{Frame: 0, Duration: 16},
{Frame: 1, Duration: 16},
{Frame: 2, Duration: 16},
},
},
"red_tiles": {
Frames: []engine.AnimFrame{
{Frame: 3, Duration: 12},
{Frame: 4, Duration: 12},
{Frame: 5, Duration: 12},
{Frame: 6, Duration: 12},
},
},
"aw_idle_right": {
Frames: []engine.AnimFrame{
{Frame: 0, Duration: 60},
},
},
"aw_idle_left": {
Frames: []engine.AnimFrame{
{Frame: 1, Duration: 60},
},
},
"aw_walk_right": {
Frames: []engine.AnimFrame{
{Frame: 2, Duration: 6},
{Frame: 3, Duration: 6},
{Frame: 4, Duration: 6},
{Frame: 5, Duration: 6},
},
},
"aw_walk_left": {
Frames: []engine.AnimFrame{
{Frame: 6, Duration: 6},
{Frame: 7, Duration: 6},
{Frame: 8, Duration: 6},
{Frame: 9, Duration: 6},
},
},
"aw_run_right": {
Frames: []engine.AnimFrame{
{Frame: 10, Duration: 3},
{Frame: 11, Duration: 5},
{Frame: 12, Duration: 3},
{Frame: 13, Duration: 3},
},
},
"aw_run_left": {
Frames: []engine.AnimFrame{
{Frame: 14, Duration: 3},
{Frame: 15, Duration: 5},
{Frame: 16, Duration: 3},
{Frame: 17, Duration: 3},
},
},
}
}

View file

@ -26,7 +26,7 @@ type Awakeman struct {
jumpBuffer int jumpBuffer int
noclip bool noclip bool
animIdleLeft, animIdleRight, animRunLeft, animRunRight *engine.Anim animIdleLeft, animIdleRight, animRunLeft, animRunRight, animWalkLeft, animWalkRight *engine.Anim
} }
func (aw *Awakeman) Update() error { func (aw *Awakeman) Update() error {
@ -163,10 +163,40 @@ func (aw *Awakeman) realUpdate() error {
func (aw *Awakeman) Prepare(game *engine.Game) { func (aw *Awakeman) Prepare(game *engine.Game) {
aw.camera = game.Component(aw.CameraID).(*engine.Camera) aw.camera = game.Component(aw.CameraID).(*engine.Camera)
aw.animIdleLeft = &engine.Anim{Def: engine.AnimDefs["aw_idle_left"]} aw.animIdleLeft = &engine.Anim{Frames: []engine.AnimFrame{
aw.animIdleRight = &engine.Anim{Def: engine.AnimDefs["aw_idle_right"]} {Frame: 1, Duration: 60},
aw.animRunLeft = &engine.Anim{Def: engine.AnimDefs["aw_run_left"]} }}
aw.animRunRight = &engine.Anim{Def: engine.AnimDefs["aw_run_right"]} aw.animIdleRight = &engine.Anim{Frames: []engine.AnimFrame{
{Frame: 0, Duration: 60},
}}
aw.animRunLeft = &engine.Anim{Frames: []engine.AnimFrame{
{Frame: 14, Duration: 3},
{Frame: 15, Duration: 5},
{Frame: 16, Duration: 3},
{Frame: 17, Duration: 3},
}}
aw.animRunRight = &engine.Anim{Frames: []engine.AnimFrame{
{Frame: 10, Duration: 3},
{Frame: 11, Duration: 5},
{Frame: 12, Duration: 3},
{Frame: 13, Duration: 3},
}}
aw.animWalkRight = &engine.Anim{
Frames: []engine.AnimFrame{
{Frame: 2, Duration: 6},
{Frame: 3, Duration: 6},
{Frame: 4, Duration: 6},
{Frame: 5, Duration: 6},
},
}
aw.animWalkLeft = &engine.Anim{
Frames: []engine.AnimFrame{
{Frame: 6, Duration: 6},
{Frame: 7, Duration: 6},
{Frame: 8, Duration: 6},
{Frame: 9, Duration: 6},
},
}
} }
func (aw *Awakeman) Scan() []interface{} { return []interface{}{&aw.Sprite} } func (aw *Awakeman) Scan() []interface{} { return []interface{}{&aw.Sprite} }

33
main.go
View file

@ -20,23 +20,32 @@ func main() {
ebiten.SetWindowSize(640, 480) ebiten.SetWindowSize(640, 480)
ebiten.SetWindowTitle("TODO") ebiten.SetWindowTitle("TODO")
// engine.AnimDefs set in game/anims.go redTileAnim := &engine.Anim{Frames: []engine.AnimFrame{
{Frame: 3, Duration: 12},
{Frame: 4, Duration: 12},
{Frame: 5, Duration: 12},
{Frame: 6, Duration: 12},
}}
greenTileAnim := &engine.Anim{Frames: []engine.AnimFrame{
{Frame: 0, Duration: 16},
{Frame: 1, Duration: 16},
{Frame: 2, Duration: 16},
}}
denseTiles := [][]engine.Tile{ denseTiles := [][]engine.Tile{
{engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, engine.StaticTile(9)}, {engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, engine.StaticTile(9)},
{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "red_tiles"}}, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{Animer: redTileAnim.Copy()}, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "red_tiles"}}, nil, nil, nil}, {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{Animer: redTileAnim.Copy()}, nil, nil, nil},
{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "green_tiles"}}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, &engine.AnimatedTile{Animer: greenTileAnim.Copy()}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "red_tiles"}}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, nil, &engine.AnimatedTile{Animer: redTileAnim.Copy()}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "green_tiles"}}, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{Animer: greenTileAnim.Copy()}, nil, nil, nil, nil, nil, nil},
{nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "green_tiles"}}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, &engine.AnimatedTile{Animer: greenTileAnim.Copy()}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "green_tiles"}}, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{Animer: greenTileAnim.Copy()}, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "red_tiles"}}, nil, nil, nil, nil, nil, nil, engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, &engine.AnimatedTile{Animer: redTileAnim.Copy()}, nil, nil, nil, nil, nil, nil, engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil},
{nil, nil, nil, nil, nil, engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, engine.StaticTile(9), nil, nil, nil}, {nil, nil, nil, nil, nil, engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, engine.StaticTile(9), nil, nil, nil},
{nil, nil, nil, nil, engine.StaticTile(9), engine.StaticTile(9), engine.StaticTile(9), nil, nil, nil, engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil}, {nil, nil, nil, nil, engine.StaticTile(9), engine.StaticTile(9), engine.StaticTile(9), nil, nil, nil, engine.StaticTile(9), nil, nil, nil, nil, nil, nil, nil, nil, nil},
{engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "red_tiles"}}, engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8)}, {engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), &engine.AnimatedTile{Animer: redTileAnim.Copy()}, engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8), engine.StaticTile(8)},
{engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "red_tiles"}}, engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "green_tiles"}}, engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7)}, {engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), &engine.AnimatedTile{Animer: redTileAnim.Copy()}, engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), &engine.AnimatedTile{Animer: greenTileAnim.Copy()}, engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7)},
{engine.StaticTile(9), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(9)}, {engine.StaticTile(9), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(7), engine.StaticTile(9)},
} }
tiles := make(map[image.Point]engine.Tile) tiles := make(map[image.Point]engine.Tile)