В этой части изучения Tkinter мы создадим клон игры Nibbles (змейка). Nibbles – это первоначальная версия классической змейки. Впервые ее создали в конце 70х годов. Позже она была перенесена на ПК. В этой игре игрок управляет змейкой. Цель игры – съесть как можно больше яблок. После каждого съеденного яблока тело змеи увеличивается. Во время движения змейка должна уклоняться от стен и собственного тела.
Содержание курса
- Создание окна по центру и кнопка выхода в Tkinter
- Разметка виджетов в Tkinter — pack, grid и place
- Виджеты Checkbutton, Label, Scale и Listbox в Tkinter
- Меню, подменю и панель инструментов в Tkinter
- Диалоговые окна в Tkinter — Выбор цвета — Выбор файла
- Рисуем линии, прямоугольники, круг и текст в Tkinter
- Пишем игру змейка на Tkinter
Содержание статьи
Этапы разработки игры на Tkinter
Размер каждого соединения змейки – 10 пикселей. Змейка управляется стрелками на клавиатуре. Изначально у змейки есть всего три соединения. Игра начинается мгновенно. Когда игра заканчивается, на экране появляется надпись «Игра Закончена».
Для начало, мы создаем Canvas виджет. Объектами в игре являются изображения. Мы используем методы canvas для создания объектов изображения. Также, мы используем методы canvas для обнаружения объектов на холсте при помощи тегов, и для обнаружения столкновений с другими объектами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
import sys import random from PIL import Image, ImageTk from tkinter import Tk, Frame, Canvas, ALL, NW class Cons: BOARD_WIDTH = 300 BOARD_HEIGHT = 300 DELAY = 100 DOT_SIZE = 10 MAX_RAND_POS = 27 class Board(Canvas): def __init__(self): super().__init__( width=Cons.BOARD_WIDTH, height=Cons.BOARD_HEIGHT, background="blue", highlightthickness=0 ) self.initGame() self.pack() def initGame(self): """ Инициализация игры. """ self.inGame = True self.dots = 3 self.score = 0 # Переменные для передвижения замеи. self.moveX = Cons.DOT_SIZE self.moveY = 0 # Изначальные стартовые координаты яблоки. self.appleX = 100 self.appleY = 190 self.loadImages() self.createObjects() self.locateApple() self.bind_all("<Key>", self.onKeyPressed) self.after(Cons.DELAY, self.onTimer) def loadImages(self): """ Подгружаем нужные изображения для игры. """ try: self.idot = Image.open("dot.png") self.dot = ImageTk.PhotoImage(self.idot) self.ihead = Image.open("head.png") self.head = ImageTk.PhotoImage(self.ihead) self.iapple = Image.open("apple.png") self.apple = ImageTk.PhotoImage(self.iapple) except IOError as e: print(e) sys.exit(1) def createObjects(self): """ Создание объектов на холсте. """ self.create_text( 30, 10, text="Счет: {0}".format(self.score), tag="score", fill="white" ) self.create_image( self.appleX, self.appleY, image=self.apple, anchor=NW, tag="apple" ) self.create_image(50, 50, image=self.head, anchor=NW, tag="head") self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot") self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot") def checkAppleCollision(self): """ Проверяем, не столкнулась ли голова змеи с яблоком. """ apple = self.find_withtag("apple") head = self.find_withtag("head") x1, y1, x2, y2 = self.bbox(head) overlap = self.find_overlapping(x1, y1, x2, y2) for ovr in overlap: if apple[0] == ovr: self.score += 1 x, y = self.coords(apple) self.create_image(x, y, image=self.dot, anchor=NW, tag="dot") self.locateApple() def moveSnake(self): """ Меняем положение змеи на холсте. """ dots = self.find_withtag("dot") head = self.find_withtag("head") items = dots + head z = 0 while z < len(items)-1: c1 = self.coords(items[z]) c2 = self.coords(items[z+1]) self.move(items[z], c2[0]-c1[0], c2[1]-c1[1]) z += 1 self.move(head, self.moveX, self.moveY) def checkCollisions(self): """ Проверка на столкновение змеи с другими объектами. """ dots = self.find_withtag("dot") head = self.find_withtag("head") x1, y1, x2, y2 = self.bbox(head) overlap = self.find_overlapping(x1, y1, x2, y2) for dot in dots: for over in overlap: if over == dot: self.inGame = False if x1 < 0: self.inGame = False if x1 > Cons.BOARD_WIDTH - Cons.DOT_SIZE: self.inGame = False if y1 < 0: self.inGame = False if y1 > Cons.BOARD_HEIGHT - Cons.DOT_SIZE: self.inGame = False def locateApple(self): """ Распределяем яблоки по холсту (canvas). """ apple = self.find_withtag("apple") self.delete(apple[0]) r = random.randint(0, Cons.MAX_RAND_POS) self.appleX = r * Cons.DOT_SIZE r = random.randint(0, Cons.MAX_RAND_POS) self.appleY = r * Cons.DOT_SIZE self.create_image( self.appleX, self.appleY, anchor=NW, image=self.apple, tag="apple" ) def onKeyPressed(self, e): """ Управляем змеей через стрелки клавиатуры. """ key = e.keysym LEFT_CURSOR_KEY = "Left" if key == LEFT_CURSOR_KEY and self.moveX <= 0: self.moveX = -Cons.DOT_SIZE self.moveY = 0 RIGHT_CURSOR_KEY = "Right" if key == RIGHT_CURSOR_KEY and self.moveX >= 0: self.moveX = Cons.DOT_SIZE self.moveY = 0 RIGHT_CURSOR_KEY = "Up" if key == RIGHT_CURSOR_KEY and self.moveY <= 0: self.moveX = 0 self.moveY = -Cons.DOT_SIZE DOWN_CURSOR_KEY = "Down" if key == DOWN_CURSOR_KEY and self.moveY >= 0: self.moveX = 0 self.moveY = Cons.DOT_SIZE def onTimer(self): """ Создает игровой цикл для каждого события таймера """ self.drawScore() self.checkCollisions() if self.inGame: self.checkAppleCollision() self.moveSnake() self.after(Cons.DELAY, self.onTimer) else: self.gameOver() def drawScore(self): """ Рисуем счет игры """ score = self.find_withtag("score") self.itemconfigure(score, text="Счет: {0}".format(self.score)) def gameOver(self): """ Удаляем все объекты и выводим сообщение об окончании игры. """ self.delete(ALL) self.create_text(self.winfo_width() / 2, self.winfo_height()/2, text="Игра закончилась со счетом {0}".format(self.score), fill="white") class Snake(Frame): def __init__(self): super().__init__() self.master.title('Змейка') self.board = Board() self.pack() def main(): root = Tk() nib = Snake() root.mainloop() if __name__ == '__main__': main() |
В первую очередь, мы расшифруем некоторые переменные, использующиеся в нашей игре:
BOARD_WIDTH
иBOARD_HEIGHT
обозначают размер игрового поля.DELAY
– скорость игры;DOT_SIZE
— размер яблока и соединения змейки;MAX_RAND_POS
– создание случайной точки для яблока.
Метод iniGame() инициализирует переменные, загружает изображения игры и запускает функцию таймера.
1 2 |
self.createObjects() self.locateApple() |
Метод createObject() создает объекты на холсте, а locateApple() устанавливает яблоко на случайную точку на холсте.
1 |
self.bind_all("<Key>", self.onKeyPressed) |
Мы назначаем клавиши клавиатуры при помощи метода onKeyPressed(). Для управления замеей в игре используются стрелки на клавиатуре.
1 2 3 4 5 6 7 8 9 10 11 |
try: self.idot = Image.open("dot.png") self.dot = ImageTk.PhotoImage(self.idot) self.ihead = Image.open("head.png") self.head = ImageTk.PhotoImage(self.ihead) self.iapple = Image.open("apple.png") self.apple = ImageTk.PhotoImage(self.iapple) except IOError as e: print(e) sys.exit(1) |
В этих строчках показана загрузка наших изображений. Это три изображения для нашей игры:
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def createObjects(self): """ Создание объектов на холсте. """ self.create_text( 30, 10, text="Счет: {0}".format(self.score), tag="score", fill="white" ) self.create_image( self.appleX, self.appleY, image=self.apple, anchor=NW, tag="apple" ) self.create_image(50, 50, image=self.head, anchor=NW, tag="head") self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot") self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot") |
С помощью метода createObject() мы создаем игровые объекты на холсте. Это canvas объекты. У них есть стартовые
x
и y
координаты.
Параметр image позволяет изображениям появиться на холсте. Параметр anchor установлен на NW (Север и Запад), что означает ориентир на верхнюю левую точку холста. И этот шаг очень важен, если мы хотим отображать изображения около границ корневого окна tkinter.
Попробуйте удалить anchor и посмотрите, что произойдет. Параметр tag используется для идентификации объектов на холсте.
Вы можете прямо сейчас купить дешевых подписчиков на YouTube с помощью сервиса doctorsmm.com. Тут Вы найдете не только вкусный прайс, но и разнообразные критерии к каждой услуге, которые подойдут любому каналу. Кроме того, Вы можете получить большинство услуг со стабильной гарантией качества и защитой от списаний.
Один тег может использоваться для нескольких объектов на холсте.
Метод checkAppleCollision()
позволяет нам узнать, съела ли змея яблоко или нет. Если да, мы добавляем одно соединение змейке и выполняем метод locateApple()
для создания нового яблока на нашей игровой карте.
1 2 |
apple = self.find_withtag("apple") head = self.find_withtag("head") |
Метод find_withtag() позволяет нам найти объект на холсте, используя имя данного тега.
Нам нужно два объекта: голова змеи и яблоко.
Стоит отметить, что даже если присутствует только один объект с указанным тегом, метод вернет кортеж. Это для случая с объектом яблока. Позже объект яблока станет доступен следующим образом: apple[0].
1 2 |
x1, y1, x2, y2 = self.bbox(head) overlap = self.find_overlapping(x1, y1, x2, y2) |
Метод bbox() возвращает точки границ объекта. С помощью метода find_overlapping() мы найдем столкновение объектов с этими координатами.
1 2 3 4 5 6 |
for ovr in overlap: if apple[0] == ovr: x, y = self.coords(apple) self.create_image(x, y, image=self.dot, anchor=NW, tag="dot") self.locateApple() |
Если яблоко сталкивается с головой змеи, то мы создаем новое соединение для змейки на координатах яблока. Также, мы выполняем метод locateApple()
, который удаляет старое яблоко из холста и создает новое в случайной точке на игровой карте.
Метод moveSnake()
устанавливаем механизм управлениями змеей через клавиатуру. Чтобы понять, нужно посмотреть на то, как двигается змейка. Мы управляем ее головой. Мы можем изменить ее направление при помощи клавиш стрелок на клавиатуре. Остальные соединения двигаются за головой как одна единая цепочка, прямо как блокчейн на python который мы создали в другой статье.
1 2 3 4 5 6 |
z = 0 while z < len(items)-1: c1 = self.coords(items[z]) c2 = self.coords(items[z+1]) self.move(items[z], c2[0]-c1[0], c2[1]-c1[1]) z += 1 |
Этот код позволяет соединениям (туловище змеи) двигаться одной цепью.
1 |
self.move(head, self.moveX, self.moveY) |
Этот код меняет направление движения головы змеи. Значение атрибутов класса self.moveX
и self.moveY
меняются после каждого нажатия по стрелкам клавиатуры для изменения траектории движения змеи.
С помощью метода checkColission() мы определяем, когда змея удариться головой об свой хвост либо об стену.
1 2 3 4 5 6 7 |
x1, y1, x2, y2 = self.bbox(head) overlap = self.find_overlapping(x1, y1, x2, y2) for dot in dots: for over in overlap: if over == dot: self.inGame = False |
Игра заканчивается, если голова змеи ударяется об свой хвост.
1 2 |
if y1 > HEIGHT - DOT_SIZE: self.inGame = False |
Игра заканчивается, если змея ударяется о край игрового поля.
Метод locateApple() устанавливает новое яблоко в случайной точке на холсте и удаляет старое яблоко с игровой карты.
1 2 |
apple = self.find_withtag("apple") self.delete(apple[0]) |
В этой части кода мы находим и удаляем яблоко, которое было съедено змейкой.
1 |
r = random.randint(0, Cons.MAX_RAND_POS) |
Мы получаем случайное число от 0 до MAX_RAND_POS
— 1
1 2 3 |
self.appleX = r * Cons.DOT_SIZE ... self.appleY = r * Cons.DOT_SIZE |
Здесь мы устанавливаем x
и y
координаты объекта яблока.
В методе onKeyPressed(), мы реагируем на какие клавиши нажал игрок.
1 2 3 4 5 |
LEFT_CURSOR_KEY = "Left" if key == LEFT_CURSOR_KEY and self.moveX <= 0: self.moveX = -Cons.DOT_SIZE self.moveY = 0 |
Если мы нажали на стрелку влево, то обновляем значения из атрибутов self.moveX
и self.moveY
в соответствии с положением змеи на карте. Эти атрибуты используется в методе moveSnake()
, который изменяет координаты объекта змеи. Стоит отметить, что если змея поворачивается направо, мы не можем сразу же развернуться влево.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def onTimer(self): """ Создает игровой цикл для каждого события таймера """ self.drawScore() self.checkCollisions() if self.inGame: self.checkAppleCollision() self.moveSnake() self.after(Cons.DELAY, self.onTimer) else: self.gameOver() |
После каждого DELAY
запускается метод onTimer(). Если мы в игре, мы запускаем три метода, которые являются основой логики игры. В противном случае игра заканчивается.
Таймер основан на методе after(), который запускает метод после DELAY только однажды. Чтобы повторно запускать таймер, мы рекурсивно запускаем метод onTimer().
1 2 3 4 5 6 7 |
def drawScore(self): """ Рисуем счет игры """ score = self.find_withtag("score") self.itemconfigure(score, text="Счет: {0}".format(self.score)) |
Метод drawScore
рисует счет игры на нашей игровой карте в верхнем левом углу.
1 2 3 4 5 6 7 8 |
def gameOver(self): """ Удаляем все объекты и выводим сообщение об окончании игры. """ self.delete(ALL) self.create_text(self.winfo_width() / 2, self.winfo_height()/2, text="Игра закончилась со счетом {0}".format(self.score), fill="white") |
Если игра закончена, мы удаляем все объекты на холсте. Затем мы выводим по центру экрана финальный счет игрока.
В этой части мы разобрали простую компьютерную игру «змейка», созданную в Tkinter.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»