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/ui.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

170 lines
6.8 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.

"""Элементы интерфейса в экранных координатах (960×540): текст, панели, меню, сердечки, карточки."""
import pygame
from . import assets
from .settings import BLACK, GOLD, GREY, PANEL, PANEL_EDGE, PANEL_LIGHT, SCALE, SCREEN_H, SCREEN_W, WHITE
# размеры шрифта: Tiny5 чёткий на кратных 10
SMALL, MEDIUM, LARGE, HUGE = 20, 30, 40, 60
_text_cache = {}
def outlined(value, size, color, outline=(20, 16, 24), face="text"):
"""Текст с контуром — читается на любом фоне. Результат кэшируется."""
key = (value, size, color, outline, face)
image = _text_cache.get(key)
if image is None:
font = assets.font(size, face)
base = font.render(value, False, color)
edge = font.render(value, False, outline)
w = max(2, size // 10)
image = pygame.Surface((base.get_width() + 2 * w, base.get_height() + 2 * w), pygame.SRCALPHA)
for dx in (0, w, 2 * w):
for dy in (0, w, 2 * w):
image.blit(edge, (dx, dy))
image.blit(base, (w, w))
if len(_text_cache) > 600:
_text_cache.clear()
_text_cache[key] = image
return image
def text(surface, value, pos, size=SMALL, color=WHITE, anchor="topleft", face="text"):
image = outlined(str(value), size, color, face=face)
rect = image.get_rect(**{anchor: pos})
surface.blit(image, rect)
return rect
def big(image, factor=SCALE):
return pygame.transform.scale_by(image, factor)
def panel(surface, rect, color=PANEL, edge=PANEL_EDGE, light=PANEL_LIGHT):
rect = pygame.Rect(rect)
pygame.draw.rect(surface, edge, rect, border_radius=6)
pygame.draw.rect(surface, light, rect.inflate(-4, -4), border_radius=5)
pygame.draw.rect(surface, color, rect.inflate(-8, -8), border_radius=4)
def dim(surface, alpha=150):
shade = pygame.Surface(surface.get_size(), pygame.SRCALPHA)
shade.fill((10, 8, 14, alpha))
surface.blit(shade, (0, 0))
class Menu:
"""Вертикальный список кнопок: стрелки/WS, Enter/пробел, мышь."""
def __init__(self, items, center, width=300, gap=40):
self.items = items # [(id, подпись)]
self.center = center
self.width, self.gap = width, gap
self.index = 0
def rects(self):
top = self.center[1] - (len(self.items) * self.gap) // 2
return [pygame.Rect(self.center[0] - self.width // 2, top + i * self.gap, self.width, self.gap - 8)
for i in range(len(self.items))]
def handle(self, event, audio):
"""Возвращает id выбранной кнопки или None."""
if event.type == pygame.KEYDOWN:
if event.key in (pygame.K_UP, pygame.K_w):
self.index = (self.index - 1) % len(self.items)
audio.play("menu_move")
elif event.key in (pygame.K_DOWN, pygame.K_s):
self.index = (self.index + 1) % len(self.items)
audio.play("menu_move")
elif event.key in (pygame.K_RETURN, pygame.K_SPACE, pygame.K_KP_ENTER):
audio.play("menu_accept")
return self.items[self.index][0]
elif event.type == pygame.MOUSEMOTION:
for i, rect in enumerate(self.rects()):
if rect.collidepoint(event.pos) and i != self.index:
self.index = i
audio.play("menu_move")
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
for i, rect in enumerate(self.rects()):
if rect.collidepoint(event.pos):
self.index = i
audio.play("menu_accept")
return self.items[i][0]
return None
def draw(self, surface):
for i, (rect, (_, label)) in enumerate(zip(self.rects(), self.items, strict=True)):
active = i == self.index
panel(surface, rect, color=(86, 58, 52) if active else PANEL)
text(surface, label, rect.center, SMALL, GOLD if active else WHITE, "center")
if active:
text(surface, ">", (rect.left - 12, rect.centery), SMALL, GOLD, "midright")
_hearts = None
def hearts(surface, player, pos):
global _hearts
if _hearts is None:
_hearts = [big(f) for f in assets.strip("ui/hearts.png", 16)] # пусто, 1/4, 1/2, 3/4, целое
x, y = pos
for i in range(player.max_hp // 4):
quarters = max(0, min(4, player.hp - i * 4))
surface.blit(_hearts[quarters], (x + i * 26, y))
def bar(surface, rect, ratio, color, back=(40, 30, 40)):
rect = pygame.Rect(rect)
pygame.draw.rect(surface, BLACK, rect)
inner = rect.inflate(-4, -4)
pygame.draw.rect(surface, back, inner)
if ratio > 0:
pygame.draw.rect(surface, color, (inner.x, inner.y, round(inner.width * min(1.0, ratio)), inner.height))
def weapon_slots(surface, player, pos):
x, y = pos
for i, weapon in enumerate(player.weapons):
rect = pygame.Rect(x + i * 44, y, 40, 52)
active = weapon == player.weapon
panel(surface, rect, color=(86, 58, 52) if active else PANEL, light=GOLD if active else PANEL_LIGHT)
icon = big(assets.image(f"items/{weapon}.png"))
surface.blit(icon, icon.get_rect(center=(rect.centerx, rect.centery - 2)))
text(surface, i + 1, (rect.right - 3, rect.bottom + 2), SMALL, GREY, "bottomright")
def card_rects(count=3):
w, h, gap = 232, 240, 24
left = SCREEN_W // 2 - (count * w + (count - 1) * gap) // 2
return [pygame.Rect(left + i * (w + gap), SCREEN_H // 2 - h // 2 + 20, w, h) for i in range(count)]
def cards(surface, keys, lang, selected):
for i, (key, rect) in enumerate(zip(keys, card_rects(len(keys)), strict=True)):
if i == selected:
rect = rect.move(0, -6)
active = i == selected
panel(surface, rect, color=(86, 58, 52) if active else PANEL, light=GOLD if active else PANEL_LIGHT)
icon = big(assets.image(f"icons/{key}.png"), 3)
surface.blit(icon, icon.get_rect(center=(rect.centerx, rect.top + 64)))
title, desc = lang.upgrade(key)
text(surface, title, (rect.centerx, rect.top + 118), SMALL, GOLD, "midtop")
for j, line in enumerate(wrap(desc, 20)):
text(surface, line, (rect.centerx, rect.top + 152 + j * 22), SMALL, WHITE, "midtop")
text(surface, i + 1, (rect.centerx, rect.bottom - 8), SMALL, GREY, "midbottom")
def wrap(value, width):
words, lines, line = value.split(), [], ""
for word in words:
if len(line) + len(word) + 1 > width and line:
lines.append(line)
line = word
else:
line = f"{line} {word}".strip()
if line:
lines.append(line)
return lines