1
0
Fork 0
mirror of https://github.com/EDeev/pixel_gamble.git synced 2026-10-07 23:49:29 +03:00
pixel_gamble/game/pixelgamble/entity.py
Egor Deev 4ecb9257c8 Pixel Gamble 2.0: законченная игра на ресурсах Ninja Adventure
- 10 волн, 5 видов врагов со своим поведением и босс в три фазы
- меч, копьё и посох, 11 улучшений между волнами, опыт и уровни
- меню, пауза, настройки, итоги партии и рекорд, русский и английский
- мир 480×270 с масштабированием, интерфейс в 960×540, скорости от dt
- асинхронный цикл: та же игра в браузере через pygbag
- тесты без окна, автоигрок, CI, релизы exe/Linux, GitHub Pages
- README на двух языках; исходная версия — релиз v1.0.0
2026-10-07 12:29:41 +00:00

112 lines
4.7 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 . import assets
Vector = pygame.Vector2
def direction_name(vec):
"""Ближайшее из четырёх направлений спрайта."""
if abs(vec.x) > abs(vec.y):
return "right" if vec.x > 0 else "left"
return "down" if vec.y >= 0 else "up"
DIR_VECTORS = {"down": Vector(0, 1), "up": Vector(0, -1), "left": Vector(-1, 0), "right": Vector(1, 0)}
class Entity(pygame.sprite.Sprite):
def __init__(self, pos, hit_w, hit_h):
super().__init__()
self.pos = Vector(pos)
self.hitbox = pygame.Rect(0, 0, hit_w, hit_h)
self.hitbox.center = (round(self.pos.x), round(self.pos.y))
self.knock = Vector()
self.flash = 0.0
self.facing = "down"
self.anim = 0.0
self.image = pygame.Surface((1, 1), pygame.SRCALPHA)
self.rect = self.image.get_rect()
@property
def sort_y(self):
return self.hitbox.bottom
def move(self, delta, obstacles):
"""Сдвиг с упором в препятствия: сначала по X, потом по Y."""
for axis in (0, 1):
if not delta[axis]:
continue
before = self.hitbox.copy()
self.pos[axis] += delta[axis]
self.hitbox.center = (round(self.pos.x), round(self.pos.y))
hits = self.hitbox.collidelistall(obstacles)
for i in hits:
wall = obstacles[i]
if before.colliderect(wall):
self.push_out(wall) # уже стояли внутри — упор по направлению шага выкинул бы насквозь
elif axis == 0:
if delta.x > 0:
self.hitbox.right = wall.left
else:
self.hitbox.left = wall.right
elif delta.y > 0:
self.hitbox.bottom = wall.top
else:
self.hitbox.top = wall.bottom
if hits: # дробную часть позиции сбрасываем только при упоре
self.pos.update(self.hitbox.center)
def steer(self, direction, distance, obstacles, dt):
"""Шаг с обходом препятствий: если прямо упёрлись — пробуем в обход и держим выбранную сторону."""
self.detour_time = max(0.0, getattr(self, "detour_time", 0.0) - dt)
if self.detour_time > 0:
direction = direction.rotate(self.detour)
start = Vector(self.pos)
self.move(direction * distance, obstacles)
if distance <= 0 or self.pos.distance_to(start) > distance * 0.4:
return direction
side = getattr(self, "detour_side", 1)
for angle in (45 * side, -45 * side, 90 * side, -90 * side, 135 * side, -135 * side):
self.pos.update(start)
self.hitbox.center = (round(start.x), round(start.y))
self.move(direction.rotate(angle) * distance, obstacles)
if self.pos.distance_to(start) > distance * 0.4:
self.detour, self.detour_time = angle, 0.6
self.detour_side = 1 if angle > 0 else -1
return direction.rotate(angle)
return direction
def push_out(self, wall):
"""Вытолкнуть из препятствия по кратчайшему пути."""
box = self.hitbox
shifts = [(wall.left - box.right, 0), (wall.right - box.left, 0),
(0, wall.top - box.bottom), (0, wall.bottom - box.top)]
dx, dy = min(shifts, key=lambda s: abs(s[0]) + abs(s[1]))
box.move_ip(dx, dy)
def keep_inside(self, area):
"""Страховка: не выходить за пределы арены."""
if not area.contains(self.hitbox):
self.hitbox.clamp_ip(area)
self.pos.update(self.hitbox.center)
def apply_knock(self, dt, obstacles):
if self.knock.length_squared() > 1:
self.move(self.knock * dt, obstacles)
self.knock *= max(0.0, 1 - 10 * dt)
else:
self.knock.update(0, 0)
def place_image(self, image, offset_y=0):
self.image = assets.white(image) if self.flash > 0 else image
self.rect = self.image.get_rect(midbottom=(self.hitbox.centerx, self.hitbox.bottom + offset_y))
def distance_to(self, other):
return self.pos.distance_to(other.pos)
def direction_to(self, other):
delta = other.pos - self.pos
return delta.normalize() if delta.length_squared() > 0 else Vector()