nice working and testing
This commit is contained in:
390
jrpg/girl.py
Normal file
390
jrpg/girl.py
Normal file
@@ -0,0 +1,390 @@
|
||||
import pygame
|
||||
import math
|
||||
import sys
|
||||
|
||||
pygame.init()
|
||||
|
||||
W, H = 600, 750
|
||||
screen = pygame.display.set_mode((W, H))
|
||||
pygame.display.set_caption("Anime Girl - Blue Pink Black")
|
||||
|
||||
# Color palette
|
||||
BLACK = (5, 5, 15)
|
||||
DARK = (10, 10, 30)
|
||||
PINK = (255, 100, 180)
|
||||
PINK2 = (255, 160, 210)
|
||||
PINK3 = (200, 60, 130)
|
||||
BLUE = (80, 180, 255)
|
||||
BLUE2 = (40, 120, 220)
|
||||
BLUE3 = (130, 210, 255)
|
||||
CYAN = (0, 220, 255)
|
||||
WHITE = (255, 255, 255)
|
||||
SKIN = (255, 220, 210)
|
||||
SKIN2 = (240, 195, 185)
|
||||
PURPLE = (160, 80, 220)
|
||||
GLITCH = (0, 255, 200)
|
||||
|
||||
clock = pygame.time.Clock()
|
||||
t = 0
|
||||
|
||||
def draw_gradient_bg(surf):
|
||||
for y in range(H):
|
||||
ratio = y / H
|
||||
r = int(5 + 15 * ratio)
|
||||
g = int(5 + 10 * ratio)
|
||||
b = int(15 + 40 * ratio)
|
||||
pygame.draw.line(surf, (r, g, b), (0, y), (W, y))
|
||||
|
||||
def draw_glow(surf, color, pos, radius, alpha=80):
|
||||
glow = pygame.Surface((radius*2, radius*2), pygame.SRCALPHA)
|
||||
for i in range(radius, 0, -4):
|
||||
a = int(alpha * (i / radius) ** 2)
|
||||
r, g, b = color
|
||||
pygame.draw.circle(glow, (r, g, b, a), (radius, radius), i)
|
||||
surf.blit(glow, (pos[0]-radius, pos[1]-radius))
|
||||
|
||||
def draw_stars(surf, t):
|
||||
import random
|
||||
random.seed(42)
|
||||
for i in range(60):
|
||||
x = random.randint(0, W)
|
||||
y = random.randint(0, H//2)
|
||||
size = random.uniform(1, 2.5)
|
||||
flicker = 0.5 + 0.5 * math.sin(t * 2 + i)
|
||||
c = int(150 + 105 * flicker)
|
||||
pygame.draw.circle(surf, (c, c, min(255, c+50)), (x, y), int(size))
|
||||
|
||||
def draw_scanlines(surf):
|
||||
s = pygame.Surface((W, H), pygame.SRCALPHA)
|
||||
for y in range(0, H, 4):
|
||||
pygame.draw.line(s, (0, 0, 0, 30), (0, y), (W, y))
|
||||
surf.blit(s, (0, 0))
|
||||
|
||||
def draw_girl(surf, t):
|
||||
# floating bob
|
||||
bob = math.sin(t * 1.5) * 5
|
||||
cx = W // 2
|
||||
base_y = 480 + bob
|
||||
|
||||
# --- HAIR BACK LAYER ---
|
||||
# Long flowing hair behind body
|
||||
hair_pts_back = [
|
||||
(cx - 120, base_y - 320),
|
||||
(cx - 160, base_y - 200),
|
||||
(cx - 190, base_y - 50),
|
||||
(cx - 170, base_y + 100),
|
||||
(cx - 120, base_y + 200),
|
||||
(cx - 60, base_y + 280),
|
||||
(cx, base_y + 300),
|
||||
(cx + 60, base_y + 280),
|
||||
(cx + 120, base_y + 200),
|
||||
(cx + 170, base_y + 100),
|
||||
(cx + 190, base_y - 50),
|
||||
(cx + 160, base_y - 200),
|
||||
(cx + 120, base_y - 320),
|
||||
]
|
||||
pygame.draw.polygon(surf, BLUE2, hair_pts_back)
|
||||
pygame.draw.polygon(surf, (20, 60, 120), hair_pts_back, 2)
|
||||
|
||||
# --- BODY / TORSO ---
|
||||
# Dress / top
|
||||
body_rect = pygame.Rect(cx - 70, base_y - 100, 140, 200)
|
||||
pygame.draw.ellipse(surf, DARK, pygame.Rect(cx-65, base_y-95, 130, 180))
|
||||
|
||||
# Dress gradient effect - layered
|
||||
for i, col in enumerate([(30, 10, 60), (50, 20, 90), (70, 30, 110)]):
|
||||
pygame.draw.ellipse(surf, col,
|
||||
pygame.Rect(cx-60+i*3, base_y-90+i*2, 120-i*6, 170-i*4))
|
||||
|
||||
# Collar / ribbon
|
||||
ribbon_pts = [(cx-40, base_y-90), (cx, base_y-70), (cx+40, base_y-90),
|
||||
(cx+20, base_y-60), (cx, base_y-50), (cx-20, base_y-60)]
|
||||
pygame.draw.polygon(surf, PINK3, ribbon_pts)
|
||||
|
||||
# Bow on collar
|
||||
bow_left = [(cx-35, base_y-78), (cx-8, base_y-65), (cx-8, base_y-75)]
|
||||
bow_right = [(cx+35, base_y-78), (cx+8, base_y-65), (cx+8, base_y-75)]
|
||||
pygame.draw.polygon(surf, PINK, bow_left)
|
||||
pygame.draw.polygon(surf, PINK, bow_right)
|
||||
pygame.draw.circle(surf, PINK2, (cx, base_y-72), 5)
|
||||
|
||||
# Glowing trim
|
||||
pygame.draw.arc(surf, CYAN,
|
||||
pygame.Rect(cx-62, base_y-92, 124, 40), 0, math.pi, 2)
|
||||
|
||||
# --- NECK ---
|
||||
pygame.draw.rect(surf, SKIN, pygame.Rect(cx-18, base_y-140, 36, 55), border_radius=8)
|
||||
|
||||
# --- HEAD ---
|
||||
head_cy = base_y - 200
|
||||
# Face
|
||||
pygame.draw.ellipse(surf, SKIN, pygame.Rect(cx-65, head_cy-75, 130, 155))
|
||||
# Cheek blush
|
||||
blush_alpha = pygame.Surface((60, 25), pygame.SRCALPHA)
|
||||
pygame.draw.ellipse(blush_alpha, (255, 100, 130, 80), (0, 0, 60, 25))
|
||||
surf.blit(blush_alpha, (cx-85, head_cy+10))
|
||||
surf.blit(blush_alpha, (cx+25, head_cy+10))
|
||||
|
||||
# --- HAIR FRONT ---
|
||||
# Top of head / bangs
|
||||
hair_top = [
|
||||
(cx-70, head_cy-70),
|
||||
(cx-90, head_cy-100),
|
||||
(cx-70, head_cy-130),
|
||||
(cx-30, head_cy-155),
|
||||
(cx, head_cy-165),
|
||||
(cx+30, head_cy-155),
|
||||
(cx+70, head_cy-130),
|
||||
(cx+90, head_cy-100),
|
||||
(cx+70, head_cy-70),
|
||||
(cx+50, head_cy-40),
|
||||
(cx, head_cy-30),
|
||||
(cx-50, head_cy-40),
|
||||
]
|
||||
pygame.draw.polygon(surf, BLUE, hair_top)
|
||||
|
||||
# Bangs (front)
|
||||
bang1 = [(cx-70, head_cy-70), (cx-95, head_cy-10), (cx-65, head_cy+20),
|
||||
(cx-40, head_cy-10), (cx-50, head_cy-50)]
|
||||
bang2 = [(cx-30, head_cy-75), (cx-45, head_cy-5), (cx-20, head_cy+10),
|
||||
(cx+5, head_cy-10), (cx-5, head_cy-60)]
|
||||
bang3 = [(cx+30, head_cy-75), (cx+10, head_cy-5), (cx+30, head_cy+15),
|
||||
(cx+55, head_cy-5), (cx+45, head_cy-60)]
|
||||
bang4 = [(cx+70, head_cy-70), (cx+50, head_cy-50), (cx+55, head_cy+20),
|
||||
(cx+80, head_cy-10), (cx+95, head_cy-30)]
|
||||
for bang in [bang1, bang2, bang3, bang4]:
|
||||
pygame.draw.polygon(surf, BLUE, bang)
|
||||
|
||||
# Hair highlight
|
||||
highlight_pts = [(cx-20, head_cy-155), (cx+20, head_cy-155),
|
||||
(cx+10, head_cy-110), (cx-10, head_cy-110)]
|
||||
pygame.draw.polygon(surf, BLUE3, highlight_pts)
|
||||
|
||||
# Hair shine line
|
||||
pygame.draw.arc(surf, (200, 240, 255),
|
||||
pygame.Rect(cx-30, head_cy-140, 60, 30), 0.2, math.pi-0.2, 2)
|
||||
|
||||
# --- EYES ---
|
||||
eye_y = head_cy + 5
|
||||
eye_offset = math.sin(t * 0.8) * 1 # subtle sway
|
||||
|
||||
for side in [-1, 1]:
|
||||
ex = cx + side * 28
|
||||
ey = eye_y
|
||||
|
||||
# Eye shadow
|
||||
eye_sh = pygame.Surface((50, 30), pygame.SRCALPHA)
|
||||
pygame.draw.ellipse(eye_sh, (80, 0, 60, 100), (0, 0, 50, 30))
|
||||
surf.blit(eye_sh, (ex-25, ey-10))
|
||||
|
||||
# White of eye
|
||||
pygame.draw.ellipse(surf, WHITE, pygame.Rect(ex-18, ey-12, 36, 28))
|
||||
|
||||
# Iris - blue gradient effect
|
||||
pygame.draw.ellipse(surf, BLUE2, pygame.Rect(ex-13, ey-10, 26, 25))
|
||||
pygame.draw.ellipse(surf, BLUE, pygame.Rect(ex-10, ey-7, 20, 19))
|
||||
pygame.draw.ellipse(surf, CYAN, pygame.Rect(ex-6, ey-4, 12, 12))
|
||||
|
||||
# Pupil
|
||||
pygame.draw.ellipse(surf, BLACK, pygame.Rect(ex-5, ey-3, 10, 11))
|
||||
|
||||
# Eye shine
|
||||
pygame.draw.circle(surf, WHITE, (ex-4, ey-1), 3)
|
||||
pygame.draw.circle(surf, WHITE, (ex+3, ey+3), 2)
|
||||
|
||||
# Eyelashes - upper lid
|
||||
pygame.draw.arc(surf, BLACK,
|
||||
pygame.Rect(ex-19, ey-14, 38, 22), 0.1, math.pi-0.1, 3)
|
||||
# Lash lines
|
||||
for lx_off, angle in [(-18, 2.4), (-10, 2.0), (-2, 1.7), (6, 1.5), (14, 1.8)]:
|
||||
lx = ex + lx_off
|
||||
pygame.draw.line(surf, BLACK,
|
||||
(lx, ey - int(12 * math.sin(angle))),
|
||||
(lx - side*2, ey - int(12 * math.sin(angle)) - 5), 2)
|
||||
|
||||
# Lower lash
|
||||
pygame.draw.arc(surf, SKIN2,
|
||||
pygame.Rect(ex-16, ey+2, 32, 14), math.pi+0.2, 2*math.pi-0.2, 1)
|
||||
|
||||
# --- NOSE (subtle) ---
|
||||
pygame.draw.arc(surf, SKIN2,
|
||||
pygame.Rect(cx-6, head_cy+28, 12, 8), math.pi+0.3, 2*math.pi-0.3, 1)
|
||||
|
||||
# --- MOUTH ---
|
||||
mouth_y = head_cy + 50
|
||||
# Smile
|
||||
pygame.draw.arc(surf, PINK3,
|
||||
pygame.Rect(cx-18, mouth_y, 36, 16), math.pi+0.3, 2*math.pi-0.3, 2)
|
||||
# Lips
|
||||
pygame.draw.ellipse(surf, PINK2, pygame.Rect(cx-14, mouth_y+1, 28, 9))
|
||||
pygame.draw.arc(surf, PINK3,
|
||||
pygame.Rect(cx-12, mouth_y, 24, 12), math.pi+0.4, 2*math.pi-0.4, 1)
|
||||
|
||||
# --- EARS ---
|
||||
for side in [-1, 1]:
|
||||
ex = cx + side * 63
|
||||
pygame.draw.ellipse(surf, SKIN, pygame.Rect(ex - 10, head_cy - 5, 20, 28))
|
||||
pygame.draw.ellipse(surf, SKIN2, pygame.Rect(ex - 6, head_cy, 12, 18))
|
||||
|
||||
# --- HAIRPIN / ACCESSORIES ---
|
||||
# Star hairpin left
|
||||
star_x, star_y = cx - 50, head_cy - 110
|
||||
for i in range(5):
|
||||
a1 = math.radians(i * 72 - 90)
|
||||
a2 = math.radians(i * 72 + 36 - 90)
|
||||
p1 = (star_x + 12 * math.cos(a1), star_y + 12 * math.sin(a1))
|
||||
p2 = (star_x + 5 * math.cos(a2), star_y + 5 * math.sin(a2))
|
||||
pygame.draw.line(surf, PINK, (int(p1[0]), int(p1[1])),
|
||||
(int(p2[0]), int(p2[1])), 2)
|
||||
pygame.draw.circle(surf, PINK, (star_x, star_y), 4)
|
||||
|
||||
# Glowing dot accessories in hair
|
||||
for gx, gy, gc in [(cx+40, head_cy-130, CYAN),
|
||||
(cx+70, head_cy-90, PINK),
|
||||
(cx-60, head_cy-95, BLUE3)]:
|
||||
draw_glow(surf, gc, (gx, gy), 15, 60)
|
||||
pygame.draw.circle(surf, gc, (gx, gy), 4)
|
||||
pygame.draw.circle(surf, WHITE, (gx, gy), 2)
|
||||
|
||||
# --- ARMS ---
|
||||
# Left arm
|
||||
arm_wave = math.sin(t * 1.2) * 8
|
||||
arm_l_pts = [(cx-70, base_y-80), (cx-100, base_y-30+arm_wave),
|
||||
(cx-115, base_y+60+arm_wave), (cx-95, base_y+65+arm_wave),
|
||||
(cx-80, base_y-20+arm_wave), (cx-55, base_y-70)]
|
||||
pygame.draw.polygon(surf, (40, 20, 80), arm_l_pts)
|
||||
|
||||
# Right arm
|
||||
arm_r_pts = [(cx+70, base_y-80), (cx+100, base_y-30-arm_wave),
|
||||
(cx+115, base_y+60-arm_wave), (cx+95, base_y+65-arm_wave),
|
||||
(cx+80, base_y-20-arm_wave), (cx+55, base_y-70)]
|
||||
pygame.draw.polygon(surf, (40, 20, 80), arm_r_pts)
|
||||
|
||||
# Hands
|
||||
pygame.draw.circle(surf, SKIN, (cx-108, base_y+62+arm_wave), 14)
|
||||
pygame.draw.circle(surf, SKIN, (cx+108, base_y+62-arm_wave), 14)
|
||||
|
||||
# Arm glow trim
|
||||
pygame.draw.arc(surf, CYAN,
|
||||
pygame.Rect(cx-118, base_y+46+arm_wave, 20, 20), 0, math.pi*2, 1)
|
||||
pygame.draw.arc(surf, PINK,
|
||||
pygame.Rect(cx+98, base_y+46-arm_wave, 20, 20), 0, math.pi*2, 1)
|
||||
|
||||
# --- SKIRT ---
|
||||
skirt_top = base_y + 90
|
||||
skirt_pts = [
|
||||
(cx - 70, skirt_top),
|
||||
(cx - 130, skirt_top + 100),
|
||||
(cx - 110, skirt_top + 180),
|
||||
(cx, skirt_top + 200),
|
||||
(cx + 110, skirt_top + 180),
|
||||
(cx + 130, skirt_top + 100),
|
||||
(cx + 70, skirt_top),
|
||||
]
|
||||
pygame.draw.polygon(surf, (50, 20, 90), skirt_pts)
|
||||
|
||||
# Skirt details - ruffles
|
||||
for i, col in enumerate([PURPLE, PINK3, PINK]):
|
||||
offset = i * 15
|
||||
s_pts = [
|
||||
(cx - 70 - offset//2, skirt_top + offset*3),
|
||||
(cx - 130 - offset, skirt_top + 100 + offset*2),
|
||||
(cx + 130 + offset, skirt_top + 100 + offset*2),
|
||||
(cx + 70 + offset//2, skirt_top + offset*3),
|
||||
]
|
||||
pygame.draw.lines(surf, col, False, s_pts, 2)
|
||||
|
||||
# Skirt sparkles
|
||||
skirt_wave = math.sin(t * 2) * 3
|
||||
for sx, sy in [(cx-80, skirt_top+40), (cx+60, skirt_top+60),
|
||||
(cx-30, skirt_top+120), (cx+100, skirt_top+140)]:
|
||||
pygame.draw.circle(surf, CYAN, (sx, sy+int(skirt_wave)), 2)
|
||||
pygame.draw.circle(surf, PINK2, (sx+5, sy-5+int(skirt_wave)), 1)
|
||||
|
||||
# --- LEGS ---
|
||||
leg_h = skirt_top + 180
|
||||
pygame.draw.rect(surf, SKIN, pygame.Rect(cx-40, leg_h, 30, 80), border_radius=8)
|
||||
pygame.draw.rect(surf, SKIN, pygame.Rect(cx+10, leg_h, 30, 80), border_radius=8)
|
||||
|
||||
# Stockings / boots
|
||||
pygame.draw.rect(surf, (20, 20, 50), pygame.Rect(cx-42, leg_h+50, 34, 40), border_radius=5)
|
||||
pygame.draw.rect(surf, (20, 20, 50), pygame.Rect(cx+8, leg_h+50, 34, 40), border_radius=5)
|
||||
pygame.draw.line(surf, BLUE, (cx-42, leg_h+50), (cx-8, leg_h+50), 2)
|
||||
pygame.draw.line(surf, BLUE, (cx+8, leg_h+50), (cx+42, leg_h+50), 2)
|
||||
|
||||
def draw_particles(surf, t):
|
||||
for i in range(20):
|
||||
px = int((W/2 + 220 * math.cos(t*0.3 + i * 0.9)) % W)
|
||||
py = int((300 + 200 * math.sin(t*0.4 + i * 1.1) + i * 20) % H)
|
||||
size = 1 + int(2 * abs(math.sin(t + i)))
|
||||
colors = [PINK, CYAN, BLUE3, PINK2]
|
||||
c = colors[i % len(colors)]
|
||||
alpha_surf = pygame.Surface((size*4, size*4), pygame.SRCALPHA)
|
||||
pygame.draw.circle(alpha_surf, (*c, 120), (size*2, size*2), size*2)
|
||||
surf.blit(alpha_surf, (px-size*2, py-size*2))
|
||||
pygame.draw.circle(surf, c, (px, py), size)
|
||||
|
||||
def draw_frame_decoration(surf, t):
|
||||
# Corner decorations
|
||||
corner_glow = abs(math.sin(t)) * 100 + 100
|
||||
corners = [(20, 20), (W-20, 20), (20, H-20), (W-20, H-20)]
|
||||
for cx, cy in corners:
|
||||
pygame.draw.circle(surf, PINK, (cx, cy), 3)
|
||||
draw_glow(surf, PINK, (cx, cy), 20, int(corner_glow*0.5))
|
||||
|
||||
# Animated border
|
||||
border_col = (
|
||||
int(60 + 40 * math.sin(t)),
|
||||
int(20 + 20 * math.sin(t + 1)),
|
||||
int(120 + 60 * math.sin(t + 2))
|
||||
)
|
||||
pygame.draw.rect(surf, border_col, (5, 5, W-10, H-10), 2, border_radius=8)
|
||||
|
||||
# Title text
|
||||
try:
|
||||
font = pygame.font.SysFont("Arial", 22, bold=True)
|
||||
font_sm = pygame.font.SysFont("Arial", 13)
|
||||
except:
|
||||
font = pygame.font.Font(None, 28)
|
||||
font_sm = pygame.font.Font(None, 18)
|
||||
|
||||
title = font.render("夢の少女", True, CYAN)
|
||||
sub = font_sm.render("DREAM GIRL ✦ ANIME STYLE", True, PINK2)
|
||||
# Glow effect for title
|
||||
draw_glow(surf, CYAN, (W//2, H-38), 60, 40)
|
||||
surf.blit(title, title.get_rect(center=(W//2, H-38)))
|
||||
surf.blit(sub, sub.get_rect(center=(W//2, H-18)))
|
||||
|
||||
running = True
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
|
||||
running = False
|
||||
|
||||
t += 0.03
|
||||
|
||||
# Background
|
||||
draw_gradient_bg(screen)
|
||||
draw_stars(screen, t)
|
||||
|
||||
# Ambient glow behind character
|
||||
draw_glow(screen, BLUE2, (W//2, 400), 200, 30)
|
||||
draw_glow(screen, PINK3, (W//2, 300), 150, 20)
|
||||
|
||||
# Particles
|
||||
draw_particles(screen, t)
|
||||
|
||||
# Character
|
||||
draw_girl(screen, t)
|
||||
|
||||
# Post effects
|
||||
draw_scanlines(screen)
|
||||
draw_frame_decoration(screen, t)
|
||||
|
||||
pygame.display.flip()
|
||||
clock.tick(60)
|
||||
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
546
jrpg/island_novel.py
Normal file
546
jrpg/island_novel.py
Normal file
@@ -0,0 +1,546 @@
|
||||
"""
|
||||
🏝️ ОСТРОВ ДВУХ СЕРДЕЦ — визуальная новелла
|
||||
Запуск: pip install pygame && python island_novel.py
|
||||
"""
|
||||
|
||||
import pygame
|
||||
import sys
|
||||
import math
|
||||
import random
|
||||
|
||||
pygame.init()
|
||||
|
||||
W, H = 1024, 600
|
||||
screen = pygame.display.set_mode((W, H), pygame.SCALED | pygame.RESIZABLE)
|
||||
pygame.display.set_caption("🏝️ Остров двух сердец")
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
# ── Шрифты ──────────────────────────────────────────────────────────────────
|
||||
def load_font(size, bold=False):
|
||||
for name in ["Segoe UI", "Arial Unicode MS", "DejaVu Sans", "FreeSans", "Liberation Sans"]:
|
||||
try:
|
||||
return pygame.font.SysFont(name, size, bold=bold)
|
||||
except:
|
||||
pass
|
||||
return pygame.font.Font(None, size)
|
||||
|
||||
FONT_BIG = load_font(30, bold=True)
|
||||
FONT_MED = load_font(22)
|
||||
FONT_SMALL = load_font(18)
|
||||
FONT_NAME = load_font(24, bold=True)
|
||||
|
||||
# ── Цвета ────────────────────────────────────────────────────────────────────
|
||||
SKY_DAWN = [(255,180,100), (255,120,80), (180,80,120)]
|
||||
SKY_DAY = [(100,180,255), (50,130,230), (30,80,180)]
|
||||
SKY_DUSK = [(255,120,60), (200,80,120), (100,40,100)]
|
||||
SKY_NIGHT = [(10,10,40), (20,20,80), (5,5,20)]
|
||||
|
||||
SAND_COL = (240, 220, 160)
|
||||
WATER_COL = (60, 160, 220)
|
||||
PALM_TRUNK = (120, 80, 40)
|
||||
PALM_LEAF = (40, 160, 60)
|
||||
|
||||
# ── Рисование фонов ──────────────────────────────────────────────────────────
|
||||
def draw_gradient(surface, colors, rect=None):
|
||||
if rect is None:
|
||||
rect = surface.get_rect()
|
||||
x, y, w, h = rect
|
||||
c1, c2, c3 = colors
|
||||
for i in range(h):
|
||||
t = i / h
|
||||
if t < 0.5:
|
||||
r = c1[0] + (c2[0]-c1[0]) * t*2
|
||||
g = c1[1] + (c2[1]-c1[1]) * t*2
|
||||
b = c1[2] + (c2[2]-c1[2]) * t*2
|
||||
else:
|
||||
r = c2[0] + (c3[0]-c2[0]) * (t-0.5)*2
|
||||
g = c2[1] + (c3[1]-c2[1]) * (t-0.5)*2
|
||||
b = c2[2] + (c3[2]-c2[2]) * (t-0.5)*2
|
||||
pygame.draw.line(surface, (int(r),int(g),int(b)), (x,y+i),(x+w,y+i))
|
||||
|
||||
def draw_stars(surface, n=80):
|
||||
rng = random.Random(42)
|
||||
for _ in range(n):
|
||||
x = rng.randint(0, W)
|
||||
y = rng.randint(0, H//2)
|
||||
r = rng.randint(1,2)
|
||||
pygame.draw.circle(surface, (255,255,220), (x,y), r)
|
||||
|
||||
def draw_sun(surface, time_of_day):
|
||||
if time_of_day == "morning":
|
||||
pos, col = (150, 120), (255,230,100)
|
||||
elif time_of_day == "day":
|
||||
pos, col = (512, 80), (255,255,180)
|
||||
elif time_of_day == "evening":
|
||||
pos, col = (870, 130), (255,160,60)
|
||||
else:
|
||||
pos, col = None, None
|
||||
if pos:
|
||||
pygame.draw.circle(surface, col, pos, 45)
|
||||
pygame.draw.circle(surface, (255,255,255,80), pos, 55, 3)
|
||||
|
||||
def draw_moon(surface):
|
||||
pygame.draw.circle(surface, (230,230,200), (800,100), 35)
|
||||
pygame.draw.circle(surface, (10,10,40), (815,90), 30)
|
||||
|
||||
def draw_palm(surface, x, y, seed=0):
|
||||
rng = random.Random(seed)
|
||||
# ствол
|
||||
points = [(x,y),(x-8,y-40),(x-5,y-90),(x,y-150),(x+5,y-180)]
|
||||
pygame.draw.lines(surface, PALM_TRUNK, False, points, 10)
|
||||
# листья
|
||||
for angle in range(0, 360, 60):
|
||||
rad = math.radians(angle + rng.randint(-20,20))
|
||||
lx = x + int(80*math.cos(rad))
|
||||
ly = y - 185 + int(50*math.sin(rad))
|
||||
pygame.draw.line(surface, PALM_LEAF, (x, y-185), (lx,ly), 6)
|
||||
# скругление
|
||||
pygame.draw.circle(surface, PALM_LEAF, (lx,ly), 8)
|
||||
|
||||
def draw_waves(surface, t):
|
||||
for i in range(5):
|
||||
oy = 30*i
|
||||
for x in range(0, W, 4):
|
||||
y = int(H*0.62 + 8 + oy + 6*math.sin((x/60 + t*0.5 + i)*1.2))
|
||||
pygame.draw.circle(surface, (100,200,240,120), (x,y), 2)
|
||||
|
||||
def build_background(time_of_day):
|
||||
surf = pygame.Surface((W,H))
|
||||
# небо
|
||||
sky = {"morning":SKY_DAWN,"day":SKY_DAY,"evening":SKY_DUSK,"night":SKY_NIGHT}[time_of_day]
|
||||
draw_gradient(surf, sky)
|
||||
if time_of_day == "night":
|
||||
draw_stars(surf)
|
||||
draw_moon(surf)
|
||||
else:
|
||||
draw_sun(surf, time_of_day)
|
||||
# вода
|
||||
pygame.draw.rect(surf, WATER_COL, (0, int(H*0.6), W, int(H*0.4)))
|
||||
# песок
|
||||
pygame.draw.ellipse(surf, SAND_COL, (50, int(H*0.55), W-100, int(H*0.35)))
|
||||
# пальмы
|
||||
draw_palm(surf, 120, int(H*0.75), seed=1)
|
||||
draw_palm(surf, W-180, int(H*0.73), seed=2)
|
||||
draw_palm(surf, 400, int(H*0.78), seed=3)
|
||||
return surf
|
||||
|
||||
# Кэшируем фоны
|
||||
BG = {tod: build_background(tod) for tod in ["morning","day","evening","night"]}
|
||||
|
||||
# ── Персонажи ────────────────────────────────────────────────────────────────
|
||||
def draw_anime_girl(surface, x, y, palette, name, emotion="neutral", flip=False):
|
||||
"""Рисует простую анимешную девочку программно."""
|
||||
s = pygame.Surface((160,300), pygame.SRCALPHA)
|
||||
|
||||
hair_col, dress_col, skin_col, eye_col = palette
|
||||
|
||||
# тело / платье
|
||||
pygame.draw.ellipse(s, dress_col, (35,130,90,140))
|
||||
# руки
|
||||
pygame.draw.ellipse(s, skin_col, (10,140,30,70))
|
||||
pygame.draw.ellipse(s, skin_col, (120,140,30,70))
|
||||
# шея
|
||||
pygame.draw.rect(s, skin_col, (67,105,26,35))
|
||||
# голова
|
||||
pygame.draw.ellipse(s, skin_col, (30,20,100,100))
|
||||
# волосы
|
||||
pygame.draw.ellipse(s, hair_col, (25,10,110,70))
|
||||
pygame.draw.ellipse(s, hair_col, (15,30,40,80)) # левая прядь
|
||||
pygame.draw.ellipse(s, hair_col, (105,30,40,80)) # правая прядь
|
||||
pygame.draw.rect(s, hair_col, (25,10,110,40))
|
||||
# глаза
|
||||
eye_y = 65
|
||||
for ex in [55,95]:
|
||||
pygame.draw.ellipse(s, (255,255,255), (ex-12,eye_y,24,16))
|
||||
pygame.draw.ellipse(s, eye_col, (ex-8,eye_y+2,16,12))
|
||||
pygame.draw.circle(s, (0,0,0), (ex,eye_y+8), 5)
|
||||
pygame.draw.circle(s, (255,255,255), (ex+3,eye_y+4), 2)
|
||||
# брови
|
||||
brow_y = eye_y - 8
|
||||
if emotion == "happy":
|
||||
pygame.draw.arc(s, hair_col, (45,brow_y,20,10), 0, math.pi, 2)
|
||||
pygame.draw.arc(s, hair_col, (90,brow_y,20,10), 0, math.pi, 2)
|
||||
elif emotion == "sad":
|
||||
pygame.draw.arc(s, hair_col, (45,brow_y,20,10), math.pi, 2*math.pi, 2)
|
||||
pygame.draw.arc(s, hair_col, (90,brow_y,20,10), math.pi, 2*math.pi, 2)
|
||||
else:
|
||||
pygame.draw.line(s, hair_col, (45,brow_y),(65,brow_y+2), 2)
|
||||
pygame.draw.line(s, hair_col, (90,brow_y),(110,brow_y+2), 2)
|
||||
# рот
|
||||
mouth_y = 95
|
||||
if emotion == "happy":
|
||||
pygame.draw.arc(s, (200,80,80), (60,mouth_y,30,16), math.pi, 2*math.pi, 2)
|
||||
elif emotion == "sad":
|
||||
pygame.draw.arc(s, (200,80,80), (60,mouth_y,30,16), 0, math.pi, 2)
|
||||
else:
|
||||
pygame.draw.line(s, (200,80,80), (65,mouth_y+8),(90,mouth_y+8), 2)
|
||||
# ушки
|
||||
pygame.draw.circle(s, skin_col, (30,75), 10)
|
||||
pygame.draw.circle(s, skin_col, (130,75), 10)
|
||||
# ноги
|
||||
pygame.draw.rect(s, skin_col, (55,255,20,40))
|
||||
pygame.draw.rect(s, skin_col, (90,255,20,40))
|
||||
|
||||
if flip:
|
||||
s = pygame.transform.flip(s, True, False)
|
||||
surface.blit(s, (x-80, y-280))
|
||||
|
||||
# Палитры персонажей
|
||||
SAKURA_PALETTE = (
|
||||
(255,150,180), # розовые волосы
|
||||
(220,160,220), # фиолетовое платье
|
||||
(255,220,195), # кожа
|
||||
(255,100,180), # розовые глаза
|
||||
)
|
||||
YUKI_PALETTE = (
|
||||
(100,180,255), # голубые волосы
|
||||
(180,220,255), # голубое платье
|
||||
(255,220,195),
|
||||
(60,140,255), # синие глаза
|
||||
)
|
||||
|
||||
# ── Диалоги ──────────────────────────────────────────────────────────────────
|
||||
STORY = [
|
||||
# (day, time_of_day, speaker, emotion, text, points_sakura, points_yuki)
|
||||
# ДЕНЬ 1
|
||||
("День 1","morning","narrator","neutral",
|
||||
"Волны выбросили тебя на песчаный берег. Рядом — две незнакомки.",0,0),
|
||||
("День 1","morning","Сакура","happy",
|
||||
"Эй! Ты жив? Я Сакура! Не переживай, я умею разводить костёр!",0,0),
|
||||
("День 1","morning","Юки","neutral",
|
||||
"Я Юки. Надо составить план выживания. Паниковать бесполезно.",0,0),
|
||||
("День 1","morning","narrator","neutral",
|
||||
"Ты решаешь, кому помочь первой: собрать дрова или найти пресную воду.",0,0),
|
||||
# выбор
|
||||
("День 1","morning","CHOICE","neutral","[1] Помочь Сакуре с костром / [2] Помочь Юки с водой",0,0),
|
||||
|
||||
("День 1","day","narrator","neutral",
|
||||
"Полдень. Солнце жжёт нещадно. Вы нашли кокосы.",0,0),
|
||||
("День 1","day","Сакура","happy",
|
||||
"УРА! Кокосы! Давай устроим пикник прямо здесь! Я так рада!",0,0),
|
||||
("День 1","day","Юки","neutral",
|
||||
"Кокосовое молоко восполнит электролиты. Рационально разделим поровну.",0,0),
|
||||
("День 1","day","CHOICE","neutral","[1] Потанцевать с Сакурой / [2] Вместе составить карту острова с Юки",0,0),
|
||||
|
||||
("День 1","evening","narrator","neutral",
|
||||
"Вечер. Костёр потрескивает. Звёзды зажигаются одна за другой.",0,0),
|
||||
("День 1","evening","Сакура","happy",
|
||||
"Смотри, как красиво! Знаешь... я рада, что ты рядом.",0,0),
|
||||
("День 1","evening","Юки","neutral",
|
||||
"По созвездиям можно определить стороны света. Это пригодится.",0,0),
|
||||
("День 1","evening","CHOICE","neutral","[1] Смотреть на звёзды с Сакурой / [2] Учиться навигации у Юки",0,0),
|
||||
|
||||
("День 1","night","narrator","neutral",
|
||||
"Ночь. Все устали. Ты засыпаешь под шум волн...",0,0),
|
||||
|
||||
# ДЕНЬ 2
|
||||
("День 2","morning","narrator","neutral",
|
||||
"Утро второго дня. Тебя будит Сакура — она уже приготовила фрукты!",0,0),
|
||||
("День 2","morning","Сакура","happy",
|
||||
"Доброе утро! Я нашла манго и бананы! Ешь, пожалуйста!",0,0),
|
||||
("День 2","morning","Юки","neutral",
|
||||
"Я слышала шум вертолёта ночью. Нужно сделать сигнальный знак на пляже.",0,0),
|
||||
("День 2","morning","CHOICE","neutral","[1] Помочь Сакуре украсить лагерь / [2] Помочь Юки сделать SOS-знак",0,0),
|
||||
|
||||
("День 2","day","narrator","neutral",
|
||||
"День. Начался дождь. Пришлось укрываться вместе в пальмовом шалаше.",0,0),
|
||||
("День 2","day","Сакура","happy",
|
||||
"Ахаха, так тесно! Это как приключение в аниме! Мне нравится!",0,0),
|
||||
("День 2","day","Юки","neutral",
|
||||
"Дождевую воду можно собирать. Я уже поставила листья как воронки.",0,0),
|
||||
("День 2","day","CHOICE","neutral","[1] Рассказать Сакуре смешную историю / [2] Помочь Юки с системой сбора воды",0,0),
|
||||
|
||||
("День 2","evening","narrator","neutral",
|
||||
"Вечер. Дождь стих. Все вышли на берег смотреть на закат.",0,0),
|
||||
("День 2","evening","Сакура","happy",
|
||||
"Правда ведь... я хотела бы остаться здесь чуть подольше...",0,0),
|
||||
("День 2","evening","Юки","neutral",
|
||||
"Завтра мы должны попробовать подать сигнал. Но сегодня... спасибо, что ты здесь.",0,0),
|
||||
("День 2","evening","CHOICE","neutral","[1] Взять Сакуру за руку / [2] Сесть рядом с Юки",0,0),
|
||||
|
||||
("День 2","night","narrator","neutral",
|
||||
"Ночь. Ты долго смотришь в звёздное небо, думая о них обеих...",0,0),
|
||||
|
||||
# ДЕНЬ 3
|
||||
("День 3","morning","narrator","neutral",
|
||||
"Третье утро. Вдали виден силуэт корабля!",0,0),
|
||||
("День 3","morning","Юки","happy",
|
||||
"Корабль! Мой сигнальный знак сработал! Нас спасут!",0,0),
|
||||
("День 3","morning","Сакура","sad",
|
||||
"Ура... хотя мне немного грустно. Это были лучшие дни в моей жизни.",0,0),
|
||||
("День 3","morning","CHOICE","neutral","[1] Обнять Сакуру / [2] Сказать Юки, что она умница",0,0),
|
||||
|
||||
("День 3","day","narrator","neutral",
|
||||
"Корабль приближается. Осталось несколько часов. Вы готовите плот.",0,0),
|
||||
("День 3","day","Сакура","neutral",
|
||||
"Эй... что будет, когда мы вернёмся? Мы ведь увидимся снова?",0,0),
|
||||
("День 3","day","Юки","neutral",
|
||||
"Я хочу сказать тебе кое-что важное, пока есть время...",0,0),
|
||||
("День 3","day","CHOICE","neutral","[1] Пообещать Сакуре встретиться / [2] Выслушать Юки",0,0),
|
||||
|
||||
("День 3","evening","narrator","neutral",
|
||||
"Корабль у берега. Это последний вечер на острове.",0,0),
|
||||
("День 3","evening","CHOICE","neutral",
|
||||
"[1] Попрощаться со всеми / [2] Остаться с Сакурой / [3] Остаться с Юки",0,0),
|
||||
]
|
||||
|
||||
# Очки за каждый выбор: (delta_sakura, delta_yuki)
|
||||
CHOICE_POINTS = {
|
||||
# day1 morning
|
||||
0: {1:(3,0), 2:(0,3)},
|
||||
# day1 day
|
||||
1: {1:(3,0), 2:(0,3)},
|
||||
# day1 evening
|
||||
2: {1:(3,0), 2:(0,3)},
|
||||
# day2 morning
|
||||
3: {1:(3,0), 2:(0,3)},
|
||||
# day2 day
|
||||
4: {1:(3,0), 2:(0,3)},
|
||||
# day2 evening
|
||||
5: {1:(3,0), 2:(0,3)},
|
||||
# day3 morning
|
||||
6: {1:(3,0), 2:(0,3)},
|
||||
# day3 day
|
||||
7: {1:(3,0), 2:(0,3)},
|
||||
# day3 evening — финальный выбор
|
||||
8: {1:(0,0), 2:(5,0), 3:(0,5)},
|
||||
}
|
||||
|
||||
# ── Текстовая обёртка ─────────────────────────────────────────────────────────
|
||||
def wrap_text(text, font, max_w):
|
||||
words = text.split()
|
||||
lines, line = [], ""
|
||||
for w in words:
|
||||
test = (line + " " + w).strip()
|
||||
if font.size(test)[0] <= max_w:
|
||||
line = test
|
||||
else:
|
||||
if line: lines.append(line)
|
||||
line = w
|
||||
if line: lines.append(line)
|
||||
return lines
|
||||
|
||||
# ── UI-компоненты ─────────────────────────────────────────────────────────────
|
||||
def draw_textbox(surface, speaker, text, palette=None):
|
||||
box = pygame.Rect(30, H-170, W-60, 150)
|
||||
s = pygame.Surface((box.w, box.h), pygame.SRCALPHA)
|
||||
s.fill((0,0,0,180))
|
||||
surface.blit(s, (box.x, box.y))
|
||||
pygame.draw.rect(surface, (200,200,200), box, 2, border_radius=8)
|
||||
|
||||
name_col = (255,200,100)
|
||||
if speaker == "Сакура":
|
||||
name_col = SAKURA_PALETTE[3]
|
||||
elif speaker == "Юки":
|
||||
name_col = YUKI_PALETTE[3]
|
||||
elif speaker == "narrator":
|
||||
name_col = (180,180,180)
|
||||
|
||||
disp_name = "" if speaker in ("narrator","CHOICE") else speaker
|
||||
if disp_name:
|
||||
ns = FONT_NAME.render(disp_name, True, name_col)
|
||||
surface.blit(ns, (box.x+16, box.y+8))
|
||||
|
||||
lines = wrap_text(text, FONT_MED, box.w-30)
|
||||
for i,l in enumerate(lines[:5]):
|
||||
ts = FONT_MED.render(l, True, (255,255,255))
|
||||
surface.blit(ts, (box.x+16, box.y+38+i*26))
|
||||
|
||||
def draw_hud(surface, ps, py):
|
||||
# полоски очков
|
||||
pygame.draw.rect(surface, (0,0,0,120), (10,10,220,60), border_radius=6)
|
||||
pygame.draw.rect(surface, SAKURA_PALETTE[3], (80,18, min(ps*8,140),18), border_radius=4)
|
||||
pygame.draw.rect(surface, YUKI_PALETTE[3], (80,42, min(py*8,140),18), border_radius=4)
|
||||
surface.blit(FONT_SMALL.render(f"Сакура {ps:2d}", True, SAKURA_PALETTE[3]), (12,18))
|
||||
surface.blit(FONT_SMALL.render(f"Юки {py:2d}", True, YUKI_PALETTE[3]), (12,42))
|
||||
|
||||
def draw_choice_buttons(surface, choices, hover):
|
||||
bw, bh = 360, 44
|
||||
start_y = H - 170 + 50
|
||||
rects = []
|
||||
for i, ch in enumerate(choices):
|
||||
bx = W//2 - bw//2
|
||||
by = start_y + i*(bh+10)
|
||||
col = (80,130,200) if hover==i else (40,60,100)
|
||||
pygame.draw.rect(surface, col, (bx,by,bw,bh), border_radius=8)
|
||||
pygame.draw.rect(surface, (200,200,220),(bx,by,bw,bh), 2, border_radius=8)
|
||||
ts = FONT_MED.render(ch, True, (255,255,255))
|
||||
surface.blit(ts, (bx + bw//2 - ts.get_width()//2, by+bh//2-ts.get_height()//2))
|
||||
rects.append(pygame.Rect(bx,by,bw,bh))
|
||||
return rects
|
||||
|
||||
# ── Экран концовки ────────────────────────────────────────────────────────────
|
||||
def ending_screen(surface, ps, py, last_choice):
|
||||
surface.blit(BG["day"], (0,0))
|
||||
|
||||
if last_choice == 1: # уплываем одни
|
||||
title = "Конец: Одиночное плавание"
|
||||
desc = ("Ты помахал им рукой с борта корабля. Остров скрылся за горизонтом.\n"
|
||||
"В душе — тёплые воспоминания о трёх необычных днях.\n"
|
||||
"Может, судьба ещё сведёт вас снова...")
|
||||
col = (200,200,220)
|
||||
elif last_choice == 2 or (ps > py):
|
||||
title = "Конец: Сердце Сакуры 🌸"
|
||||
desc = ("Сакура прыгнула тебе на шею прямо на пирсе!\n"
|
||||
"«Я знала! Я знала, что ты выберешь меня!»\n"
|
||||
"Юки улыбнулась и тихо пожелала вам счастья.")
|
||||
col = SAKURA_PALETTE[3]
|
||||
draw_anime_girl(surface, 300, 420, SAKURA_PALETTE, "Сакура", "happy")
|
||||
elif last_choice == 3 or (py > ps):
|
||||
title = "Конец: Звезда Юки ❄️"
|
||||
desc = ("Юки остановила тебя у трапа и тихо сказала:\n"
|
||||
"«Я... хотела бы видеть тебя снова. Очень.»\n"
|
||||
"Её щёки стали розовыми. Это было дороже всех слов.")
|
||||
col = YUKI_PALETTE[3]
|
||||
draw_anime_girl(surface, 700, 420, YUKI_PALETTE, "Юки", "happy", flip=True)
|
||||
else:
|
||||
title = "Конец: Три сердца"
|
||||
desc = ("Вы все трое не смогли расстаться.\n"
|
||||
"Решили вместе путешествовать на арендованной яхте.\n"
|
||||
"Остров остался позади, а впереди — бесконечное море.")
|
||||
col = (255,255,200)
|
||||
draw_anime_girl(surface, 280, 420, SAKURA_PALETTE, "Сакура", "happy")
|
||||
draw_anime_girl(surface, 720, 420, YUKI_PALETTE, "Юки", "happy", flip=True)
|
||||
|
||||
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
|
||||
overlay.fill((0,0,0,100))
|
||||
surface.blit(overlay, (0,0))
|
||||
|
||||
ts = FONT_BIG.render(title, True, col)
|
||||
surface.blit(ts, (W//2 - ts.get_width()//2, 60))
|
||||
|
||||
for i, line in enumerate(desc.split("\n")):
|
||||
ls = FONT_MED.render(line, True, (255,255,255))
|
||||
surface.blit(ls, (W//2 - ls.get_width()//2, 150 + i*35))
|
||||
|
||||
hint = FONT_SMALL.render("Нажми любую клавишу для выхода", True, (180,180,180))
|
||||
surface.blit(hint, (W//2 - hint.get_width()//2, H-40))
|
||||
|
||||
score_s = FONT_SMALL.render(f"Очки: Сакура {ps} | Юки {py}", True, (200,200,200))
|
||||
surface.blit(score_s, (W//2 - score_s.get_width()//2, H-65))
|
||||
|
||||
pygame.display.flip()
|
||||
waiting = True
|
||||
while waiting:
|
||||
for e in pygame.event.get():
|
||||
if e.type == pygame.QUIT: pygame.quit(); sys.exit()
|
||||
if e.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN):
|
||||
waiting = False
|
||||
|
||||
# ── Главный цикл ──────────────────────────────────────────────────────────────
|
||||
def main():
|
||||
ps, py = 0, 0 # очки
|
||||
scene_idx = 0
|
||||
choice_idx = 0 # счётчик выборов
|
||||
wave_t = 0
|
||||
hover = -1
|
||||
last_final_choice = 1
|
||||
|
||||
# фильтруем сцены по индексу, собираем выборы
|
||||
scene_list = []
|
||||
for entry in STORY:
|
||||
scene_list.append(entry)
|
||||
|
||||
current = 0
|
||||
choice_mode = False
|
||||
choice_options = []
|
||||
choice_rects = []
|
||||
|
||||
def get_bg(tod):
|
||||
return BG.get(tod, BG["day"])
|
||||
|
||||
def parse_choices(text):
|
||||
parts = [p.strip() for p in text.split("/")]
|
||||
result = []
|
||||
for p in parts:
|
||||
if p.startswith("["):
|
||||
bracket_end = p.index("]")
|
||||
result.append(p[bracket_end+1:].strip())
|
||||
else:
|
||||
result.append(p)
|
||||
return result
|
||||
|
||||
running = True
|
||||
while running and current < len(scene_list):
|
||||
dt = clock.tick(60) / 1000
|
||||
wave_t += dt
|
||||
scene = scene_list[current]
|
||||
day_label, tod, speaker, emotion, text, _, _ = scene
|
||||
|
||||
bg = get_bg(tod)
|
||||
|
||||
# events
|
||||
for e in pygame.event.get():
|
||||
if e.type == pygame.QUIT:
|
||||
pygame.quit(); sys.exit()
|
||||
if e.type == pygame.MOUSEMOTION and choice_mode:
|
||||
hover = -1
|
||||
for i,r in enumerate(choice_rects):
|
||||
if r.collidepoint(e.pos): hover = i
|
||||
if e.type == pygame.MOUSEBUTTONDOWN and e.button==1:
|
||||
if choice_mode:
|
||||
for i,r in enumerate(choice_rects):
|
||||
if r.collidepoint(e.pos):
|
||||
chosen = i+1
|
||||
if choice_idx in CHOICE_POINTS:
|
||||
dp = CHOICE_POINTS[choice_idx].get(chosen,(0,0))
|
||||
ps += dp[0]; py += dp[1]
|
||||
# для финального выбора запомним
|
||||
if choice_idx == 8:
|
||||
last_final_choice = chosen
|
||||
choice_idx += 1
|
||||
choice_mode = False
|
||||
current += 1
|
||||
break
|
||||
else:
|
||||
current += 1
|
||||
if e.type == pygame.KEYDOWN:
|
||||
if not choice_mode:
|
||||
current += 1
|
||||
|
||||
# draw
|
||||
screen.blit(bg, (0,0))
|
||||
# анимация волн поверх воды
|
||||
draw_waves(screen, wave_t)
|
||||
|
||||
# персонажи
|
||||
if speaker != "narrator" and speaker != "CHOICE":
|
||||
if speaker == "Сакура":
|
||||
draw_anime_girl(screen, 280, H-175, SAKURA_PALETTE, "Сакура", emotion)
|
||||
elif speaker == "Юки":
|
||||
draw_anime_girl(screen, W-280, H-175, YUKI_PALETTE, "Юки", emotion, flip=True)
|
||||
|
||||
# всегда рисуем обоих в фоне (маленьких)
|
||||
if speaker == "narrator" or speaker == "CHOICE":
|
||||
# рисуем обоих немного прозрачно
|
||||
tmp = pygame.Surface((W,H), pygame.SRCALPHA)
|
||||
draw_anime_girl(tmp, 220, H-175, SAKURA_PALETTE, "Сакура", "neutral")
|
||||
draw_anime_girl(tmp, W-220, H-175, YUKI_PALETTE, "Юки", "neutral", flip=True)
|
||||
tmp.set_alpha(120)
|
||||
screen.blit(tmp, (0,0))
|
||||
|
||||
# день-лейбл
|
||||
day_surf = FONT_BIG.render(day_label, True, (255,255,220))
|
||||
screen.blit(day_surf, (W//2 - day_surf.get_width()//2, 12))
|
||||
|
||||
# HUD
|
||||
draw_hud(screen, ps, py)
|
||||
|
||||
if speaker == "CHOICE":
|
||||
choice_mode = True
|
||||
choice_options = parse_choices(text)
|
||||
choice_rects = draw_choice_buttons(screen, choice_options, hover)
|
||||
else:
|
||||
draw_textbox(screen, speaker, text)
|
||||
hint = FONT_SMALL.render("[ Кликни или нажми любую клавишу ]", True, (160,160,160))
|
||||
screen.blit(hint, (W//2-hint.get_width()//2, H-22))
|
||||
choice_mode = False
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
# Концовка
|
||||
ending_screen(screen, ps, py, last_final_choice)
|
||||
pygame.quit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
970
jrpg/jrpg_game.py
Normal file
970
jrpg/jrpg_game.py
Normal file
@@ -0,0 +1,970 @@
|
||||
"""
|
||||
Пиксельная деревня - JRPG
|
||||
Уютная маленькая JRPG с деревней, лесом и пошаговыми боями!
|
||||
|
||||
Требования: pip install pygame
|
||||
Запуск: python jrpg_game.py
|
||||
|
||||
Управление:
|
||||
Стрелки - Движение
|
||||
E / Пробел - Взаимодействие / Подтвердить
|
||||
Escape - Отмена / Назад
|
||||
"""
|
||||
|
||||
import pygame
|
||||
import sys
|
||||
import random
|
||||
import math
|
||||
|
||||
pygame.init()
|
||||
|
||||
# ── Константы ──────────────────────────────────────────────────────────────
|
||||
W, H = 640, 480
|
||||
TILE = 32
|
||||
FPS = 60
|
||||
|
||||
# Яркая пиксельная палитра
|
||||
C = {
|
||||
"bg": (20, 20, 35),
|
||||
"sky": (100, 180, 255),
|
||||
"grass": (80, 200, 80),
|
||||
"dark_grass":(60, 160, 60),
|
||||
"path": (210, 180, 130),
|
||||
"water": (60, 160, 220),
|
||||
"tree": (40, 140, 40),
|
||||
"tree_top": (50, 200, 60),
|
||||
"trunk": (140, 90, 40),
|
||||
"house_w": (240, 220, 200),
|
||||
"house_r": (220, 60, 60),
|
||||
"store_w": (200, 220, 255),
|
||||
"store_r": (60, 100, 220),
|
||||
"door": (140, 80, 30),
|
||||
"window": (180, 230, 255),
|
||||
"sign": (240, 200, 100),
|
||||
"white": (255, 255, 255),
|
||||
"black": (0, 0, 0),
|
||||
"gray": (160, 160, 160),
|
||||
"dark": (30, 30, 50),
|
||||
"panel": (20, 20, 50),
|
||||
"panel2": (40, 40, 80),
|
||||
"border": (120, 100, 200),
|
||||
"gold": (255, 210, 50),
|
||||
"red": (240, 60, 60),
|
||||
"green": (80, 220, 100),
|
||||
"blue": (80, 160, 240),
|
||||
"pink": (255, 140, 200),
|
||||
"purple": (180, 100, 255),
|
||||
"yellow": (255, 240, 80),
|
||||
"orange": (255, 160, 40),
|
||||
"hp_red": (220, 50, 50),
|
||||
"mp_blue": (50, 100, 220),
|
||||
"xp_green": (50, 200, 80),
|
||||
}
|
||||
|
||||
screen = pygame.display.set_mode((W, H), pygame.SCALED | pygame.RESIZABLE)
|
||||
pygame.display.set_caption("Пиксельная деревня")
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
# Шрифты с поддержкой кириллицы
|
||||
FONT_PATHS = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
]
|
||||
FONT_PATHS_REGULAR = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
||||
"/usr/share/fonts/truetype/freefont/FreeMono.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
]
|
||||
|
||||
def load_font(paths, size):
|
||||
for p in paths:
|
||||
try:
|
||||
return pygame.font.Font(p, size)
|
||||
except:
|
||||
pass
|
||||
return pygame.font.SysFont("dejavusansmono", size, bold=True)
|
||||
|
||||
font_lg = load_font(FONT_PATHS, 20)
|
||||
font_md = load_font(FONT_PATHS, 15)
|
||||
font_sm = load_font(FONT_PATHS_REGULAR, 12)
|
||||
|
||||
|
||||
# ── Вспомогательные функции ────────────────────────────────────────────────
|
||||
def txt(surface, text, x, y, color, fnt=None):
|
||||
fnt = fnt or font_md
|
||||
surface.blit(fnt.render(str(text), True, color), (x, y))
|
||||
|
||||
def draw_bar(surf, x, y, w, h, val, mx, color, bg=(60,60,60)):
|
||||
pygame.draw.rect(surf, bg, (x, y, w, h))
|
||||
fill = int(w * max(0, val) / max(1, mx))
|
||||
pygame.draw.rect(surf, color, (x, y, fill, h))
|
||||
pygame.draw.rect(surf, C["white"], (x, y, w, h), 1)
|
||||
|
||||
def panel(surf, x, y, w, h, border=True):
|
||||
pygame.draw.rect(surf, C["panel"], (x, y, w, h))
|
||||
if border:
|
||||
pygame.draw.rect(surf, C["border"], (x, y, w, h), 2)
|
||||
|
||||
def flash_color(base, t, amp=40):
|
||||
v = int(amp * abs(math.sin(t * 4)))
|
||||
return tuple(min(255, c + v) for c in base)
|
||||
|
||||
|
||||
# ── Рисование пиксель-арта ──────────────────────────────────────────────────
|
||||
def draw_tree(surf, x, y):
|
||||
pygame.draw.rect(surf, C["trunk"], (x+12, y+28, 8, 12))
|
||||
pygame.draw.polygon(surf, C["tree"], [(x+16,y),(x+2,y+28),(x+30,y+28)])
|
||||
pygame.draw.polygon(surf, C["tree_top"], [(x+16,y+4),(x+4,y+24),(x+28,y+24)])
|
||||
|
||||
def draw_house(surf, x, y, color_w=None, color_r=None):
|
||||
cw = color_w or C["house_w"]
|
||||
cr = color_r or C["house_r"]
|
||||
pygame.draw.rect(surf, cw, (x, y+16, 48, 32))
|
||||
pygame.draw.polygon(surf, cr, [(x,y+16),(x+24,y),(x+48,y+16)])
|
||||
pygame.draw.rect(surf, C["door"], (x+18, y+32, 12, 16))
|
||||
pygame.draw.rect(surf, C["window"], (x+4, y+22, 10, 10))
|
||||
pygame.draw.rect(surf, C["window"], (x+34, y+22, 10, 10))
|
||||
|
||||
def draw_store(surf, x, y):
|
||||
draw_house(surf, x, y, C["store_w"], C["store_r"])
|
||||
pygame.draw.rect(surf, C["sign"], (x+4, y+12, 40, 9))
|
||||
txt(surf, "ЛАВКА", x+5, y+11, C["dark"], font_sm)
|
||||
|
||||
def draw_player(surf, x, y, frame=0, facing=0):
|
||||
bob = int(math.sin(frame * 0.3) * 2)
|
||||
bx, by = x, y + bob
|
||||
pygame.draw.rect(surf, C["blue"], (bx+5, by+10, 12, 12))
|
||||
pygame.draw.rect(surf, (255,210,170), (bx+4, by+2, 14, 12))
|
||||
pygame.draw.rect(surf, C["orange"], (bx+4, by+2, 14, 5))
|
||||
ex = bx+7 if facing != 2 else bx+11
|
||||
pygame.draw.rect(surf, C["dark"], (ex, by+6, 2, 2))
|
||||
pygame.draw.rect(surf, C["dark"], (ex+4, by+6, 2, 2))
|
||||
lx = bx+5 + (2 if frame % 20 < 10 else -1)
|
||||
pygame.draw.rect(surf, C["dark"], (lx, by+22, 4, 6))
|
||||
pygame.draw.rect(surf, C["dark"], (lx+6, by+22, 4, 6))
|
||||
|
||||
def draw_npc_mitsuha(surf, x, y, frame=0):
|
||||
bob = int(math.sin(frame * 0.2) * 1.5)
|
||||
bx, by = x, y + bob
|
||||
pygame.draw.rect(surf, C["pink"], (bx+5, by+10, 12, 12))
|
||||
pygame.draw.rect(surf, (255,210,170), (bx+4, by+2, 14, 12))
|
||||
pygame.draw.rect(surf, C["purple"], (bx+4, by+2, 14, 5))
|
||||
pygame.draw.rect(surf, C["purple"], (bx+14, by+4, 4, 10))
|
||||
pygame.draw.rect(surf, C["dark"], (bx+7, by+6, 2, 2))
|
||||
pygame.draw.rect(surf, C["dark"], (bx+11, by+6, 2, 2))
|
||||
pygame.draw.rect(surf, C["pink"], (bx+5, by+22, 4, 6))
|
||||
pygame.draw.rect(surf, C["pink"], (bx+11, by+22, 4, 6))
|
||||
|
||||
def draw_slime(surf, x, y, frame=0, color=None):
|
||||
c = color or C["green"]
|
||||
bounce = int(abs(math.sin(frame * 0.15)) * 3)
|
||||
bx, by = x, y - bounce
|
||||
pygame.draw.ellipse(surf, c, (bx+2, by+10, 20, 16))
|
||||
pygame.draw.ellipse(surf, tuple(min(255,v+40) for v in c), (bx+4, by+10, 16, 10))
|
||||
pygame.draw.rect(surf, C["white"], (bx+6, by+13, 4, 4))
|
||||
pygame.draw.rect(surf, C["white"], (bx+14, by+13, 4, 4))
|
||||
pygame.draw.rect(surf, C["dark"], (bx+7, by+14, 2, 2))
|
||||
pygame.draw.rect(surf, C["dark"], (bx+15, by+14, 2, 2))
|
||||
|
||||
def draw_fairy(surf, x, y, frame=0):
|
||||
bob = int(math.sin(frame * 0.2) * 4)
|
||||
bx, by = x, y + bob
|
||||
wing_surf = pygame.Surface((24, 14), pygame.SRCALPHA)
|
||||
pygame.draw.ellipse(wing_surf, (200,220,255,160), (0,0,10,10))
|
||||
pygame.draw.ellipse(wing_surf, (200,220,255,160), (14,2,10,8))
|
||||
surf.blit(wing_surf, (bx+4, by+8))
|
||||
pygame.draw.circle(surf, C["yellow"], (bx+12, by+12), 7)
|
||||
pygame.draw.circle(surf, C["white"], (bx+12, by+12), 5)
|
||||
pygame.draw.rect(surf, C["dark"], (bx+10, by+10, 2, 2))
|
||||
pygame.draw.rect(surf, C["dark"], (bx+13, by+10, 2, 2))
|
||||
|
||||
|
||||
# ── Данные игры ────────────────────────────────────────────────────────────
|
||||
ITEMS = {
|
||||
"Зелье": {"desc": "Восст. 30 ОЗ", "price": 50, "effect": ("hp", 30)},
|
||||
"Хай-зелье": {"desc": "Восст. 80 ОЗ", "price": 120, "effect": ("hp", 80)},
|
||||
"Эфир": {"desc": "Восст. 20 ОМ", "price": 60, "effect": ("mp", 20)},
|
||||
"Эликсир": {"desc": "Полное ОЗ", "price": 300, "effect": ("hp", 9999)},
|
||||
}
|
||||
|
||||
MONSTERS = [
|
||||
{"name": "Зел. слизень", "hp": 20, "mp": 0, "atk": 5, "def": 2, "xp": 10, "gold": 8, "color": C["green"]},
|
||||
{"name": "Син. слизень", "hp": 28, "mp": 0, "atk": 6, "def": 3, "xp": 14, "gold": 10, "color": C["blue"]},
|
||||
{"name": "Розовая фея", "hp": 18, "mp": 15, "atk": 8, "def": 1, "xp": 18, "gold": 14, "color": C["pink"]},
|
||||
{"name": "Лесной огонёк","hp": 35, "mp": 20, "atk": 10, "def": 4, "xp": 25, "gold": 20, "color": C["purple"]},
|
||||
]
|
||||
|
||||
MITSUHA_LINES = [
|
||||
"Привет! Какой чудесный день, правда?",
|
||||
"Говорят, в лесу живут\nмиленькие монстрики!",
|
||||
"Будь осторожен, ладно?\nНо и повеселись тоже!",
|
||||
"В лавке продают отличные зелья~",
|
||||
"Как я люблю нашу деревню!",
|
||||
"О! Ты сегодня выглядишь сильнее!",
|
||||
]
|
||||
|
||||
HOME_LINES = [
|
||||
"Уютный домик. Ты хорошо отдохнул!",
|
||||
"Сладкие сны ждут тебя~",
|
||||
"Дома и стены помогают!",
|
||||
]
|
||||
|
||||
# ── Игрок ──────────────────────────────────────────────────────────────────
|
||||
class Player:
|
||||
def __init__(self):
|
||||
self.name = "Герой"
|
||||
self.x, self.y = 5, 6
|
||||
self.hp, self.max_hp = 80, 80
|
||||
self.mp, self.max_mp = 30, 30
|
||||
self.atk = 15
|
||||
self.dfn = 5
|
||||
self.xp, self.xp_next = 0, 50
|
||||
self.level = 1
|
||||
self.gold = 100
|
||||
self.inventory = {"Зелье": 2}
|
||||
self.frame = 0
|
||||
self.facing = 1
|
||||
self.px = self.x * TILE
|
||||
self.py = self.y * TILE
|
||||
|
||||
def gain_xp(self, amount):
|
||||
msgs = []
|
||||
self.xp += amount
|
||||
while self.xp >= self.xp_next:
|
||||
self.xp -= self.xp_next
|
||||
self.level += 1
|
||||
self.xp_next = int(self.xp_next * 1.4)
|
||||
self.max_hp += 15
|
||||
self.hp = self.max_hp
|
||||
self.max_mp += 5
|
||||
self.mp = self.max_mp
|
||||
self.atk += 3
|
||||
self.dfn += 1
|
||||
msgs.append(f"Новый уровень! Ур.{self.level}!")
|
||||
return msgs
|
||||
|
||||
def add_item(self, item):
|
||||
self.inventory[item] = self.inventory.get(item, 0) + 1
|
||||
|
||||
def use_item(self, item):
|
||||
if self.inventory.get(item, 0) <= 0:
|
||||
return False, "Предметов нет!"
|
||||
eff = ITEMS[item]["effect"]
|
||||
self.inventory[item] -= 1
|
||||
if self.inventory[item] == 0:
|
||||
del self.inventory[item]
|
||||
if eff[0] == "hp":
|
||||
healed = min(eff[1], self.max_hp - self.hp)
|
||||
self.hp = min(self.max_hp, self.hp + eff[1])
|
||||
return True, f"Восст. {healed} ОЗ!"
|
||||
elif eff[0] == "mp":
|
||||
self.mp = min(self.max_mp, self.mp + eff[1])
|
||||
return True, f"Восст. {eff[1]} ОМ!"
|
||||
return False, "Ничего не произошло."
|
||||
|
||||
|
||||
# ── Карты ──────────────────────────────────────────────────────────────────
|
||||
TOWN_MAP = [
|
||||
"WWWWWWWWWWWWWWWWWWWW",
|
||||
"W..................W",
|
||||
"W..H.......S.....tW",
|
||||
"W..................W",
|
||||
"W...........ttt...W",
|
||||
"W....t............W",
|
||||
"W..................W",
|
||||
"W....P.....M......W",
|
||||
"W..................W",
|
||||
"W..........t......W",
|
||||
"W...##.....t......W",
|
||||
"W...##............W",
|
||||
"W.................W",
|
||||
"WWWWWWWWWFWWWWWWWWW",
|
||||
]
|
||||
FOREST_MAP = [
|
||||
"TTTTTTTTTTTTTTTTTTTT",
|
||||
"T.t....t....t.....T",
|
||||
"T....t.....t......T",
|
||||
"T.t........t....t.T",
|
||||
"T.....t...........T",
|
||||
"T....t...t........T",
|
||||
"T.........t.......T",
|
||||
"T...t.............T",
|
||||
"T....t....t.......T",
|
||||
"T.................T",
|
||||
"T.....t...........T",
|
||||
"T.....t...........T",
|
||||
"T.................T",
|
||||
"TTTTTTTTTTTTTTTTTTTT",
|
||||
]
|
||||
|
||||
|
||||
# ── Камера ─────────────────────────────────────────────────────────────────
|
||||
class Camera:
|
||||
def __init__(self):
|
||||
self.ox = 0
|
||||
self.oy = 0
|
||||
|
||||
def update(self, px, py, map_w, map_h):
|
||||
self.ox = px - W // 2 + TILE // 2
|
||||
self.oy = py - H // 2 + TILE // 2
|
||||
self.ox = max(0, min(self.ox, map_w * TILE - W))
|
||||
self.oy = max(0, min(self.oy, map_h * TILE - H))
|
||||
|
||||
def world_to_screen(self, wx, wy):
|
||||
return wx - self.ox, wy - self.oy
|
||||
|
||||
|
||||
# ── Система боя ────────────────────────────────────────────────────────────
|
||||
class Battle:
|
||||
def __init__(self, player, monster_data):
|
||||
self.player = player
|
||||
self.mon = dict(monster_data)
|
||||
self.mon_hp = self.mon["hp"]
|
||||
self.mon_max_hp = self.mon["hp"]
|
||||
self.state = "player_turn"
|
||||
self.log = [f"Появился {self.mon['name']}!"]
|
||||
self.frame = 0
|
||||
self.selected = 0
|
||||
self.menu = "main"
|
||||
self.item_list = []
|
||||
self.shake_player = 0
|
||||
self.shake_enemy = 0
|
||||
self.result_msgs = []
|
||||
|
||||
def add_log(self, msg):
|
||||
self.log.append(msg)
|
||||
if len(self.log) > 4:
|
||||
self.log.pop(0)
|
||||
|
||||
def player_attack(self):
|
||||
dmg = max(1, self.player.atk - self.mon["def"] + random.randint(-3, 3))
|
||||
self.mon_hp -= dmg
|
||||
self.add_log(f"Ты нанёс {dmg} урона!")
|
||||
self.shake_enemy = 10
|
||||
if self.mon_hp <= 0:
|
||||
self.mon_hp = 0
|
||||
self.end_battle(True)
|
||||
else:
|
||||
self.state = "enemy_turn"
|
||||
|
||||
def player_magic(self):
|
||||
if self.player.mp < 8:
|
||||
self.add_log("Мало маны!")
|
||||
return
|
||||
self.player.mp -= 8
|
||||
dmg = max(1, self.player.atk * 2 - self.mon["def"] + random.randint(-2, 5))
|
||||
self.mon_hp -= dmg
|
||||
self.add_log(f"Магия! {dmg} урона! (-8 ОМ)")
|
||||
self.shake_enemy = 12
|
||||
if self.mon_hp <= 0:
|
||||
self.mon_hp = 0
|
||||
self.end_battle(True)
|
||||
else:
|
||||
self.state = "enemy_turn"
|
||||
|
||||
def player_defend(self):
|
||||
self.add_log("Ты принял защитную стойку!")
|
||||
self.player.dfn += 5
|
||||
self._defending = True
|
||||
self.state = "enemy_turn"
|
||||
|
||||
def enemy_turn(self):
|
||||
if self.state != "enemy_turn":
|
||||
return
|
||||
dmg = max(1, self.mon["atk"] - self.player.dfn + random.randint(-2, 3))
|
||||
self.player.hp -= dmg
|
||||
self.add_log(f"{self.mon['name']} нанёс {dmg} урона!")
|
||||
self.shake_player = 10
|
||||
if hasattr(self, "_defending"):
|
||||
self.player.dfn -= 5
|
||||
del self._defending
|
||||
if self.player.hp <= 0:
|
||||
self.player.hp = 0
|
||||
self.end_battle(False)
|
||||
else:
|
||||
self.state = "player_turn"
|
||||
|
||||
def end_battle(self, won):
|
||||
if won:
|
||||
xp = self.mon["xp"]
|
||||
gold = self.mon["gold"]
|
||||
self.player.gold += gold
|
||||
lvl_msgs = self.player.gain_xp(xp)
|
||||
self.add_log(f"Победа! +{xp} ОП, +{gold} зол.")
|
||||
for m in lvl_msgs:
|
||||
self.add_log(m)
|
||||
self.result_msgs = ["Победа!", f"+{xp} ОП +{gold} золота"] + lvl_msgs
|
||||
self.state = "win"
|
||||
else:
|
||||
self.player.hp = self.player.max_hp // 4
|
||||
self.add_log("Ты потерпел поражение...")
|
||||
self.result_msgs = ["Ты упал в обморок!", "Очнулся дома."]
|
||||
self.state = "lose"
|
||||
|
||||
def draw(self, surf):
|
||||
self.frame += 1
|
||||
for yy in range(H):
|
||||
ratio = yy / H
|
||||
r = int(20 + ratio * 20)
|
||||
g = int(10 + ratio * 20)
|
||||
b = int(40 + ratio * 30)
|
||||
pygame.draw.line(surf, (r, g, b), (0, yy), (W, yy))
|
||||
|
||||
random.seed(42)
|
||||
for _ in range(40):
|
||||
sx = random.randint(0, W)
|
||||
sy = random.randint(0, H // 2)
|
||||
br = 150 + int(50 * math.sin(self.frame * 0.05 + sx))
|
||||
pygame.draw.rect(surf, (br, br, br), (sx, sy, 2, 2))
|
||||
random.seed()
|
||||
|
||||
pygame.draw.rect(surf, (60, 160, 60), (0, H-80, W, 80))
|
||||
pygame.draw.rect(surf, (40, 120, 40), (0, H-82, W, 4))
|
||||
|
||||
ex = W * 3 // 4 - 24
|
||||
ey = H // 2 - 40
|
||||
esx = int(math.sin(self.shake_enemy * 0.8) * 5) if self.shake_enemy > 0 else 0
|
||||
self.shake_enemy = max(0, self.shake_enemy - 1)
|
||||
|
||||
mon_name = self.mon["name"]
|
||||
if "слизень" in mon_name:
|
||||
c = self.mon["color"]
|
||||
for _ in range(3):
|
||||
bc = int(abs(math.sin(self.frame * 0.1 + _)) * 3)
|
||||
pygame.draw.ellipse(surf, c, (ex+esx+10, ey+bc+20, 60, 48))
|
||||
pygame.draw.ellipse(surf, tuple(min(255,v+60) for v in c), (ex+esx+16, ey+24, 46, 30))
|
||||
pygame.draw.rect(surf, C["white"], (ex+esx+22, ey+32, 10, 10))
|
||||
pygame.draw.rect(surf, C["white"], (ex+esx+42, ey+32, 10, 10))
|
||||
pygame.draw.rect(surf, C["dark"], (ex+esx+24, ey+34, 6, 6))
|
||||
pygame.draw.rect(surf, C["dark"], (ex+esx+44, ey+34, 6, 6))
|
||||
elif "фея" in mon_name:
|
||||
bob = int(math.sin(self.frame * 0.2) * 6)
|
||||
pygame.draw.ellipse(surf, (200, 230, 255), (ex+esx-10, ey+bob+30, 30, 20))
|
||||
pygame.draw.ellipse(surf, (200, 230, 255), (ex+esx+60, ey+bob+30, 30, 20))
|
||||
pygame.draw.circle(surf, C["yellow"], (ex+esx+40, ey+bob+40), 22)
|
||||
pygame.draw.circle(surf, C["white"], (ex+esx+40, ey+bob+40), 16)
|
||||
pygame.draw.rect(surf, C["dark"], (ex+esx+32, ey+bob+34, 6, 6))
|
||||
pygame.draw.rect(surf, C["dark"], (ex+esx+44, ey+bob+34, 6, 6))
|
||||
elif "огонёк" in mon_name:
|
||||
for ring in range(3):
|
||||
r2 = 25 + ring * 10 + int(math.sin(self.frame * 0.1 + ring) * 4)
|
||||
pygame.draw.circle(surf, C["purple"], (ex+esx+40, ey+40), r2, 2)
|
||||
pygame.draw.circle(surf, C["purple"], (ex+esx+40, ey+40), 18)
|
||||
pygame.draw.circle(surf, C["white"], (ex+esx+40, ey+40), 10)
|
||||
|
||||
draw_bar(surf, ex+esx+5, ey-12, 70, 8, self.mon_hp, self.mon_max_hp, C["hp_red"])
|
||||
txt(surf, mon_name, ex+esx-20, ey-28, C["white"], font_sm)
|
||||
|
||||
psx = W // 4 - 12
|
||||
psy = H // 2
|
||||
pshake = int(math.sin(self.shake_player * 0.8) * 5) if self.shake_player > 0 else 0
|
||||
self.shake_player = max(0, self.shake_player - 1)
|
||||
draw_player(surf, psx + pshake, psy, self.frame, 3)
|
||||
|
||||
# Панель характеристик игрока
|
||||
panel(surf, 10, H-130, 195, 120)
|
||||
txt(surf, f"Ур.{self.player.level} {self.player.name}", 18, H-126, C["gold"])
|
||||
draw_bar(surf, 18, H-108, 172, 10, self.player.hp, self.player.max_hp, C["hp_red"])
|
||||
txt(surf, f"ОЗ {self.player.hp}/{self.player.max_hp}", 18, H-96, C["white"], font_sm)
|
||||
draw_bar(surf, 18, H-82, 172, 10, self.player.mp, self.player.max_mp, C["mp_blue"])
|
||||
txt(surf, f"ОМ {self.player.mp}/{self.player.max_mp}", 18, H-70, C["white"], font_sm)
|
||||
draw_bar(surf, 18, H-56, 172, 8, self.player.xp, self.player.xp_next, C["xp_green"])
|
||||
txt(surf, f"ОП {self.player.xp}/{self.player.xp_next}", 18, H-44, C["white"], font_sm)
|
||||
|
||||
# Журнал боя
|
||||
panel(surf, W//2-160, H-130, 320, 70)
|
||||
for i, line in enumerate(self.log[-3:]):
|
||||
txt(surf, line, W//2-152, H-126 + i*22, C["white"], font_sm)
|
||||
|
||||
# Меню / результат
|
||||
if self.state in ("win", "lose"):
|
||||
panel(surf, W//2-130, H//2-60, 260, 130)
|
||||
color = C["gold"] if self.state == "win" else C["red"]
|
||||
txt(surf, self.result_msgs[0], W//2-90, H//2-50, color)
|
||||
for i, m in enumerate(self.result_msgs[1:]):
|
||||
txt(surf, m, W//2-110, H//2-20 + i*22, C["white"], font_sm)
|
||||
txt(surf, "Нажми E чтобы продолжить", W//2-110, H//2+62, C["gray"], font_sm)
|
||||
|
||||
elif self.state == "player_turn":
|
||||
if self.menu == "main":
|
||||
panel(surf, W-215, H-130, 205, 120)
|
||||
options = ["Атаковать", "Магия (8ОМ)", "Защититься", "Предметы"]
|
||||
for i, opt in enumerate(options):
|
||||
color = C["yellow"] if i == self.selected else C["white"]
|
||||
prefix = "> " if i == self.selected else " "
|
||||
txt(surf, prefix + opt, W-210, H-122 + i*26, color)
|
||||
elif self.menu == "items":
|
||||
panel(surf, W-230, H-150, 220, 140)
|
||||
txt(surf, "Предметы:", W-222, H-146, C["gold"])
|
||||
if not self.item_list:
|
||||
txt(surf, "Нет предметов!", W-210, H-120, C["gray"])
|
||||
else:
|
||||
for i, (name, qty) in enumerate(self.item_list):
|
||||
color = C["yellow"] if i == self.selected else C["white"]
|
||||
prefix = "> " if i == self.selected else " "
|
||||
txt(surf, f"{prefix}{name} x{qty}", W-222, H-120 + i*24, color, font_sm)
|
||||
txt(surf, "ESC=Назад", W-222, H-18, C["gray"], font_sm)
|
||||
|
||||
def handle_input(self, event):
|
||||
if event.type != pygame.KEYDOWN:
|
||||
return None
|
||||
|
||||
if self.state in ("win", "lose"):
|
||||
if event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
|
||||
return self.state
|
||||
return None
|
||||
|
||||
if self.state != "player_turn":
|
||||
return None
|
||||
|
||||
if self.menu == "main":
|
||||
if event.key == pygame.K_UP:
|
||||
self.selected = (self.selected - 1) % 4
|
||||
elif event.key == pygame.K_DOWN:
|
||||
self.selected = (self.selected + 1) % 4
|
||||
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
|
||||
if self.selected == 0:
|
||||
self.player_attack()
|
||||
elif self.selected == 1:
|
||||
self.player_magic()
|
||||
elif self.selected == 2:
|
||||
self.player_defend()
|
||||
pygame.time.wait(400)
|
||||
self.enemy_turn()
|
||||
elif self.selected == 3:
|
||||
self.item_list = [(n, q) for n, q in self.player.inventory.items() if n in ITEMS]
|
||||
self.selected = 0
|
||||
self.menu = "items"
|
||||
elif self.menu == "items":
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
self.menu = "main"
|
||||
self.selected = 0
|
||||
elif event.key == pygame.K_UP:
|
||||
self.selected = max(0, self.selected - 1)
|
||||
elif event.key == pygame.K_DOWN:
|
||||
self.selected = min(len(self.item_list)-1, self.selected + 1)
|
||||
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
|
||||
if self.item_list:
|
||||
name, _ = self.item_list[self.selected]
|
||||
ok, msg = self.player.use_item(name)
|
||||
self.add_log(msg)
|
||||
self.item_list = [(n, q) for n, q in self.player.inventory.items() if n in ITEMS]
|
||||
if ok:
|
||||
self.menu = "main"
|
||||
self.selected = 0
|
||||
self.state = "enemy_turn"
|
||||
|
||||
if self.state == "enemy_turn":
|
||||
pygame.time.wait(300)
|
||||
self.enemy_turn()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ── Лавка ──────────────────────────────────────────────────────────────────
|
||||
class Shop:
|
||||
def __init__(self, player):
|
||||
self.player = player
|
||||
self.items = list(ITEMS.keys())
|
||||
self.selected = 0
|
||||
|
||||
def draw(self, surf):
|
||||
panel(surf, W//2-190, 55, 380, 370)
|
||||
txt(surf, "= ЛАВКА ПРЕДМЕТОВ =", W//2-130, 65, C["gold"])
|
||||
txt(surf, f"Золото: {self.player.gold}", W//2+80, 65, C["yellow"], font_sm)
|
||||
pygame.draw.line(surf, C["border"], (W//2-185, 90), (W//2+185, 90), 1)
|
||||
|
||||
for i, name in enumerate(self.items):
|
||||
info = ITEMS[name]
|
||||
y = 100 + i * 58
|
||||
color = C["yellow"] if i == self.selected else C["white"]
|
||||
prefix = "> " if i == self.selected else " "
|
||||
txt(surf, prefix + name, W//2-180, y, color)
|
||||
txt(surf, info["desc"], W//2-165, y+22, C["gray"], font_sm)
|
||||
txt(surf, f"{info['price']} зол.", W//2+90, y, C["gold"], font_sm)
|
||||
|
||||
txt(surf, "E=Купить ESC=Выйти", W//2-100, 382, C["gray"], font_sm)
|
||||
|
||||
def handle_input(self, event):
|
||||
if event.type != pygame.KEYDOWN:
|
||||
return False
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
return True
|
||||
if event.key == pygame.K_UP:
|
||||
self.selected = (self.selected - 1) % len(self.items)
|
||||
elif event.key == pygame.K_DOWN:
|
||||
self.selected = (self.selected + 1) % len(self.items)
|
||||
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
|
||||
name = self.items[self.selected]
|
||||
price = ITEMS[name]["price"]
|
||||
if self.player.gold >= price:
|
||||
self.player.gold -= price
|
||||
self.player.add_item(name)
|
||||
return False
|
||||
|
||||
|
||||
# ── Диалог ─────────────────────────────────────────────────────────────────
|
||||
class Dialogue:
|
||||
def __init__(self, lines):
|
||||
self.lines = lines if isinstance(lines, list) else [lines]
|
||||
self.idx = 0
|
||||
|
||||
@property
|
||||
def done(self):
|
||||
return self.idx >= len(self.lines)
|
||||
|
||||
def advance(self):
|
||||
self.idx += 1
|
||||
|
||||
def draw(self, surf):
|
||||
if self.done:
|
||||
return
|
||||
panel(surf, 30, H-110, W-60, 100)
|
||||
line = self.lines[self.idx]
|
||||
for i, l in enumerate(line.split("\n")):
|
||||
txt(surf, l, 46, H-100 + i*22, C["white"])
|
||||
txt(surf, "V E", W-70, H-24, C["gray"], font_sm)
|
||||
|
||||
|
||||
# ── Отрисовка карты ────────────────────────────────────────────────────────
|
||||
def tile_walkable(tile):
|
||||
return tile not in ("W", "t", "#", "T")
|
||||
|
||||
def render_map(surf, map_data, cam, frame):
|
||||
rows = len(map_data)
|
||||
for row in range(rows):
|
||||
cols = len(map_data[row])
|
||||
for col in range(cols):
|
||||
tile = map_data[row][col]
|
||||
wx = col * TILE
|
||||
wy = row * TILE
|
||||
sx, sy = cam.world_to_screen(wx, wy)
|
||||
if sx > W+TILE or sy > H+TILE or sx < -TILE or sy < -TILE:
|
||||
continue
|
||||
|
||||
grass_c = C["dark_grass"] if (row + col) % 2 == 0 else C["grass"]
|
||||
pygame.draw.rect(surf, grass_c, (sx, sy, TILE, TILE))
|
||||
|
||||
if tile == "W":
|
||||
pygame.draw.rect(surf, (100, 80, 60), (sx, sy, TILE, TILE))
|
||||
pygame.draw.rect(surf, (80, 60, 40), (sx, sy, TILE, TILE), 2)
|
||||
elif tile == "#":
|
||||
wo = int(math.sin(frame * 0.05 + col * 0.5) * 2)
|
||||
pygame.draw.rect(surf, C["water"], (sx, sy+wo, TILE, TILE))
|
||||
pygame.draw.rect(surf, (100, 200, 255), (sx+4, sy+4+wo, TILE-8, 4), 1)
|
||||
elif tile == "t":
|
||||
draw_tree(surf, sx, sy - 8)
|
||||
elif tile == "H":
|
||||
draw_house(surf, sx-8, sy-16)
|
||||
elif tile == "S":
|
||||
draw_store(surf, sx-8, sy-16)
|
||||
elif tile == "F":
|
||||
pygame.draw.rect(surf, C["dark_grass"], (sx, sy, TILE, TILE))
|
||||
pygame.draw.rect(surf, C["green"], (sx+4, sy+4, TILE-8, TILE-8), 2)
|
||||
elif tile == "T":
|
||||
pygame.draw.rect(surf, C["path"], (sx, sy, TILE, TILE))
|
||||
|
||||
|
||||
# ── Основная игра ──────────────────────────────────────────────────────────
|
||||
class Game:
|
||||
TOWN = "town"
|
||||
FOREST = "forest"
|
||||
|
||||
def __init__(self):
|
||||
self.player = Player()
|
||||
self.cam = Camera()
|
||||
self.scene = self.TOWN
|
||||
self.current_map = TOWN_MAP
|
||||
|
||||
self.dialogue = None
|
||||
self.shop = None
|
||||
self.battle = None
|
||||
self.frame = 0
|
||||
|
||||
self.npc_frame = 0
|
||||
self.message = None
|
||||
|
||||
self.mitsuha_dialogue_idx = 0
|
||||
self.sparks = []
|
||||
|
||||
def show_message(self, text, duration=120):
|
||||
self.message = (text, duration)
|
||||
|
||||
def add_spark(self, x, y, color=None):
|
||||
c = color or random.choice([C["yellow"], C["white"], C["gold"], C["pink"]])
|
||||
self.sparks.append({
|
||||
"x": x, "y": y,
|
||||
"vx": random.uniform(-1.5, 1.5),
|
||||
"vy": random.uniform(-3, -0.5),
|
||||
"life": 40, "color": c
|
||||
})
|
||||
|
||||
def get_tile(self, tx, ty):
|
||||
m = self.current_map
|
||||
if ty < 0 or ty >= len(m) or tx < 0 or tx >= len(m[ty]):
|
||||
return "W"
|
||||
return m[ty][tx]
|
||||
|
||||
def try_move(self, dx, dy):
|
||||
nx = self.player.x + dx
|
||||
ny = self.player.y + dy
|
||||
if not tile_walkable(self.get_tile(nx, ny)):
|
||||
return
|
||||
|
||||
if self.scene == self.TOWN and self.get_tile(nx, ny) == "F":
|
||||
self.scene = self.FOREST
|
||||
self.current_map = FOREST_MAP
|
||||
self.player.x, self.player.y = 10, 12
|
||||
self.player.px = self.player.x * TILE
|
||||
self.player.py = self.player.y * TILE
|
||||
self.show_message("Ты вошёл в лес!")
|
||||
return
|
||||
if self.scene == self.FOREST and self.get_tile(nx, ny) == "T":
|
||||
self.scene = self.TOWN
|
||||
self.current_map = TOWN_MAP
|
||||
self.player.x, self.player.y = 9, 12
|
||||
self.player.px = self.player.x * TILE
|
||||
self.player.py = self.player.y * TILE
|
||||
self.show_message("Ты вернулся в деревню!")
|
||||
return
|
||||
|
||||
self.player.x = nx
|
||||
self.player.y = ny
|
||||
self.player.px = nx * TILE
|
||||
self.player.py = ny * TILE
|
||||
|
||||
if dx > 0: self.player.facing = 3
|
||||
elif dx < 0: self.player.facing = 2
|
||||
elif dy > 0: self.player.facing = 1
|
||||
else: self.player.facing = 0
|
||||
|
||||
if self.scene == self.FOREST:
|
||||
if random.random() < 0.2:
|
||||
mon = random.choice(MONSTERS)
|
||||
self.battle = Battle(self.player, mon)
|
||||
|
||||
def try_interact(self):
|
||||
fx, fy = self.player.x, self.player.y
|
||||
facing = self.player.facing
|
||||
if facing == 0: fy -= 1
|
||||
elif facing == 1: fy += 1
|
||||
elif facing == 2: fx -= 1
|
||||
elif facing == 3: fx += 1
|
||||
|
||||
tile = self.get_tile(fx, fy)
|
||||
|
||||
if self.scene == self.TOWN:
|
||||
for row_i, row in enumerate(TOWN_MAP):
|
||||
if "M" in row:
|
||||
mx, my = row.index("M"), row_i
|
||||
if abs(mx - self.player.x) + abs(my - self.player.y) <= 1:
|
||||
line = MITSUHA_LINES[self.mitsuha_dialogue_idx % len(MITSUHA_LINES)]
|
||||
self.mitsuha_dialogue_idx += 1
|
||||
self.dialogue = Dialogue(line)
|
||||
return
|
||||
|
||||
if tile == "H":
|
||||
self.player.hp = self.player.max_hp
|
||||
self.player.mp = self.player.max_mp
|
||||
self.dialogue = Dialogue(random.choice(HOME_LINES) + "\n(ОЗ и ОМ восстановлены!)")
|
||||
for _ in range(12):
|
||||
self.add_spark(self.player.px + random.randint(-20, 20),
|
||||
self.player.py + random.randint(-30, 10), C["gold"])
|
||||
elif tile == "S":
|
||||
self.shop = Shop(self.player)
|
||||
|
||||
def update(self):
|
||||
self.frame += 1
|
||||
self.npc_frame += 1
|
||||
self.player.frame += 1
|
||||
|
||||
for s in self.sparks[:]:
|
||||
s["x"] += s["vx"]
|
||||
s["y"] += s["vy"]
|
||||
s["vy"] += 0.1
|
||||
s["life"] -= 1
|
||||
if s["life"] <= 0:
|
||||
self.sparks.remove(s)
|
||||
|
||||
if self.message:
|
||||
text, timer = self.message
|
||||
self.message = (text, timer - 1) if timer > 0 else None
|
||||
|
||||
def draw_world(self, surf):
|
||||
surf.fill(C["sky"])
|
||||
render_map(surf, self.current_map, self.cam, self.frame)
|
||||
|
||||
if self.scene == self.TOWN:
|
||||
for row_i, row in enumerate(TOWN_MAP):
|
||||
if "M" in row:
|
||||
mx, my = row.index("M"), row_i
|
||||
sx, sy = self.cam.world_to_screen(mx * TILE, my * TILE)
|
||||
draw_npc_mitsuha(surf, sx, sy, self.npc_frame)
|
||||
panel(surf, sx-4, sy-22, 64, 16)
|
||||
txt(surf, "Мицуха", sx-2, sy-21, C["pink"], font_sm)
|
||||
|
||||
px, py = self.cam.world_to_screen(self.player.px, self.player.py)
|
||||
draw_player(surf, px, py, self.player.frame, self.player.facing)
|
||||
|
||||
for s in self.sparks:
|
||||
c = tuple(min(255, v) for v in s["color"])
|
||||
pygame.draw.rect(surf, c, (int(s["x"]) - self.cam.ox, int(s["y"]) - self.cam.oy, 3, 3))
|
||||
|
||||
def draw_hud(self, surf):
|
||||
panel(surf, 8, 8, 210, 68)
|
||||
txt(surf, f"Ур.{self.player.level} {self.player.name}", 14, 12, C["gold"])
|
||||
draw_bar(surf, 14, 32, 190, 8, self.player.hp, self.player.max_hp, C["hp_red"])
|
||||
txt(surf, f"ОЗ {self.player.hp}/{self.player.max_hp}", 14, 42, C["white"], font_sm)
|
||||
draw_bar(surf, 14, 54, 190, 8, self.player.mp, self.player.max_mp, C["mp_blue"])
|
||||
txt(surf, f"ОМ {self.player.mp}/{self.player.max_mp}", 14, 64, C["white"], font_sm)
|
||||
|
||||
panel(surf, W-165, 8, 157, 26)
|
||||
txt(surf, f"Золото: {self.player.gold}", W-159, 12, C["gold"], font_sm)
|
||||
|
||||
loc = "** Деревня **" if self.scene == self.TOWN else "~~ Лес ~~"
|
||||
lw = font_sm.size(loc)[0] + 16
|
||||
panel(surf, W//2 - lw//2, 8, lw, 26)
|
||||
txt(surf, loc, W//2 - lw//2 + 8, 12, C["white"], font_sm)
|
||||
|
||||
txt(surf, "Стрелки: движение E: действие", 10, H-18, C["gray"], font_sm)
|
||||
|
||||
if self.message:
|
||||
text, timer = self.message
|
||||
tw = font_md.size(text)[0] + 24
|
||||
panel(surf, W//2 - tw//2, H//2-20, tw, 40)
|
||||
fc = flash_color(C["white"], self.frame * 0.02)
|
||||
txt(surf, text, W//2 - tw//2 + 12, H//2-12, fc)
|
||||
|
||||
def run(self):
|
||||
running = True
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
|
||||
if self.dialogue:
|
||||
self.dialogue = None
|
||||
elif self.shop:
|
||||
self.shop = None
|
||||
elif self.battle:
|
||||
pass
|
||||
else:
|
||||
running = False
|
||||
|
||||
if self.battle:
|
||||
result = self.battle.handle_input(event)
|
||||
if result in ("win", "lose"):
|
||||
if result == "lose":
|
||||
self.scene = self.TOWN
|
||||
self.current_map = TOWN_MAP
|
||||
self.player.x, self.player.y = 5, 6
|
||||
self.player.px = self.player.x * TILE
|
||||
self.player.py = self.player.y * TILE
|
||||
self.battle = None
|
||||
continue
|
||||
|
||||
if self.shop:
|
||||
if self.shop.handle_input(event):
|
||||
self.shop = None
|
||||
continue
|
||||
|
||||
if self.dialogue:
|
||||
if event.type == pygame.KEYDOWN and event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
|
||||
self.dialogue.advance()
|
||||
if self.dialogue.done:
|
||||
self.dialogue = None
|
||||
continue
|
||||
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_UP:
|
||||
self.try_move(0, -1)
|
||||
elif event.key == pygame.K_DOWN:
|
||||
self.try_move(0, 1)
|
||||
elif event.key == pygame.K_LEFT:
|
||||
self.try_move(-1, 0)
|
||||
elif event.key == pygame.K_RIGHT:
|
||||
self.try_move(1, 0)
|
||||
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
|
||||
self.try_interact()
|
||||
|
||||
self.update()
|
||||
self.cam.update(self.player.px, self.player.py,
|
||||
len(self.current_map[0]), len(self.current_map))
|
||||
|
||||
if self.battle:
|
||||
self.battle.draw(screen)
|
||||
else:
|
||||
self.draw_world(screen)
|
||||
self.draw_hud(screen)
|
||||
if self.shop:
|
||||
self.shop.draw(screen)
|
||||
if self.dialogue:
|
||||
self.dialogue.draw(screen)
|
||||
|
||||
pygame.display.flip()
|
||||
clock.tick(FPS)
|
||||
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
||||
|
||||
# ── Заставка ───────────────────────────────────────────────────────────────
|
||||
def title_screen():
|
||||
frame = 0
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit(); sys.exit()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
return
|
||||
|
||||
frame += 1
|
||||
screen.fill(C["bg"])
|
||||
|
||||
random.seed(7)
|
||||
for _ in range(60):
|
||||
sx = random.randint(0, W)
|
||||
sy = random.randint(0, H)
|
||||
br = 100 + int(80 * math.sin(frame * 0.03 + sx * 0.1))
|
||||
pygame.draw.rect(screen, (br, br, int(br*0.8)), (sx, sy, 2, 2))
|
||||
random.seed()
|
||||
|
||||
tc = flash_color(C["gold"], frame * 0.04, 30)
|
||||
title = font_lg.render(">> Пиксельная деревня <<", True, tc)
|
||||
screen.blit(title, (W//2 - title.get_width()//2, 90))
|
||||
|
||||
sub = font_md.render("~ Уютная маленькая JRPG ~", True, C["pink"])
|
||||
screen.blit(sub, (W//2 - sub.get_width()//2, 128))
|
||||
|
||||
draw_player(screen, W//2-80, 220, frame)
|
||||
draw_npc_mitsuha(screen, W//2-20, 220, frame)
|
||||
draw_slime(screen, W//2+60, 230, frame, C["green"])
|
||||
draw_fairy(screen, W//2+100, 220, frame)
|
||||
|
||||
features = [
|
||||
"[Д] Деревня: дом, лавка и пруд",
|
||||
"[?] Поговори с соседкой Мицухой",
|
||||
"[Л] Лес с милыми монстриками",
|
||||
"[!] Пошаговые бои",
|
||||
"[*] Повышай уровень героя!",
|
||||
]
|
||||
for i, f in enumerate(features):
|
||||
fc = C["white"] if i % 2 == 0 else C["gray"]
|
||||
fs = font_sm.render(f, True, fc)
|
||||
screen.blit(fs, (W//2 - fs.get_width()//2, 300 + i*22))
|
||||
|
||||
if int(frame / 30) % 2 == 0:
|
||||
ps = font_md.render("Нажми любую клавишу!", True, C["yellow"])
|
||||
screen.blit(ps, (W//2 - ps.get_width()//2, 420))
|
||||
|
||||
pygame.display.flip()
|
||||
clock.tick(FPS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
title_screen()
|
||||
game = Game()
|
||||
game.run()
|
||||
1017
jrpg/jrpg_game_en.py
Normal file
1017
jrpg/jrpg_game_en.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user