Создание игры с нуля на Tkinter Python - Земейка

В этой части изучения Tkinter мы создадим клон игры Nibbles (змейка). Nibbles – это первоначальная версия классической змейки. Впервые ее создали в конце 70х годов. Позже она была перенесена на ПК. В этой игре игрок управляет змейкой. Цель игры – съесть как можно больше яблок. После каждого съеденного яблока тело змеи увеличивается. Во время движения змейка должна уклоняться от стен и собственного тела.

Содержание курса

  1. Создание окна по центру и кнопка выхода в Tkinter
  2. Разметка виджетов в Tkinter — pack, grid и place
  3. Виджеты Checkbutton, Label, Scale и Listbox в Tkinter
  4. Меню, подменю и панель инструментов в Tkinter
  5. Диалоговые окна в Tkinter — Выбор цвета — Выбор файла
  6. Рисуем линии, прямоугольники, круг и текст в Tkinter
  7. Пишем игру змейка на 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() инициализирует переменные, загружает изображения игры и запускает функцию таймера.

self.createObjects()

self.locateApple()

Метод createObject() создает объекты на холсте, а locateApple() устанавливает яблоко на случайную точку на холсте.

self.bind_all("<Key>", self.onKeyPressed)

Мы назначаем клавиши клавиатуры при помощи метода onKeyPressed(). Для управления замеей в игре используются стрелки на клавиатуре.

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?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по 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.

Создание игр на Python

Попробуйте удалить anchor и посмотрите, что произойдет. Параметр tag используется для идентификации объектов на холсте.

Вы можете прямо сейчас купить дешевых подписчиков на YouTube с помощью сервиса doctorsmm.com. Тут Вы найдете не только вкусный прайс, но и разнообразные критерии к каждой услуге, которые подойдут любому каналу. Кроме того, Вы можете получить большинство услуг со стабильной гарантией качества и защитой от списаний.

Один тег может использоваться для нескольких объектов на холсте.

Метод checkAppleCollision() позволяет нам узнать, съела ли змея яблоко или нет. Если да, мы добавляем одно соединение змейке и выполняем метод locateApple() для создания нового яблока на нашей игровой карте.

apple = self.find_withtag("apple")

head = self.find_withtag("head")

Метод find_withtag() позволяет нам найти объект на холсте, используя имя данного тега.

Нам нужно два объекта: голова змеи и яблоко.

Стоит отметить, что даже если присутствует только один объект с указанным тегом, метод вернет кортеж. Это для случая с объектом яблока. Позже объект яблока станет доступен следующим образом: apple[0].

x1, y1, x2, y2 = self.bbox(head)

overlap = self.find_overlapping(x1, y1, x2, y2)

Метод bbox() возвращает точки границ объекта. С помощью метода find_overlapping() мы найдем столкновение объектов с этими координатами.

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 который мы создали в другой статье.

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)

Этот код меняет направление движения головы змеи. Значение атрибутов класса self.moveX и self.moveY меняются после каждого нажатия по стрелкам клавиатуры для изменения траектории движения змеи.

С помощью метода checkColission() мы определяем, когда змея удариться головой об свой хвост либо об стену.

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 y1 > HEIGHT - DOT_SIZE:

    self.inGame = False

Игра заканчивается, если змея ударяется о край игрового поля.

Метод locateApple() устанавливает новое яблоко в случайной точке на холсте и удаляет старое яблоко с игровой карты.

apple = self.find_withtag("apple")

self.delete(apple[0])

В этой части кода мы находим и удаляем яблоко, которое было съедено змейкой.

r = random.randint(0, Cons.MAX_RAND_POS)

Мы получаем случайное число от 0 до MAX_RAND_POS — 1

self.appleX = r * Cons.DOT_SIZE

...

self.appleY = r * Cons.DOT_SIZE

Здесь мы устанавливаем x и y координаты объекта яблока.

В методе onKeyPressed(), мы реагируем на какие клавиши нажал игрок.

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(), который изменяет координаты объекта змеи. Стоит отметить, что если змея поворачивается направо, мы не можем сразу же развернуться влево.

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().

def drawScore(self):

    """

    Рисуем счет игры

    """

    score = self.find_withtag("score")

    self.itemconfigure(score, text="Счет: {0}".format(self.score))

Метод drawScore рисует счет игры на нашей игровой карте в верхнем левом углу.

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 Игра

В этой части мы разобрали простую компьютерную игру «змейка», созданную в Tkinter.

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: vasile.buldumac@ati.utm.md

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»