Не знаете, чем заняться на новогодних праздниках? Предлагаем отложить нарезку оливье и взяться за новый проект — написать свою игру для тачбара. В тексте показываем, как это сделать на Python всего за час.
Вот что получится, если будете следовать инструкции.
С 2016 года у некоторых моделей MacBook Pro есть сенсорная OLED-панель. Она заменяет функциональные клавиши, закладки и элементы настроек.
Для программирования тачбара не обязательно погружаться в Swift и AppKit — достаточно знания Python и библиотеки PyTouchBar. С последним познакомимся подробней: напишем игру с динозавриком и освоим встроенные инструменты модуля.
Используйте навигацию, если нужно изучить конкретные шаги, а не читать инструкцию целиком:
- Делаем меню и знакомимся с элементами библиотеки
- Загружаем игровую сцену из кактусов
- Заставляем динозаврика бежать
- Выводим количество очков
- На что еще способна библиотека?
Делаем меню и знакомимся с элементами библиотеки
Кнопка «играть»
PyTouchBar работает в связке с Tkinter. Для начала нужно установить и первый, и второй модули. А после — подготовить GUI-окно, которое будет отображаться на тачбаре. И добавить, например, кнопку «играть».
from tkinter import *
import PyTouchBar
# создание окна Tkinter
root = Tk()
# параметризация кнопки
starter = PyTouchBar.TouchBarItems.Button(title=’играть’, color=(PyTouchBar.Color.green))
# добавление элемента
PyTouchBar.set_touchbar([starter])
# подготовка окна
PyTouchBar.prepare_tk_windows(root)
root.mainloop()
После запуска программы кнопка отобразится на панели.
Для окрашивания кнопки можно использовать встроенную константу PyTouchBar.Color. Или передать в функцию кортеж (r, g, b, a), где r, g, b, a — значения от 0 до 1.
Если нажать на кнопку, ничего не произойдет. Для запуска подпрограммы нужно добавить аргумент action и сослаться на имя функции.
# PyTouchBar, добавление функции для action.
…
def start(button):
# здесь будет запуск игровой сцены game
print(‘Hello world’)
starter = PyTouchBar.TouchBarItems.Button(title=’играть’, color=(PyTouchBar.Color.green), action=start)
…
Подпрограмма start нужна для запуска игровой сцены game.
Настройка скорости динозаврика
Библиотека поддерживает не только простые кнопки, но и так называемые «степперы» — с помощью них можно вводить числовые значения. В нашем случае — скорость динозаврика.
# PyTouchBar, добавление степпера.
…
# минимальная скорость
speed = 2
def set_speed(stepper):
global speed
size = int(speed.value)
print (‘Скорость динозавра:’, speed)
speed_p = PyTouchBar.TouchBarItems.Stepper(min = 2, max = 7, action = set_speed) # параметризация степпера
# добавление кнопки и степпера
PyTouchBar.set_touchbar([speed_p, starter])
…
После запуска программы элементы отобразятся на панели.
На самом деле PyTouchBar поддерживает больше элементов. Некоторые из них мы добавим в игру ниже. Полный список ищите в официальной документации.
Загружаем игровую сцену из кактусов
Представление 2D-сцены
После нажатия кнопки «играть» должна сработать специальная функция game, которая нужна для отрисовки сцены — динозаврика и кактусов. Один из вариантов — добавить их с помощью кнопок, которые мы будем называть чанками.
Каждый чанк — целочисленное значение:
- -2 — динозавр врезался в кактус,
- -1 — кактус,
- 0 — плоскость,
- 1 — динозаврик,
- 2 — динозавр перепрыгнул через кактус.
Карту можно представить так:
map = [1, 0, 0, -1, 0, -1, 0]
Визуализация элементов
Кнопки поддерживают параметр image: на фон можно поставить любое статическое изображение.
Так, например, можно «пройтись» циклом по списку map и наполнить список buttons кнопками с изображениями, выбранными по значениям чанков.
# Отрисовка карты map с помощью кнопок.
…
for chunk in range(len(map)):
if map[chink] == 1:
buttons[chunk] = PyTouchBar.TouchBarItems.Button(image=’assets/trex.png’, color=(PyTouchBar.Color.black), action=jumping)
elif map[chunk]== 0:
buttons[chunk] = PyTouchBar.TouchBarItems.Button(image=’assets/platform.png’, color=(PyTouchBar.Color.black))
else:
buttons[chunk] = PyTouchBar.TouchBarItems.Button(image=’assets/cactus.png’, color=(PyTouchBar.Color.black))
…
В результате схема карты преобразится в игровую сцену.
Что нужно учитывать
Для запуска сцены нужно закрыть root-окно и запустить новое. PyTouchBar не поддерживает обновление Tkinter-программ.
def game(map):
# отрисовка карты map
…
def prepare():
root = Tk()
def start(button): # запускается по клику кнопки «играть»
root.destroy()
…
# генерируем карту и передаем в функцию игровой сцены
game(map)
…
PyTouchBar.set_touchbar([starter])
PyTouchBar.prepare_tk_windows(root)
root.mainloop()
prepare()
Но как тогда заставить динозаврика бежать?
Заставляем динозаврика бежать
Чтобы динозаврик побежал, нужно как-то обновлять сцену без root.update(). То есть уничтожать настоящую сцену с помощью root.destroy() и запускать новую с обновленными параметрами map. Получается некое обновление кадров.
Удаление старых кадров
Автоматизировать удаление старых кадров с помощью root.destroy() можно с помощью встроенного метода root.after(). Он умеет запускать отложенные функции.
# root.after(), пример вызова метода destroy.
…
# после запуска Tkinter удалит кадр через 1 секунду
root.after(1000, start.destroy)
root.mainloop()
…
1000 — время, спустя которое удалится root-окно. Чем меньше значение, тем быстрее бежит динозаврик.
Создание новых кадров
Сам динозаврик не двигается. Обновляется только задний план — ландшафт сцены.
По сути, генерировать новые кадры можно разными способами. Выберем один из самых простых: будем делать срез списка на один элемент и добавлять рандомный чанк в конец. А после — запускать на новой карте игровую сцену game.
# Генерация новых кадров. Одна итерация — один новый кадр и чанк.
…
# бесконечная генерация новых чанков и кадров
while True:
new_map = map[1:]
# первый чанк — всегда динозавр (1)
new_map[0] = 1
# условие, чтобы не генерировать два кактуса подряд
if map[-1] == 0:
new_map.append(random.choice([0, -1]))
else:
new_map.append(0)
game(new_map)
…
На тачбаре это выглядит вот так:
Покадровое обновление игровой сцены.
Для большей плавности нужно создать ассеты для промежуточных кадров. И добавить в игровую сцену дополнительный цикл при отрисовке чанков.
Добавление событий
Предпоследний этап — то, ради чего играют в Google-динозаврика, — «паркур по кактусам».
Чтобы добавить прыжок, нужно завести переменную jump — к ней мы будем прибавлять единицу при нажатии на кнопку с динозавром. И модифицировать список map таким образом, чтобы в первом чанке кактус и динозавр могли встретиться несколькими способами.
→ Динозаврик врезался в кактус — чанк -2. Комбинация [1, -1], jump = 0.
→ Динозаврик перепрыгнул кактус — чанк 2. Комбинация [1, -1], jump = 1.
Вот как «сценарии» записаны в программе:
# Ситуативная генерация новых кадров.
…
jump = 0
# первый элемент — комбинация значений, где второе значение — следующий чанк
map = [[1,0], 0, 0, -1, 0, -1, 0]
…
while True:
# итоговое значение для комбинации:
# [1,0] → 1 кактуса нет, динозаврик бежит
# [1,-1] → кактус есть: если jump = 1, все хорошо
zero_chunk = 1
# записываем комбинацию
map[0] = [1,map[1]]
# динозаврик не прыгнул на прошлом чанке и врезался
if jump == 0 and map[0] == [1, -1]:
zero_chunk = -2
# на чанке без кактуса jump обнуляется
elif jump >= 1 and map[0] == [1, 0]:
jump = 0
zero_chunk = 1
# динозаврик прыгнул на прошлом чанке, все хорошо
elif jump == 1 and map[0] == [1,-1]:
zero_chunk = 2
points += 1
# настраиваем формат карты для передачи в функцию
new_map = map[1:]
new_map[0] = zero_chunk
if map[-1] == 0:
new_map.append(random.choice([0, -1]))
else:
new_map.append(0)
map = new_map
game(new_map)
В коде отрисовки сцены также нужно добавить новые события:
# Отрисовка игровой сцены.
def game(buttons)
…
for b in range(len(map)):
# если динозаврик не перепрыгнул, загружаем ассет со столкновением
if map[b] == -2:
buttons[b] = PyTouchBar.TouchBarItems.Button(image=’assets/loss.png’, color=(PyTouchBar.Color.black))
# если динозаврик перепрыгнул, загружаем ассет с прыжком
elif map[b] == 2:
buttons[b] = PyTouchBar.TouchBarItems.Button(image=’assets/lucky.png’, color=(PyTouchBar.Color.black))
…
На тачбаре это выглядит вот так:
Итоговый результат.
Выводим количество очков
Если динозаврик все же врезался, игру необходимо завершить: показать игроку количество набранных очков и сообщение.
Подсчет очков
Для подсчета очков нужно завести переменную points и прибавлять к ней единицу каждый раз, когда динозаврик перепрыгивает через кактус. Но с выводом сообщения немного сложней.
Вывод сообщения
Чтобы вывести сообщение, нужно создать новое окно Tkinter, подготовить его и добавить текстовое поле.
# PyTouchBar, добавление элемента Label.
def finish(points):
…
# объявление элемента label
label = PyTouchBar.TouchBarItems.Label(text = f’Упс:( Вы набрали: {points}’)
…
Элемент Label поддерживает разные шрифты, цвета и масштабы. Полный список параметров есть в официальной документации.
Притом вызвать функцию finish нужно после отрисовки сцены.
# Обработка окончания игры.
while True:
points = 0
…
# динозаврик не прыгнул на прошлом чанке и врезался
if jump == 0 and map[0] == [1, -1]:
zero_chunk = -2
elif jump == 1 and map[0] == [1, -1]:
zero_chunk = 2
points += 1
…
game(new_map)
# если динозаврик врезался — вызвать finish()
if zero_chunk == -2:
finish(points)
Как закрыть сообщение и программу?
У PyTouchBar есть особенность: с помощью библиотеки можно модифицировать встроенную кнопку escape. Например, сделать ее кнопкой для закрытия программы.
…
def exit_f(button):
exit()
esc = PyTouchBar.TouchBarItems.Button(title = «exit», action = exit_f)
PyTouchBar.set_touchbar(… , esc_key = esc)
…
Полная версия кода доступна на GitHub. Подключайтесь и предлагайте свои улучшения.
На что еще способна библиотека?
Даже с ограничениями библиотека кажется интересной. С помощью нее можно написать софт для решения повседневных задач. Например, сделать дополнительный ряд кнопок для всяких спецсимволов, если надоела раскладка Бирмана.
Но Doom с помощью PyTouchBar запустить будет сложно: не понятно, как «вытягивать» отдельные кадры и управлять игрой через панель. Для более сложных проектов лучше «прыгнуть в нору за кроликом» и программировать тачбар с помощью Objective-C.
Напишите в комментариях, какую еще программу или игру можно написать для тачбара. И подпишитесь на блог Selectel, чтобы не пропустить обзоры, новости, кейсы и полезные гайды из мира IT.
Читайте также:
- Как я локализовал игру в слова с ИИ: делюсь инструкцией и ссылкой для тестов
- Какой тип облака выбрать: частное, публичное или гибридное? Объясняем на рыбах и удочках
- Как начать работать с крупным бизнесом? Советы разработчикам SaaS
Время на прочтение
8 мин
Количество просмотров 5.1K
С 2016 года у некоторых моделей MacBook Pro есть сенсорная OLED-панель. По сути, она просто заменяет функциональные клавиши. Но с ней чуть интересней: на тачбар можно вывести закладки и даже медиаэлементы.
На примере игры с динозавриком показываю, как написать свою программу для тачбара с помощью открытой библиотеки PyTouchBar.
В конце статьи — конкурс на плюшевого тирекса.
Меню игры: основные элементы библиотеки
Кнопка «играть». PyTouchBar работает в связке с Tkinter. Для начала нужно установить и первый, и второй модули. А после — подготовить GUI-окно, которое будет отображаться на тачбаре. И добавить, например, кнопку «играть».
from tkinter import *
import PyTouchBar
# создание окна Tkinter
root = Tk()
# параметризация кнопки
starter = PyTouchBar.TouchBarItems.Button(title='играть', color=(PyTouchBar.Color.green))
# добавление элемента
PyTouchBar.set_touchbar([starter])
# подготовка окна
PyTouchBar.prepare_tk_windows(root)
root.mainloop()
Подготовка, добавление кнопки.
После запуска программы кнопка отобразится на панели.
Для окрашивания кнопки я использую встроенную константу PuTouchBar.Color, хотя то же самое можно сделать через rgba. Для этого в функцию нужно передать кортеж формата
(r, g, b, a)
, где r, g, b, a — значения от 0 до 1.
Если нажать на кнопку, ничего не произойдет. Для запуска подпрограммы нужно добавить аргумент action
и сослаться на имя функции.
...
def start(button):
# здесь будет запуск игровой сцены game
print('Hello world')
starter = PyTouchBar.TouchBarItems.Button(title='играть', color=(PyTouchBar.Color.green), action=start)
...
Добавление функции для action кнопки.
Подпрограмма start
нужна для запуска игровой сцены game
.
Настройка скорости динозаврика. Библиотека поддерживает не только простые кнопки, но и так называемые «степперы» — с помощью них можно вводить числовые значения. В нашем случае — скорость динозаврика.
...
# минимальная скорость
speed = 2
def set_speed(stepper):
global speed
speed = int(stepper.value)
# параметризация степпера
speed_p = PyTouchBar.TouchBarItems.Stepper(min = 2, max = 8, action = set_speed)
# добавление кнопки и степпера
PyTouchBar.set_touchbar([speed_p, starter])
...
Добавление степпера.
После запуска программы элементы отобразятся на панели.
На самом деле PyTouchBar поддерживает больше элементов. Среди них — палитры, текстовые лейблы, слайдеры и другие. Некоторые из них мы добавим в игру ниже. С полным списком можно ознакомиться в официальной документации.
Статическая сцена: загрузка карты и ассетов
Представление 2D-сцены. После нажатия кнопки «играть» должна сработать специальная функция game
, которая нужна для отрисовки сцены — динозаврика и кактусов. Их я тоже добавил с помощью кнопок, которые условно называю чанками.
Каждый чанк — целочисленное значение:
- -2 — динозавр врезался в кактус,
- -1 — кактус,
- 0 — плоскость,
- 1 — динозаврик,
- 2 — динозавр перепрыгнул через кактус.
Карту можно представить так:
map = [1, 0, 0, -1, 0, -1, 0]
Визуализация элементов. Кнопки поддерживают параметр image
: на фон можно поставить любое статическое изображение.
Так, например, можно «пройтись» циклом по map
и наполнить список buttons
кнопками с изображениями, выбранными по значениям чанков.
for chunk in range(len(map)):
if map[chunk] == 1:
buttons[chunk] = PyTouchBar.TouchBarItems.Button(image='assets/trex.png', color=(PyTouchBar.Color.black), action=jumping)
elif map[chunk] == 0:
buttons[chunk] = PyTouchBar.TouchBarItems.Button(image='assets/platform.png', color=(PyTouchBar.Color.black))
else:
buttons[chunk] = PyTouchBar.TouchBarItems.Button(image='assets/cactus.png', color=(PyTouchBar.Color.black))
Отрисовка карты map с помощью кнопок.
В результате схема карты преобразится в игровую сцену.
Представление игровой сцены.
Что нужно учитывать. Для запуска сцены нужно закрыть root-окно и запустить новое. PyTouchBar не поддерживает обновление Tkinter-программ.
def game(map):
# создание нового root-окна
root = Tk()
# отрисовка карты map
...
# запуск нового root-окна
root.mainloop()
def prepare():
root = Tk()
def start(button): # запускается по клику кнопки «играть»
root.destroy()
...
# генерируем карту и передаем в функцию игровой сцены
game(map, speed)
...
PyTouchBar.set_touchbar([starter])
PyTouchBar.prepare_tk_windows(root)
root.mainloop()
prepare()
Переключение между окнами.
Но как тогда заставить динозаврика бежать?
Динамическая сцена: работа с кадрами
Чтобы динозаврик побежал, нужно как-то обновлять сцену без root.update()
. То есть уничтожать настоящую сцену с помощью root.destroy()
и запускать новую с обновленными параметрами map
. Получается некое обновление кадров.
Удаление старых кадров. Автоматизировать удаление старых кадров с помощью root.destroy()
можно несколькими способами.
- Использовать запаздывание time. Можно запустить отдельный «поток», который будет периодически «заглядывать» в основной и удалять root-окно. А отсчет времени реализовать, например, на базе time.sleep().
- Использовать встроенный метод root.after(). С помощью него я удаляю старые кадры.
...
# после запуска Tkinter удалит кадр через 1 секунду при speed = 2
root.after(1200 - speed*100, root.destroy)
root.mainloop()
...
root.after(), пример вызова метода destroy.
Первый аргумент — время, спустя которое удалится root-окно. С помощью него можно установить скорость игры: чем быстрее обновляются кадры, тем быстрее бежит динозаврик.
Создание новых кадров. По сути, генерировать новые кадры можно разными способами. Я выбрал один из самых простых: делаю срез списка map
на один элемент и добавляю рандомный чанк в конец. А после — запускаю на новой карте игровую сцену game.
# бесконечная генерация новых чанков и кадров
while True:
new_map = map[1:]
# первый чанк — всегда динозавр (1)
new_map[0] = 1
# условие, чтобы не генерировать два кактуса подряд
if map[-1] == 0:
new_map.append(random.choice([0, -1]))
else:
new_map.append(0)
map = new_map
game(new_map, speed)
Генерация новых кадров. Одна итерация — один новый кадр и чанк.
На тачбаре это выглядит вот так:
Для большей плавности нужно создать ассеты для промежуточных кадров. И добавить в игровую сцену дополнительный цикл при отрисовке чанков.
Добавление событий. Предпоследний этап — то, ради чего играют в Google-динозаврика, — «паркур по кактусам».
Чтобы добавить прыжок, нужно завести переменную jump
— к ней мы будем прибавлять единицу при нажатии на кнопку с динозавром. И модифицировать список map таким образом, чтобы в первом чанке кактус и динозавр могли встретиться несколькими способами.
- Динозаврик врезался в кактус — чанк -2. Комбинация [1, -1], jump = 0.
- Динозаврик перепрыгнул кактус — чанк 2. Комбинация [1, -1], jump = 1.
Вот как «сценарии» записаны в программе:
...
jump = 0
# первый элемент — комбинация значений, где второе значение — следующий чанк
map = [[1,0], 0, 0, -1, 0, -1, 0]
...
while True:
# итоговое значение для комбинации:
# [1,0] → 1 кактуса нет, динозаврик бежит
# [1,-1] → кактус есть: если jump = 1, все хорошо
zero_chunk = 1
# записываем комбинацию
map[0] = [1,map[1]]
# динозаврик не прыгнул на прошлом чанке и врезался
if jump == 0 and map[0] == [1, -1]:
zero_chunk = -2
# на чанке без кактуса jump обнуляется
elif jump >= 1 and map[0] == [1, 0]:
jump = 0
zero_chunk = 1
# динозаврик прыгнул на прошлом чанке, все хорошо
elif jump == 1 and map[0] == [1,-1]:
zero_chunk = 2
points += 1
# настраиваем формат карты для передачи в функцию
new_map = map[1:]
new_map[0] = zero_chunk
if map[-1] == 0:
new_map.append(random.choice([0, -1]))
else:
new_map.append(0)
map = new_map
game(new_map, speed)
Ситуативная генерация новых кадров.
В коде отрисовки сцены также нужно добавить новые события:
def game(buttons)
...
for b in range(len(map)):
# если динозаврик не перепрыгнул, загружаем ассет со столкновением
if map[b] == -2:
buttons[b] = PyTouchBar.TouchBarItems.Button(image='assets/loss.png', color=(PyTouchBar.Color.black))
# если динозаврик перепрыгнул, загружаем ассет с прыжком
elif map[b] == 2:
buttons[b] = PyTouchBar.TouchBarItems.Button(image='assets/lucky.png', color=(PyTouchBar.Color.black))
...
Отрисовка игровой сцены.
На тачбаре это выглядит вот так:
Динозаврик научился бегать и прыгать!
Возможно, эти тексты тоже вас заинтересуют:
→ Docker на роутере MikroTik: как развернуть и не утонуть в багах
→ Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере
→ Делаем тетрис в QR-коде, который работает
Сообщение об окончании игры: элемент Label
Если динозаврик все же врезался, игру необходимо завершить: показать игроку количество набранных очков и сообщение.
Подсчет очков. Для подсчета очков нужно завести переменную points и прибавлять к ней единицу каждый раз, когда динозаврик перепрыгивает через кактус. Но с выводом сообщения немного сложней.
Вывод сообщения. Чтобы вывести сообщение, нужно создать новое окно Tkinter, подготовить его и добавить текстовое поле.
def finish(points):
...
# объявление элемента label
label = PyTouchBar.TouchBarItems.Label(text = f'Упс:( Вы набрали: {points}')
...
Добавление элемента Label.
Элемент Label поддерживает разные шрифты, цвета и масштабы. Полный список параметров есть в официальной документации.
Притом вызвать функцию finish
нужно после отрисовки сцены.
while True:
points = 0
...
# динозаврик не прыгнул на прошлом чанке и врезался
if jump == 0 and map[0] == [1, -1]:
zero_chunk = -2
elif jump == 1 and map[0] == [1, -1]:
zero_chunk = 2
points += 1
...
game(new_map, speed)
# если динозаврик врезался — вызвать finish()
if zero_chunk == -2:
finish(points)
Обработка события «конец игры».
Арендуйте выделенный сервер с запуском от 2 минут и бесплатной заменой комплектующих. И используйте его ресурсы для гейминга.
Как закрыть сообщение и программу? У PyTouchBar есть особенность: с помощью библиотеки можно модифицировать встроенную кнопку escape. Например, сделать ее кнопкой для закрытия программы.
...
def exit_f(button):
exit()
esc = PyTouchBar.TouchBarItems.Button(title = "exit", action = exit_f)
PyTouchBar.set_touchbar(... , esc_key = esc)
...
Полная версия кода доступна на GitHub. Подключайтесь и предлагайте свои улучшения.
Особенности в работе с PyTouchBar
Отсутствие настройки расстояний. Минимальное расстояние между кнопками фиксированное. Это плохо, если нужно сделать «непрерывное» изображение на панели.
Расстояния между элементами нельзя сократить, но можно увеличить. Для этого в библиотеке есть элемент Space — пустое пространство.
«Сырые» контроллеры. В библиотеке есть контроллеры (Control). По сути, это те же кнопки, но сопряженные между собой. Хотя у них нет некоторых параметров. Например, нельзя задать цвет фона.
А другие настройки и вовсе работают хуже, чем в элементе Button. Например, чтобы добавить изображение, его нужно «подогнать» под размеры контроллера. И, возможно, дополнительно использовать параметр image_scale, перебирая различные константы в поисках лучшего разрешения картинки.
Элементы не поддерживают gif. Это плохо, если нужно написать программу с большим количеством анимаций. Конечно, можно сделать раскадровку самостоятельно и обновлять окно приложения с заданной периодичностью. Но это сильно усложняет алгоритм.
«Непрямое» обновление окон. Единственный способ обновить окно — закрыть его и создать заново. Метод root.update()
не работает.
В сети нет подробной документации. О назначении некоторых функций остается только догадываться. Документация ограничивается репозиторием на GitHub. А некоторая ее часть не актуальна.
Например, есть раздел про интеграцию библиотеки с Pygame. Буквально несколько строчек кода — и все, больше ничего нет. К тому же загрузка Pygame через PyTouchBar на данный момент не работает. Будем ждать апдейтов от разработчиков.
На что способна библиотека?
Даже с ограничениями библиотека кажется интересной. С помощью нее можно написать софт для решения повседневных задач. Например, сделать дополнительный ряд кнопок для всяких спецсимволов, если надоела раскладка Бирмана.
Но Doom с помощью PyTouchBar запустить будет сложно: не понятно, как «вытягивать» отдельные кадры и управлять игрой через панель. Для более сложных проектов лучше «прыгнуть в нору за кроликом» и программировать тачбар с помощью Objective-C. А если нужно просто подключить какой-то виджет, лучше установить утилиту вроде BetterTouchTool.
Придумайте, какую еще программу или игру можно написать для тачбара. Самому креативному комментатору отправим нашего маскота — плюшевого тирекса Selectel.
Improve Article
Save Article
Improve Article
Save Article
What would you see in your Chrome browser when there is no internet connection ? Yes, everybody knows that dinosaur game that comes on screen. So, in this article, we are going to build a simple python bot that plays Chrome Dino Game without user interaction. Here we are not using any machine learning or artificial intelligence to counter this problem but we will use simple image/screen processing.
We will work with Pyautogui and PIL (Python Imaging Library) for implementation. This project is very basic and consists of only about 50 lines of code but its result will make you surprise.
Some libraries used are:
- PIL : Python Imaging Library (PIL) is a free library for the Python programming language that adds support for opening, manipulating, and saving many different image file formats.
- Pyautogui : PyAutoGUI is a Python module for programmatically controlling the mouse and keyboard without any user interaction.
- Time : Python “Time” Module which allows us to handle various operations regarding time, its conversions and representations, which find its use in various applications in life.
- Numpy :NumPy is a library for the Python programming language, adding support for large, multi-dimensional arrays and matrices, along with a large collection of high-level mathematical functions to operate on these arrays.
Algorithm –
- Click on the restart button using Pyautogui library using “replaybutton” coordinates.
- Calculate the sum of all white pixels values present in the box in front of Dinosaur.
- If the sum of pixels values present at any time in the box becomes less than the sum of white pixels values, it means either “bush” or “bird” is coming. So either we have to make our Dino jump or bend down.
- In order to protect Dino from “Bush”, we make a jump.
- In order to protect Dino from “Bird”, we always keep our Dino down.
Below is the Python implementation –
Python3
from
PIL
import
ImageGrab, ImageOps
import
pyautogui
import
time
import
numpy as np
class
coordinates():
replaybutton
=
(
360
,
214
)
dinasaur
=
(
149
,
239
)
def
restartGame():
pyautogui.click(coordinates.replaybutton)
pyautogui.keyDown(
'down'
)
def
press_space():
pyautogui.keyUp(
'down'
)
pyautogui.keyDown(
'space'
)
time.sleep(
0.05
)
print
(
"jump"
)
time.sleep(
0.10
)
pyautogui.keyUp(
'space'
)
pyautogui.keyDown(
'down'
)
def
imageGrab():
box
=
(coordinates.dinasaur[
0
]
+
30
, coordinates.dinasaur[
1
],
coordinates.dinasaur[
0
]
+
120
, coordinates.dinasaur[
1
]
+
2
)
image
=
ImageGrab.grab(box)
grayImage
=
ImageOps.grayscale(image)
a
=
np.array(grayImage.getcolors())
print
(a.
sum
())
return
a.
sum
()
restartGame()
while
True
:
if
(imageGrab()!
=
435
):
press_space()
time.sleep(
0.1
)
Output :
Improvements : Over a period of time, the Dino Bot Game becomes fast. The Birds and Bushes start coming very fast. So we are not making our Bot to learn all these things, changing its speed based on past learning. So our bot will function for around 2000 score. In order to score more, we have to apply machine learning and artificial intelligence.
Chrome-Dinosaur
by Pun Waiwitlikhit 29 September 2018 for Brighton College Programming Club
What are libraries?
- Libraries are a collection of functions which adds functions to a language.
- A video game library in python might add functions such as createSprite() which are not usable in normal python.
- Libraries are essentially a bunch of functions that can be imported to another file where it can be used.
- Pygame is a video game library that is written in python.
Intro to Pygame
- Pygame is a library meaning that you have to download it and import it into your code before you are able to use it.
You can install pygame by typing this in your TERMINAL:
pip3 install pygame
To import pygame into your python code, add this to your python code:
import pygame
Initializing
The first thing we would like to do is initialize the project: We do this by
import pygame
pygame.init() #this ‘starts up’ pygame
size = width,height = 640, 480#creates tuple called size with width 400 and height 230
gameDisplay= pygame.display.set_mode(size) #creates screen
while True: #gameLoop it draws the frames of the game
for event in pygame.event.get(): #Check for events
if event.type == pygame.QUIT:
pygame.quit() #quits
quit()
pygame.display.update() #updates the screen
The important parts of this code is the contents of while True: as it is the game loop, or the loop which draws the frame every second and updates the state of the game. The event loop (for event in pygame.event.get()) loops through all the events that have just happened — an example of this being the user pressing the exit button on the window — and then decides how to respond to the user input. Anything before the gameloop is just initialization code.
Drawing Shapes
To draw shapes in pygame, we use predefined commands from the pygame library. To do this, we edit the game loop
white = 255,255,255 #Define the RGB value of white as a tuple
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pygame.draw.rect(gameDisplay, white, [30,30,40,50])
#draws a rectangle at coordinate (30 ,30) with width 40 pixel, height 50 pixels and with colour white on the surface ’gameDisplay’
pygame.display.update()
Drawing the Ground
Now that we know how to draw a shape, we would like to have the practical use of the shape in the game. To do this, we will draw a ground. The ground is essentially a rectangle which left corner is on the edge and the width of the rectangle is as wide as the window. As we may want to experiment with the height of the ground we will use a variable to store it.
GROUND_HEIGHT = height-200#The y coordinate of the floor which is 200 pixels away from the bottom
To draw the ground, add this code to the main loop
pygame.draw.rect(gameDisplay,white, [0,GROUND_HEIGHT, width, height-GROUND_HEIGHT])
As an update, your code should now look like this.
#main.py
import pygame
pygame.init() #this ‘starts up’ pygame
size = width,height = 640, 480 #creates tuple called size with width 400 and height 230
gameDisplay= pygame.display.set_mode(size) #creates screen
GROUND_HEIGHT = height-100
black = 0,0,0
white = 255,255,255 #Define the RGB value of white as a tuple
xPos = 0
yPos = 0
while True: #gameLoop it draws the frames of the game
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() #quits
quit()
gameDisplay.fill(black)
pygame.draw.rect(gameDisplay,white, [0,GROUND_HEIGHT, width, height-GROUND_HEIGHT])
xPos += 1
yPos += 1
pygame.display.update() #updates the screen
The Dinosaur
To create the dinosaur, we will first create a class defining a dinosaur and then create a dinosaur object which we will manipulate in the gameloop. To create a class, we start a new file called dinosaur.py in the same project folder as the main.py code. The class should look like this.
import pygame
dinocolour = 255,255,255
DINOHEIGHT = 40
DINOWIDTH = 20
class Dinosaur:
def __init__(self, surfaceHeight):
self.x = 60
self.y = 0
self.yvelocity = 0
self.height = DINOHEIGHT
self.width = DINOWIDTH
self.surfaceHeight = surfaceHeight
def jump(self): #When adding classes into function, the first parameter must be the parameter
if(self.y == 0): #Only allow jumping if the dinosaur is on the ground to prevent mid air jumps.
self.yvelocity = 300
def update(self, deltaTime): #Updates the y position of the dinosaur each second
self.yvelocity += -500*deltaTime #Gravity
self.y += self.yvelocity * deltaTime
if self.y < 0: #if the dinosaur sinks into the ground, make velocity and y = 0
self.y = 0
self.yvelocity = 0
def draw(self,display):
pygame.draw.rect(display,dinocolour,[self.x,self.surfaceHeight-self.y-self.height,self.width,self.height])
Now that we have created the class, we need to import the class into the game and create the dinosaur object to do that, we add the following code OUTSIDE the game loop.
from dinosaur import Dinosaur #import the class Dinosaur from the file ’dinosaur’
dinosaur = Dinosaur(GROUND_HEIGHT)
As our functions require a deltaTime (change in time between the current Game loop and the previous gameloop) which makes our dinosaur move relative to time not the speed of the computer running the game. To do that, we add this before the gameloop
dinosaur = Dinosaur(GROUND_HEIGHT)
and this at the start of the gameloop
deltaTime = (t-lastFrame)/1000.0 #Find difference in time and then convert it to seconds lastFrame = t #set lastFrame as the current time for next frame.
Now we want to draw and update the dinosaur
dinosaur.draw(gameDisplay) #Draw dinosaur on gameDisplay
After that, we would like to jump if the user presses space.
if event.key == pygame.K_SPACE: #If that key is space
dinosaur.jump() #Make dinosaur jump
In the end, your main.py should look like the following
import pygame
from dinosaur import Dinosaur #import the class Dinosaur from the file ’dinosaur’
pygame.init() #this ‘starts up’ pygame
#initialize game
size = width,height = 640, 480#creates tuple called size with width 400 and height 230
gameDisplay= pygame.display.set_mode(size) #creates screen
xPos = 0
yPos = 0
GROUND_HEIGHT = height-100
# create Dinosaur
dinosaur = Dinosaur(GROUND_HEIGHT)
#create lastframe variable
lastFrame = pygame.time.get_ticks() #get ticks returns current time in milliseconds
#define game colours
white = 255,255,255
black = 0,0,0
while True: #gameLoop it draws the frames of the game
t = pygame.time.get_ticks() #Get current time
deltaTime = (t-lastFrame)/1000.0 #Find difference in time and then convert it to seconds
lastFrame = t #set lastFrame as the current time for next frame.
for event in pygame.event.get(): #Check for events
if event.type == pygame.QUIT:
pygame.quit() #quits
quit()
if event.type == pygame.KEYDOWN: #If user uses the keyboard
if event.key == pygame.K_SPACE: #If that key is space
dinosaur.jump() #Make dinosaur jump
gameDisplay.fill(black)
dinosaur.update(deltaTime)
dinosaur.draw(gameDisplay)
pygame.draw.rect(gameDisplay,white, [0,GROUND_HEIGHT, width, height-GROUND_HEIGHT])
pygame.display.update() #updates the screen
The Obstacle
Just like the dinosaur, we will create the class for the obstacles on a completely separate file. I will name mine obstacle.py
# obstacle.py
import pygame
colour = 0,0,255
class Obstacle:
def __init__(self, x, size, GroundHeight):
self.x = x
self.size = size
self.GroundHeight = GroundHeight
def draw(self, gameDisplay):
pygame.draw.rect(gameDisplay, colour, [self.x, self.GroundHeight-self.size, self.size, self.size])
def update(self, deltaTime, velocity):
self.x -= velocity*deltaTime
def checkOver(self):
if self.x < 0:
return True
else:
return False
Initializing the obstacle
To initialize the obstacle, we add this before the gameloop in your main.py file
# main.py
import random
from obstacle import Obstacle
MINGAP = 200
VELOCITY = 300
MAXGAP = 600
obstacles = []
num_of_obstacles = 4
lastObstacle = width
SCORE = 0
obstaclesize = 50
for i in range(4):
lastObstacle += MINGAP+(MAXGAP-MINGAP)*random.random() #Make distance between rocks random
obstacles.append(Obstacle(lastObstacle, obstaclesize, GROUND_HEIGHT))
Making the Obstacles move
To make the obstacles move, we would need to add the following to the main.py file
Now that we have created obstacles, we have to use the update function on them. In the initializing code above, we have defined VELOCITY which will be the speed at which the obstacles will move. Add the following into the main loop.
for obs in obstacles:
obs.update(deltaTime, VELOCITY)
obs.draw(gameDisplay)
To check if the obstacle has passed, we need add the if statement into the for loop above. Each time the obstacle touches the edge of the screen, it resets its position and the player also scores a point.
if(obs.checkOver()):
SCORE += 1
lastObstacle += MINGAP+(MAXGAP-MINGAP)*random.random()
obs.x = lastObstacle
Also, in each frame of the game, we have to update the position of the lastObstacle (Keeps track of the position of the final obstacle so that the obstacles can add itself to the back of the queue. We do this by
lastObstacle -= VELOCITY*deltaTime
https://learnopengl.com/img/in-practice/breakout/collisions_overlap.png
Chrome-Dinosaur
by Pun Waiwitlikhit 29 September 2018 for Brighton College Programming Club
What are libraries?
- Libraries are a collection of functions which adds functions to a language.
- A video game library in python might add functions such as createSprite() which are not usable in normal python.
- Libraries are essentially a bunch of functions that can be imported to another file where it can be used.
- Pygame is a video game library that is written in python.
Intro to Pygame
- Pygame is a library meaning that you have to download it and import it into your code before you are able to use it.
You can install pygame by typing this in your TERMINAL:
pip3 install pygame
To import pygame into your python code, add this to your python code:
import pygame
Initializing
The first thing we would like to do is initialize the project: We do this by
import pygame
pygame.init() #this ‘starts up’ pygame
size = width,height = 640, 480#creates tuple called size with width 400 and height 230
gameDisplay= pygame.display.set_mode(size) #creates screen
while True: #gameLoop it draws the frames of the game
for event in pygame.event.get(): #Check for events
if event.type == pygame.QUIT:
pygame.quit() #quits
quit()
pygame.display.update() #updates the screen
The important parts of this code is the contents of while True: as it is the game loop, or the loop which draws the frame every second and updates the state of the game. The event loop (for event in pygame.event.get()) loops through all the events that have just happened — an example of this being the user pressing the exit button on the window — and then decides how to respond to the user input. Anything before the gameloop is just initialization code.
Drawing Shapes
To draw shapes in pygame, we use predefined commands from the pygame library. To do this, we edit the game loop
white = 255,255,255 #Define the RGB value of white as a tuple
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pygame.draw.rect(gameDisplay, white, [30,30,40,50])
#draws a rectangle at coordinate (30 ,30) with width 40 pixel, height 50 pixels and with colour white on the surface ’gameDisplay’
pygame.display.update()
Drawing the Ground
Now that we know how to draw a shape, we would like to have the practical use of the shape in the game. To do this, we will draw a ground. The ground is essentially a rectangle which left corner is on the edge and the width of the rectangle is as wide as the window. As we may want to experiment with the height of the ground we will use a variable to store it.
GROUND_HEIGHT = height-200#The y coordinate of the floor which is 200 pixels away from the bottom
To draw the ground, add this code to the main loop
pygame.draw.rect(gameDisplay,white, [0,GROUND_HEIGHT, width, height-GROUND_HEIGHT])
As an update, your code should now look like this.
#main.py
import pygame
pygame.init() #this ‘starts up’ pygame
size = width,height = 640, 480 #creates tuple called size with width 400 and height 230
gameDisplay= pygame.display.set_mode(size) #creates screen
GROUND_HEIGHT = height-100
black = 0,0,0
white = 255,255,255 #Define the RGB value of white as a tuple
xPos = 0
yPos = 0
while True: #gameLoop it draws the frames of the game
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() #quits
quit()
gameDisplay.fill(black)
pygame.draw.rect(gameDisplay,white, [0,GROUND_HEIGHT, width, height-GROUND_HEIGHT])
xPos += 1
yPos += 1
pygame.display.update() #updates the screen
The Dinosaur
To create the dinosaur, we will first create a class defining a dinosaur and then create a dinosaur object which we will manipulate in the gameloop. To create a class, we start a new file called dinosaur.py in the same project folder as the main.py code. The class should look like this.
import pygame
dinocolour = 255,255,255
DINOHEIGHT = 40
DINOWIDTH = 20
class Dinosaur:
def __init__(self, surfaceHeight):
self.x = 60
self.y = 0
self.yvelocity = 0
self.height = DINOHEIGHT
self.width = DINOWIDTH
self.surfaceHeight = surfaceHeight
def jump(self): #When adding classes into function, the first parameter must be the parameter
if(self.y == 0): #Only allow jumping if the dinosaur is on the ground to prevent mid air jumps.
self.yvelocity = 300
def update(self, deltaTime): #Updates the y position of the dinosaur each second
self.yvelocity += -500*deltaTime #Gravity
self.y += self.yvelocity * deltaTime
if self.y < 0: #if the dinosaur sinks into the ground, make velocity and y = 0
self.y = 0
self.yvelocity = 0
def draw(self,display):
pygame.draw.rect(display,dinocolour,[self.x,self.surfaceHeight-self.y-self.height,self.width,self.height])
Now that we have created the class, we need to import the class into the game and create the dinosaur object to do that, we add the following code OUTSIDE the game loop.
from dinosaur import Dinosaur #import the class Dinosaur from the file ’dinosaur’
dinosaur = Dinosaur(GROUND_HEIGHT)
As our functions require a deltaTime (change in time between the current Game loop and the previous gameloop) which makes our dinosaur move relative to time not the speed of the computer running the game. To do that, we add this before the gameloop
dinosaur = Dinosaur(GROUND_HEIGHT)
and this at the start of the gameloop
deltaTime = (t-lastFrame)/1000.0 #Find difference in time and then convert it to seconds lastFrame = t #set lastFrame as the current time for next frame.
Now we want to draw and update the dinosaur
dinosaur.draw(gameDisplay) #Draw dinosaur on gameDisplay
After that, we would like to jump if the user presses space.
if event.key == pygame.K_SPACE: #If that key is space
dinosaur.jump() #Make dinosaur jump
In the end, your main.py should look like the following
import pygame
from dinosaur import Dinosaur #import the class Dinosaur from the file ’dinosaur’
pygame.init() #this ‘starts up’ pygame
#initialize game
size = width,height = 640, 480#creates tuple called size with width 400 and height 230
gameDisplay= pygame.display.set_mode(size) #creates screen
xPos = 0
yPos = 0
GROUND_HEIGHT = height-100
# create Dinosaur
dinosaur = Dinosaur(GROUND_HEIGHT)
#create lastframe variable
lastFrame = pygame.time.get_ticks() #get ticks returns current time in milliseconds
#define game colours
white = 255,255,255
black = 0,0,0
while True: #gameLoop it draws the frames of the game
t = pygame.time.get_ticks() #Get current time
deltaTime = (t-lastFrame)/1000.0 #Find difference in time and then convert it to seconds
lastFrame = t #set lastFrame as the current time for next frame.
for event in pygame.event.get(): #Check for events
if event.type == pygame.QUIT:
pygame.quit() #quits
quit()
if event.type == pygame.KEYDOWN: #If user uses the keyboard
if event.key == pygame.K_SPACE: #If that key is space
dinosaur.jump() #Make dinosaur jump
gameDisplay.fill(black)
dinosaur.update(deltaTime)
dinosaur.draw(gameDisplay)
pygame.draw.rect(gameDisplay,white, [0,GROUND_HEIGHT, width, height-GROUND_HEIGHT])
pygame.display.update() #updates the screen
The Obstacle
Just like the dinosaur, we will create the class for the obstacles on a completely separate file. I will name mine obstacle.py
# obstacle.py
import pygame
colour = 0,0,255
class Obstacle:
def __init__(self, x, size, GroundHeight):
self.x = x
self.size = size
self.GroundHeight = GroundHeight
def draw(self, gameDisplay):
pygame.draw.rect(gameDisplay, colour, [self.x, self.GroundHeight-self.size, self.size, self.size])
def update(self, deltaTime, velocity):
self.x -= velocity*deltaTime
def checkOver(self):
if self.x < 0:
return True
else:
return False
Initializing the obstacle
To initialize the obstacle, we add this before the gameloop in your main.py file
# main.py
import random
from obstacle import Obstacle
MINGAP = 200
VELOCITY = 300
MAXGAP = 600
obstacles = []
num_of_obstacles = 4
lastObstacle = width
SCORE = 0
obstaclesize = 50
for i in range(4):
lastObstacle += MINGAP+(MAXGAP-MINGAP)*random.random() #Make distance between rocks random
obstacles.append(Obstacle(lastObstacle, obstaclesize, GROUND_HEIGHT))
Making the Obstacles move
To make the obstacles move, we would need to add the following to the main.py file
Now that we have created obstacles, we have to use the update function on them. In the initializing code above, we have defined VELOCITY which will be the speed at which the obstacles will move. Add the following into the main loop.
for obs in obstacles:
obs.update(deltaTime, VELOCITY)
obs.draw(gameDisplay)
To check if the obstacle has passed, we need add the if statement into the for loop above. Each time the obstacle touches the edge of the screen, it resets its position and the player also scores a point.
if(obs.checkOver()):
SCORE += 1
lastObstacle += MINGAP+(MAXGAP-MINGAP)*random.random()
obs.x = lastObstacle
Also, in each frame of the game, we have to update the position of the lastObstacle (Keeps track of the position of the final obstacle so that the obstacles can add itself to the back of the queue. We do this by
lastObstacle -= VELOCITY*deltaTime
https://learnopengl.com/img/in-practice/breakout/collisions_overlap.png
Who has not played Google’s famous Dino Game whenever we are not connected to the internet? Probably everyone might have played this game once. Today, in this article, we will help you develop a Dino Game in Python. This tutorial will include an in-depth explanation of each line of code along with reference materials. We will try our level best to make our readers understand this project in detail and thoroughly. The task record for the Python version of the Dino Game includes picture documents and Python material. GUI makes use of the pygame library.
We will use one of the most famous Python libraries that is PyGame. Apart from that, we will also use random, os, and sys libraries for the development of this Dino Game in Python. Before moving on to the actual coding let us take a brief overview of What Dino Game is all about? How does it work? etc. After that, we will take a look at the list of features that we need to add to this game. So let us initialize this article without any further ado.
Basic Idea of the Dino Game in Python
The main objective of this simplified game is to score an increasing amount of points without becoming distracted by any obstacles. The same playing techniques are used in all games. As stated the core of this mini-game is to accumulate points without coming into contact with any obstacles. The user must use two keyboard keys to play this straightforward game. Jump with the spacebar, and cover with the down arrow. The gaming environment has been modified in this copy compared to the original.
Having a basic idea of what exactly this game is all about. Let us talk about the mandatory features that need to be there in this project of Dino Game in Python
Features of this game
The set of features for Dino Game includes the following:
- Display previous high score and current score
- Keyboard support
- Sound for jump and when dinosaurs die
- Checkpoint sound
- Option to replay the game
Now let us move on to the actual coding of this project.
Coding Dino Game in Python
Basic library import
import os
import sys
import pygame
import random
from pygame import *
pygame.init()
Explanation:
Here we imported all the libraries that we are going to use in the development of this project. The last line pygame. init() is used to initialize all the imported pygame modules.
Setting up the display window and title
screenDisplay = (width_screen, height_screen) = (600, 400)
FPS = 60
gravity = 0.6
black_color = (0,0,0)
white_color = (255,255,255)
backgroundColor = (235, 235, 235)
highest_scores = 0
screenDisplay = pygame.display.set_mode(screenDisplay)
timerClock = pygame.time.Clock()
pygame.display.set_caption("Dino Run - CopyAssignment ")
Explanation:
In this section of code in Dino Game in Python, we have used the various functions of PyGame in order to set the window size, title, etc. We used to display.set_mode will initialize a window or screen for display. In order to set the title of the window we used set_caption
Basic resources addition
soundOnJump = pygame.mixer.Sound('resources/jump.wav')
soundOnDie = pygame.mixer.Sound('resources/die.wav')
soundOnCheckpoint = pygame.mixer.Sound('resources/checkPoint.wav')
Explanation:
Here w declared three variables that are basically to add various sounds in the game. We made a folder named resources and in that, we have our three .wav files. In order to send sound to our game, we made use of a mixer.Sound(). We will use these three sounds in the other parts of the code.
Functions to load the images
def load_image(
name,
sx=-1,
sy=-1,
colorkey=None,
):
fullname = os.path.join('resources', name)
img = pygame.image.load(fullname)
img = img.convert()
if colorkey is not None:
if colorkey == -1:
colorkey = img.get_at((0, 0))
img.set_colorkey(colorkey, RLEACCEL)
if sx != -1 or sy != -1:
img = pygame.transform.scale(img, (sx, sy))
return (img, img.get_rect())
def load_sprite_sheet(
s_name,
namex,
namey,
scx = -1,
scy = -1,
c_key = None,
):
fullname = os.path.join('resources', s_name)
sh = pygame.image.load(fullname)
sh = sh.convert()
sh_rect = sh.get_rect()
sprites = []
sx = sh_rect.width/ namex
sy = sh_rect.height/ namey
for i in range(0, namey):
for j in range(0, namex):
rect = pygame.Rect((j*sx,i*sy,sx,sy))
img = pygame.Surface(rect.size)
img = img.convert()
img.blit(sh,(0,0),rect)
if c_key is not None:
if c_key == -1:
c_key = img.get_at((0, 0))
img.set_colorkey(c_key, RLEACCEL)
if scx != -1 or scy != -1:
img = pygame.transform.scale(img, (scx, scy))
sprites.append(img)
sprite_rect = sprites[0].get_rect()
return sprites,sprite_rect
Explanation:
Onto the other block of code in Dino Game in Python, this section is mainly responsible for loading images on the game window of Dino Game. Here we used Python’s os. path.join() function that intelligently joins one or more path components. With the exception of the last path component, this approach concatenates different path components by placing exactly one directory separator (‘/’) after each non-empty portion. A directory separator (‘/’) is added at the end of the final path component to be linked is empty.
pygame. image.load() this helps us to load the image and then in the for loop, we used the set_colorkey() that will basically set the transparent color key. In the same way, we used the above functions in another user-defined function named load_sprite_sheet
To display the game over the message
def gameover_display_message(rbtn_image, gmo_image):
rbtn_rect = rbtn_image.get_rect()
rbtn_rect.centerx = width_screen / 2
rbtn_rect.top = height_screen * 0.52
gmo_rect = gmo_image.get_rect()
gmo_rect.centerx = width_screen / 2
gmo_rect.centery = height_screen * 0.35
screenDisplay.blit(rbtn_image, rbtn_rect)
screenDisplay.blit(gmo_image, gmo_rect)
def extractDigits(num):
if num > -1:
d = []
i = 0
while(num / 10 != 0):
d.append(num % 10)
num = int(num / 10)
d.append(num % 10)
for i in range(len(d),5):
d.append(0)
d.reverse()
return d
Explanation:
Here in this function, we made use of two rectangles in order to display the two images: One of the “Game Over” image and another of the “Replay Image”. We used blit() which means Block Transfer which refers to the process of copying material from one surface to another Surface. The screen you made and the new Surface are the two surfaces in concern. So, this Surface will be taken and placed on top of the screen by the blit() function. The approach requires two arguments. Another function that we defined is extractDigits() – this function is responsible for keeping the track of user’s score.
Defining the Dino class
class Dino():
def __init__(self, sx=-1, sy=-1):
self.imgs, self.rect = load_sprite_sheet('dino.png', 5, 1, sx, sy, -1)
self.imgs1, self.rect1 = load_sprite_sheet('dino_ducking.png', 2, 1, 59, sy, -1)
self.rect.bottom = int(0.98 * height_screen)
self.rect.left = width_screen / 15
self.image = self.imgs[0]
self.index = 0
self.counter = 0
self.score = 0
self.jumping = False
self.dead = False
self.ducking = False
self.blinking = False
self.movement = [0,0]
self.jumpSpeed = 11.5
self.stand_position_width = self.rect.width
self.duck_position_width = self.rect1.width
def draw(self):
screenDisplay.blit(self.image, self.rect)
def checkbounds(self):
if self.rect.bottom > int(0.98 * height_screen):
self.rect.bottom = int(0.98 * height_screen)
self.jumping = False
def update(self):
if self.jumping:
self.movement[1] = self.movement[1] + gravity
if self.jumping:
self.index = 0
elif self.blinking:
if self.index == 0:
if self.counter % 400 == 399:
self.index = (self.index + 1)%2
else:
if self.counter % 20 == 19:
self.index = (self.index + 1)%2
elif self.ducking:
if self.counter % 5 == 0:
self.index = (self.index + 1)%2
else:
if self.counter % 5 == 0:
self.index = (self.index + 1)%2 + 2
if self.dead:
self.index = 4
if not self.ducking:
self.image = self.imgs[self.index]
self.rect.width = self.stand_position_width
else:
self.image = self.imgs1[(self.index) % 2]
self.rect.width = self.duck_position_width
self.rect = self.rect.move(self.movement)
self.checkbounds()
if not self.dead and self.counter % 7 == 6 and self.blinking == False:
self.score += 1
if self.score % 100 == 0 and self.score != 0:
if pygame.mixer.get_init() != None:
soundOnCheckpoint.play()
self.counter = (self.counter + 1)
Explanation:
This class can be considered the heart of Dino Game in Python. It is mainly responsible for handling all the functions of the dinosaur. Initially, we hard-coded all the different variables of the dino like its speed, jump movements, etc. Apart from that in order to show its movement, we used the function update() that will keep track of all the movements like ducking, jumping, death, etc of the dino.
Defining the Cactus class
class Ground():
def __init__(self,speed=-5):
self.image,self.rect = load_image('ground.png',-1,-1,-1)
self.image1,self.rect1 = load_image('ground.png',-1,-1,-1)
self.rect.bottom = height_screen
self.rect1.bottom = height_screen
self.rect1.left = self.rect.right
self.speed = speed
def draw(self):
screenDisplay.blit(self.image, self.rect)
screenDisplay.blit(self.image1, self.rect1)
def update(self):
self.rect.left += self.speed
self.rect1.left += self.speed
if self.rect.right < 0:
self.rect.left = self.rect1.right
if self.rect1.right < 0:
self.rect1.left = self.rect.right
Explanation:
This class is responsible for handling the placement of cacti in this Dino Game in Python. At first, we load the cactus image from our resources folder and set its place, movement, etc. To remove the cactus that are passed from the screen, we made use of an update(). Inside that, we set and if block with the condition that self. rect.right < 0 then we will call the self.kill()
Defining the Birds class
class birds(pygame.sprite.Sprite):
def __init__(self, speed=5, sx=-1, sy=-1):
pygame.sprite.Sprite.__init__(self,self.containers)
self.imgs, self.rect = load_sprite_sheet('birds.png', 2, 1, sx, sy, -1)
self.birds_height = [height_screen * 0.82, height_screen * 0.75, height_screen * 0.60]
self.rect.centery = self.birds_height[random.randrange(0, 3)]
self.rect.left = width_screen + self.rect.width
self.image = self.imgs[0]
self.movement = [-1*speed,0]
self.index = 0
self.counter = 0
def draw(self):
screenDisplay.blit(self.image, self.rect)
def update(self):
if self.counter % 10 == 0:
self.index = (self.index+1)%2
self.image = self.imgs[self.index]
self.rect = self.rect.move(self.movement)
self.counter = (self.counter + 1)
if self.rect.right < 0:
self.kill()
Explanation:
This class is responsible for handling the placement of cacti in this Dino Game in Python. At first, we load the cactus image from our resources folder and set its place, movement, etc. To remove the cactus that are passed from the screen, we made use of an update(). Inside that, we set and if block with the condition that self.rect.right < 0 then we will call the self.kill(). It is used to remove the widget from the screen and so we use kill().
Defining the Ground class
class Ground():
def __init__(self,speed=-5):
self.image,self.rect = load_image('ground.png',-1,-1,-1)
self.image1,self.rect1 = load_image('ground.png',-1,-1,-1)
self.rect.bottom = height_screen
self.rect1.bottom = height_screen
self.rect1.left = self.rect.right
self.speed = speed
def draw(self):
screenDisplay.blit(self.image, self.rect)
screenDisplay.blit(self.image1, self.rect1)
def update(self):
self.rect.left += self.speed
self.rect1.left += self.speed
if self.rect.right < 0:
self.rect.left = self.rect1.right
if self.rect1.right < 0:
self.rect1.left = self.rect.right
Explanation
In the same way, as we defined the birds class, we defined the ground class and for the movement of the ground, we used the same functions as we did in birds.
Defining the Cloud class
class Cloud(pygame.sprite.Sprite):
def __init__(self,x,y):
pygame.sprite.Sprite.__init__(self,self.containers)
self.image,self.rect = load_image('cloud.png',int(90*30/42),30,-1)
self.speed = 1
self.rect.left = x
self.rect.top = y
self.movement = [-1*self.speed,0]
def draw(self):
screenDisplay.blit(self.image, self.rect)
def update(self):
self.rect = self.rect.move(self.movement)
if self.rect.right < 0:
self.kill()
Explanation:
Moving on to another class in Dino Game in Python. Clouds are also one of the components of this game. Its function and movement are totally the same as the ground. The functions in this class will be the same as those we used in the ground class.
Handling the scores
class Scoreboard():
def __init__(self,x=-1,y=-1):
self.score = 0
self.scre_img, self.screrect = load_sprite_sheet('numbers.png', 12, 1, 11, int(11 * 6 / 5), -1)
self.image = pygame.Surface((55,int(11*6/5)))
self.rect = self.image.get_rect()
if x == -1:
self.rect.left = width_screen * 0.89
else:
self.rect.left = x
if y == -1:
self.rect.top = height_screen * 0.1
else:
self.rect.top = y
def draw(self):
screenDisplay.blit(self.image, self.rect)
def update(self,score):
score_digits = extractDigits(score)
self.image.fill(backgroundColor)
for s in score_digits:
self.image.blit(self.scre_img[s], self.screrect)
self.screrect.left += self.screrect.width
self.screrect.left = 0
Explanation:
Now we have to work on handling the scoreboard in Dino Game. We did this by tracking the movement of the screen. We used our “numbers.png” images to update the scores. On top of that, we called our extractDigits() function that we declared previously to extract single- single digits from the image and then display it based on the movement of the components on the window.
Introduction screen
def introduction_screen():
ado_dino = Dino(44,47)
ado_dino.blinking = True
starting_game = False
t_ground,t_ground_rect = load_sprite_sheet('ground.png',15,1,-1,-1,-1)
t_ground_rect.left = width_screen / 20
t_ground_rect.bottom = height_screen
logo,l_rect = load_image('logo.png',300,140,-1)
l_rect.centerx = width_screen * 0.6
l_rect.centery = height_screen * 0.6
while not starting_game:
if pygame.display.get_surface() == None:
print("Couldn't load display surface")
return True
else:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE or event.key == pygame.K_UP:
ado_dino.jumping = True
ado_dino.blinking = False
ado_dino.movement[1] = -1*ado_dino.jumpSpeed
ado_dino.update()
if pygame.display.get_surface() != None:
screenDisplay.fill(backgroundColor)
screenDisplay.blit(t_ground[0], t_ground_rect)
if ado_dino.blinking:
screenDisplay.blit(logo, l_rect)
ado_dino.draw()
pygame.display.update()
timerClock.tick(FPS)
if ado_dino.jumping == False and ado_dino.blinking == False:
starting_game = True
Explanation:
This screen will be displayed t first whenever the user will run this program of Dino Game in Python. This screen will initially load some of the components of the game like the ground and the logo image only. We used if-else to display a message that “Couldn’t load display surface” just in case our image does not load up. Apart from that, we used the three keys that are Upward and Downward arrow keys and Spacebar to start the game. The game will only start if the user presses any of these keys. The handling of key presses is done using the event.
Handling the overall gameplay
def gameplay():
global highest_scores
gp = 4
s_Menu = False
g_Over = False
g_exit = False
gamer_Dino = Dino(44,47)
new_grnd = Ground(-1*gp)
score_boards = Scoreboard()
highScore = Scoreboard(width_screen * 0.78)
counter = 0
cactusan = pygame.sprite.Group()
smallBird = pygame.sprite.Group()
skyClouds = pygame.sprite.Group()
last_end_obs = pygame.sprite.Group()
Cactus.containers = cactusan
birds.containers = smallBird
Cloud.containers = skyClouds
rbtn_image,rbtn_rect = load_image('replay_button.png',35,31,-1)
gmo_image,gmo_rect = load_image('game_over.png',190,11,-1)
t_images,t_rect = load_sprite_sheet('numbers.png',12,1,11,int(11*6/5),-1)
ado_image = pygame.Surface((22,int(11*6/5)))
ado_rect = ado_image.get_rect()
ado_image.fill(backgroundColor)
ado_image.blit(t_images[10],t_rect)
t_rect.left += t_rect.width
ado_image.blit(t_images[11],t_rect)
ado_rect.top = height_screen * 0.1
ado_rect.left = width_screen * 0.73
while not g_exit:
while s_Menu:
pass
while not g_Over:
if pygame.display.get_surface() == None:
print("Couldn't load display surface")
g_exit = True
g_Over = True
else:
for event in pygame.event.get():
if event.type == pygame.QUIT:
g_exit = True
g_Over = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if gamer_Dino.rect.bottom == int(0.98 * height_screen):
gamer_Dino.jumping = True
if pygame.mixer.get_init() != None:
soundOnJump.play()
gamer_Dino.movement[1] = -1*gamer_Dino.jumpSpeed
if event.key == pygame.K_DOWN:
if not (gamer_Dino.jumping and gamer_Dino.dead):
gamer_Dino.ducking = True
if event.type == pygame.KEYUP:
if event.key == pygame.K_DOWN:
gamer_Dino.ducking = False
for c in cactusan:
c.movement[0] = -1*gp
if pygame.sprite.collide_mask(gamer_Dino,c):
gamer_Dino.dead = True
if pygame.mixer.get_init() != None:
soundOnDie.play()
for p in smallBird:
p.movement[0] = -1*gp
if pygame.sprite.collide_mask(gamer_Dino,p):
gamer_Dino.dead = True
if pygame.mixer.get_init() != None:
soundOnDie.play()
if len(cactusan) < 2:
if len(cactusan) == 0:
last_end_obs.empty()
last_end_obs.add(Cactus(gp,40,40))
else:
for l in last_end_obs:
if l.rect.right < width_screen*0.7 and random.randrange(0, 50) == 10:
last_end_obs.empty()
last_end_obs.add(Cactus(gp, 40, 40))
if len(smallBird) == 0 and random.randrange(0,200) == 10 and counter > 500:
for l in last_end_obs:
if l.rect.right < width_screen*0.8:
last_end_obs.empty()
last_end_obs.add(birds(gp, 46, 40))
if len(skyClouds) < 5 and random.randrange(0,300) == 10:
Cloud(width_screen, random.randrange(height_screen / 5, height_screen / 2))
gamer_Dino.update()
cactusan.update()
smallBird.update()
skyClouds.update()
new_grnd.update()
score_boards.update(gamer_Dino.score)
highScore.update(highest_scores)
if pygame.display.get_surface() != None:
screenDisplay.fill(backgroundColor)
new_grnd.draw()
skyClouds.draw(screenDisplay)
score_boards.draw()
if highest_scores != 0:
highScore.draw()
screenDisplay.blit(ado_image, ado_rect)
cactusan.draw(screenDisplay)
smallBird.draw(screenDisplay)
gamer_Dino.draw()
pygame.display.update()
timerClock.tick(FPS)
if gamer_Dino.dead:
g_Over = True
if gamer_Dino.score > highest_scores:
highest_scores = gamer_Dino.score
if counter%700 == 699:
new_grnd.speed -= 1
gp += 1
counter = (counter + 1)
if g_exit:
break
while g_Over:
if pygame.display.get_surface() == None:
print("Couldn't load display surface")
g_exit = True
g_Over = False
else:
for event in pygame.event.get():
if event.type == pygame.QUIT:
g_exit = True
g_Over = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
g_exit = True
g_Over = False
if event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
g_Over = False
gameplay()
highScore.update(highest_scores)
if pygame.display.get_surface() != None:
gameover_display_message(rbtn_image, gmo_image)
if highest_scores != 0:
highScore.draw()
screenDisplay.blit(ado_image, ado_rect)
pygame.display.update()
timerClock.tick(FPS)
pygame.quit()
quit()
Explanation:
The gameplay function comes into action whenever the user presses the three keys that we described above to start the game. Once the game starts, it is the responsibility of the gameplay() function to control the movement of each and every component in the game. Besides that, the key presses for various types of movement of the dino are also controlled in this function. The constant display of birds, cacti, clouds, etc. In the end, if the player loses then the game automatically gets into the quit() state. This will help us to take the game into the quit state and display the two images of replay and game over.
main function
def main():
isGameQuit = introduction_screen()
if not isGameQuit:
gameplay()
main()
Explanation:
This function will call the introduction_screen() if the user quits the game. If that is not the case then the gameplay() function will be called. That simply means that the game will again be in a state wherein the user will be able to play.
Output:
Image output:
Video output:
Complete Code for Dino Game in Python
import os import sys import pygame import random from pygame import * pygame.init() screenDisplay = (width_screen, height_screen) = (600, 400) FPS = 60 gravity = 0.6 black_color = (0,0,0) white_color = (255,255,255) backgroundColor = (235, 235, 235) highest_scores = 0 screenDisplay = pygame.display.set_mode(screenDisplay) timerClock = pygame.time.Clock() pygame.display.set_caption("Dino Run - CopyAssignment ") soundOnJump = pygame.mixer.Sound('resources/jump.wav') soundOnDie = pygame.mixer.Sound('resources/die.wav') soundOnCheckpoint = pygame.mixer.Sound('resources/checkPoint.wav') def load_image( name, sx=-1, sy=-1, colorkey=None, ): fullname = os.path.join('resources', name) img = pygame.image.load(fullname) img = img.convert() if colorkey is not None: if colorkey == -1: colorkey = img.get_at((0, 0)) img.set_colorkey(colorkey, RLEACCEL) if sx != -1 or sy != -1: img = pygame.transform.scale(img, (sx, sy)) return (img, img.get_rect()) def load_sprite_sheet( s_name, namex, namey, scx = -1, scy = -1, c_key = None, ): fullname = os.path.join('resources', s_name) sh = pygame.image.load(fullname) sh = sh.convert() sh_rect = sh.get_rect() sprites = [] sx = sh_rect.width/ namex sy = sh_rect.height/ namey for i in range(0, namey): for j in range(0, namex): rect = pygame.Rect((j*sx,i*sy,sx,sy)) img = pygame.Surface(rect.size) img = img.convert() img.blit(sh,(0,0),rect) if c_key is not None: if c_key == -1: c_key = img.get_at((0, 0)) img.set_colorkey(c_key, RLEACCEL) if scx != -1 or scy != -1: img = pygame.transform.scale(img, (scx, scy)) sprites.append(img) sprite_rect = sprites[0].get_rect() return sprites,sprite_rect def gameover_display_message(rbtn_image, gmo_image): rbtn_rect = rbtn_image.get_rect() rbtn_rect.centerx = width_screen / 2 rbtn_rect.top = height_screen * 0.52 gmo_rect = gmo_image.get_rect() gmo_rect.centerx = width_screen / 2 gmo_rect.centery = height_screen * 0.35 screenDisplay.blit(rbtn_image, rbtn_rect) screenDisplay.blit(gmo_image, gmo_rect) def extractDigits(num): if num > -1: d = [] i = 0 while(num / 10 != 0): d.append(num % 10) num = int(num / 10) d.append(num % 10) for i in range(len(d),5): d.append(0) d.reverse() return d class Dino(): def __init__(self, sx=-1, sy=-1): self.imgs, self.rect = load_sprite_sheet('dino.png', 5, 1, sx, sy, -1) self.imgs1, self.rect1 = load_sprite_sheet('dino_ducking.png', 2, 1, 59, sy, -1) self.rect.bottom = int(0.98 * height_screen) self.rect.left = width_screen / 15 self.image = self.imgs[0] self.index = 0 self.counter = 0 self.score = 0 self.jumping = False self.dead = False self.ducking = False self.blinking = False self.movement = [0,0] self.jumpSpeed = 11.5 self.stand_position_width = self.rect.width self.duck_position_width = self.rect1.width def draw(self): screenDisplay.blit(self.image, self.rect) def checkbounds(self): if self.rect.bottom > int(0.98 * height_screen): self.rect.bottom = int(0.98 * height_screen) self.jumping = False def update(self): if self.jumping: self.movement[1] = self.movement[1] + gravity if self.jumping: self.index = 0 elif self.blinking: if self.index == 0: if self.counter % 400 == 399: self.index = (self.index + 1)%2 else: if self.counter % 20 == 19: self.index = (self.index + 1)%2 elif self.ducking: if self.counter % 5 == 0: self.index = (self.index + 1)%2 else: if self.counter % 5 == 0: self.index = (self.index + 1)%2 + 2 if self.dead: self.index = 4 if not self.ducking: self.image = self.imgs[self.index] self.rect.width = self.stand_position_width else: self.image = self.imgs1[(self.index) % 2] self.rect.width = self.duck_position_width self.rect = self.rect.move(self.movement) self.checkbounds() if not self.dead and self.counter % 7 == 6 and self.blinking == False: self.score += 1 if self.score % 100 == 0 and self.score != 0: if pygame.mixer.get_init() != None: soundOnCheckpoint.play() self.counter = (self.counter + 1) class Cactus(pygame.sprite.Sprite): def __init__(self, speed=5, sx=-1, sy=-1): pygame.sprite.Sprite.__init__(self,self.containers) self.imgs, self.rect = load_sprite_sheet('cactus-small.png', 3, 1, sx, sy, -1) self.rect.bottom = int(0.98 * height_screen) self.rect.left = width_screen + self.rect.width self.image = self.imgs[random.randrange(0, 3)] self.movement = [-1*speed,0] def draw(self): screenDisplay.blit(self.image, self.rect) def update(self): self.rect = self.rect.move(self.movement) if self.rect.right < 0: self.kill() class birds(pygame.sprite.Sprite): def __init__(self, speed=5, sx=-1, sy=-1): pygame.sprite.Sprite.__init__(self,self.containers) self.imgs, self.rect = load_sprite_sheet('birds.png', 2, 1, sx, sy, -1) self.birds_height = [height_screen * 0.82, height_screen * 0.75, height_screen * 0.60] self.rect.centery = self.birds_height[random.randrange(0, 3)] self.rect.left = width_screen + self.rect.width self.image = self.imgs[0] self.movement = [-1*speed,0] self.index = 0 self.counter = 0 def draw(self): screenDisplay.blit(self.image, self.rect) def update(self): if self.counter % 10 == 0: self.index = (self.index+1)%2 self.image = self.imgs[self.index] self.rect = self.rect.move(self.movement) self.counter = (self.counter + 1) if self.rect.right < 0: self.kill() class Ground(): def __init__(self,speed=-5): self.image,self.rect = load_image('ground.png',-1,-1,-1) self.image1,self.rect1 = load_image('ground.png',-1,-1,-1) self.rect.bottom = height_screen self.rect1.bottom = height_screen self.rect1.left = self.rect.right self.speed = speed def draw(self): screenDisplay.blit(self.image, self.rect) screenDisplay.blit(self.image1, self.rect1) def update(self): self.rect.left += self.speed self.rect1.left += self.speed if self.rect.right < 0: self.rect.left = self.rect1.right if self.rect1.right < 0: self.rect1.left = self.rect.right class Cloud(pygame.sprite.Sprite): def __init__(self,x,y): pygame.sprite.Sprite.__init__(self,self.containers) self.image,self.rect = load_image('cloud.png',int(90*30/42),30,-1) self.speed = 1 self.rect.left = x self.rect.top = y self.movement = [-1*self.speed,0] def draw(self): screenDisplay.blit(self.image, self.rect) def update(self): self.rect = self.rect.move(self.movement) if self.rect.right < 0: self.kill() class Scoreboard(): def __init__(self,x=-1,y=-1): self.score = 0 self.scre_img, self.screrect = load_sprite_sheet('numbers.png', 12, 1, 11, int(11 * 6 / 5), -1) self.image = pygame.Surface((55,int(11*6/5))) self.rect = self.image.get_rect() if x == -1: self.rect.left = width_screen * 0.89 else: self.rect.left = x if y == -1: self.rect.top = height_screen * 0.1 else: self.rect.top = y def draw(self): screenDisplay.blit(self.image, self.rect) def update(self,score): score_digits = extractDigits(score) self.image.fill(backgroundColor) for s in score_digits: self.image.blit(self.scre_img[s], self.screrect) self.screrect.left += self.screrect.width self.screrect.left = 0 def introduction_screen(): ado_dino = Dino(44,47) ado_dino.blinking = True starting_game = False t_ground,t_ground_rect = load_sprite_sheet('ground.png',15,1,-1,-1,-1) t_ground_rect.left = width_screen / 20 t_ground_rect.bottom = height_screen logo,l_rect = load_image('logo.png',300,140,-1) l_rect.centerx = width_screen * 0.6 l_rect.centery = height_screen * 0.6 while not starting_game: if pygame.display.get_surface() == None: print("Couldn't load display surface") return True else: for event in pygame.event.get(): if event.type == pygame.QUIT: return True if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE or event.key == pygame.K_UP: ado_dino.jumping = True ado_dino.blinking = False ado_dino.movement[1] = -1*ado_dino.jumpSpeed ado_dino.update() if pygame.display.get_surface() != None: screenDisplay.fill(backgroundColor) screenDisplay.blit(t_ground[0], t_ground_rect) if ado_dino.blinking: screenDisplay.blit(logo, l_rect) ado_dino.draw() pygame.display.update() timerClock.tick(FPS) if ado_dino.jumping == False and ado_dino.blinking == False: starting_game = True def gameplay(): global highest_scores gp = 4 s_Menu = False g_Over = False g_exit = False gamer_Dino = Dino(44,47) new_grnd = Ground(-1*gp) score_boards = Scoreboard() highScore = Scoreboard(width_screen * 0.78) counter = 0 cactusan = pygame.sprite.Group() smallBird = pygame.sprite.Group() skyClouds = pygame.sprite.Group() last_end_obs = pygame.sprite.Group() Cactus.containers = cactusan birds.containers = smallBird Cloud.containers = skyClouds rbtn_image,rbtn_rect = load_image('replay_button.png',35,31,-1) gmo_image,gmo_rect = load_image('game_over.png',190,11,-1) t_images,t_rect = load_sprite_sheet('numbers.png',12,1,11,int(11*6/5),-1) ado_image = pygame.Surface((22,int(11*6/5))) ado_rect = ado_image.get_rect() ado_image.fill(backgroundColor) ado_image.blit(t_images[10],t_rect) t_rect.left += t_rect.width ado_image.blit(t_images[11],t_rect) ado_rect.top = height_screen * 0.1 ado_rect.left = width_screen * 0.73 while not g_exit: while s_Menu: pass while not g_Over: if pygame.display.get_surface() == None: print("Couldn't load display surface") g_exit = True g_Over = True else: for event in pygame.event.get(): if event.type == pygame.QUIT: g_exit = True g_Over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: if gamer_Dino.rect.bottom == int(0.98 * height_screen): gamer_Dino.jumping = True if pygame.mixer.get_init() != None: soundOnJump.play() gamer_Dino.movement[1] = -1*gamer_Dino.jumpSpeed if event.key == pygame.K_DOWN: if not (gamer_Dino.jumping and gamer_Dino.dead): gamer_Dino.ducking = True if event.type == pygame.KEYUP: if event.key == pygame.K_DOWN: gamer_Dino.ducking = False for c in cactusan: c.movement[0] = -1*gp if pygame.sprite.collide_mask(gamer_Dino,c): gamer_Dino.dead = True if pygame.mixer.get_init() != None: soundOnDie.play() for p in smallBird: p.movement[0] = -1*gp if pygame.sprite.collide_mask(gamer_Dino,p): gamer_Dino.dead = True if pygame.mixer.get_init() != None: soundOnDie.play() if len(cactusan) < 2: if len(cactusan) == 0: last_end_obs.empty() last_end_obs.add(Cactus(gp,40,40)) else: for l in last_end_obs: if l.rect.right < width_screen*0.7 and random.randrange(0, 50) == 10: last_end_obs.empty() last_end_obs.add(Cactus(gp, 40, 40)) if len(smallBird) == 0 and random.randrange(0,200) == 10 and counter > 500: for l in last_end_obs: if l.rect.right < width_screen*0.8: last_end_obs.empty() last_end_obs.add(birds(gp, 46, 40)) if len(skyClouds) < 5 and random.randrange(0,300) == 10: Cloud(width_screen, random.randrange(height_screen / 5, height_screen / 2)) gamer_Dino.update() cactusan.update() smallBird.update() skyClouds.update() new_grnd.update() score_boards.update(gamer_Dino.score) highScore.update(highest_scores) if pygame.display.get_surface() != None: screenDisplay.fill(backgroundColor) new_grnd.draw() skyClouds.draw(screenDisplay) score_boards.draw() if highest_scores != 0: highScore.draw() screenDisplay.blit(ado_image, ado_rect) cactusan.draw(screenDisplay) smallBird.draw(screenDisplay) gamer_Dino.draw() pygame.display.update() timerClock.tick(FPS) if gamer_Dino.dead: g_Over = True if gamer_Dino.score > highest_scores: highest_scores = gamer_Dino.score if counter%700 == 699: new_grnd.speed -= 1 gp += 1 counter = (counter + 1) if g_exit: break while g_Over: if pygame.display.get_surface() == None: print("Couldn't load display surface") g_exit = True g_Over = False else: for event in pygame.event.get(): if event.type == pygame.QUIT: g_exit = True g_Over = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: g_exit = True g_Over = False if event.key == pygame.K_RETURN or event.key == pygame.K_SPACE: g_Over = False gameplay() highScore.update(highest_scores) if pygame.display.get_surface() != None: gameover_display_message(rbtn_image, gmo_image) if highest_scores != 0: highScore.draw() screenDisplay.blit(ado_image, ado_rect) pygame.display.update() timerClock.tick(FPS) pygame.quit() quit() def main(): isGameQuit = introduction_screen() if not isGameQuit: gameplay() main()
Reference Material and Link
Images
Sound Files
NOTE: Add all the above files under the “resource” folder:
Links
- PyGame Tutorial: Detailed tutorial
- List of Projects: PyGame projects
- Random Official Documentation: Click here
- Random Official Documentation: Click here
- OS library Official Documentation: Click here
- sys library Official Documentation: Click here
- PyGame YouTube Tutorial
Endnote
This concludes our tutorial. Dino Game in Python has reached its conclusion. We have tried our level best to explain each and every concept of Dino Game in Python. We at CopyAssignments strongly believe that only by creating projects in Python, can get a strong grip on the various concept and libraries of Python. It is now up to our readers to change the look and add various other functionalities in order to make this project suitable for your resume. We have already utilized a range of libraries and developed a number of fundamental to intermediate-level functions. We really hope that the structure of this article was easy to understand. Keep learning, doing, and growing until we return with another lesson. We appreciate you visiting our website.
Also Read:
В Google есть хорошо известное яйцо: когда у вас возникнут проблемы с вашей сетью, появится «маленькая игра динозавров».
(Если вы хотите играть в игру напрямую, вы можете ввести в адресную строку: Chrome: // Dino или ++ Buckle Skirt 609616831 бесплатно для получения исходного кода, чтобы играть самостоятельно)
Сегодня мы покажем вам мимическую «маленькую игру динозавров» с Python!
Не много ерунды, давайте начнем счастливо ~
Инструменты разработки:
Версия Python: 3.6.4
Связанный модуль:
Модуль Pygame; и некоторые модули Python.
Экологическое строительство
Установите Python и добавьте его в переменную среды, которые могут быть соответствующие модули, необходимые для установки PIP.
Посмотрите, как быстро
Просто запустите следующую команду на терминале:
python Game7.py
Введение кода
Вот принцип реализации игры.
Во -первых, мы выполняем необходимую работу по инициализации в игре:
#
pygame.init()
screen = pygame.display.set_mode(cfg.SCREENSIZE)
pygame.display.set_caption('T-Rex Rush-Pikachu of Charles')
# Импортировать все звуковые файлы
sounds = {}
for key, value in cfg.AUDIO_PATHS.items():
sounds[key] = pygame.mixer.Sound(value)
Затем давайте подумаем о том, какие элементы игры в игре:
Динозавр: Игрок контролирует, чтобы избежать препятствий на дороге;
тротуар: Фон игры;
облако: Фон игры;
Летающий дракон: Один из препятствий на дороге, маленькие динозавры умрут, когда столкнутся;
кактус: Один из препятствий на дороге, маленькие динозавры умрут, когда столкнутся;
Счет: Запишите текущий счет и исторический самый высокий балл.
Давайте определим эти классы игровых элементов по порядку. Для облаков, дорожного покрытия и кактуса определение очень простое. Нам нужно только загрузить соответствующие изображения элемента игры:
Затем напишите два внутренних метода, обновление и рисование в порядке. Два метода используются для непрерывно перемещать сцену влево, чтобы достичь эффекта анимации, когда мелкие динозавры движутся вперед и отображают сцену в соответствующей позиции интерфейса игры. В частности, реализация кода выглядит следующим образом:
'''пол'''
class Ground(pygame.sprite.Sprite):
def __init__(self, imagepath, position, **kwargs):
pygame.sprite.Sprite.__init__(self)
# #
self.image_0 = pygame.image.load(imagepath)
self.rect_0 = self.image_0.get_rect()
self.rect_0.left, self.rect_0.bottom = position
self.image_1 = pygame.image.load(imagepath)
self.rect_1 = self.image_1.get_rect()
self.rect_1.left, self.rect_1.bottom = self.rect_0.right, self.rect_0.bottom
# Определите некоторые необходимые параметры
self.speed = -10
'' 'Обновление пола' '
def update(self):
self.rect_0.left += self.speed
self.rect_1.left += self.speed
if self.rect_0.right < 0:
self.rect_0.left = self.rect_1.right
if self.rect_1.right < 0:
self.rect_1.left = self.rect_0.right
'' 'Нарисуйте пол на экране' '' '
def draw(self, screen):
screen.blit(self.image_0, self.rect_0)
screen.blit(self.image_1, self.rect_1)
'''облако'''
class Cloud(pygame.sprite.Sprite):
def __init__(self, imagepath, position, **kwargs):
pygame.sprite.Sprite.__init__(self)
# #
self.image = pygame.image.load(imagepath)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# Определите некоторые необходимые параметры
self.speed = -1
'' Нарисуйте облако на экран '' '' '
def draw(self, screen):
screen.blit(self.image, self.rect)
'' 'Обновление облака' '
def update(self):
self.rect = self.rect.move([self.speed, 0])
if self.rect.right < 0:
self.kill()
'''кактус'''
class Cactus(pygame.sprite.Sprite):
def __init__(self, imagepaths, position=(600, 147), sizes=[(40, 40), (40, 40)], **kwargs):
pygame.sprite.Sprite.__init__(self)
# #
self.images = []
image = pygame.image.load(imagepaths[0])
for i in range(3):
self.images.append(pygame.transform.scale(image.subsurface((i*101, 0), (101, 101)), sizes[0]))
image = pygame.image.load(imagepaths[1])
for i in range(3):
self.images.append(pygame.transform.scale(image.subsurface((i*68, 0), (68, 70)), sizes[1]))
self.image = random.choice(self.images)
self.rect = self.image.get_rect()
self.rect.left, self.rect.bottom = position
self.mask = pygame.mask.from_surface(self.image)
# Определите некоторые необходимые переменные
self.speed = -10
'' 'Нарисуйте на экран' '' ''
def draw(self, screen):
screen.blit(self.image, self.rect)
'' rebend '' '' '
def update(self):
self.rect = self.rect.move([self.speed, 0])
if self.rect.right < 0:
self.kill()
Определение платы оценки аналогично, но оно не должно двигаться, но его необходимо обновлять в режиме реального времени: текущий счет:
'' '' Parp Board '' ''
class Scoreboard(pygame.sprite.Sprite):
def __init__(self, imagepath, position, size=(11, 13), is_highest=False, bg_color=None, **kwargs):
pygame.sprite.Sprite.__init__(self)
# #
self.images = []
image = pygame.image.load(imagepath)
for i in range(12):
self.images.append(pygame.transform.scale(image.subsurface((i*20, 0), (20, 24)), size))
if is_highest:
self.image = pygame.Surface((size[0]*8, size[1]))
else:
self.image = pygame.Surface((size[0]*5, size[1]))
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# Некоторые необходимые переменные
self.is_highest = is_highest
self.bg_color = bg_color
self.score = '00000'
'' 'Set Score' '' '
def set(self, score):
self.score = str(score).zfill(5)
'' 'Нарисуйте на экран' '' ''
def draw(self, screen):
self.image.fill(self.bg_color)
for idx, digital in enumerate(list(self.score)):
digital_image = self.images[int(digital)]
if self.is_highest:
self.image.blit(digital_image, ((idx+3)*digital_image.get_rect().width, 0))
else:
self.image.blit(digital_image, (idx*digital_image.get_rect().width, 0))
if self.is_highest:
self.image.blit(self.images[-2], (0, 0))
self.image.blit(self.images[-1], (digital_image.get_rect().width, 0))
screen.blit(self.image, self.rect)
Приведенный выше код использует is_houghest переменные, чтобы отличить, используется ли оценка для записи максимальной оценки игры, или это всего лишь запись текущего балла. Причина этого различия заключается в том, что максимальный счет игры — логотип HI перед игрой, поэтому пространство занимает больше:
Определение Feilong немного сложнее, потому что оно должно быть не только двигаться влево, но и эффект постоянно раздувания крыльев. В частности, есть две фотографии Фейлонга:
Все, что вам нужно сделать, это переключать текущие картинки Flying Dragon каждый раз, чтобы достичь эффекта Flying Dragon Fanwings:
'' Flying Dragon ''
class Ptera(pygame.sprite.Sprite):
def __init__(self, imagepath, position, size=(46, 40), **kwargs):
pygame.sprite.Sprite.__init__(self)
# #
self.images = []
image = pygame.image.load(imagepath)
for i in range(2):
self.images.append(pygame.transform.scale(image.subsurface((i*92, 0), (92, 81)), size))
self.image_idx = 0
self.image = self.images[self.image_idx]
self.rect = self.image.get_rect()
self.rect.left, self.rect.centery = position
self.mask = pygame.mask.from_surface(self.image)
# Определите некоторые необходимые переменные
self.speed = -10
self.refresh_rate = 10
self.refresh_counter = 0
'' 'Нарисуйте на экран' '' ''
def draw(self, screen):
screen.blit(self.image, self.rect)
'' rebend '' '' '
def update(self):
if self.refresh_counter % self.refresh_rate == 0:
self.refresh_counter = 0
self.image_idx = (self.image_idx + 1) % len(self.images)
self.loadImage()
self.rect = self.rect.move([self.speed, 0])
if self.rect.right < 0:
self.kill()
self.refresh_counter += 1
'' 'Картинки загружены в текущее состояние' '' '
def loadImage(self):
self.image = self.images[self.image_idx]
rect = self.image.get_rect()
rect.left, rect.top = self.rect.left, self.rect.top
self.rect = rect
self.mask = pygame.mask.from_surface(self.image)
Наконец, нам нужно определить маленьких динозавров, которые являются самым сложным классом игрового эльфа. Он имеет три состояния поклона, прыжков и общего прогресса. Для поклонения:
Вам просто нужно переключить две снимки лука дракона на одни и те же крылья, чтобы достичь эффекта, когда работают маленькие динозавры. Это также похоже на нормальное состояние:
Что касается состояния прыжков, мы можем смоделировать формулу верхнего броска и свободного падающего движения средней школы, тем самым рассчитывая положение мелких динозавров в вертикальном направлении. В частности, реализация кода выглядит следующим образом:
'' 'Маленькие динозавры' '
class Dinosaur(pygame.sprite.Sprite):
def __init__(self, imagepaths, position=(40, 147), size=[(44, 47), (59, 47)], **kwargs):
pygame.sprite.Sprite.__init__(self)
# Импортировать все фотографии
self.images = []
image = pygame.image.load(imagepaths[0])
for i in range(5):
self.images.append(pygame.transform.scale(image.subsurface((i*88, 0), (88, 95)), size[0]))
image = pygame.image.load(imagepaths[1])
for i in range(2):
self.images.append(pygame.transform.scale(image.subsurface((i*118, 0), (118, 95)), size[1]))
self.image_idx = 0
self.image = self.images[self.image_idx]
self.rect = self.image.get_rect()
self.rect.left, self.rect.bottom = position
self.mask = pygame.mask.from_surface(self.image)
# Определите некоторые необходимые переменные
self.init_position = position
self.refresh_rate = 5
self.refresh_counter = 0
self.speed = 11.5
self.gravity = 0.6
self.is_jumping = False
self.is_ducking = False
self.is_dead = False
self.movement = [0, 0]
'''Прыжок'''
def jump(self, sounds):
if self.is_dead or self.is_jumping:
return
sounds['jump'].play()
self.is_jumping = True
self.movement[1] = -1 * self.speed
'' 'Low Head' '' '
def duck(self):
if self.is_jumping or self.is_dead:
return
self.is_ducking = True
'' 'Не склонись головой' '' '
def unduck(self):
self.is_ducking = False
'' 'умер' '' '' '
def die(self, sounds):
if self.is_dead:
return
sounds['die'].play()
self.is_dead = True
'' Нарисуйте динозавр на экран '' ''
def draw(self, screen):
screen.blit(self.image, self.rect)
'' 'Картинки загружены в текущее состояние' '' '
def loadImage(self):
self.image = self.images[self.image_idx]
rect = self.image.get_rect()
rect.left, rect.top = self.rect.left, self.rect.top
self.rect = rect
self.mask = pygame.mask.from_surface(self.image)
'' Обновление маленьких динозавров '' ''
def update(self):
if self.is_dead:
self.image_idx = 4
self.loadImage()
return
if self.is_jumping:
self.movement[1] += self.gravity
self.image_idx = 0
self.loadImage()
self.rect = self.rect.move(self.movement)
if self.rect.bottom >= self.init_position[1]:
self.rect.bottom = self.init_position[1]
self.is_jumping = False
elif self.is_ducking:
if self.refresh_counter % self.refresh_rate == 0:
self.refresh_counter = 0
self.image_idx = 5 if self.image_idx == 6 else 6
self.loadImage()
else:
if self.refresh_counter % self.refresh_rate == 0:
self.refresh_counter = 0
if self.image_idx == 1:
self.image_idx = 2
elif self.image_idx == 2:
self.image_idx = 3
else:
self.image_idx = 1
self.loadImage()
self.refresh_counter += 1
После определения игровых эльфов мы можем их создавать:
# Определите некоторые необходимые элементы и переменные в игре
score = 0
score_board = Scoreboard(cfg.IMAGE_PATHS['numbers'], position=(534, 15), bg_color=cfg.BACKGROUND_COLOR)
highest_score = highest_score
highest_score_board = Scoreboard(cfg.IMAGE_PATHS['numbers'], position=(435, 15), bg_color=cfg.BACKGROUND_COLOR, is_highest=True)
dino = Dinosaur(cfg.IMAGE_PATHS['dino'])
ground = Ground(cfg.IMAGE_PATHS['ground'], position=(0, cfg.SCREENSIZE[1]))
cloud_sprites_group = pygame.sprite.Group()
cactus_sprites_group = pygame.sprite.Group()
ptera_sprites_group = pygame.sprite.Group()
add_obstacle_timer = 0
score_timer = 0
Затем напишите главную петлю игры:
#
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE or event.key == pygame.K_UP:
dino.jump(sounds)
elif event.key == pygame.K_DOWN:
dino.duck()
elif event.type == pygame.KEYUP and event.key == pygame.K_DOWN:
dino.unduck()
screen.fill(cfg.BACKGROUND_COLOR)
# -Random Добавить облако
if len(cloud_sprites_group) < 5 and random.randrange(0, 300) == 10:
cloud_sprites_group.add(Cloud(cfg.IMAGE_PATHS['cloud'], position=(cfg.SCREENSIZE[0], random.randrange(30, 75))))
# -Randomly добавить кактус/летающий дракон
add_obstacle_timer += 1
if add_obstacle_timer > random.randrange(50, 150):
add_obstacle_timer = 0
random_value = random.randrange(0, 10)
if random_value >= 5 and random_value <= 7:
cactus_sprites_group.add(Cactus(cfg.IMAGE_PATHS['cacti']))
else:
position_ys = [cfg.SCREENSIZE[1]*0.82, cfg.SCREENSIZE[1]*0.75, cfg.SCREENSIZE[1]*0.60, cfg.SCREENSIZE[1]*0.20]
ptera_sprites_group.add(Ptera(cfg.IMAGE_PATHS['ptera'], position=(600, random.choice(position_ys))))
# -Update Elements Game
dino.update()
ground.update()
cloud_sprites_group.update()
cactus_sprites_group.update()
ptera_sprites_group.update()
score_timer += 1
if score_timer > (cfg.FPS//12):
score_timer = 0
score += 1
score = min(score, 99999)
if score > highest_score:
highest_score = score
if score % 100 == 0:
sounds['point'].play()
if score % 1000 == 0:
ground.speed -= 1
for item in cloud_sprites_group:
item.speed -= 1
for item in cactus_sprites_group:
item.speed -= 1
for item in ptera_sprites_group:
item.speed -= 1
# -Проверка Impact
for item in cactus_sprites_group:
if pygame.sprite.collide_mask(dino, item):
dino.die(sounds)
for item in ptera_sprites_group:
if pygame.sprite.collide_mask(dino, item):
dino.die(sounds)
# -Порада игровых элементов на экране
dino.draw(screen)
ground.draw(screen)
cloud_sprites_group.draw(screen)
cactus_sprites_group.draw(screen)
ptera_sprites_group.draw(screen)
score_board.set(score)
highest_score_board.set(highest_score)
score_board.draw(screen)
highest_score_board.draw(screen)
# -Update экран
pygame.display.update()
clock.tick(cfg.FPS)
# -Будь окончилась игра
if dino.is_dead:
break
Логика основной петли игры очень проста, то есть игра в игровом экране каждого кадра, нам всем нужно обнаружить работу игрока. Если игрок нажимает кнопку пространства или клавишу ↑, то маленький динозавр прыгает. Если игрок нажимает кнопку ↓, то маленький динозавр смотрит вниз, в противном случае маленькие динозавры будут нормально спешить вперед.
Затем в игре мы случайным образом генерируем игровые сцены и препятствия облака, летающего дракона и кактуса и движемся налево на той же скорости, что и дорога, тем самым достигая визуальных эффектов мелких динозавров вправо. В процессе движения нам нужно провести испытание на столкновение маленьких динозавров и кактусов, маленьких динозавров и летающего дракона. Когда маленькие динозавры столкнулись с этими препятствиями, маленькие динозавры умерли, и игра в этой игре закончилась.
Следует отметить, что мы должны использовать функцию coloride_mask для выполнения более точного обнаружения столкновений вместо предыдущей функции Color_rect:
То есть, когда минимальный внешний прямоугольник двух целей перекрывается, Collide_Rect определит, что эти две цели имеют столкновения. Это, очевидно, неразумно и принесет плохой игровой опыт игрокам.
Кроме того, каждый раз, когда счет увеличивается, мы добавляем небольшую сцену и препятствия влево (то есть увеличивая скорость маленьких динозавров вправо) в качестве оригинальной игры и препятствий.
Наконец, можно связать все текущие элементы игры с экраном и обновить текущий экран.
Вероятно, это так.
Здесь я все еще рекомендую группу по разработке и обмену (QQ) Python Development and Exchange (QQ): 609616831, группа разработана Python. Если вы изучаете Python, вы можете присоединиться. Dry Goods (только разработка программного обеспечения Python), включая новейший 2020 год. Python Advanced Data и Advanced Development Rutorial, добро пожаловать в расширенную нейтрализацию и небольшой партнер, который хочет проникнуть в Python. Код может ввести группу бесплатно