donburi

package module
v1.2.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2022 License: CC0-1.0, MIT Imports: 7 Imported by: 196

README

donburi

Donburi

Donburi is just another Entity Component System library for Ebitengine inspired by legion.

It aims to be a feature rich and high performance ECS Library.

GoDoc

Contents

Features

  • It introduces the concept of Archetype, which allows us to query entities very efficiently based on the components layout.
  • It is possible to combine And, Or, and Not conditions to perform complex queries for components.
  • It avoids reflection on every frame for performance.
  • Ability to dynamically add or remove components from an entity.
  • APIs with Go Generics

There are many features that need to be added in the future (e.g., parent-child relationship, event-notification system etc).

Examples

To check all examples, visit this page.

The bunnymark example was adapted from mizu's code, which is made by sedyh.

Installation

go get github.com/yohamta/donburi

Getting Started

Worlds

import "github.com/yohamta/donburi"

world := donburi.NewWorld()

Entities can be created via either Create (for a single entity) or CreateMany (for a collection of entities with the same component types). The world will create a unique ID for each entity upon insertion that you can use to refer to that entity later.

// Component is any struct that holds some kind of data.
type PositionData struct {
  X, Y float64
}

type VelocityData struct {
  X, Y float64
}

// ComponentType represents kind of component which is used to create or query entities.
var Position = donburi.NewComponentType[PositionData]()
var Velocity = donburi.NewComponentType[VelocityData]()

// Create an entity by specifying components that the entity will have.
// Component data will be initialized by default value of the struct.
entity = world.Create(Position, Velocity);

// You can use entity (it's a wrapper of int64) to get an Entry object from World
// which allows you to access the components that belong to the entity.
entry := world.Entry(entity)

position := donburi.Get[PositionData](entry, Position)
velocity := donburi.Get[VelocityData](entry, Velocity)
position.X += velocity.X
position.Y += velocity.y

We can define helper functions to get components for better readability. This was advice from eliasdaler.

func GetPosition(entry *donburi.Entry) *PositionData {
  return donburi.Get[PositionData](entry, Position)
}

func GetVelocity(entry *donburi.Entry) *VelocityData {
  return donburi.Get[VelocityData](entry, Velocity)
}

Components can be added and removed through Entry objects.

// Fetch the first entity with PlayerTag component
query := query.NewQuery(filter.Contains(PlayerTag))
// Query.FirstEntity() returns only the first entity that 
// matches the query.
if entry, ok := query.FirstEntity(world); ok {
  donburi.Add(entry, Position, &PositionData{
    X: 100,
    Y: 100,
  })
  donburi.Remove(entry, Velocity)
}

Entities can be removed from World with the World.Remove() as follows:

if SomeLogic.IsDead(world, someEntity) {
  // World.Remove() removes the entity from the world.
  world.Remove(someEntity)
  // Deleted entities become invalid immediately.
  if world.Valid(someEntity) == false {
    println("this entity is invalid")
  }
}

Queries

Queries allow for high performance and expressive iteration through the entities in a world, to get component references, test if an entity has a component or to add and remove components.

You can search for entities that have all of a set of components.

// You can define a query by declaring what componet you want to find.
query := query.NewQuery(filter.Contains(Position, Velocity))

// You can then iterate through the entity found in the world
query.EachEntity(world, func(entry *donburi.Entry) {
  // An entry is an accessor to entity and its components.
  position := donburi.Get[PositionData](entry, Position)
  velocity := donburi.Get[VelocityData](entry, Velocity)
  
  position.X += velocity.X
  position.Y += velocity.Y
})

There are other types of filters such as And, Or, Exact and Not. You can combine them to find the target entities.

For example:

// This query retrieves entities that have an NpcTag and no Position component.
query := query.NewQuery(filter.And(
  filter.Contains(NpcTag),
  filter.Not(filter.Contains(Position))))

If you need to determine if an entity has a component, there is entry.HasComponent

For example:

// We have a query for all entities that have Position and Size, but also any of Sprite, Text or Shape.
query := query.NewQuery(
  filter.And(
    filter.Contains(Position, Size),
    filter.Or(
      filter.Contains(Sprite),
      filter.Contains(Text),
      filter.Contains(Shape),
    )
  )
)

// In our query we can check if the entity has some of the optional components before attempting to retrieve them
query.EachEntity(world, func(entry *donburi.Entry) {
  // We'll always be able to access Position and Size
  position := donburi.Get[PositionData](entry, Position)
  size := donburi.Get[SizeData](entry, Size)
  
  
  if entry.HasComponent(Sprite) {
    sprite := donburi.Get[SpriteData](entry, Sprite)
    // .. do sprite things
  }
  
  if entry.HasComponent(Text) {
    text := donburi.Get[TextData](entry, Text)
    // .. do text things
  }
  
  if entry.HasComponent(Shape) {
    shape := donburi.Get[ShapeData](entry, Shape)
    // .. do shape things
  }
  
})

Tags

You can attach one or multiple "Tag" components to an entity. "Tag"s are just components with no data.

Here is the utility function to create a tag component.

// This is the utility function to make tag component
func NewTag() *ComponentType {
  return NewComponentType(struct{}{})
}

Since "Tags" are components, they can be used in queries in the same way as components as follows:

var EnemyTag = donburi.NewTag()
world.CreateMany(100, EnemyTag, Position, Velocity)

// Search entities with EnemyTag
query := query.NewQuery(filter.Contains(EnemyTag))
query.EachEntity(world, func(entry *donburi.Entry) {
  // Perform some operation on the Entities with the EnemyTag component.
}

Systems (Experimental)

The ECS package provides so-called System feature in ECS which can be used together with a World instance.

See GoDoc and Example.

Note: ECS package is experimental and API is unstable

Hierarchy (Experimental)

The Hierarchy package provides Parent-Children relationship function.

See GoDoc and Example.

Note: Hierarchy package is experimental and API is unstable

Internal Design for World

It is a bit complex, so please refer to the following diagram to understand it.

arch

How to contribute?

Feel free to contribute in any way you want. Share ideas, questions, submit issues, and create pull requests. Thanks!

Contributors

Made with contrib.rocks.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Null = entity.Null

Null represents a invalid entity which is zero.

Functions

func Add added in v1.1.1

func Add[T any](e *Entry, ctype *component.ComponentType, component *T)

Add adds the component to the entry.

func Get added in v1.1.1

func Get[T any](e *Entry, ctype *component.ComponentType) *T

Get returns the component from the entry

func Remove added in v1.1.1

func Remove(e *Entry, ctype *component.ComponentType)

Remove removes the component from the entry.

func Set added in v1.1.1

func Set[T any](e *Entry, ctype *component.ComponentType, component *T)

Set sets the comopnent of the entry.

func SetValue added in v1.1.1

func SetValue[T any](e *Entry, ctype *component.ComponentType, value T)

SetValue sets the value of the component.

func Valid added in v1.2.11

func Valid(e *Entry) bool

Valid returns true if the entry is valid.

Types

type ComponentType

type ComponentType = component.ComponentType

ComponentType represents a component type. It is used to add components to entities, and to filter entities based on their components. It contains a function that returns a pointer to a new component.

func NewComponentType

func NewComponentType[T any]() *ComponentType

NewComponentType creates a new component type. The function is used to create a new component of the type. It receives a function that returns a pointer to a new component.

func NewTag

func NewTag() *ComponentType

NewTag is an utility to create a tag component. Which is just an component that contains no data.

type Entity

type Entity = entity.Entity

Entity is identifier of an entity. Entity is just a wrapper of uint64.

type Entry

type Entry struct {
	World *world
	// contains filtered or unexported fields
}

Entry is a struct that contains an entity and a location in an archetype.

func (*Entry) AddComponent

func (e *Entry) AddComponent(ctype *component.ComponentType, components ...unsafe.Pointer)

AddComponent adds the component to the entity.

func (*Entry) Archetype

func (e *Entry) Archetype() *storage.Archetype

Archetype returns the archetype.

func (*Entry) Component

func (e *Entry) Component(ctype *component.ComponentType) unsafe.Pointer

Component returns the component.

func (*Entry) Entity

func (e *Entry) Entity() Entity

Entity returns the entity.

func (*Entry) HasComponent added in v1.1.0

func (e *Entry) HasComponent(componentType *component.ComponentType) bool

HasComponent returns true if the entity has the given component type.

func (*Entry) Id added in v0.0.3

func (e *Entry) Id() entity.EntityId

Id returns the entity id.

func (*Entry) Remove added in v1.2.5

func (e *Entry) Remove()

Remove removes the entity from the world.

func (*Entry) RemoveComponent

func (e *Entry) RemoveComponent(ctype *component.ComponentType)

RemoveComponent removes the component from the entity.

func (*Entry) SetComponent

func (e *Entry) SetComponent(ctype *component.ComponentType, component unsafe.Pointer)

SetComponent sets the component.

func (*Entry) String added in v1.2.9

func (e *Entry) String() string

func (*Entry) Valid added in v1.2.4

func (e *Entry) Valid() bool

Valid returns true if the entry is valid.

type StorageAccessor

type StorageAccessor struct {
	// Index is the search index for the world.
	Index *storage.Index
	// Components is the component storage for the world.
	Components *storage.Components
	// Archetypes is the archetype storage for the world.
	Archetypes []*storage.Archetype
}

StorageAccessor is an accessor for the world's storage.

type World

type World interface {
	// Id returns the unique identifier for the world.
	Id() WorldId
	// Create creates a new entity with the specified components.
	Create(components ...*component.ComponentType) Entity
	// CreateMany creates a new entity with the specified components.
	CreateMany(n int, components ...*component.ComponentType) []Entity
	// Entry returns an entry for the specified entity.
	Entry(entity Entity) *Entry
	// Remove removes the specified entity.
	Remove(entity Entity)
	// Valid returns true if the specified entity is valid.
	Valid(e Entity) bool
	// Len returns the number of entities in the world.
	Len() int
	// StorageAccessor returns an accessor for the world's storage.
	// It is used to access components and archetypes by queries.
	StorageAccessor() StorageAccessor
}

World is a collection of entities and components.

func NewWorld

func NewWorld() World

NewWorld creates a new world.

type WorldId

type WorldId int

WorldId is a unique identifier for a world.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL