Simple In-Game Currency System in Godot

Posted by Gemma Ellison
./
July 13, 2025

It’s a universally accepted truth: games need currency. But many beginners stumble when implementing a simple currency system in Godot. They often overcomplicate things, leading to messy code and frustrating debugging sessions. Let’s ditch the overly complex approaches and build a robust, easily understandable in-game currency system.

Storing the Coin: The Power of GDScript Variables

The cornerstone of any currency system is storing the actual amount. Let’s use GDScript and a simple variable. We’ll avoid using resources or external files for this basic system.

extends Node

var coins: int = 0 # Initialize the player's coin count

This snippet declares a variable named coins and initializes it to zero. It’s strongly typed as an integer (int), ensuring that we only store whole numbers, preventing floating-point errors down the line. A common mistake is forgetting to initialize the variable, leading to unexpected null values and crashes.

Displaying Wealth: Integrating with the UI

Now, for the fun part – showing our hard-earned coins on the screen! Godot’s UI system is incredibly flexible. We’ll use a Label node to display the currency.

First, create a CanvasLayer to house your UI elements. Then, add a Label node as a child of the CanvasLayer. Position the Label where you want it on the screen.

# In your main game script or player script
onready var coin_label: Label = $CanvasLayer/CoinLabel

func _ready():
    update_coin_label()

func update_coin_label():
    coin_label.text = "Coins: " + str(coins)

Here, we grab a reference to our Label node using onready var. This ensures that the node is ready before we try to access it. The update_coin_label function converts the integer coins value to a string using str() and updates the text property of the Label. Forgetting to convert the integer to a string is a very common mistake. The _ready function calls the update_coin_label so our initial coin count is displayed.

Transaction Time: Earning and Spending Coins

Let’s add some basic transaction logic. Imagine the player defeats an enemy and earns coins, or buys an item from a shop.

func add_coins(amount: int):
    coins += amount
    update_coin_label()

func remove_coins(amount: int):
    if coins >= amount:
        coins -= amount
        update_coin_label()
        return true # Transaction successful
    else:
        print("Not enough coins!")
        return false # Transaction failed

These two functions, add_coins and remove_coins, handle the earning and spending of currency. The remove_coins function includes a crucial check: ensuring the player has enough coins before attempting to subtract. Without this check, you’ll end up with negative coin counts and potentially broken game logic. Returning a boolean allows other game logic to determine success or failure of spending.

Signals for Dynamic UI Updates: The Key to Responsiveness

Directly calling update_coin_label() after every transaction works, but it’s not the cleanest approach. Signals provide a more elegant solution. Think of signals as event broadcasters. When something interesting happens (like the coin count changing), a signal is emitted, and any connected functions are automatically called.

signal coins_changed(new_amount)

func add_coins(amount: int):
    coins += amount
    coins_changed.emit(coins)

func remove_coins(amount: int):
    if coins >= amount:
        coins -= amount
        coins_changed.emit(coins)
        return true
    else:
        print("Not enough coins!")
        return false

Now, in your main game script or UI script, connect the coins_changed signal to the update_coin_label function. This can be done either in the editor or in code:

# In your main game script or UI script's _ready() function
func _ready():
    # Assuming your Player node is called "Player"
    var player = get_node("Player")
    if player:
        player.coins_changed.connect(update_coin_label)
    update_coin_label()

Now, whenever add_coins or remove_coins is called, the coins_changed signal will be emitted, triggering the update_coin_label function and automatically updating the UI. Remember to connect the signal appropriately! Forgetting this connection is a frequent cause of headaches.

Potential Pitfalls and How to Dodge Them

Beginners often stumble over a few common issues:

  • Data Types: Using the wrong data type for storing currency (e.g., floats when you only need integers) can lead to rounding errors and inconsistencies. Stick to integers unless you specifically need fractional currency.
  • Negative Currency: Failing to prevent negative currency values can break game mechanics. Always check if the player has enough funds before allowing a purchase.
  • UI Updates: Directly manipulating UI elements from different threads or nodes can cause crashes. Use signals to ensure UI updates are handled safely and efficiently.
  • Save/Load Issues: Forgetting to save/load the currency value will mean your player starts with zero coins every time they play. Use ConfigFile or JSON to persist the coins value.

Real-World Application: A Simple Shop System

Let’s expand our system to include a basic shop. Imagine a player interacting with an NPC to buy health potions.

# Shop script (attached to the NPC)
export var potion_cost: int = 10

func buy_potion(player):
    if player.remove_coins(potion_cost):
        # Give the player a potion (implementation omitted for brevity)
        print("Potion purchased!")
    else:
        print("Not enough coins to buy a potion.")

This example demonstrates how to integrate the currency system into a larger game mechanic. The buy_potion function attempts to remove the cost of the potion from the player’s inventory. If successful, the player receives a potion. If not, they receive an error message.

By mastering these fundamentals, you’ll create a simple, yet effective, in-game currency system that works. Remember, keep it simple, use signals wisely, and always validate your transactions. Now go forth and create economies!