All Projects → harmboschloo → elm-ecs

harmboschloo / elm-ecs

Licence: BSD-3-Clause license
Using the entity-component-system (ECS) pattern in elm.

Programming Languages

elm
856 projects

Projects that are alternatives of or similar to elm-ecs

ecs
Build your own Game-Engine based on the Entity Component System concept in Golang.
Stars: ✭ 68 (+119.35%)
Mutual labels:  entity-component-system
artemis-odb-entity-tracker
🎲 Visual Entity Tracker for ECS library: artemis-odb
Stars: ✭ 27 (-12.9%)
Mutual labels:  entity-component-system
harmony-ecs
A small archetypal ECS focused on compatibility and performance
Stars: ✭ 33 (+6.45%)
Mutual labels:  entity-component-system
imgui entt entity editor
A drop-in entity editor for EnTT with Dear ImGui
Stars: ✭ 146 (+370.97%)
Mutual labels:  entity-component-system
entropy
Framework for making games and not only games in entity system manner.
Stars: ✭ 15 (-51.61%)
Mutual labels:  entity-component-system
echo
Super lightweight Entity Component System framework for Haxe
Stars: ✭ 41 (+32.26%)
Mutual labels:  entity-component-system
rockgo
A developing game server framework,based on Entity Component System(ECS).
Stars: ✭ 617 (+1890.32%)
Mutual labels:  entity-component-system
ecs
A dependency free, lightweight, fast Entity-Component System (ECS) implementation in Swift
Stars: ✭ 79 (+154.84%)
Mutual labels:  entity-component-system
ECS-Phyllotaxis
Learning ECS - 100k Cubes in Phyllotaxis pattern
Stars: ✭ 17 (-45.16%)
Mutual labels:  entity-component-system
UGUIDOTS
Converting UGUI to be DOTS compliant
Stars: ✭ 105 (+238.71%)
Mutual labels:  entity-component-system
ash
A Typescript port of Ash Framework - https://github.com/richardlord/Ash - an Actionscript 3 entity framework for game development
Stars: ✭ 19 (-38.71%)
Mutual labels:  entity-component-system
gdk-for-unity-blank-project
SpatialOS GDK for Unity Blank Project
Stars: ✭ 33 (+6.45%)
Mutual labels:  entity-component-system
apecs
A petite entity component system
Stars: ✭ 17 (-45.16%)
Mutual labels:  entity-component-system
ECS
Simple implement of ECS on C++
Stars: ✭ 13 (-58.06%)
Mutual labels:  entity-component-system
Entitas-Redux
An entity-component framework for Unity with code generation and visual debugging
Stars: ✭ 84 (+170.97%)
Mutual labels:  entity-component-system
RASM
3D Ray-Tracing WebGPU Game Engine Written in Rust WebAssembly.
Stars: ✭ 20 (-35.48%)
Mutual labels:  entity-component-system
Learning-Unity-ECS
A bunch of small Unity projects where I explore and learn Unity's new ECS and Job System.
Stars: ✭ 60 (+93.55%)
Mutual labels:  entity-component-system
OSIS
Entity Component System with network support (for haxe)
Stars: ✭ 40 (+29.03%)
Mutual labels:  entity-component-system
ecs-benchmark
ECS benchmark comparison
Stars: ✭ 68 (+119.35%)
Mutual labels:  entity-component-system
ECSCombat
A space battle simulation, based around Unity ECS framework
Stars: ✭ 81 (+161.29%)
Mutual labels:  entity-component-system

ECS

This package provides a way to use the entity-component-system (ECS) pattern in Elm. This patterns is mainly used in games (and simulations) and is useful when you want to create highly composable game objects and minimize coupling between game logic. The ECS pattern follows these basic ideas:

  • An entity represents a generic container for components. You can think of this as a game object.
  • A component contains data. Multiple components can be associated with an entity.
  • A system contains logic and operates on entities with a specific subset of component types.

Since the module overview is a bit cluttered here are the main modules:

  • Ecs - for creating a world and managing entities, components and singletons.
  • Ecs.EntityComponents - for processing entities with a specific subset of component types.
  • Ecs.ComponentsN - for setting up a container for N component types.
  • Ecs.SingletonsN - for setting up a container for N singleton types.

If you want to dive right in, here are some example projects:

Example 1

Components

Suppose we start building a game and we want some static shapes and some moving shapes. We might define some data types like this:

type alias Position =
    { x : Float
    , y : Float
    }


type alias Velocity =
    { velocityX : Float
    , velocityY : Float
    }


type alias Shape =
    { width : Float
    , height : Float
    , color : String
    }

These three data types will be our ECS component types. To associate a component with an entity we will use an entity id. That is actually all an entity is. It is nothing more than an id that represents a game object. Here we use an Int type but it can be any comparable type.

type alias EntityId =
    Int

Since we have three component types we will be using the Ecs.Components3 module. Now we can define our components container type that we will use for our game world:

type alias Components =
    Ecs.Components3.Components3 EntityId Position Velocity Shape

Specs

Before we can create our game world and insert our entities and components we need to set up some specs. The Ecs module needs these specs to know how to retrieve and update components. First we create a record type for all our specs and then we initialize it with the Ecs.Components3.specs function using the record constructor.

type alias Specs =
    { all : AllComponentsSpec
    , position : ComponentSpec Position
    , velocity : ComponentSpec Velocity
    , shape : ComponentSpec Shape
    }


type alias AllComponentsSpec =
    Ecs.AllComponentsSpec EntityId Components


type alias ComponentSpec a =
    Ecs.ComponentSpec EntityId a Components


specs : Specs
specs =
    Ecs.Components3.specs Specs

World

Now we can define our world which will hold all our components:

type alias World =
    Ecs.World EntityId Components ()


emptyWorld : World
emptyWorld =
    Ecs.emptyWorld specs.all ()

Note: The () is a empty tuple which specifies that we are not using singletons here. Below we will discuss singletons in more detail.

For our game lets create three entities. Note that the first entity (0) has a position and shape component while the second and third entities (1 and 2) also have a velocity component. This effectively makes entity 0 static and will cause 1 and 2 to move around. More on that below.

initEntities : World -> World
initEntities world =
    world
        -- entity id 0, static red shape
        |> Ecs.insertEntity 0
        |> Ecs.insertComponent specs.position
            { x = 20
            , y = 20
            }
        |> Ecs.insertComponent specs.shape
            { width = 20
            , height = 15
            , color = "red"
            }
        -- entity id 1, moving green shape
        |> Ecs.insertEntity 1
        |> Ecs.insertComponent specs.position
            { x = 30
            , y = 75
            }
        |> Ecs.insertComponent specs.velocity
            { velocityX = 4
            , velocityY = -1
            }
        |> Ecs.insertComponent specs.shape
            { width = 15
            , height = 20
            , color = "green"
            }
        -- entity id 2, moving blue shape
        |> Ecs.insertEntity 2
        |> Ecs.insertComponent specs.position
            { x = 70
            , y = 30
            }
        |> Ecs.insertComponent specs.velocity
            { velocityX = -5
            , velocityY = -5
            }
        |> Ecs.insertComponent specs.shape
            { width = 15
            , height = 15
            , color = "blue"
            }

Moving

Entities 1 and 2 have a velocity component. Now we have to make sure their position gets updated according to their velocity. For this we create the function below. In ECS terms you could call this a system. The function only operates on entities which have a specific subset of component types. In this case only entities which have both a position and a velocity component. This package does not have explicit concept of an ECS system. Instead it provides functionality to do operations on entities with a subset of component types in the Ecs.EntityComponents module. We use the specs defined previously to specify which component types we want to include. To update a component for an entity we simple insert a new component, overriding the old component.

updatePositions : Float -> World -> World
updatePositions deltaSeconds world =
    Ecs.EntityComponents.processFromLeft2
        specs.velocity
        specs.position
        (updateEntityPosition deltaSeconds)
        world


updateEntityPosition : Float -> EntityId -> Velocity -> Position -> World -> World
updateEntityPosition deltaSeconds _ velocity position world =
    Ecs.insertComponent specs.position
        { x = position.x + velocity.velocityX * deltaSeconds
        , y = position.y + velocity.velocityY * deltaSeconds
        }
        world

Bounds check

We will remove an entity when a shape reaches the boundary our game. This only applies to moving shapes so we include the velocity component next to the position and shape components. Ecs.removeEntity needs specs.all here because it needs to remove all components for the entity. The entity id will also be removed from the world. This means that when you insert new components for this entity they will not be added to the world since the entity id is no longer part of the world.

checkBounds : World -> World
checkBounds world =
    Ecs.EntityComponents.processFromLeft3
        specs.shape
        specs.velocity
        specs.position
        checkEntityBounds
        world


checkEntityBounds : EntityId -> Shape -> Velocity -> Position -> World -> World
checkEntityBounds _ shape _ position world =
    if
        (position.x < 0 || (position.x + shape.width) > 100)
            || (position.y < 0 || (position.y + shape.height) > 100)
    then
        Ecs.removeEntity specs.all world

    else
        world

Finally

That covers setting up specs, creating a world, inserting and removing entities and inserting components. For more operations checkout the Ecs module. If you want to know more about how everything fits together in a program and how to do rendering please check out the full source code. You can also watch the result here.

If you want to know more about singletons and spawning entities read on.

Example 2

Components

Let's modify our first example to allow for spawning entities every frameInterval. We create a new component SpawnConfig which also includes the velocity and shape of the entity we are going to spawn. Now we are using 4 components types, so we use the Ecs.Components4 module.

type alias Components =
    Ecs.Components4.Components4 EntityId Position Velocity Shape SpawnConfig


type alias SpawnConfig =
    { frameInterval : Int
    , velocity : Velocity
    , shape : Shape
    }

Singletons

Now that we are going to be spawning entities dynamically we can not hard-code the id of the entity anymore. We have to keep track of it in another way. Here we will use a singleton EntityId. We also want to keep track of the number of frames rendered, so we also add a Int singleton counter. There are two singletons to we use the Ecs.Singletons2 module.

Note: Singletons are optional, they are not required when using this package. You can also just keep this data in your model next to the ecs world just like you do in any Elm program. Singletons where added to the package for convenience and to create a consistent way to deal with data (singletons and components).

type alias Singletons =
    Ecs.Singletons2.Singletons2 EntityId Int

Specs

We will need to add the new component and singleton types to our specs.

type alias Specs =
    { allComponents : AllComponentsSpec
    , position : ComponentSpec Position
    , velocity : ComponentSpec Velocity
    , shape : ComponentSpec Shape
    , spawnConfig : ComponentSpec SpawnConfig
    , nextEntityId : SingletonSpec EntityId
    , frameCount : SingletonSpec Int
    }


type alias SingletonSpec a =
    Ecs.SingletonSpec a Singletons


specs : Specs
specs =
    Specs |> Ecs.Components4.specs |> Ecs.Singletons2.specs

World

Our world will now look like this. You have to provide an initial value for every singleton when creating the world.

type alias World =
    Ecs.World EntityId Components Singletons


emptyWorld : World
emptyWorld =
    Ecs.emptyWorld specs.allComponents (Ecs.Singletons2.init 0 0)

To create new entities we are going to add a newEntity function which is going to insert a new entity using the nextEntityId singleton value. And then the function updates the singleton value for the next new entity to be created.

newEntity : World -> World
newEntity world =
    world
        |> Ecs.insertEntity (Ecs.getSingleton specs.nextEntityId world)
        |> Ecs.updateSingleton specs.nextEntityId (\id -> id + 1)

Now let's create our initial entities. We have one static shape as before, but instead of the moving shapes we create two entity spawners. The spawners have a position and contain data about the shape and velocity of the entities they should spawn.

initEntities : World -> World
initEntities world =
    world
        -- entity id 0, static red shape
        |> newEntity
        |> Ecs.insertComponent specs.position
            { x = 20
            , y = 20
            }
        |> Ecs.insertComponent specs.shape
            { width = 20
            , height = 15
            , color = "red"
            }
        -- entity id 1, spawner for moving green shapes
        |> newEntity
        |> Ecs.insertComponent specs.position
            { x = 30
            , y = 75
            }
        |> Ecs.insertComponent specs.spawnConfig
            { frameInterval = 120
            , velocity =
                { velocityX = 4
                , velocityY = -1
                }
            , shape =
                { width = 15
                , height = 20
                , color = "green"
                }
            }
        -- entity id 2, spawner for moving blue shapes
        |> newEntity
        |> Ecs.insertComponent specs.position
            { x = 70
            , y = 30
            }
        |> Ecs.insertComponent specs.spawnConfig
            { frameInterval = 60
            , velocity =
                { velocityX = -5
                , velocityY = -5
                }
            , shape =
                { width = 15
                , height = 15
                , color = "blue"
                }
            }

Counting frames

We already saw how the nextEntityId singleton gets updated. The frameCount singleton is even simpler, it just increments the value. This function is called every render frame.

updateFrameCount : World -> World
updateFrameCount world =
    Ecs.updateSingleton specs.frameCount (\frameCount -> frameCount + 1) world

Spawning entities

Now let's spawn some entities. Every entity with a SpawnConfig and Position component will effectively be a spawner. First we get the current frame count and check if it matches the frame interval defined in the spawn config. If it matches we insert a new entity with the position of the spawner and the velocity and shape in the spawn config. If the frame interval does not match the current frame count we do nothing.

spawnEntities : World -> World
spawnEntities world =
    Ecs.EntityComponents.processFromLeft2
        specs.spawnConfig
        specs.position
        spawnEntity
        world


spawnEntity : EntityId -> SpawnConfig -> Position -> World -> World
spawnEntity _ config position world =
    let
        frameCount =
            Ecs.getSingleton specs.frameCount world
    in
    if remainderBy config.frameInterval frameCount == 0 then
        world
            |> newEntity
            |> Ecs.insertComponent specs.position position
            |> Ecs.insertComponent specs.velocity config.velocity
            |> Ecs.insertComponent specs.shape config.shape

    else
        world

Finally

That covers setting up singletons and spawning entities. Check out the full source code and the result.

There is also a playful orbits demo which includes user interaction and randomness (source code).

Misc

Originally inspired by Slime, Ash, Mogee and others.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].