Optimise multiple instantiation of the same node with different assets

Godot Version

4.2.1

Question

I am building a 2D hex based game. There will be multiple hex types, each with its own behaviour. My implementation so far:

A single ‘hex_tile’ scene which will provide a sprite and a collision2D nodes:
822fWVOT

A ‘HexTile’ class which implements shared game logic:

class_name HexTile extends Area2D

@export var hex_type : String = "Unasssinged"
@onready var hex_sprite = $HexSprite
  
func _input_event(viewport: Node, event: InputEvent, shape_idx: int) -> void:
    if event.is_action_pressed("mouse_left_click"):
        print(hexType + " Hex Clicked")

func setPosition(_position: Vector2):
    position = _position

Each hex type is implemented with an inheriting class:

class_name HexTileForest extends HexTile

# Called when the node enters the scene tree for the first time.
func _ready():
    hexType = "Forest"
    super._ready()

A JSON file stores the paths to the assets for each hex type. There are 6 type of hexes (not all shown here):

{
    "hexes": [
        {
            "hex_type_id": "1",
            "name": "Grass",
            "script": "hex_tile_grass.gd",
            "texture": "hex_tile_grass.png"
        },

A configuration class loads the configuration and serves assets:

class_name HexConfiguration extends Node

var hex_data
var hex_textures = []
var hex_scripts = []

func load_hex_config():
    var file = FileAccess.open("res://level/hex_tiles/hex_config.json", FileAccess.READ)
    var json_text = file.get_as_text()
    hex_data = JSON.parse_string(json_text)
    
    for hex in hex_data["hexes"]:
        hex_textures.append(
            [
                hex["hex_type_id"], 
                load("res://level/hex_tiles/" + hex['texture'])
            ]
        )
        hex_scripts.append(
            [
                hex["hex_type_id"], 
                load("res://level/hex_tiles/" + hex['script'])
            ]
        )

func get_hex_script(hex_type_id: String) -> Script:
    for script in hex_scripts:
        if script[0] == hex_type_id:
            return(script[1])
    
    return null

func get_hex_texture(hex_type_id: String) -> Texture:
    for texture in hex_textures:
        if texture[0] == hex_type_id:
            return(texture[1])
    
    return null

func get_hex_name(hex_type_id: String) -> String:
    for hex in hex_data["hexes"]:
        if hex["hex_type_id"] == hex_type_id:
            return(hex['name'])
    
    return ""

A map is an array of integers, each representing a hex_type_id: [[7,7,7,7,7,7,7,7,7,7,7,7],[7,5,5,5,5,5,5,5,5,5,7,7],[7,5,1,1,1,1,1,1,1,5,7,7],[7,5,1,3,3,3,6,1,5,5,7,7],[7,7,5,1,1,3,3,6,6,5,7,7],[7,7,5,5,5,5,5,6,6,7,7,7],[7,7,7,7,7,7,7,7,6,6,5,7],[7,5,5,5,5,5,7,7,6,6,5,7],[7,7,5,1,3,3,3,7,7,7,7,7],[7,5,5,1,3,3,3,6,6,6,5,7],[7,7,5,5,5,5,5,5,6,6,5,7],[7,7,7,7,7,7,7,7,7,7,7,7]]

Finally, the hex_map class loads a map file, and creates the hex map:

class_name HexMap extends Node

# The name of the map file to load
@export var map_name := "map_02.txt"
# Load the vanilla base hex scene
var hex_tile_scene: PackedScene = load("res://level/HexTile.tscn")
# Stores the map array
var map_data = []
# A configuration class
var hex_configuration: HexConfiguration

func _ready():
    # Load the configuration file
    hex_configuration = HexConfiguration.new()
    hex_configuration.load_hex_config()
    # Load the map data
    load_map_data("res://level/maps/" + map_name)
    # Create the map
    create_hex_map()
    
func load_map_data(file_path: String):
    # This loads the map 2D array into map_data, no issues here

func create_hex_map():
    for row_index in range(map_data.size()):
        for column_index in range(map_data[row_index].size()):
            # Fetch the hex_type from map_data
            var hex_type_index = str(map_data[row_index][column_index])
            # Instantiate a new hex tile node
            var new_hex_tile = hex_tile_scene.instantiate()
            # Get the script from hex_configuration and assign to the node
            new_hex_tile.set_script(
            hex_configuration.get_hex_script(hex_type_index))
            # Adding the hex instance to the map
            add_child(new_hex_tile)
            # Set the tile's type name
            new_hex_tile.hex_type = hex_configuration.get_hex_name(hex_type_index)
            # Get the texture from hex_configuration and assign to the node
            new_hex_tile.hex_sprite.texture = hex_configuration.get_hex_texture(hex_type_index)
            # Do some more positioning stuff

This is all working. The issue is that it takes a very long time to load big maps, which is what the game will need. I had an earlier prototype where each hex type was its own scene with script and texture, however I am planning to add more nodes to the hex node, and don’t want to do it again for each type. How can I optimise this?

Make sure to use the profiler to see what needs optimization. If loading map data isn’t an issue but creating a hex map is, then I would guess the constant add_child call might be an issue. Manipulating the scene tree can be very taxing, as a hail mary try adding the new tiles to a intermediate node, the finally add the intermediate to the scene tree.

func create_hex_map():
    var intermediate := Node2D.new()

    for row_index in range(map_data.size()):
        for column_index in range(map_data[row_index].size()):
            var hex_type_index = str(map_data[row_index][column_index])
            var new_hex_tile = hex_tile_scene.instantiate()
            new_hex_tile.set_script(
            hex_configuration.get_hex_script(hex_type_index))

            # adding to disconnected node first
            intermediate.add_child(new_hex_tile)

            new_hex_tile.hex_type = hex_configuration.get_hex_name(hex_type_index)
            new_hex_tile.hex_sprite.texture = hex_configuration.get_hex_texture(hex_type_index)

    add_child(intermediate)

If this does nothing to help, then you’ll have to check in with the profilier and/or try moving to Tilemaps. Try exporting your game too, this will compress text-based resources into a binary/pack format that loads faster.

Bad news all around. It looks like it’s not possible to start the profiler before the game is running so I can’t analyse the initialisation process

More worrying is that I exported the project and it’s not running.
Debug mode

Export