guidelines - Poobslag/turbofat GitHub Wiki

Wiki style

Titles and section headings should use Sentence case. Capitalize headings like you would capitalize a sentence. Do not capitalize each letter, do not include ending punctuation.

Wiki URLs should use kebab-case. All lower case, hyphens instead of spaces.

New wiki pages should be linked in Home or a child of Home. If Home grows to have more than 20 links, it should be reorganized into a hierarchical structure.

Code style

GDScript (.gd)

Code follows the GDScript style guide and GDQuest best practices.

A delinter shell script is included in the project. This should be run periodically to catch code which deviates from these guidelines.

Standard guidelines

Code order

01. tool
02. class_name
03. extends
04. # docstring
05. inner classes
06. signals
07. enums
08. constants
09. exported variables
10. public variables
11. private variables
12. public onready variables
13. private onready variables
14. optional built-in virtual _init method
15. built-in virtual _ready method
16. remaining built-in virtual methods
17. public methods
18. private methods
19. private signal receiver methods
20. public static methods
21. private static methods

Some of these code ordering rules can be verified by running this series of intellij code order regexes.

Project-specific guidelines

Comments and code are wrapped at 120 lines.

Constants and export variables are public (not prefixed with an underscore.)

Setters follow the convention func set_foo(new_foo: Type) -> void.

Initializers follow the conventionfunc _init(init_foo: Type) -> void.

Packed scenes follow the convention export (PackedScene) var FooScene, using upper-case and the suffix of 'Scene'.

Node groups are pluralized with snake case, such as chat_choices or level_select_buttons.

AnimationPlayer animation names are kebab-case, such as 'eat-again-smile0' or 'dance-end-large'

For enums, lists and dictionaries with two or more rows, include a trailing comma to avoid syntax errors when adding new elements.

enum EndResult {
	WON,
	FINISHED,
	LOST,
}

Signals should include past-tense verbs like ended or changed. When a noun is needed to disambiguate the signal, it should precede the verb like game_ended or score_changed (not ended_game or changed_score)

Scripts which contain two or more nested classes should separate them with a row of hyphens:

class SomeClass:
# snip

# -----------------------------------------------------------------------------

class SomeOtherClass:

Comments

All public classes, fields and functions with a non-trivial description should be documented. A 'trivial description' is one which can be discerned from the field's name or context. For example, a Score.compute_score(level_stats: Dictionary) would warrant a comment, but a Score.add_score(score_delta: int) function would not.

If a method includes any non-trivial parameters or return values, then all parameters/return values should be documented as well.

Comments should follow Godot 4.0's proposed documentation comment syntax, prefixing each line with a double hash symbol ##.

Method and class comments should be written in third person present tense, like Removes the last character from a String, not Remove the last character... or Will remove the last character...

## Here is an example description.
## 
## The first line should succinctly describe the method's purpose. It can be supplemented with a more detailed description
## afterwards. The description should wrap if it is more than 120 characters long. This comment is long enough to wrap, so
## it is wrapped at 120 characters.
## 
## Parameters:
## 	'param_foo': Description of parameter foo, indented with a single tab.
## 
## 	'param_bar': Description of parameter bar. The description should wrap to a newline prefixed with two tabs
## 		if it is more than 120 characters long. This line is long enough to wrap, so it is wrapped at 120
## 		characters.
## 
## Returns:
## 	Description of the method's return type and purpose, if the response isn't obvious. If the value is long
## 	enough that it is forced to wrap, the following lines are indented as well.

Some properties are decorated with getters/setters but deliberately avoid assigning the property's value. In those cases, add a comment # virtual property; value is only exposed through getters/setters to avoid misleading developers who expect the property to hold a value.

Dictionary fields should have their keys and values documented, including their types. GDScript does not support statically typed dictionaries, so this is our replacement for it.

## key: (int) an enum from Creatures.Mood
## value: (Array, String) string chat prefixes (emoticons)
var _prefixes_by_mood := {}

Array fields should have their values documented, including their types.

## List of string keys corresponding to branches off of this chat event.
var links: Array

Code which mentions a Godot issue should use the syntax Godot #12345 (https///www.example.com/). For example:

# Workaround for Godot #40357 (https://github.com/godotengine/godot/issues/40357) to force the label to shrink to
# its minimum size. Otherwise, the ScoreParticles will be positioned incorrectly if the text ever shrinks.

JSON data

When using JSON dictionaries, data should be extracted with json.get("foo", "default"), not json["foo"] or json.get("foo") (unless null is an acceptable value.) Providing a sensible default means players won't get errors if the save format is changed slightly.

Objects which need to serialize/deserialize json should use the following function names:

func from_json_string_array(dict: Array) -> void:
func from_json_dict(json: Dictionary) -> void:
func to_json_dict() -> Dictionary:

JSON keys and values should use snake_case to separate words. This is for consistency with Python conventions. (GDScript is similar to Python.)

get_class() and is_class()

In Godot 3.2.1, get_class() and is_class() do not return the class_name. Inject the following boilerplate as a workaround:

## Workaround for Godot #21789 to make get_class return class_name
func get_class() -> String:
	return "ExampleClass"


## Workaround for Godot #21789 to make is_class match class_name
func is_class(name: String) -> bool:
	return name == "ExampleClass" or .is_class(name)

This boilerplate should only be used for classes which are known to require get_class() and is_class() to return class_name, do not apply it globally. Be sure to include the comment so that if #21789 is addressed in a future Godot release, we can find and delete all of the relevant workaround code.

Appropriate get_node() use

Avoid calling get_node or using the $Dollar/Sign/Operator for the same node repeatedly. Store the node in an onready field to avoid repetition and performance hits.

If a setter needs to reference an onready field, (such as set_first_name depending on the _label field) create a method named something like _refresh_first_name which checks for not is_inside_tree to avoid NPEs, which is called in the setter and _ready function. For example:

var _first_name: String
onready var label: Label = $Label

func _ready() -> void:
    _refresh_first_name()

func set_first_name(new_first_name: int) -> void:
    _first_name = new_first_name
    _refresh_first_name()

func _refresh_first_name() -> void:
    if not is_inside_tree():
        return
    label.text = _first_name

Scene files (.tscn)

Nodes should be UpperCamelCase.

Shaders (.shader)

Shaders should start with a documentation comment.

shader_type canvas_item;
/**
 * Paints the opaque parts of the texture with a flat color.
 */

Shell scripts (.sh)

Shell scripts should start with a documentation comment. If the shell script accepts parameters, this comment should include some example usage.

#!/bin/bash
################################################################################
# Analyzes code and assets for stylistic errors, correcting them or printing
# them to the console
#
# Usage:
#   delint.sh:
#     Analyze code for stylistic errors, printing them to the console
#   delint.sh -c, delint.sh --clean:
#     Correct simple errors which can be automatically corrected

Glossary

Here's a glossary of terms used within the code. When editing code, try to use consistent terms like "arrange two pieces into a box" instead of "arrange two blocks into a food item", or "whenever the player sees" instead of "whenever you see"

box: a 3x3, 3x4 or 3x5 rectangle built from intact pieces.

block: a solid element occupying one cell of the playfield. Specific pieces are referred to with names like T-Block or P-Block, but the term 'block' should not be used when referring to pieces more generally.

cake box: a 4x3 or 5x3 rectangle built from either three pentominos or three quadrominos. There are eight types of cake boxes: jjo, jlo, jtt, llo, ltt, pqv, pvu, qvu.

cell: a unit square within the playfield.

chat: a series of chat events which make up a single conversation or cutscene.

creature: an inhabitant of the game world or puzzle.

customer: an inhabitant of the world (including Turbo, at the moment.)

ghost piece: a representation of where a piece will land if allowed to drop into the playfield.

goop: frosting which appears on pieces, and which covers terrain on the overworld

initial das: holding left, right or hard drop when a piece spawns to immediately spawn the piece in place.

initial rotation: holding a rotate key when a piece spawns to immediately spawn the piece in a different orientation.

level: a single block-dropping session, its preroll/postroll cutscenes and any other metadata. Also 'puzzle'.

line: a horizontal row of nine blocks in the playfield.

lock cancel: pressing soft drop to cancel a piece from locking.

lull character: A slash character '/' used to add pauses to spoken text.

overworld: the 3D part of the game where the player picks which levels to play.

pentomino: a piece with five blocks.

piece: a set of blocks that moves as a unit. Specific pentominos and quadrominos are referred to with names like T-Block or P-Block, but the term 'piece' is still used when referring to pentominos and quadrominos more generally.

player: the person playing the game.

playfield: the grid of cells into which pieces are placed.

postroll: a cutscene which follows a puzzle.

preroll: a cutscene which precedes a puzzle.

puzzle: a single block-dropping session, its preroll/postroll cutscenes and any other metadata. Also 'level'.

region: an area in career mode with a unique appearance, characters and levels

rotate: to turn the active piece. Pieces can 'rotate clockwise', 'rotate counterclockwise', or 'rotate 180'. These can be alternatively referred to as 'clockwise/right/cw', 'counterclockwise/left/ccw', '180/flip' for contexts where long words or numbers cause problems.

scenario: a set of rules and end conditions for a puzzle. For example, a scenario might involve clearing 50 lines as fast as you can while new rows show up from the bottom.

snack box: a 3x3 square built from a pentomino and a quadromino. There are four types of snack boxes: jp, lq, ov, ut.

squish move: sliding a piece through an impossibly narrow gap by pressing or tapping the 'soft drop' key.

squish drop: holding 'soft drop' and tapping 'hard drop' to quickly to the bottom of the playfield, past any pieces in the way.

tetromino: a piece with four blocks.