{ "info": { "author": "Benjamin Moran", "author_email": "benmoran@protonmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", "Topic :: Games/Entertainment", "Topic :: Software Development :: Libraries" ], "description": ".. image:: https://travis-ci.org/benmoran56/esper.svg?branch=master\n :target: https://travis-ci.org/benmoran56/esper\n\nEsper\n=====\n**Esper is a lightweight Entity System for Python, with a focus on performance.**\n\nEsper is an MIT licensed Entity System, or, Entity Component System (ECS).\nThe design is based on the Entity System concepts outlined by Adam Martin in his blog at\nhttp://t-machine.org/, and others. Efforts were made to keep it as lightweight and performant\nas possible.\n\nThere is a fairly accurate writeup describing Entity Systems in this Wikipedia article:\nhttps://en.wikipedia.org/wiki/Entity_component_system\n\nInspired by Sean Fisk's **ecs** https://github.com/seanfisk/ecs,\nand Marcus von Appen's **ebs** https://bitbucket.org/marcusva/python-utils.\n\n\nWhat's New\n----------\n**1.2** - Calls to `super()` are no longer necessary in your Processor subclasses.\n This should eliminate a fair amount of boilerplate. The README has also been updated\n with more usage examples. All methods should now have at least one example. And finally,\n wheels are now uploaded to PyPi. This should help with packaging systems that only support\n wheels. Addresses issue #38.\n\n**1.0.0** - Esper is now using simple lru_caching internally by default. The cache is currently\n flushed when adding or deleting Entities or Components from the World, Component queries\n are much faster otherwise. This will likely be improved in a Future version. In addition\n to caching, Esper now supports passing kwargs to Processors. Continuous Integration testing\n is now being done for Python 3.7.\n\n**0.9.9** - The big change in this release is that esper has been condensed into a single\n file: `esper.py`. This will make it simple to just drop into your project folder,\n without cluttering your project with additional folders that didn't really need to\n exist. You can still install it from PyPi via pip if you wish, but it's easy enough\n to just ship with your project (and of course the license allows for this).\n\n**0.9.8** - This release contains a new timer that can be enabled to profile Processor execution\n time. Simply pass the \"timed=True\" parameter to the World on instantiation, and a new\n World.process_times dictionary will be available. This contains the total execution time\n of each Processor in milliseconds, and can be logged, printed, or displayed on screen as\n is useful. It's useful to see a quick profile of which processors are using the most cpu\n time, without fully profiling your game. This release also contains some consolidations\n and cleanups for the benchmarks.\n\n**0.9.7** - By default, entities are now lazily deleted. When calling *World.delete_entity(entity_id)*,\n Entities are now placed into a queue to be deleted at the beginning of the next call\n to World.process(). This means it is now safe to delete entities even while iterating\n over components in your processors. This should allow for cleaner Processor classes, by\n removing the need to manually track and delete \"dead\" Entities after iteration. If you\n do wish to delete an Entity immediately, simply pass the new optional *immediate=True*\n argument. Ie: *self.world.delete_entity(entity, immediate=True)*.\n\n\n1) Compatibility\n----------------\nEsper is a Python 3 library only. Specifically, all currently supported versions of Python 3. \nIt also supports Pypy3. Being written in pure Python, it should work on *any* compliant\ninterpreter. Continuous Integration (automated testing) is done for both CPython and PyPy3.\n\n\n2) Installation\n---------------\nNo installation is necessary. Esper is a tiny library with no dependencies. Simply copy\n*esper.py* into the top level of your project folder, and *import esper*.\n\nIf you prefer, Esper is also available on PyPI for easy installation via pip.\n\n\n3) Project Structure\n--------------------\n* World\n\nA World is the main point of interaction in Esper. After creating a World object, you will use\nthat object to create Entities and assigning Components to them. A World is also assigned all of\nyour Processor instances, and handles smoothly running everything with a single call per frame.\nOf course, Entities, Components and Processors can be created and assigned, or deleted while\nyour application is running.\n\n\n* Entities \n\nEntities are simple integer IDs (1, 2, 3, 4, etc.).\nEntities are \"created\", but they are generally not used directly. Instead, they are\nsimply used as IDs in the internal Component database, to track collections of Components.\nCreating an Entity is done with the World.create_entity() method.\n\n\n* Components\n\nComponents are defined as simple Python classes. In keeping with a pure Entity System\ndesign philosophy, they should not contain any logic. They might have initialization\ncode, but no processing logic whatsoever. A simple Component might look like::\n\n class Position:\n def __init__(self, x=0.0, y=0.0):\n self.x = x\n self.y = y\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. All Processors will have access to the World instance,\nwhich is how you query Components to operate on. A simple Processor might look like::\n\n class MovementProcessor(esper.Processor):\n\n def process(self):\n for ent, (vel, pos) in self.world.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 *World.get_components()* method. This\nmethod allows efficient iteration over all Entities that contain the specified Component types.\nThis method 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 the Components, you also\nget a reference to the Entity ID (the *ent* object) for the current pair of Velocity/Position\nComponents. This entity ID can be useful in a variety of cases. For example, if your Processor\nwill need to delete certain Entites, you can call the *self.world.delete_entity()* method on\nthis Entity ID. Another common use is if you wish to add or remove a Component on this Entity\nas a result of some condition being met. \n\n\n4) Basic Usage\n--------------\n\nThe first step after importing Esper is to create a World instance. You can have a single World\ninstance for your entire game, or you can have a separate instance for each of your game scenes.\nWhatever makes sense for your design. Create a World instance like this::\n\n world = esper.World()\n\n\nCreate some Processor instances, and assign them to the World. You can specify an\noptional processing priority (higher numbers are processed first). All Processors are\npriority \"0\" by default::\n\n movement_processor = MovementProcessor()\n collision_processor = CollisionProcessor()\n rendering_processor = RenderingProcessor()\n world.add_processor(movement_processor, priority=2)\n world.add_processor(collision_processor, priority=3)\n world.add_processor(rendering_processor)\n # or just add them in one line: \n world.add_processor(SomeProcessor())\n\n\nCreate an Entity, and assign some Component instances to it::\n\n player = world.create_entity()\n world.add_component(player, Velocity(x=0.9, y=1.2))\n world.add_component(player, Position(x=5, y=5))\n\nOptionally, Component instances can be assigned directly to the Entity on creation::\n\n player = world.create_entity(Velocity(x=0.9, y=1.2),\n Position(x=5, y=5))\n\n\nExecuting all Processors is done with a single call to world.process(). This will call the\nprocess method on all assigned Processors, in order of their priority. This is usually called\nonce per frame update of your game.::\n\n world.process()\n\n\nNote: You can pass any args you need to *world.process()*, but you must also make sure to recieve\nthem properly in the *process()* methods of your Processors. For example, if you pass a delta time\nargument as *world.process(dt)*, your Processor's *process()* methods should all receive it as:\n*def process(self, dt):*\nThis is appropriate for libraries such as **pyglet**, which automatically pass a delta time value\ninto scheduled methods. \n\n\n5) Additional methods\n=====================\n\nAdding and Removing Processors\n------------------------------\nYou have already seen examples of adding Processors in an eariler section. There is also a *remove_processor*\nmethod available:\n\n* World.add_processor(processor_instance)\n* World.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 of your Processors. The following methods are availble for this purpose: \n\n* World.add_component(entity_id, component_instance)\n* World.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 s specific period of time, then dissapear. 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 self.world.get_component(Enemy):\n ...\n is_damaged = self._some_method()\n if is_damaged:\n self.world.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 self.world.get_components(Renderable, BlinkComponent):\n if blink.duration < 0:\n # Times up. Remove the Component:\n rend.sprite.visible = True\n self.world.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 Engity ID and wish to query one specific, or ALL Components that are assigned\nto it, the following methods are available: \n\n* World.component_for_entity\n* World.components_for_entity\n\nThe *component_for_entity* method 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*\nmethod that is explained in the next section. For example::\n\n if self.world.has_component(ent, SFX):\n sfx = self.world.component_for_entity(ent, SFX)\n sfx.play()\n\nThe *components_for_entity* method is a special method that returns ALL of 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 of your *Processor.process* method. It can be useful, however, if\nyou wanted to transfer all of a specific Entity's Components between two separate World instances\n(such as when changing Scenes, or Levels). For example:\n\n player_components = old_world.components_for_entity(player_entity_id)\n ...\n player_entity_id = new_world.create_entity(player_components)\n\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 two methods are available for this task:\n\n* World.has_component(entity, ComponentType)\n* World.try_component(entity, ComponentType)\n\nFor example, you may want projectiles (and only projectiles) to dissapear when hitting a \nwall in your game. The simplified code below shows how that might look::\n\n class CollisionProcessor(esper.Processor):\n\n def process(self, dt):\n for ent, body in self.world.get_component(PhysicsBody):\n ...\n colliding_with_wall = self._some_method(body):\n if colliding_with_wall and self.world.has_component(ent, Projectile):\n self.world.delete_entity(ent)\n ...\n\nThe above example is easy enough, as you don't want to actually do anything to the Component - \njust check if it's there. In cases where you want to both check if a Component exists, and then\noperate on it if so, the *try_component* method is useful. Consider the following example, where\nyou want to first check if an Entity has a Component, get it if so, then operate on it. You could\nwrite it this way:: \n\n if self.world.has_component(ent, Stun):\n stun = self.world.get_component(ent, Stun)\n stun.duration -= dt\n\nThe above code works fine, but the *try_component* method is more concise and slightly faster. \nIt allows you to get specific Components only if they exist, but passes silently if they do not::\n\n for stun in self.world.try_component(ent, Stun):\n stun.duration -= dt\n\n\n5) More Examples\n----------------\n\nSee the **/examples** folder to get an idea of how a basic structure of a game might look.\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "https://github.com/benmoran56/esper/releases", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/benmoran56/esper", "keywords": "ecs,entity component system,game", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "esper", "package_url": "https://pypi.org/project/esper/", "platform": "POSIX", "project_url": "https://pypi.org/project/esper/", "project_urls": { "Download": "https://github.com/benmoran56/esper/releases", "Homepage": "https://github.com/benmoran56/esper" }, "release_url": "https://pypi.org/project/esper/1.2/", "requires_dist": null, "requires_python": "", "summary": "Esper is a lightweight Entity System for Python, with a focus on performance.", "version": "1.2" }, "last_serial": 5394931, "releases": { "0.9.8": [ { "comment_text": "built for Linux-4.9.11-1-ARCH-x86_64-with-glibc2.3.4", "digests": { "md5": "590d31b913dc78e52ef5c89ac1fd7c39", "sha256": "7308f6500f8b24d571d4d8530c90b77f315b3978aa7867f3cb4311982fd21b66" }, "downloads": -1, "filename": "esper-0.9.8.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "590d31b913dc78e52ef5c89ac1fd7c39", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 16226, "upload_time": "2017-03-01T07:47:16", "url": "https://files.pythonhosted.org/packages/7a/08/91ed1064759fc0d2543270425472c7efb880da2d3747cb5654d32030ec84/esper-0.9.8.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "ed002f8b9229f6d4a26cab90671178cb", "sha256": "32632a3b6546cf77d3158dea07bf5db22423ae08a45d39dbdad88412162ff25a" }, "downloads": -1, "filename": "esper-0.9.8.tar.gz", "has_sig": false, "md5_digest": "ed002f8b9229f6d4a26cab90671178cb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21448, "upload_time": "2017-03-01T07:47:02", "url": "https://files.pythonhosted.org/packages/0d/73/f32e1debe8816915b3fb63e117d9ce6f71fca70987f86d1eedbbc24fabfe/esper-0.9.8.tar.gz" } ], "0.9.9": [ { "comment_text": "", "digests": { "md5": "c0d406409d00267b3ff6313602d272f6", "sha256": "620795087566ce2f862daf469ab798e7b8446c1bf0469bb8f7eb52f841957072" }, "downloads": -1, "filename": "esper-0.9.9.tar.gz", "has_sig": false, "md5_digest": "c0d406409d00267b3ff6313602d272f6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18962, "upload_time": "2017-10-21T15:16:48", "url": "https://files.pythonhosted.org/packages/3b/97/9deb2221f379ac62643c003ea4566ef2d2d839f855776cf8ab0e7c092954/esper-0.9.9.tar.gz" } ], "0.9.9.1": [ { "comment_text": "", "digests": { "md5": "b86608347fedc1ca8fc1e45ef5860eb3", "sha256": "7521538ee3f9d8acd0bd2a062cffb8d5c36e389df177831df330d3ada5bdf5e3" }, "downloads": -1, "filename": "esper-0.9.9.1-py3-none-any.whl", "has_sig": false, "md5_digest": "b86608347fedc1ca8fc1e45ef5860eb3", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8747, "upload_time": "2019-06-11T11:39:38", "url": "https://files.pythonhosted.org/packages/91/10/165d1382592cb74c2aab709ff38bd3a63b9ebee9204b5087852a8bded313/esper-0.9.9.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2a427c45b80b480f8097c33aec45e2ed", "sha256": "a231a1e6c44d6868f5d400898bb657eac297ff30c85e3640eb28839404537740" }, "downloads": -1, "filename": "esper-0.9.9.1.tar.gz", "has_sig": false, "md5_digest": "2a427c45b80b480f8097c33aec45e2ed", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24908, "upload_time": "2017-10-21T15:20:32", "url": "https://files.pythonhosted.org/packages/1a/bb/c92e3c2063ff893f21ca4fe68e8f42d64c2b592125e01f35771bf3ce4ad0/esper-0.9.9.1.tar.gz" } ], "1.0": [ { "comment_text": "", "digests": { "md5": "b0238c93dbc1ccc4e075d8e17d854ec8", "sha256": "20bafc9535346abcb39c20aed7e7c81a34054b32f112d299b62d57851198041f" }, "downloads": -1, "filename": "esper-1.0.tar.gz", "has_sig": false, "md5_digest": "b0238c93dbc1ccc4e075d8e17d854ec8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15941, "upload_time": "2018-08-10T04:04:31", "url": "https://files.pythonhosted.org/packages/01/af/e20db459b8a3360144b3e7fdda3dab3b8967b1fb61749e2fb8b85107f09b/esper-1.0.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "8f36e2e5da4ab895d6253fe680f07665", "sha256": "a217e1adb7b17c9ef5e0f8b3ff867932ce37157be227b237dcbbe36e6bb5cf0b" }, "downloads": -1, "filename": "esper-1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "8f36e2e5da4ab895d6253fe680f07665", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 10442, "upload_time": "2019-06-13T08:54:02", "url": "https://files.pythonhosted.org/packages/34/a3/d1093095f091d8601b71dbefd4942032935bc4567051f68339cf5686ff94/esper-1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ac434e5b4d28edfb125f92bb1ee7f018", "sha256": "1a7e049cffebcc6a99de19443483650fab080112ceaa29d379ef2b838b0b7724" }, "downloads": -1, "filename": "esper-1.2.tar.gz", "has_sig": false, "md5_digest": "ac434e5b4d28edfb125f92bb1ee7f018", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19523, "upload_time": "2019-06-13T08:54:04", "url": "https://files.pythonhosted.org/packages/e5/9a/e7c8e31e9bc534d382fefac547771e902ca6ea2d455d613adfe5b7124c1d/esper-1.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "8f36e2e5da4ab895d6253fe680f07665", "sha256": "a217e1adb7b17c9ef5e0f8b3ff867932ce37157be227b237dcbbe36e6bb5cf0b" }, "downloads": -1, "filename": "esper-1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "8f36e2e5da4ab895d6253fe680f07665", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 10442, "upload_time": "2019-06-13T08:54:02", "url": "https://files.pythonhosted.org/packages/34/a3/d1093095f091d8601b71dbefd4942032935bc4567051f68339cf5686ff94/esper-1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ac434e5b4d28edfb125f92bb1ee7f018", "sha256": "1a7e049cffebcc6a99de19443483650fab080112ceaa29d379ef2b838b0b7724" }, "downloads": -1, "filename": "esper-1.2.tar.gz", "has_sig": false, "md5_digest": "ac434e5b4d28edfb125f92bb1ee7f018", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19523, "upload_time": "2019-06-13T08:54:04", "url": "https://files.pythonhosted.org/packages/e5/9a/e7c8e31e9bc534d382fefac547771e902ca6ea2d455d613adfe5b7124c1d/esper-1.2.tar.gz" } ] }