esper 3.2__tar.gz → 3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. esper-3.4/.ruff.toml +2 -0
  2. {esper-3.2 → esper-3.4}/LICENSE +1 -1
  3. {esper-3.2 → esper-3.4}/PKG-INFO +73 -49
  4. {esper-3.2 → esper-3.4}/README.md +71 -47
  5. {esper-3.2 → esper-3.4}/RELEASE_NOTES +11 -0
  6. {esper-3.2 → esper-3.4}/docs/index.rst +22 -4
  7. {esper-3.2 → esper-3.4}/esper/__init__.py +30 -32
  8. {esper-3.2 → esper-3.4}/examples/pyglet_example.py +1 -1
  9. {esper-3.2 → esper-3.4}/tests/test_world.py +10 -0
  10. esper-3.2/.ruff.toml +0 -3
  11. esper-3.2/examples/pyglet_example_batch.py +0 -245
  12. {esper-3.2 → esper-3.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {esper-3.2 → esper-3.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {esper-3.2 → esper-3.4}/.github/ISSUE_TEMPLATE/question-or-comment.md +0 -0
  15. {esper-3.2 → esper-3.4}/.github/workflows/type-checking.yml +0 -0
  16. {esper-3.2 → esper-3.4}/.github/workflows/unit-tests.yml +0 -0
  17. {esper-3.2 → esper-3.4}/.gitignore +0 -0
  18. {esper-3.2 → esper-3.4}/.mypy.ini +0 -0
  19. {esper-3.2 → esper-3.4}/.readthedocs.yaml +0 -0
  20. {esper-3.2 → esper-3.4}/MANIFEST.in +0 -0
  21. {esper-3.2 → esper-3.4}/docs/Makefile +0 -0
  22. {esper-3.2 → esper-3.4}/docs/conf.py +0 -0
  23. {esper-3.2 → esper-3.4}/docs/make.bat +0 -0
  24. {esper-3.2 → esper-3.4}/esper/py.typed +0 -0
  25. {esper-3.2 → esper-3.4}/examples/benchmark.py +0 -0
  26. {esper-3.2 → esper-3.4}/examples/benchmark_cache.py +0 -0
  27. {esper-3.2 → esper-3.4}/examples/bluesquare.png +0 -0
  28. {esper-3.2 → esper-3.4}/examples/headless_example.py +0 -0
  29. {esper-3.2 → esper-3.4}/examples/pygame_example.py +0 -0
  30. {esper-3.2 → esper-3.4}/examples/pysdl2_example.py +0 -0
  31. {esper-3.2 → esper-3.4}/examples/pythonista_ios_example.py +0 -0
  32. {esper-3.2 → esper-3.4}/examples/redsquare.png +0 -0
  33. {esper-3.2 → esper-3.4}/make.py +0 -0
  34. {esper-3.2 → esper-3.4}/pyproject.toml +0 -0
  35. {esper-3.2 → esper-3.4}/tests/__init__.py +0 -0
esper-3.4/.ruff.toml ADDED
@@ -0,0 +1,2 @@
1
+ lint.select = ["F", "E", "W", "C90", "B", "A", "C4", "G", "T20", "PT", "SLF", "TCH", "RET", "N", "ARG"]
2
+ lint.ignore = ["E501"]
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2023 Benjamin Moran
3
+ Copyright (c) 2024 Benjamin Moran
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: esper
3
- Version: 3.2
3
+ Version: 3.4
4
4
  Summary: esper is a lightweight Entity System (ECS) for Python, with a focus on performance
5
5
  Author-email: Benjamin Moran <benmoran@protonmail.com>
6
6
  Requires-Python: >=3.8
@@ -12,12 +12,12 @@ Project-URL: Home, https://github.com/benmoran56/esper
12
12
  [![rtd](https://readthedocs.org/projects/esper/badge/?version=latest)](https://esper.readthedocs.io)
13
13
  [![PyTest](https://github.com/benmoran56/esper/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/benmoran56/esper/actions/workflows/unit-tests.yml)
14
14
 
15
- Esper is a lightweight Entity System module for Python, with a focus on performance
15
+ esper is a lightweight Entity System module for Python, with a focus on performance
16
16
  ===================================================================================
17
17
 
18
- Esper is an MIT licensed Entity System, or, Entity Component System (ECS).
18
+ **esper** is an MIT licensed Entity System, or, Entity Component System (ECS).
19
19
  The design is based on the Entity System concepts originally popularized by
20
- Adam Martin and others. The primary focus for esper is to maximize perfomance,
20
+ Adam Martin and others. The primary focus for **esper** is to maximize perfomance,
21
21
  while handling most common use cases.
22
22
 
23
23
  For more information on the ECS pattern, you might find the following
@@ -30,7 +30,7 @@ API documentation is hosted at ReadTheDocs: https://esper.readthedocs.io
30
30
  Due to the small size of the project, this README currently serves as general usage
31
31
  documentation.
32
32
 
33
- > :warning: **Esper 3.0 introduces breaking changes**. Version 3.0 removes the
33
+ > :warning: **esper 3.0 introduces breaking changes**. Version 3.0 removes the
34
34
  > World object, and migrates its methods to module level functions. Multiple
35
35
  > contexts can be created and switched between. The v2.x README can be found
36
36
  > here: https://github.com/benmoran56/esper/blob/v2_maintenance/README.md
@@ -51,15 +51,15 @@ documentation.
51
51
 
52
52
  Compatibility
53
53
  =============
54
- Esper attempts to target all currently supported Python releases (any Python version that is
55
- not EOL). Esper is written in 100% pure Python, so *any* compliant interpreter should work.
54
+ **esper** attempts to target all currently supported Python releases (any Python version that is
55
+ not EOL). **esper** is written in 100% pure Python, so *any* compliant interpreter should work.
56
56
  Automated testing is currently done for both CPython and PyPy3.
57
57
 
58
58
 
59
59
  Installation
60
60
  ============
61
- Esper is a pure Python package with no dependencies, so installation is flexible.
62
- You can simply copy the *esper* folder right into your project, and *import esper*.
61
+ **esper** is a pure Python package with no dependencies, so installation is flexible.
62
+ You can simply copy the `esper` folder right into your project, and `import esper`.
63
63
  You can also install into your site-packages from PyPi via `pip`::
64
64
 
65
65
  pip install --user --upgrade esper
@@ -74,8 +74,8 @@ Design
74
74
 
75
75
  * World Context
76
76
 
77
- Esper uses the concept of "World" contexts. When you first `import esper`, a default context is
78
- active. You create Entities, assign Components, register Processesors, etc., by calling functions
77
+ **esper** uses the concept of "World" contexts. When you first `import esper`, a default context is
78
+ active. You create Entities, assign Components, register Processors, etc., by calling functions
79
79
  on the `esper` module. Entities, Components and Processors can be created, assigned, or deleted
80
80
  while your game is running. A simple call to `esper.process()` is all that's needed for each
81
81
  iteration of your game loop. Advanced users can switch contexts, which can be useful for
@@ -84,18 +84,19 @@ isolating different game scenes that have different Processor requirements.
84
84
 
85
85
  * Entities
86
86
 
87
- Entities are simple integer IDs (1, 2, 3, 4, etc.).
88
- Entities are "created", but they are generally not used directly. Instead, they are
89
- simply used as IDs in the internal Component database to track collections of Components.
90
- Creating an Entity is done with the `esper.create_entity()` function.
91
-
87
+ Entities are defined internally as plain integer IDs (1, 2, 3, 4, etc.). Generally speaking
88
+ you should not need to care about the individual entity IDs, since entities are queried based
89
+ on their specific combination of Components - not by their ID. An Entity can be thought of as
90
+ a specific combination of Components. Creating an Entity is done with the `esper.create_entity()`
91
+ function. You can pass Component instances on creation or add/remove them later.
92
92
 
93
93
  * Components
94
94
 
95
- Components are defined as simple Python classes. In keeping with a pure Entity System
96
- design philosophy, they should not contain any logic. They might have initialization
97
- code or perhaps Python properties, but no processing logic whatsoever. A simple
98
- Component can be defined as::
95
+ Components are defined as simple Python classes. In keeping with a pure Entity System design
96
+ philosophy, Components should not contain any processing logic. They may contain initialization
97
+ logic, and you can take advantage of Python language features, like properties, to simplify data
98
+ lookup. The key point is that game logic does not belong in these classes, and Components should
99
+ have no knowledge of other Components or Entities. A simple Component can be defined as::
99
100
 
100
101
  class Position:
101
102
  def __init__(self, x=0.0, y=0.0):
@@ -114,11 +115,26 @@ be repeated, and you can still instantiate the Component with positional or keyw
114
115
  x: float = 0.0
115
116
  y: float = 0.0
116
117
 
118
+ Python language features, like properties, can be useful to simplify data access. For example,
119
+ a Body component that is often repositioned may benefit from a local AABB (axis aligned bounding
120
+ box) property::
121
+
122
+ @dataclass
123
+ class Body:
124
+ width: int
125
+ height: int
126
+ pos_x: float = 0
127
+ pos_y: float = 0
128
+
129
+ @property
130
+ def aabb(self) -> tuple[float, float, float, float]:
131
+ return self.pos_x, self.pos_y, self.pos_x + self.width, self.pos_y + self.height
132
+
117
133
 
118
134
  * Processors
119
135
 
120
136
  Processors, also commonly known as "Systems", are where all processing logic is defined and executed.
121
- All Processors must inherit from the *esper.Processor* class, and have a method called *process*.
137
+ All Processors must inherit from the `esper.Processor` class, and have a method called `process`.
122
138
  Other than that, there are no restrictions. You can define any additional methods you might need.
123
139
  A simple Processor might look like::
124
140
 
@@ -129,21 +145,31 @@ A simple Processor might look like::
129
145
  pos.x += vel.x
130
146
  pos.y += vel.y
131
147
 
132
- In the above code, you can see the standard usage of the *esper.get_components()* function. This
148
+ In the above code, you can see the standard usage of the `esper.get_components()` function. This
133
149
  function allows efficient iteration over all Entities that contain the specified Component types.
134
150
  This function can be used for querying two or more components at once. Note that tuple unpacking
135
- is necessary for the return component pairs: *(vel, pos)*. In addition the Components, you also
136
- get a reference to the Entity ID (the *ent* object) for the current pair of Velocity/Position
137
- Components. This entity ID can be useful in a variety of cases. For example, if your Processor
138
- will need to delete certain Entites, you can call the *esper.delete_entity()* function on
139
- this Entity ID. Another common use is if you wish to add or remove a Component on this Entity
140
- as a result of some condition being met.
151
+ is necessary for the return component pairs: `(vel, pos)`. In addition to Components, you also
152
+ get a reference to the Entity ID for the current pair of Velocity/Position Components. This entity
153
+ ID can be useful in a variety of cases. For example, if your Processor will need to delete certain
154
+ Entites, you can call the `esper.delete_entity()` function on this Entity ID. Another common use
155
+ is if you wish to add or remove a Component on this Entity as a result of some condition being met.
156
+ For example, an Entity that should be deleted once it's `Lifecycle` Component reaches 0::
157
+
158
+ class LifecycleProcessor(esper.Processor):
159
+ def __init__(self, ...):
160
+ ...
161
+
162
+ def process(self, dt):
163
+ for ent, (life, rend) in esper.get_components(Lifecycle, Renderable):
164
+ life.lifespan -= dt
165
+ if life.lifespan <= 0:
166
+ esper.delete_entity(ent)
141
167
 
142
168
 
143
169
  Quick Start
144
170
  ===========
145
171
 
146
- To get started, simply import esper::
172
+ To get started, simply import **esper**::
147
173
 
148
174
  import esper
149
175
 
@@ -159,7 +185,7 @@ Optionally, Component instances can be assigned directly to the Entity on creati
159
185
 
160
186
 
161
187
  Design some Processors that operate on these Component types, and then register them with
162
- Esper for processing. You can specify an optional priority (higher numbers are processed first).
188
+ **esper** for processing. You can specify an optional priority (higher numbers are processed first).
163
189
  All Processors are priority "0" by default::
164
190
 
165
191
  movement_processor = MovementProcessor()
@@ -172,18 +198,17 @@ All Processors are priority "0" by default::
172
198
  esper.add_processor(SomeProcessor())
173
199
 
174
200
 
175
- Executing all Processors is done with a single call to esper.process(). This will call the
201
+ Executing all Processors is done with a single call to `esper.process()`. This will call the
176
202
  `process` method on all assigned Processors, in order of their priority. This is usually called
177
203
  once per frame update of your game (every tick of the clock).::
178
204
 
179
205
  esper.process()
180
206
 
181
207
 
182
- **Note:** You can pass any arguments (or keyword arguments) you need to *esper.process()*, but you
183
- must also make sure to receive them properly in the *process()* methods of your Processors. For
184
- example, if you pass a delta time argument as *esper.process(dt)*, your Processor's *process()*
185
- methods should all receive it as:
186
- *def process(self, dt):*
208
+ **Note:** You can pass any arguments (or keyword arguments) you need to `esper.process()`, but you
209
+ must also make sure to receive them properly in the `process()` methods of your Processors. For
210
+ example, if you pass a delta time argument as `esper.process(dt)`, your Processor's `process()`
211
+ methods should all receive it as: `def process(self, dt):`
187
212
  This is appropriate for libraries such as **pyglet**, which automatically pass a delta time value
188
213
  into scheduled functions.
189
214
 
@@ -193,14 +218,13 @@ General Usage
193
218
 
194
219
  World Contexts
195
220
  --------------
196
- Esper has the capability of supporting multiple "World" contexts. On import, a "default" World is
221
+ **esper** has the capability of supporting multiple "World" contexts. On import, a "default" World is
197
222
  active. All creation of Entities, assignment of Processors, and all other operations occur within
198
223
  the confines of the active World. In other words, the World contexts are completely isolated from
199
224
  each other. For basic games and designs, you may not need to bother with this functionality. A
200
225
  single default World context can often be enough. For advanced use cases, such as when different
201
226
  scenes in your game have different Entities and Processor requirements, this functionality can be
202
227
  quite useful. World context operations are done with the following functions::
203
- *
204
228
  * esper.list_worlds()
205
229
  * esper.switch_world(name)
206
230
  * esper.delete_world(name)
@@ -212,7 +236,7 @@ delete the currently active World.
212
236
  Adding and Removing Processors
213
237
  ------------------------------
214
238
  You have already seen examples of adding Processors in an earlier section. There is also a
215
- *remove_processor* function available:
239
+ `remove_processor` function available:
216
240
 
217
241
  * esper.add_processor(processor_instance)
218
242
  * esper.remove_processor(ProcessorClass)
@@ -228,10 +252,10 @@ remove Components inside your Processors. The following functions are available
228
252
  * esper.add_component(entity_id, component_instance)
229
253
  * esper.remove_component(entity_id, ComponentClass)
230
254
 
231
- As an example of this, you could have a "Blink" component with a *duration* attribute. This can be used
255
+ As an example of this, you could have a "Blink" component with a `duration` attribute. This can be used
232
256
  to make certain things blink for s specific period of time, then disappear. For example, the code below
233
257
  shows a simplified case of adding this Component to an Entity when it takes damage in one processor. A
234
- dedicated *BlinkProcessor* handles the effect, and then removes the Component after the duration expires::
258
+ dedicated `BlinkProcessor` handles the effect, and then removes the Component after the duration expires::
235
259
 
236
260
  class BlinkComponent:
237
261
  def __init__(self, duration):
@@ -274,16 +298,16 @@ to it, the following functions are available:
274
298
  * esper.component_for_entity
275
299
  * esper.components_for_entity
276
300
 
277
- The *component_for_entity* function is useful in a limited number of cases where you know a specific
301
+ The `component_for_entity` function is useful in a limited number of cases where you know a specific
278
302
  Entity ID, and wish to get a specific Component for it. An error is raised if the Component does not
279
- exist for the Entity ID, so it may be more useful when combined with the *has_component*
303
+ exist for the Entity ID, so it may be more useful when combined with the `has_component`
280
304
  function that is explained in the next section. For example::
281
305
 
282
306
  if esper.has_component(ent, SFX):
283
307
  sfx = esper.component_for_entity(ent, SFX)
284
308
  sfx.play()
285
309
 
286
- The *components_for_entity* function is a special function that returns ALL the Components that are
310
+ The `components_for_entity` function is a special function that returns ALL the Components that are
287
311
  assigned to a specific Entity, as a tuple. This is a heavy operation, and not something you would
288
312
  want to do each frame or inside your `Processor.process` method. It can be useful, however, if
289
313
  you wanted to transfer all of a specific Entity's Components between two separate contexts
@@ -344,7 +368,7 @@ Let's look at the core part of the code::
344
368
  stun = esper.component_for_entity(ent, Stun)
345
369
  stun.duration -= dt
346
370
 
347
- This code works fine, but the *try_component* function can accomplish the same thing with one
371
+ This code works fine, but the `try_component` function can accomplish the same thing with one
348
372
  less function call. The following example will get a specific Component if it exists, or
349
373
  return None if it does not::
350
374
 
@@ -362,12 +386,12 @@ functions even more concise ::
362
386
  More Examples
363
387
  -------------
364
388
 
365
- See the **/examples** folder to get an idea of how the basic structure of a game might look.
389
+ See the `/examples` folder to get an idea of how the basic structure of a game might look.
366
390
 
367
391
  Event Dispatching
368
392
  =================
369
393
 
370
- Esper includes basic support for event dispatching and handling. This functionality is
394
+ **esper** includes basic support for event dispatching and handling. This functionality is
371
395
  provided by three functions to set (register), remove, and dispatch events. Minimal error
372
396
  checking is done, so it's left up to the user to ensure correct naming and number of
373
397
  arguments are used when dispatching and receiving events.
@@ -397,7 +421,7 @@ Registered events and handlers are part of the current `World` context.
397
421
  Contributing
398
422
  ============
399
423
 
400
- Contributions to Esper are always welcome, but there are some specific project goals to keep in mind:
424
+ Contributions to **esper** are always welcome, but there are some specific project goals to keep in mind:
401
425
 
402
426
  - Pure Python code only: no binary extensions, Cython, etc.
403
427
  - Try to target all non-EOL Python versions. Exceptions can be made if there is a compelling reason.
@@ -2,12 +2,12 @@
2
2
  [![rtd](https://readthedocs.org/projects/esper/badge/?version=latest)](https://esper.readthedocs.io)
3
3
  [![PyTest](https://github.com/benmoran56/esper/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/benmoran56/esper/actions/workflows/unit-tests.yml)
4
4
 
5
- Esper is a lightweight Entity System module for Python, with a focus on performance
5
+ esper is a lightweight Entity System module for Python, with a focus on performance
6
6
  ===================================================================================
7
7
 
8
- Esper is an MIT licensed Entity System, or, Entity Component System (ECS).
8
+ **esper** is an MIT licensed Entity System, or, Entity Component System (ECS).
9
9
  The design is based on the Entity System concepts originally popularized by
10
- Adam Martin and others. The primary focus for esper is to maximize perfomance,
10
+ Adam Martin and others. The primary focus for **esper** is to maximize perfomance,
11
11
  while handling most common use cases.
12
12
 
13
13
  For more information on the ECS pattern, you might find the following
@@ -20,7 +20,7 @@ API documentation is hosted at ReadTheDocs: https://esper.readthedocs.io
20
20
  Due to the small size of the project, this README currently serves as general usage
21
21
  documentation.
22
22
 
23
- > :warning: **Esper 3.0 introduces breaking changes**. Version 3.0 removes the
23
+ > :warning: **esper 3.0 introduces breaking changes**. Version 3.0 removes the
24
24
  > World object, and migrates its methods to module level functions. Multiple
25
25
  > contexts can be created and switched between. The v2.x README can be found
26
26
  > here: https://github.com/benmoran56/esper/blob/v2_maintenance/README.md
@@ -41,15 +41,15 @@ documentation.
41
41
 
42
42
  Compatibility
43
43
  =============
44
- Esper attempts to target all currently supported Python releases (any Python version that is
45
- not EOL). Esper is written in 100% pure Python, so *any* compliant interpreter should work.
44
+ **esper** attempts to target all currently supported Python releases (any Python version that is
45
+ not EOL). **esper** is written in 100% pure Python, so *any* compliant interpreter should work.
46
46
  Automated testing is currently done for both CPython and PyPy3.
47
47
 
48
48
 
49
49
  Installation
50
50
  ============
51
- Esper is a pure Python package with no dependencies, so installation is flexible.
52
- You can simply copy the *esper* folder right into your project, and *import esper*.
51
+ **esper** is a pure Python package with no dependencies, so installation is flexible.
52
+ You can simply copy the `esper` folder right into your project, and `import esper`.
53
53
  You can also install into your site-packages from PyPi via `pip`::
54
54
 
55
55
  pip install --user --upgrade esper
@@ -64,8 +64,8 @@ Design
64
64
 
65
65
  * World Context
66
66
 
67
- Esper uses the concept of "World" contexts. When you first `import esper`, a default context is
68
- active. You create Entities, assign Components, register Processesors, etc., by calling functions
67
+ **esper** uses the concept of "World" contexts. When you first `import esper`, a default context is
68
+ active. You create Entities, assign Components, register Processors, etc., by calling functions
69
69
  on the `esper` module. Entities, Components and Processors can be created, assigned, or deleted
70
70
  while your game is running. A simple call to `esper.process()` is all that's needed for each
71
71
  iteration of your game loop. Advanced users can switch contexts, which can be useful for
@@ -74,18 +74,19 @@ isolating different game scenes that have different Processor requirements.
74
74
 
75
75
  * Entities
76
76
 
77
- Entities are simple integer IDs (1, 2, 3, 4, etc.).
78
- Entities are "created", but they are generally not used directly. Instead, they are
79
- simply used as IDs in the internal Component database to track collections of Components.
80
- Creating an Entity is done with the `esper.create_entity()` function.
81
-
77
+ Entities are defined internally as plain integer IDs (1, 2, 3, 4, etc.). Generally speaking
78
+ you should not need to care about the individual entity IDs, since entities are queried based
79
+ on their specific combination of Components - not by their ID. An Entity can be thought of as
80
+ a specific combination of Components. Creating an Entity is done with the `esper.create_entity()`
81
+ function. You can pass Component instances on creation or add/remove them later.
82
82
 
83
83
  * Components
84
84
 
85
- Components are defined as simple Python classes. In keeping with a pure Entity System
86
- design philosophy, they should not contain any logic. They might have initialization
87
- code or perhaps Python properties, but no processing logic whatsoever. A simple
88
- Component can be defined as::
85
+ Components are defined as simple Python classes. In keeping with a pure Entity System design
86
+ philosophy, Components should not contain any processing logic. They may contain initialization
87
+ logic, and you can take advantage of Python language features, like properties, to simplify data
88
+ lookup. The key point is that game logic does not belong in these classes, and Components should
89
+ have no knowledge of other Components or Entities. A simple Component can be defined as::
89
90
 
90
91
  class Position:
91
92
  def __init__(self, x=0.0, y=0.0):
@@ -104,11 +105,26 @@ be repeated, and you can still instantiate the Component with positional or keyw
104
105
  x: float = 0.0
105
106
  y: float = 0.0
106
107
 
108
+ Python language features, like properties, can be useful to simplify data access. For example,
109
+ a Body component that is often repositioned may benefit from a local AABB (axis aligned bounding
110
+ box) property::
111
+
112
+ @dataclass
113
+ class Body:
114
+ width: int
115
+ height: int
116
+ pos_x: float = 0
117
+ pos_y: float = 0
118
+
119
+ @property
120
+ def aabb(self) -> tuple[float, float, float, float]:
121
+ return self.pos_x, self.pos_y, self.pos_x + self.width, self.pos_y + self.height
122
+
107
123
 
108
124
  * Processors
109
125
 
110
126
  Processors, also commonly known as "Systems", are where all processing logic is defined and executed.
111
- All Processors must inherit from the *esper.Processor* class, and have a method called *process*.
127
+ All Processors must inherit from the `esper.Processor` class, and have a method called `process`.
112
128
  Other than that, there are no restrictions. You can define any additional methods you might need.
113
129
  A simple Processor might look like::
114
130
 
@@ -119,21 +135,31 @@ A simple Processor might look like::
119
135
  pos.x += vel.x
120
136
  pos.y += vel.y
121
137
 
122
- In the above code, you can see the standard usage of the *esper.get_components()* function. This
138
+ In the above code, you can see the standard usage of the `esper.get_components()` function. This
123
139
  function allows efficient iteration over all Entities that contain the specified Component types.
124
140
  This function can be used for querying two or more components at once. Note that tuple unpacking
125
- is necessary for the return component pairs: *(vel, pos)*. In addition the Components, you also
126
- get a reference to the Entity ID (the *ent* object) for the current pair of Velocity/Position
127
- Components. This entity ID can be useful in a variety of cases. For example, if your Processor
128
- will need to delete certain Entites, you can call the *esper.delete_entity()* function on
129
- this Entity ID. Another common use is if you wish to add or remove a Component on this Entity
130
- as a result of some condition being met.
141
+ is necessary for the return component pairs: `(vel, pos)`. In addition to Components, you also
142
+ get a reference to the Entity ID for the current pair of Velocity/Position Components. This entity
143
+ ID can be useful in a variety of cases. For example, if your Processor will need to delete certain
144
+ Entites, you can call the `esper.delete_entity()` function on this Entity ID. Another common use
145
+ is if you wish to add or remove a Component on this Entity as a result of some condition being met.
146
+ For example, an Entity that should be deleted once it's `Lifecycle` Component reaches 0::
147
+
148
+ class LifecycleProcessor(esper.Processor):
149
+ def __init__(self, ...):
150
+ ...
151
+
152
+ def process(self, dt):
153
+ for ent, (life, rend) in esper.get_components(Lifecycle, Renderable):
154
+ life.lifespan -= dt
155
+ if life.lifespan <= 0:
156
+ esper.delete_entity(ent)
131
157
 
132
158
 
133
159
  Quick Start
134
160
  ===========
135
161
 
136
- To get started, simply import esper::
162
+ To get started, simply import **esper**::
137
163
 
138
164
  import esper
139
165
 
@@ -149,7 +175,7 @@ Optionally, Component instances can be assigned directly to the Entity on creati
149
175
 
150
176
 
151
177
  Design some Processors that operate on these Component types, and then register them with
152
- Esper for processing. You can specify an optional priority (higher numbers are processed first).
178
+ **esper** for processing. You can specify an optional priority (higher numbers are processed first).
153
179
  All Processors are priority "0" by default::
154
180
 
155
181
  movement_processor = MovementProcessor()
@@ -162,18 +188,17 @@ All Processors are priority "0" by default::
162
188
  esper.add_processor(SomeProcessor())
163
189
 
164
190
 
165
- Executing all Processors is done with a single call to esper.process(). This will call the
191
+ Executing all Processors is done with a single call to `esper.process()`. This will call the
166
192
  `process` method on all assigned Processors, in order of their priority. This is usually called
167
193
  once per frame update of your game (every tick of the clock).::
168
194
 
169
195
  esper.process()
170
196
 
171
197
 
172
- **Note:** You can pass any arguments (or keyword arguments) you need to *esper.process()*, but you
173
- must also make sure to receive them properly in the *process()* methods of your Processors. For
174
- example, if you pass a delta time argument as *esper.process(dt)*, your Processor's *process()*
175
- methods should all receive it as:
176
- *def process(self, dt):*
198
+ **Note:** You can pass any arguments (or keyword arguments) you need to `esper.process()`, but you
199
+ must also make sure to receive them properly in the `process()` methods of your Processors. For
200
+ example, if you pass a delta time argument as `esper.process(dt)`, your Processor's `process()`
201
+ methods should all receive it as: `def process(self, dt):`
177
202
  This is appropriate for libraries such as **pyglet**, which automatically pass a delta time value
178
203
  into scheduled functions.
179
204
 
@@ -183,14 +208,13 @@ General Usage
183
208
 
184
209
  World Contexts
185
210
  --------------
186
- Esper has the capability of supporting multiple "World" contexts. On import, a "default" World is
211
+ **esper** has the capability of supporting multiple "World" contexts. On import, a "default" World is
187
212
  active. All creation of Entities, assignment of Processors, and all other operations occur within
188
213
  the confines of the active World. In other words, the World contexts are completely isolated from
189
214
  each other. For basic games and designs, you may not need to bother with this functionality. A
190
215
  single default World context can often be enough. For advanced use cases, such as when different
191
216
  scenes in your game have different Entities and Processor requirements, this functionality can be
192
217
  quite useful. World context operations are done with the following functions::
193
- *
194
218
  * esper.list_worlds()
195
219
  * esper.switch_world(name)
196
220
  * esper.delete_world(name)
@@ -202,7 +226,7 @@ delete the currently active World.
202
226
  Adding and Removing Processors
203
227
  ------------------------------
204
228
  You have already seen examples of adding Processors in an earlier section. There is also a
205
- *remove_processor* function available:
229
+ `remove_processor` function available:
206
230
 
207
231
  * esper.add_processor(processor_instance)
208
232
  * esper.remove_processor(ProcessorClass)
@@ -218,10 +242,10 @@ remove Components inside your Processors. The following functions are available
218
242
  * esper.add_component(entity_id, component_instance)
219
243
  * esper.remove_component(entity_id, ComponentClass)
220
244
 
221
- As an example of this, you could have a "Blink" component with a *duration* attribute. This can be used
245
+ As an example of this, you could have a "Blink" component with a `duration` attribute. This can be used
222
246
  to make certain things blink for s specific period of time, then disappear. For example, the code below
223
247
  shows a simplified case of adding this Component to an Entity when it takes damage in one processor. A
224
- dedicated *BlinkProcessor* handles the effect, and then removes the Component after the duration expires::
248
+ dedicated `BlinkProcessor` handles the effect, and then removes the Component after the duration expires::
225
249
 
226
250
  class BlinkComponent:
227
251
  def __init__(self, duration):
@@ -264,16 +288,16 @@ to it, the following functions are available:
264
288
  * esper.component_for_entity
265
289
  * esper.components_for_entity
266
290
 
267
- The *component_for_entity* function is useful in a limited number of cases where you know a specific
291
+ The `component_for_entity` function is useful in a limited number of cases where you know a specific
268
292
  Entity ID, and wish to get a specific Component for it. An error is raised if the Component does not
269
- exist for the Entity ID, so it may be more useful when combined with the *has_component*
293
+ exist for the Entity ID, so it may be more useful when combined with the `has_component`
270
294
  function that is explained in the next section. For example::
271
295
 
272
296
  if esper.has_component(ent, SFX):
273
297
  sfx = esper.component_for_entity(ent, SFX)
274
298
  sfx.play()
275
299
 
276
- The *components_for_entity* function is a special function that returns ALL the Components that are
300
+ The `components_for_entity` function is a special function that returns ALL the Components that are
277
301
  assigned to a specific Entity, as a tuple. This is a heavy operation, and not something you would
278
302
  want to do each frame or inside your `Processor.process` method. It can be useful, however, if
279
303
  you wanted to transfer all of a specific Entity's Components between two separate contexts
@@ -334,7 +358,7 @@ Let's look at the core part of the code::
334
358
  stun = esper.component_for_entity(ent, Stun)
335
359
  stun.duration -= dt
336
360
 
337
- This code works fine, but the *try_component* function can accomplish the same thing with one
361
+ This code works fine, but the `try_component` function can accomplish the same thing with one
338
362
  less function call. The following example will get a specific Component if it exists, or
339
363
  return None if it does not::
340
364
 
@@ -352,12 +376,12 @@ functions even more concise ::
352
376
  More Examples
353
377
  -------------
354
378
 
355
- See the **/examples** folder to get an idea of how the basic structure of a game might look.
379
+ See the `/examples` folder to get an idea of how the basic structure of a game might look.
356
380
 
357
381
  Event Dispatching
358
382
  =================
359
383
 
360
- Esper includes basic support for event dispatching and handling. This functionality is
384
+ **esper** includes basic support for event dispatching and handling. This functionality is
361
385
  provided by three functions to set (register), remove, and dispatch events. Minimal error
362
386
  checking is done, so it's left up to the user to ensure correct naming and number of
363
387
  arguments are used when dispatching and receiving events.
@@ -387,7 +411,7 @@ Registered events and handlers are part of the current `World` context.
387
411
  Contributing
388
412
  ============
389
413
 
390
- Contributions to Esper are always welcome, but there are some specific project goals to keep in mind:
414
+ Contributions to **esper** are always welcome, but there are some specific project goals to keep in mind:
391
415
 
392
416
  - Pure Python code only: no binary extensions, Cython, etc.
393
417
  - Try to target all non-EOL Python versions. Exceptions can be made if there is a compelling reason.
@@ -1,3 +1,14 @@
1
+ esper 3.3
2
+ =========
3
+ Maintenance release
4
+
5
+ Changes
6
+ -------
7
+ - Fix unreadable `esper.current_world` property. (#100)
8
+ - Minor typing configuration updates.
9
+ - Remove outdated pyglet example. (Another one already exists).
10
+
11
+
1
12
  esper 3.2
2
13
  =========
3
14
  Maintenance release
@@ -9,10 +9,28 @@ Processors
9
9
  Components
10
10
  ----------
11
11
  **esper** does not define any specific Component base class
12
- to inherit from. Instead, a normal Python class can be used.
13
- Also, while it's not required, the the `@dataclass` decorator
14
- from the `dataclasses` module can be useful to help write
15
- compact Component classes.
12
+ to inherit from. Any valid Python class can be used. For more
13
+ compact definitions, the ``@dataclass`` decorator from the
14
+ ``dataclasses`` module is quite useful. You can also use a
15
+ ``namedtuple`` instead of a class, but this is limited to
16
+ cases where the Component's data does not need to be modified.
17
+ Three examples of valid Components::
18
+
19
+ class Velocity:
20
+ def __init__(self, x=0.0, y=0.0, accel=0.1, decel=0.75, maximum=3):
21
+ self.vector = Vec2(x, y)
22
+ self.accel = accel
23
+ self.decel = decel
24
+ self.maximum = maximum
25
+
26
+
27
+ @dataclass
28
+ class Camera:
29
+ current_x_offset: float = 0
30
+ current_y_offset: float = 0
31
+
32
+
33
+ Interaction = namedtuple('Interaction', 'interaction_type target_name')
16
34
 
17
35
 
18
36
  The World context
@@ -23,7 +23,7 @@ from weakref import WeakMethod as _WeakMethod
23
23
 
24
24
  from itertools import count as _count
25
25
 
26
- __version__ = version = '3.2'
26
+ __version__ = version = '3.4'
27
27
 
28
28
 
29
29
  ###################
@@ -81,10 +81,11 @@ def remove_handler(name: str, func: _Callable[..., None]) -> None:
81
81
  receive the named event, or if the named event does
82
82
  not exist, this function call will pass silently.
83
83
  """
84
- if func not in event_registry.get(name, []):
84
+ func_ref = _ref(func)
85
+ if func_ref not in event_registry.get(name, []):
85
86
  return
86
87
 
87
- event_registry[name].remove(func)
88
+ event_registry[name].remove(func_ref)
88
89
  if not event_registry[name]:
89
90
  del event_registry[name]
90
91
 
@@ -131,7 +132,7 @@ class Processor:
131
132
  # ECS functions
132
133
  ###################
133
134
 
134
- _current_context: str = "default"
135
+ _current_world: str = "default"
135
136
  _entity_count: "_count[int]" = _count(start=1)
136
137
  _components: _Dict[_Type[_Any], _Set[_Any]] = {}
137
138
  _entities: _Dict[int, _Dict[_Type[_Any], _Any]] = {}
@@ -139,10 +140,13 @@ _dead_entities: _Set[int] = set()
139
140
  _get_component_cache: _Dict[_Type[_Any], _List[_Any]] = {}
140
141
  _get_components_cache: _Dict[_Tuple[_Type[_Any], ...], _List[_Any]] = {}
141
142
  _processors: _List[Processor] = []
142
- process_times: _Dict[str, int] = {}
143
143
  event_registry: _Dict[str, _Any] = {}
144
+ process_times: _Dict[str, int] = {}
145
+ current_world: str = "default"
144
146
 
145
- # {context_name: (entity_count, components, entities, dead_entities, comp_cache, comps_cache, processors, process_times, event_registry)}
147
+
148
+ # {context_name: (entity_count, components, entities, dead_entities,
149
+ # comp_cache, comps_cache, processors, process_times, event_registry)}
146
150
  _context_map: _Dict[str, _Tuple[
147
151
  "_count[int]",
148
152
  _Dict[_Type[_Any], _Set[_Any]],
@@ -228,10 +232,10 @@ def get_processor(processor_type: _Type[Processor]) -> _Optional[Processor]:
228
232
  def create_entity(*components: _C) -> int:
229
233
  """Create a new Entity, with optional initial Components.
230
234
 
231
- This funcion returns an Entity ID, which is a plain integer.
235
+ This function returns an Entity ID, which is a plain integer.
232
236
  You can optionally pass one or more Component instances to be
233
237
  assigned to the Entity on creation. Components can be also be
234
- added later with the :py:func:`esper.add_component` funcion.
238
+ added later with the :py:func:`esper.add_component` function.
235
239
  """
236
240
  entity = next(_entity_count)
237
241
 
@@ -321,7 +325,8 @@ def has_component(entity: int, component_type: _Type[_C]) -> bool:
321
325
 
322
326
  def has_components(entity: int, *component_types: _Type[_C]) -> bool:
323
327
  """Check if an Entity has all the specified Component types."""
324
- return all(comp_type in _entities[entity] for comp_type in component_types)
328
+ components_dict = _entities[entity]
329
+ return all(comp_type in components_dict for comp_type in component_types)
325
330
 
326
331
 
327
332
  def add_component(entity: int, component_instance: _C, type_alias: _Optional[_Type[_C]] = None) -> None:
@@ -387,9 +392,7 @@ def get_component(component_type: _Type[_C]) -> _List[_Tuple[int, _C]]:
387
392
  try:
388
393
  return _get_component_cache[component_type]
389
394
  except KeyError:
390
- return _get_component_cache.setdefault(
391
- component_type, list(_get_component(component_type))
392
- )
395
+ return _get_component_cache.setdefault(component_type, list(_get_component(component_type)))
393
396
 
394
397
 
395
398
  @_overload
@@ -404,7 +407,7 @@ def get_components(__c1: _Type[_C], __c2: _Type[_C2], __c3: _Type[_C3]) -> _List
404
407
 
405
408
  @_overload
406
409
  def get_components(__c1: _Type[_C], __c2: _Type[_C2], __c3: _Type[_C3], __c4: _Type[_C4]) -> _List[
407
- _Tuple[int, _Tuple[_C, _C2, _C3, _C4]]]:
410
+ _Tuple[int, _Tuple[_C, _C2, _C3, _C4]]]:
408
411
  ...
409
412
 
410
413
 
@@ -413,9 +416,7 @@ def get_components(*component_types: _Type[_Any]) -> _Iterable[_Tuple[int, _Tupl
413
416
  try:
414
417
  return _get_components_cache[component_types]
415
418
  except KeyError:
416
- return _get_components_cache.setdefault(
417
- component_types, list(_get_components(*component_types))
418
- )
419
+ return _get_components_cache.setdefault(component_types, list(_get_components(*component_types)))
419
420
 
420
421
 
421
422
  def try_component(entity: int, component_type: _Type[_C]) -> _Optional[_C]:
@@ -447,7 +448,7 @@ def try_components(entity: int, __c1: _Type[_C], __c2: _Type[_C2], __c3: _Type[_
447
448
 
448
449
 
449
450
  def try_components(entity: int, *component_types: _Type[_C]) -> _Optional[_Tuple[_C, ...]]:
450
- """Try to get a multiple component types for an Entity.
451
+ """Try to get multiple component types for an Entity.
451
452
 
452
453
  This function will return the requested Components if they exist,
453
454
  or None if they do not. This allows a way to access optional Components
@@ -502,7 +503,8 @@ def timed_process(*args: _Any, **kwargs: _Any) -> None:
502
503
 
503
504
  This function is identical to :py:func:`esper.process`, but
504
505
  it additionally records the elapsed time of each processor
505
- call (in milliseconds) in the`esper.process_times` dictionary.
506
+ (in milliseconds) in the :py:attr:`~process_times` dictionary
507
+ after each call.
506
508
  """
507
509
  clear_dead_entities()
508
510
  for processor in _processors:
@@ -525,7 +527,7 @@ def delete_world(name: str) -> None:
525
527
  Raises `PermissionError` if you attempt to delete the currently
526
528
  active World context.
527
529
  """
528
- if _current_context == name:
530
+ if _current_world == name:
529
531
  raise PermissionError("The active World context cannot be deleted.")
530
532
 
531
533
  del _context_map[name]
@@ -537,20 +539,24 @@ def switch_world(name: str) -> None:
537
539
  Esper can have one or more "Worlds". Each World is a dedicated
538
540
  context, and does not share Entities, Components, events, etc.
539
541
  Some game designs can benefit from using a dedicated World
540
- for each scene. For other designs, a single World may
541
- be sufficient.
542
+ for each scene. For other designs, a single World may be sufficient.
542
543
 
543
544
  This function will allow you to create and switch between as
544
545
  many World contexts as required. If the requested name does not
545
546
  exist, a new context is created automatically with that name.
546
547
 
548
+ The name of the currently active World context can be checked
549
+ at any time by examining the :py:attr:`esper.current_world`.
550
+ This attribute gets updated whenever you switch Worlds, and
551
+ modifying it has no effect.
552
+
547
553
  .. note:: At startup, a "default" World context is active.
548
554
  """
549
555
  if name not in _context_map:
550
556
  # Create a new context if the name does not already exist:
551
557
  _context_map[name] = (_count(start=1), {}, {}, set(), {}, {}, [], {}, {})
552
558
 
553
- global _current_context
559
+ global _current_world
554
560
  global _entity_count
555
561
  global _components
556
562
  global _entities
@@ -560,17 +566,9 @@ def switch_world(name: str) -> None:
560
566
  global _processors
561
567
  global process_times
562
568
  global event_registry
569
+ global current_world
563
570
 
564
571
  # switch the references to the objects in the named context_map:
565
572
  (_entity_count, _components, _entities, _dead_entities, _get_component_cache,
566
573
  _get_components_cache, _processors, process_times, event_registry) = _context_map[name]
567
- _current_context = name
568
-
569
-
570
- @property # type: ignore
571
- def current_world() -> str:
572
- """The currently active World context.
573
-
574
- To switch World contexts, see :py:func:`esper.switch_world`.
575
- """
576
- return _current_context
574
+ _current_world = current_world = name
@@ -36,7 +36,7 @@ class MovementProcessor:
36
36
  def process(self, dt):
37
37
  # This will iterate over every Entity that has BOTH of these components:
38
38
  for ent, (vel, rend) in esper.get_components(Velocity, Renderable):
39
- # Update the Renderable Component's position by it's Velocity:
39
+ # Update the Renderable Component's position by its Velocity:
40
40
  # An example of keeping the sprite inside screen boundaries. Basically,
41
41
  # adjust the position back inside screen boundaries if it is outside:
42
42
  new_x = max(self.minx, rend.sprite.x + vel.x)
@@ -505,6 +505,16 @@ def test_event_handler_switch_world():
505
505
  esper.dispatch_event("foo")
506
506
  assert called == 2
507
507
 
508
+ def test_remove_handler():
509
+ def handler():
510
+ pass
511
+
512
+ assert esper.event_registry == {}
513
+ esper.set_handler("foo", handler)
514
+ assert "foo" in esper.event_registry
515
+ esper.remove_handler("foo", handler)
516
+ assert esper.event_registry == {}
517
+
508
518
 
509
519
  ##################################################
510
520
  # Some helper functions and Component templates:
esper-3.2/.ruff.toml DELETED
@@ -1,3 +0,0 @@
1
- select = ["F", "E", "W", "C90", "B", "A", "C4", "G", "T20", "PT", "SLF", "TCH", "RET", "N", "ARG"]
2
- ignore = ["E501"]
3
-
@@ -1,245 +0,0 @@
1
- import pyglet
2
- from pyglet.gl import *
3
- from pyglet.window import key
4
-
5
- import esper
6
-
7
-
8
- FPS = 60
9
- RESOLUTION = 720, 480
10
- BGCOLOR = (0, 0, 0, 255)
11
-
12
-
13
- ##################################
14
- # Define some Components:
15
- ##################################
16
- class Velocity:
17
- def __init__(self, x=0.0, y=0.0):
18
- self.x = x
19
- self.y = y
20
-
21
-
22
- class Renderable:
23
- def __init__(self, texture, width, height, posx, posy):
24
- self.texture = texture
25
- self._x = posx
26
- self._y = posy
27
- self.w = width
28
- self.h = height
29
- self.group = TextureBindGroup(texture)
30
- self.vertex_list = None
31
- self._dirty = True
32
-
33
- @property
34
- def x(self):
35
- return self._x
36
-
37
- @x.setter
38
- def x(self, val):
39
- if val != self._x:
40
- self._x = val
41
- self._dirty = True
42
-
43
- @property
44
- def y(self):
45
- return self._y
46
-
47
- @y.setter
48
- def y(self, val):
49
- if val != self._y:
50
- self._y = val
51
- self._dirty = True
52
-
53
-
54
- ################################
55
- # Define some Processors:
56
- ################################
57
- class MovementProcessor:
58
- def __init__(self, minx, miny, maxx, maxy):
59
- super().__init__()
60
- self.minx = minx
61
- self.miny = miny
62
- self.maxx = maxx
63
- self.maxy = maxy
64
-
65
- def process(self):
66
- # This will iterate over every Entity that has BOTH of these components:
67
- for ent, (vel, rend) in esper.get_components(Velocity, Renderable):
68
- # Update the Renderable Component's position by it's Velocity:
69
- rend.x += vel.x
70
- rend.y += vel.y
71
- # An example of keeping the sprite inside screen boundaries. Basically,
72
- # adjust the position back inside screen boundaries if it tries to go outside:
73
- rend.x = max(self.minx, rend.x)
74
- rend.y = max(self.miny, rend.y)
75
- rend.x = min(self.maxx - rend.w, rend.x)
76
- rend.y = min(self.maxy - rend.h, rend.y)
77
-
78
-
79
- class TextureRenderProcessor():
80
- def __init__(self, batch):
81
- super().__init__()
82
- self.batch = batch
83
-
84
- def process(self):
85
- # This will iterate over every Entity that has this Component, and
86
- # add the texture associated with the Renderable Component instance
87
- # and its vertice_list to the render batch. The batch will then be
88
- # drawn by the 'on_draw' event handler of teh main window:
89
- for entity, renderable in esper.get_component(Renderable):
90
- self.draw_texture(renderable)
91
-
92
- def draw_texture(self, renderable):
93
- texture = renderable.texture
94
-
95
- if renderable.vertex_list is None:
96
- vertex_format = 'v2i/dynamic'
97
- renderable.vertex_list = self.batch.add(4, GL_QUADS,
98
- renderable.group,
99
- vertex_format, 'c4B',
100
- ('t3f', texture.tex_coords))
101
-
102
- if renderable._dirty:
103
- x1 = renderable.x - texture.anchor_x
104
- y1 = renderable.y - texture.anchor_y
105
- x2 = x1 + texture.width
106
- y2 = y1 + texture.height
107
- renderable.vertex_list.vertices[:] = [x1, y1, x2, y1, x2, y2, x1, y2]
108
- renderable.vertex_list.colors[:] = [255, 255, 255, 255] * 4
109
- renderable._dirty = False
110
-
111
-
112
- ############################################
113
- # Some pyglet helper classes and functions:
114
- ############################################
115
- def texture_from_image(image_name):
116
- """Create a pyglet Texture from an image file"""
117
- return pyglet.resource.image(image_name).get_texture()
118
-
119
-
120
- # Code below cobbled together from
121
- # https://pyglet.readthedocs.org/en/latest/programming_guide/graphics.html#hierarchical-state
122
- # and pyglet.sprite.SpriteGroup
123
-
124
- class TextureEnableGroup(pyglet.graphics.Group):
125
- def set_state(self):
126
- glEnable(GL_TEXTURE_2D)
127
-
128
- def unset_state(self):
129
- glDisable(GL_TEXTURE_2D)
130
-
131
-
132
- texture_enable_group = TextureEnableGroup()
133
-
134
-
135
- class TextureBindGroup(pyglet.graphics.Group):
136
- def __init__(self, texture):
137
- super().__init__(parent=texture_enable_group)
138
- assert texture.target == GL_TEXTURE_2D
139
- self.texture = texture
140
- self.blend_src = GL_SRC_ALPHA
141
- self.blend_dest = GL_ONE_MINUS_SRC_ALPHA
142
-
143
- def set_state(self):
144
- glBindTexture(GL_TEXTURE_2D, self.texture.id)
145
- glPushAttrib(GL_COLOR_BUFFER_BIT)
146
- glEnable(GL_BLEND)
147
- glBlendFunc(self.blend_src, self.blend_dest)
148
-
149
- def unset_state(self):
150
- glPopAttrib()
151
- glDisable(self.texture.target)
152
-
153
- def __eq__(self, other):
154
- return (other.__class__ is self.__class__ and
155
- self.parent is other.parent and
156
- self.texture.target == other.texture.target and
157
- self.texture.id == other.texture.id and
158
- self.blend_src == other.blend_src and
159
- self.blend_dest == other.blend_dest)
160
-
161
- def __hash__(self):
162
- return hash((id(self.parent),
163
- self.texture.id, self.texture.target,
164
- self.blend_src, self.blend_dest))
165
-
166
-
167
- ################################
168
- # The main core of the program:
169
- ################################
170
- def run(args=None):
171
- # Initialize the main window stuff
172
- window = pyglet.window.Window(width=RESOLUTION[0], height=RESOLUTION[1])
173
- window.set_caption("Esper pyglet Example")
174
- pyglet.gl.glClearColor(*BGCOLOR)
175
- # pyglet graphics batch for efficient rendering
176
- renderbatch = pyglet.graphics.Batch()
177
-
178
- # Initialize Esper world, and create a "player" Entity with a few Components.
179
- player = esper.create_entity()
180
- esper.add_component(player, Velocity(x=0, y=0))
181
- redsquare = Renderable(texture=texture_from_image("redsquare.png"),
182
- width=64,
183
- height=64,
184
- posx=100,
185
- posy=100)
186
- esper.add_component(player, redsquare)
187
-
188
- # Another motionless Entity:
189
- enemy = esper.create_entity()
190
- bluesquare = Renderable(texture=texture_from_image("bluesquare.png"),
191
- width=64,
192
- height=64,
193
- posx=400,
194
- posy=250)
195
- esper.add_component(enemy, bluesquare)
196
-
197
- # Create some Processor instances, and asign them to be processed.
198
- render_processor = TextureRenderProcessor(batch=renderbatch)
199
- movement_processor = MovementProcessor(minx=0, maxx=RESOLUTION[0], miny=0,
200
- maxy=RESOLUTION[1])
201
-
202
- @window.event
203
- def on_key_press(symbol, modifiers):
204
- if symbol == key.UP:
205
- # Here is a way to directly access a specific Entity's Velocity
206
- # Component's attribute (y) without making a temporary variable.
207
- esper.component_for_entity(player, Velocity).y = 3
208
- elif symbol == key.DOWN:
209
- # For clarity, here is an alternate way in which a temporary variable
210
- # is created and modified. The previous way above is recommended instead.
211
- player_velocity_component = esper.component_for_entity(player, Velocity)
212
- player_velocity_component.y = -3
213
- elif symbol == key.LEFT:
214
- esper.component_for_entity(player, Velocity).x = -3
215
- elif symbol == key.RIGHT:
216
- esper.component_for_entity(player, Velocity).x = 3
217
- elif symbol == key.ESCAPE:
218
- pyglet.app.exit()
219
-
220
- @window.event
221
- def on_key_release(symbol, modifiers):
222
- if symbol in (key.UP, key.DOWN):
223
- esper.component_for_entity(player, Velocity).y = 0
224
- if symbol in (key.LEFT, key.RIGHT):
225
- esper.component_for_entity(player, Velocity).x = 0
226
-
227
- @window.event
228
- def on_draw():
229
- # Clear the window:
230
- window.clear()
231
- # Draw the batch of Renderables:
232
- renderbatch.draw()
233
-
234
- def update(dt):
235
- # A single call to esper.process() will update all Processors:
236
- render_processor.process()
237
- movement_processor.process()
238
-
239
- pyglet.clock.schedule_interval(update, 1.0 / FPS)
240
- pyglet.app.run()
241
-
242
-
243
- if __name__ == "__main__":
244
- import sys
245
- sys.exit(run(sys.argv[1:]) or 0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes