Addon Advice and Help

Godot Version

4.6

I have a lot of images with transparent borders and only want them to be select-able by the actual bounds of the content itself.

I started with a tool script but that didn’t cut it so I’m working on it now as an plugin.

Anyway I have most of the code working and the UI started, basically what it does is scan a folder, recursively if requested, it generates a dictionary of [int,Rect2i], and saves it as a binary file (working on json support as well). Is there a way to automatically add the extension for the generated file to the include filter for exporting the project? Also can you adjust the editor settings to show the file in the project file dock directly in code?

Secondly the addon also has an option to generate a node for use as a global node, that automatically loads the data on ready and has methods to access the bounds or get a TextureAtlas for any texture that has been scanned. Is there anyway to automatically add the node as a global node to the project settings?

Also I’m mostly a csharp developer when using Godot, so I was wondering for the node generated in gdscript for use as a global node would it be better for it to have a class name or not?

Implement a ResourceFormatLoader class and register its instance via ResourceLoader.add_resource_format_loader(). For a file type to be treated as a resource just override the method that returns extension list. After that, the engine will consider files with those extensions to be resources, so they’ll be included into export by default.

To setup an autoloaded node call EditorPlugin::add_autoload_singleton() from the _enable_plugin() callback. Don’t forget to call its counterpart in _disable_plugin().

For the autoload functionality naming the class is not needed.

I already have code to load the data.

the data gets generated in a .whatever file in the project folder based on textures in the project.

It then generates a node in the project to load the data like so (gd script version):slight_smile:


extends Node

var texture_regions: Dictionary[int,Rect2i]

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	_load_regions()


func _load_regions()->void:
	var file: FileAccess = FileAccess.open("res://textures.dat", FileAccess.ModeFlags.READ)

	if file:
		var texture_data = file.get_var()
		if typeof(texture_data) == TYPE_DICTIONARY:
			texture_regions.assign(texture_data)
		else:
			printerr("Failed to load texture data as a dictionary")
	else:
		printerr("Failed to load textures data file, is the path correct?")

func get_region(resource_path: String)->Rect2i:
	var key: int = hash(resource_path)
	return texture_region_from_hash(key)

func texture_region_from_hash(key: int)->Rect2i:
	return texture_regions[key]

func atalas_from_texture(texture: Texture2D)->AtlasTexture:
	var key: int = hash(texture.resource_path)
	var bounds: Rect2i = texture_regions[key]
	var atlas: AtlasTexture = AtlasTexture.new()
	atlas.atlas = texture
	atlas.region = bounds
	return atlas

My question was if it was possible to then have the addon adjust the project settings to have this be a global node in the project - not in the editor for the plugin.

Implementing a resource format loader seems a lot of work for something that can be accomplished by simply going into the export settings and adding the extension to the include list. In this instance the binary file is .dat.

This is what i was asking if there was a way to set this programmatically. I was also wondering if it was possible programmatically to change the editor settings to include the file when showing files in the project:

It’s not about loading, it’s about getting the file extension recognized as a resource file type.

Why would you want to do this? You’re starting the plugin anyway, let the plugin set it up.

You asked how to show the files with a certain extension in Godot’s file system. That’s how. Getting automatically exported is just an added bonus. Implementing ResourceFormatLoader is literally a few lines of code:

@tool
class_name MyFormat extends ResourceFormatLoader

func _get_recognized_extensions():
	return PackedStringArray(["myextension"])

Then in plugin initialization code:

ResourceLoader.add_resource_format_loader(MyFormat.new())

That’s all there is to it.

1 Like

I’m not including the plugin in the export it’s for generating data for runtime for the project.

As mentioned all textures in the project have transparent borders, and I want the bounding rects available at runtime. The plugin will eventually do other things as well, but for now I need to be able to get the bounding rects for any texture at runtime.

I didn’t ask how to show the files I asked how to show them in a specific way, by editing the editor settings which works fine for my needs.

Then setup the autoload manually. Why do you need to do it through code? It’s a one off operation that takes 5 seconds.

Seems like everything you asked can be done by manually setting it up in the editor. Also, you can just crop the textures in an image editing app. Most of them can automatically chop off transparent pixels from the edges.

Yes, still would have been nice to automate the whole process.

That way when i release it, it would require little to no interaction on the users part other than making a few choices in the UI.

I’ll probably play with the resource format loader but when I looked it seemed a little more complicated than your code. And I don’t know if I specifically need it to be a resource, as it is not like the data can be edited as it saves ints as the key for the resource and there is no way to go from the int back to the original string.

I don’t want to crop the images, that would change the sizes of the images, all of which are meant to work on 256x256 grid. Secondly the number of images at almost a thousand would take some while to crop manually while it takes the plugin seconds. Lastly the images are svg so cropping might be more complicated than with a png.

Thanks for the help.

You’re not listening what I’m saying. It’s more complicated if you want to actually load a custom resource format. You can use it in a simple way as I suggested though, to merely force the engine to display the files with a given extension and export them (as it sees them as resources). Which is what you asked for.

