parent
a820927a43
commit
8d4dabd62e
42
component.go
42
component.go
|
@ -4,10 +4,10 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
var componentMutex sync.RWMutex
|
||||
var componentMutex sync.Mutex
|
||||
|
||||
// ComponentID is a component identifier. Each Component is assigned a unique ID
|
||||
// via NextComponentID, and implements a ComponentID method which returns the ID.
|
||||
// via NewComponentID, and implements a ComponentID method returning its ID.
|
||||
type ComponentID int
|
||||
|
||||
// Component represents data for an entity, and how it interacts with the world.
|
||||
|
@ -15,31 +15,32 @@ type Component interface {
|
|||
ComponentID() ComponentID
|
||||
}
|
||||
|
||||
var nextComponentID ComponentID
|
||||
var maxComponentID ComponentID
|
||||
|
||||
// NewComponentID returns the next available ComponentID.
|
||||
func NewComponentID() ComponentID {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
// NextComponentID returns the next available ComponentID.
|
||||
func NextComponentID() ComponentID {
|
||||
componentMutex.Lock()
|
||||
defer componentMutex.Unlock()
|
||||
|
||||
nextComponentID++
|
||||
return nextComponentID
|
||||
maxComponentID++
|
||||
|
||||
for i := Entity(1); i < maxEntityID; i++ {
|
||||
gameComponents[i] = append(gameComponents[i], nil)
|
||||
}
|
||||
|
||||
return maxComponentID
|
||||
}
|
||||
|
||||
// AddComponent adds a Component to an Entity.
|
||||
func (entity EntityID) AddComponent(component Component) {
|
||||
componentMutex.Lock()
|
||||
defer componentMutex.Unlock()
|
||||
|
||||
if wasRemoved(entity) {
|
||||
return
|
||||
}
|
||||
|
||||
func (entity Entity) AddComponent(component Component) {
|
||||
componentID := component.ComponentID()
|
||||
|
||||
if gameComponents[entity] == nil {
|
||||
gameComponents[entity] = make(map[ComponentID]interface{})
|
||||
}
|
||||
gameComponents[entity][componentID] = component
|
||||
|
||||
entityMutex.Lock()
|
||||
|
@ -48,10 +49,7 @@ func (entity EntityID) AddComponent(component Component) {
|
|||
}
|
||||
|
||||
// Component gets a Component of an Entity.
|
||||
func (entity EntityID) Component(componentID ComponentID) interface{} {
|
||||
componentMutex.RLock()
|
||||
defer componentMutex.RUnlock()
|
||||
|
||||
func (entity Entity) Component(componentID ComponentID) interface{} {
|
||||
components := gameComponents[entity]
|
||||
if components == nil {
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package gohan
|
||||
|
||||
import "testing"
|
||||
|
||||
var testComponentID = NewComponentID()
|
||||
|
||||
type testComponent struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
func (t testComponent) ComponentID() ComponentID {
|
||||
return testComponentID
|
||||
}
|
||||
|
||||
func BenchmarkComponent(b *testing.B) {
|
||||
e := NewEntity()
|
||||
|
||||
e.AddComponent(&testComponent{
|
||||
X: 108,
|
||||
Y: 0,
|
||||
})
|
||||
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = e.Component(testComponentID)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddComponent(b *testing.B) {
|
||||
e := NewEntity()
|
||||
|
||||
c := &testComponent{
|
||||
X: 108,
|
||||
Y: 0,
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
e.AddComponent(c)
|
||||
}
|
||||
}
|
5
doc.go
5
doc.go
|
@ -1,6 +1,11 @@
|
|||
/*
|
||||
Package gohan provides an Entity Component System framework for Ebiten.
|
||||
|
||||
An example game is available at /examples/twinstick which may be built by
|
||||
executing the following command (in /examples/twinstick):
|
||||
|
||||
go build -tags example .
|
||||
|
||||
Entity
|
||||
|
||||
A general-purpose object, which consists of a unique ID, starting with 1.
|
||||
|
|
43
entity.go
43
entity.go
|
@ -5,25 +5,36 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// EntityID is an entity identifier.
|
||||
type EntityID int
|
||||
// Entity is an entity identifier.
|
||||
type Entity int
|
||||
|
||||
var nextEntityID EntityID
|
||||
var maxEntityID Entity
|
||||
|
||||
var entityMutex sync.Mutex
|
||||
|
||||
// NextEntityID returns the next available EntityID.
|
||||
func NextEntityID() EntityID {
|
||||
// NewEntity returns a new (or previously removed and cleared) Entity. Because
|
||||
// Gohan reuses removed Entity IDs, a previously removed ID may be returned.
|
||||
func NewEntity() Entity {
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
nextEntityID++
|
||||
allEntities = append(allEntities, nextEntityID)
|
||||
return nextEntityID
|
||||
if len(availableEntityIDs) > 0 {
|
||||
id := availableEntityIDs[0]
|
||||
availableEntityIDs = availableEntityIDs[1:]
|
||||
allEntities = append(allEntities, id)
|
||||
return id
|
||||
}
|
||||
|
||||
maxEntityID++
|
||||
allEntities = append(allEntities, maxEntityID)
|
||||
gameComponents = append(gameComponents, make([]interface{}, maxComponentID+1))
|
||||
return maxEntityID
|
||||
}
|
||||
|
||||
// RemoveEntity removes the provided Entity, and all of its components.
|
||||
func RemoveEntity(entity EntityID) {
|
||||
// Remove removes the provided Entity's components, causing it to no longer be
|
||||
// handled by any system. Because Gohan reuses removed EntityIDs, applications
|
||||
// must also remove any internal references to the removed Entity.
|
||||
func (entity Entity) Remove() {
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
|
@ -36,18 +47,6 @@ func RemoveEntity(entity EntityID) {
|
|||
}
|
||||
}
|
||||
|
||||
func wasRemoved(entity EntityID) bool {
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
for _, e := range allEntities {
|
||||
if e == entity {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var numEntities int
|
||||
var numEntitiesT time.Time
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package gohan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestActiveEntities(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
active := ActiveEntities()
|
||||
if active != 0 {
|
||||
t.Fatalf("expected 0 active entities, got %d", active)
|
||||
}
|
||||
|
||||
wait()
|
||||
active = ActiveEntities()
|
||||
if active != 0 {
|
||||
t.Fatalf("expected 0 active entities, got %d", active)
|
||||
}
|
||||
|
||||
// Create entity.
|
||||
e1 := NewEntity()
|
||||
|
||||
wait()
|
||||
active = ActiveEntities()
|
||||
if active != 1 {
|
||||
t.Fatalf("expected 1 active entities, got %d", active)
|
||||
}
|
||||
|
||||
// Create entity.
|
||||
e2 := NewEntity()
|
||||
|
||||
wait()
|
||||
active = ActiveEntities()
|
||||
if active != 2 {
|
||||
t.Fatalf("expected 2 active entities, got %d", active)
|
||||
}
|
||||
|
||||
e1.Remove()
|
||||
|
||||
wait()
|
||||
active = ActiveEntities()
|
||||
if active != 1 {
|
||||
t.Fatalf("expected 1 active entities, got %d", active)
|
||||
}
|
||||
|
||||
e2.Remove()
|
||||
|
||||
wait()
|
||||
active = ActiveEntities()
|
||||
if active != 0 {
|
||||
t.Fatalf("expected 0 active entities, got %d", active)
|
||||
}
|
||||
}
|
||||
|
||||
// wait causes the program to wait long enough to expire all duration-based caches.
|
||||
func wait() {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
|
@ -10,13 +10,13 @@ import (
|
|||
type BulletComponent struct {
|
||||
}
|
||||
|
||||
var BulletComponentID = gohan.NextComponentID()
|
||||
var BulletComponentID = gohan.NewComponentID()
|
||||
|
||||
func (p *BulletComponent) ComponentID() gohan.ComponentID {
|
||||
return BulletComponentID
|
||||
}
|
||||
|
||||
func Bullet(e gohan.EntityID) *BulletComponent {
|
||||
func Bullet(e gohan.Entity) *BulletComponent {
|
||||
c, ok := e.Component(BulletComponentID).(*BulletComponent)
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
|
@ -11,13 +11,13 @@ type PositionComponent struct {
|
|||
X, Y float64
|
||||
}
|
||||
|
||||
var PositionComponentID = gohan.NextComponentID()
|
||||
var PositionComponentID = gohan.NewComponentID()
|
||||
|
||||
func (p *PositionComponent) ComponentID() gohan.ComponentID {
|
||||
return PositionComponentID
|
||||
}
|
||||
|
||||
func Position(e gohan.EntityID) *PositionComponent {
|
||||
func Position(e gohan.Entity) *PositionComponent {
|
||||
c, ok := e.Component(PositionComponentID).(*PositionComponent)
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
|
@ -11,13 +11,13 @@ type VelocityComponent struct {
|
|||
X, Y float64
|
||||
}
|
||||
|
||||
var VelocityComponentID = gohan.NextComponentID()
|
||||
var VelocityComponentID = gohan.NewComponentID()
|
||||
|
||||
func (c *VelocityComponent) ComponentID() gohan.ComponentID {
|
||||
return VelocityComponentID
|
||||
}
|
||||
|
||||
func Velocity(e gohan.EntityID) *VelocityComponent {
|
||||
func Velocity(e gohan.Entity) *VelocityComponent {
|
||||
c, ok := e.Component(VelocityComponentID).(*VelocityComponent)
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
|
@ -20,13 +20,13 @@ type WeaponComponent struct {
|
|||
BulletSpeed float64
|
||||
}
|
||||
|
||||
var WeaponComponentID = gohan.NextComponentID()
|
||||
var WeaponComponentID = gohan.NewComponentID()
|
||||
|
||||
func (p *WeaponComponent) ComponentID() gohan.ComponentID {
|
||||
return WeaponComponentID
|
||||
}
|
||||
|
||||
func Weapon(e gohan.EntityID) *WeaponComponent {
|
||||
func Weapon(e gohan.Entity) *WeaponComponent {
|
||||
c, ok := e.Component(WeaponComponentID).(*WeaponComponent)
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"code.rocketnine.space/tslocum/gohan/examples/twinstick/component"
|
||||
)
|
||||
|
||||
func NewBullet(x, y, xSpeed, ySpeed float64) gohan.EntityID {
|
||||
bullet := gohan.NextEntityID()
|
||||
func NewBullet(x, y, xSpeed, ySpeed float64) gohan.Entity {
|
||||
bullet := gohan.NewEntity()
|
||||
|
||||
bullet.AddComponent(&component.PositionComponent{
|
||||
X: x,
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"code.rocketnine.space/tslocum/gohan/examples/twinstick/component"
|
||||
)
|
||||
|
||||
func NewPlayer() gohan.EntityID {
|
||||
player := gohan.NextEntityID()
|
||||
func NewPlayer() gohan.Entity {
|
||||
player := gohan.NewEntity()
|
||||
|
||||
// Set position to -1,-1 to indicate the player has not been assigned a
|
||||
// position yet. We will place the player in the center of the screen when
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
type game struct {
|
||||
w, h int
|
||||
|
||||
player gohan.EntityID
|
||||
player gohan.Entity
|
||||
|
||||
op *ebiten.DrawImageOptions
|
||||
|
||||
|
@ -62,7 +62,7 @@ func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||
g.w, g.h = w, h
|
||||
g.movementSystem.ScreenW, g.movementSystem.ScreenH = float64(w), float64(h)
|
||||
|
||||
position := g.player.Component(component.PositionComponentID).(*component.PositionComponent)
|
||||
position := component.Position(g.player)
|
||||
if position.X == -1 && position.Y == -1 {
|
||||
position.X, position.Y = float64(g.w)/2-16, float64(g.h)/2-16
|
||||
}
|
||||
|
|
|
@ -20,19 +20,19 @@ func NewDrawBulletsSystem() *DrawBulletsSystem {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *DrawBulletsSystem) Matches(entity gohan.EntityID) bool {
|
||||
func (s *DrawBulletsSystem) Matches(entity gohan.Entity) bool {
|
||||
position := entity.Component(component.PositionComponentID)
|
||||
bullet := entity.Component(component.BulletComponentID)
|
||||
|
||||
return position != nil && bullet != nil
|
||||
}
|
||||
|
||||
func (s *DrawBulletsSystem) Update(_ gohan.EntityID) error {
|
||||
func (s *DrawBulletsSystem) Update(_ gohan.Entity) error {
|
||||
return gohan.ErrSystemWithoutUpdate
|
||||
}
|
||||
|
||||
func (s *DrawBulletsSystem) Draw(entity gohan.EntityID, screen *ebiten.Image) error {
|
||||
position := entity.Component(component.PositionComponentID).(*component.PositionComponent)
|
||||
func (s *DrawBulletsSystem) Draw(entity gohan.Entity, screen *ebiten.Image) error {
|
||||
position := component.Position(entity)
|
||||
|
||||
s.op.GeoM.Reset()
|
||||
s.op.GeoM.Translate(-16, -16)
|
||||
|
|
|
@ -11,27 +11,27 @@ import (
|
|||
)
|
||||
|
||||
type drawPlayerSystem struct {
|
||||
player gohan.EntityID
|
||||
player gohan.Entity
|
||||
op *ebiten.DrawImageOptions
|
||||
}
|
||||
|
||||
func NewDrawPlayerSystem(player gohan.EntityID) *drawPlayerSystem {
|
||||
func NewDrawPlayerSystem(player gohan.Entity) *drawPlayerSystem {
|
||||
return &drawPlayerSystem{
|
||||
player: player,
|
||||
op: &ebiten.DrawImageOptions{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *drawPlayerSystem) Matches(entity gohan.EntityID) bool {
|
||||
func (s *drawPlayerSystem) Matches(entity gohan.Entity) bool {
|
||||
return entity == s.player
|
||||
}
|
||||
|
||||
func (s *drawPlayerSystem) Update(_ gohan.EntityID) error {
|
||||
func (s *drawPlayerSystem) Update(_ gohan.Entity) error {
|
||||
return gohan.ErrSystemWithoutUpdate
|
||||
}
|
||||
|
||||
func (s *drawPlayerSystem) Draw(entity gohan.EntityID, screen *ebiten.Image) error {
|
||||
position := entity.Component(component.PositionComponentID).(*component.PositionComponent)
|
||||
func (s *drawPlayerSystem) Draw(entity gohan.Entity, screen *ebiten.Image) error {
|
||||
position := component.Position(entity)
|
||||
|
||||
s.op.GeoM.Reset()
|
||||
s.op.GeoM.Translate(position.X-16, position.Y-16)
|
||||
|
|
|
@ -18,16 +18,16 @@ func angle(x1, y1, x2, y2 float64) float64 {
|
|||
}
|
||||
|
||||
type fireInputSystem struct {
|
||||
player gohan.EntityID
|
||||
player gohan.Entity
|
||||
}
|
||||
|
||||
func NewFireInputSystem(player gohan.EntityID) *fireInputSystem {
|
||||
func NewFireInputSystem(player gohan.Entity) *fireInputSystem {
|
||||
return &fireInputSystem{
|
||||
player: player,
|
||||
}
|
||||
}
|
||||
|
||||
func (_ *fireInputSystem) Matches(e gohan.EntityID) bool {
|
||||
func (_ *fireInputSystem) Matches(e gohan.Entity) bool {
|
||||
weapon := e.Component(component.WeaponComponentID)
|
||||
|
||||
return weapon != nil
|
||||
|
@ -48,14 +48,14 @@ func (s *fireInputSystem) fire(weapon *component.WeaponComponent, position *comp
|
|||
_ = bullet
|
||||
}
|
||||
|
||||
func (s *fireInputSystem) Update(_ gohan.EntityID) error {
|
||||
weapon := s.player.Component(component.WeaponComponentID).(*component.WeaponComponent)
|
||||
func (s *fireInputSystem) Update(_ gohan.Entity) error {
|
||||
weapon := component.Weapon(s.player)
|
||||
|
||||
if weapon.Ammo <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
position := s.player.Component(component.PositionComponentID).(*component.PositionComponent)
|
||||
position := component.Position(s.player)
|
||||
|
||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
cursorX, cursorY := ebiten.CursorPosition()
|
||||
|
@ -99,6 +99,6 @@ func (s *fireInputSystem) Update(_ gohan.EntityID) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (_ *fireInputSystem) Draw(_ gohan.EntityID, _ *ebiten.Image) error {
|
||||
func (_ *fireInputSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -10,21 +10,21 @@ import (
|
|||
)
|
||||
|
||||
type movementInputSystem struct {
|
||||
player gohan.EntityID
|
||||
player gohan.Entity
|
||||
}
|
||||
|
||||
func NewMovementInputSystem(player gohan.EntityID) *movementInputSystem {
|
||||
func NewMovementInputSystem(player gohan.Entity) *movementInputSystem {
|
||||
return &movementInputSystem{
|
||||
player: player,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *movementInputSystem) Matches(e gohan.EntityID) bool {
|
||||
func (s *movementInputSystem) Matches(e gohan.Entity) bool {
|
||||
return e == s.player
|
||||
}
|
||||
|
||||
func (s *movementInputSystem) Update(e gohan.EntityID) error {
|
||||
velocity := s.player.Component(component.VelocityComponentID).(*component.VelocityComponent)
|
||||
func (s *movementInputSystem) Update(e gohan.Entity) error {
|
||||
velocity := component.Velocity(s.player)
|
||||
if ebiten.IsKeyPressed(ebiten.KeyA) {
|
||||
velocity.X -= 0.5
|
||||
if velocity.X < -5 {
|
||||
|
@ -52,6 +52,6 @@ func (s *movementInputSystem) Update(e gohan.EntityID) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *movementInputSystem) Draw(_ gohan.EntityID, _ *ebiten.Image) error {
|
||||
func (s *movementInputSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -15,21 +15,21 @@ import (
|
|||
)
|
||||
|
||||
type profileSystem struct {
|
||||
player gohan.EntityID
|
||||
player gohan.Entity
|
||||
cpuProfile *os.File
|
||||
}
|
||||
|
||||
func NewProfileSystem(player gohan.EntityID) *profileSystem {
|
||||
func NewProfileSystem(player gohan.Entity) *profileSystem {
|
||||
return &profileSystem{
|
||||
player: player,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *profileSystem) Matches(e gohan.EntityID) bool {
|
||||
func (s *profileSystem) Matches(e gohan.Entity) bool {
|
||||
return e == s.player
|
||||
}
|
||||
|
||||
func (s *profileSystem) Update(e gohan.EntityID) error {
|
||||
func (s *profileSystem) Update(e gohan.Entity) error {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyP) {
|
||||
if s.cpuProfile == nil {
|
||||
log.Println("CPU profiling started...")
|
||||
|
@ -60,6 +60,6 @@ func (s *profileSystem) Update(e gohan.EntityID) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *profileSystem) Draw(_ gohan.EntityID, _ *ebiten.Image) error {
|
||||
func (s *profileSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -13,23 +13,23 @@ type MovementSystem struct {
|
|||
ScreenW, ScreenH float64
|
||||
}
|
||||
|
||||
func (_ *MovementSystem) Matches(entity gohan.EntityID) bool {
|
||||
position := entity.Component(component.PositionComponentID)
|
||||
velocity := entity.Component(component.VelocityComponentID)
|
||||
func (_ *MovementSystem) Matches(entity gohan.Entity) bool {
|
||||
position := component.Position(entity)
|
||||
velocity := component.Velocity(entity)
|
||||
|
||||
return position != nil && velocity != nil
|
||||
}
|
||||
|
||||
func (s *MovementSystem) Update(entity gohan.EntityID) error {
|
||||
position := entity.Component(component.PositionComponentID).(*component.PositionComponent)
|
||||
velocity := entity.Component(component.VelocityComponentID).(*component.VelocityComponent)
|
||||
func (s *MovementSystem) Update(entity gohan.Entity) error {
|
||||
position := component.Position(entity)
|
||||
velocity := component.Velocity(entity)
|
||||
|
||||
bullet := entity.Component(component.BulletComponentID)
|
||||
|
||||
// Check for collision.
|
||||
if position.X+velocity.X < 16 {
|
||||
if bullet != nil {
|
||||
gohan.RemoveEntity(entity)
|
||||
entity.Remove()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ func (s *MovementSystem) Update(entity gohan.EntityID) error {
|
|||
velocity.X = 0
|
||||
} else if position.X+velocity.X > s.ScreenW-16 {
|
||||
if bullet != nil {
|
||||
gohan.RemoveEntity(entity)
|
||||
entity.Remove()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func (s *MovementSystem) Update(entity gohan.EntityID) error {
|
|||
}
|
||||
if position.Y+velocity.Y < 16 {
|
||||
if bullet != nil {
|
||||
gohan.RemoveEntity(entity)
|
||||
entity.Remove()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func (s *MovementSystem) Update(entity gohan.EntityID) error {
|
|||
velocity.Y = 0
|
||||
} else if position.Y+velocity.Y > s.ScreenH-16 {
|
||||
if bullet != nil {
|
||||
gohan.RemoveEntity(entity)
|
||||
entity.Remove()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,6 @@ func (s *MovementSystem) Update(entity gohan.EntityID) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (_ *MovementSystem) Draw(_ gohan.EntityID, _ *ebiten.Image) error {
|
||||
func (_ *MovementSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@ import (
|
|||
type printInfoSystem struct {
|
||||
img *ebiten.Image
|
||||
op *ebiten.DrawImageOptions
|
||||
player gohan.EntityID
|
||||
player gohan.Entity
|
||||
}
|
||||
|
||||
func NewPrintInfoSystem(player gohan.EntityID) *printInfoSystem {
|
||||
func NewPrintInfoSystem(player gohan.Entity) *printInfoSystem {
|
||||
p := &printInfoSystem{
|
||||
img: ebiten.NewImage(200, 100),
|
||||
op: &ebiten.DrawImageOptions{},
|
||||
|
@ -27,15 +27,15 @@ func NewPrintInfoSystem(player gohan.EntityID) *printInfoSystem {
|
|||
return p
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Matches(e gohan.EntityID) bool {
|
||||
func (s *printInfoSystem) Matches(e gohan.Entity) bool {
|
||||
return e == s.player
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Update(_ gohan.EntityID) error {
|
||||
func (s *printInfoSystem) Update(_ gohan.Entity) error {
|
||||
return gohan.ErrSystemWithoutUpdate
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Draw(_ gohan.EntityID, screen *ebiten.Image) error {
|
||||
func (s *printInfoSystem) Draw(_ gohan.Entity, screen *ebiten.Image) error {
|
||||
s.img.Clear()
|
||||
ebitenutil.DebugPrint(s.img, fmt.Sprintf("KEY WASD+MOUSE\nENT %d\nUPD %d\nDRA %d\nTPS %0.0f\nFPS %0.0f", gohan.ActiveEntities(), gohan.UpdatedEntities(), gohan.DrawnEntities(), ebiten.CurrentTPS(), ebiten.CurrentFPS()))
|
||||
screen.DrawImage(s.img, s.op)
|
||||
|
|
31
gohan.go
31
gohan.go
|
@ -12,15 +12,19 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
gameComponents = make(map[EntityID]map[ComponentID]interface{})
|
||||
gameComponents [][]interface{}
|
||||
|
||||
allEntities []EntityID
|
||||
allEntities []Entity
|
||||
|
||||
modifiedEntities []EntityID
|
||||
removedEntities []EntityID
|
||||
modifiedEntities []Entity
|
||||
removedEntities []Entity
|
||||
|
||||
// availableEntityIDs is the set of EntityIDs available because they were
|
||||
// removed from the game.
|
||||
availableEntityIDs []Entity
|
||||
|
||||
gameSystems []System
|
||||
gameSystemEntities [][]EntityID // Slice of entities matching each system.
|
||||
gameSystemEntities [][]Entity // Slice of entities matching each system.
|
||||
|
||||
gameSystemReceivesUpdate []bool
|
||||
gameSystemReceivesDraw []bool
|
||||
|
@ -39,6 +43,9 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
// Pad slices to match IDs starting with 1.
|
||||
gameComponents = append(gameComponents, nil)
|
||||
|
||||
debugEnv := os.Getenv("GOHAN_DEBUG")
|
||||
debugEnv = strings.TrimSpace(debugEnv)
|
||||
debugEnv = strings.ToLower(debugEnv)
|
||||
|
@ -59,7 +66,7 @@ func attachEntitiesToSystem(system System) {
|
|||
// This function is always called on a newly added system.
|
||||
systemID := len(gameSystemEntities) - 1
|
||||
|
||||
for entity := EntityID(0); entity < nextEntityID; entity++ {
|
||||
for entity := Entity(0); entity < maxEntityID; entity++ {
|
||||
if system.Matches(entity) {
|
||||
gameSystemEntities[systemID] = append(gameSystemEntities[systemID], entity)
|
||||
|
||||
|
@ -99,12 +106,9 @@ func propagateEntityChanges() {
|
|||
defer entityMutex.Unlock()
|
||||
|
||||
for _, entity := range removedEntities {
|
||||
delete(gameComponents, entity)
|
||||
|
||||
// Remove from attached systems.
|
||||
REMOVED:
|
||||
for i := range gameSystemEntities {
|
||||
delete(gameComponents, entity)
|
||||
|
||||
for j, e := range gameSystemEntities[i] {
|
||||
if e == entity {
|
||||
gameSystemEntities[i] = append(gameSystemEntities[i][:j], gameSystemEntities[i][j+1:]...)
|
||||
|
@ -112,7 +116,14 @@ func propagateEntityChanges() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove components.
|
||||
gameComponents[entity] = make([]interface{}, maxComponentID+1)
|
||||
}
|
||||
|
||||
// Mark EntityIDs as available.
|
||||
availableEntityIDs = append(availableEntityIDs, removedEntities...)
|
||||
|
||||
removedEntities = nil
|
||||
|
||||
for _, entity := range modifiedEntities {
|
||||
|
|
|
@ -14,13 +14,13 @@ import (
|
|||
// See ErrSystemWithoutUpdate and ErrSystemWithoutDraw.
|
||||
type System interface {
|
||||
// Matches returns whether the provided entity is handled by this system.
|
||||
Matches(entity EntityID) bool
|
||||
Matches(entity Entity) bool
|
||||
|
||||
// Update is called once for each matching entity each time the game state is updated.
|
||||
Update(entity EntityID) error
|
||||
Update(entity Entity) error
|
||||
|
||||
// Draw is called once for each matching entity each time the game is drawn to the screen.
|
||||
Draw(entity EntityID, screen *ebiten.Image) error
|
||||
Draw(entity Entity, screen *ebiten.Image) error
|
||||
}
|
||||
|
||||
// Special error values.
|
||||
|
|
Loading…
Reference in New Issue