Gli strumenti di sviluppo
Non cercare di reinventare (del tutto) la ruota!

Decisioni
Nell’articolo sull’inizio forse hai letto che avevo deciso di creare un gioco.
Ci sono tantissimi modi per affrontare una cosa del genere e per ogni situazione l’approccio è diverso. L’unica cosa che sapevo al 100% era che doveva essere un’avventura punta e clicca old school in 2D.
Così molti strumenti di sviluppo sono stati subito esclusi, ma restavano alcune domande aperte:
- Con quale linguaggio di programmazione?
- Su quale piattaforma deve funzionare il gioco?
- Quale motore o framework usare?
- Un forum di supporto?
Linguaggio di programmazione
Sapevo già usare PHP, C#, Python, Javascript, Node, Blitz 3D, Monkey X, Monkey 2. Con gli ultimi due linguaggi avevo già fatto un prototipo. Ma lo sviluppatore di quel linguaggio si è fermato, quindi non avevo più fiducia in un buon esito e all’epoca mi sono fermato anch’io.
Doveva quindi essere un linguaggio semplice e affidabile. Amo la semplicità e non avevo nemmeno il tempo di imparare bene un nuovo linguaggio.
Motore
Un motore di gioco come Unreal Engine esiste per rendere il lavoro molto più semplice. Per questo la mia ricerca è cominciata studiando l’elenco completo https://en.wikipedia.org/wiki/List_of_game_engines.
Molti sono stati subito esclusi, perché non volevo spendere soldi per un motore, o almeno un pagamento unico non era un problema. Ma di sicuro niente strane costruzioni di licenza che alcuni motori si erano inventati. Volevo anche che fosse ancora sviluppato attivamente e che non fosse C o C++. E ovviamente 2D.
Sì, Unreal Engine può fare anche il 2D, ma va contro il mio principio ’less is more’. Motori del genere contengono troppe funzioni che non userò e che possono solo rompersi. Per questo motori del genere sono esclusi.
- Adventure Game Studio
- Windows, e non ci posso fare tutto quello che avevo in mente.
- Construct
- Problemi di licenza, troppo caro e pagamento mensile
- Flixel
- Non voglio usare ActionScript
- GameMaker Studio
- Troppo vecchio, problemi di licenza e non lo trovavo abbastanza figo haha
- LÖVE
- Bello! Ma lo sto ancora studiando
- Monkey X
- Avevo cominciato con questo, ma non ha più supporto
- Pygame
- Interessante, ma alla fine non l’ho scelto
- HaxeFlixel
- No, semplicemente no
- Cocos2d
- Interessante, ma no, secondo me non era abbastanza essenziale
- Godot
- Interessante, ma no, secondo me non era abbastanza essenziale
- Unity
- Problemi di licenza
La conclusione era che evidentemente volevo davvero creare qualcosa io stesso. Doveva avere una licenza aperta MIT o qualcosa di simile ed essere versatile. Dalle ricerche è emerso che alcuni dei motori qui sopra usano SDL2, e su questo dovevo saperne di più.
SDL2
Se cerchi come funziona il rapporto tra software e hardware per far muovere un’immagine sullo schermo tramite la scheda video (e il suono), vedi che Windows usa DirectX, Ubuntu OpenGL e Mac Metal/Vulkan. E Windows e Mac possono usare anche semplicemente OpenGL…
Quindi per ogni piattaforma c’è una libreria propria, scritta a sua volta nel proprio linguaggio. Molto scomodo, perché non vuoi riscrivere il codice per ogni piattaforma. Quello che vuoi è scrivere il codice 1 volta e che funzioni ovunque.
La soluzione a questo problema è la libreria https://www.libsdl.org. È gratuita con la licenza giusta e con lo stesso codice puoi usare tutte le piattaforme. SDL2 è scritta in C e ci sono diversi binding che permettono di usare SDL2 in un altro linguaggio di programmazione.
Visto che non volevo usare il C, ho provato tutto l’elenco dei binding:
- C
- Non mi sembrava pratico
- C#
- Escluso, perché a casa usavo un Mac
- GO
- In realtà mi piaceva ed era semplice, ma dovevi compilare ogni volta
- Haskell
- Un orrore, per niente quello che cercavo
- Rust
- Ehm, troppo difficile, all’improvviso dovevo programmare in modo funzionale
- Lua
- Interessante e semplice
- Python
- In qualche modo non era la scelta giusta, e non ho voglia di mettere un TAB ovunque.
- Nim
- Sembrava che nessuno lo usasse
A quel punto restavano GO e Lua, e in una ricerca precedente di un motore avevo già visto LÖVE.
LÖVE
Il framework https://love2d.org soddisfaceva tutti i miei requisiti.
Non lo chiamo motore ma framework, perché contiene solo funzioni che usano SDL2 in modo semplice. Ed è proprio quello che cerco, soddisfa tutti i miei punti:
- C’è un forum dove tutti sono ancora disponibili e gentili.
- Il framework è essenziale (less is more).
- Il linguaggio Lua è semplice ed è usato da anni.
- Target sulle piattaforme su cui funzionerà il gioco.
function love.draw()
love.graphics.print("Hello: Signal From The Stars", 400, 300)
end
Lua

Il logo ufficiale di Lua
Dovevo solo imparare Lua, e allora cosa fai? Vai dai tuoi genitori a stampare tutto il manuale di Lua… Non ne erano molto contenti.
Alla fine si è scoperto che il linguaggio era così semplice che il manuale non mi serviva.
Lua si può vedere, credo, come un wrapper che parla con il C. È anche un vantaggio non dover aspettare ogni volta un compilatore dopo ogni modifica al codice.
Ovviamente ci sono anche degli svantaggi, per esempio non è veloce come il C, ma non è un problema.
Target
Il gioco Signal From The Stars deve funzionare almeno su Mac, Windows e iOS (iPad). Più avanti verranno aggiunti Nintendo Switch, Ubuntu e Android. Probabilmente potrà funzionare anche su Xbox e PlayStation, ma non subito out of the box.
Immagini
Il gioco avrà anche delle immagini, e per queste all’inizio usavo il programma Aseprite, visto che ha una modalità pixel perfect. È uno stile particolare che uso. Più avanti però sono passato ad Adobe Photoshop. Scriverò un articolo su questo più avanti.
Tutte le immagini vengono poi elaborate in modo ottimale tramite TexturePacker https://www.codeandweb.com/texturepacker, per poi elaborare di nuovo quell’output con uno script.
Script
Servono alcuni script separati per sistemare certe cose. La maggior parte è in Lua, ma più avanti se ne aggiungeranno sempre di più.
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
La scelta di Node è dovuta solo al fatto che esisteva un modulo in grado di leggere i file di Adobe Photoshop (https://github.com/meltingice/psd.js). Lo uso ancora e converte il file Photoshop in asset e in un Atlas per il gioco.
Se allora avessi trovato la stessa cosa in Python, non avrei usato Node. In qualche modo Node, con i suoi miliardi di librerie, non è del tutto quello che cerco. Mi ricorda un po’ l’inferno delle DLL di Windows.
Python
Mi dispiace di non aver scritto più Python. Qui sotto c’è al momento l’unico script, ma importante, che uso in Python. Con questo posso trovare il contorno di un file .png trasparente e semplificarne le coordinate.
# 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
Tutto viene tracciato e salvato in git e su BitBucket, perché offre più repository privati (gratuiti).
Editor
Ho provato diversi editor, ma per me personalmente Visual Studio Code funzionava meglio. In combinazione con diverse estensioni.
- Code Runner
- Love2D Support
- Markdown Preview Enhanced
- reStructuredText Syntax highlighting
- vscode-lua
Così con la pressione di un tasto posso riavviare il gioco ed eventualmente fare debug. E anche l’evidenziazione della sintassi per me è una boccata d’aria. Il completamento automatico in realtà non lo uso mai.
Scrivere la storia
E ora arriva la parte strana. Penseresti a Word, LibreOffice, Markdown… Ma io la scrivo in file 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...
Tutti i file .rst sono in cartelle diverse, e con lo script qui sotto ne faccio un’esportazione in PDF. Per poi stamparla.
Su questa avventura scriverò ovviamente un articolo.
#!/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
Diagramma
Tutta la storia contiene ovviamente degli enigmi, e li rendo visibili per me stesso con il programma gratuito (https://app.diagrams.net).
Yaml
Per mettere insieme tutto in modo semplice all’inizio, ho scelto di non spendere tempo in un editor di gioco. Tutto il gioco è costruito con file .yaml che vengono convertiti in json e usati nel gioco.
Panoramica
Se guardi tutto ciò che ho scritto finora, arrivi allo schema qui sotto.
Ci vedi engine3 e game, che spiegherò in un altro post.
Diagramma degli strumenti finali