Logo Signal From The Stars

The development tools

Don't try to reinvent the wheel (completely)!

Martin avatar
  • Martin
  • 10 min read
The official Love2d framework logo

Decisions

In the article the beginning you may have read that I had decided to make a game.

There are a huge number of possibilities how to approach such a thing and for each situation the approach is different. The only thing I knew 100% was that it had to be an oldschool point & click adventure game in 2D.

That immediately eliminated a lot of development tools and left a few questions unanswered:

  • With which programming language?
  • What platform should the game work on?
  • Which engine or framework will you use?
  • Support forum?

Programming Language

I could already handle PHP, C#, Python, Javascript, Node, Blitz 3D, Monkey X, Monkey 2. With the last two languages I had already created a prototype. Only the developer of the language in question stopped, because of this I no longer had confidence in a good outcome and stopped at the time as well.

So it had to be a simple language and one that was reliable. I like simplicity and also had no time to master a new language well.

Engine

A game engine like Unreal Engine are there to make work a lot easier. So my search started there to study the complete list https://en.wikipedia.org/wiki/List_of_game_engines.

A lot fell off right away since I did not want to spend money on an engine, at least not once. But certainly no weird vague licensing structures that some engines had come up with. I also wanted active development and not C or C++. And of course 2D.

Yes Unreal Engine, can also be 2D only that goes against my principle that ’less is more’, such engines contain way too many functions that I am not going to use and can only break. Such engine’s therefore also drop out.

  • Adventure Game Studio
    • Windows, and I can’t do everything I had in mind there.
  • Construct
    • License problems, too expensive and pay monthly
  • Flixel
    • I don’t want to use actionscript
  • GameMaker Studio
    • Too old, license problems and didn’t think it was cool enough haha
  • LÖVE
    • Nice, but I’m still researching
  • Monkey X
    • I once started with this, but no longer has support
  • Pygame
    • Interesting, but didn’t make it in the end
  • HaxeFlixel
    • No, just no
  • Cocos2d
    • Interesting, but no it wasn’t basic enough in my eyes
  • Godot
    • Interesting, but no it was not basic enough in my eyes
  • Unity
    • License problems

The conclusion was that apparently I really wanted to make something myself anyway, it had to have an open MIT license or something similar and be versatile. Research showed that some of the above engines use SDL2, I needed to know more about this.

SDL2

If you look up how sofware <> hardware works to move an image on the screen via the video card (and sound) you will see that Window uses DirectX, Ubuntu uses OpenGL and Mac uses Metal/Vulcan. And Windows and Mac can also just use OpenGL….

So for each platform there is its own library which again is written in its own language. Very inconvenient, because you don’t want to write code again for each platform. What you want is to write code once and it works everywhere.

The solution to this problem is the library https://www.libsdl.org. This is free under the right license and you can use all platforms with the same code. SDL2 is written in C and there are a number of bindings that allow you to use SDL2 in another programming language.

Since I did not want to use a C programming language I went through the complete binding list to test some things:

  • C
    • Didn’t seem convenient
  • C#
    • Dropped out because I used a Mac at home
  • GO
    • I actually liked it, and it was simple but you had to compile every time
  • Haskell
    • Horror, this is absolutely not what I was looking for
  • Quietness
    • Eeeh this was too hard, I then suddenly have to program functionally
  • Lua
    • Interesting and simple
  • Python
    • Somehow this is not a good match, I also don’t feel like having to put a TAB everywhere.
  • Nim
    • No one seemed to be using it

So at this point I was left with GO and Lua, and had already seen LÖVE in an ephemeral search for an engine.

LÖVE

The framework https://love2d.org met all my requirements.

I don’t call this not an engine but a framework since it only contains functions that address SDL2 in a basic way. And that is exactly what I am looking for, it meets all my points:

  1. There is a forum where everyone is still helpful and nice.
  2. The framework is basic (less is more).
  3. The Lua language is simple and has been used for years.
  4. Targets what platform the game will work on.
function love.draw()
  love.graphics.print("Hello: Signal From The Stars", 400, 300)
end

Lua

Het officiele Lua logo

Het officiele Lua logo

I just had to learn Lua, so what do you do, you go to your parents to print out the complete Lua manual…. They were a little less happy about that.

In the end it also turned out that the language was so simple that I didn’t need the manual. Lua I guess you can think of it as a wrapper that appeals to C. Another advantage is that you don`t have to wait for a compiler every time after every code change.

Of course there are also some disadvantages, for example is not as fast as C but that is not a problem.

Targets

The game Signal Of The Stars should work at least on a Mac, Windows, iOS (iPad). In addition, at a later point Nintendo Switch, Ubuntu, Android will be added. Probably it will also be able to work on Xbox & Playstation but it can’t just be out of box.

Images

The game will also have graphics, and for this I initially used the program Aseprite, since it has a pixel perfect mode. That is a certain style I use. However, I later started using Adobe Photoshop. I will write an article about this later.

All images are then optimally processed via TexturePacker https://www.codeandweb.com/texturepacker to then process that output again with a script.

Scripts

A few separate scripts are needed to get certain things right. Most of it is in Lua, but more and more will be added later.

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

The choice of Node is only because there was a module that could read Adobe Photoshop files (https://github.com/meltingice/psd.js). This I still use and converts the Photoshop file to assets and Atlas for the game.

If I had found the same thing in Python back then I would not have used Node. Somehow Node with all its billions of libs is not quite what matches what I am looking for, it reminds me a bit of the DLL hell of Windows.

Python

I regret not writing more Python. Below is currently the only but important script I use in Python. It allows me to find an outline of a transparent .png file and simplify those coordinates.

# 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

Everything is tracked and stored in git and BitBucket, as it has multiple (free) private repos.

Editor

Tested a number of editors but personally Visual Studio Code worked best for me. Well in combination with different extensions.

  • Code Runner
  • Love2D Support
  • Markdown Preview Enhanced
  • reStructuredText Syntax highlighting
  • vscode-lua

I can then restart the game with 1 click of a button, and debug if necessary. And the highlighting is also a relief for me. I never use the auto-completion.

Writing a story

And now comes the weird thing, you would think Word, LibreOffice, Markdown…. But I write it in reStructuredText (.rst) files.

===========================
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...

All the .rst files are in different folders that I use to create an export to PDF with the script below. To then print out.

Here, of course, I will write an article about this adventure.

#!/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

To the whole story of course contains puzzles which I make visible to myself visually with the free program (https://app.diagrams.net).

Yaml

In order to put everything together easily at first, I chose not to spend time on a game editor. The whole game is put together with .yaml files that are converted to json and used in the game.

Overview

When you look at everything I have written so far you come up with the diagram below. In it you will see engine3 and game, I will explain this in another post.

The tools I use

The tools I use