diff --git a/engine/game.go b/engine/game.go index 0b659c1..5979d2c 100644 --- a/engine/game.go +++ b/engine/game.go @@ -1,55 +1,17 @@ package engine -import ( - "sort" - - "github.com/hajimehoshi/ebiten/v2" -) - -// Updater is a component that can update. Update is called repeatedly. -type Updater interface { - Update() error -} - -// Drawer is a component that can draw itself. Draw is called often. -// Z is used to reorder components. -type Drawer interface { - Draw(screen *ebiten.Image, geom ebiten.GeoM) - Z() float64 -} +import "github.com/hajimehoshi/ebiten/v2" // Game implements the ebiten methods using a collection of components. type Game struct { ScreenWidth int ScreenHeight int - Components []interface{} - - needsSort bool + Layers *Layers } -// Update calls Update on all Updater components. -func (g *Game) Update() error { - for _, c := range g.Components { - if u, ok := c.(Updater); ok { - if err := u.Update(); err != nil { - return err - } - } - } - if g.needsSort { - g.needsSort = false - g.sortDrawers() - } - return nil -} - -// Draw calls Draw on all Drawer components. +// Draw draws the entire thing. func (g *Game) Draw(screen *ebiten.Image) { - for _, c := range g.Components { - if d, ok := c.(Drawer); ok { - d.Draw(screen, ebiten.GeoM{}) - } - } + g.Layers.Draw(screen, ebiten.GeoM{}) } // Layout returns the configured screen width/height. @@ -57,22 +19,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (w, h int) { return g.ScreenWidth, g.ScreenHeight } -// SetNeedsSort tells the game that the Drawers need sorting. -// This will be done in the current update. -func (g *Game) SetNeedsSort() { - g.needsSort = true -} - -// sortDrawers sorts the components by Z position. -// Non-Drawers are sorted before all Drawers. -func (g *Game) sortDrawers() { - // Stable sort to avoid z-fighting (among Non-Drawers and equal Drawers) - sort.SliceStable(g.Components, func(i, j int) bool { - a, aok := g.Components[i].(Drawer) - b, bok := g.Components[j].(Drawer) - if aok && bok { - return a.Z() < b.Z() - } - return !aok && bok - }) +// Update just passes the call onto Layers. +func (g *Game) Update() error { + return g.Layers.Update() } diff --git a/engine/layers.go b/engine/layers.go new file mode 100644 index 0000000..6176e51 --- /dev/null +++ b/engine/layers.go @@ -0,0 +1,71 @@ +package engine + +import ( + "sort" + + "github.com/hajimehoshi/ebiten/v2" +) + +// Drawer is a component that can draw itself. Draw is called often. +type Drawer interface { + Draw(screen *ebiten.Image, geom ebiten.GeoM) +} + +// Updater is a component that can update. Update is called repeatedly. +type Updater interface { + Update() error +} + +// ZPositioner is used to reorder layers. +type ZPositioner interface { + Z() float64 +} + +// Layers +type Layers struct { + Components []interface{} + needsSort bool +} + +// Draw draws all layers in order. +func (l *Layers) Draw(screen *ebiten.Image, geom ebiten.GeoM) { + for _, i := range l.Components { + if d, ok := i.(Drawer); ok { + d.Draw(screen, geom) + } + } +} + +// SetNeedsSort informs l that its layers may be out of order. +func (l *Layers) SetNeedsSort() { + l.needsSort = true +} + +// sortByZ sorts the components by Z position. +// Stable sort is used to avoid z-fighting among layers without a Z. +func (l *Layers) sortByZ() { + sort.SliceStable(l.Components, func(i, j int) bool { + a, aok := l.Components[i].(ZPositioner) + b, bok := l.Components[j].(ZPositioner) + if aok && bok { + return a.Z() < b.Z() + } + return !aok && bok + }) + l.needsSort = false +} + +// Update calls Update on all Updater components. +func (l *Layers) Update() error { + for _, c := range l.Components { + if u, ok := c.(Updater); ok { + if err := u.Update(); err != nil { + return err + } + } + } + if l.needsSort { + l.sortByZ() + } + return nil +} diff --git a/main.go b/main.go index 5fe062a..66bcc2f 100644 --- a/main.go +++ b/main.go @@ -76,15 +76,19 @@ func main() { TileSize: 16, } + components := []interface{}{ + tilemap, + engine.PerfDisplay{}, + } + game := &engine.Game{ ScreenHeight: screenHeight, ScreenWidth: screenWidth, - Components: []interface{}{ - tilemap, - engine.PerfDisplay{}, + Layers: &engine.Layers{ + Components: components, }, } - game.SetNeedsSort() + game.Layers.SetNeedsSort() if err := ebiten.RunGame(game); err != nil { log.Fatalf("Game error: %v", err)