Сборка через командную строку
ПРИМЕЧАНИЕ Сборка исполняемых пакетов через командную строку доступна только для определенных лицензий, поэтому посмотрите на этой странице, чтобы узнать, поддерживает ли это ваша лицензия. Для всех остальных лицензий вы можете запускать и отлаживать свой проект через командную строку без каких-либо ограничений.
В дополнение к сборке проекта через IDE , GameMaker позволяет создавать проекты через интерфейс командной строки, используя множество опций и команд, описанных ниже. Вы можете использовать это для сборки проекта, его тестирования и развертывания на нескольких платформах с помощью одного пакетного файла, а также для настройки непрерывной интеграции с помощью сервера автоматизации, такого как Jenkins.
Это делается путем запуска исполняемого файла Igor.exe , находящегося в вашей папке времени выполнения, и передачи опций и команд, перечисленных на этой странице. На компьютере Windows он будет находиться в папке C:\ProgramData\GameMaker\Cache\runtimes\runtime-[version]\bin , а на Mac — в папке /Users/Shared/GameMaker/Cache/runtimes/runtime-[version]/bin .
Игорь CI Билдинг
Настройка
Чтобы настроить сборку CI на машине, вам нужно сделать следующее:
- Установите GameMaker и необходимые программы выполнения
- Сборка проектов через IDE для требуемых целей, чтобы убедиться, что они работают нормально
- Тестовая сборка из командной строки (см. примеры ниже)
- Создайте пакетный файл, который будет выполнять сборку, требуемую в рамках задачи (проверьте это из командной строки)
- Настройте среду CI (это зависит от того, как вы собираетесь собирать свои игры); мы предлагаем использовать Jenkins
- Настройте задачу CI и убедитесь, что все необходимые условия настроены (например, синхронизация контроля исходных текстов с вашим проектом).
- Подключите пакетный файл к задаче CI и проведите тестирование в среде Jenkins
Примечания
- Некоторые платформы могут иметь проблемы с длиной путей к файлам, в этом случае вам потребуется subst виртуальных дисков на вашем ПК (как это делает IDE ), прежде чем передавать их в команды.
- Некоторые платформы (в частности, Android ) автоматически subst диск во время сборки, поэтому в случае ошибки вам может потребоваться вручную очистить его.
Опции
Ниже перечислены опции, которые можно использовать при запуске исполняемого файла Igor :
Примеры
Ниже приведены примеры команд сборки для всех платформ:
Очистка проекта Opera GX:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — OperaGX Clean
Запуск Opera GX:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — OperaGX Run
Уборка Windows проект:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — Windows Clean
Построение VM для Windows — Run , PackageZip и PackageNsis :
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — Windows Run
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] — Windows PackageZip
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] — Windows PackageNsis
Создание YYC для Windows — Run , PackageZip и PackageNsis :
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /platform=YYC — Windows Run
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC — Windows PackageZip
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC — Windows PackageNsis
Уборка macOS проект:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — Mac Clean
Создание виртуальной машины для macOS на компьютере Mac:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] — Mac Package
Обратите внимание, что на Mac для запуска Игоря необходимо использовать mono, поэтому перед всеми командами нужно писать mono , например: mono Igor.exe [options]
Создание виртуальной машины для macOS на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — Mac Package
Создание YYC для macOS на компьютере Mac:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC — Mac Package
Построение YYC для macOS во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — Mac Package
Уборка Linux проект:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — Linux Clean
Создание виртуальной машины для Linux на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — Linux Package
Построение YYC для Linux во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — Linux Package
Чистка проекта HTML5:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — HTML5 Clean
Создание папки HTML5 (обязательно укажите целевую папку с помощью /tf) :
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_folder] — HTML5 folder
Создание виртуальной машины для iOS на компьютере Mac:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] — ios Package
Создание виртуальной машины для iOS на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — ios Package
Создание YYC для iOS на компьютере Mac:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC — ios Package
Построение YYC для iOS во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — ios Package
Уборка Android проект:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — Windows Clean
Создание Android APK с помощью VM:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] — Android Package
Создание Android APK с помощью YYC:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC — Android Package
Уборка tvOS проект:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — tvos Clean
Создание виртуальной машины для tvOS на компьютере Mac:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] — tvos Package
Создание виртуальной машины для tvOS на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — tvos Package
Создание YYC для tvOS на компьютере Mac:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC — tvos Package
Построение YYC для tvOS во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — tvos Package
Проект «Очистка PS4»:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — ps4 Clean
Сборка VM для PS4 на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — ps4 Package
Создание YYC для PS4 во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — ps4 Package
Чистка проекта PS5:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — ps5 Clean
Создание виртуальной машины для PS5 на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — ps5 Package
Создание YYC для PS5 во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — ps5 Package
Чистка проекта Xbox One:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — XBoxOne Clean
Создание VM для Xbox One на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — XBoxOne Package
Создание YYC для Xbox One во время работы на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — XBoxOne Package
Чистка проекта Xbox Series X/S:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — XBoxOneSeriesXS Clean
Создание виртуальной машины для Xbox Series X/S:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — XBoxOneSeriesXS Package
Создание YYC для Xbox Series X/S:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — XBoxOneSeriesXS Package
Уборка Switch проект:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] — Switch Clean
Создание виртуальной машины для Switch на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /device=[device_IDE_Name] — Switch Package
Создание виртуальной машины для YYC на ПК:
Igor.exe /uf=[user_folder] /rp=[runtime_path] /project=[project_YYP_file] /cache=[cache_dir_path] /temp=[temp_dir_path] /of=[output_folder_filename] /tf=[target_file] /platform=YYC /device=[device_IDE_Name] — Switch Package
Время выполнения Игоря
Здесь перечислены опции, которые могут быть использованы при работе с программой Igor:
| Вариант | Описание |
|---|---|
| /uf=[user_folder] | Установите пользовательскую папку, используемую для получения информации о лицензии, чтобы GameMaker знал, какие модули могут быть установлены |
| /lf=[license_file] | Установите прямой путь к файлу лицензии, может использоваться как альтернатива установке пользовательской папки ( /uf ). |
| /ru=[runtime_url] | Установите URL для получения информации о времени выполнения (по умолчанию используется стабильный выпуск). |
| /rp=[runtime_root] | Установите локальную папку установки времени выполнения для списка установленных времен выполнения или установки новых времен выполнения |
Здесь перечислены команды, которые можно использовать в среде выполнения Igor:
Синтаксис:
Вы можете указать каталог для поиска, но если он не указан, то по умолчанию будет использоваться текущий каталог.
version может быть либо строкой, используемой для поиска по названиям фидов (например: «739» покажет информацию для всех сборок, содержащих 739 в номере версии), либо all , чтобы показать информацию для всех фидов.
При этом будут отмечены все файлы, контрольные суммы которых не совпадают, отсутствующие файлы и файлы, которых не должно быть.
Игорь Тестинг
Вот варианты, которые можно использовать для тестирования ваших сборок с помощью Igor:
Существует одна команда, которую вы можете использовать для запуска тестов с помощью Igor:
Igor.exe Tests RunTests [test_directory/test_filename]
Вы должны указать либо каталог тестирования, либо имя файла тестирования.
- Если это каталог, Игорь будет искать файл с именем tests.json в каталоге
- Если файл не найден, Игорь будет рекурсивно искать файлы .yyz и .yyp в каталоге
- Каждый найденный проект будет собран и запущен на Windows.
- Он будет ждать, пока бегунок не дойдет до главного цикла
- Тест пройден, если проект все еще запущен по истечении времени ожидания.
- Тест не пройден, если проект не скомпилировался в течение тайм-аута или завершился до истечения времени ожидания.
Файл Tests.json
Формат
Файл tests.json должен иметь следующий формат:
- Файл JSON должен содержать массив объектов
- Каждый объект должен описывать один тест
- Объект test должен содержать следующие ключи:
- name : Имя, используемое для сообщения о том, прошел тест или нет.
- path : Путь к файлу или папке .yyp , .yyz , .gml , .js для теста.
- command : Команда Igor для теста ( Run , CreatePackage и т.д., как описано выше)
- platform : Передается непосредственно в Igor для выполнения теста
- device : Имя устройства из файла devices.json
- target : Цель в формате » PLATFORM|TARGET» , например: » Windows|Local»
- timeout : Таймаут для всего теста, включая компиляцию и выполнение; если он превышен, тест будет остановлен и помечен как неудачный.
- waittime : Длительность ожидания после входа в основной цикл для принятия решения о том, прошел ли тест.
- owner : адрес электронной почты пользователя, которому будет отправлено письмо в случае неудачи теста.
Пример
Вот пример файла tests.json :
[
<
«name» : «Game Idea Windows»,
«path» : «C:/scratch/GameIdea.yyz»,
«platform» : «Windows»,
«command» : «Run»,
«timeout» : «500»,
«waittime» : «30»
>,
<
«name» : «Platformer Game Windows»,
«path» : «C:/Users//Documents/GameMaker/Platformer Game/PlatformerGame.yyp»,
«target» : «Windows|Local,HTML5|selenium:firefox,PS4|Default»,
«command» : «Run»
>,
<
«name» : «Puzzle Game Windows»,
«path» : «C:/Users//Documents/GameMaker/Puzzle Game/PuzzleGame.yyp»,
«platform» : «HTML5»,
«command» : «Run»,
«device» : «selenium:chrome»
>
]Параметры командной строки
При создании игр важно протестировать исполняемый файл, чтобы убедиться, что окончательная компиляция корректна и не содержит непредвиденных ошибок или различий. Однако создание исполняемого файла означает, что, поскольку у вас нет доступа ни к консоли отладки, ни к окну компилятора, может быть трудно определить и отладить любые проблемы, которые вы можете найти. Однако для этого есть решение — использовать параметры командной строки для установки определенных свойств при запуске исполняемой версии игры, что, в свою очередь, позволяет создавать выходные журналы и тестировать другие специальные ситуации.
ПРИМЕЧАНИЕ Это доступно только для стандартного целевого модуля Windows .
Чтобы это заработало, сначала необходимо создать исполняемый файл игры Windows и установить его на тестовой машине. После этого необходимо создать ярлык игры на рабочем столе. Именно здесь мы будем добавлять параметры, которые будут передаваться на *.exe (чтобы создать ярлык, щелкните правой кнопкой мыши на исполняемом файле игры и выберите Отправить на > Рабочий стол (создать ярлык)).
После создания ярлыка вы можете использовать любой из следующих параметров в целевом пути:
-noaudio : Это отключает все звуки в вашей игре.
-inawindow : Заставляет игру запускаться в окне, даже если она запущена в полноэкранном режиме.
-output filename> : Отправляет вывод консоли на имя файла, включая любые отладочные сообщения, которые есть в вашей кодовой базе.
-debugoutput filename> : Отправляет консольный вывод в указанный файл, исключая любые пользовательские отладочные сообщения, но включая дополнительную информацию из бегущей строки для сообщения об ошибке.
-software : Заставляет игру использовать программную обработку вершин вместо аппаратной для рендеринга игровой графики. Это особенно полезно для тех ПК, которые используют встроенный чипсет Intel GFX или демонстрируют проблемы с отображением на старых машинах.
-intel : Это включит исправление для плохих драйверов Intel, независимо от того, какой GPU используется. Это решение для плохих драйверов замедлит работу, что даст заметное снижение производительности вашей игры, поэтому если вам не нужно исправление, не используйте его.
-vanillaGFX : Использование этого параметра отключает любую проверку производителя драйвера и может быть очень полезно для тестирования вашей игры «как есть».
Стоит отметить, что filename> в -output и -debugoutput при желании может быть одним и тем же, так как файл открывается и закрывается при каждой записи.
Теперь вы должны перейти к тому месту, где вы создали ярлык исполняемого файла игры, а затем щелкнуть на нем правой кнопкой мыши и выбрать пункт «Свойства».
Откроется окно, аналогичное приведенному выше. Здесь вы можете добавить в Target path вашу командную строку. Просто прокрутите до конца заданного пути, добавьте пробел, а затем строку параметров. Так, например, для создания вывода файла отладки вы можете сделать следующее:
D:\Users\Me\GameMaker\CustomLoadBar-Default-1.0.0.0\CustomLoadBar.exe -debugoutput debug.txt
В результате будет создан файл под названием » debug.txt » в том же месте, что и исполняемый файл (не в месте расположения ярлыка, а там, где установлена игра) с информацией об отладке и компиляторе вашей готовой игры. Нажмите на «OK», а затем используйте ярлык для запуска игры, чтобы она использовала эти параметры и помогла в отладке.
Более подробную информацию о том, как отладить вашу игру, вы найдете в разделе руководства » Отладка».
Бегущая строка YoYo также имеет специальный параметр командной строки:
-game filename> : загружает заданный файл игры
Зачем вам это нужно? Обычно вы этого не делаете, но если вы хотите иметь более одной версии вашей игры одновременно, это можно сделать с помощью этого метода, что значительно упрощает сравнительные тесты A/B. С помощью этого метода вы можете провести фокус-тестирование с кем-то другим или, как дизайнер, получить лучшее представление о вещах, проводя тесты бок о бок.
Чтобы настроить его, необходимо знать несколько вещей. Для начала вам понадобится путь к самому Runner. Его можно найти в папке Program Data вашей ОС Windows (по умолчанию она может быть скрыта, поэтому вам придется изменить свойства просмотра, чтобы увидеть ее), а типичный формат выглядит следующим образом:
Обратите внимание, что папка runtime_version будет зависеть от установленных у вас в данный момент времен выполнения. Вам также понадобится путь к файлу yourgame>.win , поскольку именно на него мы будем указывать бегунку. Самый простой способ найти этот путь — зайти в папку Temp (обычно она находится по адресу %localappdata%\Temp\GameMakerStudio\\ ) и просто найти *.win , так как это покажет вам все доступные файлы, и вы сможете просто выбрать тот, к которому вам нужно получить полный путь.
Теперь мы могли бы создать ярлык для бегущей строки, как мы это сделали для исполняемого файла, а затем изменять параметры по мере необходимости, но это означает необходимость открывать и редактировать ее между тестами, что сводит на нет цель использования этой функциональности для тестирования бок о бок. Вместо этого мы создадим пару маленьких «пакетных» файлов, чтобы справиться с этой задачей.
Для этого сначала создайте новый текстовый документ на рабочем столе и откройте его в Блокноте (или любом другом текстовом редакторе). Добавьте следующее:
START C:\ProgramData\GameMaker\Cache\runtimes\\windows\Runner.exe -game \.win
Вам нужно установить временный путь к файлу игры *.win , а затем сохранить его как » GM_Test_1.bat » на рабочем столе (имейте в виду, что Блокнот автоматически сохранит файл *.txt , если вы не выберете «все файлы» в опциях диалога сохранения). Пример того, как должна выглядеть ваша конечная команда:
START C:\ProgramData\GameMaker\Cache\runtimes\runtime-9.9.1.199\windows\Runner.exe -game D:\Users\Mark\AppData\Local\Temp\GameMaker\AlphaDog_Reboot\AlphaDog_Reboot.win
Теперь создайте другой текстовый файл и добавьте ту же строку, только на этот раз укажите путь ко второй версии игры, с которой вы хотите провести тестирование бок о бок, и сохраните его как » GM_Test_2.bat «. Вы можете дважды щелкнуть по любому из этих файлов , чтобы открыть различные версии вашей игры, а наличие этих файлов в виде пакетных файлов означает, что вы можете легко открыть их снова, чтобы отредактировать их и изменить тестируемую игру.
Создаю игру на командной строке
Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.
9.9K открытий
Ещё в 2017, когда я писал курсовую работу, где надо было создать что-то на Java, я понял, что буду создавать текстовую игру. И я создал. Она была размером примерно на 3000 строк кода. Операторов If в коде было дофига. Ну так вот я решил вспомнить былые времена, но написать не на джаве, а на моей любимой командной строке Windows.
Создаём текстовый файл, вводим название игры(игру я назвал Adventure of the past), затем сохраняем его с расширением bat или cmd. Я привык к bat. Затем украшаем наш батник, меняя цвет и название. Также меняем кодировку на 1251.
Статистика
Я создал ещё один батник. В нём будут хранится статы героя. Всего их 6: уровень, ловкость, удача, красноречие, сила, количество жизней и урон. Изначально они все равны 1(кроме хп и урона), но с увеличением уровня игрок сможет прокачивать характеристики. Для того, чтобы значение характеристик на экране обновлялось, я сделал цикл, печатал характеристики, а потом очищал экран.
Я создал ещё один батник для эффектов. Допустим если главному герою отрезали руку, наложили проклятье или отравили, это будет отображаться в окне с эффектами.
Также я сделал отдельно окно торговли. В статистику я также добавил показатель количества денег. Также я русифицировал все показатели
Интвентарь
И последний батник, который я сегодня добавил это инвентарь. Там будут отображаться вещи игрока
Игра на данный момент весит 20 КБ. В каждом следующем посте я буду писать её вес, чтобы наблюдать прогресс так сказать) В этом посте я хотел написать о карте и о том, как будет генерироваться подземелье, однако эта тема оказалась немного посложнее и шире, чем я думал. Поэтому про это я наверное расскажу в отдельном посте. Всем пока, подписывайтесь на блог, здесь прикольно :3
Полноценная игра, сделанная мною в обычной windows консоли
Я вдохновился идеей сделать что-то простое на первый взгляд, но в тоже время интересное в плане разработки. Мне в голову пришла мысль, сделать игру в консоли, это интересно в плане разработки, и посмотреть на это будет интересно даже просто со стороны, как например на эту игру.
Игровой движок
Итак, начнем с того как игра устроена в корне, и какова ее идея работы.
Сначала я определился с тем, как будет выводится игровой мир в консоль. Понял, что для вывода игровых объектов, нам нужен список, который хранит в себе другие списки, которые хранят в себе символы, которые в последующем выводятся на игровое поле циклом for .
Вот таким кодом:
for line_words in OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="")Здесь мы рисуем все символы из списка, и переходим на новую строку, чтоб нарисовать следующий список символов.
Вот так выглядит переменная, которая хранит списки символов:
Тут cразу мы получаем решение как нам выводить по X и Y объекты, мы теперь можем указывать:
X — символ в списке
Y — список в котором содержится X
Тем самым нарисовать на поле какой-нибудь символ. Это мы будем использовать при рисовании игровых объектов.Можем попробовать нарисовать на поле «мяч», подставив на место X и Y букву «O».
Для этого напишем такой код:
import os OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] OUTPUT_IMAGE[4][6] = "O" os.system("cls||clear") for line_words in OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="")
И вот, мы нарисовали объект на нашем игровом поле. Правда координаты X и Y получились не классическими. Во первых, мы указываем сначала Y, потом X, что не совсем по классике, во вторых, координата Y должна увеличиваться чтоб поднять объект, у нас же наоборот, она должна уменьшатся.
График X и Y в игре:

