{"id":15014591,"url":"https://github.com/keyslam-group/concord","last_synced_at":"2025-04-09T15:08:25.904Z","repository":{"id":37564650,"uuid":"118922021","full_name":"Keyslam-Group/Concord","owner":"Keyslam-Group","description":"A feature-complete ECS library","archived":false,"fork":false,"pushed_at":"2024-06-30T08:45:46.000Z","size":272,"stargazers_count":261,"open_issues_count":9,"forks_count":27,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-09T15:08:18.719Z","etag":null,"topics":["ecs","game-development","love2d","lua"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Keyslam-Group.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-25T14:19:35.000Z","updated_at":"2025-04-08T03:56:45.000Z","dependencies_parsed_at":"2023-01-19T18:32:53.920Z","dependency_job_id":"dd1f4e9d-61fe-4991-add0-c8249cdedc60","html_url":"https://github.com/Keyslam-Group/Concord","commit_stats":null,"previous_names":["tjakka5/concord"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keyslam-Group%2FConcord","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keyslam-Group%2FConcord/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keyslam-Group%2FConcord/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keyslam-Group%2FConcord/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Keyslam-Group","download_url":"https://codeload.github.com/Keyslam-Group/Concord/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055283,"owners_count":21040157,"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":["ecs","game-development","love2d","lua"],"created_at":"2024-09-24T19:45:48.951Z","updated_at":"2025-04-09T15:08:25.883Z","avatar_url":"https://github.com/Keyslam-Group.png","language":"Lua","readme":"# Concord\n\nConcord is a feature complete ECS for LÖVE.\nIt's main focus is performance and ease of use.\nWith Concord it is possibile to easily write fast and clean code. \n\nThis readme will explain how to use Concord.\n\nAdditionally all of Concord is documented using the LDoc format.\nAuto generated docs for Concord can be found in `docs` folder, or on the [GitHub page](https://keyslam-group.github.io/Concord/).\n\n--- \n\n## Table of Contents  \n[Installation](#installation)  \n[ECS](#ecs)  \n[API](#api) :\n- [Components](#components)  \n- [Entities](#entities)  \n- [Systems](#systems)\n- [Worlds](#worlds)\n- [Assemblages](#assemblages)\n  \n[Quick Example](#quick-example)  \n[Contributors](#contributors)  \n[License](#licence)\n\n---\n\n## Installation\nDownload the repository and copy the 'concord' folder into your project. Then require it in your project like so: \n```lua\nlocal Concord = require(\"path.to.concord\")\n```\n\nConcord has a bunch of modules. These can be accessed through Concord:\n\n```lua\n-- Modules\nlocal Entity     = Concord.entity\nlocal Component  = Concord.component\nlocal System     = Concord.system\nlocal World      = Concord.world\n\n-- Containers\nlocal Components  = Concord.components\n```\n\n---\n\n## ECS\nConcord is an Entity Component System (ECS for short) library.\nThis is a coding paradigm where _composition_ is used over _inheritance_.\nBecause of this it is easier to write more modular code. It often allows you to combine any form of behaviour for the objects in your game (Entities).\n\nAs the name might suggest, ECS consists of 3 core things: Entities, Components, and Systems. A proper understanding of these is required to use Concord effectively.\nWe'll start with the simplest one.\n\n### Components\nComponents are pure raw data. In Concord this is just a table with some fields.\nA position component might look like \n`{ x = 100, y = 50}`, whereas a health Component might look like `{ currentHealth = 10, maxHealth = 100 }`.\nWhat is most important is that Components are data and nothing more. They have 0 functionality.\n\n### Entities\nEntities are the actual objects in your game. Like a player, an enemy, a crate, or a bullet.\nEvery Entity has it's own set of Components, with their own values.\n\nA crate might have the following components (Note: Not actual Concord syntax):\n```lua\n{\n    position = { x = 100, y = 200 },\n    texture  = { path = \"crate.png\", image = Image },\n    pushable = { },\n}\n```\n\nWhereas a player might have the following components:\n```lua\n{\n    position     = { x = 200, y = 300 },\n    texture      = { path = \"player.png\", image = Image },\n    controllable = { keys = \"wasd\" },\n    health       = { currentHealth = 10, maxHealth = 100},\n}\n```\n\nAny Component can be given to any Entity (once). Which Components an Entity has will determine how it behaves. This is done through the last thing...\n\n### Systems\nSystems are the things that actually _do_ stuff. They contain all your fancy algorithms and cool game logic.\nEach System will do one specific task like say, drawing Entities.\nFor this they will only act on Entities that have the Components needed for this: `position` and `texture`. All other Components are irrelevant.\n\nIn Concord this is done something alike this:\n\n```lua\ndrawSystem = System({pool = {position, texture}}) -- Define a System that takes all Entities with a position and texture Component\n\nfunction drawSystem:draw() -- Give it a draw function\n    for _, entity in ipairs(self.pool) do -- Iterate over all Entities that this System acts on\n        local position = entity.position -- Get the position Component of this Entity\n        local texture = entity.texture -- Get the texture Component of this Entity\n\n        -- Draw the Entity\n        love.graphics.draw(texture.image, position.x, position.y)\n    end\nend\n```\n\n### To summarize...\n- Components contain only data.\n- Entities contain any set of Components.\n- Systems act on Entities that have a required set of Components.\n\nBy creating Components and Systems you create modular behaviour that can apply to any Entity.\nWhat if we took our crate from before and gave it the `controllable` Component? It would respond to our user input of course.\n\nOr what if the enemy shot bullets with a `health` Component? It would create bullets that we'd be able to destroy by shooting them.\n\nAnd all that without writing a single extra line of code. Just reusing code that already existed and is guaranteed to be reuseable.\n\n---\n\n## API\n\n### General design\n\nConcord does a few things that might not be immediately clear. This segment should help understanding.\n\n#### Requiring files\n\nSince you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in.\n\n```lua\n-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary.\nConcord.utils.loadNamespace(\"path/to/components\")\n\nprint(Concord.components.componentName)\n\n-- Loads all files in the directory, and puts the return value in the table Systems. The key is their filename without any extension\nlocal Systems = {}\nConcord.utils.loadNamespace(\"path/to/systems\", Systems)\n\nmyWorld:addSystems(\n  Systems.healthSystem\n  Systems.damageSystem,\n  Systems.moveSystem,\n -- etc\n)\n```\n\n#### Method chaining\n```lua\n-- Most (if not all) methods will return self\n-- This allowes you to chain methods\n\nmyEntity\n:give(\"position\", 100, 50)\n:give(\"velocity\", 200, 0)\n:remove(\"position\")\n:destroy()\n\nmyWorld\n:addEntity(fooEntity)\n:addEntity(barEntity)\n:clear()\n:emit(\"test\")\n```\n\n### Components\nWhen defining a ComponentClass you need to pass in a name and usually a `populate` function. This will fill the Component with values.\n\n```lua\n-- Create the position class with a populate function\n-- The component variable is the actual Component given to an Entity\n-- The x and y variables are values we pass in when we create the Component \nConcord.component(\"position\", function(component, x, y)\n    component.x = x or 0\n    component.y = y or 0\nend)\n\n-- Create a ComponentClass without a populate function\n-- Components of this type won't have any fields.\n-- This can be useful to indiciate state.\nlocal pushableComponentClass = Concord.component(\"position\")\n```\n\n### Entities\nEntities can be freely made and be given Components. You pass the name of the ComponentClass and the values you want to pass. It will then create the Component for you.\n\nEntities can only have a maximum of one of each Component.\nEntities can not share Components.\n\n```lua\n-- Create a new Entity\nlocal myEntity = Entity() \n-- or\nlocal myEntity = Entity(myWorld) -- To add it to a world immediately ( See World )\n```\n\n```lua\n-- Give the entity the position Component defined above\n-- x will become 100. y will become 50\nmyEntity:give(\"position\", 100, 50)\n```\n\n```lua\n-- Retrieve a Component\nlocal position = myEntity.position\n\nprint(position.x, position.y) -- 100, 50\n```\n\n```lua\n-- Remove a Component\nmyEntity:remove(\"position\")\n```\n\n```lua\n-- Entity:give will override a Component if the Entity already has it\n-- Entity:ensure will only put the Component if the Entity does not already have it\n\nEntity:ensure(\"position\", 0, 0) -- Will give\n-- Position is {x = 0, y = 0}\n\nEntity:give(\"position\", 50, 50) -- Will override\n-- Position is {x = 50, y = 50}\n\nEntity:give(\"position\", 100, 100) -- Will override\n-- Position is {x = 100, y = 100}\n\nEntity:ensure(\"position\", 0, 0) -- Wont do anything\n-- Position is {x = 100, y = 100}\n```\n\n```lua\n-- Retrieve all Components\n-- WARNING: Do not modify this table. It is read-only\nlocal allComponents = myEntity:getComponents()\n\nfor ComponentClass, Component in ipairs(allComponents) do\n    -- Do stuff\nend\n```\n\n```lua\n-- Assemble the Entity ( See Assemblages )\nmyEntity:assemble(assemblageFunction, 100, true, \"foo\")\n```\n\n```lua\n-- Check if the Entity is in a world\nlocal inWorld = myEntity:inWorld()\n\n-- Get the World the Entity is in\nlocal world = myEntity:getWorld()\n```\n\n```lua\n-- Destroy the Entity\nmyEntity:destroy()\n```\n\n### Systems\n\nSystems are defined as a SystemClass. Concord will automatically create an instance of a System when it is needed.\n\nSystems get access to Entities through `pools`. They are created using a filter.\nSystems can have multiple pools.\n\n```lua\n-- Create a System\nlocal mySystemClass = Concord.system({pool = {\"position\"}}) -- Pool named 'pool' will contain all Entities with a position Component\n\n-- Create a System with multiple pools\nlocal mySystemClass = Concord.system({\n    pool = { -- This pool will be named 'pool'\n        \"position\",\n        \"velocity\",\n    },\n    secondPool = { -- This pool's name will be 'secondPool'\n        \"health\",\n        \"damageable\",\n    }\n})\n```\n\n```lua\n-- If a System has a :init function it will be called on creation\n\n-- world is the World the System was created for\nfunction mySystemClass:init(world)\n    -- Do stuff\nend\n```\n\n```lua\n-- Defining a function\nfunction mySystemClass:update(dt)\n    -- Iterate over all entities in the Pool\n    for _, e in ipairs(self.pool) do\n        -- Do something with the Components\n        e.position.x = e.position.x + e.velocity.x * dt\n        e.position.y = e.position.y + e.velocity.y * dt\n    end\n\n    -- Iterate over all entities in the second Pool\n    for _, e in ipairs(self.secondPool) do\n        -- Do something\n    end\nend\n```\n\n```lua\n-- Systems can be enabled and disabled\n-- When systems are disabled their callbacks won't be executed.\n-- Note that pools will still be updated\n-- This is mainly useful for systems that display debug information\n-- Systems are enabled by default\n\n-- Enable a System\nmySystem:setEnable(true)\n\n-- Disable a System\nmySystem:setEnable(false)\n\n-- Get enabled state\nlocal isEnabled = mySystem:isEnabled()\nprint(isEnabled) -- false\n```\n\n```lua\n-- Get the World the System is in\nlocal world = System:getWorld()\n```\n\n### Worlds\n\nWorlds are the thing your System and Entities live in.\nWith Worlds you can `:emit` a callback. All Systems with this callback will then be called.\n\nWorlds can have 1 instance of every SystemClass.\nWorlds can have any number of Entities.\n\n```lua\n-- Create World\nlocal myWorld = Concord.world()\n```\n\n```lua\n-- Add an Entity to the World\nmyWorld:addEntity(myEntity)\n\n-- Remove an Entity from the World\nmyWorld:removeEntity(myEntity)\n```\n\n```lua\n-- Add a System to the World\nmyWorld:addSystem(mySystemClass)\n\n-- Add multiple Systems to the World\nmyWorld:addSystems(moveSystemClass, renderSystemClass, controlSystemClass)\n```\n\n```lua\n-- Check if the World has a System\nlocal hasSystem = myWorld:hasSystem(mySystemClass)\n\n-- Get a System from the World\nlocal mySystem = myWorld:getSystem(mySystemClass)\n```\n\n```lua\n-- Emit an event\n\n-- This will call the 'update' function of all added Systems if they have one\n-- They will be called in the order they were added\nmyWorld:emit(\"update\", dt)\n\n-- You can emit any event with any parameters\nmyWorld:emit(\"customCallback\", 100, true, \"Hello World\")\n```\n\n```lua\n-- Remove all Entities from the World\nmyWorld:clear()\n```\n\n```lua\n-- Override-able callbacks\n\n-- Called when an Entity is added to the World\n-- e is the Entity added\nfunction myWorld:onEntityAdded(e)\n    -- Do something\nend\n\n-- Called when an Entity is removed from the World\n-- e is the Entity removed\nfunction myWorld:onEntityRemoved(e)\n    -- Do something\nend\n```\n\n### Assemblages\n\nAssemblages are functions to 'make' Entities something.\nAn important distinction is that they _append_ Components.\n\n```lua\n-- Make an Assemblage function\n-- e is the Entity being assembled.\n-- cuteness and legs are variables passed in\nfunction animal(e, cuteness, legs)\n    e\n    :give(cutenessComponentClass, cuteness)\n    :give(limbs, legs, 0) -- Variable amount of legs. 0 arm.\nend)\n\n-- Make an Assemblage that uses animal\n-- cuteness is a variables passed in\nfunction cat(e, cuteness)\n    e\n    :assemble(animal, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs.\n    :give(soundComponent, \"meow.mp3\")\nend)\n```\n\n```lua\n-- Use an Assemblage\nmyEntity:assemble(cat, 100) -- 100 cuteness\n```\n\n---\n\n## Quick Example\n```lua\nlocal Concord = require(\"concord\")\n\n-- Defining components\nConcord.component(\"position\", function(c, x, y)\n    c.x = x or 0\n    c.y = y or 0\nend)\n\nConcord.component(\"velocity\", function(c, x, y)\n    c.x = x or 0\n    c.y = y or 0\nend)\n\nlocal Drawable = Concord.component(\"drawable\")\n\n\n-- Defining Systems\nlocal MoveSystem = Concord.system({\n    pool = {\"position\", \"velocity\"}\n})\n\nfunction MoveSystem:update(dt)\n    for _, e in ipairs(self.pool) do\n        e.position.x = e.position.x + e.velocity.x * dt\n        e.position.y = e.position.y + e.velocity.y * dt\n    end\nend\n\n\nlocal DrawSystem = Concord.system({\n    pool = {\"position\", \"drawable\"}\n})\n\nfunction DrawSystem:draw()\n    for _, e in ipairs(self.pool) do\n        love.graphics.circle(\"fill\", e.position.x, e.position.y, 5)\n    end\nend\n\n\n-- Create the World\nlocal world = Concord.world()\n\n-- Add the Systems\nworld:addSystems(MoveSystem, DrawSystem)\n\n-- This Entity will be rendered on the screen, and move to the right at 100 pixels a second\nlocal entity_1 = Concord.entity(world)\n:give(\"position\", 100, 100)\n:give(\"velocity\", 100, 0)\n:give(\"drawable\")\n\n-- This Entity will be rendered on the screen, and stay at 50, 50\nlocal entity_2 = Concord.entity(world)\n:give(\"position\", 50, 50)\n:give(\"drawable\")\n\n-- This Entity does exist in the World, but since it doesn't match any System's filters it won't do anything\nlocal entity_3 = Concord.entity(world)\n:give(\"position\", 200, 200)\n\n\n-- Emit the events\nfunction love.update(dt)\n    world:emit(\"update\", dt)\nend\n\nfunction love.draw()\n    world:emit(\"draw\")\nend\n```\n\n---\n\n## Contributors\n- __Positive07__: Constant support and a good rubberduck\n- __Brbl__: Early testing and issue reporting\n- __Josh__: Squashed a few bugs and generated docs\n- __Erasio__: I took inspiration from HooECS. He also introduced me to ECS\n- __Speak__: Lots of testing for new features of Concord\n- __Tesselode__: Brainstorming and helpful support\n\n---\n\n## License\nMIT Licensed - Copyright Justin van der Leij (Tjakka5)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeyslam-group%2Fconcord","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeyslam-group%2Fconcord","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeyslam-group%2Fconcord/lists"}