180 lines
6.2 KiB
Python
180 lines
6.2 KiB
Python
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)
|