{"id":13565941,"url":"https://github.com/benmoran56/esper","last_synced_at":"2026-01-30T02:32:49.203Z","repository":{"id":42077987,"uuid":"48353366","full_name":"benmoran56/esper","owner":"benmoran56","description":"An ECS (Entity Component System) for Python","archived":false,"fork":false,"pushed_at":"2026-01-24T05:27:18.000Z","size":266,"stargazers_count":668,"open_issues_count":6,"forks_count":79,"subscribers_count":29,"default_branch":"master","last_synced_at":"2026-01-24T16:42:04.501Z","etag":null,"topics":["entity-component-system","entity-system","gamedev","rogue","rougelike"],"latest_commit_sha":null,"homepage":"","language":"Python","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/benmoran56.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2015-12-21T05:50:40.000Z","updated_at":"2026-01-24T05:27:04.000Z","dependencies_parsed_at":"2023-11-15T06:26:24.502Z","dependency_job_id":"d71edc14-4192-4a32-abae-c07cc6519ad8","html_url":"https://github.com/benmoran56/esper","commit_stats":{"total_commits":245,"total_committers":24,"mean_commits":"10.208333333333334","dds":0.6408163265306123,"last_synced_commit":"f5dbf645dbc8146775c207bd143d9b5f85595c0b"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/benmoran56/esper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmoran56%2Fesper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmoran56%2Fesper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmoran56%2Fesper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmoran56%2Fesper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benmoran56","download_url":"https://codeload.github.com/benmoran56/esper/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmoran56%2Fesper/sbom","scorecard":{"id":233285,"data":{"date":"2025-08-11","repo":{"name":"github.com/benmoran56/esper","commit":"4fd30df8a92a6c7c0c0edf562342f6818b72d43a"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Maintained","score":2,"reason":"0 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":1,"reason":"Found 4/30 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/type-checking.yml:1","Warn: no topLevel permission defined: .github/workflows/unit-tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/type-checking.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/benmoran56/esper/type-checking.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/type-checking.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/benmoran56/esper/type-checking.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/unit-tests.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/benmoran56/esper/unit-tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/unit-tests.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/benmoran56/esper/unit-tests.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/type-checking.yml:24","Warn: pipCommand not pinned by hash: .github/workflows/unit-tests.yml:24","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T05:11:45.294Z","repository_id":42077987,"created_at":"2025-08-17T05:11:45.294Z","updated_at":"2025-08-17T05:11:45.294Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28896548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-29T21:06:44.224Z","status":"online","status_checked_at":"2026-01-30T02:00:06.810Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["entity-component-system","entity-system","gamedev","rogue","rougelike"],"created_at":"2024-08-01T13:01:58.442Z","updated_at":"2026-01-30T02:32:49.196Z","avatar_url":"https://github.com/benmoran56.png","language":"Python","funding_links":[],"categories":["ECS Libraries","Python","[ECS Libraries](#contents)"],"sub_categories":[],"readme":"[![pypi](https://badge.fury.io/py/esper.svg)](https://pypi.python.org/pypi/esper)\n[![rtd](https://readthedocs.org/projects/esper/badge/?version=latest)](https://esper.readthedocs.io)\n[![PyTest](https://github.com/benmoran56/esper/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/benmoran56/esper/actions/workflows/unit-tests.yml)\n\nesper is a lightweight Entity System module for Python, with a focus on performance\n===================================================================================\n\n**esper** is an MIT licensed Entity System, or, Entity Component System (ECS).\nThe design is based on the Entity System concepts originally popularized by\nAdam Martin and others. The primary focus for **esper** is to maximize perfomance,\nwhile handling most common use cases. \n\nFor more information on the ECS pattern, you might find the following\nresources interesting:\nhttps://github.com/SanderMertens/ecs-faq/blob/master/README.md\nhttps://github.com/jslee02/awesome-entity-component-system/blob/master/README.md\nhttps://en.wikipedia.org/wiki/Entity_component_system\n\nAPI documentation is hosted at ReadTheDocs: https://esper.readthedocs.io\nDue to the small size of the project, this README currently serves as general usage\ndocumentation.\n\n\u003e :warning: **esper 3.0 introduces breaking changes**. Version 3.0 removes the\n\u003e World object, and migrates its methods to module level functions. Multiple \n\u003e contexts can be created and switched between. The v2.x README can be found\n\u003e here: https://github.com/benmoran56/esper/blob/v2_maintenance/README.md\n\n- [Compatibility](#compatibility)\n- [Installation](#installation)\n- [Design](#design)\n- [Quick Start](#quick-start)\n- [General Usage](#general-usage)\n  * [Adding and Removing Processors](#adding-and-removing-processors)\n  * [Adding and Removing Components](#adding-and-removing-components)\n  * [Querying Specific Components](#querying-specific-components)\n  * [Boolean and Conditional Checks](#boolean-and-conditional-checks)\n  * [More Examples](#more-examples)\n- [Event Dispatching](#event-dispatching)\n- [Contributing](#contributing)\n\n\nCompatibility\n=============\n**esper** attempts to target all currently supported Python releases (any Python version that is\nnot EOL). **esper** is written in 100% pure Python, so *any* compliant interpreter should work.\nAutomated testing is currently done for both CPython and PyPy3.\n\n\nInstallation\n============\n**esper** is a pure Python package with no dependencies, so installation is flexible.\nYou can simply copy the `esper` folder right into your project, and `import esper`.\nYou can also install into your site-packages from PyPi via `pip`::\n\n    pip install --user --upgrade esper\n\nOr from the source directory::\n\n    pip install . --user\n\n\nDesign\n======\n\n* World Context\n\n**esper** uses the concept of \"World\" contexts. When you first `import esper`, a default context is\nactive. You create Entities, assign Components, register Processors, etc., by calling functions\non the `esper` module. Entities, Components and Processors can be created, assigned, or deleted\nwhile your game is running. A simple call to `esper.process()` is all that's needed for each\niteration of your game loop. Advanced users can switch contexts, which can be useful for\nisolating different game scenes that have different Processor requirements.\n\n\n* Entities \n\nEntities are defined internally as plain integer IDs (1, 2, 3, 4, etc.). Generally speaking\nyou should not need to care about the individual entity IDs, since entities are queried based\non their specific combination of Components - not by their ID. An Entity can be thought of as\na specific combination of Components. Creating an Entity is done with the `esper.create_entity()`\nfunction. You can pass Component instances on creation or add/remove them later.\n\n* Components\n\nComponents are defined as simple Python classes. In keeping with a pure Entity System design\nphilosophy, Components should not contain any processing logic. They may contain initialization\nlogic, and you can take advantage of Python language features, like properties, to simplify data\nlookup. The key point is that game logic does not belong in these classes, and Components should\nhave no knowledge of other Components or Entities. A simple Component can be defined as::\n\n    class Position:\n        def __init__(self, x=0.0, y=0.0):\n            self.x = x\n            self.y = y\n\nTo save on typing, the standard library dataclass decorator is quite useful. \nhttps://docs.python.org/3/library/dataclasses.html#module-dataclasses\nThis decorator simplifies defining your Component classes. The attribute names don't need to\nbe repeated, and you can still instantiate the Component with positional or keyword arguments::\n\n    from dataclasses import dataclass as component\n\n    @component\n    class Position:\n        x: float = 0.0\n        y: float = 0.0\n\nPython language features, like properties, can be useful to simplify data access. For example,\na Body component that is often repositioned may benefit from a local AABB (axis aligned bounding\nbox) property::\n\n    @dataclass\n    class Body:\n        width:  int\n        height: int\n        pos_x:  float = 0\n        pos_y:  float = 0\n\n        @property\n        def aabb(self) -\u003e tuple[float, float, float, float]:\n            return self.pos_x, self.pos_y, self.pos_x + self.width, self.pos_y + self.height\n\n\n* Processors\n\nProcessors, also commonly known as \"Systems\", are where all processing logic is defined and executed.\nAll Processors must inherit from the `esper.Processor` class, and have a method called `process`.\nOther than that, there are no restrictions. You can define any additional methods you might need. \nA simple Processor might look like::\n\n    class MovementProcessor(esper.Processor):\n\n        def process(self):\n            for ent, (vel, pos) in esper.get_components(Velocity, Position):\n                pos.x += vel.x\n                pos.y += vel.y\n\nIn the above code, you can see the standard usage of the `esper.get_components()` function. This\nfunction allows efficient iteration over all Entities that contain the specified Component types.\nThis function can be used for querying two or more components at once. Note that tuple unpacking\nis necessary for the return component pairs: `(vel, pos)`.  In addition to Components, you also\nget a reference to the Entity ID for the current pair of Velocity/Position Components. This entity\nID can be useful in a variety of cases. For example, if your Processor will need to delete certain\nEntites, you can call the `esper.delete_entity()` function on this Entity ID. Another common use\nis if you wish to add or remove a Component on this Entity as a result of some condition being met. \nFor example, an Entity that should be deleted once it's `Lifecycle` Component reaches 0::\n\n    class LifecycleProcessor(esper.Processor):\n        def __init__(self, ...):\n            ...\n    \n        def process(self, dt):\n            for ent, (life, rend) in esper.get_components(Lifecycle, Renderable):\n                life.lifespan -= dt\n                if life.lifespan \u003c= 0:\n                    esper.delete_entity(ent)\n\n\nQuick Start\n===========\n\nTo get started, simply import **esper**::\n\n    import esper\n\nFrom there, define some Components, and create Entities that use them::\n\n    player = esper.create_entity()\n    esper.add_component(player, Velocity(x=0.9, y=1.2))\n    esper.add_component(player, Position(x=5, y=5))\n\nOptionally, Component instances can be assigned directly to the Entity on creation::\n\n    player = esper.create_entity(Velocity(x=0.9, y=1.2), Position(x=5, y=5))\n\n\nDesign some Processors that operate on these Component types, and then register them with\n**esper** for processing. You can specify an optional priority (higher numbers are processed first).\nAll Processors are priority \"0\" by default::\n\n    movement_processor = MovementProcessor()\n    collision_processor = CollisionProcessor()\n    rendering_processor = RenderingProcessor()\n    esper.add_processor(collision_processor, priority=2)\n    esper.add_processor(movement_processor, priority=3)\n    esper.add_processor(rendering_processor)\n    # or just add them in one line: \n    esper.add_processor(SomeProcessor())\n\n\nExecuting all Processors is done with a single call to `esper.process()`. This will call the\n`process` method on all assigned Processors, in order of their priority. This is usually called\nonce per frame update of your game (every tick of the clock).::\n\n    esper.process()\n\n\n**Note:** You can pass any arguments (or keyword arguments) you need to `esper.process()`, but you\nmust also make sure to receive them properly in the `process()` methods of your Processors. For\nexample, if you pass a delta time argument as `esper.process(dt)`, your Processor's `process()`\nmethods should all receive it as: `def process(self, dt):`\nThis is appropriate for libraries such as **pyglet**, which automatically pass a delta time value\ninto scheduled functions.  \n\n\nGeneral Usage\n=============\n\nWorld Contexts\n--------------\n**esper** has the capability of supporting multiple \"World\" contexts. On import, a \"default\" World is\nactive. All creation of Entities, assignment of Processors, and all other operations occur within\nthe confines of the active World. In other words, the World contexts are completely isolated from\neach other. For basic games and designs, you may not need to bother with this functionality. A\nsingle default World context can often be enough. For advanced use cases, such as when different\nscenes in your game have different Entities and Processor requirements, this functionality can be\nquite useful. World context operations are done with the following functions::\n* esper.list_worlds()\n* esper.switch_world(name)\n* esper.delete_world(name)\n\nWhen switching Worlds, be mindful of the `name`. If a World doesn't exist, it will be created when\nyou first switch to it. You can delete old Worlds if they are no longer needed, but you can not\ndelete the currently active World.  \n\nAdding and Removing Processors\n------------------------------\nYou have already seen examples of adding Processors in an earlier section. There is also a\n`remove_processor` function available:\n\n* esper.add_processor(processor_instance)\n* esper.remove_processor(ProcessorClass)\n\nDepending on the structure of your game, you may want to add or remove certain Processors when changing\nscenes, etc. \n\nAdding and Removing Components\n------------------------------\nIn addition to adding Components to Entities when you're creating them, it's a common pattern to add or\nremove Components inside your Processors. The following functions are available for this purpose: \n\n* esper.add_component(entity_id, component_instance)\n* esper.remove_component(entity_id, ComponentClass)\n\nAs an example of this, you could have a \"Blink\" component with a `duration` attribute. This can be used\nto make certain things blink for a specific period of time, then disappear. For example, the code below\nshows a simplified case of adding this Component to an Entity when it takes damage in one processor. A \ndedicated `BlinkProcessor` handles the effect, and then removes the Component after the duration expires::\n\n    class BlinkComponent:\n        def __init__(self, duration):\n            self.duration = duration\n\n\n    .....\n\n\n    class CollisionProcessor(esper.Processor):\n\n        def process(self, dt):\n            for ent, enemy in esper.get_component(Enemy):\n                ...\n                is_damaged = self._some_method()\n                if is_damaged:\n                    esper.add_component(ent, BlinkComponent(duration=1))\n                ...\n\n\n    class BlinkProcessor(esper.Processor):\n\n        def process(self, dt):\n            for ent, (rend, blink) in esper.get_components(Renderable, BlinkComponent):\n                if blink.duration \u003c 0:\n                    # Times up. Remove the Component:\n                    rend.sprite.visible = True\n                    esper.remove_component(ent, BlinkComponent)\n                else:\n                    blink.duration -= dt\n                    # Toggle between visible and not visible each frame:\n                    rend.sprite.visible = not rend.sprite.visible\n\n\nQuerying Specific Components\n----------------------------\nIf you have an Entity ID and wish to query one specific, or ALL Components that are assigned\nto it, the following functions are available: \n\n* esper.component_for_entity\n* esper.components_for_entity\n\nThe `component_for_entity` function is useful in a limited number of cases where you know a specific\nEntity ID, and wish to get a specific Component for it. An error is raised if the Component does not\nexist for the Entity ID, so it may be more useful when combined with the `has_component`\nfunction that is explained in the next section. For example::\n\n    if esper.has_component(ent, SFX):\n        sfx = esper.component_for_entity(ent, SFX)\n        sfx.play()\n\nThe `components_for_entity` function is a special function that returns ALL the Components that are\nassigned to a specific Entity, as a tuple. This is a heavy operation, and not something you would\nwant to do each frame or inside your `Processor.process` method. It can be useful, however, if\nyou wanted to transfer all of a specific Entity's Components between two separate contexts\n(such as when changing Scenes, or levels). For example::\n    \n    player_components = esper.components_for_entity(player_entity_id)\n    esper.switch_world('context_name')\n    player_entity_id = esper.create_entity(player_components)\n\nBoolean and Conditional Checks\n------------------------------\nIn some cases you may wish to check if an Entity has a specific Component before performing\nsome action. The following functions are available for this task:\n\n* esper.has_component(entity, ComponentType)\n* esper.has_components(entity, ComponentTypeA, ComponentTypeB)\n* esper.try_component(entity, ComponentType)\n* esper.try_components(entity, ComponentTypeA, ComponentTypeB)\n\n\nFor example, you may want projectiles (and only projectiles) to disappear when hitting a wall in\nyour game. We can do this by checking if the Entity has a `Projectile` Component. We don't  want\nto do anything to this Component, simply check if it's there. Consider this example::\n\n    class CollisionProcessor(esper.Processor):\n\n        def process(self, dt):\n            for ent, body in esper.get_component(PhysicsBody):\n                ...\n                colliding_with_wall = self._some_method(body):\n                if colliding_with_wall and esper.has_component(ent, Projectile):\n                    esper.delete_entity(ent)\n                ...\n\n\nIn a different scenario, we may want to perform some action on an Entity's Component, *if* it has\none. For example, a MovementProcessor that skips over Entities that have a `Stun` Component::\n\n    class MovementProcessor(esper.Processor):\n\n        def process(self, dt):\n            for ent, (body, vel) in esper.get_components(PhysicsBody, Velocity):\n\n                if esper.has_component(ent, Stun):\n                    stun = esper.component_for_entity(ent, Stun)\n                    stun.duration -= dt\n                    if stun.duration \u003c= 0:\n                        esper.remove_component(ent, Stun)\n                    continue    # Continue to the next Entity\n\n                movement_code_here()\n                ...\n\n\nLet's look at the core part of the code::\n\n    if esper.has_component(ent, Stun):\n        stun = esper.component_for_entity(ent, Stun)\n        stun.duration -= dt\n\nThis code works fine, but the `try_component` function can accomplish the same thing with one\nless function call. The following example will get a specific Component if it exists, or\nreturn None if it does not::\n\n    stun = esper.try_component(ent, Stun)\n    if stun:\n        stun.duration -= dt\n\nWith Python 3.8+, the new \"walrus\" operator (`:=`) can also be used, making the `try_component`\nfunctions even more concise ::\n\n    if stun :=  esper.try_component(ent, Stun):\n        stun.duration -= dt\n\n\nMore Examples\n-------------\n\nSee the `/examples` folder to get an idea of how the basic structure of a game might look.\n\nEvent Dispatching\n=================\n\n**esper** includes basic support for event dispatching and handling. This functionality is\nprovided by three functions to set (register), remove, and dispatch events. Minimal error\nchecking is done, so it's left up to the user to ensure correct naming and number of\narguments are used when dispatching and receiving events.\n\nEvents are dispatched by name::\n\n    esper.dispatch_event('event_name', arg1, arg2)\n\nIn order to receive the above event, you must register handlers. An event handler can be a\nfunction or class method. Registering a handler is also done by name::\n\n    esper.set_handler('event_name', my_func)\n    # or\n    esper.set_handler('event_name', self.my_method)\n\n**Note:** Only weak-references are kept to the registered handlers. If a handler is garbage\ncollected, it will be automatically un-registered by an internal callback.\n\nHandlers can also be removed at any time, if you no longer want them to receive events::\n\n    esper.remove_handler('event_name', my_func)\n    # or\n    esper.remove_handler('event_name', self.my_method)\n\nRegistered events and handlers are part of the current `World` context. \n\nContributing\n============\n\nContributions to **esper** are always welcome, but there are some specific project goals to keep in mind:\n\n- Pure Python code only: no binary extensions, Cython, etc.\n- Try to target all non-EOL Python versions. Exceptions can be made if there is a compelling reason.\n- Avoid bloat as much as possible. New features will be considered if they are commonly useful. Generally speaking, we don't want to add functionality that is better served by another module or library. \n- Performance is preferrable to readability. The public API should remain clean, but ugly internal code is acceptable if it provides a performance benefit. Every cycle counts! \n\nIf you have any questions before contributing, feel free to [open an issue].\n\n[open an issue]: https://github.com/benmoran56/esper/issues\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenmoran56%2Fesper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenmoran56%2Fesper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenmoran56%2Fesper/lists"}