Пишем игру на Python

Пишем игру на Python



Логика игры

Есть игровое поле — простой прямоугольник с твёрдыми границами. Когда шарик касается стенки или потолка, он отскакивает в другую сторону. Если он упадёт на пол — вы проиграли. Чтобы этого не случилось, внизу вдоль пола летает платформа, а вы ей управляете с помощью стрелок. Ваша задача — подставлять платформу под шарик как можно дольше. За каждое удачное спасение шарика вы получаете одно очко.

Алгоритм

Чтобы реализовать такую логику игры, нужно предусмотреть такие сценарии поведения:

  • игра начинается;
  • шарик начинает двигаться;
  • если нажаты стрелки влево или вправо — двигаем платформу;
  • если шарик коснулся стенок, потолка или платформы — делаем отскок;
  • если шарик коснулся платформы — увеличиваем счёт на единицу;
  • если шарик упал на пол — выводим сообщение и заканчиваем игру.

Хитрость в том, что всё это происходит параллельно и независимо друг от друга. То есть пока шарик летает, мы вполне можем двигать платформу, а можем и оставить её на месте. И когда шарик отскакивает от стен, это тоже не мешает другим объектам двигаться и взаимодействовать между собой.

Получается, что нам нужно определить три класса — платформу, сам шарик и счёт, и определить, как они реагируют на действия друг друга. Поле нам самим определять не нужно — для этого есть уже готовая библиотека. А потом в этих классах мы пропишем методы — они как раз и будут отвечать за поведение наших объектов.

Весь кайф в том, что мы всё это задаём один раз, а потом объекты сами разбираются, как им реагировать друг на друга и что делать в разных ситуациях. Мы не прописываем жёстко весь алгоритм, а задаём правила игры — а для этого классы подходят просто идеально.

По коням, пишем на Python

Для этого проекта вам потребуется установить и запустить среду Python. Как это сделать — читайте в нашей статье.

Начало программы

Чтобы у нас появилась графика в игре, используем библиотеку Tkinter. Она входит в набор стандартных библиотек Python и позволяет рисовать простейшие объекты — линии, прямоугольники, круги и красить их в разные цвета. Такой простой Paint, только для Python.

Чтобы создать окно, где будет видна графика, используют класс Tk(). Он просто делает окно, но без содержимого. Чтобы появилось содержимое, создают холст — видимую часть окна. Именно на нём мы будем рисовать нашу игру. За холст отвечает класс Canvas(), поэтому нам нужно будет создать свой объект из этого класса и дальше уже работать с этим объектом.

Если мы принудительно не ограничим скорость платформы, то она будет перемещаться мгновенно, ведь компьютер считает очень быстро и моментально передвинет её к другому краю. Поэтому мы будем искусственно ограничивать время движения, а для этого нам понадобится модуль Time — он тоже стандартный.

Последнее, что нам глобально нужно, — задавать случайным образом начальное положение шарика и платформы, чтобы было интереснее играть. За это отвечает модуль Random — он помогает генерировать случайные числа и перемешивать данные.

Запишем всё это в виде кода на Python:

# подключаем графическую библиотеку
from tkinter import *
# подключаем модули, которые отвечают за время и случайные числа
import time
import random
# создаём новый объект — окно с игровым полем. В нашем случае переменная окна называется tk, и мы его сделали из класса Tk() — он есть в графической библиотеке 
tk = Tk()
# делаем заголовок окна — Games с помощью свойства объекта title
tk.title('Game')
# запрещаем менять размеры окна, для этого используем свойство resizable 
tk.resizable(0, 0)
# помещаем наше игровое окно выше остальных окон на компьютере, чтобы другие окна не могли его заслонить
tk.wm_attributes('-topmost', 1)
# создаём новый холст — 400 на 500 пикселей, где и будем рисовать игру
canvas = Canvas(tk, width=500, height=400, highlightthickness=0)
# говорим холсту, что у каждого видимого элемента будут свои отдельные координаты 
canvas.pack()
# обновляем окно с холстом
tk.update()

Шарик

Сначала проговорим словами, что нам нужно от шарика. Он должен уметь:

  • задавать своё начальное положение и направление движение;
  • понимать, когда он коснулся платформы;
  • рисовать сам себя и понимать, когда нужно отрисовать себя в новом положении (например, после отскока от стены).


Этого достаточно, чтобы шарик жил своей жизнью и умел взаимодействовать с окружающей средой. При этом нужно не забыть о том, что каждый класс должен содержать конструктор — код, который отвечает за создание нового объекта. Без этого сделать шарик не получится. Запишем это на Python:# Описываем класс Ball, который будет отвечать за шарик

class Ball:
    # конструктор — он вызывается в момент создания нового объекта на основе этого класса
    def __init__(self, canvas, paddle, score, color):
        # задаём параметры объекта, которые нам передают в скобках в момент создания
        self.canvas = canvas
        self.paddle = paddle
        self.score = score
        # цвет нужен был для того, чтобы мы им закрасили весь шарик
        # здесь появляется новое свойство id, в котором хранится внутреннее название шарика
        # а ещё командой create_oval мы создаём круг радиусом 15 пикселей и закрашиваем нужным цветом
        self.id = canvas.create_oval(10,10, 25, 25, fill=color)
        # помещаем шарик в точку с координатами 245,100
        self.canvas.move(self.id, 245, 100)
        # задаём список возможных направлений для старта
        starts = [-2, -1, 1, 2]
        # перемешиваем его 
        random.shuffle(starts)
        # выбираем первый из перемешанного — это будет вектор движения шарика
        self.x = starts[0]
        # в самом начале он всегда падает вниз, поэтому уменьшаем значение по оси y
        self.y = -2
        # шарик узнаёт свою высоту и ширину
        self.canvas_height = self.canvas.winfo_height()
        self.canvas_width = self.canvas.winfo_width()
        # свойство, которое отвечает за то, достиг шарик дна или нет. Пока не достиг, значение будет False
        self.hit_bottom = False
    # обрабатываем касание платформы, для этого получаем 4 координаты шарика в переменной pos (левая верхняя и правая нижняя точки)
    def hit_paddle(self, pos):
        # получаем кординаты платформы через объект paddle (платформа)
        paddle_pos = self.canvas.coords(self.paddle.id)
        # если координаты касания совпадают с координатами платформы
        if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
            if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
                # увеличиваем счёт (обработчик этого события будет описан ниже)
                self.score.hit()
                # возвращаем метку о том, что мы успешно коснулись
                return True
        # возвращаем False — касания не было
        return False
    # обрабатываем отрисовку шарика
    def draw(self):
        # передвигаем шарик на заданные координаты x и y
        self.canvas.move(self.id, self.x, self.y)
        # запоминаем новые координаты шарика
        pos = self.canvas.coords(self.id)
        # если шарик падает сверху  
        if pos[1] <= 0:
            # задаём падение на следующем шаге = 2
            self.y = 2
        # если шарик правым нижним углом коснулся дна
        if pos[3] >= self.canvas_height:
            # помечаем это в отдельной переменной
            self.hit_bottom = True
            # выводим сообщение и количество очков
            canvas.create_text(250, 120, text='Вы проиграли', font=('Courier', 30), fill='red')
        # если было касание платформы
        if self.hit_paddle(pos) == True:
            # отправляем шарик наверх
            self.y = -2
        # если коснулись левой стенки
        if pos[0] <= 0:
            # движемся вправо
            self.x = 2
        # если коснулись правой стенки
        if pos[2] >= self.canvas_width:
            # движемся влево
            self.x = -2

Платформа

Сделаем то же самое для платформы — сначала опишем её поведение словами, а потом переведём в код. Итак, вот что должна уметь платформа:

  • двигаться влево или вправо в зависимости от нажатой стрелки;
  • понимать, когда игра началась и можно двигаться.

А вот как это будет в виде кода:# Описываем класс Paddle, который отвечает за платформы

