{"id":13561680,"url":"https://github.com/harmboschloo/elm-ecs","last_synced_at":"2025-03-17T15:31:17.635Z","repository":{"id":57675114,"uuid":"150308015","full_name":"harmboschloo/elm-ecs","owner":"harmboschloo","description":"Using the entity-component-system (ECS) pattern in elm.","archived":false,"fork":false,"pushed_at":"2019-05-16T17:18:28.000Z","size":1744,"stargazers_count":32,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-08T07:04:55.388Z","etag":null,"topics":["elm","entity-component-system","game-development"],"latest_commit_sha":null,"homepage":"https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/","language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/harmboschloo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-25T18:02:51.000Z","updated_at":"2023-05-22T18:54:20.000Z","dependencies_parsed_at":"2022-09-14T17:20:55.457Z","dependency_job_id":null,"html_url":"https://github.com/harmboschloo/elm-ecs","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harmboschloo%2Felm-ecs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harmboschloo%2Felm-ecs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harmboschloo%2Felm-ecs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harmboschloo%2Felm-ecs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harmboschloo","download_url":"https://codeload.github.com/harmboschloo/elm-ecs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221695508,"owners_count":16865232,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elm","entity-component-system","game-development"],"created_at":"2024-08-01T13:00:59.917Z","updated_at":"2024-10-27T15:11:01.776Z","avatar_url":"https://github.com/harmboschloo.png","language":"Elm","funding_links":[],"categories":["Elm"],"sub_categories":[],"readme":"# ECS\n\nThis package provides a way to use the [entity-component-system (ECS) pattern](https://en.wikipedia.org/wiki/Entity-component-system) 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:\n\n- An **entity** represents a generic container for components. You can think of this as a game object.\n- A **component** contains data. Multiple components can be associated with an entity.\n- A **system** contains logic and operates on entities with a specific subset of component types.\n\nSince the [module overview](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/) is a bit cluttered here are the main modules:\n\n- [**Ecs**](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/Ecs) - for creating a world and managing entities, components and singletons.\n- [**Ecs.EntityComponents**](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/Ecs-EntityComponents) - for processing entities with a specific subset of component types.\n- **Ecs.ComponentsN** - for setting up a container for **N** component types.\n- **Ecs.SingletonsN** - for setting up a container for **N** singleton types.\n\nIf you want to dive right in, here are some example projects:\n\n- [readme1 - example 1 below](https://harmboschloo.github.io/elm-ecs-2.0/readme1/) ([source](https://github.com/harmboschloo/elm-ecs/blob/master/examples/readme1/Main.elm))\n- [readme2 - example 2 below](https://harmboschloo.github.io/elm-ecs-2.0/readme2/) ([source](https://github.com/harmboschloo/elm-ecs/blob/master/examples/readme2/Main.elm))\n- [orbits - a playful demo](https://harmboschloo.github.io/elm-ecs-2.0/orbits/) ([source](https://github.com/harmboschloo/elm-ecs/tree/master/examples/orbits))\n\n## Example 1\n\n### Components\n\nSuppose we start building a game and we want some static shapes and some moving shapes. We might define some data types like this:\n\n```elm\ntype alias Position =\n    { x : Float\n    , y : Float\n    }\n\n\ntype alias Velocity =\n    { velocityX : Float\n    , velocityY : Float\n    }\n\n\ntype alias Shape =\n    { width : Float\n    , height : Float\n    , color : String\n    }\n```\n\nThese 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](https://faq.elm-community.org/#does-elm-have-ad-hoc-polymorphism-or-typeclasses).\n\n```elm\ntype alias EntityId =\n    Int\n```\n\nSince we have three component types we will be using the [**Ecs.Components3**](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/Ecs-Components3) module. Now we can define our components container type that we will use for our game world:\n\n```elm\ntype alias Components =\n    Ecs.Components3.Components3 EntityId Position Velocity Shape\n```\n\n### Specs\n\nBefore we can create our game world and insert our entities and components we need to set up some specs. The [**Ecs**](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/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**](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/Ecs-Components3#specs) function using the [record constructor](https://guide.elm-lang.org/types/type_aliases.html#record-constructors).\n\n```elm\ntype alias Specs =\n    { all : AllComponentsSpec\n    , position : ComponentSpec Position\n    , velocity : ComponentSpec Velocity\n    , shape : ComponentSpec Shape\n    }\n\n\ntype alias AllComponentsSpec =\n    Ecs.AllComponentsSpec EntityId Components\n\n\ntype alias ComponentSpec a =\n    Ecs.ComponentSpec EntityId a Components\n\n\nspecs : Specs\nspecs =\n    Ecs.Components3.specs Specs\n```\n\n### World\n\nNow we can define our world which will hold all our components:\n\n```elm\ntype alias World =\n    Ecs.World EntityId Components ()\n\n\nemptyWorld : World\nemptyWorld =\n    Ecs.emptyWorld specs.all ()\n```\n\n**Note:** The `()` is a [empty tuple](https://faq.elm-community.org/#what-does--mean) which specifies that we are not using singletons here. Below we will discuss singletons in more detail.\n\nFor 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.\n\n```elm\ninitEntities : World -\u003e World\ninitEntities world =\n    world\n        -- entity id 0, static red shape\n        |\u003e Ecs.insertEntity 0\n        |\u003e Ecs.insertComponent specs.position\n            { x = 20\n            , y = 20\n            }\n        |\u003e Ecs.insertComponent specs.shape\n            { width = 20\n            , height = 15\n            , color = \"red\"\n            }\n        -- entity id 1, moving green shape\n        |\u003e Ecs.insertEntity 1\n        |\u003e Ecs.insertComponent specs.position\n            { x = 30\n            , y = 75\n            }\n        |\u003e Ecs.insertComponent specs.velocity\n            { velocityX = 4\n            , velocityY = -1\n            }\n        |\u003e Ecs.insertComponent specs.shape\n            { width = 15\n            , height = 20\n            , color = \"green\"\n            }\n        -- entity id 2, moving blue shape\n        |\u003e Ecs.insertEntity 2\n        |\u003e Ecs.insertComponent specs.position\n            { x = 70\n            , y = 30\n            }\n        |\u003e Ecs.insertComponent specs.velocity\n            { velocityX = -5\n            , velocityY = -5\n            }\n        |\u003e Ecs.insertComponent specs.shape\n            { width = 15\n            , height = 15\n            , color = \"blue\"\n            }\n```\n\n### Moving\n\nEntities `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](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/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.\n\n```elm\nupdatePositions : Float -\u003e World -\u003e World\nupdatePositions deltaSeconds world =\n    Ecs.EntityComponents.processFromLeft2\n        specs.velocity\n        specs.position\n        (updateEntityPosition deltaSeconds)\n        world\n\n\nupdateEntityPosition : Float -\u003e EntityId -\u003e Velocity -\u003e Position -\u003e World -\u003e World\nupdateEntityPosition deltaSeconds _ velocity position world =\n    Ecs.insertComponent specs.position\n        { x = position.x + velocity.velocityX * deltaSeconds\n        , y = position.y + velocity.velocityY * deltaSeconds\n        }\n        world\n```\n\n### Bounds check\n\nWe 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.\n\n```elm\ncheckBounds : World -\u003e World\ncheckBounds world =\n    Ecs.EntityComponents.processFromLeft3\n        specs.shape\n        specs.velocity\n        specs.position\n        checkEntityBounds\n        world\n\n\ncheckEntityBounds : EntityId -\u003e Shape -\u003e Velocity -\u003e Position -\u003e World -\u003e World\ncheckEntityBounds _ shape _ position world =\n    if\n        (position.x \u003c 0 || (position.x + shape.width) \u003e 100)\n            || (position.y \u003c 0 || (position.y + shape.height) \u003e 100)\n    then\n        Ecs.removeEntity specs.all world\n\n    else\n        world\n```\n\n### Finally\n\nThat covers setting up specs, creating a world, inserting and removing entities and inserting components. For more operations checkout the [Ecs](https://package.elm-lang.org/packages/harmboschloo/elm-ecs/latest/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](https://github.com/harmboschloo/elm-ecs/blob/master/examples/readme1/Main.elm). You can also watch the result [here](https://harmboschloo.github.io/elm-ecs-2.0/readme1/).\n\nIf you want to know more about singletons and spawning entities read on.\n\n## Example 2\n\n### Components\n\nLet'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.\n\n```elm\ntype alias Components =\n    Ecs.Components4.Components4 EntityId Position Velocity Shape SpawnConfig\n\n\ntype alias SpawnConfig =\n    { frameInterval : Int\n    , velocity : Velocity\n    , shape : Shape\n    }\n```\n\n### Singletons\n\nNow 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.\n\n**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).\n\n```elm\ntype alias Singletons =\n    Ecs.Singletons2.Singletons2 EntityId Int\n```\n\n### Specs\n\nWe will need to add the new component and singleton types to our specs.\n\n```elm\ntype alias Specs =\n    { allComponents : AllComponentsSpec\n    , position : ComponentSpec Position\n    , velocity : ComponentSpec Velocity\n    , shape : ComponentSpec Shape\n    , spawnConfig : ComponentSpec SpawnConfig\n    , nextEntityId : SingletonSpec EntityId\n    , frameCount : SingletonSpec Int\n    }\n\n\ntype alias SingletonSpec a =\n    Ecs.SingletonSpec a Singletons\n\n\nspecs : Specs\nspecs =\n    Specs |\u003e Ecs.Components4.specs |\u003e Ecs.Singletons2.specs\n```\n\n### World\n\nOur world will now look like this. You have to provide an initial value for every singleton when creating the world.\n\n```elm\ntype alias World =\n    Ecs.World EntityId Components Singletons\n\n\nemptyWorld : World\nemptyWorld =\n    Ecs.emptyWorld specs.allComponents (Ecs.Singletons2.init 0 0)\n```\n\nTo 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.\n\n```elm\nnewEntity : World -\u003e World\nnewEntity world =\n    world\n        |\u003e Ecs.insertEntity (Ecs.getSingleton specs.nextEntityId world)\n        |\u003e Ecs.updateSingleton specs.nextEntityId (\\id -\u003e id + 1)\n```\n\nNow 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.\n\n```elm\ninitEntities : World -\u003e World\ninitEntities world =\n    world\n        -- entity id 0, static red shape\n        |\u003e newEntity\n        |\u003e Ecs.insertComponent specs.position\n            { x = 20\n            , y = 20\n            }\n        |\u003e Ecs.insertComponent specs.shape\n            { width = 20\n            , height = 15\n            , color = \"red\"\n            }\n        -- entity id 1, spawner for moving green shapes\n        |\u003e newEntity\n        |\u003e Ecs.insertComponent specs.position\n            { x = 30\n            , y = 75\n            }\n        |\u003e Ecs.insertComponent specs.spawnConfig\n            { frameInterval = 120\n            , velocity =\n                { velocityX = 4\n                , velocityY = -1\n                }\n            , shape =\n                { width = 15\n                , height = 20\n                , color = \"green\"\n                }\n            }\n        -- entity id 2, spawner for moving blue shapes\n        |\u003e newEntity\n        |\u003e Ecs.insertComponent specs.position\n            { x = 70\n            , y = 30\n            }\n        |\u003e Ecs.insertComponent specs.spawnConfig\n            { frameInterval = 60\n            , velocity =\n                { velocityX = -5\n                , velocityY = -5\n                }\n            , shape =\n                { width = 15\n                , height = 15\n                , color = \"blue\"\n                }\n            }\n```\n\n### Counting frames\n\nWe 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.\n\n```elm\nupdateFrameCount : World -\u003e World\nupdateFrameCount world =\n    Ecs.updateSingleton specs.frameCount (\\frameCount -\u003e frameCount + 1) world\n```\n\n### Spawning entities\n\nNow 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.\n\n```elm\nspawnEntities : World -\u003e World\nspawnEntities world =\n    Ecs.EntityComponents.processFromLeft2\n        specs.spawnConfig\n        specs.position\n        spawnEntity\n        world\n\n\nspawnEntity : EntityId -\u003e SpawnConfig -\u003e Position -\u003e World -\u003e World\nspawnEntity _ config position world =\n    let\n        frameCount =\n            Ecs.getSingleton specs.frameCount world\n    in\n    if remainderBy config.frameInterval frameCount == 0 then\n        world\n            |\u003e newEntity\n            |\u003e Ecs.insertComponent specs.position position\n            |\u003e Ecs.insertComponent specs.velocity config.velocity\n            |\u003e Ecs.insertComponent specs.shape config.shape\n\n    else\n        world\n```\n\n### Finally\n\nThat covers setting up singletons and spawning entities. Check out the full [source code](https://github.com/harmboschloo/elm-ecs/blob/master/examples/readme2/Main.elm) and the [result](https://harmboschloo.github.io/elm-ecs-2.0/readme2/).\n\nThere is also a playful [orbits demo](https://harmboschloo.github.io/elm-ecs-2.0/orbits/) which includes user interaction and randomness ([source code](https://github.com/harmboschloo/elm-ecs/blob/master/examples/orbits/Main.elm)).\n\n## Misc\n\nOriginally inspired by [Slime](https://package.elm-lang.org/packages/seurimas/slime/latest/Slime), [Ash](https://www.richardlord.net/ash/), [Mogee](https://github.com/w0rm/elm-mogee) and others.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharmboschloo%2Felm-ecs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharmboschloo%2Felm-ecs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharmboschloo%2Felm-ecs/lists"}