..

Developing a high res texture mod for Godot games

Note: Using Godot 3. Concepts are the same in Godot 4.

Video games are getting larger and larger. Everyone deserves to play games, not only those with high internet speed, lots of VRAM, and a large hard drive.

Players demand two things:

  1. Low file sizes
  2. High resolution textures

The best solution to satisfy both would be a high resolution texture mod / dlc.

In Godot mods are created with .pck files which are files which contain a directory. We can “mount” a directory to our Godot game using the function: ProjectSettings.load_resource_pack(). Once loaded the engine can access any file as if it were in our project folder: var data = load("res://textures/file.png").

By default the loaded directories will replace any with the same name. This is how our high resolution texture mod will work. Loading a .pck with a textures directory containing the high resolution images will replace the low resolution textures!

When we develop our game we create two texture folders: textures/ and textures_hifi/. Any high resolution images in textures_hifi must have the same filename and path as its lower resolution counterpart in textures/. When exporting we will not export our high resolution texture folder.

Note: Godot does not directly load files in the project. It loads the .import files which remap to the actual file data found in the .import folder. Image data in Godot is actually stored in .stex files in the .import folder. Our solution will store the .import and .stex files in the .pck. We make sure to edit the .import filepath such that it replaces the low-resolution one.

tool
extends Node

export(bool) var export = false setget set_export_textures

func set_export_textures(value):
	if value == false:
		return
	
	var import_files = get_all_files("res://textures_hifi", "import")
	
	if import_files.empty():
		return

	var high_res_pck = "res://hifi.pck"
	var packer = PCKPacker.new()
	packer.pck_start(high_res_pck)
	
	for file in import_files:
		var config = ConfigFile.new()
		var err = config.load(file)
		if err != OK:
			return # Error
		
		var import_path = config.get_value("remap", "path")
		
		packer.add_file(file.replace("textures_hifi", "textures"), file)
		packer.add_file(import_path, import_path)
	packer.flush()
	

func get_all_files(path: String, file_ext := "", files := []):
	var dir = Directory.new()

	if dir.open(path) == OK:
		dir.list_dir_begin(true, true)

		var file_name = dir.get_next()

		while file_name != "":
			if dir.current_is_dir():
				files = get_all_files(dir.get_current_dir().plus_file(file_name), file_ext, files)
			else:
				if file_ext and file_name.get_extension() != file_ext:
					file_name = dir.get_next()
					continue

				files.append(dir.get_current_dir().plus_file(file_name))

			file_name = dir.get_next()
	else:
		print("An error occurred when trying to access %s." % path)

	return files

Before loading a scene with texture dependencies:

var success = ProjectSettings.load_resource_pack("res://hifi.pck")

Pitfalls:

  1. Loading from a .pck file is case sensitive! A .PNG is different than a .png.
  2. Make sure your .pck file is not open / in use, as subsequent exports will fail.

Thank you u/hiulit for the get_all_files function.