pgzero-plus 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of pgzero-plus might be problematic. Click here for more details.
- pgzero/__init__.py +41 -0
- pgzero/__main__.py +3 -0
- pgzero/actor.py +597 -0
- pgzero/animation.py +244 -0
- pgzero/builtins.py +45 -0
- pgzero/clock.py +200 -0
- pgzero/constants.py +33 -0
- pgzero/data/icon.png +0 -0
- pgzero/data/joypad.png +0 -0
- pgzero/game.py +573 -0
- pgzero/keyboard.py +67 -0
- pgzero/loaders.py +308 -0
- pgzero/music.py +180 -0
- pgzero/ptext.py +500 -0
- pgzero/rect.py +523 -0
- pgzero/runner.py +233 -0
- pgzero/screen.py +210 -0
- pgzero/soundfmt.py +105 -0
- pgzero/spellcheck.py +173 -0
- pgzero/sprites.py +182 -0
- pgzero/storage.py +191 -0
- pgzero/tilemaps.py +307 -0
- pgzero/tone.py +132 -0
- pgzero_plus-0.1.0.dist-info/METADATA +259 -0
- pgzero_plus-0.1.0.dist-info/RECORD +30 -0
- pgzero_plus-0.1.0.dist-info/WHEEL +5 -0
- pgzero_plus-0.1.0.dist-info/entry_points.txt +2 -0
- pgzero_plus-0.1.0.dist-info/licenses/COPYING +165 -0
- pgzero_plus-0.1.0.dist-info/top_level.txt +2 -0
- pgzrun.py +34 -0
pgzero/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Pygame Zero Plus."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
# Every way of starting a game (``pgzrun game.py``, ``import pgzrun``,
|
|
7
|
+
# ``python -m pgzero``) imports this package before pygame, which is when
|
|
8
|
+
# SDL and pygame read these settings.
|
|
9
|
+
|
|
10
|
+
# Codio has no sound device, so use SDL's silent audio driver there.
|
|
11
|
+
if (
|
|
12
|
+
"CODIO_BOX_DOMAIN" in os.environ
|
|
13
|
+
or "CODIO_HOSTNAME" in os.environ
|
|
14
|
+
or os.environ.get("USER") == "codio"
|
|
15
|
+
or os.path.exists("/home/codio")
|
|
16
|
+
):
|
|
17
|
+
os.environ["SDL_AUDIODRIVER"] = "dummy"
|
|
18
|
+
warnings.filterwarnings("ignore", message=".*avx2 capable.*", category=RuntimeWarning)
|
|
19
|
+
|
|
20
|
+
# Hide pygame welcome message
|
|
21
|
+
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"
|
|
22
|
+
|
|
23
|
+
# Suppress pkg_resources deprecation warning (from pygame)
|
|
24
|
+
warnings.filterwarnings(
|
|
25
|
+
"ignore",
|
|
26
|
+
message=r"pkg_resources is deprecated as an API.*",
|
|
27
|
+
category=UserWarning,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
32
|
+
except ImportError:
|
|
33
|
+
# Python 3.6 and 3.7
|
|
34
|
+
from importlib_metadata import PackageNotFoundError, version
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
__version__ = version("pgzero-plus")
|
|
38
|
+
except PackageNotFoundError:
|
|
39
|
+
__version__ = "unknown"
|
|
40
|
+
|
|
41
|
+
__all__ = ["__version__"]
|
pgzero/__main__.py
ADDED
pgzero/actor.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import pygame
|
|
3
|
+
from math import radians, sin, cos, atan2, degrees, sqrt
|
|
4
|
+
|
|
5
|
+
from . import game
|
|
6
|
+
from . import loaders
|
|
7
|
+
from . import rect
|
|
8
|
+
from . import spellcheck
|
|
9
|
+
|
|
10
|
+
ANCHORS = {
|
|
11
|
+
"x": {
|
|
12
|
+
"left": 0.0,
|
|
13
|
+
"center": 0.5,
|
|
14
|
+
"middle": 0.5,
|
|
15
|
+
"right": 1.0,
|
|
16
|
+
},
|
|
17
|
+
"y": {
|
|
18
|
+
"top": 0.0,
|
|
19
|
+
"center": 0.5,
|
|
20
|
+
"middle": 0.5,
|
|
21
|
+
"bottom": 1.0,
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def calculate_anchor(value, dim, total):
|
|
27
|
+
if isinstance(value, str):
|
|
28
|
+
try:
|
|
29
|
+
return total * ANCHORS[dim][value]
|
|
30
|
+
except KeyError:
|
|
31
|
+
raise ValueError("%r is not a valid %s-anchor name" % (value, dim))
|
|
32
|
+
return float(value)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# These are methods (of the same name) on pygame.Rect
|
|
36
|
+
SYMBOLIC_POSITIONS = set(
|
|
37
|
+
(
|
|
38
|
+
"topleft",
|
|
39
|
+
"bottomleft",
|
|
40
|
+
"topright",
|
|
41
|
+
"bottomright",
|
|
42
|
+
"midtop",
|
|
43
|
+
"midleft",
|
|
44
|
+
"midbottom",
|
|
45
|
+
"midright",
|
|
46
|
+
"center",
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Provides more meaningful default-arguments e.g. for display in IDEs etc.
|
|
51
|
+
POS_TOPLEFT = None
|
|
52
|
+
ANCHOR_CENTER = None
|
|
53
|
+
|
|
54
|
+
MAX_ALPHA = 255 # Based on pygame's max alpha.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def transform_anchor(ax, ay, w, h, angle):
|
|
58
|
+
"""Transform anchor based upon a rotation of a surface of size w x h."""
|
|
59
|
+
theta = -radians(angle)
|
|
60
|
+
|
|
61
|
+
sintheta = sin(theta)
|
|
62
|
+
costheta = cos(theta)
|
|
63
|
+
|
|
64
|
+
# Dims of the transformed rect
|
|
65
|
+
tw = abs(w * costheta) + abs(h * sintheta)
|
|
66
|
+
th = abs(w * sintheta) + abs(h * costheta)
|
|
67
|
+
|
|
68
|
+
# Offset of the anchor from the center
|
|
69
|
+
cax = ax - w * 0.5
|
|
70
|
+
cay = ay - h * 0.5
|
|
71
|
+
|
|
72
|
+
# Rotated offset of the anchor from the center
|
|
73
|
+
rax = cax * costheta - cay * sintheta
|
|
74
|
+
ray = cax * sintheta + cay * costheta
|
|
75
|
+
|
|
76
|
+
return (tw * 0.5 + rax, th * 0.5 + ray)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def rotate_vector(x, y, angle):
|
|
80
|
+
"""Rotate a vector using the same rotation convention as pygame surfaces."""
|
|
81
|
+
theta = -radians(angle)
|
|
82
|
+
sintheta = sin(theta)
|
|
83
|
+
costheta = cos(theta)
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
x * costheta - y * sintheta,
|
|
87
|
+
x * sintheta + y * costheta,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _set_opacity(actor, current_surface):
|
|
92
|
+
alpha = int(actor.opacity * MAX_ALPHA + 0.5)
|
|
93
|
+
|
|
94
|
+
if alpha == MAX_ALPHA:
|
|
95
|
+
return current_surface
|
|
96
|
+
|
|
97
|
+
alpha_img = pygame.Surface(current_surface.get_size(), pygame.SRCALPHA)
|
|
98
|
+
alpha_img.fill((255, 255, 255, alpha))
|
|
99
|
+
alpha_img.blit(current_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
|
|
100
|
+
return alpha_img
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _set_scale(actor, current_surface):
|
|
104
|
+
if actor._scale == 1.0:
|
|
105
|
+
return current_surface
|
|
106
|
+
|
|
107
|
+
w, h = current_surface.get_size()
|
|
108
|
+
sw = max(1, int(round(w * actor._scale)))
|
|
109
|
+
sh = max(1, int(round(h * actor._scale)))
|
|
110
|
+
return pygame.transform.scale(current_surface, (sw, sh))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _set_flip(actor, current_surface):
|
|
114
|
+
surf = current_surface
|
|
115
|
+
|
|
116
|
+
if actor._flip_d:
|
|
117
|
+
surf = pygame.transform.rotate(surf, -90)
|
|
118
|
+
surf = pygame.transform.flip(surf, True, False)
|
|
119
|
+
|
|
120
|
+
if actor._flip_h or actor._flip_v:
|
|
121
|
+
surf = pygame.transform.flip(surf, actor._flip_h, actor._flip_v)
|
|
122
|
+
|
|
123
|
+
return surf
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _set_angle(actor, current_surface):
|
|
127
|
+
if actor._angle % 360 == 0:
|
|
128
|
+
return current_surface
|
|
129
|
+
return pygame.transform.rotate(current_surface, actor._angle)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Actor:
|
|
133
|
+
EXPECTED_INIT_KWARGS = SYMBOLIC_POSITIONS
|
|
134
|
+
DELEGATED_ATTRIBUTES = [a for a in dir(rect.ZRect) if not a.startswith("_")]
|
|
135
|
+
|
|
136
|
+
function_order = [_set_opacity, _set_scale, _set_flip, _set_angle]
|
|
137
|
+
|
|
138
|
+
_anchor = _anchor_value = (0, 0)
|
|
139
|
+
_angle = 0.0
|
|
140
|
+
_opacity = 1.0
|
|
141
|
+
_scale = 1.0
|
|
142
|
+
_flip_h = False
|
|
143
|
+
_flip_v = False
|
|
144
|
+
_flip_d = False
|
|
145
|
+
_animate_counter = 0
|
|
146
|
+
fps = 5
|
|
147
|
+
direction = 0
|
|
148
|
+
_collision_rect_spec = None
|
|
149
|
+
|
|
150
|
+
def _build_transformed_surf(self):
|
|
151
|
+
cache_len = len(self._surface_cache)
|
|
152
|
+
if cache_len == 0:
|
|
153
|
+
last = self._orig_surf
|
|
154
|
+
else:
|
|
155
|
+
last = self._surface_cache[-1]
|
|
156
|
+
|
|
157
|
+
for f in self.function_order[cache_len:]:
|
|
158
|
+
new_surf = f(self, last)
|
|
159
|
+
self._surface_cache.append(new_surf)
|
|
160
|
+
last = new_surf
|
|
161
|
+
|
|
162
|
+
return self._surface_cache[-1]
|
|
163
|
+
|
|
164
|
+
def __init__(self, image, pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, collision_rect=None, **kwargs):
|
|
165
|
+
self._handle_unexpected_kwargs(kwargs)
|
|
166
|
+
|
|
167
|
+
self._surface_cache = []
|
|
168
|
+
self.__dict__["_rect"] = rect.ZRect((0, 0), (0, 0))
|
|
169
|
+
self._collision_rect_spec = collision_rect
|
|
170
|
+
self._images = []
|
|
171
|
+
|
|
172
|
+
self.image = image
|
|
173
|
+
self._init_position(pos, anchor, **kwargs)
|
|
174
|
+
|
|
175
|
+
def __getattr__(self, attr):
|
|
176
|
+
if attr in self.__class__.DELEGATED_ATTRIBUTES:
|
|
177
|
+
return getattr(self._rect, attr)
|
|
178
|
+
else:
|
|
179
|
+
return object.__getattribute__(self, attr)
|
|
180
|
+
|
|
181
|
+
def __setattr__(self, attr, value):
|
|
182
|
+
if attr in self.__class__.DELEGATED_ATTRIBUTES:
|
|
183
|
+
return setattr(self._rect, attr, value)
|
|
184
|
+
else:
|
|
185
|
+
return object.__setattr__(self, attr, value)
|
|
186
|
+
|
|
187
|
+
def __iter__(self):
|
|
188
|
+
return iter(self._rect)
|
|
189
|
+
|
|
190
|
+
def __repr__(self):
|
|
191
|
+
return "<{} {!r} pos={!r}>".format(type(self).__name__, self._image_name, self.pos)
|
|
192
|
+
|
|
193
|
+
def __dir__(self):
|
|
194
|
+
standard_attributes = [key for key in self.__dict__.keys() if not key.startswith("_")]
|
|
195
|
+
return standard_attributes + self.__class__.DELEGATED_ATTRIBUTES
|
|
196
|
+
|
|
197
|
+
def _handle_unexpected_kwargs(self, kwargs):
|
|
198
|
+
unexpected_kwargs = set(kwargs.keys()) - self.EXPECTED_INIT_KWARGS
|
|
199
|
+
if not unexpected_kwargs:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
typos, _ = spellcheck.compare(unexpected_kwargs, self.EXPECTED_INIT_KWARGS)
|
|
203
|
+
for found, suggested in typos:
|
|
204
|
+
raise TypeError("Unexpected keyword argument '{}' (did you mean '{}'?)".format(found, suggested))
|
|
205
|
+
|
|
206
|
+
def _init_position(self, pos, anchor, **kwargs):
|
|
207
|
+
if anchor is None:
|
|
208
|
+
anchor = ("center", "center")
|
|
209
|
+
self.anchor = anchor
|
|
210
|
+
|
|
211
|
+
symbolic_pos_args = {k: kwargs[k] for k in kwargs if k in SYMBOLIC_POSITIONS}
|
|
212
|
+
|
|
213
|
+
if not pos and not symbolic_pos_args:
|
|
214
|
+
self.topleft = (0, 0)
|
|
215
|
+
elif pos and symbolic_pos_args:
|
|
216
|
+
raise TypeError("'pos' argument cannot be mixed with 'topleft', " "'topright' etc. argument.")
|
|
217
|
+
elif pos:
|
|
218
|
+
self.pos = pos
|
|
219
|
+
else:
|
|
220
|
+
self._set_symbolic_pos(symbolic_pos_args)
|
|
221
|
+
|
|
222
|
+
def _set_symbolic_pos(self, symbolic_pos_dict):
|
|
223
|
+
if len(symbolic_pos_dict) == 0:
|
|
224
|
+
raise TypeError("No position-setting keyword arguments ('topleft', " "'topright' etc) found.")
|
|
225
|
+
if len(symbolic_pos_dict) > 1:
|
|
226
|
+
raise TypeError("Only one 'topleft', 'topright' etc. argument is allowed.")
|
|
227
|
+
|
|
228
|
+
setter_name, position = symbolic_pos_dict.popitem()
|
|
229
|
+
setattr(self, setter_name, position)
|
|
230
|
+
|
|
231
|
+
def _update_transform(self, function):
|
|
232
|
+
if function in self.function_order:
|
|
233
|
+
i = self.function_order.index(function)
|
|
234
|
+
del self._surface_cache[i:]
|
|
235
|
+
else:
|
|
236
|
+
raise IndexError("function {!r} does not have a registered order." "".format(function))
|
|
237
|
+
|
|
238
|
+
def _body_spec(self):
|
|
239
|
+
"""Return the unscaled body as (width, height, offset_x, offset_y).
|
|
240
|
+
|
|
241
|
+
The body is the actor's authoritative rectangle. All position anchors,
|
|
242
|
+
symbolic positions, and collision checks are based on this rectangle.
|
|
243
|
+
|
|
244
|
+
collision_rect formats:
|
|
245
|
+
- None: use the full image as the body
|
|
246
|
+
- (width, height): centered body
|
|
247
|
+
- (width, height, offset_x, offset_y): body center offset from image center
|
|
248
|
+
"""
|
|
249
|
+
image_w, image_h = self._orig_surf.get_size()
|
|
250
|
+
|
|
251
|
+
if self._collision_rect_spec is None:
|
|
252
|
+
return float(image_w), float(image_h), 0.0, 0.0
|
|
253
|
+
|
|
254
|
+
if len(self._collision_rect_spec) == 2:
|
|
255
|
+
width, height = self._collision_rect_spec
|
|
256
|
+
return float(width), float(height), 0.0, 0.0
|
|
257
|
+
|
|
258
|
+
if len(self._collision_rect_spec) == 4:
|
|
259
|
+
width, height, offset_x, offset_y = self._collision_rect_spec
|
|
260
|
+
return float(width), float(height), float(offset_x), float(offset_y)
|
|
261
|
+
|
|
262
|
+
raise ValueError("collision_rect must be None, (width, height), or " "(width, height, offset_x, offset_y).")
|
|
263
|
+
|
|
264
|
+
def _transformed_body_size(self):
|
|
265
|
+
body_w, body_h, _, _ = self._body_spec()
|
|
266
|
+
|
|
267
|
+
w = body_w * self._scale
|
|
268
|
+
h = body_h * self._scale
|
|
269
|
+
|
|
270
|
+
if self._flip_d:
|
|
271
|
+
w, h = h, w
|
|
272
|
+
|
|
273
|
+
if self._angle != 0.0:
|
|
274
|
+
ra = radians(self._angle)
|
|
275
|
+
sin_a = sin(ra)
|
|
276
|
+
cos_a = cos(ra)
|
|
277
|
+
return (
|
|
278
|
+
abs(w * cos_a) + abs(h * sin_a),
|
|
279
|
+
abs(w * sin_a) + abs(h * cos_a),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return w, h
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def anchor(self):
|
|
286
|
+
return self._anchor_value
|
|
287
|
+
|
|
288
|
+
@anchor.setter
|
|
289
|
+
def anchor(self, val):
|
|
290
|
+
self._anchor_value = val
|
|
291
|
+
self._calc_anchor()
|
|
292
|
+
|
|
293
|
+
def _calc_anchor(self):
|
|
294
|
+
if not hasattr(self, "_orig_surf"):
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
ax, ay = self._anchor_value
|
|
298
|
+
body_w, body_h, _, _ = self._body_spec()
|
|
299
|
+
|
|
300
|
+
ax = calculate_anchor(ax, "x", body_w)
|
|
301
|
+
ay = calculate_anchor(ay, "y", body_h)
|
|
302
|
+
self._untransformed_anchor = ax, ay
|
|
303
|
+
|
|
304
|
+
transformed_w = body_w * self._scale
|
|
305
|
+
transformed_h = body_h * self._scale
|
|
306
|
+
|
|
307
|
+
anchor_x = ax * self._scale
|
|
308
|
+
anchor_y = ay * self._scale
|
|
309
|
+
|
|
310
|
+
if self._flip_d:
|
|
311
|
+
anchor_x, anchor_y = anchor_y, anchor_x
|
|
312
|
+
transformed_w, transformed_h = transformed_h, transformed_w
|
|
313
|
+
|
|
314
|
+
if self._angle != 0.0:
|
|
315
|
+
self._anchor = transform_anchor(
|
|
316
|
+
anchor_x,
|
|
317
|
+
anchor_y,
|
|
318
|
+
transformed_w,
|
|
319
|
+
transformed_h,
|
|
320
|
+
self._angle,
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
self._anchor = anchor_x, anchor_y
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def angle(self):
|
|
327
|
+
return self._angle
|
|
328
|
+
|
|
329
|
+
@angle.setter
|
|
330
|
+
def angle(self, angle):
|
|
331
|
+
self._angle = angle
|
|
332
|
+
self._update_pos()
|
|
333
|
+
self._update_transform(_set_angle)
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def opacity(self):
|
|
337
|
+
"""Get/set the current opacity value."""
|
|
338
|
+
return self._opacity
|
|
339
|
+
|
|
340
|
+
@opacity.setter
|
|
341
|
+
def opacity(self, opacity):
|
|
342
|
+
self._opacity = min(1.0, max(0.0, opacity))
|
|
343
|
+
self._update_transform(_set_opacity)
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def scale(self):
|
|
347
|
+
return self._scale
|
|
348
|
+
|
|
349
|
+
@scale.setter
|
|
350
|
+
def scale(self, scale):
|
|
351
|
+
self._scale = scale
|
|
352
|
+
self._update_pos()
|
|
353
|
+
self._update_transform(_set_scale)
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def flip_h(self):
|
|
357
|
+
return self._flip_h
|
|
358
|
+
|
|
359
|
+
@flip_h.setter
|
|
360
|
+
def flip_h(self, value):
|
|
361
|
+
self._flip_h = value
|
|
362
|
+
self._update_pos()
|
|
363
|
+
self._update_transform(_set_flip)
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def flip_v(self):
|
|
367
|
+
return self._flip_v
|
|
368
|
+
|
|
369
|
+
@flip_v.setter
|
|
370
|
+
def flip_v(self, value):
|
|
371
|
+
self._flip_v = value
|
|
372
|
+
self._update_pos()
|
|
373
|
+
self._update_transform(_set_flip)
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def flip_d(self):
|
|
377
|
+
return self._flip_d
|
|
378
|
+
|
|
379
|
+
@flip_d.setter
|
|
380
|
+
def flip_d(self, value):
|
|
381
|
+
self._flip_d = value
|
|
382
|
+
self._update_pos()
|
|
383
|
+
self._update_transform(_set_flip)
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def pos(self):
|
|
387
|
+
px, py = self.topleft
|
|
388
|
+
ax, ay = self._anchor
|
|
389
|
+
return px + ax, py + ay
|
|
390
|
+
|
|
391
|
+
@pos.setter
|
|
392
|
+
def pos(self, pos):
|
|
393
|
+
px, py = pos
|
|
394
|
+
ax, ay = self._anchor
|
|
395
|
+
self.topleft = px - ax, py - ay
|
|
396
|
+
|
|
397
|
+
def rect(self):
|
|
398
|
+
return self._rect.copy()
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def x(self):
|
|
402
|
+
ax = self._anchor[0]
|
|
403
|
+
return self.left + ax
|
|
404
|
+
|
|
405
|
+
@x.setter
|
|
406
|
+
def x(self, px):
|
|
407
|
+
self.left = px - self._anchor[0]
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def y(self):
|
|
411
|
+
ay = self._anchor[1]
|
|
412
|
+
return self.top + ay
|
|
413
|
+
|
|
414
|
+
@y.setter
|
|
415
|
+
def y(self, py):
|
|
416
|
+
self.top = py - self._anchor[1]
|
|
417
|
+
|
|
418
|
+
# @property
|
|
419
|
+
# def image(self):
|
|
420
|
+
# if self._image_name is not None:
|
|
421
|
+
# return self._image_name
|
|
422
|
+
# return self._orig_surf
|
|
423
|
+
@property
|
|
424
|
+
def image(self) -> str:
|
|
425
|
+
return self._image_name or ""
|
|
426
|
+
|
|
427
|
+
@image.setter
|
|
428
|
+
def image(self, image):
|
|
429
|
+
if isinstance(image, pygame.Surface):
|
|
430
|
+
self._image_name = None
|
|
431
|
+
self._orig_surf = image
|
|
432
|
+
else:
|
|
433
|
+
self._image_name = image
|
|
434
|
+
self._orig_surf = loaders.images.load(image)
|
|
435
|
+
|
|
436
|
+
self._surface_cache.clear()
|
|
437
|
+
self._update_pos()
|
|
438
|
+
|
|
439
|
+
def _update_pos(self):
|
|
440
|
+
if not hasattr(self, "_orig_surf"):
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
p = getattr(self, "pos", (0, 0))
|
|
444
|
+
|
|
445
|
+
self.width, self.height = self._transformed_body_size()
|
|
446
|
+
|
|
447
|
+
self._calc_anchor()
|
|
448
|
+
self.pos = p
|
|
449
|
+
|
|
450
|
+
def _image_center_offset_from_body_center(self):
|
|
451
|
+
_, _, offset_x, offset_y = self._body_spec()
|
|
452
|
+
|
|
453
|
+
# offset_x / offset_y describe the body center compared to the image
|
|
454
|
+
# center, before scale or transforms. To draw the image, we need the
|
|
455
|
+
# opposite vector: image center compared to body center.
|
|
456
|
+
x = -offset_x * self._scale
|
|
457
|
+
y = -offset_y * self._scale
|
|
458
|
+
|
|
459
|
+
# The diagonal flip used by Tiled/Pygame here acts like swapping x/y
|
|
460
|
+
# around the body's center.
|
|
461
|
+
if self._flip_d:
|
|
462
|
+
x, y = y, x
|
|
463
|
+
|
|
464
|
+
if self._flip_h:
|
|
465
|
+
x = -x
|
|
466
|
+
|
|
467
|
+
if self._flip_v:
|
|
468
|
+
y = -y
|
|
469
|
+
|
|
470
|
+
if self._angle != 0.0:
|
|
471
|
+
x, y = rotate_vector(x, y, self._angle)
|
|
472
|
+
|
|
473
|
+
return x, y
|
|
474
|
+
|
|
475
|
+
def _image_topleft(self):
|
|
476
|
+
s = self._build_transformed_surf()
|
|
477
|
+
image_offset_x, image_offset_y = self._image_center_offset_from_body_center()
|
|
478
|
+
body_cx, body_cy = self.center
|
|
479
|
+
|
|
480
|
+
return (
|
|
481
|
+
body_cx + image_offset_x - s.get_width() / 2,
|
|
482
|
+
body_cy + image_offset_y - s.get_height() / 2,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def _move_body_to_preserve_image_topleft(self, image_topleft):
|
|
486
|
+
s = self._build_transformed_surf()
|
|
487
|
+
image_offset_x, image_offset_y = self._image_center_offset_from_body_center()
|
|
488
|
+
|
|
489
|
+
image_cx = image_topleft[0] + s.get_width() / 2
|
|
490
|
+
image_cy = image_topleft[1] + s.get_height() / 2
|
|
491
|
+
|
|
492
|
+
self.center = (
|
|
493
|
+
image_cx - image_offset_x,
|
|
494
|
+
image_cy - image_offset_y,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def draw(self):
|
|
498
|
+
s = self._build_transformed_surf()
|
|
499
|
+
game.screen.blit(s, self._image_topleft())
|
|
500
|
+
|
|
501
|
+
@property
|
|
502
|
+
def images(self):
|
|
503
|
+
return self._images
|
|
504
|
+
|
|
505
|
+
@images.setter
|
|
506
|
+
def images(self, images):
|
|
507
|
+
self._images = images
|
|
508
|
+
if self._images:
|
|
509
|
+
self.image = self._images[0]
|
|
510
|
+
|
|
511
|
+
def next_image(self):
|
|
512
|
+
if not self._images:
|
|
513
|
+
return
|
|
514
|
+
|
|
515
|
+
if self.image in self._images:
|
|
516
|
+
current = self._images.index(self.image)
|
|
517
|
+
self.image = self._images[(current + 1) % len(self._images)]
|
|
518
|
+
else:
|
|
519
|
+
self.image = self._images[0]
|
|
520
|
+
|
|
521
|
+
def animate(self):
|
|
522
|
+
now = int(time.time() * self.fps)
|
|
523
|
+
if now != self._animate_counter:
|
|
524
|
+
self._animate_counter = now
|
|
525
|
+
self.next_image()
|
|
526
|
+
|
|
527
|
+
def colliderect(self, other):
|
|
528
|
+
if hasattr(other, "_rect"):
|
|
529
|
+
other_rect = other._rect
|
|
530
|
+
else:
|
|
531
|
+
other_rect = other
|
|
532
|
+
|
|
533
|
+
return self._rect.colliderect(other_rect)
|
|
534
|
+
|
|
535
|
+
def collidelist(self, others):
|
|
536
|
+
for n, other in enumerate(others):
|
|
537
|
+
if self.colliderect(other):
|
|
538
|
+
return n
|
|
539
|
+
return -1
|
|
540
|
+
|
|
541
|
+
def collidepoint(self, *args):
|
|
542
|
+
return self._rect.collidepoint(*args)
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def collision_rect(self):
|
|
546
|
+
return self._rect
|
|
547
|
+
|
|
548
|
+
@collision_rect.setter
|
|
549
|
+
def collision_rect(self, value):
|
|
550
|
+
if value is not None and len(value) not in (2, 4):
|
|
551
|
+
raise ValueError("collision_rect must be None, (width, height), or " "(width, height, offset_x, offset_y).")
|
|
552
|
+
|
|
553
|
+
old_image_topleft = self._image_topleft() if hasattr(self, "_orig_surf") else None
|
|
554
|
+
|
|
555
|
+
self._collision_rect_spec = value
|
|
556
|
+
self._update_pos()
|
|
557
|
+
|
|
558
|
+
# Changing the body should not make the image appear to jump.
|
|
559
|
+
# The body moves to match the new collision rectangle, while the
|
|
560
|
+
# already-drawn image stays in the same visual place.
|
|
561
|
+
if old_image_topleft is not None:
|
|
562
|
+
self._move_body_to_preserve_image_topleft(old_image_topleft)
|
|
563
|
+
|
|
564
|
+
def draw_collision_rect(self):
|
|
565
|
+
r = self.collision_rect
|
|
566
|
+
pygame.draw.rect(
|
|
567
|
+
game.screen,
|
|
568
|
+
(255, 0, 0),
|
|
569
|
+
(r.x, r.y, r.w, r.h),
|
|
570
|
+
2,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
def angle_to(self, target):
|
|
574
|
+
"""Return the angle from this actors position to target, in degrees."""
|
|
575
|
+
if isinstance(target, Actor):
|
|
576
|
+
tx, ty = target.pos
|
|
577
|
+
else:
|
|
578
|
+
tx, ty = target
|
|
579
|
+
myx, myy = self.pos
|
|
580
|
+
dx = tx - myx
|
|
581
|
+
dy = myy - ty
|
|
582
|
+
return degrees(atan2(dy, dx))
|
|
583
|
+
|
|
584
|
+
def distance_to(self, target):
|
|
585
|
+
"""Return the distance from this actor's pos to target, in pixels."""
|
|
586
|
+
if isinstance(target, Actor):
|
|
587
|
+
tx, ty = target.pos
|
|
588
|
+
else:
|
|
589
|
+
tx, ty = target
|
|
590
|
+
myx, myy = self.pos
|
|
591
|
+
dx = tx - myx
|
|
592
|
+
dy = ty - myy
|
|
593
|
+
return sqrt(dx * dx + dy * dy)
|
|
594
|
+
|
|
595
|
+
def unload_image(self):
|
|
596
|
+
if self._image_name is not None:
|
|
597
|
+
loaders.images.unload(self._image_name)
|