Skip to content

Instantly share code, notes, and snippets.

@hiulit
Created November 23, 2021 17:18
Show Gist options
  • Select an option

  • Save hiulit/772b8784436898fd7f942750ad99e33e to your computer and use it in GitHub Desktop.

Select an option

Save hiulit/772b8784436898fd7f942750ad99e33e to your computer and use it in GitHub Desktop.
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(file_name)
file_name = dir.get_next()
else:
print("An error occurred when trying to access %s." % path)
return files
@hiulit
Copy link
Author

hiulit commented Nov 24, 2021

If you want the full path of the file instead of just the name, change:

Line 17

files.append(file_name)

for

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

@RedwanFox
Copy link

Upgraded version for godot 4.x

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

    if DirAccess.get_open_error() == OK:
        dir.list_dir_begin()

        var file_name = dir.get_next()

        while file_name != "":
            if dir.current_is_dir():
                files = get_all_files(dir.get_current_dir() +"/"+ 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() +"/"+ file_name)

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

    return files

@aftamat4ik
Copy link

still works (godot4.2) + small improvement

## returns list of files at given path recursively
## [br]taken from - https://gist.github.com/hiulit/772b8784436898fd7f942750ad99e33e
static func get_all_files(path: String, file_ext := "", files : Array[String] = []) -> Array[String]:
	var dir : = DirAccess.open(path)
	if file_ext.begins_with("."): # get rid of starting dot if we used, for example ".tscn" instead of "tscn"
		file_ext = file_ext.substr(1,file_ext.length()-1)
	
	if DirAccess.get_open_error() == OK:
		dir.list_dir_begin()

		var file_name = dir.get_next()

		while file_name != "":
			if dir.current_is_dir():
				# recursion
				files = get_all_files(dir.get_current_dir() +"/"+ 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() +"/"+ file_name)

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

use like this:

var level_scenes := FileUtils.get_all_files(levels_folder_path, ".tscn")

@wantg
Copy link

wantg commented May 19, 2025

improvement

func get_files(path: String, ext: Variant = null) -> Array[String]:
	var dir_access = DirAccess.open(path)
	var files: Array[String] = []
	for file_name in dir_access.get_files():
		if ext == null || file_name.get_extension() == str(ext):
			files.append(path.path_join(file_name))
	for dir in dir_access.get_directories():
		files.append_array(get_files(path.path_join(dir), ext))
	return files

# get_files("res://")
# get_files("res://", "tscn")

@lucacicada
Copy link

Two variants, stack based, no recursion, documentation comments, extension comparison is case-insensitive

get_files("res://somefolder")
get_files("res://somefolder", [".tscn", ".tres"])

With DirAccess.open

## Recursively gets the absolute path of all files in a directory with an optional extension filter.
## An empty filter array will return all files.
## The filter can optionally include the leading dot. The comparison is case-insensitive so [code]".TSCN"[/code] and [code]".tscn"[/code] are equivalent.
## This function uses [method DirAccess.open].
func get_files(path: String, filter: Array[String] = []) -> Array[String]:
	# Godot get_extension() does not include the dot, so we remove it here for comparison
	for i in filter.size():
		while filter[i].begins_with("."):
			filter[i] = filter[i].substr(1)

	var found: Array[String] = []

	var stack: Array[String] = [path]
	while stack.size() > 0:
		var current_path: String = stack.pop_back()

		var dir := DirAccess.open(current_path)
		if not dir:
			push_warning("Could not open directory: \"%s\", %s" % [
				error_string(DirAccess.get_open_error()),
				current_path
			])
			continue

		var err := dir.list_dir_begin()
		if err != OK:
			push_warning("Could not list directory: \"%s\", %s" % [
				error_string(err),
				current_path
			])
			continue

		var file_name := dir.get_next()

		while not file_name.is_empty():
			if dir.current_is_dir():
				stack.append(dir.get_current_dir().path_join(file_name))

			# Fast, return everything
			elif filter.is_empty():
				found.append(dir.get_current_dir().path_join(file_name))

			# Filter by extension
			else:
				var file_ext := file_name.get_extension()
				for filter_ext in filter:
					# Perfdorm a case-insensitive comparison, it's typically what you want
					if file_ext.nocasecmp_to(filter_ext) == 0:
						found.append(dir.get_current_dir().path_join(file_name))
						break

			# Get the next file or directory
			file_name = dir.get_next()

	return found

With ResourceLoader.list_directory

## Recursively gets the absolute path of all files in a directory with an optional extension filter.
## An empty filter array will return all files.
## The filter can optionally include the leading dot. The comparison is case-insensitive so [code]".TSCN"[/code] and [code]".tscn"[/code] are equivalent.
## This function uses [method ResourceLoader.list_directory].
func get_resource_files(path: String, filter: Array[String] = []) -> Array[String]:
	# Godot get_extension() does not include the dot, so we remove it here for comparison
	for i in filter.size():
		while filter[i].begins_with("."):
			filter[i] = filter[i].substr(1)

	var found: Array[String] = []

	var stack: Array[String] = [path]
	while stack.size() > 0:
		var current_path: String = stack.pop_back()
		for res_path in ResourceLoader.list_directory(current_path):
			if res_path.ends_with("/"):
				stack.append(current_path.path_join(res_path))

			# Fast, return everything
			elif filter.is_empty():
				found.append(current_path.path_join(res_path))

			# Filter by extension
			else:
				var file_ext := res_path.get_extension()
				for filter_ext in filter:
					# Perfdorm a case-insensitive comparison, it's typically what you want
					if file_ext.nocasecmp_to(filter_ext) == 0:
						found.append(current_path.path_join(res_path))
						break

	return found

Some additional notes, once you iterate over a directory, the next iteration will be faster as the OS (really pretty much any OS) cache the filesystem for subsequential access, very noticeable when you are iterating over a large asset folder or an external/virtual drive.

There is a glob match implementation in Godot that would filter "*.png", it is not used here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment