Utvecklingsverktygen
Försök inte uppfinna hjulet (helt) på nytt!

Beslut
I artikeln om början har du kanske läst att jag hade bestämt mig för att göra ett spel.
Det finns enormt många sätt att ta sig an något sådant, och varje situation kräver ett eget angreppssätt. Det enda jag visste till 100 % var att det skulle bli ett oldschool peka och klicka äventyrsspel i 2D.
Därmed föll en hel del utvecklingsverktyg bort direkt, men några frågor fanns kvar:
- Vilket programmeringsspråk?
- Vilken plattform ska spelet fungera på?
- Vilken motor eller vilket ramverk ska användas?
- Supportforum?
Programmeringsspråk
Jag kunde redan hantera PHP, C#, Python, Javascript, Node, Blitz 3D, Monkey X, Monkey 2. Med de två sista språken hade jag redan gjort en prototyp. Men utvecklaren av språket slutade, så jag hade inte längre förtroende för att det skulle sluta bra och slutade också då.
Det måste alltså vara ett enkelt och pålitligt språk. Jag gillar enkelhet och hade inte heller tid att lära mig ett nytt språk ordentligt.
Motor
En spelmotor som Unreal Engine finns för att göra arbetet mycket enklare. Därför började mitt sökande med att studera hela listan https://en.wikipedia.org/wiki/List_of_game_engines.
Mycket föll bort direkt eftersom jag inte ville lägga pengar på en motor, i alla fall var en engångsbetalning inget problem. Men absolut inga konstiga, luddiga licenskonstruktioner som vissa motorer hade hittat på. Jag ville också att den fortfarande utvecklades aktivt och att det inte var C eller C++. Och förstås 2D.
Ja, Unreal Engine kan också 2D, men det går emot min princip ’less is more’. Sådana motorer innehåller alldeles för många funktioner som jag inte kommer att använda och som bara kan gå sönder. Därför faller sådana motorer bort.
- Adventure Game Studio
- Windows, och jag kan inte göra allt där som jag hade tänkt mig.
- Construct
- Licensproblem, för dyrt och månadsbetalning
- Flixel
- Jag vill inte använda ActionScript
- GameMaker Studio
- För gammalt, licensproblem och tyckte inte att det var tillräckligt coolt haha
- LÖVE
- Snyggt! Men jag undersöker det fortfarande
- Monkey X
- Här började jag en gång, men det har ingen support längre
- Pygame
- Intressant, men det blev inte det till slut
- HaxeFlixel
- Nej, bara nej
- Cocos2d
- Intressant, men nej, i mina ögon inte tillräckligt grundläggande
- Godot
- Intressant, men nej, i mina ögon inte tillräckligt grundläggande
- Unity
- Licensproblem
Slutsatsen var att jag tydligen verkligen ville göra något själv. Det skulle ha en öppen MIT-licens eller något liknande och vara mångsidigt. Efterforskningar visade att några av motorerna ovan använder SDL2, och det behövde jag veta mer om.
SDL2
När man tar reda på hur mjukvara och hårdvara samarbetar för att få en bild att röra sig på skärmen via grafikkortet (och ljud) ser man att Windows använder DirectX, Ubuntu OpenGL och Mac Metal/Vulkan. Och Windows och Mac kan också bara använda OpenGL…
Så varje plattform har sitt eget bibliotek, som i sin tur är skrivet i sitt eget språk. Väldigt opraktiskt, för man vill inte skriva ny kod för varje plattform. Det man vill är att skriva koden 1 gång och att den fungerar överallt.
Lösningen på det problemet är biblioteket https://www.libsdl.org. Det är gratis under rätt licens och med samma kod kan man använda alla plattformar. SDL2 är skrivet i C och det finns ett antal bindings som gör att man kan använda SDL2 i ett annat programmeringsspråk.
Eftersom jag inte ville använda C gick jag igenom hela listan med bindings för att testa lite:
- C
- Verkade inte praktiskt
- C#
- Föll bort eftersom jag använde en Mac hemma
- GO
- Gillade det faktiskt och det var enkelt, men man behövde kompilera varje gång
- Haskell
- Skräck, det här var absolut inte vad jag letade efter
- Rust
- Ehm, det var för svårt, jag skulle plötsligt behöva programmera funktionellt
- Lua
- Intressant och enkelt
- Python
- På något sätt var det ingen bra match, och jag har inte heller lust att sätta en TAB överallt.
- Nim
- Ingen verkade använda det
Vid det här laget återstod alltså GO och Lua, och vid ett tidigare sökande efter en motor hade jag redan sett LÖVE.
LÖVE
Ramverket https://love2d.org uppfyllde alla mina krav.
Jag kallar det inte en motor utan ett ramverk, eftersom det bara innehåller funktioner som använder SDL2 på ett enkelt sätt. Och det är precis vad jag letar efter, det uppfyller alla mina punkter:
- Det finns ett forum där alla fortfarande är hjälpsamma och trevliga.
- Ramverket är grundläggande (less is more).
- Språket Lua är enkelt och har använts i många år.
- Targets för de plattformar som spelet ska fungera på.
function love.draw()
love.graphics.print("Hello: Signal From The Stars", 400, 300)
end
Lua