Эту особенность тоже придется учитывать в дальнейшем, когда мы будем делать столкновение объектов в консоли.
Теперь мы можем попробовать перемещать наш объект по игровому полю, т.е. создавать движение.
Нам понадобится очищать консоль, для того чтобы стирать старую картинку игрового поля.
Это мы сделаем командой:os.system("cls||clear")Также, нам понадобится переопределять переменную OUTPUT_IMAGE , для того чтобы очищать все ранее нарисованные в игровом поле объекты.
Также нам все это нужно будет поместить в while True .
Добавим в while True функцию time.sleep(1) , для того чтобы ограничить FPS.
И вот, код нарисовался на глазах:
from time import sleep from os import system OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] x = 0 y = 0 while True: sleep(1) system("cls||clear") OUTPUT_IMAGE[y][x] = "O" for line_words in OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="") y += 1 x += 1 OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ]Теперь у нас есть возможность распределять объекты по полю.
Правда эти объекты слишком примитивы, и надо бы научится рисовать сложные объекты по типу игроков, домов, еды…
Для того чтобы нарисовать сложный объект, нам нужно понять и придумать, как нарисовать объект указав лишь один раз его X и Y.
Для этого нам понадобится функция, которая принимает картинку (символы), X, Y;
def SetImage(image: str, x: int, y: int): passТеперь нам нужно ее реализовать. Для этого нужно решить, как нарисовать изображение, которое растягивается по оси X и Y, я придумал так:
рисовать объект разделяя его на символы, и как только встретится символ «\n», прибавить ось Y.Ось Y как мы уже говорили неправильная, перевернутая наоборот, поэтому к ней мы прибавляем чтобы опустить объект.
Пример изображения который рисуется по моему принципу:
image = " O\n'|'\n |"#игрокТеперь давайте это опишем в нашей функции:
def SetImage(x: int, y: int, image: str): x_start = x x = x y = y for word in image: if word == "\n": x = x_start y += 1 else: x += 1 try: OUTPUT_IMAGE[y][x] = word except IndexError: breakДобавим try: except() для того чтобы небыло ошибок если объект имеет X и Y слишком мальенькие или слишком большие.
x_start Это X, с которого нужно начинать рисовать при увеличении Y (при символе «\n»)
Теперь мы можем использовать нашу функцию, падать в нее X и Y, и картинку которую нужно рисовать:
from time import sleep from os import system OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] def SetImage(x: int, y: int, image: str): x_start = x x = x y = y for word in image: if word == "\n": x = x_start y += 1 else: x += 1 try: OUTPUT_IMAGE[y][x] = word except IndexError: break while True: sleep(1) system("cls||clear") SetImage(x=3,y=4,image=" O\n'|'\n |") for line_words in OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="") OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ]И вот что у нас получилось:

