9

Пишем игру "Змейка"

Добрый день. Я тут в качестве хобби разрабатываю 2д движок для создания игр. Сегодня хочу с вами поделиться как на нем сделать классическую игру "змейка". Прошу внимательно прочитать, покритиковать, похвалить, посоветовать, и прочее.

О движке

Пишу я его на с++ с использованием графической библиотеке SDL, в качестве скриптового языка использую Питон.

Прошу прощения за скриншоты, но не понял как вставлять "правильный код", а вставлять цитатами было очень страшно и не красиво.

Для лиги лени сразу прикрепляю ссылку с готовым архивом:

Скачать архив

И демонстрация игры

Заранее спасибо. И так начнем.

Создаем пустой проект

Структура каталогов пустого проекта:

Редактируем config.xml

config.xml - это файл который читается в движке первым.

имеет структуру xml

Основная строка которая нас интересует

<AppMain value = "..\Assets\ScriptsP\AppMain.py" />

Application.Script.AppMain - это путь к скрипту с которого стартует приложение

Все конфиг нам больше не нужен.

Создаем Тайлы

В каталог /Assets/Tiles/ - добавляем следующие картинки (все имеют формат 120 на 120)

  1. snakeApple.png - яблоко

2. snakeBody.png - часть тела змеи (кружочек зеленый)

3. snakeGrid.png - квадратик с полупрозрачными черными линиями(из него мы будем делать сетку)

4. snakeHeadDown.png - голова которая смотри вниз

5. snakeHeadLeft.png - голова которая смотри влево

6. snakeHeadRight.png - голова которая смотри вправо

7. snakeHeadUp.png - голова которая смотри вверх

Создаем UI

Элементы ГУИ можно создавать как програмно так и задавать расположение в xml файлах.

Получается что то типа "Окна" - к которому прикреплены все элементы как дочерние.

создаем файл UISnake.xml

К основному объекту типа xdCanvas прикрепляем

Объект типа xdText с именем tFPS который будет счетчиком фпс

Аналогично добавляем

Объект типа xdText с именем tObjects который будет счетчиком количества объектов на экране

Объект типа xdText с именем tScore который будет отображать текущий счет.

Ниже полный текст файла UISnake.xml

<?xml version="1.0"?>

<object type="xdCanvas">

<position x="0" y="0" />

<scale value="1" />

<order value="999998" />

<name value="UISnake" />

<relative value="XD_UI" />

<alignment value="XD_UI_ALI_NONE" />

<anchorVertical value="XD_UI_ANC_V_TOP" />

<anchorHorizontal value="XD_UI_ANC_H_LEFT" />

<childs>

<object type="xdText">

<position x="-100" y="-20" />

<text value="FPS:" />

<font name="..\Assets\Fonts\Comfortaa.ttf" size = "10" style = "1" colorR = "93" colorG = "86" colorB = "79" />

<name value="tFPS" />

<anchorVertical value="XD_UI_ANC_V_DOWN" />

<anchorHorizontal value="XD_UI_ANC_H_RIGHT" />

</object>

<object type="xdText">

<position x="-100" y="-10" />

<text value="Objects:" />

<font name="..\Assets\Fonts\Comfortaa.ttf" size = "10" style = "1" colorR = "93" colorG = "86" colorB = "79" />

<name value="tObjects" />

<anchorVertical value="XD_UI_ANC_V_DOWN" />

<anchorHorizontal value="XD_UI_ANC_H_RIGHT" />

</object>

<object type="xdText">

<position x="20" y="20" />

<text value="Счет:" />

<font name="..\Assets\Fonts\Comfortaa.ttf" size = "30" style = "1" colorR = "100" colorG = "50" colorB = "50" />

<name value="tScore" />

<anchorVertical value="XD_UI_ANC_V_TOP" />

<anchorHorizontal value="XD_UI_ANC_H_LEFT" />

</object>

</childs>

</object>

Создаем файл UISnakeMenu.xml

Который будет отвечать за меню начала игры и отображения результатов

