Загрузка данных
import pygame
import random
import math
import sys
pygame.init()
SCREEN_W, SCREEN_H = 1000, 650
WORLD_W, WORLD_H = 2400, 1800
FPS = 60
screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("The Witcher 2D - Velen Woods")
clock = pygame.time.Clock()
font = pygame.font.SysFont("consolas", 14, bold=True)
dmg_font = pygame.font.SysFont("consolas", 18, bold=True)
# Цвета
C_GROUND = (38, 50, 40)
C_GRASS = (48, 64, 48)
C_TREE_TRUNK = (60, 42, 30)
C_TREE_LEAVES = (28, 48, 28)
C_WITCHER = (220, 220, 230)
C_QUEN = (255, 215, 0)
C_DROWN = (45, 110, 115)
C_HP = (220, 50, 50)
C_STAMINA = (230, 180, 30)
C_FIRE = (255, 90, 20)
class Camera:
def __init__(self):
self.x = 0
self.y = 0
def follow(self, target_rect):
self.x = target_rect.centerx - SCREEN_W // 2
self.y = target_rect.centery - SCREEN_H // 2
self.x = max(0, min(self.x, WORLD_W - SCREEN_W))
self.y = max(0, min(self.y, WORLD_H - SCREEN_H))
def apply(self, rect):
return rect.move(-self.x, -self.y)
def apply_pos(self, pos):
return (pos[0] - self.x, pos[1] - self.y)
camera = Camera()
class DamageText:
def __init__(self, x, y, text, color):
self.x, self.y = x, y
self.text = str(text)
self.color = color
self.lifetime = 35
def update(self):
self.y -= 1
self.lifetime -= 1
def draw(self, surf):
render = dmg_font.render(self.text, True, self.color)
screen_pos = camera.apply_pos((self.x, self.y))
surf.blit(render, screen_pos)
class Witcher:
def __init__(self, x, y):
self.rect = pygame.Rect(x, y, 32, 44)
self.hp = 120
self.max_hp = 120
self.stamina = 100
self.max_stamina = 100
self.speed = 4.5
self.facing = pygame.Vector2(0, 1)
self.attacking = False
self.atk_timer = 0
self.attack_hitbox = pygame.Rect(0, 0, 0, 0)
self.quen_active = False
self.dodge_timer = 0
def update(self, obstacles):
if self.stamina < self.max_stamina:
self.stamina = min(self.max_stamina, self.stamina + 0.35)
# Передвижение
keys = pygame.key.get_pressed()
move = pygame.Vector2(0, 0)
if keys[pygame.K_w]: move.y -= 1
if keys[pygame.K_s]: move.y += 1
if keys[pygame.K_a]: move.x -= 1
if keys[pygame.K_d]: move.x += 1
if move.length() > 0:
move = move.normalize()
self.facing = move
spd = self.speed * 2.0 if self.dodge_timer > 0 else self.speed
# Движение с проверкой препятствий X
self.rect.x += int(move.x * spd)
for obs in obstacles:
if self.rect.colliderect(obs):
if move.x > 0: self.rect.right = obs.left
if move.x < 0: self.rect.left = obs.right
# Движение с проверкой препятствий Y
self.rect.y += int(move.y * spd)
for obs in obstacles:
if self.rect.colliderect(obs):
if move.y > 0: self.rect.bottom = obs.top
if move.y < 0: self.rect.top = obs.bottom
self.rect.clamp_ip(pygame.Rect(0, 0, WORLD_W, WORLD_H))
if self.dodge_timer > 0:
self.dodge_timer -= 1
# Атака мечом
if self.attacking:
self.atk_timer -= 1
reach = 36
ax = self.rect.centerx + int(self.facing.x * reach) - 16
ay = self.rect.centery + int(self.facing.y * reach) - 16
self.attack_hitbox = pygame.Rect(ax, ay, 32, 32)
if self.atk_timer <= 0:
self.attacking = False
self.attack_hitbox = pygame.Rect(0, 0, 0, 0)
def draw(self, surf):
r = camera.apply(self.rect)
# Тело Ведьмака
pygame.draw.rect(surf, (80, 70, 60), r, border_radius=4)
# Волосы (белые)
pygame.draw.circle(surf, C_WITCHER, (r.centerx, r.top + 6), 9)
# Щит Квен
if self.quen_active:
pygame.draw.circle(surf, C_QUEN, r.center, 30, 2)
# Взмах меча
if self.attacking:
pygame.draw.arc(surf, (220, 240, 255), camera.apply(self.attack_hitbox), 0, 3.14, 4)
class Drowner:
def __init__(self, x, y):
self.rect = pygame.Rect(x, y, 28, 38)
self.hp = 60
self.max_hp = 60
self.speed = 2.4
self.hit_cd = 0
def update(self, player, dmg_texts):
self.hit_cd = max(0, self.hit_cd - 1)
dist = pygame.Vector2(player.rect.center) - pygame.Vector2(self.rect.center)
length = dist.length()
# Агро-радиус
if 18 < length < 450:
direction = dist.normalize()
self.rect.x += int(direction.x * self.speed)
self.rect.y += int(direction.y * self.speed)
# Нанесение урона Ведьмаку
if self.rect.colliderect(player.rect) and self.hit_cd == 0 and player.dodge_timer == 0:
self.hit_cd = 40
dmg = random.randint(12, 18)
if player.quen_active:
player.quen_active = False
dmg_texts.append(DamageText(player.rect.centerx, player.rect.top, "ABSORBED", C_QUEN))
else:
player.hp = max(0, player.hp - dmg)
dmg_texts.append(DamageText(player.rect.centerx, player.rect.top, f"-{dmg}", C_HP))
def draw(self, surf):
r = camera.apply(self.rect)
pygame.draw.rect(surf, C_DROWN, r, border_radius=3)
# ХП бар врага
hp_pct = max(0, self.hp / self.max_hp)
pygame.draw.rect(surf, (30, 30, 30), (r.x, r.y - 8, self.rect.w, 4))
pygame.draw.rect(surf, C_HP, (r.x, r.y - 8, int(self.rect.w * hp_pct), 4))
class Particle:
def __init__(self, x, y, dx, dy, life, color):
self.x, self.y = x, y
self.dx, self.dy = dx, dy
self.life = life
self.color = color
def update(self):
self.x += self.dx
self.y += self.dy
self.life -= 1
def draw(self, surf):
pos = camera.apply_pos((self.x, self.y))
pygame.draw.circle(surf, self.color, pos, 3)
# Генерация мира
trees = [pygame.Rect(random.randint(50, WORLD_W - 100), random.randint(50, WORLD_H - 100), 50, 50) for _ in range(35)]
grass_patches = [(random.randint(0, WORLD_W), random.randint(0, WORLD_H), random.randint(2, 4)) for _ in range(250)]
drowners = [Drowner(random.randint(400, WORLD_W - 100), random.randint(400, WORLD_H - 100)) for _ in range(8)]
particles = []
dmg_texts = []
witcher = Witcher(SCREEN_W // 2, SCREEN_H // 2)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
# Атака мечом (SPACE)
if event.key == pygame.K_SPACE and not witcher.attacking and witcher.stamina >= 10:
witcher.attacking = True
witcher.atk_timer = 12
witcher.stamina -= 10
# Кувырок / Рывок (LSHIFT)
if event.key == pygame.K_LSHIFT and witcher.dodge_timer == 0 and witcher.stamina >= 25:
witcher.dodge_timer = 14
witcher.stamina -= 25
# Знак Игни (1 или Q)
if event.key in (pygame.K_1, pygame.K_q) and witcher.stamina >= 35:
witcher.stamina -= 35
for _ in range(25):
spread = (random.random() - 0.5) * 0.8
dir_vec = witcher.facing.rotate_rad(spread) * random.uniform(5, 9)
particles.append(Particle(witcher.rect.centerx, witcher.rect.centery, dir_vec.x, dir_vec.y, random.randint(15, 25), C_FIRE))
# Знак Квен (2 или E)
if event.key in (pygame.K_2, pygame.K_e) and witcher.stamina >= 40:
witcher.stamina -= 40
witcher.quen_active = True
# Обновление сущностей
witcher.update(trees)
camera.follow(witcher.rect)
# Проверка попаданий меча
if witcher.attacking and witcher.attack_hitbox.width > 0:
for d in drowners:
if witcher.attack_hitbox.colliderect(d.rect):
dmg = random.randint(22, 32)
d.hp -= dmg
dmg_texts.append(DamageText(d.rect.centerx, d.rect.top, f"-{dmg}", (255, 255, 255)))
witcher.attack_hitbox = pygame.Rect(0, 0, 0, 0) # Один удар - одно попадание
break
# Обновление частиц огня (Игни) и урон монстрам
for p in particles[:]:
p.update()
for d in drowners:
if math.hypot(p.x - d.rect.centerx, p.y - d.rect.centery) < 22:
d.hp -= 2
dmg_texts.append(DamageText(d.rect.centerx, d.rect.top, "BURN", C_FIRE))
p.life = 0
break
if p.life <= 0:
particles.remove(p)
# Обновление монстров
for d in drowners[:]:
d.update(witcher, dmg_texts)
if d.hp <= 0:
drowners.remove(d)
# Обновление плавающего текста
for dt in dmg_texts[:]:
dt.update()
if dt.lifetime <= 0:
dmg_texts.remove(dt)
# Отрисовка
screen.fill(C_GROUND)
# Трава
for gx, gy, sz in grass_patches:
pos = camera.apply_pos((gx, gy))
if -10 < pos[0] < SCREEN_W + 10 and -10 < pos[1] < SCREEN_H + 10:
pygame.draw.circle(screen, C_GRASS, pos, sz)
# Деревья
for t in trees:
tr = camera.apply(t)
pygame.draw.circle(screen, C_TREE_LEAVES, (tr.centerx, tr.centery), 38)
pygame.draw.rect(screen, C_TREE_TRUNK, (tr.centerx - 8, tr.centery - 8, 16, 22))
# Сущности
for d in drowners:
d.draw(screen)
witcher.draw(screen)
for p in particles:
p.draw(screen)
for dt in dmg_texts:
dt.draw(screen)
# HUD (Интерфейс)
# Здоровье
pygame.draw.rect(screen, (30, 30, 30), (20, 20, 200, 16), border_radius=3)
pygame.draw.rect(screen, C_HP, (20, 20, int(200 * (witcher.hp / witcher.max_hp)), 16), border_radius=3)
# Энергия
pygame.draw.rect(screen, (30, 30, 30), (20, 40, 200, 10), border_radius=3)
pygame.draw.rect(screen, C_STAMINA, (20, 40, int(200 * (witcher.stamina / witcher.max_stamina)), 10), border_radius=3)
hint = font.render("[SPACE] Меч | [SHIFT] Кувырок | [Q/1] Игни | [E/2] Квен", True, (200, 200, 200))
screen.blit(hint, (20, 60))
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()