Инструменты разработки
Не пытайтесь (полностью) изобретать велосипед заново!

Решения
В статье о самом начале вы, возможно, читали, что я решил сделать игру.
Подойти к такому можно огромным количеством способов, и для каждой ситуации подход свой. Единственное, что я знал на 100 %, это что должен получиться олдскульный квест в жанре point and click в 2D.
Из-за этого сразу отпало множество инструментов разработки, но оставалось несколько вопросов:
- На каком языке программирования?
- На какой платформе должна работать игра?
- Какой движок или фреймворк использовать?
- Форум поддержки?
Язык программирования
Я уже умел работать с PHP, C#, Python, Javascript, Node, Blitz 3D, Monkey X, Monkey 2. На последних двух языках я уже делал прототип. Но разработчик этого языка прекратил работу, из-за этого я больше не верил в хороший исход и тогда тоже остановился.
Значит, язык должен был быть простым и надёжным. Я люблю простоту, а времени хорошо освоить новый язык у меня не было.
Движок
Игровой движок вроде Unreal Engine существует, чтобы сильно упростить работу. Поэтому мои поиски начались с изучения полного списка https://en.wikipedia.org/wiki/List_of_game_engines.
Многое отпало сразу, потому что я не хотел тратить деньги на движок, по крайней мере разовая оплата была не проблемой. Но уж точно никаких странных и мутных лицензионных схем, которые придумали некоторые движки. Ещё я хотел, чтобы он активно развивался и не был на C или C++. И, конечно, 2D.
Да, Unreal Engine умеет и 2D, но это противоречит моему принципу ’less is more’. В таких движках слишком много функций, которые я не буду использовать и которые могут только сломаться. Поэтому такие движки отпадают.
- Adventure Game Studio
- Windows, и там нельзя сделать всё, что я задумал.
- Construct
- Проблемы с лицензией, слишком дорого и ежемесячная оплата
- Flixel
- Не хочу использовать ActionScript
- GameMaker Studio
- Слишком старый, проблемы с лицензией, и мне он показался недостаточно крутым, ха-ха
- LÖVE
- Круто! Но я ещё изучаю
- Monkey X
- С него я когда-то начинал, но поддержки больше нет
- Pygame
- Интересно, но в итоге выбор пал не на него
- HaxeFlixel
- Нет, просто нет
- Cocos2d
- Интересно, но нет, на мой взгляд недостаточно простой
- Godot
- Интересно, но нет, на мой взгляд недостаточно простой
- Unity
- Проблемы с лицензией
Вывод был такой: похоже, я действительно хотел сделать что-то сам. Нужна была открытая лицензия MIT или что-то похожее, и универсальность. Исследование показало, что некоторые из движков выше используют SDL2, и мне нужно было узнать об этом больше.
SDL2
Если разобраться, как софт и железо вместе двигают картинку на экране через видеокарту (и звук), видно, что Windows использует DirectX, Ubuntu OpenGL, а Mac Metal/Vulkan. А Windows и Mac могут использовать и просто OpenGL…
То есть для каждой платформы своя библиотека, написанная на своём языке. Очень неудобно, ведь не хочется переписывать код под каждую платформу. Хочется написать код 1 раз, чтобы он работал везде.
Решение этой проблемы библиотека https://www.libsdl.org. Она бесплатна при правильной лицензии, и с одним и тем же кодом можно использовать все платформы. SDL2 написана на C, и есть ряд привязок, позволяющих использовать SDL2 в другом языке программирования.
Поскольку писать на C я не хотел, я прошёл весь список привязок, чтобы кое-что попробовать:
- C
- Показалось неудобным
- C#
- Отпал, потому что дома у меня был Mac
- GO
- Вообще-то понравился и был простым, только каждый раз нужно было компилировать
- Haskell
- Ужас, совсем не то, что я искал
- Rust
- Э-э, слишком сложно, пришлось бы вдруг программировать функционально
- Lua
- Интересно и просто
- Python
- Почему-то не подошёл, да и неохота везде ставить TAB.
- Nim
- Похоже, им никто не пользовался
На этом этапе остались GO и Lua, а при более раннем поиске движка я уже видел LÖVE.
LÖVE
Фреймворк https://love2d.org отвечал всем моим требованиям.
Я называю его не движком, а фреймворком, потому что в нём только функции, которые просто обращаются к SDL2. И это именно то, что я ищу, он подходит по всем пунктам:
- Есть форум, где все до сих пор отзывчивые и доброжелательные.
- Фреймворк простой (less is more).
- Язык Lua простой и используется уже много лет.
- Целевые платформы, на которых будет работать игра.
function love.draw()
love.graphics.print("Hello: Signal From The Stars", 400, 300)
end
Lua

