De ontwikkeltools
Probeer het wiel niet (helemaal) opnieuw uitvinden!
- Martin
- 10 min read

Beslissingen
In het artikel het begin heb je misschien gelezen dat ik besloten had om een game te maken.
Er zijn enorm veel mogelijkheden hoe je zoiets aanpakt en voor elke situatie is de aanpak weer anders. Het enige wat ik 100% wist was dat het een oldschool point & click adventure game moest worden in 2D.
Daarmee vielen er gelijk een hoop ontwikkeltools af en bleven er nog een aantal vragen open:
- Met welke programmeertaal?
- Op welke platform moet het spel werken?
- Welke engine of framework ga je gebruiken?
- Support forum?
Programmeertaal
Ik kon al overweg met PHP, C#, Python, Javascript, Node, Blitz 3D, Monkey X, Monkey 2. Met de laatste twee talen had ik al een prototype gemaakt. Alleen stopte de ontwikkelaar van de betreffende taal, hierdoor had ik geen vertrouwen meer in een goed afloop en stopte ik destijds ook.
Het moest dus een eenvoudige taal zijn en die betrouwbaar was. Ik hou van eenvoud en had ook geen tijd om een nieuwe taal goed te beheersen.
Engine
Een game engine zoals Unreal Engine zijn er om het werk een stuk eenvoudiger te maken. Mijn zoektocht begon daarom ook daar om de complete lijst https://en.wikipedia.org/wiki/List_of_game_engines te bestuderen.
Er viel al gelijk een hoop af aangezien ik geen geld aan een engine wou uitgeven, althans eenmalig was geen geen probleem. Maar zeker geen rare vage licenties constructies die sommige engines hadden bedacht. Ook wou ik dat er nog actief ontwikkeld werd en dat het geen C of C++ was. En natuurlijk 2D.
Ja Unreal Engine, kan ook 2D alleen gaat dat tegen mijn principe in dat ’less is more’, dergelijke engine’s bevatten veel te veel functies die ik niet ga gebruiken en alleen maar kapot kunnen gaan. Dergelijke engine’s vallen daarom ook af.
- Adventure Game Studio
- Windows, en ik kan daar niet alles wat ik in gedachte had.
- Construct
- Licentie problemen, te duur en maandelijks betalen
- Flixel
- Ik wil geen actionscript gebruiken
- GameMaker Studio
- Te oud, licentie problemen en vond het niet stoer genoeg haha
- LÖVE
- Nice!, maar ik ben nog aan het onderzoeken
- Monkey X
- Hier was ik ooit mee begonnen, maar heeft geen support meer
- Pygame
- Interessant, maar is het uiteindelijk niet geworden
- HaxeFlixel
- Nee, gewoon nee
- Cocos2d
- Interessant, maar nee het was in mijn ogen niet basic genoeg
- Godot
- Interessant, maar nee het was in mijn ogen niet basic genoeg
- Unity
- Licentie problemen
De conclusie was dat ik blijkbaar toch echt iets zelf wou maken, het moest een open MIT licentie hebben of iets wat erop lijkt en veelzijdig zijn. Uit onderzoek bleek dat sommige van bovenstaande engines SDL2 gebruiken, hier moest ik meer van weten.
SDL2
Wanneer je opzoek gaat hoe sofware <> hardware werkt om een afbeelding op het scherm te laten bewegen via de videokaart (en geluid) dan zie je dat Window DirectX gebruikt, Ubuntu OpenGL en Mac Metal/Vulcan. En Windows en Mac kunnen ook gewoon OpenGL…
Dus per platform is er een eigen library die weer in een eigen taal geschreven is. Zeer ongemakkelijk, want je wilt niet voor elk platform opnieuw code schrijven. Wat je wilt is 1x code schrijven en het werkt overal.
De oplossing voor dit probleem is de library https://www.libsdl.org. Dit is gratis onder de juiste licentie en kun je met dezelfde code alle platformen gebruiken. SDL2 is geschreven in C en er zijn een aantal bindings waardoor je SDL2 kunt gebruiken in een andere programmeertaal.
Aangezien ik geen C programmeertaal wou gebruiken heb ik de complete binding lijst afgelopen om een en ander te testen:
- C
- Leek me niet handig
- C#
- Viel af want thuis gebruikte ik een Mac
- GO
- Beviel me eigenlijk wel, en was simpel alleen moest je telkens compilen
- Haskell
- Horror, dit is absoluut niet wat ik zocht
- Rust
- Eeeh dit was te moeilijk, ik moet dan ineens functioneel programmeren
- Lua
- Interessant en eenvoudig
- Python
- Op een of andere manier is dit geen goeie match, ik heb ook geen zin om overal een TAB te moeten plaatsen.
- Nim
- Niemand leek het te gebruiken
Op dit punt bleef dus GO en Lua over, en had in een eedere zoektocht naar een engine al LÖVE gezien.
LÖVE
Het framework https://love2d.org voldeed aan al mijn eisen.
Dit noem ik geen geen engine maar een framework aangezien het alleen maar functies bevat die SDL2 aanspreken op een basic manier. En dat is precies wat ik zoek, het voldoet aan al mijn punten:
- Er is een forum waar iedereen nog behulpzaam en aardig is.
- Het framework is basic (less is more).
- De taal Lua eenvoudig en word al jaren gebruikt.
- Targets op welke platform het spel gaat werken.
function love.draw()
love.graphics.print("Hello: Signal From The Stars", 400, 300)
end
Lua

Het officiele Lua logo
Ik moest alleen nog Lua leren, dus wat doe je dan, je gaat naar je ouders om de complete handleiding Lua uit te printen… Die waren daar wat minder gelukkig mee.
Uiteindelijk bleek ook nog dat de taal zo eenvoudig was dat ik de handleiding niet nodig had.
Lua kun je denk ik zien als een wrapper die C aanspreekt. Het is ook een voordeel dat je niet telkens hoeft te wachten op een compiler na elke code wijziging.
Natuurlijk zijn er ook wat nadelen, zo is niet zo snel als C maar dat is geen probleem.
Targets
Het spel Signal Of The Stars moet minimaal werken op een Mac, Windows, iOS (iPad). Daarnaast zal op een later punt Nintendo Switch, Ubuntu, Android wordt toegevoegd. Waarschijnlijk zal het ook wel kunnen werken op de Xbox & Playstation maar dat kan niet zomaar out of box.
Afbeeldingen
Het spel zal ook afbeeldingen krijgen, en hiervoor gebruikte ik in eerste instanatie het programma Aseprite, aangezien die een pixel perfect modus heeft. Dat is een bepaalde stijl die ik gebruik. Echter ben ik later toch Adobe Photoshop gaan gebruiken. Ik zal hierover later een artikel schrijven.
Alle afbeeldingen worden vervolgens optimaal verwerkt via TexturePacker https://www.codeandweb.com/texturepacker om vervolgens ook die output weer te verwerken met een script.
Scripts
Er zijn enkele losse scripts nodig om bepaalde dingen op order te krijgen. Het meeste is in Lua maar later komt er meer en meer bij.
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
De keuze voor Node is alleen omdat er een module was die Adobe Photoshop bestanden kon uitlezen (https://github.com/meltingice/psd.js). Dit gebruik ik nog en zet het Photoshop bestand om naar assets en Atlas voor het spel.
Indien ik toen hetzelfde had gevonden in Python had ik Node niet gebruikt. Op een of andere manier is Node met al zijn miljarden libs niet helemaal wat aansluit op wat ik zoek, het doet me een beetje denken aan de DLL hel van Windows.
Python
Ik heb spijt dat ik niet meer Python heb geschreven. Hieronder staat op dit moment het enige maar belangrijk script wat ik in Python gebruik. Hiermee kan ik een omlijning vinden van een transparant .png bestand en die coordinaten versimpelen.
# 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
Alles wordt bijgehouden en bewaard in git en BitBucket, aangezien deze meerdere (gratis) private repo’s heeft.
Editor
Een aantal editors getest maar persoonlijk werkte voor mij Visual Studio Code het beste. Wel in combinatie met verschillende extensies.
- Code Runner
- Love2D Support
- Markdown Preview Enhanced
- reStructuredText Syntax highlighting
- vscode-lua
Ik kan dan met 1 druk op een knop het spel opnieuw opstarten, en eventueel debuggen. En de highlighting is voor mij ook een verademing. Het auto aanvullen gebruik ik eigenlijk nooit.
Verhaal schrijven
En nu komt het rare, je zou denken Word, LibreOffice, Markdown… Maar ik schrijf het in reStructuredText (.rst) bestanden.
===========================
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...
Alle .rst bestanden staan in verschillende mappen waarmee ik met het onderstaande script een export naar PDF maak. Om vervolgens uit te printen.
Hier zal ik natuurlijk over dit avontuur een artikel schrijven.
#!/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
Diagram
Om het hele verhaal bevat natuurlijk puzzels die maak ik voor mezelf visueel zichtbaar met het gratis programma (https://app.diagrams.net).
Yaml
Om alles in eerste instantie eenvoudig in elkaar te zetten, heb ik er voor gekozen om geen tijd te besteden aan een game editor. Het hele spel zit in elkaar met .yaml files die omgezet worden naar json en in de game gebruikt worden.
Overzicht
Wanneer je alles wat ik tot nu heb geschreven bekijkt kom je uit op het onderstaande schema.
Hierin zie je engine3 en game staan, dit zal ik in een andere post toelichten.
Diagram van uiteindelijke tools
