This commit is contained in:
Josh Deprez 2021-08-27 15:39:10 +10:00
parent cf34a7785c
commit 12e3254a34
4 changed files with 142 additions and 12 deletions

View file

@ -25,7 +25,7 @@ type Actor struct {
Pos image.Point Pos image.Point
Size image.Point Size image.Point
collisionDomain interface{} collisionDomain []Collider
xRem, yRem float64 xRem, yRem float64
} }
@ -33,7 +33,7 @@ func (a *Actor) BoundingRect() image.Rectangle { return image.Rectangle{a.Pos, a
func (a *Actor) CollidesAt(p image.Point) bool { func (a *Actor) CollidesAt(p image.Point) bool {
bounds := image.Rectangle{Min: p, Max: p.Add(a.Size)} bounds := image.Rectangle{Min: p, Max: p.Add(a.Size)}
return nil != Walk(a.collisionDomain, func(c interface{}, _ []interface{}) error { /*return nil != Walk(a.collisionDomain, func(c interface{}, _ []interface{}) error {
coll, ok := c.(Collider) coll, ok := c.(Collider)
if !ok { if !ok {
return nil return nil
@ -42,7 +42,13 @@ func (a *Actor) CollidesAt(p image.Point) bool {
return Collision{With: coll} return Collision{With: coll}
} }
return nil return nil
}) })*/
for _, c := range a.collisionDomain {
if c.CollidesWith(bounds) {
return true
}
}
return false
} }
func (a *Actor) MoveX(dx float64, onCollide func()) { func (a *Actor) MoveX(dx float64, onCollide func()) {
@ -86,7 +92,13 @@ func (a *Actor) MoveY(dy float64, onCollide func()) {
} }
func (a *Actor) Prepare(g *Game) error { func (a *Actor) Prepare(g *Game) error {
a.collisionDomain = g.Component(a.CollisionDomain) //a.collisionDomain = g.Component(a.CollisionDomain)
cs := g.Query(g.Component(a.CollisionDomain), ColliderType)
a.collisionDomain = make([]Collider, 0, len(cs))
for _, c := range cs {
a.collisionDomain = append(a.collisionDomain, c.(Collider))
}
return nil return nil
} }

View file

@ -4,6 +4,7 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"io/fs" "io/fs"
"reflect"
"sync" "sync"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@ -24,7 +25,13 @@ type Game struct {
Root DrawUpdater // typically a *Scene or SceneRef though Root DrawUpdater // typically a *Scene or SceneRef though
dbmu sync.RWMutex dbmu sync.RWMutex
db map[string]Identifier // Components by ID db map[string]Identifier // Named components by ID
dex map[dexKey][]interface{} // Ancestor/behaviour index
}
type dexKey struct {
ancestor interface{}
behaviour reflect.Type
} }
// Draw draws the entire thing, with default draw options. // Draw draws the entire thing, with default draw options.
@ -48,7 +55,22 @@ func (g *Game) Update() error {
return g.Root.Update() return g.Root.Update()
} }
func (g *Game) registerComponent(c interface{}, p []interface{}) error { func (g *Game) registerComponent(c interface{}, path []interface{}) error {
// register in g.dex
for _, b := range Behaviours {
ct := reflect.TypeOf(c)
if !ct.Implements(b) {
continue
}
k := dexKey{c, b}
g.dex[k] = append(g.dex[k], c)
for _, p := range path {
k := dexKey{p, b}
g.dex[k] = append(g.dex[k], c)
}
}
// register in g.db
i, ok := c.(Identifier) i, ok := c.(Identifier)
if !ok { if !ok {
return nil return nil
@ -65,12 +87,22 @@ func (g *Game) registerComponent(c interface{}, p []interface{}) error {
} }
// Component returns the component with a given ID, or nil if there is none. // Component returns the component with a given ID, or nil if there is none.
// This only returns sensible values after LoadAndPrepare.
func (g *Game) Component(id string) Identifier { func (g *Game) Component(id string) Identifier {
g.dbmu.RLock() g.dbmu.RLock()
defer g.dbmu.RUnlock() defer g.dbmu.RUnlock()
return g.db[id] return g.db[id]
} }
// Query looks for components having both a given ancestor and implementing
// a given behaviour (see Behaviors in interface.go). This only returns sensible
// values after LoadAndPrepare. Note that every component is its own ancestor.
func (g *Game) Query(ancestor interface{}, behaviour reflect.Type) []interface{} {
g.dbmu.RLock()
defer g.dbmu.RUnlock()
return g.dex[dexKey{ancestor, behaviour}]
}
// Scan implements Scanner. // Scan implements Scanner.
func (g *Game) Scan() []interface{} { return []interface{}{g.Root} } func (g *Game) Scan() []interface{} { return []interface{}{g.Root} }
@ -97,11 +129,9 @@ func walk(c interface{}, p []interface{}, v func(interface{}, []interface{}) err
return nil return nil
} }
// LoadAndPrepare first calls Load on all Loaders. Once loading is complete, // LoadAndPrepare first calls Load on all Loaders. Once loading is complete, it
// it builds the component database and then calls Prepare on every Preparer. // builds the component databases and then calls Prepare on every Preparer.
// You must call Prepare before any calls // LoadAndPrepare must be called before any calls to Component or Query.
// to Component. You may call Prepare again (e.g. as an alternative to
// fastidiously calling RegisterComponent/UnregisterComponent).
func (g *Game) LoadAndPrepare(assets fs.FS) error { func (g *Game) LoadAndPrepare(assets fs.FS) error {
// Load all the Loaders // Load all the Loaders
if err := Walk(g, func(c interface{}, _ []interface{}) error { if err := Walk(g, func(c interface{}, _ []interface{}) error {
@ -117,6 +147,7 @@ func (g *Game) LoadAndPrepare(assets fs.FS) error {
// Build the component databases // Build the component databases
g.dbmu.Lock() g.dbmu.Lock()
g.db = make(map[string]Identifier) g.db = make(map[string]Identifier)
g.dex = make(map[dexKey][]interface{})
if err := Walk(g, g.registerComponent); err != nil { if err := Walk(g, g.registerComponent); err != nil {
return err return err
} }

View file

@ -3,10 +3,53 @@ package engine
import ( import (
"image" "image"
"io/fs" "io/fs"
"reflect"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
// Reflection types used for queries... Is there a better way?
var (
// TypeOf(pointer to interface).Elem() is "idiomatic" -
// see https://pkg.go.dev/reflect#example-TypeOf
AnimerType = reflect.TypeOf((*Animer)(nil)).Elem()
BounderType = reflect.TypeOf((*Bounder)(nil)).Elem()
ColliderType = reflect.TypeOf((*Collider)(nil)).Elem()
DisablerType = reflect.TypeOf((*Disabler)(nil)).Elem()
DrawerType = reflect.TypeOf((*Drawer)(nil)).Elem()
DrawOrdererType = reflect.TypeOf((*DrawOrderer)(nil)).Elem()
DrawUpdaterType = reflect.TypeOf((*DrawUpdater)(nil)).Elem()
HiderType = reflect.TypeOf((*Hider)(nil)).Elem()
IdentifierType = reflect.TypeOf((*Identifier)(nil)).Elem()
LoaderType = reflect.TypeOf((*Loader)(nil)).Elem()
ParallaxScalerType = reflect.TypeOf((*ParallaxScaler)(nil)).Elem()
PrepperType = reflect.TypeOf((*Prepper)(nil)).Elem()
ScannerType = reflect.TypeOf((*Scanner)(nil)).Elem()
ScenerType = reflect.TypeOf((*Scener)(nil)).Elem()
SaverType = reflect.TypeOf((*Saver)(nil)).Elem()
UpdaterType = reflect.TypeOf((*Updater)(nil)).Elem()
// Behaviours lists all the behaviours that can be queried with Game.Query.
Behaviours = []reflect.Type{
AnimerType,
BounderType,
ColliderType,
DisablerType,
DrawerType,
DrawOrdererType,
DrawUpdaterType,
HiderType,
IdentifierType,
LoaderType,
ParallaxScalerType,
PrepperType,
ScannerType,
ScenerType,
SaverType,
UpdaterType,
}
)
// Animer components have a current frame index. // Animer components have a current frame index.
type Animer interface { type Animer interface {
Updater Updater

View file

@ -6,6 +6,7 @@ import (
"io" "io"
"io/fs" "io/fs"
"os" "os"
"reflect"
"strings" "strings"
) )
@ -33,6 +34,8 @@ func (g *Game) REPL(src io.Reader, dst io.Writer, assets fs.FS) error {
g.cmdReload(dst, assets) g.cmdReload(dst, assets)
case "tree": case "tree":
g.cmdTree(dst, argv) g.cmdTree(dst, argv)
case "query":
g.cmdQuery(dst, argv)
} }
fmt.Fprint(dst, prompt) fmt.Fprint(dst, prompt)
} }
@ -75,7 +78,7 @@ func (g *Game) cmdTree(dst io.Writer, argv []string) {
if len(argv) < 1 || len(argv) > 2 { if len(argv) < 1 || len(argv) > 2 {
fmt.Println(dst, "Usage: tree [ID]") fmt.Println(dst, "Usage: tree [ID]")
} }
var c interface{} = g c := interface{}(g)
if len(argv) == 2 { // subtree if len(argv) == 2 { // subtree
id := argv[1] id := argv[1]
c = g.Component(id) c = g.Component(id)
@ -98,3 +101,44 @@ func (g *Game) cmdTree(dst io.Writer, argv []string) {
return nil return nil
}) })
} }
func (g *Game) cmdQuery(dst io.Writer, argv []string) {
if len(argv) < 2 || len(argv) > 3 {
fmt.Fprintln(dst, "Usage: query BEHAVIOUR [ANCESTOR_ID]")
}
var behav reflect.Type
for _, b := range Behaviours {
if b.Name() == argv[1] {
behav = b
}
}
if behav == nil {
fmt.Fprintf(dst, "Unknown behaviour %q\n", argv[1])
}
ances := interface{}(g)
if len(argv) == 3 {
id := argv[2]
ances = g.Component(id)
if ances == nil {
fmt.Fprintf(dst, "Component %q not found\n", id)
return
}
}
x := g.Query(ances, behav)
if len(x) == 0 {
fmt.Fprintln(dst, "No results")
return
}
for _, c := range x {
i, ok := c.(Identifier)
if ok {
fmt.Fprintf(dst, "%T %s\n", c, i.Ident())
} else {
fmt.Fprintf(dst, "%T\n", c)
}
}
}