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

180 lines
6.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pygame
from coords import Coords, Direction
from common import DrawableGameObject, EventHandler
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)
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
self.auto_move_target = None
# картинка изначально влево, а надо бы начинать со взгляда вправо
self.flip()
def draw(self):
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
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
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)
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)