change how anim works
This commit is contained in:
parent
4eaf48f380
commit
e2e937f409
9 changed files with 165 additions and 156 deletions
|
@ -1,42 +1,47 @@
|
|||
package engine
|
||||
|
||||
// Anim is an "instance" of an AnimDef: an animation being displayed,
|
||||
// together with the current state.
|
||||
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
|
||||
}
|
||||
// Ensure Anim satisfies Animer.
|
||||
var _ Animer = &Anim{}
|
||||
|
||||
// AnimFrame describes a frame in an animation.
|
||||
type AnimFrame struct {
|
||||
Frame int // show this frame
|
||||
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
48
engine/animref.go
Normal 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() }
|
|
@ -1,28 +1,22 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/gob"
|
||||
"image"
|
||||
"io/fs"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
// Ensure ImageRef satisfies interfaces.
|
||||
// Ensure types satisfy interfaces.
|
||||
_ Loader = &ImageRef{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(AnimRef{})
|
||||
gob.Register(ImageRef{})
|
||||
}
|
||||
|
||||
|
@ -31,24 +25,17 @@ type assetKey struct {
|
|||
path string
|
||||
}
|
||||
|
||||
// AnimRef manages an Anim using a premade AnimDef from the cache.
|
||||
type AnimRef struct {
|
||||
Key string
|
||||
|
||||
anim *Anim
|
||||
func loadGobz(dst interface{}, assets fs.FS, path string) error {
|
||||
f, err := assets.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *AnimRef) Anim() *Anim {
|
||||
if r.anim != nil {
|
||||
return r.anim
|
||||
defer f.Close()
|
||||
gz, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad := AnimDefs[r.Key]
|
||||
if ad == nil {
|
||||
log.Fatalf("Unknown AnimDef %q", r.Key)
|
||||
return nil
|
||||
}
|
||||
r.anim = &Anim{Def: ad}
|
||||
return r.anim
|
||||
return gob.NewDecoder(gz).Decode(dst)
|
||||
}
|
||||
|
||||
// ImageRef loads images from the AssetFS into *ebiten.Image form.
|
||||
|
|
|
@ -7,6 +7,14 @@ import (
|
|||
"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.
|
||||
type Bounder interface {
|
||||
BoundingRect() image.Rectangle
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/gob"
|
||||
"image"
|
||||
"io/fs"
|
||||
|
@ -36,18 +35,8 @@ type SceneRef struct {
|
|||
|
||||
// Load loads the scene from the file.
|
||||
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)
|
||||
if err := gob.NewDecoder(gz).Decode(sc); err != nil {
|
||||
if err := loadGobz(sc, assets, r.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
r.scene = sc
|
||||
|
|
|
@ -141,9 +141,7 @@ func (s StaticTile) TileIndex() int { return int(s) }
|
|||
|
||||
// AnimatedTile uses an Anim to choose a tile index.
|
||||
type AnimatedTile struct {
|
||||
AnimRef
|
||||
Animer
|
||||
}
|
||||
|
||||
func (a *AnimatedTile) TileIndex() int { return a.Anim().CurrentFrame() }
|
||||
|
||||
func (a *AnimatedTile) Update() error { return a.Anim().Update() }
|
||||
func (a *AnimatedTile) TileIndex() int { return a.CurrentFrame() }
|
||||
|
|
|
@ -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},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
40
game/aw.go
40
game/aw.go
|
@ -26,7 +26,7 @@ type Awakeman struct {
|
|||
jumpBuffer int
|
||||
noclip bool
|
||||
|
||||
animIdleLeft, animIdleRight, animRunLeft, animRunRight *engine.Anim
|
||||
animIdleLeft, animIdleRight, animRunLeft, animRunRight, animWalkLeft, animWalkRight *engine.Anim
|
||||
}
|
||||
|
||||
func (aw *Awakeman) Update() error {
|
||||
|
@ -163,10 +163,40 @@ func (aw *Awakeman) realUpdate() error {
|
|||
func (aw *Awakeman) Prepare(game *engine.Game) {
|
||||
aw.camera = game.Component(aw.CameraID).(*engine.Camera)
|
||||
|
||||
aw.animIdleLeft = &engine.Anim{Def: engine.AnimDefs["aw_idle_left"]}
|
||||
aw.animIdleRight = &engine.Anim{Def: engine.AnimDefs["aw_idle_right"]}
|
||||
aw.animRunLeft = &engine.Anim{Def: engine.AnimDefs["aw_run_left"]}
|
||||
aw.animRunRight = &engine.Anim{Def: engine.AnimDefs["aw_run_right"]}
|
||||
aw.animIdleLeft = &engine.Anim{Frames: []engine.AnimFrame{
|
||||
{Frame: 1, Duration: 60},
|
||||
}}
|
||||
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} }
|
||||
|
|
33
main.go
33
main.go
|
@ -20,23 +20,32 @@ func main() {
|
|||
ebiten.SetWindowSize(640, 480)
|
||||
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{
|
||||
{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, 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, &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{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: "green_tiles"}}, 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, 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, &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, nil, nil, nil, nil, nil, nil, &engine.AnimatedTile{AnimRef: engine.AnimRef{Key: "green_tiles"}}, 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, 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{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{Animer: greenTileAnim.Copy()}, 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{Animer: greenTileAnim.Copy()}, 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, 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(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(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{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)},
|
||||
}
|
||||
tiles := make(map[image.Point]engine.Tile)
|
||||
|
|
Loading…
Reference in a new issue