diff --git a/engine/actor.go b/engine/actor.go index 2dd0410..84fb999 100644 --- a/engine/actor.go +++ b/engine/actor.go @@ -2,12 +2,15 @@ package engine import ( "encoding/gob" + "errors" "image" ) // Ensure Actor satisfies interfaces. var _ Prepper = &Actor{} +var errCollision = errors.New("collision detected") + func init() { gob.Register(Actor{}) } @@ -26,17 +29,16 @@ type Actor struct { } func (a *Actor) CollidesAt(p image.Point) bool { - hit := false - Walk(a.collisionDomain, func(c interface{}) bool { - if coll, ok := c.(Collider); ok { - if coll.CollidesWith(image.Rectangle{Min: p, Max: p.Add(a.Size)}) { - hit = true - return false - } + return nil != Walk(a.collisionDomain, func(c interface{}) error { + coll, ok := c.(Collider) + if !ok { + return nil } - return true + if coll.CollidesWith(image.Rectangle{Min: p, Max: p.Add(a.Size)}) { + return errCollision + } + return nil }) - return hit } func (a *Actor) MoveX(dx float64, onCollide func()) { diff --git a/engine/asset.go b/engine/asset.go index 65779da..49dc52d 100644 --- a/engine/asset.go +++ b/engine/asset.go @@ -19,6 +19,9 @@ var ( AnimDefs map[string]*AnimDef imageCache = make(map[string]*ebiten.Image) + + // Ensure SceneRef does the same stuff as Scene. + _ Scener = &SceneRef{} ) // AnimRef manages an Anim using a premade AnimDef from the cache. @@ -78,26 +81,37 @@ func (r *ImageRef) Image() *ebiten.Image { type SceneRef struct { Path string - scene *Scene + scene *Scene // not exported for gob reasons } -func (r *SceneRef) Scene() *Scene { - if r.scene != nil { - return r.scene - } +// Load loads the scene from the file and then calls Load +// on the freshly-loaded Scene. +func (r *SceneRef) Load() error { f, err := AssetFS.Open(r.Path) if err != nil { - log.Fatalf("Couldn't open asset: %v", err) + return err } defer f.Close() gz, err := gzip.NewReader(f) if err != nil { - log.Fatalf("Couldn't gunzip asset: %v", err) + return err } - r.scene = new(Scene) - if err := gob.NewDecoder(gz).Decode(r.scene); err != nil { - log.Fatalf("Couldn't decode asset: %v", err) + sc := new(Scene) + if err := gob.NewDecoder(gz).Decode(sc); err != nil { + return err } - return r.scene + r.scene = sc + return sc.Load() } + +// Implement Scener by forwarding all the other calls to r.scene + +func (r SceneRef) Draw(screen *ebiten.Image, opts ebiten.DrawImageOptions) { + r.scene.Draw(screen, opts) +} +func (r SceneRef) DrawOrder() float64 { return r.scene.DrawOrder() } +func (r SceneRef) Ident() string { return r.scene.Ident() } +func (r SceneRef) Prepare(g *Game) { r.scene.Prepare(g) } +func (r SceneRef) Scan() []interface{} { return r.scene.Scan() } +func (r SceneRef) Update() error { return r.scene.Update() } diff --git a/engine/game.go b/engine/game.go index 3493a33..7467294 100644 --- a/engine/game.go +++ b/engine/game.go @@ -10,21 +10,11 @@ func init() { gob.Register(Game{}) } -type GameMode int - -const ( - GameModeMenu = GameMode(iota) - GameModePlay - GameModePause - GameModeEdit -) - // Game implements the ebiten methods using a collection of components. type Game struct { - Mode GameMode ScreenWidth int ScreenHeight int - *Scene + Scene *Scene componentsByID map[string]interface{} } @@ -79,21 +69,21 @@ func (g *Game) Component(id string) interface{} { return g.componentsByID[id] } func (g *Game) Scan() []interface{} { return []interface{}{g.Scene} } // Walk calls v with every component reachable from c via Scan, recursively, -// for as long as visit returns true. -func Walk(c interface{}, v func(interface{}) bool) { - if !v(c) { - return +// for as long as visit returns nil. +func Walk(c interface{}, v func(interface{}) error) error { + if err := v(c); err != nil { + return err } sc, ok := c.(Scanner) if !ok { - return + return nil } for _, c := range sc.Scan() { - if !v(c) { - return + if err := Walk(c, v); err != nil { + return err } - Walk(c, v) } + return nil } // PrepareToRun builds the component database (using Walk) and then calls @@ -101,14 +91,14 @@ func Walk(c interface{}, v func(interface{}) bool) { // ebiten.RunGame. func (g *Game) PrepareToRun() { g.componentsByID = make(map[string]interface{}) - Walk(g, func(c interface{}) bool { + Walk(g, func(c interface{}) error { g.RegisterComponent(c) - return true + return nil }) - Walk(g, func(c interface{}) bool { + Walk(g, func(c interface{}) error { if p, ok := c.(Prepper); ok { p.Prepare(g) } - return true + return nil }) } diff --git a/engine/interface.go b/engine/interface.go index 08190df..a08f8d1 100644 --- a/engine/interface.go +++ b/engine/interface.go @@ -29,6 +29,12 @@ type Identifier interface { Ident() string } +// Loader components get the chance to load themselves. This happens +// before preparation. +type Loader interface { + Load() error +} + // ParallaxScaler components have a scaling factor. This is used for // parallax layers in a scene, and can be thought of as 1/distance. type ParallaxScaler interface { @@ -49,6 +55,18 @@ type Scanner interface { Scan() []interface{} } +// Scener components do everything that a Scene can. The other implementation +// is SceneRef. +type Scener interface { + Drawer + DrawOrderer + Identifier + Loader + Prepper + Scanner + Updater +} + // Updater components can update themselves. Update is called repeatedly. // Each component is responsible for calling Update on its child components // (so that disabling the parent prevents updates to the children, etc). diff --git a/engine/scene.go b/engine/scene.go index 47f7666..edf2a85 100644 --- a/engine/scene.go +++ b/engine/scene.go @@ -9,15 +9,8 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -// Ensure Scene satisfies interfaces. -var ( - _ Identifier = &Scene{} - _ Drawer = &Scene{} - _ DrawOrderer = &Scene{} - _ Prepper = &Scene{} - _ Scanner = &Scene{} - _ Updater = &Scene{} -) +// Ensure Scene satisfies Scener. +var _ Scener = &Scene{} func init() { gob.Register(Scene{}) @@ -46,6 +39,20 @@ func (s *Scene) Draw(screen *ebiten.Image, opts ebiten.DrawImageOptions) { } } +// Load loads any components that need loading. +func (s *Scene) Load() error { + for _, i := range s.Components { + l, ok := i.(Loader) + if !ok { + continue + } + if err := l.Load(); err != nil { + return err + } + } + return nil +} + // Prepare does an initial Z-order sort. func (s *Scene) Prepare(game *Game) { s.sortByDrawOrder() }