class Paddle:

   # конструктор

   def __init__(self, canvas, color):

       # canvas означает, что платформа будет нарисована на нашем изначальном холсте

       self.canvas = canvas

       # создаём прямоугольную платформу 10 на 100 пикселей, закрашиваем выбранным цветом и получаем её внутреннее имя

       self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)

       # задаём список возможных стартовых положений платформы

       start_1 = [40, 60, 90, 120, 150, 180, 200]

       # перемешиваем их

       random.shuffle(start_1)

       # выбираем первое из перемешанных

       self.starting_point_x = start_1[0]

       # перемещаем платформу в стартовое положение

       self.canvas.move(self.id, self.starting_point_x, 300)

       # пока платформа никуда не движется, поэтому изменений по оси х нет

       self.x = 0

       # платформа узнаёт свою ширину

       self.canvas_width = self.canvas.winfo_width()

       # задаём обработчик нажатий

       # если нажата стрелка вправо — выполняется метод turn_right()

       self.canvas.bind_all('<KeyPress-Right>', self.turn_right)

       # если стрелка влево — turn_left()

       self.canvas.bind_all('<KeyPress-Left>', self.turn_left)

       # пока игра не началась, поэтому ждём

       self.started = False

       # как только игрок нажмёт Enter — всё стартует

       self.canvas.bind_all('<KeyPress-Return>', self.start_game)

   # движемся вправо

   def turn_right(self, event):

       # будем смещаться правее на 2 пикселя по оси х

       self.x = 2

   # движемся влево

   def turn_left(self, event):

       # будем смещаться левее на 2 пикселя по оси х

       self.x = -2

   # игра начинается

   def start_game(self, event):

       # меняем значение переменной, которая отвечает за старт

       self.started = True

   # метод, который отвечает за движение платформы

   def draw(self):

       # сдвигаем нашу платформу на заданное количество пикселей

       self.canvas.move(self.id, self.x, 0)

       # получаем координаты холста

       pos = self.canvas.coords(self.id)

       # если мы упёрлись в левую границу

       if pos[0] <= 0:

           # останавливаемся

           self.x = 0

       # если упёрлись в правую границу

       elif pos[2] >= self.canvas_width:

           # останавливаемся

           self.x = 0

Счёт

Можно было не выделять счёт в отдельный класс и каждый раз обрабатывать вручную. Но здесь реально проще сделать класс, задать нужные методы, чтобы они сами потом разобрались, что и когда делать.

От счёта нам нужно только одно (кроме конструктора) — чтобы он правильно реагировал на касание платформы, увеличивал число очков и выводил их на экран:

# Описываем класс Score, который отвечает за отображение счетов

class Score:

   # конструктор

   def __init__(self, canvas, color):

       # в самом начале счёт равен нулю

       self.score = 0

       # будем использовать наш холст

       self.canvas = canvas

       # создаём надпись, которая показывает текущий счёт, делаем его нужно цвета и запоминаем внутреннее имя этой надписи

       self.id = canvas.create_text(450, 10, text=self.score, font=('Courier', 15), fill=color)

   # обрабатываем касание платформы

   def hit(self):

       # увеличиваем счёт на единицу

       self.score += 1

       # пишем новое значение счёта

       self.canvas.itemconfig(self.id, text=self.score)

       Игра

У нас всё готово для того, чтобы написать саму игру. Мы уже провели необходимую подготовку всех элементов, и нам остаётся только создать конкретные объекты шарика, платформы и счёта и сказать им, в каком порядке мы будем что делать.

Смысл игры в том, чтобы не уронить шарик. Пока этого не произошло — всё движется, но как только шарик упал — нужно показать сообщение о конце игры и остановить программу.

Посмотрите, как лаконично выглядит код непосредственно самой игры:

# создаём объект — зелёный счёт

score = Score(canvas, 'green')

# создаём объект — белую платформу

paddle = Paddle(canvas, 'White')

# создаём объект — красный шарик

ball = Ball(canvas, paddle, score, 'red')

# пока шарик не коснулся дна

while not ball.hit_bottom:

   # если игра началась и платформа может двигаться

   if paddle.started == True:

       # двигаем шарик

       ball.draw()

       # двигаем платформу

       paddle.draw()

   # обновляем наше игровое поле, чтобы всё, что нужно, закончило рисоваться

   tk.update_idletasks()

   # обновляем игровое поле, и смотрим за тем, чтобы всё, что должно было быть сделано — было сделано

   tk.update()

   # замираем на одну сотую секунды, чтобы движение элементов выглядело плавно

   time.sleep(0.01)

# если программа дошла досюда, значит, шарик коснулся дна. Ждём 3 секунды, пока игрок прочитает финальную надпись, и завершаем игру