абсолютно также как и шарик который мы рисовали, его можно двигать по оси X и Y.
from time import sleep from os import system OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] px = 0 py = 0 def SetImage(x: int, y: int, image: str): x_start = x x = x y = y for word in image: if word == "\n": x = x_start y += 1 else: x += 1 try: OUTPUT_IMAGE[y][x] = word except IndexError: break while True: sleep(1) system("cls||clear") SetImage(x=px,y=py,image=" O\n'|'\n |") for line_words in OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="") px += 1 py += 1 OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ]И вот, у нас уже двигает игрок по карте.
Тут мы сделали уже многое, уже есть игрок, уже есть карта, и казалось бы, уже можно сделать игру, но нет. Нам нужна функция высчета столкновений объектов, ведь какая это игра без взаимодействий объектов. Поэтому приступим.
Для начала нам нужно сделать функцию получение широты и высоты объекта, для того чтобы расчитать его хитбокс.
Итак, функцию я решил сделать по такой логике:
X — хитбокс объекта по X ширине, это самое больше количество символов между знаками «\n» в картинке
Y — хитбокс по Y это число символов «\n» в картинкеПо этой логике не сложно сделать функцию, которая принимает картинку, считает у нее все символы между «\n», и выбирает из этого самое больше число символов — получилась широта.
И если посчитать символы «\n», как я уже написал — получится высота.Функция получилась такой:
def GetSizeObject(img: str): w = 0 weights = [] h = [word for word in img if word == "\n"] for word in img: if word == "\n": weights.append(w) w = 0 else: w += 1 try: return except ValueError: return
Зачем здесь ValueError except?
Он здесь чтобы предотвратить ошибку при запуске игры.Итак, давайте нарисуем нашего игрока, и вычислил его ширину и длину.
код с рисовкой и вычислением широты и высоты
from time import sleep from os import system OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] px = 3 py = 3 def SetImage(x: int, y: int, image: str): global OUTPUT_IMAGE x_start = x x = x y = y for word in image: if word == "\n": x = x_start y += 1 else: x += 1 try: OUTPUT_IMAGE[y][x] = word except IndexError: break def GetSizeObject(img: str): w = 0 weights = [] h = [word for word in img if word == "\n"] h.append(1) for word in img: if word == "\n": weights.append(w) w = 0 else: w += 1 try: return except ValueError: return player_image = " O\n'|'\n |" def draw(): global OUTPUT_IMAGE sleep(1) system("cls||clear") for line_words in OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="") OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] while True: SetImage(x=px,y=py,image=player_image) print(GetSizeObject(img=player_image)) draw()Ура! у нас есть функция вычисления широты и высоты, теперь нам предстоит сделать функцию вычисления хитбокса и столкновений объектов.
Вспомним что у нас система координат не классическая, поэтому классическую функцию увы использовать не сможем, придется делать свою. Я для этого я нарисовал на графике 2 квадрата, которые сталкиваются, и по этой картинке можно придумать условие по которому будет высчитано столкновение
Для простоты понимания я нарисовал хитбоксы, Т.Е. квадраты:

