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
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
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)
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 }
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
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
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
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
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)
func _enter_tree() -> void:
var parent_name = get_parent().name
print("Parented to: " + parent_name)
func _ready() -> void:
_sync_textures()
_sync_animations()
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
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
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
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
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
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.
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)
Thanks for writing this series 🙏🏻 It's super helpful for someone testing the waters!