Программируем игру про астероиды
Пузыри
Давай вернемся к примеру с одним шаром, который летает отталкиваясь от стен.
from tkinter import *
tk = Tk()
c = Canvas(tk, width=640, height=640, bg='white')
c.pack()
# выделяем скорость шарика в переменные
vx = 10
vy = 15
# сохраняем номер соданной фигуры в переменную
my_favorite_oval = c.create_oval(10, 10, 50, 50, fill='blue')
def moveBall():
# используем переменные vx и vy, как глобальные
global vx, vy
# получаем текущие координаты овала
x1, y1, x2, y2 = c.coords(my_favorite_oval)
# если шарик на границе по x - развернуть скорость по x
if x1 <= 0 or x2 >= 640:
vx *= -1
# аналогично по y
if y1 <= 0 or y2 >= 640:
vy *= -1
# передвигаем наш овал на скорость
c.move(my_favorite_oval, vx, vy)
# повторяем через полсекунды
c.after(50, moveBall)
# спустя полсекунды (50 мс) после запуска выполнить moveBall
c.after(50, moveBall)
mainloop()
Давай применим списки, чтобы сделать сразу 10 шаров. Что потребуется сделать?
- Завести по список с шарами и списки для двух скоростей.
- Заполнить списки 10 случайными числами.
- Написать цикл для перемещения шаров.
Реагируем на нажатия клавиш
Для начала наш главный герой - это прямоугольник, который будет управляться с клавиатуры. Обработка нажатий клавиш работает по тому же принципу, что и кнопки - через дополнительную функцию. Мы можем связать нажатие клавиши на клавиатуре с вызовом нашей функции.
from tkinter import *
tk = Tk()
c = Canvas(tk, width=640, height=640, bg='white')
c.pack()
# создаем прямоульник
player = c.create_rectangle(300, 300, 340, 340, fill='green')
def move_player(key):
# если нажали a
if key.char == 'a':
# сдвигаем прямоугольник влево
c.move(player, -20, 0)
# если нажали d
if key.char == 'd':
# сдвигаем прямоугольник вправо
c.move(player, 20, 0)
# если нажали w
if key.char == 'w':
# сдвигаем прямоугольник вверх
c.move(player, 0, -20)
# если нажали s
if key.char == 's':
# сдвигаем прямоугольник вниз
c.move(player, 0, 20)
# при нажатии любой клавишы вызываем keyDown
tk.bind("<KeyPress>", move_player)
mainloop()
Добавляем графику
Вместо овалов и прямоугольников можно использовать картинки в формате gif (это ограничение библиотеки tkinter). Просто вместо create_oval
можно использовать функцию create_image
. Но есть два нюанса: во-первых картинку нужно сначала загрузить в память с помощью функции PhotoImage(путь к картинке)
, а во-вторых - у картинки не четыре координаты, две.
Перед использованием выбранные картинки нужно конвертировать в gif и подогнать под нужный нам размер (у нас - 40 на 40 пикселей). Вот что получилось у меня:
from tkinter import *
import random
tk = Tk()
c = Canvas(tk, width=640, height=640, bg='white')
c.pack()
# загружаем картинки - астероид, небо и игрок
asteroid_image = PhotoImage(file='asteroid.gif')
player_image = PhotoImage(file='player.gif')
sky_image = PhotoImage(file='sky.gif')
# рисуем небо
c.create_image(0, 0, image=sky_image, anchor=NW)
# количество шаров
N = 10
# списки со скоростями
x_speeds = []
y_speeds = []
# список с фигурами
asteroids = []
# создаем N астероидов
for i in range(N):
# придумываем координаты
x = random.randint(20, 610)
y = random.randint(20, 610)
# создаем астероид - теперь это картинка на базе asteroid_image
asteroid = c.create_image(x, y, image=asteroid_image, anchor=NW)
# придумываем скорость
vx = random.randint(-15, 15)
vy = random.randint(-15, 15)
# добавляем астероид в список
asteroids.append(asteroid)
# добавляем скорости в списки
x_speeds.append(vx)
y_speeds.append(vy)
# создаем игрока на основе картинки
player = c.create_image(300, 300, image=player_image, anchor=NW)
def move_ball():
# для каждого астероида из N
for i in range(N):
# берем номер астероида c номером i из списка
asteroid = asteroids[i]
# и его скорости
vx = x_speeds[i]
vy = y_speeds[i]
# получаем текущие координаты астероида
# поскольку у картинки в отличие от овала только две координаты (левого верхнего угла),
# мы сами вычисляем правую нижнюю координату
x1, y1 = c.coords(asteroid)
x2 = x1 + 40
y2 = y1 + 40
# если астероид на границе по x - развернуть скорость по x
if x1 <= 0 or x2 >= 640:
vx *= -1
# аналогично по y
if y1 <= 0 or y2 >= 640:
vy *= -1
# сохраняем скорости обратно в список
x_speeds[i] = vx
y_speeds[i] = vy
# передвигаем астероид
c.move(asteroid, vx, vy)
# повторяем через полсекунды
c.after(50, move_ball)
# спустя полсекунды (50 мс) после запуска выполнить moveBall
c.after(50, move_ball)
def move_player(key):
# если нажали a
if key.char == 'a':
# сдвигаем прямоугольник влево
c.move(player, -20, 0)
# если нажали d
if key.char == 'd':
# сдвигаем прямоугольник вправо
c.move(player, 20, 0)
# если нажали w
if key.char == 'w':
# сдвигаем прямоугольник вверх
c.move(player, 0, -20)
# если нажали s
if key.char == 's':
# сдвигаем прямоугольник вниз
c.move(player, 0, 20)
# при нажатии любой клавишы вызываем keyDown
tk.bind("<KeyPress>", move_player)
mainloop()
Добавляем геймплей
Чтобы в игру можно было по-настоящему поиграть, нужно уменьшать жизнь при столкновении с астероидами и засекать время жизни. Но последний этап я по традиции оставлю тебе.
Нужно добавить в самый верх окна поле с временем жизни и проверять каждый астероид на столкновение с игроком. Чтобы немного упростить задачу, я выделил места, которые нужно доделать, комментарией TODO:
.
А как обнаруживать столкновения? Самый простой способ - просто считать, что все объекты - это квадраты, тогда задача сводится к пересечению двух квадратов. Небольшая подсказка на картинке ниже, условие довольное большое, но легко записывается с помощью скобочек и операций or
и and
.
from tkinter import *
import random
window = Tk()
# добавляем статус бар для счета вверху экрана
state_label = StringVar()
Label(window, textvariable=state_label).pack()
c = Canvas(window, width=640, height=640, bg='white')
c.pack()
# загружаем картинки - астероид, небо и игрок
asteroid_image = PhotoImage(file='asteroid.gif')
player_image = PhotoImage(file='player.gif')
sky_image = PhotoImage(file='sky.gif')
# рисуем небо
c.create_image(0, 0, image=sky_image, anchor=NW)
# количество шаров
N = 10
# списки со скоростями
x_speeds = []
y_speeds = []
# список с фигурами
asteroids = []
# создаем N астероидов
for i in range(N):
# придумываем координаты
x = random.randint(20, 610)
y = random.randint(20, 610)
# создаем астероид - теперь это картинка на базе asteroid_image
asteroid = c.create_image(x, y, image=asteroid_image, anchor=NW)
# придумываем скорость
vx = random.randint(-15, 15)
vy = random.randint(-15, 15)
# добавляем астероид в список
asteroids.append(asteroid)
# добавляем скорости в списки
x_speeds.append(vx)
y_speeds.append(vy)
# создаем игрока на основе картинки
player = c.create_image(300, 300, image=player_image, anchor=NW)
# переменная для хранения жизни
life = 3
# длительности игры
game_time = 0
# выводим стартовое время вверху экрана
state_label.set("Время в игре: " + str(game_time))
def move_ball():
# TODO: получить левую верхнюю коодинату игрока
player_x1, player_y1 = ...
# TODO: вычислить правую нижнюю координату игрока
player_x2, player_y2 = ...
# для каждого астероида из N
for i in range(N):
# берем номер астероида c номером i из списка
asteroid = asteroids[i]
# и его скорости
vx = x_speeds[i]
vy = y_speeds[i]
# получаем текущие координаты астероида
# поскольку у картинки в отличие от овала только две координаты (левого верхнего угла),
# мы сами вычисляем правую нижнюю координату
x1, y1 = c.coords(asteroid)
x2 = x1 + 40
y2 = y1 + 40
# TODO: если астероид пересекается с игроком - уменьшить жизнь
# TODO: если жизнь на нуле - закрыть игру tk.destroy()
# если астероид на границе по x - развернуть скорость по x
if x1 <= 0 or x2 >= 640:
vx *= -1
# аналогично по y
if y1 <= 0 or y2 >= 640:
vy *= -1
# сохраняем скорости обратно в список
x_speeds[i] = vx
y_speeds[i] = vy
# передвигаем астероид
c.move(asteroid, vx, vy)
# повторяем через полсекунды
c.after(50, move_ball)
# TODO: увеличиваем время жизни на 0.05
# TODO: обновляем поле вверху экрана
# спустя полсекунды (50 мс) после запуска выполнить moveBall
c.after(50, move_ball)
def move_player(key):
# если нажали a
if key.char == 'a':
# сдвигаем прямоугольник влево
c.move(player, -20, 0)
# если нажали d
if key.char == 'd':
# сдвигаем прямоугольник вправо
c.move(player, 20, 0)
# если нажали w
if key.char == 'w':
# сдвигаем прямоугольник вверх
c.move(player, 0, -20)
# если нажали s
if key.char == 's':
# сдвигаем прямоугольник вниз
c.move(player, 0, 20)
# при нажатии любой клавишы вызываем keyDown
window.bind("<KeyPress>", move_player)
mainloop()