Den officiella Lua-logotypen
Jag behövde bara lära mig Lua, så vad gör man då? Man åker till sina föräldrar för att skriva ut hela Lua-manualen… De var inte så glada över det.
Till slut visade det sig att språket var så enkelt att jag inte behövde manualen.
Lua kan man nog se som en wrapper som pratar med C. Det är också en fördel att man inte hela tiden behöver vänta på en kompilator efter varje kodändring.
Det finns förstås också några nackdelar, till exempel är det inte lika snabbt som C, men det är inget problem.
Targets
Spelet Signal From The Stars måste fungera på minst Mac, Windows och iOS (iPad). Senare läggs Nintendo Switch, Ubuntu och Android till. Troligen kan det också fungera på Xbox och PlayStation, men inte direkt out of the box.
Bilder
Spelet kommer också att få bilder, och för dem använde jag först programmet Aseprite, eftersom det har ett pixel perfect läge. Det är en viss stil som jag använder. Senare gick jag ändå över till Adobe Photoshop. Jag kommer att skriva en artikel om det senare.
Alla bilder bearbetas sedan optimalt via TexturePacker https://www.codeandweb.com/texturepacker, och sedan bearbetas det resultatet igen med ett skript.
Skript
Det behövs några fristående skript för att få ordning på vissa saker. Det mesta är i Lua, men med tiden tillkommer mer och mer.
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
Valet av Node beror bara på att det fanns en modul som kunde läsa Adobe Photoshop-filer (https://github.com/meltingice/psd.js). Den använder jag fortfarande, och den gör om Photoshop-filen till tillgångar och en Atlas för spelet.
Om jag då hade hittat samma sak i Python hade jag inte använt Node. På något sätt passar Node med alla sina miljarder bibliotek inte riktigt det jag letar efter. Det påminner mig lite om DLL-helvetet i Windows.
Python
Jag ångrar att jag inte har skrivit mer Python. Nedan finns för tillfället det enda, men viktiga, skriptet som jag använder i Python. Med det kan jag hitta konturen av en transparent .png-fil och förenkla koordinaterna.
# 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
Allt versionshanteras och sparas i git och på BitBucket, eftersom det har flera (gratis) privata repon.
Editor
Jag har testat några editorer, men personligen fungerade Visual Studio Code bäst för mig. Fast i kombination med olika tillägg.
- Code Runner
- Love2D Support
- Markdown Preview Enhanced
- reStructuredText Syntax highlighting
- vscode-lua
Då kan jag starta om spelet med ett knapptryck och felsöka vid behov. Och syntaxmarkeringen är också en lättnad för mig. Autokomplettering använder jag faktiskt aldrig.
Skriva berättelsen
Och nu kommer det konstiga. Man skulle kunna tro Word, LibreOffice, Markdown… Men jag skriver den i reStructuredText-filer (.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...
Alla .rst-filer ligger i olika mappar, och med skriptet nedan exporterar jag dem till PDF. För att sedan skriva ut.
Om det här äventyret kommer jag förstås att skriva en artikel.
#!/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
Hela berättelsen innehåller förstås pussel, och dem gör jag synliga för mig själv med det kostnadsfria programmet (https://app.diagrams.net).
Yaml
För att enkelt sätta ihop allt i början valde jag att inte lägga tid på en speleditor. Hela spelet är uppbyggt av .yaml-filer som görs om till json och används i spelet.
Översikt
När man tittar på allt jag har skrivit hittills hamnar man i schemat nedan.
Där ser du engine3 och game, som jag förklarar i ett annat inlägg.
Diagram över de slutliga verktygen