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

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Без дона­тов!

Преж­де чем мы нач­нём про­грам­ми­ро­вать что-то полез­ное на 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) 

Что дальше

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

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

чтобы делать крутые проекты!


Источник: m.vk.com

Комментарии: