Skip to content

Commit

Permalink
feat: save game manager implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ManuelRauber committed Mar 11, 2024
1 parent d934493 commit 631757a
Show file tree
Hide file tree
Showing 28 changed files with 537 additions and 17 deletions.
8 changes: 8 additions & 0 deletions FairyTaleDefender/Assets/_Game/Scripts/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static class MenuNames
public const string BuildSystem = MenuName + "/Build System";
public const string Infrastructure = MenuName + "/Infrastructure";
public const string RuntimeSets = MenuName + "/Runtime Sets";
public const string SaveGame = MenuName + "/Save Game";
public const string Objectives = MenuName + "/Objectives";
}

Expand All @@ -53,6 +54,13 @@ public static class Gameplay
public const int MaximumTargets = 50;
}

public static class SaveGames
{
public static readonly string DirectoryName = "Save Games";
public static readonly string MetaFileName = "meta.json";
public static readonly string SaveGameFileName = "save.json";
}

public static class Settings
{
public static class Panning
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

namespace BoundfoxStudios.FairyTaleDefender.Extensions
{
public static class StringExtensions
{
// From: https://stackoverflow.com/a/19937132/959687
// white space, em-dash, en-dash, underscore
static readonly Regex WordDelimiters = new(@"[\s—–_]", RegexOptions.Compiled);

// characters that are not valid
static readonly Regex InvalidChars = new(@"[^a-z0-9\-]", RegexOptions.Compiled);

// multiple hyphens
static readonly Regex MultipleHyphens = new(@"-{2,}", RegexOptions.Compiled);

private static string ToSlug(string value)
{
// trim whitespace from start and end
value = value.Trim();

// convert to lower case
value = value.ToLowerInvariant();

// remove diacritics (accents)
value = RemoveDiacritics(value);

// ensure all word delimiters are hyphens
value = WordDelimiters.Replace(value, "-");

// strip out invalid characters
value = InvalidChars.Replace(value, "");

// replace multiple hyphens (-) with a single hyphen
value = MultipleHyphens.Replace(value, "-");

// trim hyphens (-) from ends
return value.Trim('-');
}

/// See: http://www.siao2.com/2007/05/14/2629747.aspx
private static string RemoveDiacritics(string stIn)
{
var stFormD = stIn.Normalize(NormalizationForm.FormD);
var sb = new StringBuilder();

foreach (var t in stFormD)
{
var uc = CharUnicodeInfo.GetUnicodeCategory(t);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(t);
}
}

return (sb.ToString().Normalize(NormalizationForm.FormC));
}

public static string Slugify(this string input) => ToSlug(input);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.IO;
using System.Linq;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace BoundfoxStudios.FairyTaleDefender.Infrastructure.FileManagement
{
public class DirectoryManager
{
private readonly string _rootPath = Application.persistentDataPath;

/// <summary>
/// Checks if the specified directory exists.
/// </summary>
/// <param name="directory">The directory to check.</param>
public UniTask<bool> ExistsAsync(string directory)
{
var result = Directory.Exists(CreateDirectoryPath(directory));

return UniTask.FromResult(result);
}


/// <summary>
/// Lists all files in the given <paramref name="directory"/>.
/// </summary>
/// <param name="directory">The directory to list files.</param>
/// <returns></returns>
public UniTask<string[]> ListFilesAsync(string directory)
{
var result = Directory.EnumerateFiles(CreateDirectoryPath(directory));

return UniTask.FromResult(result.ToArray());
}

/// <summary>
/// Lists all directories within the given <paramref name="directory"/>.
/// </summary>
/// <param name="directory">The directory to list directories.</param>
/// <returns></returns>
public UniTask<string[]> ListDirectoriesAsync(string directory)
{
var result = Directory.EnumerateDirectories(CreateDirectoryPath(directory));

return UniTask.FromResult(result.ToArray());
}

/// <summary>
/// Deletes a directory recursively.
/// </summary>
/// <param name="directory">The directory to delete.</param>
/// <returns></returns>
public UniTask DeleteAsync(string directory)
{
Directory.Delete(CreateDirectoryPath(directory), true);

return UniTask.CompletedTask;
}

private string CreateDirectoryPath(string directory) => Path.Combine(_rootPath, directory);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace BoundfoxStudios.FairyTaleDefender.Infrastructure.FileManagement
{
public class JsonFileManager : IFileManager
public class JsonFileManager
{
private readonly string _rootPath = Application.persistentDataPath;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using BoundfoxStudios.FairyTaleDefender.Infrastructure.ScriptableObjects;
using UnityEngine;
using UnityEngine.AddressableAssets;

Expand All @@ -6,7 +7,7 @@ namespace BoundfoxStudios.FairyTaleDefender.Infrastructure.SceneManagement.Scrip
/// <summary>
/// Abstract scene definition.
/// </summary>
public abstract class SceneSO : ScriptableObject
public abstract class SceneSO : IdentifiableSO
{
[field: SerializeField]
public AssetReference SceneReference { get; private set; } = default!;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace BoundfoxStudios.FairyTaleDefender.Systems.SaveGameSystem
{
public class SaveGame
{
public SaveGameMeta Meta { get; }
public SaveGameData Data { get; }

public SaveGame(SaveGameMeta meta, SaveGameData data)
{
Meta = meta;
Data = data;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using BoundfoxStudios.FairyTaleDefender.Infrastructure.ScriptableObjects;

namespace BoundfoxStudios.FairyTaleDefender.Systems.SaveGameSystem
{
/// <summary>
/// This is the actual save game that will be written to a file.
/// </summary>
[Serializable]
public class SaveGameData
{
/// <summary>
/// Identifies the last played level.
/// </summary>
public IdentifiableSO? LastLevel;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace BoundfoxStudios.FairyTaleDefender.Systems.SaveGameSystem
{
/// <summary>
/// Meta Information about a save game.
/// </summary>
[Serializable]
public class SaveGameMeta
{
public string Name = string.Empty;
public DateTime LastPlayedDate;

public string Directory { get; set; }
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.IO;
using BoundfoxStudios.FairyTaleDefender.Common;
using BoundfoxStudios.FairyTaleDefender.Extensions;
using BoundfoxStudios.FairyTaleDefender.Infrastructure.FileManagement;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace BoundfoxStudios.FairyTaleDefender.Systems.SaveGameSystem.ScriptableObjects
{
[CreateAssetMenu(menuName = Constants.MenuNames.SaveGame + "/Save Game Manager")]
public class SaveGameManagerSO : ScriptableObject
{
private DirectoryManager _directoryManager = default!;
private JsonFileManager _fileManager = default!;

private void OnEnable()
{
_directoryManager = new();
_fileManager = new();
}

public async UniTask<List<SaveGameMeta>> ListAvailableSaveGamesAsync()
{
var directories = await _directoryManager.ListDirectoriesAsync(Constants.SaveGames.DirectoryName);

var candidates = new List<string>();

foreach (var directory in directories)
{
if (await IsValidSaveGameAsync(directory))
{
candidates.Add(directory);
}
}

var metas = new List<SaveGameMeta>();

foreach (var candidate in candidates)
{
var meta = await _fileManager.ReadAsync<SaveGameMeta>(CreatePath(candidate, Constants.SaveGames.MetaFileName));
meta.Directory = candidate;

metas.Add(meta);
}

return metas;
}

public async UniTask<SaveGame?> LoadSaveGameAsync(SaveGameMeta meta)
{
if (!await IsValidSaveGameAsync(meta.Directory))
{
return null;
}

var saveGameData =
await _fileManager.ReadAsync<SaveGameData>(CreatePath(meta.Directory, Constants.SaveGames.SaveGameFileName));

var saveGame = new SaveGame(meta, saveGameData);

return saveGame;
}

public async UniTask<SaveGameMeta?> CreateSaveGameAsync(string saveName, SaveGameData data)
{
var slugifiedName = saveName.Slugify();

var meta = new SaveGameMeta()
{
Name = saveName,
LastPlayedDate = DateTime.Now,
Directory = CreatePath(slugifiedName),
};

try
{
await _fileManager.WriteAsync(Path.Combine(meta.Directory, Constants.SaveGames.MetaFileName), meta);
await _fileManager.WriteAsync(Path.Combine(meta.Directory, Constants.SaveGames.SaveGameFileName), data);
}

catch
{
await _directoryManager.DeleteAsync(meta.Directory);
return null;
}

return meta;
}

public async UniTask<bool> SaveGameExistsAsync(string saveName)
{
var slugifiedName = saveName.Slugify();

return await IsValidSaveGameAsync(slugifiedName);
}

private async UniTask<bool> IsValidSaveGameAsync(string directory)
{
var metaFileExists = await _fileManager.ExistsAsync(CreatePath(directory, Constants.SaveGames.MetaFileName));
var saveGameFileExists =
await _fileManager.ExistsAsync(CreatePath(directory, Constants.SaveGames.SaveGameFileName));

return metaFileExists && saveGameFileExists;
}

private string CreatePath(string directory, string fileName) => Path.Combine(CreatePath(directory), fileName);

private string CreatePath(string directory) => Path.Combine(Constants.SaveGames.DirectoryName, directory);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 631757a

Please sign in to comment.