Загрузка данных
import pygame as pg
"""
Инициализация Pygame и создание игрового окна.
Устанавливается размер экрана 1280x640 пикселей для горизонтального формата.
"""
pg.init()
clock = pg.time.Clock()
screen = pg.display.set_mode(size=(1280, 640))
def get_img(name, mirrored_x, mirrored_y):
"""
Загружает изображение из папки Sprites, масштабирует до 32x32 пикселей
и при необходимости отражает по горизонтали или вертикали.
Параметры:
name: имя файла изображения
mirrored_x: отразить по горизонтали (True/False)
mirrored_y: отразить по вертикали (True/False)
Возвращает:
Surface с подготовленным изображением
"""
return pg.transform.flip(pg.transform.scale(pg.image.load(f'Sprites/{name}'), (32, 32)), flip_x=mirrored_x,
flip_y=mirrored_y)
"""
Загрузка и подготовка всех спрайтов персонажа.
Для каждого состояния (idle, walk, jump, fall) создаются версии
для правого (0) и левого (1) направлений движения.
"""
idle1_r = get_img('player_idle1.png', False, False)
idle2_r = get_img('player_idle2.png', False, False)
idle1_l = get_img('player_idle1.png', True, False)
idle2_l = get_img('player_idle2.png', True, False)
walk1_r = get_img('player_walk1.png', False, False)
walk2_r = get_img('player_walk2.png', False, False)
walk1_l = get_img('player_walk1.png', True, False)
walk2_l = get_img('player_walk2.png', True, False)
jump_r = get_img('player_jump.png', False, False)
fall_r = get_img('player_fall.png', False, False)
jump_l = get_img('player_jump.png', True, False)
fall_l = get_img('player_fall.png', True, False)
"""
Словарь для организации всех спрайтов персонажа.
Структура: состояние -> направление -> список кадров анимации.
Направление 0 = вправо, 1 = влево.
"""
player_dict = {
'idle': {0: [idle1_r, idle2_r],
1: [idle1_l, idle2_l]},
'walk': {0: [walk1_r, walk2_r],
1: [walk1_l, walk2_l]},
'jump': {0: [jump_r],
1: [jump_l]},
'fall': {0: [fall_r],
1: [fall_l]}
}
"""
Списки для хранения спрайтов различных типов стен и шипов.
Каждый индекс соответствует определенному повороту спрайта.
"""
wall = []
corner_wall = []
inner_corner_wall = []
spikes = []
"""
Загрузка спрайтов стен с различными поворотами (0, 90, 180, 270 градусов).
Также загружается спрайт пустой стены для заполнения внутренних областей.
"""
wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/wall.png'), 0), (32, 32)))
wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/wall.png'), 90), (32, 32)))
wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/wall.png'), 180), (32, 32)))
wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/wall.png'), 270), (32, 32)))
wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/empty_wall.png'), 0), (32, 32)))
"""
Загрузка спрайтов для наружных углов стен с четырьмя вариантами поворота.
"""
corner_wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/corner_wall.png'), 0), (32, 32)))
corner_wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/corner_wall.png'), 90), (32, 32)))
corner_wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/corner_wall.png'), 180), (32, 32)))
corner_wall.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/corner_wall.png'), 270), (32, 32)))
"""
Загрузка спрайтов для внутренних углов стен с четырьмя вариантами поворота.
"""
inner_corner_wall.append(
pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/inner_corner_wall.png'), 0), (32, 32)))
inner_corner_wall.append(
pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/inner_corner_wall.png'), 90), (32, 32)))
inner_corner_wall.append(
pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/inner_corner_wall.png'), 180), (32, 32)))
inner_corner_wall.append(
pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/inner_corner_wall.png'), 270), (32, 32)))
"""
Загрузка спрайтов шипов с поворотами 0 (вверх) и 180 (вниз) градусов.
Шипы имеют высоту 16 пикселей (половина стандартного размера клетки).
"""
spikes.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/spikes.png'), 0), (32, 16)))
spikes.append(pg.transform.scale(pg.transform.rotate(pg.image.load('Sprites/spikes.png'), 180), (32, 16)))
"""
Словарь соответствия символов карты и игровых объектов.
Используются псевдографические символы для визуального редактирования уровней.
"""
symb_dict = {
'•': None,
'S': None,
'F': None,
'▀': wall[0],
'▌': wall[1],
'▄': wall[2],
'▐': wall[3],
'█': wall[4],
'▜': corner_wall[0],
'▛': corner_wall[1],
'▙': corner_wall[2],
'▟': corner_wall[3],
'▝': inner_corner_wall[0],
'▘': inner_corner_wall[1],
'▖': inner_corner_wall[2],
'▗': inner_corner_wall[3],
'▲': spikes[0],
'▼': spikes[1],
}
"""
Загрузка карты уровня из текстового файла.
Каждая строка файла преобразуется в список символов.
"""
map_list = []
with open('level_map.txt', 'r', encoding='utf-8') as file:
for line in file:
line_list = list(line.strip())
map_list.append(line_list)
class Player:
"""
Класс игрового персонажа.
Управляет физикой движения, анимацией, столкновениями и состоянием.
"""
def __init__(self, x, y):
"""
Инициализация персонажа в заданной позиции.
Параметры:
x: начальная координата X
y: начальная координата Y
"""
self.states = {
'standing': False
}
self.direction = 0 # 0 = вправо, 1 = влево
self.vel_y = 0 # вертикальная скорость
self.vel_x = 0 # горизонтальная скорость
self.gravity = 0.5 # сила гравитации
self.hitbox_size = (24, 32) # размер хитбокса (меньше спрайта)
self.rect = pg.Rect((x, y, self.hitbox_size[0], self.hitbox_size[1]))
self.current_img = player_dict['idle'][0][0] # текущий спрайт
self.img_offset = 0 # смещение спрайта относительно хитбокса
self.jumps_left = 2 # оставшиеся прыжки (для двойного прыжка)
# Переменные для анимации
self.anim_timer = 0 # таймер анимации
self.anim_frame = 0 # текущий кадр анимации
self.anim_speed = 15 # скорость смены кадров (в кадрах игры)
def set_img(self, key, direction, index):
"""
Устанавливает текущий спрайт на основе состояния, направления и кадра.
Параметры:
key: состояние анимации ('idle', 'walk', 'jump', 'fall')
direction: направление (0 = вправо, 1 = влево)
index: номер кадра анимации
"""
self.current_img = player_dict[key][direction][index]
self.direction = direction
# Корректировка смещения для левого направления (спрайт шире хитбокса)
if direction == 1:
self.img_offset = -(self.current_img.get_width() - self.hitbox_size[0])
else:
self.img_offset = 0
def update_animation(self):
"""
Обновляет кадр анимации на основе таймера.
Вызывается каждый кадр игры для создания плавной анимации.
"""
self.anim_timer += 1
if self.anim_timer >= self.anim_speed:
self.anim_timer = 0
# Определение максимального количества кадров для текущего состояния
if self.vel_y < 0: # прыжок
max_frames = len(player_dict['jump'][self.direction])
elif self.vel_y > 0: # падение
max_frames = len(player_dict['fall'][self.direction])
elif self.vel_x != 0: # ходьба
max_frames = len(player_dict['walk'][self.direction])
else: # покой
max_frames = len(player_dict['idle'][self.direction])
# Переключение на следующий кадр с циклическим возвратом
self.anim_frame = (self.anim_frame + 1) % max_frames
# Обновление спрайта с учетом текущего состояния
if self.vel_y < 0:
self.set_img('jump', self.direction, self.anim_frame)
elif self.vel_y > 0:
self.set_img('fall', self.direction, self.anim_frame)
elif self.vel_x != 0:
self.set_img('walk', self.direction, self.anim_frame)
else:
self.set_img('idle', self.direction, self.anim_frame)
def draw(self):
"""
Отрисовывает персонажа на экране с учетом смещения спрайта.
"""
screen.blit(self.current_img, (self.rect.x + self.img_offset, self.rect.y))
def jump(self, power):
"""
Выполняет прыжок с заданной силой.
Поддерживает двойной прыжок: первый с земли, второй в воздухе.
Параметры:
power: сила прыжка (отрицательное значение для движения вверх)
"""
if self.states['standing']:
self.vel_y = power
self.states['standing'] = False
self.jumps_left = 1
elif self.jumps_left > 0:
self.vel_y = power
self.jumps_left -= 1
def move_y(self):
"""
Обновляет вертикальную позицию с учетом гравитации.
Гравитация постоянно увеличивает вертикальную скорость.
"""
if not self.states['standing']:
self.vel_y += self.gravity
self.rect.y += self.vel_y
def move_x(self):
"""
Обновляет горизонтальную позицию на основе текущей скорости.
"""
self.rect.centerx += self.vel_x
def check_spikes_collision(self, spikes):
"""
Проверяет столкновение с шипами и возвращает на старт при касании.
Параметры:
spikes: список объектов Spike для проверки столкновений
"""
for spike in spikes:
if self.rect.colliderect(spike.rect):
self.rect.x, self.rect.y = start_pos
def check_finish(self, finish_rect):
"""
Проверяет достижение финишной зоны.
Параметры:
finish_rect: прямоугольник зоны финиша
"""
if self.rect.colliderect(finish_rect):
self.rect.x, self.rect.y = start_pos
def collision_move_update(self, solids):
"""
Основной метод физики: обновляет позицию и разрешает столкновения.
Обрабатывает движение по вертикали и горизонтали отдельно
для предотвращения застревания в углах.
Сбрасывает счетчик прыжков при приземлении.
Параметры:
solids: список твердых объектов для проверки столкновений
"""
was_standing = self.states['standing']
self.states['standing'] = False
self.move_y()
self.move_x()
# Проверка столкновений с каждым твердым объектом
for obj in solids:
# Столкновение снизу (приземление на платформу)
if (self.vel_y > 0 and
self.rect.right > obj.rect.left and
self.rect.left < obj.rect.right and
obj.rect.top + 10 >= self.rect.bottom >= obj.rect.top and
self.rect.top <= obj.rect.bottom):
self.rect.bottom = obj.rect.top
self.vel_y = 0
self.states['standing'] = True
# Столкновение сверху (удар головой о потолок)
elif (self.vel_y < 0 and
self.rect.right > obj.rect.left and
self.rect.left < obj.rect.right and
obj.rect.bottom - 10 <= self.rect.top <= obj.rect.bottom and
self.rect.bottom >= obj.rect.top):
self.rect.top = obj.rect.bottom
self.vel_y = 0
# Столкновение справа (движение вправо)
if (self.vel_x > 0 and
self.rect.right > obj.rect.left and
self.rect.left < obj.rect.right and
self.rect.top < obj.rect.bottom and
self.rect.bottom > obj.rect.top):
self.rect.right = obj.rect.left
# Столкновение слева (движение влево)
elif (self.vel_x < 0 and
self.rect.left < obj.rect.right and
self.rect.right > obj.rect.left and
self.rect.top < obj.rect.bottom and
self.rect.bottom > obj.rect.top):
self.rect.left = obj.rect.right
# Сброс прыжков при приземлении
if self.states['standing'] and not was_standing:
self.jumps_left = 2
# Уменьшение прыжков при отрыве от земли
if not self.states['standing'] and was_standing:
self.jumps_left = 1
"""
Глобальный список всех шипов на уровне.
Используется для проверки столкновений с персонажем.
"""
all_spikes = []
class Spike:
"""
Класс опасных объектов (шипов).
При касании возвращает персонажа на начальную позицию.
"""
def __init__(self, x, y, img):
"""
Создание шипа в заданной позиции.
Параметры:
x: координата X
y: координата Y
img: спрайт шипа
"""
self.x = x
self.y = y
self.img = img
self.rect = pg.Rect(x, y, 32, 16) # хитбокс половинной высоты
all_spikes.append(self)
def draw(self):
"""
Отрисовка шипа на экране.
"""
screen.blit(self.img, self.rect)
"""
Глобальный список всех твердых объектов (стен и платформ).
Используется для проверки столкновений и отрисовки.
"""
all_solids = []
class Solid:
"""
Класс твердых статических объектов (стены, платформы).
Формируют игровое пространство уровня.
"""
def __init__(self, x, y, height, width, img, debug):
"""
Создание твердого объекта.
Параметры:
x: координата X
y: координата Y
height: высота
width: ширина
img: спрайт объекта
debug: символ для отладки (тип стены)
"""
self.x = x
self.y = y
self.height = height
self.width = width
self.rect = pg.Rect(x, y, width, height)
self.img = img
all_solids.append(self)
def draw(self):
"""
Отрисовка твердого объекта на экране.
"""
screen.blit(self.img, self.rect)
"""
Список прямоугольников финишных зон.
Может содержать несколько зон финиша на одном уровне.
"""
finish_rects = []
"""
Кортеж с начальной позицией персонажа.
Обновляется при обработке карты уровня.
"""
start_pos = (0, 0)
"""
Обработка карты уровня: создание игровых объектов на основе символов.
Каждый символ преобразуется в соответствующий игровой объект
с позиционированием по сетке 32x32 пикселя.
"""
current_row = -1
for line in map_list:
current_row += 1
current_column = -1
for symbol in line:
current_column += 1
picture = symb_dict[f'{symbol}']
if picture:
# Создание стен и углов
if symbol in ['█', '▀', '▌', '▄', '▐', '▜', '▛', '▙', '▟', '▝', '▘', '▖', '▗']:
new_cell = Solid(current_column * 32, current_row * 32, 32, 32, picture, symbol)
# Создание шипов направленных вверх
elif symbol in ['▲']:
new_cell = Spike(current_column * 32, current_row * 32 + 16, picture)
# Создание шипов направленных вниз
elif symbol in ['▼']:
new_cell = Spike(current_column * 32, current_row * 32, picture)
else:
# Пустое пространство
if symbol == '•':
pass
# Стартовая позиция персонажа
elif symbol == 'S':
start_pos = (current_column * 32, current_row * 32)
# Финишная зона
elif symbol == 'F':
finish_rect = pg.Rect(current_column * 32, current_row * 32, 32, 32)
finish_rects.append(finish_rect)
"""
Создание экземпляра игрока на стартовой позиции.
"""
player = Player(start_pos[0], start_pos[1])
"""
Словарь для отслеживания состояния клавиш движения.
"""
ad_pressed = {'a': False, 'd': False}
"""
Основной игровой цикл.
Обрабатывает события, обновляет состояние игры и отрисовывает кадры.
"""
running = True
while running:
# Ограничение до 60 кадров в секунду
clock.tick(60)
# Обработка событий (клавиатура, выход из игры)
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
if event.type == pg.KEYDOWN:
# Прыжок: пробел
if event.key == pg.K_SPACE:
if player.states['standing']:
player.jump(-8) # Первый прыжок сильнее
else:
player.jump(-7) # Двойной прыжок слабее
# Движение влево
if event.key == pg.K_a:
ad_pressed['a'] = True
# Движение вправо
if event.key == pg.K_d:
ad_pressed['d'] = True
# Перезапуск уровня
if event.key == pg.K_r:
player.rect.x, player.rect.y = start_pos
if event.type == pg.KEYUP:
if event.key == pg.K_a:
ad_pressed['a'] = False
if event.key == pg.K_d:
ad_pressed['d'] = False
# Обновление горизонтальной скорости на основе нажатых клавиш
if ad_pressed['a'] and not ad_pressed['d']:
player.vel_x = -4
player.direction = 1
elif ad_pressed['d'] and not ad_pressed['a']:
player.vel_x = 4
player.direction = 0
else:
player.vel_x = 0
# Обновление физики и проверка столкновений
player.collision_move_update(all_solids)
player.check_spikes_collision(all_spikes)
# Проверка падения за пределы уровня (смерть)
if player.rect.bottom > 1000:
player.rect.x, player.rect.y = start_pos
player.vel_y = 0
# Очистка экрана (темно-синий фон)
screen.fill((1, 1, 10))
# Обновление анимации персонажа (цикл анимации)
player.update_animation()
# Отрисовка всех игровых объектов
player.draw()
for solid in all_solids:
solid.draw()
for spike in all_spikes:
spike.draw()
for f in finish_rects:
pg.draw.rect(screen, (0, 255, 0), f) # Зеленый цвет финиша
player.check_finish(f)
# Обновление экрана
pg.display.flip()
# Завершение работы Pygame
pg.quit()