zombie-escape 1.12.0__py3-none-any.whl → 1.12.3__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.
- zombie_escape/__about__.py +1 -1
- zombie_escape/colors.py +22 -14
- zombie_escape/entities.py +236 -49
- zombie_escape/entities_constants.py +6 -2
- zombie_escape/gameplay/footprints.py +4 -0
- zombie_escape/gameplay/interactions.py +19 -7
- zombie_escape/gameplay/layout.py +18 -14
- zombie_escape/gameplay/movement.py +23 -1
- zombie_escape/gameplay/spawn.py +92 -50
- zombie_escape/gameplay/state.py +15 -9
- zombie_escape/gameplay/survivors.py +9 -1
- zombie_escape/gameplay/utils.py +40 -21
- zombie_escape/level_blueprints.py +87 -4
- zombie_escape/models.py +5 -4
- zombie_escape/render.py +51 -16
- zombie_escape/render_assets.py +325 -124
- zombie_escape/render_constants.py +11 -0
- zombie_escape/screens/game_over.py +3 -3
- zombie_escape/screens/gameplay.py +4 -0
- zombie_escape/stage_constants.py +5 -5
- zombie_escape/zombie_escape.py +1 -1
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/METADATA +4 -3
- zombie_escape-1.12.3.dist-info/RECORD +47 -0
- zombie_escape-1.12.0.dist-info/RECORD +0 -47
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/WHEEL +0 -0
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/licenses/LICENSE.txt +0 -0
zombie_escape/render_assets.py
CHANGED
|
@@ -12,20 +12,21 @@ from .colors import (
|
|
|
12
12
|
BLUE,
|
|
13
13
|
DARK_RED,
|
|
14
14
|
ORANGE,
|
|
15
|
-
RED,
|
|
16
15
|
STEEL_BEAM_COLOR,
|
|
17
16
|
STEEL_BEAM_LINE_COLOR,
|
|
18
|
-
TRACKER_OUTLINE_COLOR,
|
|
19
|
-
WALL_FOLLOWER_OUTLINE_COLOR,
|
|
20
17
|
YELLOW,
|
|
21
18
|
EnvironmentPalette,
|
|
22
19
|
get_environment_palette,
|
|
23
20
|
)
|
|
24
21
|
from .render_constants import (
|
|
22
|
+
ANGLE_BINS,
|
|
25
23
|
BUDDY_COLOR,
|
|
24
|
+
HAND_SPREAD_RAD,
|
|
26
25
|
HUMANOID_OUTLINE_COLOR,
|
|
27
26
|
HUMANOID_OUTLINE_WIDTH,
|
|
28
27
|
SURVIVOR_COLOR,
|
|
28
|
+
ZOMBIE_BODY_COLOR,
|
|
29
|
+
ZOMBIE_OUTLINE_COLOR,
|
|
29
30
|
FogRing,
|
|
30
31
|
RenderAssets,
|
|
31
32
|
)
|
|
@@ -44,6 +45,102 @@ def _draw_outlined_circle(
|
|
|
44
45
|
pygame.draw.circle(surface, outline_color, center, radius, width=outline_width)
|
|
45
46
|
|
|
46
47
|
|
|
48
|
+
def _brighten_color(
|
|
49
|
+
color: tuple[int, int, int], *, factor: float = 1.25
|
|
50
|
+
) -> tuple[int, int, int]:
|
|
51
|
+
return tuple(min(255, int(c * factor + 0.5)) for c in color)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
ANGLE_STEP = math.tau / ANGLE_BINS
|
|
55
|
+
|
|
56
|
+
_PLAYER_UPSCALE_FACTOR = 4
|
|
57
|
+
_CAR_UPSCALE_FACTOR = 4
|
|
58
|
+
|
|
59
|
+
_PLAYER_DIRECTIONAL_CACHE: dict[tuple[int, int], list[pygame.Surface]] = {}
|
|
60
|
+
_SURVIVOR_DIRECTIONAL_CACHE: dict[
|
|
61
|
+
tuple[int, bool, bool, int], list[pygame.Surface]
|
|
62
|
+
] = {}
|
|
63
|
+
_ZOMBIE_DIRECTIONAL_CACHE: dict[tuple[int, bool, int], list[pygame.Surface]] = {}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def angle_bin_from_vector(
|
|
67
|
+
dx: float, dy: float, *, bins: int = ANGLE_BINS
|
|
68
|
+
) -> int | None:
|
|
69
|
+
if dx == 0 and dy == 0:
|
|
70
|
+
return None
|
|
71
|
+
angle = math.atan2(dy, dx)
|
|
72
|
+
if angle < 0:
|
|
73
|
+
angle += math.tau
|
|
74
|
+
step = math.tau / bins
|
|
75
|
+
return int(round(angle / step)) % bins
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _hand_defaults(radius: int) -> tuple[int, int]:
|
|
79
|
+
hand_radius = max(1, int(radius * 0.5))
|
|
80
|
+
hand_distance = max(hand_radius + 1, int(radius * 1.0))
|
|
81
|
+
return hand_radius, hand_distance
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _draw_capped_circle(
|
|
85
|
+
surface: pygame.Surface,
|
|
86
|
+
center: tuple[int, int],
|
|
87
|
+
radius: int,
|
|
88
|
+
base_color: tuple[int, int, int],
|
|
89
|
+
cap_color: tuple[int, int, int],
|
|
90
|
+
outline_color: tuple[int, int, int],
|
|
91
|
+
outline_width: int,
|
|
92
|
+
*,
|
|
93
|
+
angle_rad: float = 0.0,
|
|
94
|
+
hand_spread_rad: float = HAND_SPREAD_RAD,
|
|
95
|
+
hand_radius: int | None = None,
|
|
96
|
+
hand_distance: int | None = None,
|
|
97
|
+
draw_hands: bool = True,
|
|
98
|
+
) -> None:
|
|
99
|
+
if hand_radius is None or hand_distance is None:
|
|
100
|
+
hand_radius, hand_distance = _hand_defaults(radius)
|
|
101
|
+
if draw_hands:
|
|
102
|
+
for direction in (-1, 1):
|
|
103
|
+
hand_angle = angle_rad + (hand_spread_rad * direction)
|
|
104
|
+
hand_x = int(round(center[0] + math.cos(hand_angle) * hand_distance))
|
|
105
|
+
hand_y = int(round(center[1] + math.sin(hand_angle) * hand_distance))
|
|
106
|
+
pygame.draw.circle(surface, base_color, (hand_x, hand_y), hand_radius)
|
|
107
|
+
pygame.draw.circle(surface, cap_color, center, radius)
|
|
108
|
+
if outline_width > 0:
|
|
109
|
+
pygame.draw.circle(surface, outline_color, center, radius, width=outline_width)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _build_capped_surface(
|
|
113
|
+
radius: int,
|
|
114
|
+
base_color: tuple[int, int, int],
|
|
115
|
+
cap_color: tuple[int, int, int],
|
|
116
|
+
angle_bin: int,
|
|
117
|
+
*,
|
|
118
|
+
outline_scale: int = 1,
|
|
119
|
+
draw_hands: bool = True,
|
|
120
|
+
outline_color: tuple[int, int, int] = HUMANOID_OUTLINE_COLOR,
|
|
121
|
+
) -> pygame.Surface:
|
|
122
|
+
hand_radius, hand_distance = _hand_defaults(radius)
|
|
123
|
+
max_extent = max(radius, hand_distance + hand_radius)
|
|
124
|
+
size = max_extent * 2 + 2
|
|
125
|
+
surface = pygame.Surface((size, size), pygame.SRCALPHA)
|
|
126
|
+
center = (max_extent + 1, max_extent + 1)
|
|
127
|
+
angle_rad = (angle_bin % ANGLE_BINS) * ANGLE_STEP
|
|
128
|
+
_draw_capped_circle(
|
|
129
|
+
surface,
|
|
130
|
+
center,
|
|
131
|
+
radius,
|
|
132
|
+
base_color,
|
|
133
|
+
cap_color,
|
|
134
|
+
outline_color,
|
|
135
|
+
HUMANOID_OUTLINE_WIDTH * outline_scale,
|
|
136
|
+
angle_rad=angle_rad,
|
|
137
|
+
hand_radius=hand_radius,
|
|
138
|
+
hand_distance=hand_distance,
|
|
139
|
+
draw_hands=draw_hands,
|
|
140
|
+
)
|
|
141
|
+
return surface
|
|
142
|
+
|
|
143
|
+
|
|
47
144
|
@dataclass(frozen=True)
|
|
48
145
|
class PolygonSpec:
|
|
49
146
|
size: tuple[int, int]
|
|
@@ -93,7 +190,7 @@ SHOES_SPEC = PolygonSpec(
|
|
|
93
190
|
[
|
|
94
191
|
(1, 1),
|
|
95
192
|
(7, 1),
|
|
96
|
-
(
|
|
193
|
+
(8, 4),
|
|
97
194
|
(13, 6),
|
|
98
195
|
(13, 9),
|
|
99
196
|
(1, 9),
|
|
@@ -274,55 +371,145 @@ def resolve_steel_beam_colors(
|
|
|
274
371
|
return STEEL_BEAM_COLOR, STEEL_BEAM_LINE_COLOR
|
|
275
372
|
|
|
276
373
|
|
|
277
|
-
def
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
374
|
+
def build_player_directional_surfaces(
|
|
375
|
+
radius: int, *, bins: int = ANGLE_BINS
|
|
376
|
+
) -> list[pygame.Surface]:
|
|
377
|
+
cache_key = (radius, bins)
|
|
378
|
+
if cache_key in _PLAYER_DIRECTIONAL_CACHE:
|
|
379
|
+
return _PLAYER_DIRECTIONAL_CACHE[cache_key]
|
|
380
|
+
surfaces = build_humanoid_directional_surfaces(
|
|
282
381
|
radius,
|
|
283
|
-
BLUE,
|
|
284
|
-
|
|
285
|
-
|
|
382
|
+
base_color=BLUE,
|
|
383
|
+
cap_color=_brighten_color(BLUE),
|
|
384
|
+
bins=bins,
|
|
385
|
+
outline_color=HUMANOID_OUTLINE_COLOR,
|
|
286
386
|
)
|
|
287
|
-
|
|
387
|
+
_PLAYER_DIRECTIONAL_CACHE[cache_key] = surfaces
|
|
388
|
+
return surfaces
|
|
288
389
|
|
|
289
390
|
|
|
290
|
-
def
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
391
|
+
def build_humanoid_directional_surfaces(
|
|
392
|
+
radius: int,
|
|
393
|
+
*,
|
|
394
|
+
base_color: tuple[int, int, int],
|
|
395
|
+
cap_color: tuple[int, int, int],
|
|
396
|
+
bins: int = ANGLE_BINS,
|
|
397
|
+
draw_hands: bool = True,
|
|
398
|
+
outline_color: tuple[int, int, int],
|
|
399
|
+
) -> list[pygame.Surface]:
|
|
400
|
+
base_radius = radius * _PLAYER_UPSCALE_FACTOR
|
|
401
|
+
base_surface = _build_capped_surface(
|
|
402
|
+
base_radius,
|
|
403
|
+
base_color,
|
|
404
|
+
cap_color,
|
|
405
|
+
0,
|
|
406
|
+
outline_scale=_PLAYER_UPSCALE_FACTOR,
|
|
407
|
+
draw_hands=draw_hands,
|
|
408
|
+
outline_color=outline_color,
|
|
409
|
+
)
|
|
410
|
+
target_surface = _build_capped_surface(
|
|
411
|
+
radius,
|
|
412
|
+
base_color,
|
|
413
|
+
cap_color,
|
|
414
|
+
0,
|
|
415
|
+
draw_hands=draw_hands,
|
|
416
|
+
outline_color=outline_color,
|
|
417
|
+
)
|
|
418
|
+
target_size = target_surface.get_size()
|
|
419
|
+
scale = target_size[0] / base_surface.get_width()
|
|
420
|
+
half_step_deg = 360.0 / (bins * 5)
|
|
421
|
+
surfaces: list[pygame.Surface] = []
|
|
422
|
+
for idx in range(bins):
|
|
423
|
+
rotation_deg = -(idx * 360.0 / bins - half_step_deg)
|
|
424
|
+
rotated = pygame.transform.rotozoom(base_surface, rotation_deg, scale)
|
|
425
|
+
framed = pygame.Surface(target_size, pygame.SRCALPHA)
|
|
426
|
+
framed.blit(rotated, rotated.get_rect(center=framed.get_rect().center))
|
|
427
|
+
surfaces.append(framed)
|
|
428
|
+
return surfaces
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def draw_humanoid_hand(
|
|
432
|
+
surface: pygame.Surface,
|
|
433
|
+
*,
|
|
434
|
+
radius: int,
|
|
435
|
+
angle_rad: float,
|
|
436
|
+
color: tuple[int, int, int],
|
|
437
|
+
hand_radius: int | None = None,
|
|
438
|
+
hand_distance: int | None = None,
|
|
439
|
+
) -> None:
|
|
440
|
+
if hand_radius is None or hand_distance is None:
|
|
441
|
+
hand_radius, hand_distance = _hand_defaults(radius)
|
|
442
|
+
center_x, center_y = surface.get_rect().center
|
|
443
|
+
hand_x = int(round(center_x + math.cos(angle_rad) * hand_distance))
|
|
444
|
+
hand_y = int(round(center_y + math.sin(angle_rad) * hand_distance))
|
|
445
|
+
pygame.draw.circle(surface, color, (hand_x, hand_y), hand_radius)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def draw_humanoid_nose(
|
|
449
|
+
surface: pygame.Surface,
|
|
450
|
+
*,
|
|
451
|
+
radius: int,
|
|
452
|
+
angle_rad: float,
|
|
453
|
+
color: tuple[int, int, int],
|
|
454
|
+
) -> None:
|
|
455
|
+
center_x, center_y = surface.get_rect().center
|
|
456
|
+
nose_length = max(2, int(radius * 0.45))
|
|
457
|
+
nose_offset = max(1, int(radius * 0.35))
|
|
458
|
+
start_x = center_x + math.cos(angle_rad) * nose_offset
|
|
459
|
+
start_y = center_y + math.sin(angle_rad) * nose_offset
|
|
460
|
+
end_x = center_x + math.cos(angle_rad) * (nose_offset + nose_length)
|
|
461
|
+
end_y = center_y + math.sin(angle_rad) * (nose_offset + nose_length)
|
|
462
|
+
pygame.draw.line(
|
|
294
463
|
surface,
|
|
295
|
-
|
|
464
|
+
color,
|
|
465
|
+
(int(start_x), int(start_y)),
|
|
466
|
+
(int(end_x), int(end_y)),
|
|
467
|
+
width=2,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def build_survivor_directional_surfaces(
|
|
472
|
+
radius: int,
|
|
473
|
+
*,
|
|
474
|
+
is_buddy: bool,
|
|
475
|
+
bins: int = ANGLE_BINS,
|
|
476
|
+
draw_hands: bool = True,
|
|
477
|
+
) -> list[pygame.Surface]:
|
|
478
|
+
cache_key = (radius, is_buddy, draw_hands, bins)
|
|
479
|
+
if cache_key in _SURVIVOR_DIRECTIONAL_CACHE:
|
|
480
|
+
return _SURVIVOR_DIRECTIONAL_CACHE[cache_key]
|
|
481
|
+
fill_color = BUDDY_COLOR if is_buddy else SURVIVOR_COLOR
|
|
482
|
+
surfaces = build_humanoid_directional_surfaces(
|
|
296
483
|
radius,
|
|
297
|
-
fill_color,
|
|
298
|
-
|
|
299
|
-
|
|
484
|
+
base_color=fill_color,
|
|
485
|
+
cap_color=_brighten_color(fill_color),
|
|
486
|
+
bins=bins,
|
|
487
|
+
draw_hands=draw_hands,
|
|
488
|
+
outline_color=HUMANOID_OUTLINE_COLOR,
|
|
300
489
|
)
|
|
301
|
-
|
|
490
|
+
_SURVIVOR_DIRECTIONAL_CACHE[cache_key] = surfaces
|
|
491
|
+
return surfaces
|
|
302
492
|
|
|
303
493
|
|
|
304
|
-
def
|
|
494
|
+
def build_zombie_directional_surfaces(
|
|
305
495
|
radius: int,
|
|
306
496
|
*,
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
) -> pygame.Surface:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
else:
|
|
315
|
-
outline_color = DARK_RED
|
|
316
|
-
surface = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
|
|
317
|
-
_draw_outlined_circle(
|
|
318
|
-
surface,
|
|
319
|
-
(radius, radius),
|
|
497
|
+
bins: int = ANGLE_BINS,
|
|
498
|
+
draw_hands: bool = True,
|
|
499
|
+
) -> list[pygame.Surface]:
|
|
500
|
+
cache_key = (radius, draw_hands, bins)
|
|
501
|
+
if cache_key in _ZOMBIE_DIRECTIONAL_CACHE:
|
|
502
|
+
return _ZOMBIE_DIRECTIONAL_CACHE[cache_key]
|
|
503
|
+
surfaces = build_humanoid_directional_surfaces(
|
|
320
504
|
radius,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
505
|
+
base_color=ZOMBIE_BODY_COLOR,
|
|
506
|
+
cap_color=_brighten_color(ZOMBIE_BODY_COLOR),
|
|
507
|
+
bins=bins,
|
|
508
|
+
draw_hands=draw_hands,
|
|
509
|
+
outline_color=ZOMBIE_OUTLINE_COLOR,
|
|
324
510
|
)
|
|
325
|
-
|
|
511
|
+
_ZOMBIE_DIRECTIONAL_CACHE[cache_key] = surfaces
|
|
512
|
+
return surfaces
|
|
326
513
|
|
|
327
514
|
|
|
328
515
|
def build_car_surface(width: int, height: int) -> pygame.Surface:
|
|
@@ -336,54 +523,101 @@ def paint_car_surface(
|
|
|
336
523
|
height: int,
|
|
337
524
|
color: tuple[int, int, int],
|
|
338
525
|
) -> None:
|
|
339
|
-
|
|
526
|
+
upscale = _CAR_UPSCALE_FACTOR
|
|
527
|
+
if upscale > 1:
|
|
528
|
+
up_width = width * upscale
|
|
529
|
+
up_height = height * upscale
|
|
530
|
+
up_surface = pygame.Surface((up_width, up_height), pygame.SRCALPHA)
|
|
531
|
+
_paint_car_surface_base(
|
|
532
|
+
up_surface, width=up_width, height=up_height, color=color
|
|
533
|
+
)
|
|
534
|
+
scaled = pygame.transform.smoothscale(up_surface, (width, height))
|
|
535
|
+
surface.fill((0, 0, 0, 0))
|
|
536
|
+
surface.blit(scaled, (0, 0))
|
|
537
|
+
return
|
|
538
|
+
_paint_car_surface_base(surface, width=width, height=height, color=color)
|
|
340
539
|
|
|
341
|
-
body_rect = pygame.Rect(1, 4, width - 2, height - 8)
|
|
342
|
-
front_cap_height = max(8, body_rect.height // 3)
|
|
343
|
-
front_cap = pygame.Rect(
|
|
344
|
-
body_rect.left, body_rect.top, body_rect.width, front_cap_height
|
|
345
|
-
)
|
|
346
|
-
windshield_rect = pygame.Rect(
|
|
347
|
-
body_rect.left + 4,
|
|
348
|
-
body_rect.top + 3,
|
|
349
|
-
body_rect.width - 8,
|
|
350
|
-
front_cap_height - 5,
|
|
351
|
-
)
|
|
352
540
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
pygame.draw.rect(surface, wheel_color, left_wheel, border_radius=3)
|
|
365
|
-
pygame.draw.rect(surface, wheel_color, right_wheel, border_radius=3)
|
|
366
|
-
|
|
367
|
-
pygame.draw.rect(surface, body_color, body_rect, border_radius=4)
|
|
368
|
-
pygame.draw.rect(surface, trim_color, body_rect, width=2, border_radius=4)
|
|
369
|
-
pygame.draw.rect(surface, front_cap_color, front_cap, border_radius=10)
|
|
370
|
-
pygame.draw.rect(surface, trim_color, front_cap, width=2, border_radius=10)
|
|
371
|
-
pygame.draw.rect(surface, window_color, windshield_rect, border_radius=4)
|
|
372
|
-
|
|
373
|
-
headlight_color = (245, 245, 200)
|
|
374
|
-
for x in (front_cap.left + 5, front_cap.right - 5):
|
|
375
|
-
pygame.draw.circle(surface, headlight_color, (x, body_rect.top + 5), 2)
|
|
376
|
-
grille_rect = pygame.Rect(front_cap.centerx - 6, front_cap.top + 2, 12, 6)
|
|
377
|
-
pygame.draw.rect(surface, trim_color, grille_rect, border_radius=2)
|
|
541
|
+
def _paint_car_surface_base(
|
|
542
|
+
surface: pygame.Surface,
|
|
543
|
+
*,
|
|
544
|
+
width: int,
|
|
545
|
+
height: int,
|
|
546
|
+
color: tuple[int, int, int],
|
|
547
|
+
) -> None:
|
|
548
|
+
surface.fill((0, 0, 0, 0))
|
|
549
|
+
|
|
550
|
+
trim_color = tuple(int(c * 0.6) for c in color)
|
|
551
|
+
body_color = tuple(min(255, int(c * 1.15)) for c in color)
|
|
378
552
|
tail_light_color = (255, 80, 50)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
553
|
+
headlight_color = (200, 200, 200)
|
|
554
|
+
|
|
555
|
+
base_width = 150.0
|
|
556
|
+
base_height = 210.0
|
|
557
|
+
scale_x = width / base_width
|
|
558
|
+
scale_y = height / base_height
|
|
559
|
+
|
|
560
|
+
def _rect(x: float, y: float, w: float, h: float) -> pygame.Rect:
|
|
561
|
+
return pygame.Rect(
|
|
562
|
+
int(round(x * scale_x)),
|
|
563
|
+
int(round(y * scale_y)),
|
|
564
|
+
max(1, int(round(w * scale_x))),
|
|
565
|
+
max(1, int(round(h * scale_y))),
|
|
385
566
|
)
|
|
386
567
|
|
|
568
|
+
def _radius(value: float) -> int:
|
|
569
|
+
return max(1, int(round(value * min(scale_x, scale_y))))
|
|
570
|
+
|
|
571
|
+
body_top = _rect(0, 0, 150, 140)
|
|
572
|
+
body_bottom = _rect(0, 70, 150, 140)
|
|
573
|
+
rear_bed = _rect(16, 98, 118, 88)
|
|
574
|
+
|
|
575
|
+
pygame.draw.rect(surface, trim_color, body_top, border_radius=_radius(50))
|
|
576
|
+
pygame.draw.rect(surface, trim_color, body_bottom, border_radius=_radius(37))
|
|
577
|
+
pygame.draw.rect(surface, body_color, rear_bed)
|
|
578
|
+
|
|
579
|
+
tail_left = _rect(30, 190, 30, 20)
|
|
580
|
+
tail_right = _rect(90, 190, 30, 20)
|
|
581
|
+
pygame.draw.rect(surface, tail_light_color, tail_left)
|
|
582
|
+
pygame.draw.rect(surface, tail_light_color, tail_right)
|
|
583
|
+
|
|
584
|
+
headlight_left = _rect(15, 7, 40, 20)
|
|
585
|
+
headlight_right = _rect(95, 7, 40, 20)
|
|
586
|
+
pygame.draw.ellipse(surface, headlight_color, headlight_left)
|
|
587
|
+
pygame.draw.ellipse(surface, headlight_color, headlight_right)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def build_car_directional_surfaces(
|
|
591
|
+
base_surface: pygame.Surface, *, bins: int = ANGLE_BINS
|
|
592
|
+
) -> list[pygame.Surface]:
|
|
593
|
+
"""Return pre-rotated car surfaces matching angle_bin_from_vector bins."""
|
|
594
|
+
surfaces: list[pygame.Surface] = []
|
|
595
|
+
upscale = _CAR_UPSCALE_FACTOR
|
|
596
|
+
if upscale > 1:
|
|
597
|
+
src_size = base_surface.get_size()
|
|
598
|
+
upscale_surface = pygame.transform.scale(
|
|
599
|
+
base_surface,
|
|
600
|
+
(src_size[0] * upscale, src_size[1] * upscale),
|
|
601
|
+
)
|
|
602
|
+
else:
|
|
603
|
+
upscale_surface = base_surface
|
|
604
|
+
for idx in range(bins):
|
|
605
|
+
angle_rad = idx * ANGLE_STEP
|
|
606
|
+
rotation_deg = -math.degrees(angle_rad) - 90
|
|
607
|
+
rotated = pygame.transform.rotate(upscale_surface, rotation_deg)
|
|
608
|
+
if upscale > 1:
|
|
609
|
+
scaled = pygame.transform.smoothscale(
|
|
610
|
+
rotated,
|
|
611
|
+
(
|
|
612
|
+
max(1, rotated.get_width() // upscale),
|
|
613
|
+
max(1, rotated.get_height() // upscale),
|
|
614
|
+
),
|
|
615
|
+
)
|
|
616
|
+
surfaces.append(scaled)
|
|
617
|
+
else:
|
|
618
|
+
surfaces.append(rotated)
|
|
619
|
+
return surfaces
|
|
620
|
+
|
|
387
621
|
|
|
388
622
|
def paint_wall_surface(
|
|
389
623
|
surface: pygame.Surface,
|
|
@@ -522,43 +756,6 @@ def paint_steel_beam_surface(
|
|
|
522
756
|
surface.blit(top_surface, top_rect.topleft)
|
|
523
757
|
|
|
524
758
|
|
|
525
|
-
def paint_zombie_surface(
|
|
526
|
-
surface: pygame.Surface,
|
|
527
|
-
*,
|
|
528
|
-
radius: int,
|
|
529
|
-
palm_angle: float | None = None,
|
|
530
|
-
tracker: bool = False,
|
|
531
|
-
wall_follower: bool = False,
|
|
532
|
-
) -> None:
|
|
533
|
-
if tracker:
|
|
534
|
-
outline_color = TRACKER_OUTLINE_COLOR
|
|
535
|
-
elif wall_follower:
|
|
536
|
-
outline_color = WALL_FOLLOWER_OUTLINE_COLOR
|
|
537
|
-
else:
|
|
538
|
-
outline_color = DARK_RED
|
|
539
|
-
surface.fill((0, 0, 0, 0))
|
|
540
|
-
_draw_outlined_circle(
|
|
541
|
-
surface,
|
|
542
|
-
(radius, radius),
|
|
543
|
-
radius,
|
|
544
|
-
RED,
|
|
545
|
-
outline_color,
|
|
546
|
-
1,
|
|
547
|
-
)
|
|
548
|
-
if palm_angle is None:
|
|
549
|
-
return
|
|
550
|
-
palm_radius = max(1, radius // 3)
|
|
551
|
-
palm_offset = radius - palm_radius * 0.3
|
|
552
|
-
palm_x = radius + math.cos(palm_angle) * palm_offset
|
|
553
|
-
palm_y = radius + math.sin(palm_angle) * palm_offset
|
|
554
|
-
pygame.draw.circle(
|
|
555
|
-
surface,
|
|
556
|
-
outline_color,
|
|
557
|
-
(int(palm_x), int(palm_y)),
|
|
558
|
-
palm_radius,
|
|
559
|
-
)
|
|
560
|
-
|
|
561
|
-
|
|
562
759
|
def build_fuel_can_surface(width: int, height: int) -> pygame.Surface:
|
|
563
760
|
return _draw_polygon_surface(width, height, FUEL_CAN_SPEC)
|
|
564
761
|
|
|
@@ -572,6 +769,7 @@ def build_shoes_surface(width: int, height: int) -> pygame.Surface:
|
|
|
572
769
|
|
|
573
770
|
|
|
574
771
|
__all__ = [
|
|
772
|
+
"angle_bin_from_vector",
|
|
575
773
|
"EnvironmentPalette",
|
|
576
774
|
"FogRing",
|
|
577
775
|
"RenderAssets",
|
|
@@ -580,14 +778,17 @@ __all__ = [
|
|
|
580
778
|
"resolve_car_color",
|
|
581
779
|
"resolve_steel_beam_colors",
|
|
582
780
|
"CAR_COLOR_SCHEMES",
|
|
583
|
-
"
|
|
584
|
-
"
|
|
585
|
-
"
|
|
781
|
+
"build_player_directional_surfaces",
|
|
782
|
+
"build_humanoid_directional_surfaces",
|
|
783
|
+
"draw_humanoid_hand",
|
|
784
|
+
"draw_humanoid_nose",
|
|
785
|
+
"build_survivor_directional_surfaces",
|
|
786
|
+
"build_zombie_directional_surfaces",
|
|
586
787
|
"build_car_surface",
|
|
788
|
+
"build_car_directional_surfaces",
|
|
587
789
|
"paint_car_surface",
|
|
588
790
|
"paint_wall_surface",
|
|
589
791
|
"paint_steel_beam_surface",
|
|
590
|
-
"paint_zombie_surface",
|
|
591
792
|
"build_fuel_can_surface",
|
|
592
793
|
"build_flashlight_surface",
|
|
593
794
|
"build_shoes_surface",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import math
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
|
|
7
8
|
from .entities_constants import FOV_RADIUS, PLAYER_RADIUS
|
|
@@ -11,9 +12,14 @@ HUMANOID_OUTLINE_COLOR = (0, 80, 200)
|
|
|
11
12
|
HUMANOID_OUTLINE_WIDTH = 1
|
|
12
13
|
BUDDY_COLOR = (0, 180, 63)
|
|
13
14
|
SURVIVOR_COLOR = (198, 198, 198)
|
|
15
|
+
ZOMBIE_BODY_COLOR = (180, 0, 0)
|
|
16
|
+
ZOMBIE_OUTLINE_COLOR = (255, 60, 60)
|
|
17
|
+
ZOMBIE_NOSE_COLOR = (255, 80, 80)
|
|
14
18
|
FALLING_ZOMBIE_COLOR = (45, 45, 45)
|
|
15
19
|
FALLING_WHIRLWIND_COLOR = (200, 200, 200, 120)
|
|
16
20
|
FALLING_DUST_COLOR = (70, 70, 70, 130)
|
|
21
|
+
ANGLE_BINS = 16
|
|
22
|
+
HAND_SPREAD_RAD = math.radians(75)
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
@dataclass(frozen=True)
|
|
@@ -93,6 +99,11 @@ __all__ = [
|
|
|
93
99
|
"FALLING_ZOMBIE_COLOR",
|
|
94
100
|
"FALLING_WHIRLWIND_COLOR",
|
|
95
101
|
"FALLING_DUST_COLOR",
|
|
102
|
+
"ZOMBIE_BODY_COLOR",
|
|
103
|
+
"ZOMBIE_OUTLINE_COLOR",
|
|
104
|
+
"ZOMBIE_NOSE_COLOR",
|
|
105
|
+
"ANGLE_BINS",
|
|
106
|
+
"HAND_SPREAD_RAD",
|
|
96
107
|
"HUMANOID_OUTLINE_COLOR",
|
|
97
108
|
"HUMANOID_OUTLINE_WIDTH",
|
|
98
109
|
"SURVIVOR_COLOR",
|
|
@@ -47,9 +47,9 @@ def game_over_screen(
|
|
|
47
47
|
|
|
48
48
|
while True:
|
|
49
49
|
if not state.overview_created:
|
|
50
|
-
level_rect = game_data.layout.
|
|
51
|
-
level_width = level_rect
|
|
52
|
-
level_height = level_rect
|
|
50
|
+
level_rect = game_data.layout.field_rect
|
|
51
|
+
level_width = level_rect.width
|
|
52
|
+
level_height = level_rect.height
|
|
53
53
|
overview_surface = pygame.Surface((level_width, level_height))
|
|
54
54
|
footprints_to_draw = state.footprints if footprints_enabled else []
|
|
55
55
|
draw_level_overview(
|
|
@@ -120,10 +120,12 @@ def gameplay_screen(
|
|
|
120
120
|
spawn_survivors(game_data, layout_data)
|
|
121
121
|
|
|
122
122
|
occupied_centers: set[tuple[int, int]] = set()
|
|
123
|
+
cell_size = game_data.cell_size
|
|
123
124
|
if stage.requires_fuel:
|
|
124
125
|
fuel_spawn_count = stage.fuel_spawn_count
|
|
125
126
|
fuel_can = place_fuel_can(
|
|
126
127
|
layout_data["walkable_cells"],
|
|
128
|
+
cell_size,
|
|
127
129
|
player,
|
|
128
130
|
cars=game_data.waiting_cars,
|
|
129
131
|
reserved_centers=occupied_centers,
|
|
@@ -136,6 +138,7 @@ def gameplay_screen(
|
|
|
136
138
|
flashlight_count = stage.initial_flashlight_count
|
|
137
139
|
flashlights = place_flashlights(
|
|
138
140
|
layout_data["walkable_cells"],
|
|
141
|
+
cell_size,
|
|
139
142
|
player,
|
|
140
143
|
cars=game_data.waiting_cars,
|
|
141
144
|
reserved_centers=occupied_centers,
|
|
@@ -149,6 +152,7 @@ def gameplay_screen(
|
|
|
149
152
|
shoes_count = stage.initial_shoes_count
|
|
150
153
|
shoes_list = place_shoes(
|
|
151
154
|
layout_data["walkable_cells"],
|
|
155
|
+
cell_size,
|
|
152
156
|
player,
|
|
153
157
|
cars=game_data.waiting_cars,
|
|
154
158
|
reserved_centers=occupied_centers,
|
zombie_escape/stage_constants.py
CHANGED
|
@@ -130,7 +130,7 @@ STAGES: list[Stage] = [
|
|
|
130
130
|
available=True,
|
|
131
131
|
rescue_stage=True,
|
|
132
132
|
tile_size=40,
|
|
133
|
-
wall_algorithm="
|
|
133
|
+
wall_algorithm="sparse_moore.10%",
|
|
134
134
|
exterior_spawn_weight=0.7,
|
|
135
135
|
interior_spawn_weight=0.3,
|
|
136
136
|
zombie_normal_ratio=0.4,
|
|
@@ -148,7 +148,7 @@ STAGES: list[Stage] = [
|
|
|
148
148
|
grid_cols=120,
|
|
149
149
|
grid_rows=7,
|
|
150
150
|
available=True,
|
|
151
|
-
wall_algorithm="
|
|
151
|
+
wall_algorithm="sparse_moore.10%",
|
|
152
152
|
exterior_spawn_weight=0.3,
|
|
153
153
|
interior_spawn_weight=0.7,
|
|
154
154
|
zombie_normal_ratio=0.5,
|
|
@@ -224,8 +224,8 @@ STAGES: list[Stage] = [
|
|
|
224
224
|
description_key="stages.stage15.description",
|
|
225
225
|
available=True,
|
|
226
226
|
buddy_required_count=1,
|
|
227
|
-
grid_cols=
|
|
228
|
-
grid_rows=
|
|
227
|
+
grid_cols=64,
|
|
228
|
+
grid_rows=24,
|
|
229
229
|
tile_size=35,
|
|
230
230
|
wall_algorithm="grid_wire",
|
|
231
231
|
requires_fuel=True,
|
|
@@ -237,7 +237,7 @@ STAGES: list[Stage] = [
|
|
|
237
237
|
zombie_normal_ratio=0.5,
|
|
238
238
|
zombie_wall_follower_ratio=0.5,
|
|
239
239
|
fall_spawn_zones=[
|
|
240
|
-
(33, 2, 4,
|
|
240
|
+
(33, 2, 4, 18),
|
|
241
241
|
],
|
|
242
242
|
zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
|
|
243
243
|
initial_shoes_count=1,
|
zombie_escape/zombie_escape.py
CHANGED
|
@@ -21,7 +21,7 @@ from .entities_constants import (
|
|
|
21
21
|
from .gameplay import calculate_car_speed_for_passengers
|
|
22
22
|
from .level_constants import DEFAULT_TILE_SIZE
|
|
23
23
|
from .localization import set_language
|
|
24
|
-
from .models import
|
|
24
|
+
from .models import Stage
|
|
25
25
|
from .render_constants import RenderAssets, build_render_assets
|
|
26
26
|
from .screen_constants import (
|
|
27
27
|
DEFAULT_WINDOW_SCALE,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zombie-escape
|
|
3
|
-
Version: 1.12.
|
|
3
|
+
Version: 1.12.3
|
|
4
4
|
Summary: Top-down zombie survival game built with pygame.
|
|
5
5
|
Project-URL: Homepage, https://github.com/tos-kamiya/zombie-escape
|
|
6
6
|
Author-email: Toshihiro Kamiya <kamiya@mbj.nifty.com>
|
|
@@ -93,7 +93,7 @@ Open **Settings** from the title to toggle gameplay assists:
|
|
|
93
93
|
|
|
94
94
|
### Characters/Items
|
|
95
95
|
|
|
96
|
-
- **Player:** A blue circle. Controlled with the WASD or arrow keys. When carrying fuel a tiny yellow square appears near the sprite so you can immediately see whether you're ready to drive.
|
|
96
|
+
- **Player:** A blue circle with small hands. Controlled with the WASD or arrow keys. When carrying fuel a tiny yellow square appears near the sprite so you can immediately see whether you're ready to drive.
|
|
97
97
|
- **Zombie:** A red circle. Will chase the player (or car) once detected.
|
|
98
98
|
- When out of sight, the zombie's movement mode will randomly switch every certain time (moving horizontally/vertically only, side-to-side movement, random movement, etc.).
|
|
99
99
|
- Variants with different behavior have been observed.
|
|
@@ -108,10 +108,11 @@ Open **Settings** from the title to toggle gameplay assists:
|
|
|
108
108
|
- **Flashlight:** Each pickup expands your visible radius by about 20% (grab two to reach the max boost).
|
|
109
109
|
- **Steel Beam (optional):** A square post with crossed diagonals; same collision as inner walls but with triple durability. Spawns independently of inner walls (may overlap them). If an inner wall covers a beam, the beam appears once the wall is destroyed.
|
|
110
110
|
- **Fuel Can (Stages 2 & 3):** A yellow jerrycan that only spawns on the fuel-run stages. Pick it up before driving the car; once collected the on-player indicator appears until you refuel the car.
|
|
111
|
-
- **Buddy (Stage 3):** A green circle survivor with a blue outline who spawns somewhere in the building and waits.
|
|
111
|
+
- **Buddy (Stage 3):** A green circle survivor with small hands and a blue outline who spawns somewhere in the building and waits.
|
|
112
112
|
- Zombies only choose to pursue the buddy if they are on-screen; otherwise they ignore them.
|
|
113
113
|
- If a zombie tags the buddy off-screen, the buddy quietly respawns somewhere else instead of ending the run.
|
|
114
114
|
- Touch the buddy on foot to make them follow you (at 70% of player speed). Touch them while driving to pick them up.
|
|
115
|
+
- If you bash an inner wall or steel beam, the buddy will drift toward that spot and help chip away at it.
|
|
115
116
|
- **Survivors (Stage 4):** Pale gray civilians with a blue outline, scattered indoors.
|
|
116
117
|
- They stand still until you get close, then shuffle toward you at about one-third of player speed.
|
|
117
118
|
- Zombies can convert them if both are on-screen; the survivor shouts a line and turns instantly.
|