time.sleep(3)



Полный Код Программы

# подключаем графическую библиотеку

from tkinter import *

# подключаем модули, которые отвечают за время и случайные числа

import time

import random

# создаём новый объект — окно с игровым полем. В нашем случае переменная окна называется tk, и мы его сделали из класса Tk() — он есть в графической библиотеке

tk = Tk()

# делаем заголовок окна — Games с помощью свойства объекта title

tk.title('Game')

# запрещаем менять размеры окна, для этого используем свойство resizable

tk.resizable(0, 0)

# помещаем наше игровое окно выше остальных окон на компьютере, чтобы другие окна не могли его заслонить. Попробуйте :)

tk.wm_attributes('-topmost', 1)

# создаём новый холст — 400 на 500 пикселей, где и будем рисовать игру

canvas = Canvas(tk, width=500, height=400, highlightthickness=0)

# говорим холсту, что у каждого видимого элемента будут свои отдельные координаты

canvas.pack()

# обновляем окно с холстом

tk.update()

# Описываем класс Ball, который будет отвечать за шарик

class Ball:

   # конструктор — он вызывается в момент создания нового объекта на основе этого класса

   def __init__(self, canvas, paddle, score, color):

       # задаём параметры объекта, которые нам передают в скобках в момент создания

       self.canvas = canvas

       self.paddle = paddle

       self.score = score

       # цвет нужен был для того, чтобы мы им закрасили весь шарик

       # здесь появляется новое свойство id, в котором хранится внутреннее название шарика

       # а ещё командой create_oval мы создаём круг радиусом 15 пикселей и закрашиваем нужным цветом

       self.id = canvas.create_oval(10,10, 25, 25, fill=color)

       # помещаем шарик в точку с координатами 245,100

       self.canvas.move(self.id, 245, 100)

       # задаём список возможных направлений для старта

       starts = [-2, -1, 1, 2]

       # перемешиваем его

       random.shuffle(starts)

       # выбираем первый из перемешанного — это будет вектор движения шарика

       self.x = starts[0]

       # в самом начале он всегда падает вниз, поэтому уменьшаем значение по оси y

       self.y = -2

       # шарик узнаёт свою высоту и ширину

       self.canvas_height = self.canvas.winfo_height()

       self.canvas_width = self.canvas.winfo_width()

       # свойство, которое отвечает за то, достиг шарик дна или нет. Пока не достиг, значение будет False

       self.hit_bottom = False

   # обрабатываем касание платформы, для этого получаем 4 координаты шарика в переменной pos (левая верхняя и правая нижняя точки)

   def hit_paddle(self, pos):

       # получаем кординаты платформы через объект paddle (платформа)

       paddle_pos = self.canvas.coords(self.paddle.id)

       # если координаты касания совпадают с координатами платформы

       if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:

           if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:

               # увеличиваем счёт (обработчик этого события будет описан ниже)

               self.score.hit()

               # возвращаем метку о том, что мы успешно коснулись

               return True

       # возвращаем False — касания не было

       return False

   # метод, который отвечает за движение шарика

   def draw(self):

       # передвигаем шарик на заданный вектор x и y

       self.canvas.move(self.id, self.x, self.y)

       # запоминаем новые координаты шарика

       pos = self.canvas.coords(self.id)

       # если шарик падает сверху 

       if pos[1] <= 0:

           # задаём падение на следующем шаге = 2

           self.y = 2

       # если шарик правым нижним углом коснулся дна

       if pos[3] >= self.canvas_height:

           # помечаем это в отдельной переменной

           self.hit_bottom = True

           # выводим сообщение и количество очков

           canvas.create_text(250, 120, text='Вы проиграли', font=('Courier', 30), fill='red')

       # если было касание платформы

       if self.hit_paddle(pos) == True:

           # отправляем шарик наверх

           self.y = -2

       # если коснулись левой стенки

       if pos[0] <= 0:

           # движемся вправо

           self.x = 2

       # если коснулись правой стенки

       if pos[2] >= self.canvas_width:

           # движемся влево

           self.x = -2

# Описываем класс Paddle, который отвечает за платформы

class Paddle:

   # конструктор

   def __init__(self, canvas, color):

       # canvas означает, что платформа будет нарисована на нашем изначальном холсте

       self.canvas = canvas

       # создаём прямоугольную платформу 10 на 100 пикселей, закрашиваем выбранным цветом и получаем её внутреннее имя

       self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)

       # задаём список возможных стартовых положений платформы

       start_1 = [40, 60, 90, 120, 150, 180, 200]

       # перемешиваем их

       random.shuffle(start_1)

       # выбираем первое из перемешанных

       self.starting_point_x = start_1[0]

       # перемещаем платформу в стартовое положение

       self.canvas.move(self.id, self.starting_point_x, 300)

       # пока платформа никуда не движется, поэтому изменений по оси х нет

       self.x = 0

       # платформа узнаёт свою ширину

       self.canvas_width = self.canvas.winfo_width()

       # задаём обработчик нажатий

       # если нажата стрелка вправо — выполняется метод turn_right()

       self.canvas.bind_all('<KeyPress-Right>', self.turn_right)

       # если стрелка влево — turn_left()

       self.canvas.bind_all('<KeyPress-Left>', self.turn_left)

       # пока платформа не двигается, поэтому ждём

       self.started = False

       # как только игрок нажмёт Enter — всё стартует

       self.canvas.bind_all('<KeyPress-Return>', self.start_game)

   # движемся вправо

   def turn_right(self, event):

       # будем смещаться правее на 2 пикселя по оси х

       self.x = 2

   # движемся влево

   def turn_left(self, event):

       # будем смещаться левее на 2 пикселя по оси х

       self.x = -2

   # игра начинается

   def start_game(self, event):

       # меняем значение переменной, которая отвечает за старт движения платформы

       self.started = True

   # метод, который отвечает за движение платформы

   def draw(self):

       # сдвигаем нашу платформу на заданное количество пикселей

       self.canvas.move(self.id, self.x, 0)

       # получаем координаты холста

       pos = self.canvas.coords(self.id)

       # если мы упёрлись в левую границу

       if pos[0] <= 0:

           # останавливаемся

           self.x = 0

       # если упёрлись в правую границу

       elif pos[2] >= self.canvas_width:

           # останавливаемся

           self.x = 0

# Описываем класс Score, который отвечает за отображение счетов

class Score:

   # конструктор

   def __init__(self, canvas, color):

       # в самом начале счёт равен нулю

       self.score = 0

       # будем использовать наш холст

       self.canvas = canvas

       # создаём надпись, которая показывает текущий счёт, делаем его нужно цвета и запоминаем внутреннее имя этой надписи

       self.id = canvas.create_text(450, 10, text=self.score, font=('Courier', 15), fill=color)

   # обрабатываем касание платформы

   def hit(self):

       # увеличиваем счёт на единицу

       self.score += 1

       # пишем новое значение счёта

       self.canvas.itemconfig(self.id, text=self.score)

# создаём объект — зелёный счёт

score = Score(canvas, 'green')

# создаём объект — белую платформу

paddle = Paddle(canvas, 'White')

# создаём объект — красный шарик

ball = Ball(canvas, paddle, score, 'red')

# пока шарик не коснулся дна

while not ball.hit_bottom:

   # если игра началась и платформа может двигаться

   if paddle.started == True:

       # двигаем шарик

       ball.draw()

       # двигаем платформу

       paddle.draw()

   # обновляем наше игровое поле, чтобы всё, что нужно, закончило рисоваться

   tk.update_idletasks()

   # обновляем игровое поле и смотрим за тем, чтобы всё, что должно было быть сделано — было сделано

   tk.update()

   # замираем на одну сотую секунды, чтобы движение элементов выглядело плавно

   time.sleep(0.01)

# если программа дошла досюда, значит, шарик коснулся дна. Ждём 3 секунды, пока игрок прочитает финальную надпись, и завершаем игру

time.sleep(3)

___________________________________________________________________________

Что дальше

На основе этого кода вы можете сделать свою модификацию игры:

  • добавить второй шарик;
  • раскрасить элементы в другой цвет;
  • поменять размеры шарика; поменять скорость платформы;
  • сделать всё это сразу;
  • поменять логику программы на свою.


Оригинал







Report Page