Skip to content

Commit

Permalink
Yarn Projects now reimport themselves when localisation assets change.
Browse files Browse the repository at this point in the history
This behaviour can be controlled via the new project settings if it is irksome.
Needs work on the settings UI, but is functionally working.
Fixes #259
  • Loading branch information
McJones committed Jan 12, 2024
1 parent 6bc7b66 commit 1ebd52f
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Editor/Importers/YarnProjectAssetReimport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Yarn.Unity.Editor
{
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

/// <summary>
/// An asset post processor that forwards any asset database changes to all YarnProjectImporter for them to verify if they need to update their locale assets.
/// </summary>
class YarnProjectAssetReimport : AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
{
if (!YarnSpinnerProjectSettings.GetOrCreateSettings().autoRefreshLocalisedAssets)
{
return;
}
var modifiedAssets = new List<string>();
modifiedAssets.AddRange(importedAssets);
modifiedAssets.AddRange(deletedAssets);
modifiedAssets.AddRange(movedAssets);
modifiedAssets.AddRange(movedFromAssetPaths);

var yarnProjects = AssetDatabase.FindAssets($"t:{nameof(YarnProject)}");
foreach (var guid in yarnProjects)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var importer = AssetImporter.GetAtPath(path) as YarnProjectImporter;
if (importer != null)
{
importer.CheckUpdatedAssetsRequireReimport(modifiedAssets);
}
}
}
}
}
11 changes: 11 additions & 0 deletions Editor/Importers/YarnProjectAssetReimport.cs.meta

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

47 changes: 47 additions & 0 deletions Editor/Importers/YarnProjectImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,53 @@ public override void OnImportAsset(AssetImportContext ctx)
#endif
}

/// <summary>
/// Checks if the modifications on the Asset Database will necessitate a reimport of the project to stay in sync with the localisation assets.
/// </summary>
/// <remarks>
/// Because assets can be added and removed after associating a folder of assets with a locale modifications won't be detected until runtime when they cause an error.
/// This is bad for many reasons, so this method will check any modified assets and see if they correspond to this Yarn Project.
/// If they do it will reimport the project to reassociate them.
/// </remarks>
/// <param name="modifiedAssetPaths">The list of asset paths that have been modified, that is to say assets that have been added, removed, or moved.</param>
public void CheckUpdatedAssetsRequireReimport(List<string> modifiedAssetPaths)
{
bool needsReimport = false;

var comparison = System.StringComparison.CurrentCulture;
if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor)
{
comparison = System.StringComparison.OrdinalIgnoreCase;
}

var localeAssetFolderPaths = ImportData.localizations.Where(l => l.assetsFolder != null).Select(l => AssetDatabase.GetAssetPath(l.assetsFolder));
foreach (var path in localeAssetFolderPaths)
{
// we need to ensure we have the trailing seperator otherwise it is to be considered a file
// and files can never be the parent of another file
var assetPath = path;
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
assetPath += Path.DirectorySeparatorChar.ToString();
}

foreach (var modified in modifiedAssetPaths)
{
if (modified.StartsWith(assetPath, comparison))
{
needsReimport = true;
goto SHORTCUT;
}
}
}

SHORTCUT:
if (needsReimport)
{
AssetDatabase.ImportAsset(this.assetPath);
}
}

internal static string GetRelativePath(string path)
{
if (path.StartsWith(UnityProjectRootPath) == false)
Expand Down
108 changes: 108 additions & 0 deletions Editor/YarnSpinnerProjectSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
namespace Yarn.Unity.Editor
{
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;

/// <summary>
/// Basic data class of unity settings that impact Yarn Spinner.
/// </summary>
/// <remarks>
/// Currently this only supports disabling the automatic reimport of Yarn Projects when locale assets change, but other settings will eventually end up here.
/// </remarks>
class YarnSpinnerProjectSettings
{
public static string YarnSpinnerProjectSettingsPath => Path.Combine("ProjectSettings", "Packages", "dev.yarnspinner", "YarnSpinnerProjectSettings.json");

[SerializeField] internal bool autoRefreshLocalisedAssets = true;

internal static YarnSpinnerProjectSettings GetOrCreateSettings()
{
YarnSpinnerProjectSettings settings = new YarnSpinnerProjectSettings();
if (File.Exists(YarnSpinnerProjectSettingsPath))
{
try
{
var settingsData = File.ReadAllText(YarnSpinnerProjectSettingsPath);
EditorJsonUtility.FromJsonOverwrite(settingsData, settings);

return settings;
}
catch (System.Exception e)
{
Debug.LogWarning($"Failed to load Yarn Spinner project settings at {YarnSpinnerProjectSettingsPath}: {e.Message}");
}
}

settings.autoRefreshLocalisedAssets = true;
settings.WriteSettings();

return settings;
}

internal void WriteSettings()
{
var jsonValue = EditorJsonUtility.ToJson(this);

var folder = Path.GetDirectoryName(YarnSpinnerProjectSettingsPath);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}

try
{
File.WriteAllText(YarnSpinnerProjectSettingsPath, jsonValue);
}
catch (System.Exception e)
{
Debug.LogError($"Failed to save Yarn Spinner project settings to {YarnSpinnerProjectSettingsPath}: {e.Message}");
}
}
}

class YarnSpinnerProjectSettingsProvider : SettingsProvider
{
private YarnSpinnerProjectSettings baseSettings;

public YarnSpinnerProjectSettingsProvider(string path, SettingsScope scope = SettingsScope.Project) : base(path, scope) { }

public override void OnActivate(string searchContext, VisualElement rootElement)
{
// This function is called when the user clicks on the MyCustom element in the Settings window.
baseSettings = YarnSpinnerProjectSettings.GetOrCreateSettings();
}

public override void OnGUI(string searchContext)
{
// Use IMGUI to display UI:
EditorGUILayout.LabelField("Automatically update localised assets with Yarn Projects");

using (var changeCheck = new EditorGUI.ChangeCheckScope())
{
var result = EditorGUILayout.Toggle(baseSettings.autoRefreshLocalisedAssets);

if (changeCheck.changed)
{
baseSettings.autoRefreshLocalisedAssets = result;
baseSettings.WriteSettings();
}
}
}

// Register the SettingsProvider
[SettingsProvider]
public static SettingsProvider CreateYarnSpinnerProjectSettingsProvider()
{
var provider = new YarnSpinnerProjectSettingsProvider("Project/Yarn Spinner", SettingsScope.Project);

var keywords = new List<string>() { "yarn", "spinner", "localisation" };
provider.keywords = keywords;
return provider;
}
}
}
11 changes: 11 additions & 0 deletions Editor/YarnSpinnerProjectSettings.cs.meta

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

0 comments on commit 1ebd52f

Please sign in to comment.