Иерархия нашей меню будет следующая

  1. UISnakeMenu(xdCanvas) - основной канвас окна

    1. uiPanelCenterMenu(xdPanel) - панелька на которой все нарисуем

    2. tCaption(xdText) - Заголовок "Пиратская Змейка"

    3. tGameOver(xdText) - Надпись "Начните новую игру", или "Game over" (значение будем задавать скриптом)

    4. tScoreEnd(xdText) - Счет, если был проигрыш (значение будем задавать скриптом)

    5. btnNewGame(xdButton) - Кнопка новая игра (при нажатии которой игра начинается сначала)

    6. btnExit(xdButton) - Кнопка выход из игры

Из непонятного у нас только xdPanel и xdButton

это по сути те-же элементы UI, что и xdText - описанные выше, только с небольшими отличиями

xdPanel - элемент интерфейса отражает физическую панельку, на которой мы можем что то рисовать.

xdButton - это кнопка с текстом(есть разные варианты можно картинками задать статусы, но у нас кнопка простая по умолчанию)

Принципиально не отличается от текста вся логика будет задаваться на стороне скрипта.

Ниже полный текст файла UISnakeMenu.xml

<?xml version="1.0"?>

<object type="xdCanvas">

<position x="0" y="0" />

<scale value="1" />

<order value="999998" />

<name value="UISnakeMenu" />

<relative value="XD_UI" />

<alignment value="XD_UI_ALI_NONE" />

<anchorVertical value="XD_UI_ANC_V_TOP" />

<anchorHorizontal value="XD_UI_ANC_H_LEFT" />

<childs>

<object type="xdPanel">

<position x="0" y="0" />

<sizePrecent x="20" y="50" />

<name value="uiPanelCenterMenu" />

<anchorVertical value="XD_UI_ANC_V_CENTER" />

<anchorHorizontal value="XD_UI_ANC_H_CENTER" />

<childs>

<object type="xdText">

<position x="0" y="+30" />

<text value="Пиратская Змейка" />

<font name="..\\Assets\\Fonts\\Comfortaa.ttf" size = "20" style = "1" colorR = "93" colorG = "86" colorB = "79" />

<name value="tCaption" />

<anchorVertical value="XD_UI_ANC_V_TOP" />

<anchorHorizontal value="XD_UI_ANC_H_CENTER" />

</object>

<object type="xdText">

<position x="0" y="80" />

<text value="Начните новую игру" />

<font name="..\\Assets\\Fonts\\Comfortaa.ttf" size = "18" style = "1" colorR = "190" colorG = "86" colorB = "79" />

<name value="tGameOver" />

<anchorVertical value="XD_UI_ANC_V_TOP" />

<anchorHorizontal value="XD_UI_ANC_H_CENTER" />

</object>

<object type="xdText">

<position x="0" y="100" />

<text value="Счет:" />

<font name="..\\Assets\\Fonts\\Comfortaa.ttf" size = "18" style = "1" colorR = "93" colorG = "86" colorB = "79" />

<name value="tScoreEnd" />

<anchorVertical value="XD_UI_ANC_V_TOP" />

<anchorHorizontal value="XD_UI_ANC_H_CENTER" />

</object>

<object type="xdButton">

<position x="0" y="0" />

<size x="130" y="50" />

<name value="btnNewGame" />

<text value="Новая Игра" />

<anchorVertical value="XD_UI_ANC_V_CENTER" />

<anchorHorizontal value="XD_UI_ANC_H_CENTER" />

</object>

<object type="xdButton">

<position x="0" y="70" />

<size x="130" y="50" />

<name value="btnExit" />

<text value="Выход" />

<anchorVertical value="XD_UI_ANC_V_CENTER" />

<anchorHorizontal value="XD_UI_ANC_H_CENTER" />

</object>

</childs>

</object>

</childs>

</object>

