pyglet-desper 0.10.0__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.
- pyglet_desper-0.10.0/LICENSE +21 -0
- pyglet_desper-0.10.0/PKG-INFO +37 -0
- pyglet_desper-0.10.0/README.md +20 -0
- pyglet_desper-0.10.0/pyglet_desper/__init__.py +3 -0
- pyglet_desper-0.10.0/pyglet_desper/logic/__init__.py +279 -0
- pyglet_desper-0.10.0/pyglet_desper/logic/sync.py +293 -0
- pyglet_desper-0.10.0/pyglet_desper/loop.py +102 -0
- pyglet_desper-0.10.0/pyglet_desper/model.py +515 -0
- pyglet_desper-0.10.0/pyglet_desper.egg-info/PKG-INFO +37 -0
- pyglet_desper-0.10.0/pyglet_desper.egg-info/SOURCES.txt +16 -0
- pyglet_desper-0.10.0/pyglet_desper.egg-info/dependency_links.txt +1 -0
- pyglet_desper-0.10.0/pyglet_desper.egg-info/requires.txt +2 -0
- pyglet_desper-0.10.0/pyglet_desper.egg-info/top_level.txt +1 -0
- pyglet_desper-0.10.0/setup.cfg +4 -0
- pyglet_desper-0.10.0/setup.py +25 -0
- pyglet_desper-0.10.0/tests/test_logic.py +247 -0
- pyglet_desper-0.10.0/tests/test_loop.py +89 -0
- pyglet_desper-0.10.0/tests/test_model.py +331 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Francesco Mistri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyglet-desper
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: Extension package for desper and pyglet interoperation
|
|
5
|
+
Home-page: https://github.com/Ball-Man/pyglet-desper
|
|
6
|
+
Author: Francesco Mistri
|
|
7
|
+
Author-email: franc.mistri@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Topic :: Games/Entertainment
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: desper>=1.1.1
|
|
16
|
+
Requires-Dist: pyglet<2.1,>=2.0.0
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
# Desper + Pyglet interoperation
|
|
21
|
+
Combine resource and logic management of [desper](https://github.com/Ball-Man/desper]) with [pyglet](https://github.com/pyglet/pyglet)'s rendering and media.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
Simple installation from PyPi (recommended):
|
|
25
|
+
```
|
|
26
|
+
pip install pyglet-desper
|
|
27
|
+
```
|
|
28
|
+
Or installation from master branch:
|
|
29
|
+
```
|
|
30
|
+
git clone https://github.com/Ball-Man/pyglet-desper
|
|
31
|
+
cd pyglet-desper
|
|
32
|
+
pip install .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Project status
|
|
37
|
+
Docs and a user guide are in the making.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Desper + Pyglet interoperation
|
|
4
|
+
Combine resource and logic management of [desper](https://github.com/Ball-Man/desper]) with [pyglet](https://github.com/pyglet/pyglet)'s rendering and media.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
Simple installation from PyPi (recommended):
|
|
8
|
+
```
|
|
9
|
+
pip install pyglet-desper
|
|
10
|
+
```
|
|
11
|
+
Or installation from master branch:
|
|
12
|
+
```
|
|
13
|
+
git clone https://github.com/Ball-Man/pyglet-desper
|
|
14
|
+
cd pyglet-desper
|
|
15
|
+
pip install .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Project status
|
|
20
|
+
Docs and a user guide are in the making.
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import desper
|
|
5
|
+
import pyglet
|
|
6
|
+
from pyglet.gl import GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
|
|
7
|
+
|
|
8
|
+
from .sync import * # NOQA
|
|
9
|
+
|
|
10
|
+
ON_CAMERA_DRAW_EVENT_NAME = 'on_camera_draw'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@desper.event_handler('on_switch_in', 'on_switch_out', 'on_add')
|
|
14
|
+
class Sprite(pyglet.sprite.Sprite):
|
|
15
|
+
"""Specialized sprite for better integration into desper.
|
|
16
|
+
|
|
17
|
+
In particular, it listens to some events in order to schedule
|
|
18
|
+
animations correctly. See module :mod:`pyglet.sprite` to know
|
|
19
|
+
more about sprites.
|
|
20
|
+
|
|
21
|
+
This assumes that the default event workflow is being followed.
|
|
22
|
+
That is: world dispatching is disabled after creation and
|
|
23
|
+
enabled just when it is used as current world (i.e. with
|
|
24
|
+
:func:`desper.switch`).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self,
|
|
28
|
+
img, x=0, y=0, z=0,
|
|
29
|
+
blend_src=GL_SRC_ALPHA,
|
|
30
|
+
blend_dest=GL_ONE_MINUS_SRC_ALPHA,
|
|
31
|
+
batch=None,
|
|
32
|
+
group=None,
|
|
33
|
+
subpixel=False):
|
|
34
|
+
super().__init__(img, x, y, z, blend_src, blend_dest, batch, group,
|
|
35
|
+
subpixel)
|
|
36
|
+
# Pause at creation to prevent global clock from scheduling it
|
|
37
|
+
if self._animation is not None:
|
|
38
|
+
self.paused = True
|
|
39
|
+
|
|
40
|
+
__init__.__doc__ = pyglet.sprite.Sprite.__init__.__doc__
|
|
41
|
+
|
|
42
|
+
def on_add(self, entity, world: desper.World):
|
|
43
|
+
"""Start animation."""
|
|
44
|
+
if self._animation is not None:
|
|
45
|
+
self.paused = False
|
|
46
|
+
|
|
47
|
+
def on_switch_in(self, world_from: desper.World, world_to: desper.World):
|
|
48
|
+
"""Start animation."""
|
|
49
|
+
if self._animation is not None:
|
|
50
|
+
self.paused = False
|
|
51
|
+
|
|
52
|
+
def on_switch_out(self, world_from: desper.World, world_to: desper.World):
|
|
53
|
+
"""Stop animation."""
|
|
54
|
+
if self._animation is not None:
|
|
55
|
+
self.paused = True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@desper.event_handler('on_switch_in', 'on_switch_out', 'on_add')
|
|
59
|
+
class AdvancedSprite(pyglet.sprite.AdvancedSprite):
|
|
60
|
+
"""Specialized advanced sprite for better integration into desper.
|
|
61
|
+
|
|
62
|
+
Being an advanced sprite, it is possible to specify its shader
|
|
63
|
+
program at any time through the :attr:`program` attribute (see
|
|
64
|
+
:class:`pyglet.sprite.AdvancedSprite`).
|
|
65
|
+
|
|
66
|
+
In particular, it listens to some events in order to schedule
|
|
67
|
+
animations correctly. See module :mod:`pyglet.sprite` to know
|
|
68
|
+
more about sprites.
|
|
69
|
+
|
|
70
|
+
This assumes that the default event workflow is being followed.
|
|
71
|
+
That is: world dispatching is disabled after creation and
|
|
72
|
+
enabled just when it is used as current world (i.e. with
|
|
73
|
+
:func:`desper.switch`).
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self,
|
|
77
|
+
img, x=0, y=0, z=0,
|
|
78
|
+
blend_src=GL_SRC_ALPHA,
|
|
79
|
+
blend_dest=GL_ONE_MINUS_SRC_ALPHA,
|
|
80
|
+
batch=None,
|
|
81
|
+
group=None,
|
|
82
|
+
subpixel=False,
|
|
83
|
+
program=None):
|
|
84
|
+
super().__init__(img, x, y, z, blend_src, blend_dest, batch, group,
|
|
85
|
+
subpixel, program)
|
|
86
|
+
# Pause at creation to prevent global clock from scheduling it
|
|
87
|
+
if self._animation is not None:
|
|
88
|
+
self.paused = True
|
|
89
|
+
|
|
90
|
+
__init__.__doc__ = pyglet.sprite.Sprite.__init__.__doc__
|
|
91
|
+
|
|
92
|
+
def on_add(self, entity, world: desper.World):
|
|
93
|
+
"""Start animation."""
|
|
94
|
+
if self._animation is not None:
|
|
95
|
+
self.paused = False
|
|
96
|
+
|
|
97
|
+
def on_switch_in(self, world_from: desper.World, world_to: desper.World):
|
|
98
|
+
"""Start animation."""
|
|
99
|
+
if self._animation is not None:
|
|
100
|
+
self.paused = False
|
|
101
|
+
|
|
102
|
+
def on_switch_out(self, world_from: desper.World, world_to: desper.World):
|
|
103
|
+
"""Stop animation."""
|
|
104
|
+
if self._animation is not None:
|
|
105
|
+
self.paused = True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@desper.event_handler(ON_CAMERA_DRAW_EVENT_NAME)
|
|
109
|
+
class Camera:
|
|
110
|
+
"""Render content of a :class:`pyglet.graphics.Batch`.
|
|
111
|
+
|
|
112
|
+
Apply the given projection and viewport before rendering. If
|
|
113
|
+
omitted, projection and viewport will be set as window defaults
|
|
114
|
+
(from pyglet), that is:
|
|
115
|
+
|
|
116
|
+
- orthogonal projection that maps to the window pixel size, with
|
|
117
|
+
origin (0, 0) at the bottom-left corner
|
|
118
|
+
- viewport equal to: ``0, 0, window.width, window.height``
|
|
119
|
+
|
|
120
|
+
A projection matrix can be easily obtained through
|
|
121
|
+
:meth:`desper.math.Mat4.orthogonal_projection` (2D) or through
|
|
122
|
+
:meth:`desper.math.Mat4.perspective_projection` (3D).
|
|
123
|
+
|
|
124
|
+
Viewport can be manually constructed and shall be a tuple in the
|
|
125
|
+
form ``(x, y, width, height)`` (:class:`desper.math.Vec4` is
|
|
126
|
+
supported).
|
|
127
|
+
|
|
128
|
+
A :class:`pyglet.window.Window` can be specified and shall be taken
|
|
129
|
+
as target of these transformations. In case of single window
|
|
130
|
+
applications this is unnecessary and the main window will be
|
|
131
|
+
automatically retrieven.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, batch: pyglet.graphics.Batch,
|
|
135
|
+
projection: Optional[desper.math.Mat4] = None,
|
|
136
|
+
viewport: Optional[tuple[int, int, int, int]] = None,
|
|
137
|
+
window: Optional[pyglet.window.Window] = None):
|
|
138
|
+
self.batch = batch
|
|
139
|
+
|
|
140
|
+
self.window: pyglet.window.Window = window
|
|
141
|
+
if window is None:
|
|
142
|
+
assert len(pyglet.app.windows), (
|
|
143
|
+
'Unable to find an open window')
|
|
144
|
+
self.window = next(iter(pyglet.app.windows))
|
|
145
|
+
|
|
146
|
+
self.projection: desper.math.Mat4 = projection
|
|
147
|
+
if projection is None:
|
|
148
|
+
self.projection = self.window.projection
|
|
149
|
+
|
|
150
|
+
self.viewport: tuple[int, int, int, int] = viewport
|
|
151
|
+
if viewport is None:
|
|
152
|
+
self.viewport = self.window.viewport
|
|
153
|
+
|
|
154
|
+
# View transformation matrix
|
|
155
|
+
self.view = desper.math.Mat4()
|
|
156
|
+
|
|
157
|
+
def on_camera_draw(self):
|
|
158
|
+
"""Event handler: apply projection, view and viewport, render."""
|
|
159
|
+
self.window.projection = self.projection
|
|
160
|
+
self.window.viewport = self.viewport
|
|
161
|
+
self.window.view = self.view
|
|
162
|
+
|
|
163
|
+
self.batch.draw()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@desper.event_handler('on_draw')
|
|
167
|
+
class CameraProcessor(desper.Processor):
|
|
168
|
+
"""Render all cameras (:class:`Camera`).
|
|
169
|
+
|
|
170
|
+
Despite being a :class:`desper.Processor` subclass, no action
|
|
171
|
+
is done during :meth:`process`. The possibility to add it as a
|
|
172
|
+
processor in a :class:`World` is pure convenience, but adding it
|
|
173
|
+
as component in an entity (any entity) works just fine.
|
|
174
|
+
|
|
175
|
+
All the logic take place in the
|
|
176
|
+
:meth:`on_draw` method, which is a handler for the homonymous pyglet
|
|
177
|
+
connected event.
|
|
178
|
+
|
|
179
|
+
Rendering is done by dispatchment of
|
|
180
|
+
:attr:`ON_CAMERA_DRAW_EVENT_NAME` event. :class:`Camera` and
|
|
181
|
+
eventual custom objects handle this event and render accordingly.
|
|
182
|
+
|
|
183
|
+
A :class:`pyglet.window.Window` can be specified and shall be taken
|
|
184
|
+
as target of these transformations. In case of single window
|
|
185
|
+
applications this is unnecessary and the main window will be
|
|
186
|
+
automatically retrieven.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(self, window: Optional[pyglet.window.Window] = None):
|
|
190
|
+
self.window = window
|
|
191
|
+
if window is None:
|
|
192
|
+
assert len(pyglet.app.windows), (
|
|
193
|
+
'Unable to find an open window')
|
|
194
|
+
self.window = next(iter(pyglet.app.windows))
|
|
195
|
+
|
|
196
|
+
def on_draw(self):
|
|
197
|
+
"""Event handler: clear window and render all cameras.
|
|
198
|
+
|
|
199
|
+
Rendering is done by dispatchment of
|
|
200
|
+
:attr:`ON_CAMERA_DRAW_EVENT_NAME`.
|
|
201
|
+
"""
|
|
202
|
+
self.window.clear()
|
|
203
|
+
self.world.dispatch(ON_CAMERA_DRAW_EVENT_NAME)
|
|
204
|
+
|
|
205
|
+
def process(self, dt):
|
|
206
|
+
"""No implementation needed."""
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@desper.event_handler(desper.ON_POSITION_CHANGE_EVENT_NAME,
|
|
211
|
+
desper.ON_ROTATION_CHANGE_EVENT_NAME,
|
|
212
|
+
desper.ON_SCALE_CHANGE_EVENT_NAME)
|
|
213
|
+
class CameraTransform2D(desper.Controller):
|
|
214
|
+
"""Synchronize :class:`Camera` with :class:`desper.Transform2D`.
|
|
215
|
+
|
|
216
|
+
A :class:`Camera`s internal :attr:`Camera.view` matrix is updated
|
|
217
|
+
based on the entity's :class:`desper.Transform2D`.
|
|
218
|
+
Requires to be in the same desper entity of both the camera and the
|
|
219
|
+
transform component.
|
|
220
|
+
"""
|
|
221
|
+
transform: desper.Transform2D = desper.ComponentReference(
|
|
222
|
+
desper.Transform2D)
|
|
223
|
+
camera: Camera = desper.ComponentReference(Camera)
|
|
224
|
+
|
|
225
|
+
# Cached matrices for faster recalculations
|
|
226
|
+
_translation_matrix = desper.math.Mat4()
|
|
227
|
+
_rotation_matrix = desper.math.Mat4()
|
|
228
|
+
_scale_matrix = desper.math.Mat4()
|
|
229
|
+
|
|
230
|
+
def on_add(self, entity, world):
|
|
231
|
+
"""Setup transform event handling."""
|
|
232
|
+
super().on_add(entity, world)
|
|
233
|
+
|
|
234
|
+
assert self.transform is not None and self.camera is not None, (
|
|
235
|
+
'Both a Transform component and a Camera component '
|
|
236
|
+
'are required to be in the same entity in order for '
|
|
237
|
+
f'{type(self)} to work.')
|
|
238
|
+
|
|
239
|
+
transform = self.transform
|
|
240
|
+
transform.add_handler(self)
|
|
241
|
+
|
|
242
|
+
self._translation_matrix = desper.math.Mat4.from_translation(
|
|
243
|
+
(*-transform.position, 0.))
|
|
244
|
+
self._rotation_matrix = desper.math.Mat4.from_rotation(
|
|
245
|
+
math.radians(transform.rotation), (0., 0., 1.))
|
|
246
|
+
self._scale_matrix = desper.math.Mat4.from_scale(
|
|
247
|
+
(*transform.scale, 1.))
|
|
248
|
+
|
|
249
|
+
self.camera.view = self.get_view_matrix()
|
|
250
|
+
|
|
251
|
+
def get_view_matrix(self) -> desper.math.Mat4:
|
|
252
|
+
"""Compute view matrix.
|
|
253
|
+
|
|
254
|
+
Each query computes the matrix product between rotation,
|
|
255
|
+
translation and scale matrix.
|
|
256
|
+
"""
|
|
257
|
+
return (self._scale_matrix @ self._translation_matrix
|
|
258
|
+
@ self._rotation_matrix)
|
|
259
|
+
|
|
260
|
+
def on_position_change(self, new_position: desper.math.Vec2):
|
|
261
|
+
"""Event handler: update translation matrix and camera."""
|
|
262
|
+
self._translation_matrix = desper.math.Mat4.from_translation(
|
|
263
|
+
(*-new_position, 0.))
|
|
264
|
+
|
|
265
|
+
self.camera.view = self.get_view_matrix()
|
|
266
|
+
|
|
267
|
+
def on_rotation_change(self, new_rotation: float):
|
|
268
|
+
"""Event handler: update rotation matrix and camera."""
|
|
269
|
+
self._rotation_matrix = desper.math.Mat4.from_rotation(
|
|
270
|
+
math.radians(new_rotation), (0., 0., 1.))
|
|
271
|
+
|
|
272
|
+
self.camera.view = self.get_view_matrix()
|
|
273
|
+
|
|
274
|
+
def on_scale_change(self, new_scale: desper.math.Vec2):
|
|
275
|
+
"""Event handler: update scale matrix and camera."""
|
|
276
|
+
self._scale_matrix = desper.math.Mat4.from_scale(
|
|
277
|
+
(*new_scale, 1.))
|
|
278
|
+
|
|
279
|
+
self.camera.view = self.get_view_matrix()
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Synchronize pyglet graphics with desper ``Transform`` components.
|
|
2
|
+
|
|
3
|
+
High level graphic abstractions in pyglet (e.g. ``Sprite``) make use
|
|
4
|
+
of specific properties to handle and transform vertices. Most commonly,
|
|
5
|
+
positional properties (``Sprite.x``, ``Sprite.y``, etc.).
|
|
6
|
+
Due to the variety of existing graphical classes, the risk in handling
|
|
7
|
+
these properties directly is the injection in the project's
|
|
8
|
+
structure of strong dependencies based on pyglet objects and types.
|
|
9
|
+
|
|
10
|
+
To prevent this, a connection (or synchronization) between pyglet
|
|
11
|
+
classes and :class:`desper.Transform2D` is proposed (as pyglet
|
|
12
|
+
abstractions are mostly 2D, 3D support will be discussed in the future).
|
|
13
|
+
"""
|
|
14
|
+
import desper
|
|
15
|
+
import pyglet
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@desper.event_handler(desper.ON_REMOVE_EVENT_NAME)
|
|
19
|
+
class GraphicSync2D(desper.Controller):
|
|
20
|
+
"""Base class for pyglet graphical components synchronization.
|
|
21
|
+
|
|
22
|
+
It encapsules management of position, rotation and scale by using
|
|
23
|
+
the most common related pyglet properites, synchronizing them
|
|
24
|
+
with the :class:`desper.Transform2D`. In particular:
|
|
25
|
+
|
|
26
|
+
- ``x`` and ``y`` for updating position.
|
|
27
|
+
- ``rotation`` for updating rotation.
|
|
28
|
+
- ``scale_x`` and ``scale_y`` for updating scale.
|
|
29
|
+
|
|
30
|
+
Even though all these interactions are defined, they are not
|
|
31
|
+
enabled. In fact, this component does nothing on its own, but
|
|
32
|
+
defines base behaviours that can be exploited by specialized
|
|
33
|
+
subclasses. This is done since not all the graphical components
|
|
34
|
+
support the same transformations. Actually, most of them only
|
|
35
|
+
support repositioning with ``x`` and ``y``, or with
|
|
36
|
+
the ``position`` tuple.
|
|
37
|
+
|
|
38
|
+
In particular, to enable a connection
|
|
39
|
+
with :class:`desper.Transform2D` it is advisable to subclass this
|
|
40
|
+
component and apply to it the :func:`desper.event_handler`
|
|
41
|
+
decorator, enabling the desired event connections (any of
|
|
42
|
+
:attr:`desper.ON_POSITION_CHANGE_EVENT_NAME`,
|
|
43
|
+
:attr:`desper.ON_ROTATION_CHANGE_EVENT_NAME`,
|
|
44
|
+
:attr:`desper.ON_SCALE_CHANGE_EVENT_NAME`, i.e.
|
|
45
|
+
``'on_position_change'``, ``'on_rotation_change'``,
|
|
46
|
+
``'on_scale_change'``). Override homonymous methods to define
|
|
47
|
+
custom behaviour.
|
|
48
|
+
|
|
49
|
+
Moreover, the :attr:`desper.ON_REMOVE_EVENT_NAME`
|
|
50
|
+
(i.e. ``'on_remove'``) event is automatically handled, calling on
|
|
51
|
+
the referred graphical component the :func:`delete` method
|
|
52
|
+
(e.g. :meth:`pyglet.Sprite.delete`), which is fundamental to
|
|
53
|
+
correctly delete vertices of graphical components when removed in
|
|
54
|
+
real time.
|
|
55
|
+
"""
|
|
56
|
+
deleted = False
|
|
57
|
+
|
|
58
|
+
def __init__(self, component_type: type):
|
|
59
|
+
self.component_type = component_type
|
|
60
|
+
|
|
61
|
+
def on_add(self, entity, world: desper.World):
|
|
62
|
+
"""Subscribe to :class:`desper.Transform2D` for events."""
|
|
63
|
+
super().on_add(entity, world)
|
|
64
|
+
|
|
65
|
+
transform = world.get_component(entity, desper.Transform2D)
|
|
66
|
+
assert transform is not None, (
|
|
67
|
+
'A Transform2D component must be added first '
|
|
68
|
+
f'for {self.__class__} to work')
|
|
69
|
+
transform.add_handler(self)
|
|
70
|
+
|
|
71
|
+
# Apply immediately supported transformations
|
|
72
|
+
if desper.ON_POSITION_CHANGE_EVENT_NAME in self.__events__:
|
|
73
|
+
self.on_position_change(transform.position)
|
|
74
|
+
|
|
75
|
+
if desper.ON_ROTATION_CHANGE_EVENT_NAME in self.__events__:
|
|
76
|
+
self.on_rotation_change(transform.rotation)
|
|
77
|
+
|
|
78
|
+
if desper.ON_SCALE_CHANGE_EVENT_NAME in self.__events__:
|
|
79
|
+
self.on_scale_change(transform.scale)
|
|
80
|
+
|
|
81
|
+
def on_remove(self, entity, world: desper.World):
|
|
82
|
+
"""Clear vertices from memory."""
|
|
83
|
+
if not self.deleted:
|
|
84
|
+
self.deleted = True
|
|
85
|
+
self.get_component(self.component_type).delete()
|
|
86
|
+
|
|
87
|
+
def on_position_change(self, new_position: desper.math.Vec2):
|
|
88
|
+
"""Event handler: update graphical component position."""
|
|
89
|
+
self.get_component(self.component_type).position = new_position
|
|
90
|
+
|
|
91
|
+
def on_rotation_change(self, new_rotation: float):
|
|
92
|
+
"""Event handler: update graphical component rotation."""
|
|
93
|
+
self.get_component(self.component_type).rotation = new_rotation
|
|
94
|
+
|
|
95
|
+
def on_scale_change(self, new_scale: desper.math.Vec2):
|
|
96
|
+
"""Event handler: update graphical component's scale.
|
|
97
|
+
|
|
98
|
+
Possibly only supported by :class:`pyglet.sprite.Sprite`.
|
|
99
|
+
"""
|
|
100
|
+
self.get_component(self.component_type).update(scale_x=new_scale[0],
|
|
101
|
+
scale_y=new_scale[1])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@desper.event_handler(desper.ON_POSITION_CHANGE_EVENT_NAME,
|
|
105
|
+
desper.ON_ROTATION_CHANGE_EVENT_NAME,
|
|
106
|
+
desper.ON_SCALE_CHANGE_EVENT_NAME)
|
|
107
|
+
class SpriteSync(GraphicSync2D):
|
|
108
|
+
"""Synchronize :class:`desper.Transform2D` with pyglet ``Sprite``.
|
|
109
|
+
|
|
110
|
+
Handles all transformation events.
|
|
111
|
+
|
|
112
|
+
The :attr:`desper.ON_REMOVE_EVENT_NAME`
|
|
113
|
+
(i.e. ``'on_remove'``) event is automatically handled, calling on
|
|
114
|
+
the referred ``Sprite`` component the :func:`delete` method,
|
|
115
|
+
which is fundamental to correctly delete vertices of graphical
|
|
116
|
+
components when removed in real time.
|
|
117
|
+
|
|
118
|
+
This also means that ideally a ``Sprite`` instance and its
|
|
119
|
+
associated ``SpriteSync`` shall be added, removed from the world in
|
|
120
|
+
an entangled fashion.
|
|
121
|
+
|
|
122
|
+
Target class (``component_type``) defaults to
|
|
123
|
+
:class:`pyglet.sprite.Sprite`, but can be specialized to get
|
|
124
|
+
extra performance during component resolution (e.g. by specifying
|
|
125
|
+
:class:`pyglet-desper.Sprite`).
|
|
126
|
+
|
|
127
|
+
See :class:`GraphicSync2D` for more info.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, component_type: type = pyglet.sprite.Sprite):
|
|
131
|
+
super().__init__(component_type)
|
|
132
|
+
|
|
133
|
+
def on_add(self, entity, world):
|
|
134
|
+
"""Custom handler for better performance."""
|
|
135
|
+
self.entity = entity
|
|
136
|
+
self.world = world
|
|
137
|
+
|
|
138
|
+
transform = world.get_component(entity, desper.Transform2D)
|
|
139
|
+
assert transform is not None, (
|
|
140
|
+
'A Transform2D component must be added first '
|
|
141
|
+
f'for {self.__class__} to work')
|
|
142
|
+
transform.add_handler(self)
|
|
143
|
+
|
|
144
|
+
# Apply immediately supported transformations
|
|
145
|
+
# Stack them and apply in one go for better performance
|
|
146
|
+
transformations = {}
|
|
147
|
+
|
|
148
|
+
transformations['x'] = transform.position[0]
|
|
149
|
+
transformations['y'] = transform.position[1]
|
|
150
|
+
|
|
151
|
+
transformations['rotation'] = transform.rotation
|
|
152
|
+
|
|
153
|
+
transformations['scale_x'] = transform.scale[0]
|
|
154
|
+
transformations['scale_y'] = transform.scale[1]
|
|
155
|
+
|
|
156
|
+
self.get_component(self.component_type).update(**transformations)
|
|
157
|
+
|
|
158
|
+
def on_position_change(self, new_position: desper.math.Vec2):
|
|
159
|
+
"""Event handler: update graphical component position."""
|
|
160
|
+
self.get_component(self.component_type).position = (*new_position, 0.)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@desper.event_handler(desper.ON_POSITION_CHANGE_EVENT_NAME)
|
|
164
|
+
class PositionSync2D(GraphicSync2D):
|
|
165
|
+
"""Synchronize :class:`desper.Transform2D` position with graphics.
|
|
166
|
+
|
|
167
|
+
Handles the :attr:`desper.ON_POSITION_CHANGE_EVENT_NAME` event.
|
|
168
|
+
|
|
169
|
+
The :attr:`desper.ON_REMOVE_EVENT_NAME`
|
|
170
|
+
(i.e. ``'on_remove'``) event is automatically handled, calling on
|
|
171
|
+
the referred graphical component the :func:`delete` method,
|
|
172
|
+
which is fundamental to correctly delete vertices of graphical
|
|
173
|
+
components when removed in real time.
|
|
174
|
+
|
|
175
|
+
This also means that ideally a graphical instance and its
|
|
176
|
+
associated ``PositionSync2D`` shall be added, removed from the world
|
|
177
|
+
in an entangled fashion.
|
|
178
|
+
|
|
179
|
+
See :class:`GraphicSync2D` for more info.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@desper.event_handler(desper.ON_POSITION_CHANGE_EVENT_NAME,
|
|
184
|
+
desper.ON_ROTATION_CHANGE_EVENT_NAME)
|
|
185
|
+
class PositionRotationSync2D(GraphicSync2D):
|
|
186
|
+
"""Same as :class:`PositionSync2D` but also synchronizes rotation.
|
|
187
|
+
|
|
188
|
+
See :class:`PositionSync2D`.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def arc_sync_component() -> PositionRotationSync2D:
|
|
193
|
+
"""Get a sync component for :class:`pyglet.shapes.Arc`s.
|
|
194
|
+
|
|
195
|
+
See :class:`PositionRotationSync2D`.
|
|
196
|
+
"""
|
|
197
|
+
return PositionRotationSync2D(pyglet.shapes.Arc)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def circle_sync_component() -> PositionSync2D:
|
|
201
|
+
"""Get a sync component for :class:`pyglet.shapes.Circle`s.
|
|
202
|
+
|
|
203
|
+
See :class:`PositionSync2D`.
|
|
204
|
+
"""
|
|
205
|
+
return PositionSync2D(pyglet.shapes.Circle)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def ellipse_sync_component() -> PositionRotationSync2D:
|
|
209
|
+
"""Get a sync component for :class:`pyglet.shapes.Ellipse`s.
|
|
210
|
+
|
|
211
|
+
See :class:`PositionRotationSync2D`.
|
|
212
|
+
"""
|
|
213
|
+
return PositionRotationSync2D(pyglet.shapes.Ellipse)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def sector_sync_component() -> PositionRotationSync2D:
|
|
217
|
+
"""Get a sync component for :class:`pyglet.shapes.Sector`s.
|
|
218
|
+
|
|
219
|
+
See :class:`PositionRotationSync2D`.
|
|
220
|
+
"""
|
|
221
|
+
return PositionRotationSync2D(pyglet.shapes.Sector)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def line_sync_component() -> PositionSync2D:
|
|
225
|
+
"""Get a sync component for :class:`pyglet.shapes.Line`s.
|
|
226
|
+
|
|
227
|
+
See :class:`PositionSync2D`.
|
|
228
|
+
"""
|
|
229
|
+
return PositionSync2D(pyglet.shapes.Line)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def rectangle_sync_component() -> PositionRotationSync2D:
|
|
233
|
+
"""Get a sync component for :class:`pyglet.shapes.Rectangle`s.
|
|
234
|
+
|
|
235
|
+
See :class:`PositionRotationSync2D`.
|
|
236
|
+
"""
|
|
237
|
+
return PositionRotationSync2D(pyglet.shapes.Rectangle)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def borderedrectangle_sync_component() -> PositionRotationSync2D:
|
|
241
|
+
"""Get a sync component for :class:`pyglet.shapes.BorderedRectangle`s.
|
|
242
|
+
|
|
243
|
+
See :class:`PositionRotationSync2D`.
|
|
244
|
+
"""
|
|
245
|
+
return PositionRotationSync2D(pyglet.shapes.BorderedRectangle)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def triangle_sync_component() -> PositionSync2D:
|
|
249
|
+
"""Get a sync component for :class:`pyglet.shapes.Triangle`s.
|
|
250
|
+
|
|
251
|
+
See :class:`PositionSync2D`.
|
|
252
|
+
"""
|
|
253
|
+
return PositionSync2D(pyglet.shapes.Triangle)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def star_sync_component() -> PositionRotationSync2D:
|
|
257
|
+
"""Get a sync component for :class:`pyglet.shapes.Star`s.
|
|
258
|
+
|
|
259
|
+
See :class:`PositionRotationSync2D`.
|
|
260
|
+
"""
|
|
261
|
+
return PositionRotationSync2D(pyglet.shapes.Star)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def polygon_sync_component() -> PositionRotationSync2D:
|
|
265
|
+
"""Get a sync component for :class:`pyglet.shapes.Polygon`s.
|
|
266
|
+
|
|
267
|
+
See :class:`PositionRotationSync2D`.
|
|
268
|
+
"""
|
|
269
|
+
return PositionRotationSync2D(pyglet.shapes.Polygon)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def htmllabel_sync_component() -> PositionRotationSync2D:
|
|
273
|
+
"""Get a sync component for :class:`pyglet.text.HTMLLabel`.
|
|
274
|
+
|
|
275
|
+
See :class:`PositionRotationSync2D`.
|
|
276
|
+
"""
|
|
277
|
+
return PositionRotationSync2D(pyglet.text.HTMLLabel)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def documentlabel_sync_component() -> PositionRotationSync2D:
|
|
281
|
+
"""Get a sync component for :class:`pyglet.text.DocumentLabel`.
|
|
282
|
+
|
|
283
|
+
See :class:`PositionRotationSync2D`.
|
|
284
|
+
"""
|
|
285
|
+
return PositionRotationSync2D(pyglet.text.DocumentLabel)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def label_sync_component() -> PositionRotationSync2D:
|
|
289
|
+
"""Get a sync component for :class:`pyglet.text.Label`.
|
|
290
|
+
|
|
291
|
+
See :class:`PositionRotationSync2D`.
|
|
292
|
+
"""
|
|
293
|
+
return PositionRotationSync2D(pyglet.text.Label)
|