DEV Community

Cover image for GDScript Code Ordering
Godot
Godot

Posted on • Originally published at godot.community

GDScript Code Ordering

GDScript Code Ordering: Best Practices for Arranging Your Code Elements

When it comes to writing clean and organized GDScript code, the ordering of code elements plays an essential role in enhancing readability and maintainability. In this post, we'll explore best practices for ordering different code elements within your GDScript files.


Introduction to Code Ordering

Code ordering refers to the systematic arrangement of different code elements within your GDScript files. Having a consistent and logical order makes it easier for developers to quickly grasp the structure of your code and find specific elements. While there is no strict rule for code ordering, following some common conventions will lead to well-organized and easily maintainable code.


Recommended Code Element Ordering

Below, we present a recommended order for organizing code elements within your GDScript files. Remember that these conventions are guidelines, and you can adjust them to suit your project's needs and team preferences.

Declaration

Begin by listing all necessary class declaration statements at the top of your GDScript file. These statements help you access external classes and modules within your script.

class_name Player extends CharacterBody2D
Enter fullscreen mode Exit fullscreen mode

If your script is a tool (ie running in the Editor), make sure you put the annotation at the very top of the code

@tool
class_name Player extends CharacterBody2D
Enter fullscreen mode Exit fullscreen mode

You might need to save and reopen the scenes/script for it to take effect.

Signals

After that, define the signals that your script emits. Signals are custom events that allow communication between nodes, therefore it is beneficial to have it right up above.

class_name Player extends CharacterBody2D

signal health_depleted
signal player_moved(Vector2)
Enter fullscreen mode Exit fullscreen mode

Enums

If your script uses enums, you can do so after the signals. Enums are user-defined sets of named constants and are often used to represent different states or options.

enum GameState { MENU, PLAY, PAUSE }
enum Direction { UP, DOWN, LEFT, RIGHT }
Enter fullscreen mode Exit fullscreen mode

Constants

Next, define any constants that your script requires. Constants are values that do not change during runtime and are better suited for this early section of your script.

const MAX_SCORE: int = 100
const PLAYER_SPEED: float = 200.0
Enter fullscreen mode Exit fullscreen mode

Class Variables

Now, organize your class variables. Group them into different subsections based on their purposes.

Export Variables

Export variables come first, as they allow you to expose certain variables in the Inspector. This makes them easily accessible and configurable from the Godot editor.

# Good export variables ordering example
class_name Player extends CharacterBody2D
...
...
@export var player_name: String = "Player"
@export var player_health: int = 100
Enter fullscreen mode Exit fullscreen mode
Regular Variables

Then, list your regular class variables, public and private. You can mark a variable as a private by prefixing them with _ underscore. Doing so will hide your variable from showing inside the built in IDE Documentation.

class_name Player extends CharacterBody2D

var score: int = 0
var _is_gameover: bool = false #private, will not appear on docs
Enter fullscreen mode Exit fullscreen mode
Onready Variables

Onready variables come last after every other variables. They represent references to nodes that you need early on right after initialization.

class_name Player extends CharacterBody2D
...
...
@onready var player_sprite: Sprite = $PlayerSprite
@onready var enemy_spawner: EnemySpawner = $EnemySpawner
Enter fullscreen mode Exit fullscreen mode

Constructor and Initialization

After the class variables, you can include the constructor (also known as the _init method) if you need to initialize any variables. Note that overriding the _init, _ready, _enter_tree or other built-in virtual function is optional.

# Good constructor and initialization ordering example
class_name Player extends CharacterBody2D

# Constructor
func _init() -> void:
    player_additem(Item.SWORD)
    player_additem(Item.SHIELD)
Enter fullscreen mode Exit fullscreen mode
func _enter_tree() -> void:
    var parent_name = get_parent().name
    print("Parented to: " + parent_name)

func _ready() -> void:
    _sync_textures()
    _sync_animations()
Enter fullscreen mode Exit fullscreen mode

Public Methods

Finally, place your class public methods. Group them based on their purposes or relevance to specific aspects of your script.

# Good class methods ordering example
class_name Player extends CharacterBody2D

# Initialization methods
func _ready() -> void:
    pass

