py_stepik/pygame-wasm/phantomcastle/game/hero.py

180 lines
6.2 KiB
Python
Raw Normal View History

import pygame
from coords import Coords, Direction
from common import DrawableGameObject, EventHandler
2024-04-06 20:56:26 +00:00
def point_at(coords: Coords) -> tuple[pygame.Rect, pygame.Mask]:
rect = pygame.Rect(coords.x, coords.y, 1, 1)
mask = pygame.Mask((1, 1), fill=True)
return rect, mask
def collide_with_walls(coords: Coords, walls: DrawableGameObject) -> bool:
rect, mask = point_at(coords)
return walls.check_collision(rect, mask)
def is_valid_point_to_move(coords: Coords, walls: DrawableGameObject) -> bool:
return not collide_with_walls(coords, walls)
class Hero(DrawableGameObject, EventHandler):
"""объект главного героя"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
2024-04-07 17:37:02 +00:00
self._surface = pygame.image.load(self.assets["ghost.png"]).convert_alpha()
self.rect = self.surface.get_rect()
sf = Coords(0.8, 0.8)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
self.rect.topleft = coords
self.active = True
self.looking_right = False
self._speed = 1
self.direction = Direction.RIGHT
self.mouse_active = False
2024-04-06 20:56:26 +00:00
self.auto_move_target = None
# картинка изначально влево, а надо бы начинать со взгляда вправо
self.flip()
def draw(self):
2024-04-06 20:56:26 +00:00
self.auto_move()
self.parent.surface.blit(self.surface, self.rect)
def _check_collision(self, coords):
"""Проверка пересечения со стенами"""
new_rect = self.rect.copy()
new_rect.topleft = coords
return self.scene.walls.check_collision(new_rect, self.mask)
@property
def speed(self):
return max(self._speed, 1)
@speed.setter
def speed(self, value):
self._speed = min(value, 15)
def _reduce_step(self, coords):
"""Уменьшение шага движения, с целью подойти вплотную к стене"""
delta = coords - self.coords
dx, dy = 0, 0
if abs(delta.x) > 1:
dx = 1 * (delta.x < 0 or -1)
if abs(delta.y) > 1:
dy = 1 * (delta.y < 0 or -1)
return coords + Coords(dx, dy)
def set_coords(self, coords: Coords):
# проверка колизии
has_collision = self._check_collision(coords)
if not has_collision:
super().set_coords(coords)
self.scene.coins.collect(self)
return
2024-04-06 20:56:26 +00:00
self.auto_move_target = None
# уменьшение шага
while has_collision and coords != self.coords:
coords_new = self._reduce_step(coords)
if coords_new == coords:
return # не могу уменьшить шаг
coords = coords_new
has_collision = self._check_collision(coords)
super().set_coords(coords)
self.scene.coins.collect(self)
def flip(self):
self.looking_right = not self.looking_right
self._surface = pygame.transform.flip(self.surface, flip_x=True, flip_y=False)
def update_direction(self, direction: Direction):
if direction in (Direction.LEFT, Direction.RIGHT):
going_right = direction == Direction.RIGHT
if self.looking_right != going_right:
self.flip()
if direction != self.direction:
self.speed = 0
self.direction = direction
else:
self.speed += 1
def move(self, direction: Direction, step: int = 1):
self.update_direction(direction)
self.coords += direction.as_coords() * step * self.speed // 3
2024-04-06 20:56:26 +00:00
def auto_move(self):
if self.auto_move_target is None:
return
rect, mask = point_at(self.auto_move_target)
if self.overlap(rect, mask):
self.auto_move_target = None
return
direction = Direction.from_coords(self.auto_move_target - self.coords)
if direction:
self.move(direction, step=1)
else:
self.auto_move_target = None
def handle_mouse_event(self, event: pygame.event.Event):
if event.type not in (
pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP,
pygame.MOUSEMOTION,
):
return
match event.type:
case pygame.MOUSEBUTTONDOWN:
self.mouse_active = self.rect.collidepoint(event.pos)
2024-04-06 20:56:26 +00:00
if not self.mouse_active and is_valid_point_to_move(
Coords(*event.pos), self.scene.walls
):
self.auto_move_target = Coords(*event.pos)
else:
self.auto_move_target = None
case pygame.MOUSEBUTTONUP:
self.mouse_active = False
case pygame.MOUSEMOTION if self.mouse_active:
rel = Coords(*event.rel)
direction = Direction.from_coords(rel)
if direction:
self.update_direction(direction)
self.coords += rel
def handle_event(self, event: pygame.event.Event):
if not self.active:
return
self.handle_mouse_event(event)
def handle_keys(self, keys_pressed):
if not self.active:
return
wide, short = 3, 1
if keys_pressed[pygame.K_UP]:
self.move(Direction.UP, wide)
if keys_pressed[pygame.K_DOWN]:
self.move(Direction.DOWN, wide)
if keys_pressed[pygame.K_LEFT]:
self.move(Direction.LEFT, wide)
if keys_pressed[pygame.K_RIGHT]:
self.move(Direction.RIGHT, wide)
if keys_pressed[pygame.K_w]:
self.move(Direction.UP, short)
if keys_pressed[pygame.K_s]:
self.move(Direction.DOWN, short)
if keys_pressed[pygame.K_a]:
self.move(Direction.LEFT, short)
if keys_pressed[pygame.K_d]:
self.move(Direction.RIGHT, short)