```

Скрипты

Теперь переходим к самому интересному к логике.

Файл globals.py - вспомогательный скрипт который в данной игре не имеет смысла большого, можно обойтись без него.

но по замыслу тут находятся какие-то глобальные константы и методы

например есть метод Debug который позволяет отлаживать скрипты в режиме реального времени в Visual Studio Code ооооочень удобно, но об этом в другой раз.

Создаем файл AppMain.py

Импортируем пространство имен xd

import xd # Импорт в питон методов движка. почти все находится в пространстве имен xd

добавляем самый первый метод OnStart

После чего делаем обертку в виде класса Application это не обязательно , но автору это кажется красивым

Application.OnStart - будет вызываться при старте сцены.

application - экземпляр класса приложение.

в глобальный метод **OnStart** добавляем вызов:

Минимальный скрипт готов.

Далее создаем пустую сцену, а именно файл SceneGame.py

Сцена это объект порождённый от объекта xd.GameScene со следующими методами:

OnLoad - вызывается после загрузки сцены

OnStart - вызывается при старте сцены

OnFrame - вызывается каждый кадр.

Unload - метод где будем удалять все, что создали.

Текст файла SceneGame.py с минимальной сценой.

Далее в файле AppMain.py добавляем следующие строки

где в

xd.GameScene.LoadScene("SceneGame.SceneGame", self.OnLoadScene)

Вызываем метод движка xd.GameScene.LoadScene для загрузки сцены.

1. "SceneGame.SceneGame" - строка с указанием класса сцены в питон скрипте. В формате [Имя файла скрипта].[Имя класса в файле]

2. self.OnLoadScene - обработчик вызываемый после загрузки сцены.

Реализация метода SceneGame.OnLoadScene

На данный момент полный текст файла AppMain.py:

```python

import xd # type: ignore

import globals

globals.Debug(False)

class Application:

""" Глобальный класс сцена"""

def OnStart(self):

xd.GameScene.LoadScene("SceneGame.SceneGame", self.OnLoadScene)

def OnLoadScene(self, scene):

self.scene = scene

xd.App.SetGame(scene)

pass

#Экземпляр класса сцены

application = Application()

def OnStart():

"""Обработчик при старте сцены (вызывается движком)"""

application.OnStart()

return 1

После запуска должно быть пустое окно с синеватым фоном. Если это так, от значит мы все делаем правильно.

Файл AppMain.py - мы больше не меняем. Он принял конечный вид.

В методе SceneGame.OnStart добавляем следующий код

На любой сцене есть корневой объект с именем rootObject куда можно прикреплять объекты. Не прикрепленные объекты не отображаются на сцене (игнорируются циклом рендера)

Метод xd.BaseObject.FindByNameS это метод FindByNameS - ищет игровой объект на сцене с по имени. и возвращает ссылку на объект.

Метод xd.Panel.to - преобразовывает ссылку в Объект типа xdPanel есть аналогичные методы для любых предопределенных объектов xd.Text.to , xd.Button.to и др.

После запуска мы увидим Окно игровое с отображаемой меню.

Далее добавляем на сцену окно из файла UISnake.xml для отображения текущих игровых данных а запоминаем ссылки на элементы интерфейса см которыми после будем работать

Первый обработчик событий. Начнем с простого, сделаем обработчик, который выходит из игры.

У нас есть кнопка для выходи из игры с именем btnExit

Первая сцена и обработчик готовы

При текущем варианте запуска приложения должно быть окно с главным меню и рабочей кнопкой выхода из игры.

Далее добавим в проект вспомогательный класс GameData

Основная особенность которого хранить данные между сценами, аля глобальный объект. В текущей реализации это не обязательно но предлагается его использовать, потому, что при развитии может быть у нас появятся новые сцены, и как между ними передавать данные?

С высоты птичьего полета класс GameData выглядит так:

Методы:

  1. GameData.Unload - метод где описываются удаления элементов аля деструктор

  2. GameData.OnStart - обработчик который вызываем при старте приложения

  3. GameData.OnTick - обработчик таймера (обсудим позже)

  4. GameData.OnKeyPressed - обработчик нажатия клавиши

  5. GameData.GameOver - Метод проигрыша

  6. GameData.NewGame - метод новая игра

  7. GameData.IsPause - проверить на паузе ли игра

  8. GameData.NewApple - добавить новое яблоко на сцену

  9. GameData.DrawGrid - нарисовать сетку

Реализация этих методов будет позже и постепенно. Сейчас сосредоточимся на следующем.

....

Часть первая закончена (лимит картинок в посте закончился)

Спасибо, кто дочитал, и прокомментировал.