# Player movement related methods
func move_player() -> void:
    pass

# Player combat related methods
func damage_player() -> void:
    pass
Enter fullscreen mode Exit fullscreen mode

Private Methods

Methods of your class can be marked as private by prefixing them with _ underscore. Private variables and private methods that are prefixed by _ will not appear when you open Search Help (F1) of this particular class.

func _internal_check(): # _internal_check will not appear in IDE Help
    pass
Enter fullscreen mode Exit fullscreen mode

Code Organization in Functions

Within your class methods, aim for consistent code organization to maintain readability.

Arguments

List function arguments in a logical order, with required arguments first and optional arguments last.

# Good function arguments ordering example
func move_player(direction: Vector2, speed: float = 200.0):
    pass
Enter fullscreen mode Exit fullscreen mode

Local Variables

Declare local variables at the beginning of the function before using them.

# Good local variables ordering example
func calculate_score(score_value: int) -> int:
    var bonus_score: int = 10
    return score_value * 2 + bonus_score
Enter fullscreen mode Exit fullscreen mode

Function Logic

Organize the logic of your functions in a way that is clear and easy to follow. Consider using comments to explain complex sections. Best code is the one that doesn't require comments and innately self explanatory.

func is_game_over() -> bool:
    if player_health <= 0 and player_lives <= 0:
        return true
    else:
        return false
Enter fullscreen mode Exit fullscreen mode

Example

Now with all that said and done, here is the final snippet to sum it all up. See it in action.

Note: The examples and conventions provided in this post are intended as guidelines. Feel free to tailor them to your specific needs and preferences. Consistency is key!

@tool
## This is the Player class, representing the player character in the game.
class_name Player extends CharacterBody2D

# Signals
signal player_hit
signal player_jump

# Enums
enum PlayerState { IDLE, RUNNING, JUMPING, FALLING }

# Constants
const MAX_HEALTH: int = 100
const MAX_SPEED: float = 150.0

# @export variables
@export var player_name: String = "Player"
@export var player_health: int = MAX_HEALTH

# Public variables
var player_state: PlayerState = PlayerState.IDLE

# Private variables
var _is_invincible: bool = false
var _jump_power: float = 400.0

# @onready variables
@onready var player_sprite: Sprite2D = $Sprite2D
@onready var collision_shape: CollisionShape2D = $CollisionShape2D

# Optional built-in virtual _init method
func _init():
        player_health = MAX_HEALTH
        _is_invincible = false

# Optional built-in virtual _enter_tree() method
func _enter_tree():
        # Code here will be executed when the node enters the scene tree.
        pass

# Built-in virtual _ready method
func _ready():
        # Code here will be executed once when the node is added to the scene and ready.
        pass

# Remaining built-in virtual methods
func _process(delta: float):
        # Code here will be executed every frame (if the node is added to the scene and ready).
        pass

# Public methods
func take_damage(damage_amount: int):
        if not _is_invincible:
                player_health -= damage_amount
                if player_health <= 0:
                        emit_signal("player_hit")

func jump():
        if is_on_floor():
                player_state = PlayerState.JUMPING
                apply_impulse(Vector2.UP * _jump_power)
                emit_signal("player_jump")

# Private methods
func _on_invincibility_timer_timeout():
        _is_invincible = false

# Subclasses
# Add any subclasses here, if applicable.

Enter fullscreen mode Exit fullscreen mode

Summary

Following an organized code ordering is crucial for creating clean, maintainable, and readable GDScript code. By adopting a consistent approach to arranging your code elements, you can improve collaboration within your development team and make it easier for other developers to understand and contribute to your projects.

Remember that the conventions provided in this post are recommendations, not strict rules. Feel free to adapt them to your project's needs and your team's preferences. The key is to maintain consistency throughout your codebase, making it easier for everyone to work together efficiently.

Happy coding, and may your GDScript projects shine with well-organized and easily understandable code! ๐Ÿš€๐ŸŽฎ

Top comments (1)

Collapse
 
terabytetiger profile image
Tyler V. (he/him)

Thanks for writing this series ๐Ÿ™๐Ÿป It's super helpful for someone testing the waters!