Python – интерпретируемый язык, и по этой причине он не отличается высокой производительностью, которая необходима для сложных игр с гиперреалистичной графикой. Тем не менее есть несколько способов оптимизации, которые значительно ускоряют выполнение Python-кода, что позволяет писать на Питоне вполне приличные игры. Оптимизацию необязательно делать самостоятельно – в значительной степени эту задачу берут на себя специальные фреймворки и библиотеки. Фреймворков и библиотек для разработки игр на основе Python довольно много, вот самые популярные:
Pygame
PyKyra
Pyglet
Panda3D
Kivy
PyopenGL
Мы остановимся на самой популярной библиотеке, Pygame – она отлично подходит для начинающих разработчиков, и к тому же часто используется для быстрого прототипирования игр. На официальном сайте Pygame есть каталог игр , созданных с помощью библиотеки. Еще примеры игр на Pygame можно посмотреть здесь .
Pygame не входит в стандартную поставку Python, для установки библиотеки выполните:
pip install pygame
Все возможности библиотеки Pygame нереально рассмотреть в одной статье, поэтому здесь мы затронем только самые базовые концепции – рисование, движение объектов, покадровую анимацию, обработку событий, обновление счетчика, обнаружение столкновения.
🐍🎓 Библиотека собеса по Python
🐍🧩 Библиотека задач по Python
Окно и главный цикл приложения
Создание любого приложения на базе Pygame начинается с импорта и инициализации библиотеки. Затем нужно определить параметры окна, и по желанию – задать цвет (или изображение) фона:
import pygame
# инициализируем библиотеку Pygame
pygame.init()
# определяем размеры окна
window_size = (300, 300)
# задаем название окна
pygame.display.set_caption("Синий фон")
# создаем окно
screen = pygame.display.set_mode(window_size)
# задаем цвет фона
background_color = (0, 0, 255) # синий
# заполняем фон заданным цветом
screen.fill(background_color)
# обновляем экран для отображения изменений
pygame.display.flip()
# показываем окно, пока пользователь не нажмет кнопку "Закрыть"
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
Цикл while True
играет роль главного цикла программы – в нем происходит отслеживание событий
приложения и действий пользователя. Функция pygame.quit() завершает работу
приложения, и ее можно назвать противоположностью функции pygame.init() . Для
завершения Python -процесса
используется exit () , с
той же целью можно использовать sys .exit () , но ее нужно
импортировать в начале программы: import sys
.
В качестве фона можно использовать изображение :
import pygame
pygame.init()
window_size = (400, 400)
screen = pygame.display.set_mode(window_size)
pygame.display.set_caption("Peter the Piglet")
# загружаем изображение
background_image = pygame.image.load("background.png")
# подгоняем масштаб под размер окна
background_image = pygame.transform.scale(background_image, window_size)
# накладываем изображение на поверхность
screen.blit(background_image, (0, 0))
pygame.display.flip()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
Обработку событий (нажатий клавиш и кликов) в Pygame реализовать
очень просто – благодаря встроенным функциям. Приведенный ниже код изменяет
цвет фона после клика по кнопке. Обратите внимание, что в Pygame можно
задавать цвет несколькими способами:
import pygame
pygame.init()
pygame.display.set_caption('Измени цвет фона')
window_surface = pygame.display.set_mode((300, 300))
background = pygame.Surface((300, 300))
background.fill(pygame.Color('#000000'))
color_list = [
pygame.Color('#FF0000'), # красный
pygame.Color('#00FF00'), # зеленый
pygame.Color('#0000FF'), # синий
pygame.Color('#FFFF00'), # желтый
pygame.Color('#00FFFF'), # бирюзовый
pygame.Color('#FF00FF'), # пурпурный
pygame.Color('#FFFFFF') # белый
]
current_color_index = 0
button_font = pygame.font.SysFont('Verdana', 15) # используем шрифт Verdana
button_text_color = pygame.Color("black")
button_color = pygame.Color("gray")
button_rect = pygame.Rect(100, 115, 100, 50)
button_text = button_font.render('Нажми!', True, button_text_color)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if button_rect.collidepoint(event.pos):
current_color_index = (current_color_index + 1) % len(color_list)
background.fill(color_list[current_color_index])
window_surface.blit(background, (0, 0))
pygame.draw.rect(window_surface, button_color, button_rect)
button_rect_center = button_text.get_rect(center=button_rect.center)
window_surface.blit(button_text, button_rect_center)
pygame.display.update()
Как очевидно из приведенного выше примера, основной цикл Pygame приложения
состоит из трех повторяющихся действий:
Обработка событий (нажатий клавиш или кнопок).
Обновление состояния.
Отрисовка состояния на экране.
GUI для PyGame
Pygame позволяет легко и быстро интегрировать в проект многие нужные
вещи – шрифты, звук, обработку событий, – однако не имеет встроенных виджетов
для создания кнопок, лейблов, индикаторов выполнения и других подобных
элементов интерфейса. Эту проблему разработчик должен решать либо
самостоятельно (нарисовать прямоугольник, назначить ему функцию кнопки), либо с
помощью дополнительных GUI -библиотек.
Таких библиотек несколько, к самым популярным относятся:
Вот простой пример использования Pygame GUI – зеленые нули и
единицы падают вниз в стиле «Матрицы»:
import pygame
import pygame_gui
import random
window_size = (800, 600)
window = pygame.display.set_mode(window_size)
pygame.display.set_caption('Матрица Lite')
pygame.init()
gui_manager = pygame_gui.UIManager(window_size)
font = pygame.font.SysFont('Consolas', 20)
text_color = pygame.Color('green')
text_symbols = ['0', '1']
text_pos = [(random.randint(0, window_size[0]), 0) for i in range(50)]
text_speed = [(0, random.randint(1, 5)) for i in range(50)]
text_surface_list = []
button_size = (100, 50)
button_pos = (350, 250)
button_text = 'Матрица!'
button = pygame_gui.elements.UIButton(
relative_rect=pygame.Rect(button_pos, button_size),
text=button_text,
manager=gui_manager
)
while True:
time_delta = pygame.time.Clock().tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame_gui.UI_BUTTON_PRESSED:
text_surface_list = []
for i in range(50):
text_symbol = random.choice(text_symbols)
text_surface = font.render(text_symbol, True, text_color)
text_surface_list.append(text_surface)
gui_manager.process_events(event)
gui_manager.update(time_delta)
window.fill(pygame.Color('black'))
for i in range(50):
text_pos[i] = (text_pos[i][0], text_pos[i][1] + text_speed[i][1])
if text_pos[i][1] > window_size[1]:
text_pos[i] = (random.randint(0, window_size[0]), -20)
if len(text_surface_list) > i:
window.blit(text_surface_list[i], text_pos[i])
gui_manager.draw_ui(window)
pygame.display.update()
Рисование
В Pygame есть функции для простого рисования геометрических фигур –
прямоугольников, окружностей, эллипсов, линий, многоугольников. Вот пример:
import pygame
import math
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Геометрические фигуры")
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
yellow = (255, 255, 0)
pink = (255, 192, 203)
# рисуем прямоугольник
rect_x = 50
rect_y = 50
rect_width = 100
rect_height = 50
pygame.draw.rect(screen, red, (rect_x, rect_y, rect_width, rect_height))
# рисуем круг
circle_x = 200
circle_y = 75
circle_radius = 30
pygame.draw.circle(screen, green, (circle_x, circle_y), circle_radius)
# рисуем треугольник
triangle_x = 350
triangle_y = 50
triangle_width = 100
triangle_height = 100
triangle_points = [(triangle_x, triangle_y), (triangle_x + triangle_width, triangle_y),
(triangle_x + triangle_width / 2, triangle_y + triangle_height)]
pygame.draw.polygon(screen, blue, triangle_points)
# рисуем пятиугольник
pent_x = 500
pent_y = 100
radius = 40
sides = 5
pent_points = []
for i in range(sides):
angle_deg = 360 * i / sides
angle_rad = math.radians(angle_deg)
x = pent_x + radius * math.sin(angle_rad)
y = pent_y - radius * math.cos(angle_rad)
pent_points.append((x, y))
pygame.draw.polygon(screen, white, pent_points)
# рисуем эллипс
ellipse_x = 100
ellipse_y = 275
ellipse_width = 150
ellipse_height = 60
pygame.draw.ellipse(screen, red, (ellipse_x, ellipse_y, ellipse_width, ellipse_height))
# горизонтальная линия
horiz_line_y = 400
pygame.draw.line(screen, blue, (50, horiz_line_y), (590, horiz_line_y), 5)
# вертикальная линия
vert_line_x = 320
pygame.draw.line(screen, green, (vert_line_x, 50), (vert_line_x, 430), 5)
# рисуем желтую звезду
yellow_star_points = [(260 - 50, 250 - 70), (310 - 50, 250 - 70), (325 - 50, 200 - 70),
(340 - 50, 250 - 70), (390 - 50, 250 - 70), (350 - 50, 290 - 70),
(365 - 50, 340 - 70), (325 - 50, 305 - 70), (285 - 50, 340 - 70),
(300 - 50, 290 - 70)]
pygame.draw.polygon(screen, yellow, yellow_star_points)
# рисуем окружность с квадратом внутри
circle2_x = 490
circle2_y = 350
circle2_radius = 80
pygame.draw.circle(screen, white, (circle2_x, circle2_y), circle2_radius)
square_side = 60
square_x = circle2_x - square_side / 2
square_y = circle2_y - square_side / 2
pygame.draw.rect(screen, pink, (square_x, square_y, square_side, square_side))
pygame.display.update()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
Анимация и обработка событий
Выше, в примере с падающими символами в «Матрице», уже был
показан принцип простейшей имитации движения, который заключается в
последовательном изменении координат объекта и обновлении экрана с
установленной частотой кадра pygame.time.Clock().tick(60)
.
Усложним задачу – сделаем простую анимацию с падающими розовыми «звездами».
Приложение будет поддерживать обработку двух событий:
При клике мышью по экрану анимация останавливается.
При нажатии клавиши Enter – возобновляется.
Кроме того, добавим счетчик упавших звезд. Готовый код
выглядит так:
import pygame
import random
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Звезды падают вниз")
black = (0, 0, 0)
white = (255, 255, 255)
pink = (255, 192, 203)
font = pygame.font.SysFont("Verdana", 15)
star_list = []
for i in range(50):
x = random.randrange(screen_width)
y = random.randrange(-200, -50)
speed = random.randrange(1, 5)
star_list.append([x, y, speed])
score = 0
freeze = False # флаг для определения момента остановки
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN: # останавливаем падение звезд по клику
freeze = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN: # возобновляем движение вниз, если нажат Enter
freeze = False
if not freeze: # если флаг не активен,
# звезды падают вниз
for star in star_list:
star[1] += star[2]
if star[1] > screen_height:
star[0] = random.randrange(screen_width)
star[1] = random.randrange(-200, -50)
score += 1
# рисуем звезды, выводим результаты подсчета
screen.fill(black)
for star in star_list:
pygame.draw.circle(screen, pink, (star[0], star[1]), 3)
score_text = font.render("Упало звезд: " + str(score), True, white)
screen.blit(score_text, (10, 10))
pygame.display.update()
# устанавливаем частоту обновления экрана
pygame.time.Clock().tick(60)
Покадровая анимация
Если у вас есть в запасе картинки с раскадровкой движения ,
превратить их в анимацию не составит труда:
import pygame
pygame.init()
window_size = (640, 480)
screen = pygame.display.set_mode(window_size)
color = (216, 233, 243)
screen.fill(color)
pygame.display.flip()
pygame.display.set_caption("Покадровая анимация")
clock = pygame.time.Clock()
# загружаем кадры
frame_images = []
for i in range(1, 9):
frame_images.append(pygame.image.load(f"frame{i}.png"))
# параметры анимации
animation_length = len(frame_images)
animation_speed = 15 # кадры в секунду
current_frame_index = 0
animation_timer = 0
frame_position = [0, 0]
# вычисляем позицию для вывода кадров в зависимости от высоты окна
window_height = screen.get_height()
frame_height = frame_images[0].get_height()
frame_position[1] = int(window_height * 0.45) - int(frame_height / 2)
# запускаем основной цикл
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# обновление состояния
time_delta = clock.tick(60) / 1000.0
animation_timer += time_delta
if animation_timer >= 1.0 / animation_speed:
current_frame_index = (current_frame_index + 1) % animation_length
animation_timer -= 1.0 / animation_speed
frame_position[0] += 1 # сдвигаем кадр вправо
# выводим кадры, обновляем экран
current_frame = frame_images[current_frame_index]
screen.blit(current_frame, frame_position)
pygame.display.flip()
pygame.quit()
Столкновение объектов
В этом примере расстояние между объектами проверяется до тех
пор, пока объекты не столкнутся. В момент столкновение движение прекращается, а
цвет объектов – изменяется:
import pygame
import math
pygame.init()
screen_width = 400
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Драматическое столкновение")
# размеры и позиция окружности
circle_pos = [screen_width/2, 50]
circle_radius = 20
# размеры и позиция прямоугольника
rect_pos = [screen_width/2, screen_height-50]
rect_width = 100
rect_height = 50
# цвета окружности и прямоугольника
white = (255, 255, 255)
black = (0, 0, 0)
green = (0, 255, 0)
red = (255, 0, 0)
# скорость движения окружности
speed = 5
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# окружность движется вниз
circle_pos[1] += speed
# проверяем (используя формулу расстояния),
# столкнулась ли окружность с прямоугольником
circle_x = circle_pos[0]
circle_y = circle_pos[1]
rect_x = rect_pos[0]
rect_y = rect_pos[1]
distance_x = abs(circle_x - rect_x)
distance_y = abs(circle_y - rect_y)
if distance_x <= (rect_width/2 + circle_radius) and distance_y <= (rect_height/2 + circle_radius):
circle_color = red # изменяем цвет фигур
rect_color = green # в момент столкновения
else:
circle_color = green
rect_color = black
# рисуем окружность и прямоугольник на экране
screen.fill(white)
pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
pygame.draw.rect(screen, rect_color, (rect_pos[0]-rect_width/2, rect_pos[1]-rect_height/2, rect_width, rect_height))
pygame.display.update()
# останавливаем движение окружности, если она
# столкнулась с прямоугольником
if circle_pos[1] + circle_radius >= rect_pos[1] - rect_height/2:
speed = 0
# задаем частоту обновления экрана
pygame.time.Clock().tick(60)
Управление движением объекта
Для управления движением (в нашем случае – с помощью клавиш ← и →) используются pygame.K_RIGHT
(вправо) и pygame.K_LEFT
(влево). В
приведенном ниже примере передвижение падающих окружностей возможно только до
момента приземления на дно игрового поля, или на предыдущие фигуры. Для
упрощения вычисления факта столкновения фигур окружности вписываются в прямоугольники:
import pygame
import random
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
# цвета окружностей
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
black = (0, 0, 0)
yellow = (255, 255, 0)
# цвет, скорость, начальная позиция окружности
circle_radius = 30
circle_speed = 3
circle_color = random.choice([red, green, blue, yellow, white])
circle_pos = [screen_width//2, -circle_radius]
circle_landed = False
# список приземлившихся окружностей и их позиций
landed_circles = []
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# если окружность не приземлилась
if not circle_landed:
# меняем направление по нажатию клавиши
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
circle_pos[0] -= circle_speed
if keys[pygame.K_RIGHT]:
circle_pos[0] += circle_speed
# проверяем, столкнулась ли окружность с другой приземлившейся окружностью
for landed_circle in landed_circles:
landed_rect = pygame.Rect(landed_circle[0]-circle_radius, landed_circle[1]-circle_radius, circle_radius*2, circle_radius*2)
falling_rect = pygame.Rect(circle_pos[0]-circle_radius, circle_pos[1]-circle_radius, circle_radius*2, circle_radius*2)
if landed_rect.colliderect(falling_rect):
circle_landed = True
collision_x = circle_pos[0]
collision_y = landed_circle[1] - circle_radius*2
landed_circles.append((collision_x, collision_y, circle_color))
break
# если окружность не столкнулась с другой приземлившейся окружностью
if not circle_landed:
# окружность движется вниз
circle_pos[1] += circle_speed
# проверяем, достигла ли окружность дна
if circle_pos[1] + circle_radius > screen_height:
circle_pos[1] = screen_height - circle_radius
circle_landed = True
# добавляем окружность и ее позицию в список приземлившихся окружностей
landed_circles.append((circle_pos[0], circle_pos[1], circle_color))
if circle_landed:
# если окружность приземлилась, задаем параметры новой
circle_pos = [screen_width//2, -circle_radius]
circle_color = random.choice([red, green, blue, yellow, white])
circle_landed = False
# рисуем окружности
screen.fill(black)
for landed_circle in landed_circles:
pygame.draw.circle(screen, landed_circle[2], (landed_circle[0], landed_circle[1]), circle_radius)
pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
pygame.display.update()
# частота обновления экрана
pygame.time.Clock().tick(60)
Практика
Задание 1 – Лестница
Напишите Pygame
игру, в которой игрок (красная окружность) поднимается вверх по ступеням
лестницы. Необходимо подсчитывать сделанные шаги.
Пример:
Решение:
Код решения .
import pygame
pygame.init()
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
game_display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Вверх по лестнице')
# параметры ступеней
STEP_WIDTH = 20
STEP_HEIGHT = STEP_WIDTH
STEP_COLOR = (255, 255, 255)
# параметры игрока, размеры и цвет шрифта
PLAYER_RADIUS = 10
PLAYER_COLOR = (255, 0, 0)
FONT_SIZE = 20
FONT_COLOR = (255, 255, 255)
clock = pygame.time.Clock()
def game_loop():
game_exit = False
# стартовая позиция игрока
player_x = PLAYER_RADIUS
player_y = WINDOW_HEIGHT - PLAYER_RADIUS * 3
# начальные координаты ступеней
step_x = 0
step_y = WINDOW_HEIGHT - STEP_HEIGHT
# создаем счетчик шагов
step_count = 0
while not game_exit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
# удаляем игрока из предыдущей позиции
pygame.draw.circle(game_display, (0, 0, 0), (player_x, player_y), PLAYER_RADIUS)
# обновляем позицию и счет
player_x += 20
player_y -= 20
step_count += 1
# рисуем лестницу
while step_x < WINDOW_WIDTH and step_y >= 0:
pygame.draw.rect(game_display, STEP_COLOR, [step_x, step_y, STEP_WIDTH, STEP_HEIGHT])
step_x += STEP_WIDTH
step_y -= STEP_HEIGHT
# перемещаем игрока на ступень выше
pygame.draw.circle(game_display, PLAYER_COLOR, (player_x, player_y), PLAYER_RADIUS)
# подсчитываем сделанные шаги
pygame.draw.rect(game_display, (0, 0, 0), (10, 10, 100, FONT_SIZE))
font = pygame.font.SysFont('Arial', FONT_SIZE)
text = font.render(f'Шаг: {str(step_count)}', True, FONT_COLOR)
game_display.blit(text, (10, 10))
pygame.display.update()
clock.tick(60)
game_loop()
pygame.quit()
Задание 2 – Лабиринт
Напишите Pygame
игру, которая генерирует лабиринт из вертикальных стен со случайным количеством
дверей. Игрок (красная окружность) должен проходить через двери.
Пример:
Решение:
Код решения .
import pygame
import random
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Лабиринт')
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
blue = (0,0,255)
green = (0,255,0)
# параметры стен и дверей
line_width = 10
line_gap = 40
line_offset = 20
door_width = 20
door_gap = 40
max_openings_per_line = 5
# параметры и стартовая позиция игрока
player_radius = 10
player_speed = 5
player_x = screen_width - 12
player_y = screen_height - line_offset
# рисуем стены и двери
lines = []
for i in range(0, screen_width, line_gap):
rect = pygame.Rect(i, 0, line_width, screen_height)
num_openings = random.randint(1, max_openings_per_line)
if num_openings == 1:
# одна дверь посередине стены
door_pos = random.randint(line_offset + door_width, screen_height - line_offset - door_width)
lines.append(pygame.Rect(i, 0, line_width, door_pos - door_width))
lines.append(pygame.Rect(i, door_pos + door_width, line_width, screen_height - door_pos - door_width))
else:
# несколько дверей
opening_positions = [0] + sorted([random.randint(line_offset + door_width, screen_height - line_offset - door_width) for _ in range(num_openings-1)]) + [screen_height]
for j in range(num_openings):
lines.append(pygame.Rect(i, opening_positions[j], line_width, opening_positions[j+1]-opening_positions[j]-door_width))
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# передвижение игрока
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and player_x > player_radius:
player_x -= player_speed
elif keys[pygame.K_RIGHT] and player_x < screen_width - player_radius:
player_x += player_speed
elif keys[pygame.K_UP] and player_y > player_radius:
player_y -= player_speed
elif keys[pygame.K_DOWN] and player_y < screen_height - player_radius:
player_y += player_speed
# проверка столкновений игрока со стенами
player_rect = pygame.Rect(player_x - player_radius, player_y - player_radius, player_radius * 2, player_radius * 2)
for line in lines:
if line.colliderect(player_rect):
# в случае столкновения возвращаем игрока назад
if player_x > line.left and player_x < line.right:
if player_y < line.top:
player_y = line.top - player_radius
else:
player_y = line.bottom + player_radius
elif player_y > line.top and player_y < line.bottom:
if player_x < line.left:
player_x = line.left - player_radius
else:
player_x = line.right + player_radius
screen.fill(black)
for line in lines:
pygame.draw.rect(screen, green, line)
pygame.draw.circle(screen, red, (player_x, player_y), player_radius)
pygame.display.update()
clock.tick(60)
Задание 3 – Дождь
Используя Pygame ,
напишите симулятор дождя: падение каждой сотни капель приводит к подъему уровня
воды на 1 пиксель.
Пример:
Решение:
Код решения .
import pygame
import random
class RainSimulator:
def __init__(self):
pygame.init()
self.screen_width = 500
self.screen_height = 700
self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
pygame.display.set_caption("Дождь")
self.font = pygame.font.SysFont("Consolas", 20)
self.background_color = (0, 0, 0)
self.blue = (173, 216, 230)
# параметры дождевых капель
self.drops = []
self.drops_landed = 0
self.drops_per_pixel = 100
self.level_height = 0
self.clock = pygame.time.Clock()
# добавляем капли дождя
def add_drop(self):
self.drops.append([random.randint(0, self.screen_width), 0])
# рисуем дождь
def draw_drops(self):
for drop in self.drops:
pygame.draw.line(self.screen, self.blue, (drop[0], drop[1]), (drop[0], drop[1] + 5), 2)
# подсчитываем капли, поднимаем уровень воды
def update_drops(self):
for drop in self.drops:
drop[1] += 5
if drop[1] >= self.screen_height:
self.drops.remove(drop)
self.drops_landed += 1
if self.drops_landed % self.drops_per_pixel == 0:
self.level_height += 1
# выводим количество капель
def draw_score(self):
score_text = self.font.render(f"Капель дождя: {str(self.drops_landed)}", True, (255, 255, 255))
self.screen.blit(score_text, (10, 10))
pygame.draw.rect(self.screen, self.blue, (0, self.screen_height-self.level_height, self.screen_width, self.level_height))
def run_rain(self):
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
self.add_drop()
self.update_drops()
self.screen.fill(self.background_color)
self.draw_drops()
self.draw_score()
pygame.display.update()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__":
app = RainSimulator()
app.run_rain()
Задание 4 – Мерцающие звезды
Используя Pygame ,
напишите симулятор звездного неба – окружности, представляющие собой звезды,
сжимаются и расширяются, имитируя мерцание.
Пример:
Решение:
Код решения .
import pygame
import random
pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Мерцающие звезды")
# параметры звезд
MAX_STARS = 200
stars = []
for i in range(MAX_STARS):
star_radius = random.randint(1, 3)
star_color = (255, 255, 237)
star_position = (random.randint(0, WINDOW_WIDTH), random.randint(0, WINDOW_HEIGHT))
star_expand = True
star_expand_speed = random.uniform(0.1, 0.5)
stars.append((star_radius, star_color, star_position, star_expand, star_expand_speed))
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
for i in range(MAX_STARS):
star_radius, star_color, star_position, star_expand, star_expand_speed = stars[i]
# вычисление радиуса расширения
if star_expand:
star_radius += star_expand_speed
if star_radius >= 5:
star_expand = False
else:
star_radius -= star_expand_speed
if star_radius <= 1:
star_expand = True
# изменяем позиции звезд для создания эффекта мерцания
star_position = (
star_position[0] + random.randint(-1, 1),
star_position[1] + random.randint(-1, 1)
)
stars[i] = (star_radius, star_color, star_position, star_expand, star_expand_speed)
# рисуем звезды
screen.fill((0, 0, 0))
for star in stars:
star_radius, star_color, star_position, _, _ = star
pygame.draw.circle(screen, star_color, star_position, star_radius)
pygame.display.update()
clock.tick(60)
Задание 5 – Колобок
Используя Pygame ,
создайте анимацию, в которой лиса (состоящая из этих фреймов ) преследует
Колобка . Колобок вращается вокруг своей оси.
Пример:
Решение:
Код решения и необходимые изображения .
import pygame
pygame.init()
background = (24, 113, 147)
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 300
game_display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Колобок')
# загружаем изображение Колобка
kolobok = pygame.image.load('kolobok.png')
# стартовый угол вращения и скорость
kolobok_angle = 0
kolobok_rotation_speed = 2
# загружаем фреймы лисы
fox = []
for i in range(8):
fox.append(pygame.image.load(f'fox{i+1}.png'))
# частота обновления фреймов лисы
fox_frame = 0
fox_frame_rate = 8
fox_frame_timer = 0
# стартовые позиции и скорость движения лисы и Колобка
kolobok_x = 0
kolobok_y = WINDOW_HEIGHT // 2 + kolobok.get_height() // 4
fox_x = -fox[0].get_width()
fox_y = WINDOW_HEIGHT // 2 - fox[0].get_height() // 2
movement_speed = 3
clock = pygame.time.Clock()
# главный цикл
game_exit = False
while not game_exit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
# вращаем изображение Колобка вокруг своей оси
kolobok_angle += kolobok_rotation_speed
if kolobok_angle >= 360:
kolobok_angle = 0
rotated_kolobok = pygame.transform.rotate(kolobok, kolobok_angle)
# движение Колобка и лисы слева направо
kolobok_x += movement_speed
if kolobok_x > WINDOW_WIDTH:
kolobok_x = 0 - fox[0].get_width()
fox_x += movement_speed
if fox_x > WINDOW_WIDTH:
fox_x = 0 - fox[0].get_width()
# приводим скорость анимации лисы в соответствие с частотой обновления экрана
fox_frame_timer += clock.tick(60)
if fox_frame_timer >= 1000 / fox_frame_rate:
fox_frame_timer -= 1000 / fox_frame_rate
fox_frame = (fox_frame + 1) % len(fox)
# рисуем фон, выводим фигуры Колобка и лисы
game_display.fill(background)
game_display.blit(rotated_kolobok, (kolobok_x, kolobok_y))
game_display.blit(fox[fox_frame], (fox_x, fox_y))
pygame.display.update()
pygame.quit()
Задание 6 – Светофор
Напишите Pygame
приложение для демонстрации работы светофора: когда горит зеленый свет (6
секунд), прямоугольники-автомобили движутся вперед. Красный и желтый свет
включаются на 2 секунды каждый, в это время трафик останавливается.
Пример:
Решение:
Код решения .
import pygame
import random
pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
game_display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Светофор')
clock = pygame.time.Clock()
BLACK = (0, 0, 0)
DARK_GRAY = (64, 64, 64)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
def game_loop():
game_exit = False
# определяем цвета для светофора
colors = [RED, YELLOW, GREEN]
active_index = 0
last_switch = pygame.time.get_ticks()
interval = 2000
# параметры автомобилей
car_width = 40
car_height = 60
car_speed = 2
horizontal_spacing = 12
vertical_spacing = 20
car_rects = []
for i in range(2):
left_rect = pygame.Rect(100, random.randint(50, WINDOW_HEIGHT - car_height), car_width, car_height)
right_rect = pygame.Rect(WINDOW_WIDTH - 300 - car_width, random.randint(50, WINDOW_HEIGHT - car_height), car_width, car_height)
car_rects.append(left_rect)
car_rects.append(right_rect)
# вертикальная и горизонтальная дистанция между автомобилями
for i in range(1, len(car_rects)):
if car_rects[i].left - car_rects[i-1].right < horizontal_spacing:
car_rects[i].left = car_rects[i-1].right + horizontal_spacing
if car_rects[i].top - car_rects[i-1].bottom < vertical_spacing:
car_rects[i].top = car_rects[i-1].bottom + vertical_spacing
while not game_exit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
# определяем нужный цвет светофора
now = pygame.time.get_ticks()
if now - last_switch >= interval:
active_index = (active_index + 1) % len(colors)
last_switch = now
# временной интервал для зеленого цвета - 6 секунд, для остальных - 2
interval = 6000 if active_index == 2 else 2000
# движение машин
if active_index == 0 or active_index == 1:
car_speed = 0
else:
car_speed = 2
for car_rect in car_rects:
car_rect.move_ip(0, -car_speed)
if car_rect.bottom <= 0:
car_rect.top = WINDOW_HEIGHT
car_rect.left = 100 if car_rect.left == WINDOW_WIDTH - 100 - car_width else WINDOW_WIDTH - 100 - car_width
# рисуем светофор
game_display.fill(GRAY)
light_rect = pygame.Rect((WINDOW_WIDTH - 200) // 2, (WINDOW_HEIGHT - 300) // 2, 100, 300)
pygame.draw.rect(game_display, DARK_GRAY, light_rect, 5)
light_width = light_rect.width
light_height = light_rect.height // 3
light_y = light_rect.top
for i in range(3):
circle_rect = pygame.Rect(light_rect.left + 10, light_y + i * light_height + 10, light_width - 20, light_height - 20)
circle_color = colors[i] if i == active_index else BLACK
pygame.draw.circle(game_display, circle_color, circle_rect.center, circle_rect.width // 2)
# рисуем автомобили
for car_rect in car_rects:
pygame.draw.rect(game_display, BLUE, car_rect)
pygame.display.update()
clock.tick(60)
pygame.quit()
game_loop()
Задание 7 – Визуальная память
Напишите лайт-версию игры Memory game , используя возможности Pygame . Сначала приложение выводит (в случайном порядке) цветные
окружности и дает возможность пользователю запомнить их расположение в течение
нескольких секунд. Затем приложение закрывает цветные окружности серыми:
пользователь должен по памяти сопоставить цветные пары. Каждая угаданная пара
приносит пользователю 1 балл.
Пример:
Решение:
Код решения .
import pygame
from random import shuffle
pygame.init()
# определяем цвета игры
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
blue = (0, 0, 255)
green = (0, 255, 0)
yellow = (255, 255, 0)
purple = (128, 0, 128)
grey = (192, 192, 192)
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Тренируем визуальную память")
# задаем параметры окружностей и перемешиваем пары
circle_radius = 50
circle_colors = [red, blue, green, yellow, purple, white]
circle_pairs = circle_colors * 2
shuffle(circle_pairs)
# формируем список окружностей
circle_positions = []
for i in range(6):
for j in range(2):
center_x = ((screen_width / 6) * (i + 1)) - (screen_width / 12)
center_y = ((screen_height / 3) * (j + 1)) - (screen_height / 6)
circle_positions.append([center_x, center_y])
# запоминаем позиции и цвета окружностей
original_circle_positions = circle_positions.copy()
original_circle_colors = circle_pairs.copy()
# рисуем цветные окружности
for i in range(len(circle_pairs)):
position = circle_positions[i]
color = circle_pairs[i]
pygame.draw.circle(screen, color, position, circle_radius)
font = pygame.font.SysFont('Arial', 20)
pygame.display.update()
# ждем 5 секунд
pygame.time.wait(5000)
# закрываем цветные окружности серыми
for i in range(len(circle_pairs)):
position = circle_positions[i]
pygame.draw.circle(screen, grey, position, circle_radius)
pygame.display.update()
uncovered_circles = []
last_uncovered_circle = None
score = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = event.pos
for i in range(len(circle_positions)):
position = circle_positions[i]
if ((position[0] - mouse_pos[0]) ** 2 + (position[1] - mouse_pos[1]) ** 2) ** 0.5 < circle_radius:
if i not in uncovered_circles:
uncovered_circles.append(i)
color = original_circle_colors[i]
pygame.draw.circle(screen, color, position, circle_radius)
pygame.display.update()
if last_uncovered_circle is not None and original_circle_colors[last_uncovered_circle] == original_circle_colors[i]:
score += 1
last_uncovered_circle = i
if len(uncovered_circles) == len(circle_pairs):
# вывод результата
final_score_text = font.render(f"Уровень памяти: {str(score)} из 6", True, white)
screen.blit(final_score_text, (screen_width // 2, screen_height // 2 + 125))
pygame.display.update()
pygame.time.wait(3000)
pygame.quit()
exit()
Задание 8 – Подсчет фигур
Напишите Pygame
приложение, в котором сверху окна вниз плавно спускаются случайные разноцветные
фигуры – треугольники, квадраты и окружности. Цвет для фигур выбирается
случайным образом, результаты подсчета выводятся в верхнем левом углу окна.
Пример:
Решение:
Код решения .
import pygame
import random
pygame.init()
width = 800
height = 600
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Подсчет фигур")
clock = pygame.time.Clock()
white = (255, 255, 255)
black = (0, 0, 0)
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255)]
# параметры фигур
class Circle:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
self.radius = 30
def draw(self):
pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius)
class Triangle:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
self.width = 60
self.height = 60
def draw(self):
pygame.draw.polygon(screen, self.color, [(self.x, self.y), (self.x + self.width, self.y), (self.x + self.width/2, self.y - self.height)])
class Square:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
self.width = 60
self.height = 60
def draw(self):
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))
# создаем список фигур
shapes = []
x = random.randint(0, width - 60)
y = random.randint(-500, -50)
color = random.choice(colors)
shape_type = random.choice(["circle", "triangle", "square"])
if shape_type == "circle":
shape = Circle(x, y, color)
elif shape_type == "triangle":
shape = Triangle(x, y, color)
else:
shape = Square(x, y, color)
shapes.append(shape)
# счетчики фигур
circle_count = 0
triangle_count = 0
square_count = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
screen.fill(black)
# рисуем и подсчитываем фигуры
for shape in shapes:
shape.draw()
shape.y += 5
if shape.y > height:
shapes.remove(shape)
if isinstance(shape, Circle):
circle_count += 1
elif isinstance(shape, Triangle):
triangle_count += 1
else:
square_count += 1
x = random.randint(0, width - 60)
y = random.randint(-500, -50)
color = random.choice(colors)
shape_type = random.choice(["circle", "triangle", "square"])
if shape_type == "circle":
shape = Circle(x, y, color)
elif shape_type == "triangle":
shape = Triangle(x, y, color)
else:
shape = Square(x, y, color)
shapes.append(shape)
# выводим счетчики
font = pygame.font.SysFont("Verdana", 25)
circle_text = font.render(f"Окружности: {circle_count}", True, white)
triangle_text = font.render(f"Треугольники: {triangle_count}", True, white)
square_text = font.render(f"Квадраты: {square_count}", True, white)
screen.blit(circle_text, (10, 10))
screen.blit(triangle_text, (10, 40))
screen.blit(square_text, (10, 70))
pygame.display.update()
clock.tick(60)
Задание 9 – Призы и бомбы
Напишите Pygame игру, в которой игрок (зеленая окружность)
должен «ловить» синие треугольники («призы»), избегая столкновения с красными
окружностями («бомбами»). Количество пойманных призов нужно подсчитывать.
Пример:
Решение:
Код решения .
import pygame
import random
class RewardsBombs():
def __init__(self):
pygame.init()
self.screen_width = 600
self.screen_height = 600
self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
pygame.display.set_caption("Призы и бомбы")
self.clock = pygame.time.Clock()
self.green_pos = [self.screen_width // 2, self.screen_height - 30]
self.red_positions = []
self.red_speed = 2
self.score = 0
self.font = pygame.font.SysFont("Arial", 24)
self.run()
def run(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
if self.green_pos[0] - 20 >= 0:
self.green_pos[0] -= 20
elif event.key == pygame.K_RIGHT:
if self.green_pos[0] + 20 <= self.screen_width:
self.green_pos[0] += 20
elif event.key == pygame.K_UP:
if self.green_pos[1] - 20 >= 0:
self.green_pos[1] -= 20
elif event.key == pygame.K_DOWN:
if self.green_pos[1] + 20 <= self.screen_height:
self.green_pos[1] += 20
# движение красных бомб
for i in range(len(self.red_positions)):
self.red_positions[i][1] += self.red_speed
# создание бомб и призов
if random.random() < 0.02:
x = random.randint(0, self.screen_width)
num = random.randint(1, 10)
if num % 2 == 0:
self.red_positions.append([x, 0, False])
else:
self.red_positions.append([x, 0, True])
# проверка столкновений с игроком
for pos in self.red_positions:
if pos[2]:
if abs(pos[0] - self.green_pos[0]) <= 20 and abs(pos[1] - self.green_pos[1]) <= 20:
self.score += 1
self.red_positions.remove(pos)
else:
if (pos[0] - self.green_pos[0]) ** 2 + (pos[1] - self.green_pos[1]) ** 2 < 400:
self.game_over()
# убираем бомбы за пределами окна
self.red_positions = [pos for pos in self.red_positions if pos[1] < self.screen_height]
self.screen.fill((0, 0, 0))
for pos in self.red_positions:
if pos[2]:
pygame.draw.polygon(self.screen, (0, 0, 255), [[pos[0], pos[1]-10], [pos[0]+10, pos[1]+10], [pos[0]-10, pos[1]+10]])
else:
pygame.draw.circle(self.screen, (255, 0, 0), pos[:2], 10)
pygame.draw.circle(self.screen, (0, 255, 0), self.green_pos, 10)
self.draw_score()
pygame.display.update()
self.clock.tick(60)
def draw_score(self):
score_surface = self.font.render(f"Призы: {self.score}", True, (255, 255, 255))
self.screen.blit(score_surface, (10, 10))
def game_over(self):
message_surface = self.font.render(f"Игра закончена! Призы: {self.score}", True, (255, 0, 0))
self.screen.blit(message_surface, (self.screen_width // 2 - message_surface.get_width() // 2, self.screen_height // 2 - message_surface.get_height() // 2))
pygame.display.update()
pygame.time.wait(3000)
pygame.quit()
exit()
if __name__ == "__main__":
RewardsBombs()
Задание 10 – Змейка
Напишите лайт-версию игры «Змейка», используя Pygame. Змейка
ест красные яблоки, которые появляются в случайных позициях в пределах игрового
поля, и прибавляет в длине после каждого яблока. При столкновении с хвостом или
границей окна игра заканчивается.
Пример:
Решение:
Код решения .
import pygame
import random
pygame.init()
screen_width = 600
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Змейка")
green = (0, 255, 0)
red = (255, 0, 0)
font = pygame.font.SysFont("Arial", 20)
clock = pygame.time.Clock()
# основные параметры игры
cell_size = 20
snake_speed = 5
snake_length = 3
snake_body = []
for i in range(snake_length):
snake_body.append(pygame.Rect((screen_width / 2) - (cell_size * i), screen_height / 2, cell_size, cell_size))
snake_direction = "right"
new_direction = "right"
apple_position = pygame.Rect(random.randint(0, screen_width - cell_size), random.randint(0, screen_height - cell_size), cell_size, cell_size)
game_over = False
while not game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and snake_direction != "down":
new_direction = "up"
elif event.key == pygame.K_DOWN and snake_direction != "up":
new_direction = "down"
elif event.key == pygame.K_LEFT and snake_direction != "right":
new_direction = "left"
elif event.key == pygame.K_RIGHT and snake_direction != "left":
new_direction = "right"
# новое направление движения
snake_direction = new_direction
# управление змейкой
if snake_direction == "up":
snake_body.insert(0, pygame.Rect(snake_body[0].left, snake_body[0].top - cell_size, cell_size, cell_size))
elif snake_direction == "down":
snake_body.insert(0, pygame.Rect(snake_body[0].left, snake_body[0].top + cell_size, cell_size, cell_size))
elif snake_direction == "left":
snake_body.insert(0, pygame.Rect(snake_body[0].left - cell_size, snake_body[0].top, cell_size, cell_size))
elif snake_direction == "right":
snake_body.insert(0, pygame.Rect(snake_body[0].left + cell_size, snake_body[0].top, cell_size, cell_size))
# проверяем, съела ли змея яблоко
if snake_body[0].colliderect(apple_position):
apple_position = pygame.Rect(random.randint(0, screen_width - cell_size), random.randint(0, screen_height-cell_size), cell_size, cell_size)
snake_length += 1
if len(snake_body) > snake_length:
snake_body.pop()
# проверка столкновения со стенами
if snake_body[0].left < 0 or snake_body[0].right > screen_width or snake_body[0].top < 0 or snake_body[0].bottom > screen_height:
game_over = True
# проверка столкновения с собственным телом
for i in range(1, len(snake_body)):
if snake_body[0].colliderect(snake_body[i]):
game_over = True
screen.fill((0, 0, 0))
# рисуем змейку
for i in range(len(snake_body)):
if i == 0:
pygame.draw.circle(screen, green, snake_body[i].center, cell_size / 2)
else:
pygame.draw.circle(screen, green, snake_body[i].center, cell_size / 2)
pygame.draw.circle(screen, (0, 200, 0), snake_body[i].center, cell_size / 4)
# рисуем яблоко
pygame.draw.circle(screen, red, apple_position.center, cell_size / 2)
# выводим количество яблок
score_text = font.render(f"Съедено яблок: {snake_length - 3}", True, (255, 255, 255))
screen.blit(score_text, (10, 10))
pygame.display.update()
clock.tick(snake_speed)
pygame.quit()
Подведем итоги
Мы рассмотрели самые простые приемы разработки игр в Pygame
– возможности этой библиотеки намного обширнее. К примеру, для быстрой
разработки в Pygame используются спрайты – объекты для определения свойств и
поведения игровых элементов. Встроенные классы Group , GroupSingle и RenderUpdates позволяют быстро,
просто и эффективно группировать, обновлять и отрисовывать игровые элементы.
В
следующей главе будем изучать работу с SQL и базами данных .
***
Содержание самоучителя
Особенности, сферы применения, установка, онлайн IDE
Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
Типы данных: преобразование и базовые операции
Методы работы со строками
Методы работы со списками и списковыми включениями
Методы работы со словарями и генераторами словарей
Методы работы с кортежами
Методы работы со множествами
Особенности цикла for
Условный цикл while
Функции с позиционными и именованными аргументами
Анонимные функции
Рекурсивные функции
Функции высшего порядка, замыкания и декораторы
Методы работы с файлами и файловой системой
Регулярные выражения
Основы скрапинга и парсинга
Основы ООП: инкапсуляция и наследование
Основы ООП: абстракция и полиморфизм
Графический интерфейс на Tkinter
Основы разработки игр на Pygame
Основы работы с SQLite
Основы веб-разработки на Flask
Основы работы с NumPy
Основы анализа данных с Pandas
***
Материалы по теме
Над какой игрой трудитесь? Расскажите в комментариях!