A graphics/audio/ECS library for arbitrary rendering of things. Can be used for data visualization, games, and other things.
The following language(s) & libraries are requried to be installed on the host machine/container.
- Go
1.15(or higher) - SDL2
- SDL2 Image
- SDL2 Mixer
- SDL2 TTF
- SDL2 GFX
-
Clone the repo
$ git clone https://github.com/jordanbrauer/hallucinator.git
-
Install Go package dependencies
$ go get -v \ github.com/willf/bitset \ github.com/veandco/go-sdl2/sdl \ github.com/veandco/go-sdl2/img \ github.com/veandco/go-sdl2/mix \ github.com/veandco/go-sdl2/ttf -
Begin hacking!
Below describes the usage of the library, and how you can use it to lay the foundation to do whatever you want!
Here is the "Hello World!" Hallucinator program. You can use this as a skeleton or jumping off point.
package main
import (
"fmt"
"time"
"github.com/jordanbrauer/hallucinator/pkg/ecs"
"github.com/jordanbrauer/hallucinator/pkg/engine"
)
func init() {
engine.Init("Hello World!", 1200, 1024)
engine.Debug(true)
engine.Setup(func(world ecs.World) bool {
return true
})
engine.Teardown(func(world ecs.World) bool {
return true
})
}
func main() {
engine.Run(func(world ecs.World) bool {
fmt.Println("Hello World!")
time.Sleep(3 * time.Second)
return false
})
}Executing it like so,
$ go run main.goshould yield you the following output
Finished initializing subsystems
Hello World!
Cleaning up resources...
Done!
Initializing a new program is simple!
-
In your
main.gofile (or whatever entrypoint you have), import the necessary packagesimport ( "github.com/jordanbrauer/hallucinator/pkg/ecs" "github.com/jordanbrauer/hallucinator/pkg/engine" "github.com/veandco/go-sdl2/sdl" )
-
Next, define your
initfunction and initialize the enginefunc init() { engine.Init("My Window Title", 800, 800) engine.Debug(true) engine.Setup(func(world ecs.World) bool { // create entities, register systems and components, and attach entities to components! return true }) engine.Teardown(func(world ecs.World) bool { // destroy entities, close resources, and generally clean up anything before the program exits return true }) }
-
Finally, define your
mainfunction and exectue the primary engine routinefunc main() { engine.Run(func(world ecs.World) bool { // listen for events such as user input, update systems // return boolean – true to continue, false to halt return true }) }
world.CreateEntity()Components implement a simple interface with a single method – Name.
type Component interface {
Name() string
}-
Define a new struct that contains public fields that can be manipulated by systems
type MyComponent struct { SomeValue int32 AnotherValue int32 }
-
Implement the
Namemethod, and return some unique identifier (or "tag") for the component.func (MyComponent) Name() string { return "myComponent" }
-
That's it! Import and use where needed.
The interface for systems is unfortunately a bit more complex. However, much of the complexity is abstracted away by adding the built-in SystemAccess struct to your system's type definition.
type System interface {
Update(dt float32)
Updates(world World)
Unsubscribe(entity Entity)
Subscribe(entity Entity)
Subscribed(entity Entity) bool
Name() string
}-
Define your type that represents the system, and add the built-in
SystemAccesstypetype MySystem struct { ecs.SystemAccess }
-
Define the
Namemethod, returning a unique identifier for your systemfunc (MySystem) Name() string { return "mySystem" }
-
Define the
Updatemethod, where you will loop the world entities and mutate their componentsfunc (system *MySystem) Update(dt float32) { // loop through entities and update components }
-
You're done! The system is now ready to have logic added to it.
Within your system's Update method, you can make use of the following pattern to update entity components.
- Loop over the entire range of the system's entities
- Fetch necessary/needed components on the current entity to be mutated by the system
- Make sure that you use the time delta to modulate your calculations!
for _, entity := range system.Entities() {
var myComponent = system.Component(entity, MyComponent{}.Name()).(*MyComponent)
myComponent.SomeValue += 1 // add 1 every update call
myComponent.AnotherValue += 2 // add 2 every update call
}world.RegisterComponent(MyComponent{}.Name())world.RegisterSystem(new(MySystem), MyComponent{}.Name())You can register many components to a system!
world.RegisterSystem(new(MySystem), MyComponent{}.Name(), AnotherComponent{}.Name())entity = world.CreateEntity()
world.AttachComponent(entity, new(MyComponent))This should be done from within the engine.Run closure defined in the main entry point!
world.Update(MySystem{}.Name(), dt) // note that `dt` is received as argument in the closureThe following components are included as a set of "batteries included". They are not required to be used, but they offer some basic and common types used for graphical programs (e.g., games).
VectorFloat32AccelerationColourDimensionsGravityPositionRigidBodyRotationTransform