error-based walk; Scener
This commit is contained in:
parent
91bf4939e7
commit
946b76df9a
5 changed files with 83 additions and 52 deletions
|
@ -2,12 +2,15 @@ package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure Actor satisfies interfaces.
|
// Ensure Actor satisfies interfaces.
|
||||||
var _ Prepper = &Actor{}
|
var _ Prepper = &Actor{}
|
||||||
|
|
||||||
|
var errCollision = errors.New("collision detected")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(Actor{})
|
gob.Register(Actor{})
|
||||||
}
|
}
|
||||||
|
@ -26,17 +29,16 @@ type Actor struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) CollidesAt(p image.Point) bool {
|
func (a *Actor) CollidesAt(p image.Point) bool {
|
||||||
hit := false
|
return nil != Walk(a.collisionDomain, func(c interface{}) error {
|
||||||
Walk(a.collisionDomain, func(c interface{}) bool {
|
coll, ok := c.(Collider)
|
||||||
if coll, ok := c.(Collider); ok {
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if coll.CollidesWith(image.Rectangle{Min: p, Max: p.Add(a.Size)}) {
|
if coll.CollidesWith(image.Rectangle{Min: p, Max: p.Add(a.Size)}) {
|
||||||
hit = true
|
return errCollision
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
return hit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) MoveX(dx float64, onCollide func()) {
|
func (a *Actor) MoveX(dx float64, onCollide func()) {
|
||||||
|
|
|
@ -19,6 +19,9 @@ var (
|
||||||
AnimDefs map[string]*AnimDef
|
AnimDefs map[string]*AnimDef
|
||||||
|
|
||||||
imageCache = make(map[string]*ebiten.Image)
|
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.
|
// AnimRef manages an Anim using a premade AnimDef from the cache.
|
||||||
|
@ -78,26 +81,37 @@ func (r *ImageRef) Image() *ebiten.Image {
|
||||||
type SceneRef struct {
|
type SceneRef struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
scene *Scene
|
scene *Scene // not exported for gob reasons
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SceneRef) Scene() *Scene {
|
// Load loads the scene from the file and then calls Load
|
||||||
if r.scene != nil {
|
// on the freshly-loaded Scene.
|
||||||
return r.scene
|
func (r *SceneRef) Load() error {
|
||||||
}
|
|
||||||
f, err := AssetFS.Open(r.Path)
|
f, err := AssetFS.Open(r.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Couldn't open asset: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
gz, err := gzip.NewReader(f)
|
gz, err := gzip.NewReader(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Couldn't gunzip asset: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
r.scene = new(Scene)
|
sc := new(Scene)
|
||||||
if err := gob.NewDecoder(gz).Decode(r.scene); err != nil {
|
if err := gob.NewDecoder(gz).Decode(sc); err != nil {
|
||||||
log.Fatalf("Couldn't decode asset: %v", err)
|
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() }
|
||||||
|
|
|
@ -10,21 +10,11 @@ func init() {
|
||||||
gob.Register(Game{})
|
gob.Register(Game{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
GameModeMenu = GameMode(iota)
|
|
||||||
GameModePlay
|
|
||||||
GameModePause
|
|
||||||
GameModeEdit
|
|
||||||
)
|
|
||||||
|
|
||||||
// Game implements the ebiten methods using a collection of components.
|
// Game implements the ebiten methods using a collection of components.
|
||||||
type Game struct {
|
type Game struct {
|
||||||
Mode GameMode
|
|
||||||
ScreenWidth int
|
ScreenWidth int
|
||||||
ScreenHeight int
|
ScreenHeight int
|
||||||
*Scene
|
Scene *Scene
|
||||||
|
|
||||||
componentsByID map[string]interface{}
|
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} }
|
func (g *Game) Scan() []interface{} { return []interface{}{g.Scene} }
|
||||||
|
|
||||||
// Walk calls v with every component reachable from c via Scan, recursively,
|
// Walk calls v with every component reachable from c via Scan, recursively,
|
||||||
// for as long as visit returns true.
|
// for as long as visit returns nil.
|
||||||
func Walk(c interface{}, v func(interface{}) bool) {
|
func Walk(c interface{}, v func(interface{}) error) error {
|
||||||
if !v(c) {
|
if err := v(c); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
sc, ok := c.(Scanner)
|
sc, ok := c.(Scanner)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
for _, c := range sc.Scan() {
|
for _, c := range sc.Scan() {
|
||||||
if !v(c) {
|
if err := Walk(c, v); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
Walk(c, v)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareToRun builds the component database (using Walk) and then calls
|
// PrepareToRun builds the component database (using Walk) and then calls
|
||||||
|
@ -101,14 +91,14 @@ func Walk(c interface{}, v func(interface{}) bool) {
|
||||||
// ebiten.RunGame.
|
// ebiten.RunGame.
|
||||||
func (g *Game) PrepareToRun() {
|
func (g *Game) PrepareToRun() {
|
||||||
g.componentsByID = make(map[string]interface{})
|
g.componentsByID = make(map[string]interface{})
|
||||||
Walk(g, func(c interface{}) bool {
|
Walk(g, func(c interface{}) error {
|
||||||
g.RegisterComponent(c)
|
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 {
|
if p, ok := c.(Prepper); ok {
|
||||||
p.Prepare(g)
|
p.Prepare(g)
|
||||||
}
|
}
|
||||||
return true
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,12 @@ type Identifier interface {
|
||||||
Ident() string
|
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
|
// ParallaxScaler components have a scaling factor. This is used for
|
||||||
// parallax layers in a scene, and can be thought of as 1/distance.
|
// parallax layers in a scene, and can be thought of as 1/distance.
|
||||||
type ParallaxScaler interface {
|
type ParallaxScaler interface {
|
||||||
|
@ -49,6 +55,18 @@ type Scanner interface {
|
||||||
Scan() []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.
|
// Updater components can update themselves. Update is called repeatedly.
|
||||||
// Each component is responsible for calling Update on its child components
|
// Each component is responsible for calling Update on its child components
|
||||||
// (so that disabling the parent prevents updates to the children, etc).
|
// (so that disabling the parent prevents updates to the children, etc).
|
||||||
|
|
|
@ -9,15 +9,8 @@ import (
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure Scene satisfies interfaces.
|
// Ensure Scene satisfies Scener.
|
||||||
var (
|
var _ Scener = &Scene{}
|
||||||
_ Identifier = &Scene{}
|
|
||||||
_ Drawer = &Scene{}
|
|
||||||
_ DrawOrderer = &Scene{}
|
|
||||||
_ Prepper = &Scene{}
|
|
||||||
_ Scanner = &Scene{}
|
|
||||||
_ Updater = &Scene{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(Scene{})
|
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.
|
// Prepare does an initial Z-order sort.
|
||||||
func (s *Scene) Prepare(game *Game) { s.sortByDrawOrder() }
|
func (s *Scene) Prepare(game *Game) { s.sortByDrawOrder() }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue