Cloud saves use the Remote Storage API: per-player files in the cloud, synced across devices. The SDK methods are named uploadRemoteFile, downloadRemoteFile, and similar.
Upload and download files
func save_to_cloud(file_name: String):
var path = OS.get_user_data_dir() + "/" + file_name
WavedashSDK.upload_remote_file(path)
func load_from_cloud(file_name: String):
var path = OS.get_user_data_dir() + "/" + file_name
WavedashSDK.download_remote_file(path)
public async void SaveToCloud(string localPath)
{
await Wavedash.SDK.UploadRemoteFile(localPath);
}
public async void LoadFromCloud(string localPath)
{
await Wavedash.SDK.DownloadRemoteFile(localPath);
}
const data = new TextEncoder().encode(JSON.stringify(gameState));
await Wavedash.writeLocalFile("saves/slot1.json", data);
await Wavedash.uploadRemoteFile("saves/slot1.json");
const dl = await Wavedash.downloadRemoteFile("saves/slot1.json");
if (dl.success) {
const bytes = await Wavedash.readLocalFile("saves/slot1.json");
}
local co = coroutine.create(function()
local save_path = sys.get_save_file("my_game", file_name)
wavedash.upload_remote_file_async(save_path)
wavedash.download_remote_file_async(save_path)
end)
assert(coroutine.resume(co))
Paths may be either user://... (auto-normalized) or absolute paths under OS.get_user_data_dir(). Paths outside the user data directory are rejected.
Signals. Each call emits a response signal when it resolves — connect to them once in _ready() if you prefer signal-based flow over await:
func _ready():
WavedashSDK.remote_file_uploaded.connect(func(r): print("Uploaded: ", r.data))
WavedashSDK.remote_file_downloaded.connect(func(r): print("Downloaded: ", r.data))
WavedashSDK.remote_directory_downloaded.connect(func(r): print("Dir: ", r.data))
WavedashSDK.got_remote_directory_listing.connect(func(r): print(r.data))
Checking existence
remoteFileExists issues a lightweight HEAD request so you can branch on whether a save is present without paying the full download cost.
# Await the response directly
func check_save(file_name: String):
var path = OS.get_user_data_dir() + "/" + file_name
var r = await WavedashSDK.remote_file_exists(path)
print("Exists: ", r.success and r.data)
# Or listen via signal
func _ready():
WavedashSDK.got_remote_file_exists.connect(func(r): print("Exists: ", r.success and r.data))
WavedashSDK.remote_file_exists(OS.get_user_data_dir() + "/saves/slot1.json")
public async void CheckSave(string localPath)
{
bool exists = await Wavedash.SDK.RemoteFileExists(localPath);
Debug.Log($"Exists: {exists}");
}
const result = await Wavedash.remoteFileExists("saves/slot1.json");
if (result.success && result.data) {
await Wavedash.downloadRemoteFile("saves/slot1.json");
}
-- `remote_file_exists` is not exposed in the current Defold binding.
-- Use `list_remote_directory_async()` or attempt a download instead.
To check whether a directory has any contents, use listRemoteDirectory — it returns an empty list if the directory is either empty or not found.
func _ready():
WavedashSDK.got_remote_directory_listing.connect(func(r):
print("Has saves: ", r.data.size() > 0))
func check_saves_folder():
var path = OS.get_user_data_dir() + "/saves/"
WavedashSDK.list_remote_directory(path)
var path = $"{Application.persistentDataPath}/saves/";
var files = await Wavedash.SDK.ListRemoteDirectory(path);
Debug.Log($"Has saves: {files.Count > 0}");
local co = coroutine.create(function()
local path = sys.get_save_file("my_game", "saves")
local files = wavedash.list_remote_directory_async(path)
print("Has saves:", files.success and #files.data > 0)
end)
assert(coroutine.resume(co))
const list = await Wavedash.listRemoteDirectory("saves/");
const hasSaves = list.success && list.data.length > 0;
Deleting files
func delete_save(file_name: String):
var path = OS.get_user_data_dir() + "/" + file_name
WavedashSDK.delete_remote_file(path)
func _ready():
WavedashSDK.remote_file_deleted.connect(func(r): print("Deleted: ", r.data))
public async void DeleteSave(string localPath)
{
await Wavedash.SDK.DeleteRemoteFile(localPath);
}
local co = coroutine.create(function()
local save_path = sys.get_save_file("my_game", file_name)
local result = wavedash.delete_remote_file_async(save_path)
if result.success then
print("Deleted:", result.data)
end
end)
assert(coroutine.resume(co))
const result = await Wavedash.deleteRemoteFile("saves/slot1.json");
if (result.success) {
console.log("Deleted:", result.data);
}
Directories
func download_save_folder():
var path = OS.get_user_data_dir() + "/saves/"
WavedashSDK.download_remote_directory(path)
func list_remote_saves():
var path = OS.get_user_data_dir() + "/saves/"
WavedashSDK.list_remote_directory(path)
var path = $"{Application.persistentDataPath}/saves/";
await Wavedash.SDK.DownloadRemoteDirectory(path);
var files = await Wavedash.SDK.ListRemoteDirectory(path);
local co = coroutine.create(function()
local path = sys.get_save_file("my_game", "saves")
wavedash.download_remote_directory_async(path)
local files = wavedash.list_remote_directory_async(path)
end)
assert(coroutine.resume(co))
await Wavedash.downloadRemoteDirectory("saves/");
const list = await Wavedash.listRemoteDirectory("saves/");
File metadata
interface RemoteFileMetadata {
exists: boolean;
key: string;
name: string;
lastModified: number;
size: number;
etag: string;
}
Path conventions
Use forward slashes in remote keys, even on Windows builds.
saves/slot1.json
settings.json
replays/run-001.dat
Remote keys are relative to the player root. Keep names short and stable so you can migrate formats later.
Full save system example
const SAVE_VERSION = 2
var save_root: String
func _ready():
save_root = OS.get_user_data_dir() + "/saves/"
func cloud_save(slot: int, game_state: Dictionary) -> void:
var payload = {"version": SAVE_VERSION, "timestamp": Time.get_unix_time_from_system(), "state": game_state}
var path = save_root + "slot" + str(slot) + ".json"
DirAccess.make_dir_recursive_absolute(save_root)
var file = FileAccess.open(path, FileAccess.WRITE)
file.store_string(JSON.stringify(payload))
file.close()
WavedashSDK.upload_remote_file(path)
func cloud_load(slot: int) -> void:
var path = save_root + "slot" + str(slot) + ".json"
WavedashSDK.download_remote_file(path)
public async Task<bool> Save(int slot, Dictionary<string, object> gameState)
{
var payload = new Dictionary<string, object>
{
{ "version", 2 },
{ "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "state", gameState }
};
var path = $"{Application.persistentDataPath}/saves/slot{slot}.json";
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllText(path, JsonConvert.SerializeObject(payload));
var result = await Wavedash.SDK.UploadRemoteFile(path);
return !string.IsNullOrEmpty(result);
}
local SAVE_VERSION = 2
local function cloud_save(slot, game_state)
local payload = {
version = SAVE_VERSION,
timestamp = os.time(),
state = game_state,
}
local path = sys.get_save_file("my_game", ("saves/slot%d.json"):format(slot))
local wrote = wavedash.write_local_file_async(path, json.encode(payload))
if not wrote.success then
return false
end
local uploaded = wavedash.upload_remote_file_async(path).success
return uploaded
end
async function cloudSave(slot, gameState) {
const payload = { version: 2, timestamp: Date.now(), state: gameState };
const path = `saves/slot${slot}.json`;
await Wavedash.writeLocalFile(path, new TextEncoder().encode(JSON.stringify(payload)));
return (await Wavedash.uploadRemoteFile(path)).success;
}