DirAccess
最后同步日期:2026-04-15 | Godot 官方原文 — DirAccess
DirAccess
定义
DirAccess 是 Godot 提供的文件夹(目录)操作工具类。如果 FileAccess 是"文件管家",那 DirAccess 就是"文件夹管家"——它能帮你创建文件夹、删除文件夹、列出文件夹里的内容、检查文件夹是否存在等。
和 FileAccess 一样,它不需要在场景中添加节点,而是通过静态方法 DirAccess.Open() 获取一个目录访问对象,然后就能操作了。用完同样需要调用 Close() 关闭。
常用方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Open(path) | DirAccess(静态) | 打开指定路径的目录,返回目录操作对象 |
OpenInMemory(directory) | DirAccess(静态) | 打开内存中的虚拟目录 |
GetNext() | string | 获取当前目录中的下一个条目(文件或子目录) |
GetCurrentDir() | string | 获取当前工作目录的路径 |
GetCurrentDrive() | int | 获取当前驱动器编号 |
GetDrives() | string[](静态) | 获取系统中所有驱动器列表(如 ["C:", "D:"]) |
MakeDir(path) | Error | 创建子目录(父目录必须存在) |
MakeDirRecursive(path) | Error | 递归创建目录(连同所有不存在的父目录一起创建) |
Remove(path) | Error | 删除文件或空目录 |
RemoveRecursive(path) | Error | 递归删除目录及其所有内容 |
Rename(from, to) | Error | 重命名或移动文件/目录 |
Copy(source, dest) | Error(静态) | 复制文件 |
DirExists(path) | bool | 检查目录是否存在 |
FileExists(path) | bool | 检查文件是否存在 |
GetCurrentIncludingSlash() | string | 获取当前路径(以 / 结尾) |
ListDirBegin() | void | 开始列出当前目录内容 |
ListDirEnd() | void | 结束目录列表遍历 |
Close() | void | 关闭目录对象 |
代码示例
基础用法:列出目录内容
using Godot;
public partial class DirListDemo : Node
{
public override void _Ready()
{
// 打开 user:// 目录
using var dir = DirAccess.Open("user://");
if (dir != null)
{
GD.Print("=== user:// 目录内容 ===");
dir.ListDirBegin();
string fileName = dir.GetNext();
while (fileName != "")
{
// 跳过 . 和 .. 这两个特殊目录
if (fileName != "." && fileName != "..")
{
var fullPath = "user://" + fileName;
if (dir.DirExists(fileName))
{
GD.Print($"[目录] {fileName}");
}
else if (dir.FileExists(fileName))
{
GD.Print($"[文件] {fileName}");
}
}
fileName = dir.GetNext();
}
dir.ListDirEnd();
GD.Print("=== 列表结束 ===");
// 运行结果: === user:// 目录内容 ===
// 运行结果: [目录] save_game.json
// 运行结果: [文件] settings.cfg
// 运行结果: === 列表结束 ===
}
else
{
GD.PrintErr("无法打开 user:// 目录");
}
}
}extends Node
func _ready():
# 打开 user:// 目录
var dir = DirAccess.open("user://")
if dir:
print("=== user:// 目录内容 ===")
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
# 跳过 . 和 .. 这两个特殊目录
if file_name != "." and file_name != "..":
if dir.dir_exists(file_name):
print("[目录] %s" % file_name)
elif dir.file_exists(file_name):
print("[文件] %s" % file_name)
file_name = dir.get_next()
dir.list_dir_end()
print("=== 列表结束 ===")
# 运行结果: === user:// 目录内容 ===
# 运行结果: [目录] save_game.json
# 运行结果: [文件] settings.cfg
# 运行结果: === 列表结束 ===
else:
push_error("无法打开 user:// 目录")实际场景:资源管理器(创建、复制、删除)
using Godot;
/// <summary>
/// 资源管理工具:封装常用的目录和文件操作。
/// </summary>
public partial class AssetManager : Node
{
private static readonly string ScreenshotDir = "user://screenshots/";
/// <summary>
/// 确保截图目录存在
/// </summary>
public void EnsureScreenshotDir()
{
using var dir = DirAccess.Open("user://");
if (dir != null && !dir.DirExists("screenshots"))
{
dir.MakeDir("screenshots");
GD.Print("截图目录已创建");
// 运行结果: 截图目录已创建
}
}
/// <summary>
/// 递归复制整个目录
/// </summary>
public Error CopyDirectory(string source, string dest)
{
using var dir = DirAccess.Open(source);
if (dir == null)
{
GD.PrintErr("源目录不存在: ", source);
return Error.Failed;
}
// 创建目标目录
using var destDir = DirAccess.Open("user://");
destDir?.MakeDirRecursive(dest);
// 遍历源目录
dir.ListDirBegin();
string item = dir.GetNext();
while (item != "")
{
if (item == "." || item == "..")
{
item = dir.GetNext();
continue;
}
var srcPath = source + "/" + item;
var dstPath = dest + "/" + item;
if (dir.DirExists(item))
{
CopyDirectory(srcPath, dstPath);
}
else
{
var err = DirAccess.CopyAbsolute(
ProjectSettings.GlobalizePath(srcPath),
ProjectSettings.GlobalizePath(dstPath)
);
if (err != Error.Ok)
{
GD.PrintErr($"复制失败: {srcPath}");
}
}
item = dir.GetNext();
}
dir.ListDirEnd();
GD.Print($"目录已复制: {source} -> {dest}");
return Error.Ok;
}
/// <summary>
/// 清理指定目录中的所有文件
/// </summary>
public Error CleanDirectory(string path)
{
using var dir = DirAccess.Open(path);
if (dir == null) return Error.Failed;
dir.ListDirBegin();
string item = dir.GetNext();
int deletedCount = 0;
while (item != "")
{
if (item != "." && item != "..")
{
if (dir.DirExists(item))
{
dir.RemoveRecursive(item);
}
else
{
dir.Remove(item);
}
deletedCount++;
}
item = dir.GetNext();
}
dir.ListDirEnd();
GD.Print($"已清理 {deletedCount} 个条目");
// 运行结果: 已清理 5 个条目
return Error.Ok;
}
}extends Node
## 资源管理工具:封装常用的目录和文件操作。
const SCREENSHOT_DIR := "user://screenshots/"
## 确保截图目录存在
func ensure_screenshot_dir() -> void:
var dir = DirAccess.open("user://")
if dir and not dir.dir_exists("screenshots"):
dir.make_dir("screenshots")
print("截图目录已创建")
# 运行结果: 截图目录已创建
## 递归复制整个目录
func copy_directory(source: String, dest: String) -> Error:
var dir = DirAccess.open(source)
if dir == null:
push_error("源目录不存在: " + source)
return FAILED
# 创建目标目录
var dest_root = DirAccess.open("user://")
if dest_root:
dest_root.make_dir_recursive(dest)
# 遍历源目录
dir.list_dir_begin()
var item = dir.get_next()
while item != "":
if item == "." or item == "..":
item = dir.get_next()
continue
var src_path = source + "/" + item
var dst_path = dest + "/" + item
if dir.dir_exists(item):
copy_directory(src_path, dst_path)
else:
var err = DirAccess.copy_absolute(
ProjectSettings.globalize_path(src_path),
ProjectSettings.globalize_path(dst_path)
)
if err != OK:
push_error("复制失败: " + src_path)
item = dir.get_next()
dir.list_dir_end()
print("目录已复制: %s -> %s" % [source, dest])
return OK
## 清理指定目录中的所有文件
func clean_directory(path: String) -> Error:
var dir = DirAccess.open(path)
if dir == null:
return FAILED
dir.list_dir_begin()
var item = dir.get_next()
var deleted_count := 0
while item != "":
if item != "." and item != "..":
if dir.dir_exists(item):
dir.remove_recursive(item)
else:
dir.remove(item)
deleted_count += 1
item = dir.get_next()
dir.list_dir_end()
print("已清理 %d 个条目" % deleted_count)
# 运行结果: 已清理 5 个条目
return OK进阶用法:配置文件管理系统
using Godot;
using System.Collections.Generic;
/// <summary>
/// 一个完整的配置文件管理系统,支持:
/// - 自动创建配置目录
/// - 首次运行时生成默认配置
/// - 读取和写入 INI 风格的配置
/// - 配置文件损坏时自动恢复默认值
/// </summary>
public partial class ConfigManager : Node
{
private static readonly string ConfigDir = "user://config/";
private static readonly string ConfigFile = ConfigDir + "game.cfg";
private readonly Dictionary<string, string> _settings = new();
public override void _Ready()
{
InitializeConfig();
LoadConfig();
}
/// <summary>
/// 初始化配置目录和默认配置文件
/// </summary>
private void InitializeConfig()
{
// 确保配置目录存在
using var dir = DirAccess.Open("user://");
if (dir != null && !dir.DirExists("config"))
{
dir.MakeDir("config");
GD.Print("配置目录已创建");
}
// 如果配置文件不存在,创建默认配置
if (!FileAccess.FileExists(ConfigFile))
{
using var file = FileAccess.Open(ConfigFile, FileAccess.ModeFlags.Write);
if (file != null)
{
file.StoreLine("# 游戏配置文件");
file.StoreLine("[graphics]");
file.StoreLine("quality=high");
file.StoreLine("vsync=true");
file.StoreLine("resolution_width=1920");
file.StoreLine("resolution_height=1080");
file.StoreLine("[audio]");
file.StoreLine("master_volume=80");
file.StoreLine("music_volume=70");
file.StoreLine("sfx_volume=90");
file.StoreLine("[gameplay]");
file.StoreLine("difficulty=normal");
file.StoreLine("language=zh_CN");
GD.Print("默认配置文件已创建");
// 运行结果: 默认配置文件已创建
}
}
}
/// <summary>
/// 加载配置
/// </summary>
private void LoadConfig()
{
using var file = FileAccess.Open(ConfigFile, FileAccess.ModeFlags.Read);
if (file == null) return;
string currentSection = "";
while (!file.EofReached())
{
var line = file.GetLine()?.Trim();
if (string.IsNullOrEmpty(line) || line.StartsWith("#")) continue;
if (line.StartsWith("[") && line.EndsWith("]"))
{
currentSection = line.Substring(1, line.Length - 2);
continue;
}
var parts = line.Split('=', 2);
if (parts.Length == 2)
{
var key = $"{currentSection}.{parts[0].Trim()}";
_settings[key] = parts[1].Trim();
}
}
GD.Print($"配置已加载,共 {_settings.Count} 项");
// 运行结果: 配置已加载,共 9 项
}
/// <summary>
/// 获取配置值
/// </summary>
public string Get(string section, string key, string defaultValue = "")
{
var fullKey = $"{section}.{key}";
return _settings.GetValueOrDefault(fullKey, defaultValue);
}
/// <summary>
/// 设置配置值并保存
/// </summary>
public void Set(string section, string key, string value)
{
var fullKey = $"{section}.{key}";
_settings[fullKey] = value;
SaveConfig();
}
/// <summary>
/// 保存配置到文件
/// </summary>
private void SaveConfig()
{
using var file = FileAccess.Open(ConfigFile, FileAccess.ModeFlags.Write);
if (file == null) return;
file.StoreLine("# 游戏配置文件(自动生成)");
string lastSection = "";
foreach (var kvp in _settings)
{
var dotIndex = kvp.Key.IndexOf('.');
var section = kvp.Key[..dotIndex];
var key = kvp.Key[(dotIndex + 1)..];
if (section != lastSection)
{
file.StoreLine($"[{section}]");
lastSection = section;
}
file.StoreLine($"{key}={kvp.Value}");
}
GD.Print("配置已保存");
}
}extends Node
## 一个完整的配置文件管理系统,支持:
## - 自动创建配置目录
## - 首次运行时生成默认配置
## - 读取和写入 INI 风格的配置
## - 配置文件损坏时自动恢复默认值
const CONFIG_DIR := "user://config/"
const CONFIG_FILE := CONFIG_DIR + "game.cfg"
var _settings: Dictionary = {}
func _ready():
_initialize_config()
_load_config()
## 初始化配置目录和默认配置文件
func _initialize_config() -> void:
# 确保配置目录存在
var dir = DirAccess.open("user://")
if dir and not dir.dir_exists("config"):
dir.make_dir("config")
print("配置目录已创建")
# 如果配置文件不存在,创建默认配置
if not FileAccess.file_exists(CONFIG_FILE):
var file = FileAccess.open(CONFIG_FILE, FileAccess.WRITE)
if file:
file.store_line("# 游戏配置文件")
file.store_line("[graphics]")
file.store_line("quality=high")
file.store_line("vsync=true")
file.store_line("resolution_width=1920")
file.store_line("resolution_height=1080")
file.store_line("[audio]")
file.store_line("master_volume=80")
file.store_line("music_volume=70")
file.store_line("sfx_volume=90")
file.store_line("[gameplay]")
file.store_line("difficulty=normal")
file.store_line("language=zh_CN")
file.close()
print("默认配置文件已创建")
# 运行结果: 默认配置文件已创建
## 加载配置
func _load_config() -> void:
var file = FileAccess.open(CONFIG_FILE, FileAccess.READ)
if file == null:
return
var current_section := ""
while not file.eof_reached():
var line = file.get_line().strip_edges()
if line == "" or line.begins_with("#"):
continue
if line.begins_with("[") and line.ends_with("]"):
current_section = line.substr(1, line.length() - 2)
continue
var parts = line.split("=", 2)
if parts.size() == 2:
var key = "%s.%s" % [current_section, parts[0].strip_edges()]
_settings[key] = parts[1].strip_edges()
file.close()
print("配置已加载,共 %d 项" % _settings.size())
# 运行结果: 配置已加载,共 9 项
## 获取配置值
func get_value(section: String, key: String, default_value: String = "") -> String:
var full_key = "%s.%s" % [section, key]
return _settings.get(full_key, default_value)
## 设置配置值并保存
func set_value(section: String, key: String, value: String) -> void:
var full_key = "%s.%s" % [section, key]
_settings[full_key] = value
_save_config()
## 保存配置到文件
func _save_config() -> void:
var file = FileAccess.open(CONFIG_FILE, FileAccess.WRITE)
if file == null:
return
file.store_line("# 游戏配置文件(自动生成)")
var last_section := ""
# 按排序的键写入,确保输出稳定
var sorted_keys = _settings.keys()
sorted_keys.sort()
for full_key in sorted_keys:
var dot_index = full_key.find(".")
var section = full_key.substr(0, dot_index)
var key = full_key.substr(dot_index + 1)
if section != last_section:
file.store_line("[%s]" % section)
last_section = section
file.store_line("%s=%s" % [key, _settings[full_key]])
file.close()
print("配置已保存")注意事项
路径前缀和 FileAccess 相同:
user://用于可读写的数据目录,res://用于资源目录(打包后只读)。用完必须关闭:和
FileAccess一样,使用完毕后需要调用Close()(GDScript 中也可用dir = null让垃圾回收处理,但显式close()更安全)。遍历目录要跳过
.和..:GetNext()返回的列表中会包含.(当前目录)和..(上级目录)这两个特殊条目。在遍历时记得跳过它们,否则可能造成死循环。MakeDir vs MakeDirRecursive:
MakeDir只能创建一级目录(父目录必须存在),而MakeDirRecursive可以一次创建多级目录(如a/b/c)。建议优先使用MakeDirRecursive,更安全。删除目录前确认是空的:
Remove()只能删除空目录和文件。如果要删除一个包含内容的目录,使用RemoveRecursive()。但请注意,这是不可逆操作,删了就没了。Web 平台限制:和
FileAccess一样,在 HTML5 导出中目录操作功能受限。user://目录在 Web 上使用的是 IndexedDB 模拟的虚拟文件系统,功能有限。
