0 votes

I tried making a custom Resource array for this functionality but it's extremely slow to set up by code, making the game take a very long time to load. This was the code:

extends Resource

class_name TileData

export(Vector2) var position
export(int) var durability 

Essentially, I have a tilemap which has tiles and I'd like to store grid locations as well as an int value (durability) on this array which would essentially be an unrendered version of my tilemap that just stores the tile's position and the tile's durability.
I could instantiate nodes that contain the TileData script on each tile but I feel like that would cause massive slowdowns as well, so I'm not really sure on what approach to take here.

Is there a better way to do this? How can I efficiently store tile information for each tile?

Thanks in advance!

Godot version Godot Engine v3.2.3.stable.official
in Engine by (12 points)

1 Answer

+1 vote

There is no problem in making of such array, it will just be array of arrays. But why don't You use dictionary ? It would be clear and no index would be needed. It would look like this : { "1,2" : 1,"2,2" : 5} and so on. I don'd get how Your custom array would slow down project. You just make what You want in editor and create whole thing with the code :

tiledata : dictionary
for tile in get_used_cells() :
        tiledata[var2str(tile)] = get durability whatever it is :)

Converting string back into vector2 is done via str2var()

If You want to define unrendered map and durability from the outside, then You can make JSON files and load them, this is maybe most efficient option of keeping large data. You can do this in Google Sheet and download addon Export Sheet Data to it, which will translate Your table sheet into JSON file. You move JSON to Your game directory and load it from code, this part is longer but You will easily find it in documentation and tutorials.

by (2,416 points)

Thank you very much for your in-depth response!
I already am using a JSON to determine the durability values but that's the max durability of each tile. Would the dictionary be able to keep the current durability value for each tile?
I'd like for each tile to store its own value so that if you hit a tile with durability 2 once, you only need to hit again one more time.
My game's breaking mechanic right now works like Minecraft's (if you stop holding the left mouse button, the breaking progress resets) since I don't have a way to store each tiles's durability value in an updated manner. I'd like it to work like Terraria's however (where tiles keep track of their progress), would that be possible with the Dictionary?

The issue I had with the array of Resources shown above was that I had to use a special method posted here to populate it with my custom resource when the game started and only after that assign each tile's position and durability to it, which slowed down my game by a lot since the arrays I have would be 300 * 200 = 50000 elements in size. I believe I was testing with > 800000 elements at the time as well and it was slow.
Am I doing something wrong? This was my code, everything related to resource data has been commented out since then due to performance issues:

export(Array, Resource) var saved_tile_data setget set_custom_resource_data

func _ready():
    clear()
    randomize()
    generate_map()
    set_custom_resource_data(saved_tile_data)

func generate_tile_data(tile_data):
    print(len(get_used_cells()))
    tile_data.resize(len(get_used_cells()))
    set_custom_resource_data(tile_data)
    var saved_tile_data_edit = tile_data.duplicate()
    print(saved_tile_data_edit.size())

    #working, but extremely slow

    for i in saved_tile_data_edit.size():
        for x in mapWidth:
            for y in mapHeight:
                var tile_position = world_to_map(Vector2(x, y))
                saved_tile_data_edit[i].position = Vector2(tile_position.x, tile_position.y)
                saved_tile_data_edit[i].id = get_cellv(saved_tile_data[0].position)
                # freezes up the game at this point, won't load
                #saved_tile_data_edit[i].connect("changed", saved_tile_data_edit[i], "on_tile_data_changed")
    print("finished setting up tile data")

func set_custom_resource_data(value):
    for i in saved_tile_data.size():
        if not saved_tile_data[i]:
            saved_tile_data[i] = TileData.new()
            saved_tile_data[i].resource_name = "Tile Data"

Does this code look like it would slow down the game at start? It took a very long time to load the map when ran, but I might be doing something wrong since I'm new to Godot ^^'
The game ran fine after loading the map, I believe. It's just that it would take a very long time to start and sometmes it would even stop responding and crashing.
I also tried to move the function to a separate thread entirely but I got an error that said thread couldn't be created and have since then deleted that part of the code.

