Fix Sidekick tool startup and path handling

This commit is contained in:
2026-03-22 23:50:10 +09:00
parent 9d84154b54
commit c66c91e8e9
6 changed files with 345 additions and 20 deletions

View File

@@ -40,7 +40,7 @@ namespace Synty.SidekickCharacters
private const string _AUTOSAVE_MISSING_PARTS = "SK_Autosave_missing_parts"; private const string _AUTOSAVE_MISSING_PARTS = "SK_Autosave_missing_parts";
private const string _AUTOSAVE_STATE = "SK_Autosave_state"; private const string _AUTOSAVE_STATE = "SK_Autosave_state";
private const string _BASE_COLOR_SET_NAME = "Species"; private const string _BASE_COLOR_SET_NAME = "Species";
private const string _BASE_COLOR_SET_PATH = "Assets/Synty/SidekickCharacters/Resources/Species"; private const string _BASE_COLOR_SET_PATH = "Assets/External/Models/SidekickCharacters/Resources/Species";
private const string _BASE_MESH_NAME = "Meshes/SK_BaseModel"; private const string _BASE_MESH_NAME = "Meshes/SK_BaseModel";
private const string _BASE_MATERIAL_NAME = "Materials/M_BaseMaterial"; private const string _BASE_MATERIAL_NAME = "Materials/M_BaseMaterial";
private const string _BLEND_GENDER_NAME = "masculineFeminine"; private const string _BLEND_GENDER_NAME = "masculineFeminine";
@@ -121,7 +121,7 @@ namespace Synty.SidekickCharacters
private bool _showAllColourProperties = false; private bool _showAllColourProperties = false;
private float _bodyTypeBlendValue; private float _bodyTypeBlendValue;
private bool _loadingCharacter = false; private bool _loadingCharacter = false;
private bool _loadingContent = true; private bool _loadingContent = false;
private Image _loadingImage; private Image _loadingImage;
private ObjectField _materialField; private ObjectField _materialField;
private float _musclesBlendValue; private float _musclesBlendValue;
@@ -192,7 +192,7 @@ namespace Synty.SidekickCharacters
} }
// ensures we release the file lock on the database // ensures we release the file lock on the database
_dbManager.CloseConnection(); _dbManager?.CloseConnection();
} }
#if UNITY_EDITOR #if UNITY_EDITOR
@@ -211,7 +211,7 @@ namespace Synty.SidekickCharacters
/// <inheritdoc cref="Update" /> /// <inheritdoc cref="Update" />
private void Update() private void Update()
{ {
if (_loadingContent) if (_loadingContent && _loadingImage != null)
{ {
Vector3 rotation = _loadingImage.transform.rotation.eulerAngles; Vector3 rotation = _loadingImage.transform.rotation.eulerAngles;
rotation += new Vector3(0, 0, 0.5f * Time.deltaTime); rotation += new Vector3(0, 0, 0.5f * Time.deltaTime);
@@ -473,6 +473,7 @@ namespace Synty.SidekickCharacters
// if we still can't connect, something's gone wrong, don't keep building the GUI // if we still can't connect, something's gone wrong, don't keep building the GUI
if (_dbManager?.GetCurrentDbConnection() == null) if (_dbManager?.GetCurrentDbConnection() == null)
{ {
_loadingContent = false;
return; return;
} }
@@ -1814,7 +1815,12 @@ namespace Synty.SidekickCharacters
documentationButton.clickable.clicked += delegate documentationButton.clickable.clicked += delegate
{ {
Application.OpenURL("file:Assets/Synty/SidekickCharacters/Documentation/SidekickCharacters_UserGuide.pdf"); string documentationPath = Path.Combine(DatabaseManager.GetPackageRootAbsolutePath() ?? string.Empty, "Documentation", "SidekickCharacters_UserGuide.pdf");
documentationPath = Path.GetFullPath(documentationPath);
if (File.Exists(documentationPath))
{
Application.OpenURL("file:" + documentationPath);
}
}; };
Button storeButton = new Button Button storeButton = new Button
@@ -1919,7 +1925,8 @@ namespace Synty.SidekickCharacters
/// </summary> /// </summary>
private void SaveColorSet() private void SaveColorSet()
{ {
string path = Path.Combine(_BASE_COLOR_SET_PATH, _currentSpecies.Name); string baseColorSetPath = DatabaseManager.GetPackageAssetPath("Resources", "Species") ?? _BASE_COLOR_SET_PATH;
string path = Path.Combine(baseColorSetPath, _currentSpecies.Name);
path = Path.Combine(path, _currentColorSet.Name.Replace(" ", "_")); path = Path.Combine(path, _currentColorSet.Name.Replace(" ", "_"));
SaveTexturesToDisk(path); SaveTexturesToDisk(path);

View File

@@ -24,7 +24,7 @@ namespace Synty.SidekickCharacters
private const string _GIT_DB_URL = "https://github.com/SyntyStudios/SidekicksToolRelease/releases/latest/download/SidekicksDatabase.unitypackage"; private const string _GIT_DB_URL = "https://github.com/SyntyStudios/SidekicksToolRelease/releases/latest/download/SidekicksDatabase.unitypackage";
private const string _PACKAGE_CACHE = "Assets/DownloadCache/Sidekicks.unitypackage"; private const string _PACKAGE_CACHE = "Assets/DownloadCache/Sidekicks.unitypackage";
private const string _DB_PACKAGE_CACHE = "Assets/DownloadCache/SidekicksDatabase.unitypackage"; private const string _DB_PACKAGE_CACHE = "Assets/DownloadCache/SidekicksDatabase.unitypackage";
private const string _VERSION_FILE = "Assets/Synty/SidekickCharacters/Scripts/Editor/version.txt"; private const string _VERSION_FILE_NAME = "version.txt";
private const string _VERSION_TAG = "\"tag_name\":"; private const string _VERSION_TAG = "\"tag_name\":";
private const string _VERSION_KEY = "sk_current_tool_version"; private const string _VERSION_KEY = "sk_current_tool_version";
private const string _SIDEKICK_TOOL_MENU_ITEM = "Synty/Sidekick Character Tool"; private const string _SIDEKICK_TOOL_MENU_ITEM = "Synty/Sidekick Character Tool";
@@ -168,19 +168,61 @@ namespace Synty.SidekickCharacters
private void SaveCurrentInstalledVersion(string version) private void SaveCurrentInstalledVersion(string version)
{ {
File.WriteAllText(_VERSION_FILE, version); string versionFilePath = GetVersionFilePath();
if (string.IsNullOrEmpty(versionFilePath))
{
Debug.LogWarning("Unable to resolve Sidekick version file path.");
return;
}
File.WriteAllText(versionFilePath, version);
} }
private string LoadCurrentInstalledVersion() private string LoadCurrentInstalledVersion()
{ {
if (File.Exists(_VERSION_FILE)) string versionFilePath = GetVersionFilePath();
if (string.IsNullOrEmpty(versionFilePath))
{ {
return File.ReadAllText(_VERSION_FILE); return null;
}
if (File.Exists(versionFilePath))
{
return File.ReadAllText(versionFilePath);
} }
return null; return null;
} }
private string GetVersionFilePath()
{
MonoScript currentScript = MonoScript.FromScriptableObject(this);
if (currentScript == null)
{
return null;
}
string scriptPath = AssetDatabase.GetAssetPath(currentScript);
if (string.IsNullOrEmpty(scriptPath))
{
return null;
}
string utilityDirectory = Path.GetDirectoryName(scriptPath);
if (string.IsNullOrEmpty(utilityDirectory))
{
return null;
}
string editorDirectory = Path.GetDirectoryName(utilityDirectory);
if (string.IsNullOrEmpty(editorDirectory))
{
return null;
}
return Path.GetFullPath(Path.Combine(editorDirectory, _VERSION_FILE_NAME));
}
private void DownloadLatestDBVersion() private void DownloadLatestDBVersion()
{ {
WebClient client = new WebClient(); WebClient client = new WebClient();

View File

@@ -230,6 +230,42 @@ namespace Synty.SidekickCharacters.API
await runtime.PopulatePresetLibrary(); await runtime.PopulatePresetLibrary();
} }
private static List<string> GetSidekickPartFiles()
{
string packageRootPath = DatabaseManager.GetPackageRootAbsolutePath();
if (!string.IsNullOrEmpty(packageRootPath))
{
string meshesRootPath = Path.Combine(packageRootPath, "Resources", "Meshes");
if (Directory.Exists(meshesRootPath))
{
return Directory
.GetFiles(meshesRootPath, "SK_*_*_*_*_*.fbx", SearchOption.AllDirectories)
.Select(ToAssetPath)
.ToList();
}
}
return Directory.GetFiles("Assets", "SK_*_*_*_*_*.fbx", SearchOption.AllDirectories).ToList();
}
private static string ToAssetPath(string fullPath)
{
if (string.IsNullOrWhiteSpace(fullPath))
{
return fullPath;
}
string normalizedFullPath = fullPath.Replace('\\', '/');
string normalizedAssetsPath = Application.dataPath.Replace('\\', '/');
if (!normalizedFullPath.StartsWith(normalizedAssetsPath, StringComparison.OrdinalIgnoreCase))
{
return normalizedFullPath;
}
return "Assets" + normalizedFullPath.Substring(normalizedAssetsPath.Length);
}
/// <summary> /// <summary>
/// Takes all the parts selected in the window, and combines them into a single model in the scene. /// Takes all the parts selected in the window, and combines them into a single model in the scene.
/// </summary> /// </summary>
@@ -512,7 +548,7 @@ namespace Synty.SidekickCharacters.API
_partOutfitToggleMap = new Dictionary<string, bool>(); _partOutfitToggleMap = new Dictionary<string, bool>();
_partCount = 0; _partCount = 0;
List<string> files = Directory.GetFiles("Assets", "SK_*_*_*_*_*.fbx", SearchOption.AllDirectories).ToList(); List<string> files = GetSidekickPartFiles();
foreach (CharacterPartType partType in Enum.GetValues(typeof(CharacterPartType))) foreach (CharacterPartType partType in Enum.GetValues(typeof(CharacterPartType)))
{ {
@@ -610,7 +646,7 @@ namespace Synty.SidekickCharacters.API
_speciesDictionary = new Dictionary<string, SidekickSpecies>(); _speciesDictionary = new Dictionary<string, SidekickSpecies>();
_partCount = 0; _partCount = 0;
List<string> files = Directory.GetFiles("Assets", "SK_*_*_*_*_*.fbx", SearchOption.AllDirectories).ToList(); List<string> files = GetSidekickPartFiles();
Dictionary<string, string> filesOnDisk = new Dictionary<string, string>(); Dictionary<string, string> filesOnDisk = new Dictionary<string, string>();
foreach (string file in files) foreach (string file in files)
{ {

View File

@@ -154,6 +154,18 @@ namespace Synty.SidekickCharacters.Database.DTO
/// <returns>A color set with all DTO class properties set</returns> /// <returns>A color set with all DTO class properties set</returns>
private static void Decorate(DatabaseManager dbManager, SidekickColorSet set) private static void Decorate(DatabaseManager dbManager, SidekickColorSet set)
{ {
if (set == null)
{
return;
}
set.SourceColorPath = DatabaseManager.NormalizeLegacyAssetPath(set.SourceColorPath);
set.SourceMetallicPath = DatabaseManager.NormalizeLegacyAssetPath(set.SourceMetallicPath);
set.SourceSmoothnessPath = DatabaseManager.NormalizeLegacyAssetPath(set.SourceSmoothnessPath);
set.SourceReflectionPath = DatabaseManager.NormalizeLegacyAssetPath(set.SourceReflectionPath);
set.SourceEmissionPath = DatabaseManager.NormalizeLegacyAssetPath(set.SourceEmissionPath);
set.SourceOpacityPath = DatabaseManager.NormalizeLegacyAssetPath(set.SourceOpacityPath);
if (set.Species == null && set.PtrSpecies >= 0) if (set.Species == null && set.PtrSpecies >= 0)
{ {
set.Species = SidekickSpecies.GetByID(dbManager, set.PtrSpecies); set.Species = SidekickSpecies.GetByID(dbManager, set.PtrSpecies);

View File

@@ -222,6 +222,8 @@ namespace Synty.SidekickCharacters.Database.DTO
{ {
if (part != null) if (part != null)
{ {
part.Location = DatabaseManager.NormalizeLegacyAssetPath(part.Location);
if (part.Species == null && part.PtrSpecies >= 0) if (part.Species == null && part.PtrSpecies >= 0)
{ {
part.Species = SidekickSpecies.GetByID(dbManager, part.PtrSpecies); part.Species = SidekickSpecies.GetByID(dbManager, part.PtrSpecies);

View File

@@ -12,7 +12,9 @@
using SQLite; using SQLite;
using Synty.SidekickCharacters.Database.DTO; using Synty.SidekickCharacters.Database.DTO;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using UnityEngine;
namespace Synty.SidekickCharacters.Database namespace Synty.SidekickCharacters.Database
{ {
@@ -21,11 +23,18 @@ namespace Synty.SidekickCharacters.Database
/// </summary> /// </summary>
public class DatabaseManager public class DatabaseManager
{ {
private static readonly string _DATABASE_PATH = "Assets/Synty/SidekickCharacters/Database/Side_Kick_Data.db"; private const string _DATABASE_FILE_NAME = "Side_Kick_Data.db";
private const string _LEGACY_PACKAGE_ROOT = "Assets/Synty/SidekickCharacters";
private const string _LEGACY_TOOLS_PACKAGE_ROOT = "Assets/Synty/Tools/SidekickCharacters";
private const string _WORKING_DATABASE_DIRECTORY = "SidekickCharacters";
private readonly string _CURRENT_VERSION = "1.0.2"; private readonly string _CURRENT_VERSION = "1.0.2";
private static SQLiteConnection _connection; private static SQLiteConnection _connection;
private static int _connectionHash; private static int _connectionHash;
private static string _databasePath;
private static string _seedDatabasePath;
private static string _packageRootAbsolutePath;
private static string _packageRootAssetPath;
/// <summary> /// <summary>
/// Gets the DB connection with the given connection details. /// Gets the DB connection with the given connection details.
@@ -39,7 +48,13 @@ namespace Synty.SidekickCharacters.Database
{ {
if (_connection == null) if (_connection == null)
{ {
_connection = new SQLiteConnection(_DATABASE_PATH, true); string databasePath = GetDatabasePath();
if (string.IsNullOrEmpty(databasePath))
{
throw new FileNotFoundException("Unable to locate Sidekick database file in this Unity project.");
}
_connection = new SQLiteConnection(databasePath, true);
} }
else else
{ {
@@ -100,12 +115,12 @@ namespace Synty.SidekickCharacters.Database
{ {
Species = new SidekickSpecies { ID = -1, Name = "None" }, Species = new SidekickSpecies { ID = -1, Name = "None" },
Name = "Default", Name = "Default",
SourceColorPath = "Assets/Synty/Tools/SidekickCharacters/Resources/Textures/T_ColorMap.png", SourceColorPath = GetPackageAssetPath("Resources", "Textures", "T_ColorMap.png"),
SourceMetallicPath = "Assets/Synty/Tools/SidekickCharacters/Resources/Textures/T_MetallicMap.png", SourceMetallicPath = GetPackageAssetPath("Resources", "Textures", "T_MetallicMap.png"),
SourceSmoothnessPath = "Assets/Synty/Tools/SidekickCharacters/Resources/Textures/T_SmoothnessMap.png", SourceSmoothnessPath = GetPackageAssetPath("Resources", "Textures", "T_SmoothnessMap.png"),
SourceReflectionPath = "Assets/Synty/Tools/SidekickCharacters/Resources/Textures/T_ReflectionMap.png", SourceReflectionPath = GetPackageAssetPath("Resources", "Textures", "T_ReflectionMap.png"),
SourceEmissionPath = "Assets/Synty/Tools/SidekickCharacters/Resources/Textures/T_EmissionMap.png", SourceEmissionPath = GetPackageAssetPath("Resources", "Textures", "T_EmissionMap.png"),
SourceOpacityPath = "Assets/Synty/Tools/SidekickCharacters/Resources/Textures/T_OpacityMap.png", SourceOpacityPath = GetPackageAssetPath("Resources", "Textures", "T_OpacityMap.png"),
}; };
newSet.Save(this); newSet.Save(this);
@@ -206,5 +221,216 @@ namespace Synty.SidekickCharacters.Database
{ {
return new Version(GetCurrentDbConnection()?.Table<SidekickDBVersion>().FirstOrDefault().SemanticVersion ?? "0.0.1ea"); return new Version(GetCurrentDbConnection()?.Table<SidekickDBVersion>().FirstOrDefault().SemanticVersion ?? "0.0.1ea");
} }
/// <summary>
/// Gets the absolute filesystem path to the Sidekick database file.
/// </summary>
/// <returns>The absolute path to the DB file, if found.</returns>
public static string GetDatabasePath()
{
if (!string.IsNullOrEmpty(_databasePath) && File.Exists(_databasePath))
{
return _databasePath;
}
string seedDatabasePath = GetSeedDatabasePath();
if (string.IsNullOrEmpty(seedDatabasePath))
{
return null;
}
string projectRoot = Directory.GetParent(Application.dataPath)?.FullName;
if (string.IsNullOrEmpty(projectRoot))
{
return null;
}
string workingDirectory = Path.Combine(projectRoot, "Library", _WORKING_DATABASE_DIRECTORY);
Directory.CreateDirectory(workingDirectory);
_databasePath = Path.Combine(workingDirectory, _DATABASE_FILE_NAME);
if (!File.Exists(_databasePath))
{
File.Copy(seedDatabasePath, _databasePath, false);
}
_databasePath = Path.GetFullPath(_databasePath);
CleanupLegacyDatabaseArtifacts(seedDatabasePath);
return _databasePath;
}
/// <summary>
/// Gets the absolute filesystem path to the Sidekick package root.
/// </summary>
/// <returns>The absolute path to the package root, if found.</returns>
public static string GetPackageRootAbsolutePath()
{
if (!string.IsNullOrEmpty(_packageRootAbsolutePath) && Directory.Exists(_packageRootAbsolutePath))
{
return _packageRootAbsolutePath;
}
string seedDatabasePath = GetSeedDatabasePath();
if (string.IsNullOrEmpty(seedDatabasePath))
{
return null;
}
string databaseDirectory = Path.GetDirectoryName(seedDatabasePath);
if (string.IsNullOrEmpty(databaseDirectory))
{
return null;
}
_packageRootAbsolutePath = Path.GetDirectoryName(databaseDirectory);
if (!string.IsNullOrEmpty(_packageRootAbsolutePath))
{
_packageRootAbsolutePath = Path.GetFullPath(_packageRootAbsolutePath);
}
return _packageRootAbsolutePath;
}
/// <summary>
/// Gets the project-relative asset path to the Sidekick package root.
/// </summary>
/// <returns>The asset path to the package root, if found.</returns>
public static string GetPackageRootAssetPath()
{
if (!string.IsNullOrEmpty(_packageRootAssetPath))
{
return _packageRootAssetPath;
}
string packageRootAbsolutePath = GetPackageRootAbsolutePath();
if (string.IsNullOrEmpty(packageRootAbsolutePath))
{
return null;
}
_packageRootAssetPath = ToAssetPath(packageRootAbsolutePath);
return _packageRootAssetPath;
}
/// <summary>
/// Builds a project-relative asset path under the Sidekick package root.
/// </summary>
/// <param name="relativeSegments">Path segments under the package root.</param>
/// <returns>The combined asset path, if the package root could be found.</returns>
public static string GetPackageAssetPath(params string[] relativeSegments)
{
string packageRootAssetPath = GetPackageRootAssetPath();
if (string.IsNullOrEmpty(packageRootAssetPath))
{
return null;
}
string combinedPath = packageRootAssetPath;
foreach (string segment in relativeSegments)
{
combinedPath = Path.Combine(combinedPath, segment);
}
return NormalizePathSeparators(combinedPath);
}
/// <summary>
/// Normalizes legacy Sidekick asset paths so moved packages still load correctly.
/// </summary>
/// <param name="assetPath">The asset path to normalize.</param>
/// <returns>The remapped path if it pointed at an old root; otherwise the original path.</returns>
public static string NormalizeLegacyAssetPath(string assetPath)
{
if (string.IsNullOrWhiteSpace(assetPath))
{
return assetPath;
}
string normalizedPath = NormalizePathSeparators(assetPath);
string packageRootAssetPath = GetPackageRootAssetPath();
if (string.IsNullOrEmpty(packageRootAssetPath))
{
return normalizedPath;
}
if (normalizedPath.StartsWith(_LEGACY_TOOLS_PACKAGE_ROOT, StringComparison.OrdinalIgnoreCase))
{
return packageRootAssetPath + normalizedPath.Substring(_LEGACY_TOOLS_PACKAGE_ROOT.Length);
}
if (normalizedPath.StartsWith(_LEGACY_PACKAGE_ROOT, StringComparison.OrdinalIgnoreCase))
{
return packageRootAssetPath + normalizedPath.Substring(_LEGACY_PACKAGE_ROOT.Length);
}
return normalizedPath;
}
private static string ToAssetPath(string fullPath)
{
string normalizedFullPath = NormalizePathSeparators(Path.GetFullPath(fullPath));
string normalizedAssetsPath = NormalizePathSeparators(Path.GetFullPath(Application.dataPath));
if (!normalizedFullPath.StartsWith(normalizedAssetsPath, StringComparison.OrdinalIgnoreCase))
{
return normalizedFullPath;
}
return "Assets" + normalizedFullPath.Substring(normalizedAssetsPath.Length);
}
private static string NormalizePathSeparators(string path)
{
return path?.Replace('\\', '/');
}
private static string GetSeedDatabasePath()
{
if (!string.IsNullOrEmpty(_seedDatabasePath) && File.Exists(_seedDatabasePath))
{
return _seedDatabasePath;
}
string[] databaseFiles = Directory.GetFiles(Application.dataPath, _DATABASE_FILE_NAME, SearchOption.AllDirectories);
_seedDatabasePath = databaseFiles
.OrderBy(path => path.Contains($"{Path.DirectorySeparatorChar}SidekickCharacters{Path.DirectorySeparatorChar}Database{Path.DirectorySeparatorChar}") ? 0 : 1)
.FirstOrDefault();
if (!string.IsNullOrEmpty(_seedDatabasePath))
{
_seedDatabasePath = Path.GetFullPath(_seedDatabasePath);
}
return _seedDatabasePath;
}
private static void CleanupLegacyDatabaseArtifacts(string seedDatabasePath)
{
if (string.IsNullOrEmpty(seedDatabasePath))
{
return;
}
DeleteIfExists(seedDatabasePath + "-journal");
DeleteIfExists(seedDatabasePath + "-journal.meta");
DeleteIfExists(seedDatabasePath + "-wal");
DeleteIfExists(seedDatabasePath + "-wal.meta");
DeleteIfExists(seedDatabasePath + "-shm");
DeleteIfExists(seedDatabasePath + "-shm.meta");
}
private static void DeleteIfExists(string path)
{
try
{
if (File.Exists(path))
{
File.Delete(path);
}
}
catch
{
}
}
} }
} }