But I feel we’re entering the XY problem zone. You can simply store your binary data into a packed byte array that’s property of a resource object and just save that. In that case there will be no need for resource format loader or export settings. That said, why use binary data at all? Store your dictionary into a regular resource object and you’re done.

Correct me if I’m wrong but if I go the route where I store the dictionary in a resource object, then any project using the data would need the resource object to be be part of the project would it not? Whereas if the changes are made in the editor and export settings they will persist for other projects.

Secondly if i just store it in a dictionary in a resource object wouldn’t the entire dictionary have to be regenerated to change the data? Currently the data can easily be regenerated if texture paths change or textures are added or removed from the project.

Currently it takes 2 lines of script to save or load the data, so I don’t see using a resource object saving me any effort or offering any gains over the current code other then it would automatically include the file in the export.

Here’s the code it currently takes to save it:

var file = FileAccess.open(output_file, FileAccess.ModeFlags.WRITE)
	if file:
		file.store_var(regions,true)
	else:
		printerr("Unable to write data file")

What do you mean by “project needing the resource object”. It currently needs the dictionary object you store in the binary file. It’s exactly the same, just that you don’t need to bother with unpacking when using a resource, engine does it automatically for you. You also get some other serialization benefits if your data has more complex structure or dependency chain.

Again, it’s the same as what you’re doing now, only simpler.

Yes it would take one line or even zero lines if you set it up in the editor. This is actually exactly what resources are for unless I misunderstood what you’re trying to do.

I can suggest exactly how to approach this but you’ll have to describe the exact use case in its full context, what precisely are you trying to build.

normalized
1m

Correct me if I’m wrong but if I go the route where I store the dictionary in a resource object, then any project using the data would need the resource object to be be part of the project would it not?

What do you mean by “project needing the resource object”. It currently needs the dictionary object you store in the binary file.

Sorry my edit wasn’t done yet, if I use a resource it has to be part of each project, whereas if I just set .dat to be included in the editor and export settings then every project will include the .dat file if it exists correct because the export and editor settings are persistent across projects.

As it is for runtime information about the textures being loaded it will not be used in the editor. When a texture is loaded for a sprite2d the bounding rect is retrieved from the global node, and is used as an area2d for selecting the object in the subviewport as opposed to the entire texture which is used when it is actually selected to show the image bounds.

I also have a bunch of TextureRects that show textures that can be dragged and dropped onto the subviewport to be added to the map. These TextureRects are the same size and scale the image, but if the images are scaled with the border it results in images of different size because the border is scaled as well. So when it loads a group of the TextureRects it passes the texture2d to the global node which looks up the bounding rect and creates an atlasTexture from the region of the image, and the atlas is used in the texture rectangle instead of the full image resulting in uniform image sizes in the texturerects.

But do the fact that the lookup is quick it could be used for any number of things like runtime generation of collision objects for rectangle shapes etc..

So if I wanted to use your resource idea how would it work as part of a project?

I created a script with your example:


@tool
class_name TextureDataFormat extends ResourceFormatLoader

func _get_recognized_extensions():
	return PackedStringArray([".dat"])

And added it to the project but it does nothing, the .dat file neither shows up in teh filesystem dock nor is the file exported, so how would I use that as part of a project? Note I don’t want the project to have a dependency on the plugin.

You need to copy-paste a bunch of files anyway (.dat, texture resources, editor settings) when moving to another project. By storing the data in a resource you’d need to include one additional file. Don’t see that as a problem, although if what you’re doing now works for you - that’s a totally valid approach as well.

My example was only meant to force the engine to shows files with a given extension in editor’s file system. It doesn’t have anything to do with runtime or with actual loading of files with that extension. That’s why it doesn’t need any elaborate implementation.

In order for the extension to be recognized you also need to register that format loader with ResourceLoader singleton. Again this in not done for actual loading, but merely for the extension to become “known” to the editor. So you need to run:

ResourceLoader.add_resource_format_loader(TextureDataFormat.new())

in the editor, either in some editor script’s _run() or in plugin’s _enable_plugin() or _enter_tree()

If you want to store your data in a custom resource file, then you don’t need to deal with ResourceFormatLoader at all. Instead you need to create a custom resource class. Object of such class will always be saved with .tres extension and editor/exporter always recognize them as engine resources. If you migrate this to another project you’ll have to copy your resource class script and your .tres file, along with the textures.

Finally, the least tedious approach for you may be to just use json. The engine will always “see” it and you won’t need to write any custom classes. I’m also not sure why you need a plugin here. It’s a straightforward pre-processing operation. An editor script should suffice.

I originally had an editor script, but changed it to a plugin to make it more versatile, as it will be extended for many use cases, and there are so many variables, and I could not find a way for a tool script to set the variables in an editor so I created a UI, to set input resource folder, whether recursive,the output file name and path, whether the output data should be binary or json, whether to generate code for a global node, should code be c# or gdscript, if c# what namespace and class name, and finally the output file path to write the code to.