Официальный логотип Lua
Оставалось только выучить Lua. И что делаешь? Едешь к родителям распечатать всё руководство по Lua… Они были не очень рады.
В итоге оказалось, что язык настолько простой, что руководство мне не понадобилось.
Lua, по-моему, можно считать обёрткой, которая обращается к C. Ещё плюс в том, что не нужно каждый раз ждать компилятор после каждого изменения кода.
Конечно, есть и недостатки, например он не такой быстрый, как C, но это не проблема.
Целевые платформы
Игра Signal From The Stars должна работать как минимум на Mac, Windows и iOS (iPad). Позже добавятся Nintendo Switch, Ubuntu и Android. Вероятно, она сможет работать и на Xbox и PlayStation, но не просто из коробки.
Изображения
В игре будут изображения, и для них я сначала использовал программу Aseprite, потому что в ней есть режим pixel perfect. Это определённый стиль, который я использую. Но позже я всё же перешёл на Adobe Photoshop. Об этом я напишу статью позже.
Затем все изображения оптимально обрабатываются через TexturePacker https://www.codeandweb.com/texturepacker, а этот результат снова обрабатывается скриптом.
Скрипты
Нужно несколько отдельных скриптов, чтобы навести порядок в некоторых вещах. Большинство на Lua, но со временем их становится всё больше.
Shell
#!/bin/bash
cd ../
luacheck --no-cache --config scripts/.luacheckrc src
cd src/lib/engine3/scripts/
echo "UNIT TEST"
./unittest.sh
echo "INTEGRATION TEST"
./integrationtest.sh
Node
Node я выбрал только потому, что был модуль, умеющий читать файлы Adobe Photoshop (https://github.com/meltingice/psd.js). Я до сих пор им пользуюсь, и он превращает файл Photoshop в ассеты и Atlas для игры.
Если бы тогда я нашёл то же самое на Python, я бы не использовал Node. Почему-то Node со своими миллиардами библиотек не совсем то, что я ищу. Он немного напоминает мне DLL-ад в Windows.
Python
Жалею, что не писал больше на Python. Ниже пока единственный, но важный скрипт, который я использую на Python. С его помощью я могу найти контур прозрачного файла .png и упростить эти координаты.
# python3 outline.py
# pip3 install pillow
# pip3 install opencv-python
import sys
import cv2
import numpy as np
import json
import os
import argparse
def find_outer_contours(image_path, simplify=0):
img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
mask = img[:, :, 3] > 0
contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
min_contour_size = 5
filtered_contours = [contour for contour in contours if cv2.contourArea(contour) > min_contour_size]
if simplify > 0:
simplified_contours = []
for contour in filtered_contours:
epsilon = simplify * cv2.arcLength(contour, True)
simplified_contour = cv2.approxPolyDP(contour, epsilon, True)
simplified_contours.append(simplified_contour)
return simplified_contours
else:
return filtered_contours
def draw_and_save_contours(contours, output_path, original_size):
img = np.zeros((original_size[1], original_size[0]), dtype=np.uint8)
cv2.drawContours(img, contours, -1, (255), thickness=1)
cv2.imwrite(output_path, img)
def contour_coordinates_to_json(contours):
contour_coordinates_list = []
for contour in contours:
contour_coordinates = []
for point in contour:
x, y = point[0]
contour_coordinates.append([int(x), int(y)])
if len(contour_coordinates) % 2 != 0:
contour_coordinates.append([contour_coordinates[-1][0], contour_coordinates[-1][1]])
contour_coordinates_list.append(contour_coordinates)
return json.dumps(contour_coordinates_list)
def apply_convex_hull(contours):
combined_contour = np.concatenate(contours)
hull = cv2.convexHull(combined_contour)
return [hull]
def create_rectangles(contours):
rectangles = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
rectangles.append((x, y, x + w, y + h))
return rectangles
def rectangles_to_json(rectangles):
json_output = json.dumps(rectangles)
return json_output
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Find outer contours of a transparent image.')
parser.add_argument('image_path', type=str, help='Path to the image file.')
parser.add_argument('--simplify', type=float, default=0.01, help='Simplify factor for contour approximation. Set to 0 to disable simplification.')
parser.add_argument('--hull', action='store_true', help='Apply convex hull to the contours.')
parser.add_argument('--rects', action='store_true', help='Return contour x1,y2,x2,y2 outline rects')
parser.add_argument('--output_path', type=str, default='', help='Path to save the output image. If not provided, the image will not be saved.')
args = parser.parse_args()
image_path = args.image_path
original_img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
original_size = (original_img.shape[1], original_img.shape[0])
contours = find_outer_contours(image_path, simplify=args.simplify)
if args.hull:
contours = apply_convex_hull(contours)
if args.output_path:
draw_and_save_contours(contours, output_path, original_size)
if args.rects:
rectangles = create_rectangles(contours)
json_output = rectangles_to_json(rectangles)
print(json_output)
else:
json_output = contour_coordinates_to_json(contours)
print(json_output)
Git
Всё отслеживается и хранится в git и на BitBucket, потому что там есть несколько (бесплатных) приватных репозиториев.
Редактор
Я попробовал несколько редакторов, но лично мне лучше всего подошёл Visual Studio Code. Правда, вместе с разными расширениями.
- Code Runner
- Love2D Support
- Markdown Preview Enhanced
- reStructuredText Syntax highlighting
- vscode-lua
Так я могу одним нажатием клавиши перезапустить игру и при необходимости отладить. И подсветка синтаксиса для меня просто спасение. Автодополнением я, по правде, никогда не пользуюсь.
Написание истории
А теперь самое странное. Можно подумать про Word, LibreOffice, Markdown… Но я пишу её в файлах reStructuredText (.rst).
===========================
Scene 20: Nederland - Intro
===========================
.. image:: ../project/story/scene/20/resources/scene20.png
:width: 100%
.. contents::
:depth: 1
.. raw:: pdf
PageBreak
Omschrijving
============
Een bos in Nederland in de len...
Все файлы .rst лежат в разных папках, и с помощью скрипта ниже я экспортирую их в PDF. Чтобы потом распечатать.
Об этом приключении я, конечно, напишу статью.
#!/bin/bash
set -e
source config
if [ -n "$1" ]; then
TARGET_SCENE="$1"
TARGET_PDF="scene_${TARGET_SCENE}.pdf"
echo "Generating PDF for scene $TARGET_SCENE"
else
echo "Generating all PDF files"
fi
mkdir -p ../temp/docs/
for dir in ../project/story/scene/*; do
SCENE_NUMBER=$(basename "$dir")
PDF_NAME="scene_${SCENE_NUMBER}.pdf"
if [ -z "$TARGET_PDF" ] || [ "$PDF_NAME" = "$TARGET_PDF" ]; then
echo -e "Create: $PDF_NAME"
# merge *.rst scene files
first_iteration=true
TEMP_FILE=$(mktemp tempfile.XXXXXX)
for md_file in $(ls "${dir}"/*.rst | sort); do
TEMP_CONTENT=$(mktemp /tmp/tempcontent.XXXXXX)
if $first_iteration; then
first_iteration=false
else
item_number=$(basename "$md_file" .rst | cut -d'-' -f1)
item_description=$(basename "$md_file" .rst | sed -E 's/^[0-9]+-//; s/-/ /g')
title=""
# inventory item ends with 9
if [ ${#item_number} -eq 6 ] && [[ "$item_number" == *"9" ]]; then
title="$item_number $item_description (inventory)"
else
title="$item_number $item_description"
fi
title_length=${#title}
separator=$(printf '=%.0s' $(seq 1 $title_length))
echo -e "$title\n$separator\n" >> "$TEMP_CONTENT"
fi
cat "$md_file" >> "$TEMP_CONTENT"
echo -e "\n\n.. raw:: pdf\n\n PageBreak\n" >> "$TEMP_CONTENT"
cat "$TEMP_CONTENT" >> "$TEMP_FILE"
rm "$TEMP_CONTENT"
done
rst2pdf --footer="###Page### | ###Title###" "$TEMP_FILE" --output="../temp/docs/${PDF_NAME}"
rm "$TEMP_FILE"
if [ $# -eq 1 ]; then
echo "Opening $PDF_NAME..."
open "../temp/docs/${PDF_NAME}"
fi
fi
done
if [ $# -eq 0 ]; then
echo -e 'Create: concept_document.pdf'
rst2pdf --footer="###Page### | ###Title###" ../project/story/concept-document.rst --output="../temp/docs/concept_document.pdf"
echo -e 'Create: story_notes.pdf'
rst2pdf --footer="###Page### | ###Title###" ../project/story/notes.rst --output="../temp/docs/story_notes.pdf"
fi
Диаграмма
Во всей истории, конечно, есть головоломки, и я делаю их наглядными для себя в бесплатной программе (https://app.diagrams.net).
Yaml
Чтобы поначалу собрать всё просто, я решил не тратить время на редактор игры. Вся игра собрана из файлов .yaml, которые превращаются в json и используются в игре.
Обзор
Если посмотреть на всё, что я написал до сих пор, получается схема ниже.
На ней видны engine3 и game, о них я расскажу в другом посте.
Схема итоговых инструментов