Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Style and conventions

This file attempts to outline various naming and structure conventions, that are to be used throughout the project.

General

  • Files end with one newline.
  • Use Tab characters for indentation.
    • If in the indented block there is an empty line, indent it also to the same level as the rest of the block to make it clear where the block starts and ends.
  • Whenever possible, try to keep line lengths around a maximum of 80 chars.
  • Code should be self-explanatory, so avoid too clever oneliners. Do not hesitate to split a statement in several ones using temporary variables if it improves readability sensibly.

Naming

As a general rule of a thumb, name everything with snake_case.

Nodes

  • Always in snake_case with all-lowercase letters.
  • This means that default node names are not accepted, e.g. KinematicBody2D must be renamed to kinematic_body_2d for consistency (yes, we have OCDs).

Script variables, constants, etc.

  • Inner classes are in PascalCase, with first capital letter (like built-in classes)
  • signal-s, var-s and func-s are in snake_case with all-lowercase letters
  • const-s are in all-uppercase SNAKE_CASE, unless they hold a preloaded class, in which case they follow the instructions for inner classes

Order

Nodes

Nodes are in any order (or in order of importance), but usually:

  • Collision shapes are first.
  • Sprites, meshes, etc. are second (so they will cover the collision shape in 2D).
  • Position/2D or nodes with such children are next.
  • Timers are an exception to the first rule, because they are much more important than AnimationPlayers, so they go right after position nodes.
  • Collision bodies are next.
  • AnimationPlayers, SamplePlayers, StreamPlayers, and other non-CanvasItem and non-Spatial nodes go last.
  • All other nodes go wherever it is sensible to go.

Script variables, constants, etc.

  • const-s used to preload other scripts are first (as they are similar to import statements in other languages).
  • Inner classes go second.
    • Inside them, code follows the same rules as for the main scope.
  • signal-s are third.
  • const-s are next.
  • export var-s are just before var-s
  • var-s follow.
    • If possible, sort variables by usage (e.g. all variables about the "enemy" go at one place).
  • func-s are last.
    • _init constructors are first.
    • All _ready, _enter_tree, etc. callbacks follow in the order they are called in. If order is not specified (_process and _fixed_process for example), sort them in whatever order feels most natural.
    • Other function are in any order (so, most sensible order), but generaly:
      • Functions that do the most heavy-lifting are first
      • Helper functions go right after them. Here are some soft (breakable) guidelines for discerning such functions:
        • Do a small amount of work, and are used in most other functions. For example, such a function might spawn an enemy, create and initialize an instance of some class, etc.
        • Abstract member variables, and are used in other classes. For instance, such functions are getters, setters, and functions that abstract access to a subnode, etc.
        • Run a few of the main functions in order, creating a pipeline. E.g. one of those functions might run everything needed to update internal state, or even spawn particle effects randomly.
      • Signal receivers are last in any order (but possibly in the order they are called in). This doesn't include all functions that are connect-ed, as some of them might be doing the most of the work.

Scripts structure