Thanks in advance!

I must admit that I can't understand this code :P
I never tried to make custom resources, it was not necessarry.

I understand, that all You want is to have a ton of tiles in game, that keep track of their metadata, which is only one element - durability ?

I would create one dictionary in your main manager node, calling it for example durabilitymap and it would build up like this on ready :

for tile in tilemap.get_used_cells() :
      durabilitymap[str(tile)] = its base durability, whenever it comes from :)

And during the game You will get collision position of tile being mined, translate it
worldtomap if ncesarry, and again :

 durabilitymap[str(worldtomap(collisionpos)] -= 1
      if durabilitymap[str(worldtomap(collisionpos)] <= 0 :
              getridoftile

I think the only thing I'd need is durability since I can get the tile's name through getting its value (get_cellv) and getting that value from the tileset.
I'll try your solution. But what if I end up needing more than durability? Would a dictionary still do? Or should I use a JSON that has its values updated?

I did turn base game and and my tiles had a lot of metainformation. Total amount of tiles were nothing close to Your project, but I never experienced even a minor slowdown. I believe diciotnary solution is simplest thus fastest way of computing this. Definetely faster than updating JSON file, this would be the slowest. JSON is only good if You want to load predefined big amount of data once into a project, than use it, but not change it. So You can still define Your map layout and durabilities/tile in JSON, translate it into normal dictionary, and update resulting dictionary in code.

I see. I knew dictionaries could store data, I just wasn't sure if they were ok to use for storing data that would be changed during runtime.
I tried your suggestion and got it to work. But a dictionary won't hold more than a key and a value, right? What if I wanted to store more data than just durability?
Not like I need to right now, but I'm not sure if I won't have to in the future.
What would you use in that case? Multiple dictionaries for each data? That seems inefficient to me since I'd have to store positions I would already know from the other dictionary and have to update two dictionaries instead of one when I make a change to the tiles.
How would you go about this?
I'd like to make my code as future-proof as possible.

for each key You can hold multiple values - with an array or another dictionary. This is what I used for my metadata : dictionary of dictionaries. It looked like this :
tiledata = {"0,0" : {"index":1,"colour":"red","enemy": null},"0,1" : { "index" : 2, "colour" : "red", "enemy" : "boss"}}

You only need to remember, that both dictionaries and arrays are passed via reference. This means that if You pass it into another variable and change this variable, the whole dictionary will be changed, unless You pass it as dictionary.duplicate() or in case of nested dict ( as mine example ) : dictionary.duplicate(true)

I had no idea that nested dictionaries were a thing! This is very useful!

I'm still a bit confused after attempting to implement this, however.
I currently have 2 dictionaries and they both get their keys from a JSON that looks like this:

{
    "Dirt": {
        "Durability": 2,
        "Breakable": true
    },
    "Grass": {
        "Durability": 2,
        "Breakable": true
    },
    "Stone": {
        "Durability": 4,
        "Breakable": false
    }
}

I'm trying to create the dictionaries like so:

export(Dictionary) onready var durability_map
export(Dictionary) onready var breakability_map
export(Dictionary) onready var merged_map = {durability_map: {}, breakability_map: {}}

And assigning the durability and breakability dictionaries like so:

for tile in get_used_cells():
        var tile_id = get_cellv(tile)
        var tile_name = tileSet.tile_get_name(tile_id)

        durability_map[str(tile)] = int(JsonData.block_data[tile_name]["Durability"])
        breakability_map[str(tile)] = bool(JsonData.block_data[tile_name]["Breakable"])

How do I assign the variables to the merged_map?
This code gives me error (Invalid get index '(395, -21)' (on base: 'Dictionary').

merged_map[str(tile)]["Durability"] = int(JsonData.block_data[tile_name]["Durability"])
merged_map[str(tile)]["Breakable"] = bool(JsonData.block_data[tile_name]["Breakable"])

I'd want the merged_map dictionary to look like:

merged_map = ["0,0" : {"Durability:" 2, "Breakability": true}, "0,1": {"Durability:" 4, "Breakability": false}

for example.
How exactly do I need to declare the dictionary in order to achieve this?
I saw each key had 3 values in your example. Did you make a dictionary of 3 dictionaries?
Sorry for being slow, it's my first time working with dictionaries in Python/GDscript, let alone nested dictionaries! ^^'

First of all You don't need to merge these dictionaries. You are going to change dictionary a lot of times, so it should be smallest possible and only contain data, that will actually be changed. Tiles already have individual index, and your breakability var are simply resulting form tile index. Leave this in static JSON. Your tiles can simply check their own index and compare it with your translated JSON to know, if they should break or not.

In other words :
You need dynamic dictionary and static dictionary.
dynamic one is our tiledata. It will only keep CURRENT durability. You will introduce it like this:
tiledata = {}

for tile in all your tiles:
      tiledata[str(tile)] = {}
      # now dict is nested with other empty dicts, we only have keys that are stringed cells
      tiledata[str(tile)]["durability"] = Staticdict[str(get_cellv(tile))]["durability]

And this is the bridge between static and dynamic dict. Dynamic dict asks static dict for starting durability of tiles, and this is all their interaction. You should change " grass" "stone" "ground" into stringed tile indexes, this will be much easier to connect them

Staticdict = JSON.result

And on collision of miner and tiles :

if Staticdict[str(get_cellv(world_to_map(collision.position)))]["Breakable] == true:
           tiledata[str(world_to_map(collisionposition))]["durability"] -= 1

So I made my static dictionary the durability_map and the dynamic one the tile_data

for tile in get_used_cells():
        var tile_id = get_cellv(tile)
        var tile_name = tileSet.tile_get_name(tile_id)
        tile_data[str(tile)] = {}
        durability_map[str(tile)] = int(JsonData.block_data[tile_name]["Durability"])
        # now dict is nested with other empty dicts, we only have keys that are stringed cells
        tile_data[str(tile)]["Durability"] = durability_map[str(tile)]["Durability"]

I haven't turned my JSON keys into stringed indexes tile indexes yet but I'm not sure if I'd want to do that since I'm going to be using two separate tilesets: one for solids and one for background objects like walls, so the ids will differ between them and I'd rather connect them through name even though it requires a couple more lines of code and it'd make it easier to tell which tile is which when looking at the JSON. I think I can turn it into a function to make it simpler in the long run though.
I could also add another key into that JSON for "Name" if I used IDs but I'm not sure yet.

Anyhow, running the code above gives me the error: Invalid get index 'Durability' (on base: 'int').
I changed str(get_cellv(tile)) since I assumed that would be the version for a JSON with IDs instead of names.
I'm not sure if I was supposed to turn them into stringed tile indexes for the code to work but it gives me that error. I suppose it's because I want to use the tile's name instead of its id and get_cellv(tile) gets the id.

What can I do to make it read my current JSON properly?

EDIT:
Managed to make it work like this, by obtaining the values for the tile data through the JSON instead of through the durability map since it was giving me errors:

for tile in get_used_cells():
    var tile_id = get_cellv(tile)
    var tile_name = tileSet.tile_get_name(tile_id)
    tile_data[str(tile)] = {}
    durability_map[str(tile)] = int(JsonData.block_data[tile_name]["Durability"])
    tile_data[str(tile)]["Durability"] = int(JsonData.block_data[tile_name]["Durability"])
    tile_data[str(tile)]["Breakable"] = bool(JsonData.block_data[tile_name]["Breakable"])
    print(tile_data[str(tile)])

The console prints each tile's position, its durability and its breakability now, so I'm assuming it's working.
Thank you very much for your help, Inces!
I'll post again if I have any issues.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.