I used to be engaged on a bit engine wrapper for ebitengine (Go) to assist with dealing with entities (not ECS, simply EC) and got here up with this closely purposeful implementation the place every entity is a closure(??) that registers replace features within the “constructor”
I wished to leverage the advantages of ECS (utilizing solely knowledge that’s obligatory in a given context and storing that every one collectively for comparable objects) however with the liberty to outline particular behaviors for every entity sort.
entity.go:
bundle sport
import (
"picture/shade"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"starblock-studios.com/ebitengine-project/mathutil"
v2 "starblock-studios.com/ebitengine-project/mathutil"
)
func (w *world) DebugEntity(
shade shade.RGBA, dimension float64, velocity float64) (Entity *struct{ Place v2.Vector2 }) {
Entity = &struct{ Place v2.Vector2 }{}
Entity.Place = *v2.NewV2(0, 0)
getUpdate := func() EventCallback[InfoUpdate] {
cpos := mathutil.NewV2(0, 0)
return func(occasion InfoUpdate) {
x, y := ebiten.CursorPosition()
cpos.Set(float64(x), float64(y))
boon := 1 - mathutil.Clamp(cpos.DistanceSquared(&Entity.Place)/10000, 0, 1)
Entity.Place = *Entity.Place.MoveTowards(cpos, velocity*boon)
}
}
getDraw := func() EventCallback[InfoDraw] {
radius := float32(dimension)
circleColor := shade
imageSize := int(dimension * 2)
picture := ebiten.NewImage(imageSize, imageSize)
vector.DrawFilledCircle(picture, float32(imageSize/2), float32(imageSize/2), radius, circleColor, false)
choices := &ebiten.DrawImageOptions{}
return func(occasion InfoDraw) {
choices.GeoM = ebiten.GeoM{}
choices.GeoM.Translate(Entity.Place.X-float64(imageSize)/2, Entity.Place.Y-float64(imageSize)/2)
occasion.Display screen.DrawImage(picture, choices)
}
}
w.OnUpdate.register(getUpdate())
w.OnDrawLayers[0].register(getDraw())
return Entity
}
world.go:
bundle sport
import (
"time"
"github.com/hajimehoshi/ebiten/v2"
)
sort EventCallback[T any] func(T)
sort InfoUpdate struct {
Delta float64
TimeSeconds float64
}
sort InfoDraw struct {
Display screen *ebiten.Picture
}
sort EventGroup[T any] []EventCallback[T]
sort world struct {
OnUpdate EventGroup[InfoUpdate]
OnDrawLayers []EventGroup[InfoDraw]
begin time.Time
previousHandle time.Time
}
func (g EventGroup[T]) Notify(p T) {
for _, fn := vary g {
fn(p)
}
}
func CreateWorld() *world {
w := &world{}
w.begin = time.Now()
w.previousHandle = w.begin
w.OnDrawLayers = make([]EventGroup[InfoDraw], 1)
return w
}
func (w *world) Replace() {
w.OnUpdate.Notify(
InfoUpdate{
Delta: time.Since(w.previousHandle).Seconds(),
TimeSeconds: time.Since(w.begin).Seconds(),
},
)
w.previousHandle = time.Now()
}
func (w *world) Draw(display *ebiten.Picture) {
for _, v := vary w.OnDrawLayers {
v.Notify(
InfoDraw{
Display screen: display,
},
)
}
}
func (g *EventGroup[T]) register(handler EventCallback[T]) {
*g = append(*g, handler)
}
I examined this out and it truly performs fairly a bit higher than an identical implementation I attempted the place every entity is an interface (replace, draw)
As I perceive it, closures get ‘compiled’ into structs that solely maintain the info that they want, just like how ECS solely {couples} elements that require one another.
I am not extraordinarily involved about efficiency however I wish to have as a lot efficiency as I can whereas nonetheless having the flexibleness to make use of these particular person occasion ‘strategies’ (just like different composition-based engines)
I wished to know if that is truly a superb strategy that I ought to pursue and, if not, what could be an easier implementation with comparable efficiency and capabilities.
To make clear, I am working manner out of my realm of experience so would recognize suggestions from anybody skilled with this type of system (: