В этой части обучения Tkinter мы создадим клона игры Nibbles (змейка). Nibbles – это первоначальная версия классической змейки. Впервые ее создали в конце 70х годов. Позже она была перенесена на ПК. В этой игре игрок управляет змейкой. Цель игры – съесть как можно больше яблок. После каждого съеденного яблока тело змеи увеличивается. Во время движения змейка должна уклоняться от стен и собственного тела.
Разработка
Размер каждого соединения змейки – 10 пикселей. Змейка управляется стрелками на клавиатуре. Изначально у змейки есть всего три соединения. Игра начинается мгновенно. Когда игра заканчивается, на экране появляется надпись «Game Over». Чтобы создать игру, мы создаем виджет 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 |
#!/usr/bin/python # -*- coding: utf-8 -*- import sys import random from PIL import Image, ImageTk from Tkinter import Tk, Frame, Canvas, ALL, NW WIDTH = 300 HEIGHT = 300 DELAY = 100 DOT_SIZE = 10 ALL_DOTS = WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE) RAND_POS = 27 x = [0] * ALL_DOTS y = [0] * ALL_DOTS class Board(Canvas): def __init__(self, parent): Canvas.__init__(self, width=WIDTH, height=HEIGHT, background="black", highlightthickness=0) self.parent = parent self.initGame() self.pack() def initGame(self): self.left = False self.right = True self.up = False self.down = False self.inGame = True self.dots = 3 self.apple_x = 100 self.apple_y = 190 for i in range(self.dots): x[i] = 50 - i * 10 y[i] = 50 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, e: print e sys.exit(1) self.focus_get() self.createObjects() self.locateApple() self.bind_all("<Key>", self.onKeyPressed) self.after(DELAY, self.onTimer) def createObjects(self): self.create_image(self.apple_x, self.apple_y, 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 checkApple(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: x, y = self.coords(apple) self.create_image(x, y, image=self.dot, anchor=NW, tag="dot") self.locateApple() def doMove(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 if self.left: self.move(head, -DOT_SIZE, 0) if self.right: self.move(head, DOT_SIZE, 0) if self.up: self.move(head, 0, -DOT_SIZE) if self.down: self.move(head, 0, DOT_SIZE) 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 > WIDTH - DOT_SIZE: self.inGame = False if y1 < 0: self.inGame = False if y1 > HEIGHT - DOT_SIZE: self.inGame = False def locateApple(self): apple = self.find_withtag("apple") self.delete(apple[0]) r = random.randint(0, RAND_POS) self.apple_x = r * DOT_SIZE r = random.randint(0, RAND_POS) self.apple_y = r * DOT_SIZE self.create_image(self.apple_x, self.apple_y, anchor=NW, image=self.apple, tag="apple") def onKeyPressed(self, e): key = e.keysym if key == "Left" and not self.right: self.left = True self.up = False self.down = False if key == "Right" and not self.left: self.right = True self.up = False self.down = False if key == "Up" and not self.down: self.up = True self.right = False self.left = False if key == "Down" and not self.up: self.down = True self.right = False self.left = False def onTimer(self): if self.inGame: self.checkCollisions() self.checkApple() self.doMove() self.after(DELAY, self.onTimer) else: self.gameOver() def gameOver(self): self.delete(ALL) self.create_text(self.winfo_width()/2, self.winfo_height()/2, text="Game Over", fill="white") class Nibbles(Frame): def __init__(self, parent): Frame.__init__(self, parent) parent.title('Nibbles') self.board = Board(parent) self.pack() def main(): root = Tk() nib = Nibbles(root) root.mainloop() if __name__ == '__main__': main() |
В первую очередь, мы расшифруем некоторые переменные, использующиеся в нашей игре:
- WIDTH и HEIGHT обозначают размер игрового поля.
- DELAY – скорость игры.
- DOT_SIZE — размер яблока и соединения змейки.
- ALL_DOTS – максимальное количество точек (соединений змейки и яблок) на игровом поле.
- RAND_POS – создание случайной точки для яблока.
1 2 |
x = [0] * ALL_DOTS y = [0] * ALL_DOTS |
Эти два массива сохраняют x и y координаты всех возможных соединений змейки.
Метод iniGame() инициализирует переменные, загружает изображения и запускает функцию таймера.
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, e: print e sys.exit(1) |
В этих строчках показана загрузка наших изображений. Это три изображения для нашей игры: голова, соединение и яблоко.
1 2 |
self.createObjects() self.locateApple() |
Метод createObject() создает объекты на холсте, а locateApple() устанавливает яблоко в случайную точку на холсте.
1 |
self.bind_all("<Key>", self.onKeyPressed) |
Мы назначаем клавиши клавиатуры при помощи метода onKeyPressed(). Для управления в игре используются стрелки на клавиатуре.
1 2 3 4 5 6 |
def createObjects(self): self.create_image(self.apple_x, self.apple_y, 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() мы создаем игровые объекты на холсте. Это объекты холста. У них есть стартовые x и y координаты. Параметр image позволяет изображениям появиться на холсте. Параметр anchor установлен на NW (Север и Запад), что означает ориентир на верхнюю левую точку холста. И этот шаг очень важен, если мы хотим отображать изображения около границ корневого окна. Попробуйте удалить anchor и посмотрите, что произойдет. Параметр tag используется для идентификации объектов на холсте.
Вы можете прямо сейчас купить дешевых подписчиков на YouTube с помощью сервиса doctorsmm.com. Тут Вы найдете не только вкусный прайс, но и разнообразные критерии к каждой услуге, которые подойдут любому каналу. Кроме того, Вы можете получить большинство услуг со стабильной гарантией качества и защитой от списаний.
Один тег может использоваться для нескольких объектов на холсте.
Метод checkApple() позволяет нам узнать, съела ли змея объект яблоко. Если да, мы добавляем одно соединение змейке и запускаем 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(), который удаляет старое яблоко из холста и создает новое в случайной точке.
Метод doMove() включает игровые алгоритмы клавиш. Чтобы понять их, нужно посмотреть на то, как двигается змейка. Мы управляем ее головой. Мы можем изменить ее направление при помощи клавиш стрелок на клавиатуре. Остальные соединения двигаются за головой как одна единая цепочка.
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 2 |
if self.left: self.move(head, -DOT_SIZE, 0) |
Этот код показывает движение головы змеи влево.
С помощью метода 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, RAND_POS) |
Мы получаем случайное число от 0 до RAND_POS -1
1 2 3 |
self.apple_x = r * DOT_SIZE ... self.apple_y = r * DOT_SIZE |
Здесь мы устанавливаем x и y координаты объекта яблока.
Метод onKeyPressed(), применяемы на классе Board (игрового поля), позволяет нам определить, какие клавиши были нажаты.
1 2 3 4 |
if key == "Left" and not self.right: self.left = True self.up = False self.down = False |
Если мы нажали на левый курсор, переменная «лево» изменяется на True. Эта переменная используется в методе doMove(), который изменяет координаты объекта змеи. Стоит отметить, что если змея поворачивается направо, мы не можем сразу же развернуться влево.
1 2 3 4 5 6 7 8 9 |
def onTimer(self): if self.inGame: self.checkCollisions() self.checkApple() self.doMove() self.after(DELAY, self.onTimer) else: self.gameOver() |
После каждого DELAY запускается метод onTimer(). Если мы в игре, мы запускаем три метода, которые являются основой логики игры. В противном случае игра заканчивается. Таймер основан на методе after(), который запускает метод после DELAY только однажды. Чтобы повторно запускать таймер, мы рекурсивно запускаем метод onTimer().
1 2 3 4 5 |
def gameOver(self): self.delete(ALL) self.create_text(self.winfo_width()/2, self.winfo_height()/2, text="Game Over", fill="white") |
Если игра закончена, мы удаляем все объекты на холсте. Затем мы выводим на холсте «Game Over» в центре экрана.
В этой части мы разобрали простую компьютерную игру «змейка», созданную в Tkinter.