Comments

  • Comments start with a whitespace, followed by a capital letter.
  • Comments don't end in a period, except they have more than one sentence.
  • Comments about variables definitions:
    • Can be put inline if they are short enough, add one whitespace before the hashmark (#) of the comment.
    • Can be put before the variable definition if they are a long line or multiline, or if the variable definition is split over several lines.
  • Functions and classes definition comments use the Python docstring convention, and are therefore never inline.
    • Single line docstrings are formatted as follows: """Single-line docstring"""
    • Multiple line docstrings are formatted as follows:
"""First line
Second line
"""
  • As separator between different script sections (classes, variables, callbacks, etc.), use comments like: ## {Text} ##.
  • Between different logical sections inside functions, use normal non-inline comments just before the code they apply to.
  • For commenting something about a block, you can write a (short) inline comment at the opening (e.g. at the if-statement or loop). If you want to write a longer comment, use instead the first line(s) of the block.

Operators

  • All conditional operators are written in words, and have one space on each side.
    • The sole exception to this rule is the not operator, which is written as a symbol (!), without a space.
  • All comparison, compound and assignment operators (==, +=, <, =, etc.) have a space on each side.
  • The operators for division and multiplication (*, /), have no space on each side.
  • Operators for addition, subtraction and remainder/modulus (+, -, %) have a space on each side.
  • Negation operator (-x) has no space around the operator.
  • Argument-separating commas are always followed by a space.
  • Parentheses, square brackets and braces are not surrounded by spaces, except if it is required by some operator.

Conditional expressions and loops

  • if-statements and while loops
    • The conditional expressions are written without enclosing parentheses.
    • Parentheses can be used within the conditional expression to improve readability, e.g. when using both and and or operators.
    • If you have to break the conditional expression into more than one line, break it just before an and or an or, and add two tabs after the break (to make it discernable from the main code block).
  • for loops
    • The expression you loop over is written without parenthesis (not that use can do it with parenthesis, either).

Example

extends Node2D

const Constants = preload("constants.gd") # Import
const EnemySpec = preload("enemy_spec.gd") # Import

class Wave:
    """A class that holds the specifications of one wave"""
    const WAVE_NORMAL = 0 # Normal wave
    const WAVE_BOSS = 1 # Boss wave - give a random reward to the player after the wave is finished
    const WAVE_OPTIONAL = 2 # Optional wave - one that might be skipped
    
    var enemy_specs = [] # Specifications for the different types of enemies, when spawned one would be picked at random
    var enemies_amount = 4 # Amount of enemies in the wave
    
    var min_difficulty = Constants.DIFFICULTY_EASY # Minimum difficulty of the wave - if it is too big, the wave would be skipped
    var type = WAVE_NORMAL # Type of the wave
    
    func _init(_enemy_specs, _enemies_amount = 2, _min_difficulty = Constants.DIFFICULTY_EASY, _type = WAVE_NORMAL):
        enemy_specs = _enemy_specs
        enemies_amount = _enemies_amount
        min_difficulty = _min_difficulty
        type = _type
    
    func pick_random_enemy_spec():
        """Picks a random enemy from the specs"""
        if enemy_specs.size() != 0:
            return enemy_specs[randi() % enemy_specs.size()]
        else:
            return null

signal enemy_died(enemy) # Emitted when an enemy dies

var tilemap # Tilemap node
var animation_player # Animation player node

# List of all waves
var waves = [
    Wave.new([EnemySpec.Rat.new()], 30, Constants.DIFFICULTY_EASY, Wave.WAVE_OPTIONAL)
    Wave.new([EnemySpec.Zombie.new(), EnemySpec.Skeleton.new()])
]
var current_wave = 0 # Counter that holds the current wave
var enemies_spawned = 0 # Counter that holds the amount of enemies to be spawned
var live_enemies = [] # Array containing all enemies that are currently living

func _ready():
    tilemap = get_node("tilemap")
    animation_player = get_node("animation_player")
    
    get_node("hud/next_wave").connect("pressed", self, "next_wave")
    get_node("hud/quit").connect("pressed", self, "exit_game")
    get_node("spawn_timer").connect("timeout", self, "spawn_enemy")
    
    set_process(true)

func _process(delta):
    get_node("hud/fps_label").set_text(OS.get_frames_per_second()) # Update the FPS counter

func next_wave():
    """Called when the "Next Wave" button is clicked"""
    if live_enemies.size() > 0 or waves[current_wave].type != Wave.WAVE_OPTIONAL: # Can we go to the next wave?
        return
    
    clear_enemies() # Prevent buildup of enemies from different optional waves
    current_wave += 1
    enemies_spawned = 0

func spawn_enemy():
    """Called when we have to spawn a new enemy"""
    if enemies_spawned > waves[current_wave].enemies_amount: # Should we spawn more enemies?
        return
    
    enemies_spawned += 1
    var enemy = waves[current_wave].pick_random_enemy_spec().spawn() # Spawn the new enemy
    add_child(enemy)
    enemy.connect("die", self, enemy_died, [enemy])
    enemies.push_back(enemy)

func clear_enemies():
    """Called when the level has to be cleared of enemies"""
    for enemy in enemies:
        enemy.disconnect("die", self, enemy_died, [enemy]) # Prevent the enemy_died handler from being called, as this might destroy the loop
        enemy.kill()
        emit_signal("enemy_died", enemy)
    
    enemies.clear()

func enemy_died(enemy):
    """Called when an enemy dies"""
    enemies.erase(enemy)
    emit_signal("enemy_died", enemy)

func exit_game():
    """Called when the quit button is pressed"""
    clear_enemies()
    get_node("/root/global").go_to_screen("main")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.