Логика на словах
Для вычисления мы подаем
x — X первого объекта
y — Y первого объекта
h — Высота первого объекта
w — Широта первого объекта
x2 — X второго объекта
y2 — Y второго объекта
h2 — Высота второго объекта
w2 — Широта второго объектаесли
y больше y2 — h2 + h и y — h меньше чем y2 + h2 — h
или же
y2 больше y — h + h2 и y2 — h2 меньше чем y + h — h2Зачем проверять 2 раза?
Мы сделали проверку 2 раза, просто из-за того чтобы посмотреть на столкновение/не столкновение с разных объектов.
Объекты соприкасаются по оси Y
Дальше смотри соприкосновение по оси X, она такое же что и по оси Y, но вместо y — x , а вместо h — w .
x больше x2 — w2 + w и x — w меньше чем x2 + w2 — w
x2 больше x — w + w2 и x2 — w2 меньше чем x + w — w2
объекты соприкасаются по оси X
Логика в коде
Логика такая же как и на словах, только в функции:
def IsClash(x: int, y: int, h: int, w: int,x2: int, y2: int, h2: int, w2: int): if (y >= y2 - h2 + h and y - h = y - h + h2 and y2 - h2 = x2 - w2 + w and x - w = x - w + w2 and x2 - w2Функция возвращает True если объекты соприкасаются, и False если нет.
Я нарисовал дополнительно куб на нашем игровом поле, для того чтобы игроку было с кем сталкиваться.
И попробовал как работает функция высчета столкновения.
Вот игрок соприкасается и кубом:

А вот нет соприкасаются:

Код соприкосновения
Это полный код соприкосновения/не соприкосновения:
from time import sleep from os import system OUTPUT_IMAGE = [ [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",], ] def SetImage(x: int, y: int, image: str): global OUTPUT_IMAGE x_start = x x = x y = y for word in image: if word == "\n": x = x_start y += 1 else: x += 1 try: OUTPUT_IMAGE[y][x] = word except IndexError: break def GetSizeObject(img: str): w = 0 weights = [] h = [word for word in img if word == "\n"] h.append(1) for word in img: if word == "\n": weights.append(w) w = 0 else: w += 1 try: return except ValueError: return def IsClash(x: int, y: int, h: int, w: int,x2: int, y2: int, h2: int, w2: int): if (y >= y2 - h2 + h and y - h = y - h + h2 and y2 - h2 = x2 - w2 + w and x - w = x - w + w2 and x2 - w2Теперь у нас все стартовые функции для игры, собственно их основе я писал свою игру.
Игра
Идея игры в такая:
Есть игрок, вокруг появляется еда, которую он вынужден собрать чтоб не умереть. В игре также присутствуют функции: поднять еду, положить в инвентарь, съесть ее из инвентаря, положить на пол предмет из инвентаря
Я начал с того что сделал игровой цикл в 3 строчки, это просто While True :
from time import sleep while True: sleep(0.1)Дальше я посчитал нужным, создать класс, в котором будут хранится все функции будующий объектов. Поэтому создал файл main.py и папку lib, в которую поместил файл lib.py в котором был класс игры. Т.Е. файлы игры выглядели так:
+----game | + -- | | -- main.py | \ --lib | +--lib.py -> class Game() | \ | +---В дальнейшем я работал в основном с классом Game(), в main.py просто вызывал его, создавал стартовые объекты, запускал игру.
В классе game сделал функцию run(), которая заупускает игровой цикл. Также сделал функцию draw_all(), она стирает все прошлые объекты, рисует новые, и печатает на игровое поле.
И так выглядел класс:
from time import sleep class Game(): def __init__(self): self.OUTPUT_IMAGE = [] # здесь игровое поле def draw_all(self): for line_words in self.OUTPUT_IMAGE: for word in line_words: print(word, end="") print("\n", end="") def run(self): while True: self.draw_all() sleep(0.1)Добавил все основные функции, по типу set_image() , size_object() , is_clash() , и все те которые являются игровым движком, и которые я описал выше.
Сделал новую функцию create_object() и переменную self.OBJECTS , функцию create_object() я использую для создания объектов, она принимает параметры img , name , x , y , up , rigid , data .
img — картинка объекта
name — имя объекта (дом, трава, житель, еда и.т.п.)
x — X объекта
y — Y объекта
up — если этот параметр True, то объект рисуется над игроком, иначе игрок его перекрывает собой
rigid — твердость, игрок не может пройти через этот объект (еще не реализовано)
data — личные данные объекта, его личные характеристикиcreate_object()
Эта функцию которая сейчас у меня в игре:
def CreateObject(self,x: int, y: int, img: str, name: str = None, up: bool = False, rigid: bool = False, data: dict = <>): size_object = self.GetSizeObject(img=img) self.OBJECTS.append( )На тот момент я уже добавил игрока, дом, траву, и жителя.
И решил использовать тот самый параметр в объекте up , использовать его в объекте Home , Т.Е. чтоб дом закрывал собой игрока. Для этого я сделал функцию CheckAll(), циклом for проходился по всем объектам, и рисовал их на исходящей картинке, Т.Е. использовать функцию SetImage(x: int, y: int, img:str), подавая в нее X и Y объекта, и картинку.
Тем самым рисовал объекты которые мог закрыть собой игрок. В этом же цикле я объявил список up_of_payer_objects , и если у объекта стоял up=True, то я добавлял его в список, не рисуя его на поле. После рисовал самого игрока, и только этого я проходил циклом for по объектам в up_of_payer_objects, рисуя их, тем самым они были над игроком.
def CheckAll(self): up_of_payer_objects = [] for object_now in range(len(self.OBJECTS)): if object_now["up"]: up_of_payer_objects.append(object_now) continue self.SetImage(x=object_now["x"],y=object_now["y"],image=object_now["img"])Дальше я занялся движением игрока. Для этого я создал его как отдельный объект, который не находится в списке self.OBJECTS , но который хранится в переменной self.PLAYER .
Все его параметры, по типу X , Y , img , и.т.п. получить можно с помощью ключей, проще говоря это словарь (dict). С таким игроком и объектами уже можно было работать, двигать, вычислить столкновения. Я начал с движения.
Начал создавать движение с того что сделал функцию CheckKeysObjects(), которая отвечает за отслеживание нажатия клавиш, и которую я вызываю в функции CheckAll() в самом началеdef CheckAll(self): self.CheckKeysObjects() .Для отслеживания нажатий на клавиши я использовал библиотеку keyboard, и 4 переменные:
self.WALK_LEFT_PLAYER
self.WALK_RIGHT_PLAYER
self.WALK_UP_PLAYER
self.WALK_DOWN_PLAYERИ все оказалось просто, отслеживаем клавиши, и если нажата допустим d , то мы переменную self.WALK_RIGHT_PLAYER делаем True .
В самом начале функции объявляем все переменные в False , для того чтобы сбросить все прошлые результаты, а-то игрок не остановится.
CheckKeysObjects()
def CheckKeysObjects(self): #делаю все переменные в False, чтоб сбросить прошлые результаты self.WALK_LEFT_PLAYER = False self.WALK_RIGHT_PLAYER = False self.WALK_UP_PLAYER = False self.WALK_DOWN_PLAYER = False #а тут уже проверяю нажатия if keyboard.is_pressed("a"): self.WALK_LEFT_PLAYER = True elif keyboard.is_pressed("d"): self.WALK_RIGHT_PLAYER = True if keyboard.is_pressed("w"): self.WALK_UP_PLAYER = True elif keyboard.is_pressed("s"): self.WALK_DOWN_PLAYER = TrueПосле этого я в функции CheckAll() проверяю все перменные отвечающие за движение, узнаю, куда двигается игрок.
Если какая-то в True , узнаем какая, и двигаем предмет в противоположную сторону.
Получившийся код движения
def CheckAll(self): self.CheckKeysObjects() # check moves up_of_payer_objects = [] for object_now in range(len(self.OBJECTS)): self.PLAYER["img"] = self.PLAYER["image_normal"] if self.WALK_LEFT_PLAYER: self.OBJECTS[object_now]["x"] += 1 elif self.WALK_RIGHT_PLAYER: self.OBJECTS[object_now]["x"] -= 1 if self.WALK_UP_PLAYER: self.OBJECTS[object_now]["y"] += 1 elif self.WALK_DOWN_PLAYER: self.OBJECTS[object_now]["y"] -= 1Да, мы двигаем предметы в противоположную сторону, для того чтобы создать иллюзию движения. Если игрок идет на право, то все предметы окружения смещаются налево.
Дальше я добавил еще предметов окружения, и занялся спавном еды, у игрока цель собирать еду, чтобы не умереть.
Для отсчета времени спавна еды, я использовал простой time.sleep() , и библиотеку threading — для того чтобы запустить 2 функции одновременно, спавн еды и основной игровой цикл. Функция спавна еды SpawnEat() — это просто функция которая при запуске генерирует на случайных местах еду, вызывая для каждой единицы еды функцию CreateObject() .
Также, как только я сделал функцию спавна еды, я сделал переменную у игрока self.PLAYER["hungry"] , это его голод, в самом начале он равен — 100 ед., его я буду уменьшать если игрок ходит и тратит энегрию (типа энергию, ее в игре нет) или увеличивать если игрок что-то съел.
Также я сделал функцию MinimizeHungry() , она вызывается каждые 5 секунд, и просто отнимает у игрока 2 единицы голода. Это я сделал для того чтобы игроку пришлось двигаться, а не стоять на месте.
И наконец в функции Eat() , эта функция которая вызывается в отдельном потоке от игрового цикла. Она проверяет не слишком ли много еды на карте, если еды больше 10 ед. то НЕ вызывает функцию SpawnEat() , если меньше 10 ед. то вызывает SpawnEat() .
Вот какой она получилась:
def Eat(self): while True: sleep(4) if len([i for i in self.OBJECTS if i["name"] == "meat"]) < 10: self.SpawnEat() sleep(1) self.MinimizeHungry()Функция Start() , для запуска основного цикла:
def Start(self): while True: self.CheckAll() self.DrawAll() sleep(0.01)И функция run() , которая запускает всю игру.
def run(self): proc1 = threading.Thread(target=self.Start) proc1.start() proc2 = threading.Thread(target=self.Eat) proc2.start()Сам процесс поедания, я реализовал просто в функции CheckAll() и CheckKeysObjects() . В CheckKeysObjects() я проверял не нажал ли игрок на кнопку E . Если нажал, то ставил переменную self.PRESS_E в True .
В цикле CheckAll() , проверял, не еда ли нынешний объект в цикле for , если еда то проверял не сталкивается ли с ним игрок, если сталкивается то проверял переменную self.PRESS_E , и если она в True то тогда просто удалял объект, и увеличивал голод, Т.Е. переменную self.PLAYER["hungry"] .
Вот так это в коде
for object_now in range(len(self.OBJECTS)): . if self.OBJECTS[object_now]["name"] == "meat": items_objects.append(object_now) is_clash = self.IsClash( x=self.OBJECTS[object_now]["x"], y=self.OBJECTS[object_now]["y"], h=self.OBJECTS[object_now]["h"], w=self.OBJECTS[object_now]["w"], x2=self.PLAYER["x"], y2=self.PLAYER["y"], h2=self.PLAYER["h"], w2=self.PLAYER["w"], ) if is_clash: if self.PRESS_E: try: self.PLAYER["hungry"] += self.HUNGRUY_ADD del self.OBJECTS[object_now] break except IndexError: passСкажу наперед, это все мне нужно будет переписовать, когда я буду делать инветнарь
Делаю инвентарь
Итак, настало сложное, нам нужно сделать инвентарь.
Сложность в том что все предметы нужно будет отображать, хранить историю, удалять, ставить на пол объекты.
Я начал с того что добавил игроку новый ключ, это был self.PLAYER["inventory"] , там хранятся 4 яцчейки, вот в таком виде:
"inventory":< "0":, "1":, "2":, "3":, >цифры — просто номера ячеек.
status — этот ключ хранит в себе значение, пуста яйчейка или нет. Если пуста то «space», если же там есть предмет, то там хранится имя предмета.
name — хранит в себе имя предмета, оно будет использовано когда игрок будет класть предмет.
minimize_image — эта уменьшенная картинка предмета которая изображается в инвентаре игрока.
После, сделал новые проверки в нашем CheckKeysObjects() , при нажатии на X предмет будет бросаться на землю, и также при нажатии на кнопку E будет вызываться функция self.UseEat() , которую мы сейчас будем разбирать.
Итак, функция self.UseEat() представляет из себя проход по всем ячейкам инвентаря, в поисках еды, и если еда найдена, то она удаляется из инвентаря, и к голоду добавляется 10 единиц. Для удаление предмета из инвентаря я сделал функцию self.DestroyItem() , в которую подается индекс ячейки, и вся ячейкой просто становится по «дефолту» пустой и без ничего.
self.DestroyItem()
def DestroyItem(self,index_item: str): item = self.PLAYER["inventory"][index_item] self.PLAYER["inventory"][index_item] = self.PLAYER["default_inventory_item"](index_item) self.PLAYER["inventory_must_update"] = True return itemself.CheckKeysObjects()
def CheckKeysObjects(self): self.WALK_LEFT_PLAYER = False self.WALK_RIGHT_PLAYER = False self.WALK_UP_PLAYER = False self.WALK_DOWN_PLAYER = False if key("a"): self.WALK_LEFT_PLAYER = True elif key("d"): self.WALK_RIGHT_PLAYER = True if key("w"): self.WALK_UP_PLAYER = True elif key("s"): self.WALK_DOWN_PLAYER = True if key("f"): self.KEY_F = True else: self.KEY_F= False if key("e"): self.UseEat()self.UseEat()
def UseEat(self): for inventory_item in range(len(self.PLAYER["inventory"])): if self.PLAYER["inventory"][str(inventory_item)]["name"] == "meat": if self.PLAYER["hungry"] + self.ADD_HUNGRY_COUNT < 100.0: self.PLAYER["hungry"] += self.ADD_HUNGRY_COUNT self.DestroyItem(index_item=str(inventory_item))Дальше функция бросания предмета на землю.
Там впрочем ничего сложного, при нажатии на X вызывается функция self.QuitItem() , в ней проходит цикл for по всем ячейкам инвентаря, и если ключ ["status"] не ровняется "space" , то эту ячейку удаляем с помощью ранее рассмотренной функции self.DestroyItem() , и создаем объект на основе того что был в ячейке, X и Y ставит игрока, как бы он бросил его возле себя.
self.Quititem()
def QuitItem(self): for inventory_item in range(len(self.PLAYER["inventory"])): if self.PLAYER["inventory"][str(inventory_item)]["status"] != "space": self.CreateObject( img=self.PLAYER["inventory"][str(inventory_item)]["img"], x=self.PLAYER["x"], y=self.PLAYER["y"], name=self.PLAYER["inventory"][str(inventory_item)]["name"], data=self.PLAYER["inventory"][str(inventory_item)]["data"], ) self.DestroyItem(index_item=str(inventory_item)) breakИ впрочем все, многие вещи я не говорил как сделал, Т.К. они являлись не основной частью игры, хоть и интересной. Например сообщения о возможности поднятие предмета или нет (когда инвентарь полон), о том что я добавил анимацию ходьбы, о том что сделал отдельную библиотеку картинок, и тому прочих вещах.
На этом все?
Нет, я собираюсь в игру добавить нейросеть, используя библиотеку который я писал на Python,
Собираюсь сделать взаимодействие игрока с NPC оснащенными нейросетью,
небольшой, но какой-нибудь сюжет, и также какие-то припасы для игрока типа брони, еды. предметов, возможность строить блоками.Попробовать игру
Ее можно свободно скачать с моего GitHub, для запуска потребуется лишь Python3, и библиотека keyboard. Запускать нужно файл main.py .
- Объект test должен содержать следующие ключи:
- Если файл не найден, Игорь будет рекурсивно искать файлы .yyz и .yyp в каталоге
