Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction
This commit is contained in:
12
.junie/guidelines.md
Normal file
12
.junie/guidelines.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Project Guidelines
|
||||||
|
|
||||||
|
This is a placeholder of the project guidelines for Junie.
|
||||||
|
Replace this text with any project-level instructions for Junie, e.g.:
|
||||||
|
|
||||||
|
* What is the project structure
|
||||||
|
* Whether Junie should run tests to check the correctness of the proposed solution
|
||||||
|
* How does Junie run tests (once it requires any non-standard approach)
|
||||||
|
* Whether Junie should build the project before submitting the result
|
||||||
|
* Any code-style related instructions
|
||||||
|
|
||||||
|
As an option you can ask Junie to create these guidelines for you.
|
||||||
@@ -13,6 +13,7 @@ MonoBehaviour:
|
|||||||
m_Name: AddressableAssetGroupSortSettings
|
m_Name: AddressableAssetGroupSortSettings
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
sortOrder:
|
sortOrder:
|
||||||
|
- 0d5d36d6da388314b92b9c6967d23f39
|
||||||
- 75e1f68b7bf77f34f8ad4aeab74d4244
|
- 75e1f68b7bf77f34f8ad4aeab74d4244
|
||||||
- eb8a37153d819c44194f7ce97570a3d3
|
- eb8a37153d819c44194f7ce97570a3d3
|
||||||
- 2b3d7cefec0915e42be04aebf0400a56
|
- 2b3d7cefec0915e42be04aebf0400a56
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ MonoBehaviour:
|
|||||||
m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
|
m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
|
||||||
m_currentHash:
|
m_currentHash:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Hash: 58bff643ea54adc47e3181c09b68468c
|
Hash: 00000000000000000000000000000000
|
||||||
m_OptimizeCatalogSize: 0
|
m_OptimizeCatalogSize: 0
|
||||||
m_BuildRemoteCatalog: 0
|
m_BuildRemoteCatalog: 0
|
||||||
m_CatalogRequestsTimeout: 0
|
m_CatalogRequestsTimeout: 0
|
||||||
@@ -60,6 +60,7 @@ MonoBehaviour:
|
|||||||
m_BuildAddressablesWithPlayerBuild: 0
|
m_BuildAddressablesWithPlayerBuild: 0
|
||||||
m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]'
|
m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]'
|
||||||
m_GroupAssets:
|
m_GroupAssets:
|
||||||
|
- {fileID: 11400000, guid: 124f05e204489af49943d7fc039640a4, type: 2}
|
||||||
- {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2}
|
- {fileID: 11400000, guid: efe7e1728e73e9546ac5dfee2eff524f, type: 2}
|
||||||
- {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2}
|
- {fileID: 11400000, guid: 6e4927e7e19eef34b93dc2baa9e9e8e2, type: 2}
|
||||||
- {fileID: 11400000, guid: 4186fdd83f912a14b97fbf4644266b0d, type: 2}
|
- {fileID: 11400000, guid: 4186fdd83f912a14b97fbf4644266b0d, type: 2}
|
||||||
@@ -104,6 +105,7 @@ MonoBehaviour:
|
|||||||
m_LabelTable:
|
m_LabelTable:
|
||||||
m_LabelNames:
|
m_LabelNames:
|
||||||
- default
|
- default
|
||||||
|
- BlokkemonCard
|
||||||
m_SchemaTemplates: []
|
m_SchemaTemplates: []
|
||||||
m_GroupTemplateObjects:
|
m_GroupTemplateObjects:
|
||||||
- {fileID: 11400000, guid: ea0a5135f5495eb4693a23d94617fe92, type: 2}
|
- {fileID: 11400000, guid: ea0a5135f5495eb4693a23d94617fe92, type: 2}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
|
||||||
|
m_Name: BlokkemonCards
|
||||||
|
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup
|
||||||
|
m_GroupName: BlokkemonCards
|
||||||
|
m_GUID: 0d5d36d6da388314b92b9c6967d23f39
|
||||||
|
m_SerializeEntries:
|
||||||
|
- m_GUID: 28dbfbd7a6b2cd84b8274bd1126b220b
|
||||||
|
m_Address: Assets/Data/Cards/Card_Test example card 2 (Copy).asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 4b9c8b6ca63744a44acfe49ebe337619
|
||||||
|
m_Address: Assets/Data/Cards/Card_New Card 2.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 5d8121cdf52bfe9488b40ed22d649209
|
||||||
|
m_Address: Assets/Data/Cards/Card_New Card 1.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 6afed7a67f64404418f905e7808bf5cb
|
||||||
|
m_Address: Assets/Data/Cards/Card_Test example card 3 (Copy).asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 8f02c3699de87014bac8c03b96772a4b
|
||||||
|
m_Address: Assets/Data/Cards/Card_Test example card (Copy).asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 8fdeae7881d130f408e0f31c101ab41f
|
||||||
|
m_Address: Assets/Data/Cards/Card_Test example card (Copy) 1.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 91031de62f795884e8e2ccbaebeebf9b
|
||||||
|
m_Address: Assets/Data/Cards/Card_New Card.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: dec49537f6ae6d241acf8275eaa6c653
|
||||||
|
m_Address: Assets/Data/Cards/Card_Test example card 2 (Copy) 1.asset
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- BlokkemonCard
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_Settings: {fileID: 11400000, guid: 11da9bb90d9dd5848b4f7629415a6937, type: 2}
|
||||||
|
m_SchemaSet:
|
||||||
|
m_Schemas:
|
||||||
|
- {fileID: 11400000, guid: a0affac7520cbac4283e5d0fe0c0d28a, type: 2}
|
||||||
|
- {fileID: 11400000, guid: ca0e74b531acf5449a15f65103061116, type: 2}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 124f05e204489af49943d7fc039640a4
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
|
||||||
|
m_Name: BlokkemonCards_BundledAssetGroupSchema
|
||||||
|
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema
|
||||||
|
m_Group: {fileID: 11400000, guid: 124f05e204489af49943d7fc039640a4, type: 2}
|
||||||
|
m_InternalBundleIdMode: 1
|
||||||
|
m_Compression: 1
|
||||||
|
m_IncludeAddressInCatalog: 1
|
||||||
|
m_IncludeGUIDInCatalog: 1
|
||||||
|
m_IncludeLabelsInCatalog: 1
|
||||||
|
m_InternalIdNamingMode: 0
|
||||||
|
m_CacheClearBehavior: 0
|
||||||
|
m_IncludeInBuild: 1
|
||||||
|
m_BundledAssetProviderType:
|
||||||
|
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
|
||||||
|
m_StripDownloadOptions: 0
|
||||||
|
m_ForceUniqueProvider: 0
|
||||||
|
m_UseAssetBundleCache: 1
|
||||||
|
m_UseAssetBundleCrc: 1
|
||||||
|
m_UseAssetBundleCrcForCachedBundles: 1
|
||||||
|
m_UseUWRForLocalBundles: 0
|
||||||
|
m_Timeout: 0
|
||||||
|
m_ChunkedTransfer: 0
|
||||||
|
m_RedirectLimit: -1
|
||||||
|
m_RetryCount: 0
|
||||||
|
m_BuildPath:
|
||||||
|
m_Id: c258e2bcd3e8ac742ba98f152c0e6322
|
||||||
|
m_LoadPath:
|
||||||
|
m_Id: 88323bf0b2ef98446961cedd6232bd47
|
||||||
|
m_BundleMode: 0
|
||||||
|
m_AssetBundleProviderType:
|
||||||
|
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||||
|
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
|
||||||
|
m_UseDefaultSchemaSettings: 0
|
||||||
|
m_SelectedPathPairIndex: 0
|
||||||
|
m_BundleNaming: 0
|
||||||
|
m_AssetLoadMode: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a0affac7520cbac4283e5d0fe0c0d28a
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
|
||||||
|
m_Name: BlokkemonCards_ContentUpdateGroupSchema
|
||||||
|
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema
|
||||||
|
m_Group: {fileID: 11400000, guid: 124f05e204489af49943d7fc039640a4, type: 2}
|
||||||
|
m_StaticContent: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ca0e74b531acf5449a15f65103061116
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
21
Assets/Data/Cards/Card_New Card 2.asset
Normal file
21
Assets/Data/Cards/Card_New Card 2.asset
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 2a80cc88c9884512b8b633110d838780, type: 3}
|
||||||
|
m_Name: Card_New Card 2
|
||||||
|
m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Data.CardSystem.CardDefinition
|
||||||
|
Id: 7ea4fa95-5327-439b-ab7f-94f2656281f2
|
||||||
|
Name: My New Addressable Card!
|
||||||
|
Description: Description goes hereeee
|
||||||
|
Rarity: 0
|
||||||
|
Zone: 0
|
||||||
|
CardImage: {fileID: 0}
|
||||||
|
CollectionIndex: 7
|
||||||
8
Assets/Data/Cards/Card_New Card 2.asset.meta
Normal file
8
Assets/Data/Cards/Card_New Card 2.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b9c8b6ca63744a44acfe49ebe337619
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using AppleHills.Editor.Utilities;
|
||||||
using Bootstrap;
|
using Bootstrap;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.AddressableAssets;
|
|
||||||
using UnityEditor.AddressableAssets.Settings;
|
using UnityEditor.AddressableAssets.Settings;
|
||||||
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
|
|
||||||
using UnityEditorInternal;
|
using UnityEditorInternal;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AddressableAssets;
|
using UnityEngine.AddressableAssets;
|
||||||
@@ -160,10 +159,15 @@ namespace Editor.Bootstrap
|
|||||||
private static AddressableAssetEntry CreateCustomBootSettingsEntry(CustomBootSettings bootSettings,
|
private static AddressableAssetEntry CreateCustomBootSettingsEntry(CustomBootSettings bootSettings,
|
||||||
AddressableAssetGroup group, string key)
|
AddressableAssetGroup group, string key)
|
||||||
{
|
{
|
||||||
var settings = AddressableAssetSettingsDefaultObject.Settings;
|
string assetPath = AssetDatabase.GetAssetPath(bootSettings);
|
||||||
var entry = settings.CreateOrMoveEntry(
|
var settings = AddressablesUtility.GetAddressableSettings(false);
|
||||||
AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(bootSettings)),
|
if (settings == null || string.IsNullOrEmpty(assetPath) || group == null)
|
||||||
group);
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||||
|
var entry = settings.CreateOrMoveEntry(guid, group);
|
||||||
entry.address = key;
|
entry.address = key;
|
||||||
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
|
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
|
||||||
return entry;
|
return entry;
|
||||||
@@ -189,21 +193,7 @@ namespace Editor.Bootstrap
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static AddressableAssetGroup GetOrCreateGroup(string name, bool includeInBuild)
|
private static AddressableAssetGroup GetOrCreateGroup(string name, bool includeInBuild)
|
||||||
{
|
{
|
||||||
// Use GetSettings(true) to ensure the settings asset is created if missing
|
return AddressablesUtility.GetOrCreateGroup(name, includeInBuild);
|
||||||
var settings = AddressableAssetSettingsDefaultObject.GetSettings(true);
|
|
||||||
if (settings == null)
|
|
||||||
{
|
|
||||||
Debug.LogError("AddressableAssetSettings could not be found or created. Please ensure Addressables are set up in your project.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var group = settings.FindGroup(name);
|
|
||||||
if (group == null)
|
|
||||||
{
|
|
||||||
group = settings.CreateGroup(name, false, false, true, settings.DefaultGroup.Schemas);
|
|
||||||
group.GetSchema<BundledAssetGroupSchema>().IncludeInBuild = includeInBuild;
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AppleHills.Data.CardSystem;
|
using AppleHills.Data.CardSystem;
|
||||||
using AppleHills.UI.CardSystem;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEditor.AddressableAssets.Settings;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using AppleHills.Editor.Utilities;
|
||||||
|
using UI.CardSystem;
|
||||||
|
|
||||||
namespace AppleHills.Editor.CardSystem
|
namespace AppleHills.Editor.CardSystem
|
||||||
{
|
{
|
||||||
@@ -1079,6 +1081,19 @@ namespace AppleHills.Editor.CardSystem
|
|||||||
EditorUtility.SetDirty(_selectedCard);
|
EditorUtility.SetDirty(_selectedCard);
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
|
// Add to Addressables group "BlokkemonCards" and apply "BlokkemonCard" label
|
||||||
|
string assetPath = AssetDatabase.GetAssetPath(_selectedCard);
|
||||||
|
if (!string.IsNullOrEmpty(assetPath))
|
||||||
|
{
|
||||||
|
if (AddressablesUtility.EnsureAssetInGroupWithLabel(assetPath, "BlokkemonCards", "BlokkemonCard"))
|
||||||
|
{
|
||||||
|
AddressablesUtility.SaveAddressableAssets();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("Failed to add card to Addressables. Please ensure Addressables are set up in this project.");
|
||||||
|
}
|
||||||
|
}
|
||||||
// Clear dirty flag
|
// Clear dirty flag
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
}
|
}
|
||||||
|
|||||||
226
Assets/Editor/Utilities/AddressablesUtility.cs
Normal file
226
Assets/Editor/Utilities/AddressablesUtility.cs
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
using UnityEditor.AddressableAssets;
|
||||||
|
using UnityEditor.AddressableAssets.Settings;
|
||||||
|
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace AppleHills.Editor.Utilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for working with the Addressables system in editor scripts.
|
||||||
|
/// Contains common operations for creating and managing groups and entries.
|
||||||
|
/// </summary>
|
||||||
|
public static class AddressablesUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get or create the Addressables settings. Returns null if Addressables are not properly set up.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="createIfMissing">If true, will attempt to create settings if they don't exist</param>
|
||||||
|
/// <returns>The AddressableAssetSettings or null if not available</returns>
|
||||||
|
public static AddressableAssetSettings GetAddressableSettings(bool createIfMissing = false)
|
||||||
|
{
|
||||||
|
var settings = AddressableAssetSettingsDefaultObject.GetSettings(createIfMissing);
|
||||||
|
if (settings == null && createIfMissing)
|
||||||
|
{
|
||||||
|
Debug.LogError("AddressableAssetSettings could not be found or created. Please ensure Addressables are set up in your project.");
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve or create an Addressables group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of the group</param>
|
||||||
|
/// <param name="includeInBuild">Whether to include assets in this group when building</param>
|
||||||
|
/// <returns>The created or existing group, or null if settings aren't available</returns>
|
||||||
|
public static AddressableAssetGroup GetOrCreateGroup(string name, bool includeInBuild = true)
|
||||||
|
{
|
||||||
|
// Use GetSettings(true) to ensure the settings asset is created if missing
|
||||||
|
var settings = GetAddressableSettings(true);
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = settings.FindGroup(name);
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
group = settings.CreateGroup(name, false, false, true, settings.DefaultGroup.Schemas);
|
||||||
|
|
||||||
|
// Configure the bundle schema if it exists
|
||||||
|
var bundleSchema = group.GetSchema<BundledAssetGroupSchema>();
|
||||||
|
if (bundleSchema != null)
|
||||||
|
{
|
||||||
|
bundleSchema.IncludeInBuild = includeInBuild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create or move an asset entry to a specified Addressables group
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assetPath">Path to the asset</param>
|
||||||
|
/// <param name="groupName">Name of the group to add the asset to</param>
|
||||||
|
/// <param name="address">Optional custom address for the asset</param>
|
||||||
|
/// <returns>The created or moved asset entry, or null if failed</returns>
|
||||||
|
public static AddressableAssetEntry AddAssetToGroup(string assetPath, string groupName, string address = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(assetPath))
|
||||||
|
{
|
||||||
|
Debug.LogError("Cannot add asset to Addressables: Asset path is empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = GetAddressableSettings(false);
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = GetOrCreateGroup(groupName);
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||||
|
if (string.IsNullOrEmpty(guid))
|
||||||
|
{
|
||||||
|
Debug.LogError($"Cannot add asset to Addressables: Invalid asset path {assetPath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = settings.CreateOrMoveEntry(guid, group);
|
||||||
|
|
||||||
|
// Set custom address if provided
|
||||||
|
if (!string.IsNullOrEmpty(address))
|
||||||
|
{
|
||||||
|
entry.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure an asset is in the specified addressable group
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assetPath">Path to the asset</param>
|
||||||
|
/// <param name="groupName">Target group name</param>
|
||||||
|
/// <param name="markDirty">Whether to mark settings as dirty</param>
|
||||||
|
/// <returns>True if the asset is now in the correct group</returns>
|
||||||
|
public static bool EnsureAssetInGroup(string assetPath, string groupName, bool markDirty = true)
|
||||||
|
{
|
||||||
|
var settings = GetAddressableSettings(false);
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||||
|
if (string.IsNullOrEmpty(guid))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = GetOrCreateGroup(groupName);
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = settings.FindAssetEntry(guid);
|
||||||
|
|
||||||
|
// If entry exists but is in wrong group, move it
|
||||||
|
if (entry != null && entry.parentGroup != group)
|
||||||
|
{
|
||||||
|
settings.MoveEntry(entry, group);
|
||||||
|
if (markDirty)
|
||||||
|
{
|
||||||
|
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If entry doesn't exist, create it
|
||||||
|
else if (entry == null)
|
||||||
|
{
|
||||||
|
entry = settings.CreateOrMoveEntry(guid, group);
|
||||||
|
if (markDirty)
|
||||||
|
{
|
||||||
|
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryAdded, entry, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry already exists in correct group
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save any pending changes to addressable assets
|
||||||
|
/// </summary>
|
||||||
|
public static void SaveAddressableAssets()
|
||||||
|
{
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a label to an addressable asset
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assetPath">Path to the asset</param>
|
||||||
|
/// <param name="label">Label to add</param>
|
||||||
|
/// <returns>True if the label was successfully added</returns>
|
||||||
|
public static bool AddLabelToAsset(string assetPath, string label)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(label))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = GetAddressableSettings(false);
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||||
|
var entry = settings.FindAssetEntry(guid);
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Asset at {assetPath} is not addressable. Add it to a group first before applying labels.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the label exists in the settings
|
||||||
|
if (!settings.GetLabels().Contains(label))
|
||||||
|
{
|
||||||
|
settings.AddLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the label to the entry
|
||||||
|
entry.labels.Add(label);
|
||||||
|
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, entry, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure an asset is in an addressable group and has the specified label
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assetPath">Path to the asset</param>
|
||||||
|
/// <param name="groupName">Group to add the asset to</param>
|
||||||
|
/// <param name="label">Label to apply to the asset</param>
|
||||||
|
/// <returns>True if the asset was successfully added to the group and labeled</returns>
|
||||||
|
public static bool EnsureAssetInGroupWithLabel(string assetPath, string groupName, string label)
|
||||||
|
{
|
||||||
|
bool result = EnsureAssetInGroup(assetPath, groupName);
|
||||||
|
if (result && !string.IsNullOrEmpty(label))
|
||||||
|
{
|
||||||
|
result = AddLabelToAsset(assetPath, label);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Editor/Utilities/AddressablesUtility.cs.meta
Normal file
3
Assets/Editor/Utilities/AddressablesUtility.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5225b0b6611c4dc7a6e1f48919831efd
|
||||||
|
timeCreated: 1761029089
|
||||||
@@ -287,10 +287,10 @@ Texture2D:
|
|||||||
m_WrapV: 0
|
m_WrapV: 0
|
||||||
m_WrapW: 0
|
m_WrapW: 0
|
||||||
m_LightmapFormat: 0
|
m_LightmapFormat: 0
|
||||||
m_ColorSpace: 1
|
m_ColorSpace: 0
|
||||||
m_PlatformBlob:
|
m_PlatformBlob:
|
||||||
image data: 1
|
image data: 1
|
||||||
_typelessdata: cd
|
_typelessdata: 00
|
||||||
m_StreamData:
|
m_StreamData:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
offset: 0
|
offset: 0
|
||||||
|
|||||||
@@ -146,10 +146,10 @@ Texture2D:
|
|||||||
m_WrapV: 0
|
m_WrapV: 0
|
||||||
m_WrapW: 0
|
m_WrapW: 0
|
||||||
m_LightmapFormat: 0
|
m_LightmapFormat: 0
|
||||||
m_ColorSpace: 1
|
m_ColorSpace: 0
|
||||||
m_PlatformBlob:
|
m_PlatformBlob:
|
||||||
image data: 1
|
image data: 1
|
||||||
_typelessdata: cd
|
_typelessdata: 00
|
||||||
m_StreamData:
|
m_StreamData:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
offset: 0
|
offset: 0
|
||||||
|
|||||||
@@ -57,5 +57,19 @@ namespace AppleHills.Data.CardSystem
|
|||||||
return Color.white;
|
return Color.white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is CardDefinition other)
|
||||||
|
{
|
||||||
|
return string.Equals(Id, other.Id, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Id != null ? Id.GetHashCode() : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ namespace Data.CardSystem
|
|||||||
[Header("Card Collection")]
|
[Header("Card Collection")]
|
||||||
[SerializeField] private List<CardDefinition> availableCards = new List<CardDefinition>();
|
[SerializeField] private List<CardDefinition> availableCards = new List<CardDefinition>();
|
||||||
|
|
||||||
[Header("Auto-Loading Configuration")]
|
|
||||||
[SerializeField] private bool autoLoadCardDefinitions = true;
|
|
||||||
[SerializeField] private string cardDataPath = "Data/Cards";
|
|
||||||
|
|
||||||
// Runtime data - will be serialized for save/load
|
// Runtime data - will be serialized for save/load
|
||||||
[SerializeField] private CardInventory playerInventory = new CardInventory();
|
[SerializeField] private CardInventory playerInventory = new CardInventory();
|
||||||
|
|
||||||
@@ -44,22 +40,18 @@ namespace Data.CardSystem
|
|||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
|
|
||||||
// Auto-load card definitions if enabled
|
|
||||||
if (autoLoadCardDefinitions)
|
|
||||||
{
|
|
||||||
LoadCardDefinitionsFromFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build lookup dictionary
|
|
||||||
BuildDefinitionLookup();
|
|
||||||
|
|
||||||
// Register for post-boot initialization
|
// Register for post-boot initialization
|
||||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializePostBoot()
|
private void InitializePostBoot()
|
||||||
{
|
{
|
||||||
// Initialize any dependencies that require other services to be ready
|
// Load card definitions from Addressables
|
||||||
|
LoadCardDefinitionsFromAddressables();
|
||||||
|
|
||||||
|
// Build lookup dictionary
|
||||||
|
BuildDefinitionLookup();
|
||||||
|
|
||||||
Logging.Debug("[CardSystemManager] Post-boot initialization complete");
|
Logging.Debug("[CardSystemManager] Post-boot initialization complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,44 +61,33 @@ namespace Data.CardSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads all card definitions from the specified folder
|
/// Loads all card definitions from Addressables using the "BlokkemonCard" label
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void LoadCardDefinitionsFromFolder()
|
private async void LoadCardDefinitionsFromAddressables()
|
||||||
{
|
|
||||||
// Initialize list if needed
|
|
||||||
if (availableCards == null)
|
|
||||||
{
|
{
|
||||||
availableCards = new List<CardDefinition>();
|
availableCards = new List<CardDefinition>();
|
||||||
}
|
// Load by label instead of group name for better flexibility
|
||||||
|
var handle = UnityEngine.AddressableAssets.Addressables.LoadResourceLocationsAsync(new string[] { "BlokkemonCard" }, UnityEngine.AddressableAssets.Addressables.MergeMode.Union);
|
||||||
#if UNITY_EDITOR
|
await handle.Task;
|
||||||
// In editor we can load assets directly from the project folder
|
if (handle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
|
||||||
string folderPath = "Assets/" + cardDataPath;
|
|
||||||
string[] guids = AssetDatabase.FindAssets("t:CardDefinition", new[] { folderPath });
|
|
||||||
|
|
||||||
List<CardDefinition> loadedDefinitions = new List<CardDefinition>();
|
|
||||||
foreach (string guid in guids)
|
|
||||||
{
|
{
|
||||||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
var locations = handle.Result;
|
||||||
CardDefinition cardDef = AssetDatabase.LoadAssetAtPath<CardDefinition>(assetPath);
|
var loadedIds = new HashSet<string>();
|
||||||
if (cardDef != null && !string.IsNullOrEmpty(cardDef.Id))
|
foreach (var loc in locations)
|
||||||
{
|
{
|
||||||
loadedDefinitions.Add(cardDef);
|
var cardHandle = UnityEngine.AddressableAssets.Addressables.LoadAssetAsync<CardDefinition>(loc);
|
||||||
}
|
await cardHandle.Task;
|
||||||
}
|
if (cardHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
|
||||||
|
|
||||||
// Replace the existing list with loaded definitions
|
|
||||||
availableCards = loadedDefinitions;
|
|
||||||
#else
|
|
||||||
// In build, load from Resources folder
|
|
||||||
CardDefinition[] resourceCards = Resources.LoadAll<CardDefinition>(cardDataPath);
|
|
||||||
if (resourceCards != null && resourceCards.Length > 0)
|
|
||||||
{
|
{
|
||||||
availableCards = resourceCards.Where(card => card != null && !string.IsNullOrEmpty(card.Id)).ToList();
|
var cardDef = cardHandle.Result;
|
||||||
|
if (cardDef != null && !string.IsNullOrEmpty(cardDef.Id) && !loadedIds.Contains(cardDef.Id))
|
||||||
|
{
|
||||||
|
availableCards.Add(cardDef);
|
||||||
|
loadedIds.Add(cardDef.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Logging.Debug($"[CardSystemManager] Loaded {availableCards.Count} card definitions from {cardDataPath}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using Core;
|
using Core;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEditor.Experimental.GraphView;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Audio;
|
using UnityEngine.Audio;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using Data.CardSystem;
|
using Data.CardSystem;
|
||||||
using Core;
|
using Core;
|
||||||
using AppleHills.UI.CardSystem;
|
using UI.CardSystem;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace AppleHills.Tests
|
namespace AppleHills.Tests
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ using Core;
|
|||||||
using Data.CardSystem;
|
using Data.CardSystem;
|
||||||
using Pixelplacement;
|
using Pixelplacement;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
|
using UI.Core;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.CardSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UI page for viewing the player's card collection in an album.
|
/// UI page for viewing the player's card collection in an album.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using UnityEngine;
|
using Pixelplacement;
|
||||||
using TMPro;
|
|
||||||
using Pixelplacement;
|
|
||||||
using Pixelplacement.TweenSystem;
|
using Pixelplacement.TweenSystem;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.CardSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages a notification dot that displays a count (e.g., booster packs)
|
/// Manages a notification dot that displays a count (e.g., booster packs)
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ using AppleHills.Data.CardSystem;
|
|||||||
using Core;
|
using Core;
|
||||||
using Data.CardSystem;
|
using Data.CardSystem;
|
||||||
using Pixelplacement;
|
using Pixelplacement;
|
||||||
using TMPro;
|
using UI.Core;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.CardSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UI page for opening booster packs and displaying the cards obtained.
|
/// UI page for opening booster packs and displaying the cards obtained.
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using System;
|
using AppleHills.Data.CardSystem;
|
||||||
using System.Collections;
|
|
||||||
using AppleHills.Data.CardSystem;
|
|
||||||
using Core;
|
using Core;
|
||||||
using Data.CardSystem;
|
using Data.CardSystem;
|
||||||
using Pixelplacement;
|
using Pixelplacement;
|
||||||
|
using UI.Core;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.CardSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main UI controller for the card album system.
|
/// Main UI controller for the card album system.
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
using Core;
|
||||||
using AppleHills.Data.CardSystem;
|
|
||||||
using Core;
|
|
||||||
using Data.CardSystem;
|
using Data.CardSystem;
|
||||||
using Pixelplacement;
|
using Pixelplacement;
|
||||||
using TMPro;
|
using UI.Core;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.CardSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UI page for the main menu of the card system.
|
/// UI page for the main menu of the card system.
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using AppleHills.Data.CardSystem;
|
using AppleHills.Data.CardSystem;
|
||||||
using Core;
|
using Core;
|
||||||
using UnityEngine;
|
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine.UI;
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
#endif
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.CardSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles displaying and interacting with a single card in the UI.
|
/// Handles displaying and interacting with a single card in the UI.
|
||||||
|
|||||||
3
Assets/Scripts/UI/Core.meta
Normal file
3
Assets/Scripts/UI/Core.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea8964f9870d45479f09a4b05c2b0d0c
|
||||||
|
timeCreated: 1761040697
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for UI pages that can transition in and out.
|
/// Base class for UI pages that can transition in and out.
|
||||||
@@ -5,7 +5,7 @@ using Core;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
namespace AppleHills.UI.CardSystem
|
namespace UI.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages UI page transitions and maintains a stack of active pages.
|
/// Manages UI page transitions and maintains a stack of active pages.
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using AppleHills.Core.Settings;
|
using AppleHills.Core.Settings;
|
||||||
using Core;
|
using Core;
|
||||||
@@ -72,10 +73,37 @@ namespace Utils
|
|||||||
return frameAdjustedSpeed * screenNormalizationFactor;
|
return frameAdjustedSpeed * screenNormalizationFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AddressableKeyExists(object key)
|
public static bool AddressableKeyExists(object key, Addressables.MergeMode mergeMode = Addressables.MergeMode.Union)
|
||||||
{
|
{
|
||||||
IList<IResourceLocation> locations;
|
try
|
||||||
return Addressables.LoadResourceLocationsAsync(key).WaitForCompletion()?.Count > 0;
|
{
|
||||||
|
// Handle different key types
|
||||||
|
if (key is string[] keyArray)
|
||||||
|
{
|
||||||
|
// For string arrays, use the array as is with merge mode
|
||||||
|
return Addressables.LoadResourceLocationsAsync(keyArray, mergeMode).WaitForCompletion()?.Count > 0;
|
||||||
|
}
|
||||||
|
else if (key is IEnumerable<object> keyList)
|
||||||
|
{
|
||||||
|
// For collections of keys, convert to object[]
|
||||||
|
return Addressables.LoadResourceLocationsAsync(keyList.ToArray(), mergeMode).WaitForCompletion()?.Count > 0;
|
||||||
|
}
|
||||||
|
else if (key is string stringKey)
|
||||||
|
{
|
||||||
|
// For single string keys, wrap in array
|
||||||
|
return Addressables.LoadResourceLocationsAsync(new string[] { stringKey }, mergeMode).WaitForCompletion()?.Count > 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For other single keys (AssetReference, etc.), wrap in object[]
|
||||||
|
return Addressables.LoadResourceLocationsAsync(new object[] { key }, mergeMode).WaitForCompletion()?.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AppleHillsUtils] Error checking addressable key existence: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,196 @@
|
|||||||
# Camera Screen Adaptation System
|
# Camera Screen Adaptation System
|
||||||
|
|
||||||
This system helps adapt your 2D game's orthographic camera to different screen sizes, ensuring content is displayed consistently across various devices. It also provides tools for anchoring game objects to screen edges.
|
Adapts your 2D orthographic camera to different aspect ratios while keeping gameplay/UI elements aligned to screen edges. Includes a reference marker for target content width and utilities for edge anchoring.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [What This Solves](#what-this-solves)
|
||||||
|
- [Architecture at a Glance](#architecture-at-a-glance)
|
||||||
|
- [Quick Start (Code-First)](#quick-start-code-first)
|
||||||
|
- [Create and Wire Components in Code](#create-and-wire-components-in-code)
|
||||||
|
- [Manual Recalculate / Listen for Changes](#manual-recalculate--listen-for-changes)
|
||||||
|
- [Components](#components)
|
||||||
|
- [`ScreenReferenceMarker`](#screenreferencemarker)
|
||||||
|
- [`CameraScreenAdapter`](#camerascreenadapter)
|
||||||
|
- [`EdgeAnchor`](#edgeanchor)
|
||||||
|
- [Common Case Studies](#common-case-studies)
|
||||||
|
- [HUD Top Bar (safe top margin)](#hud-top-bar-safe-top-margin)
|
||||||
|
- [Side Buttons (left/right margins)](#side-buttons-leftright-margins)
|
||||||
|
- [Cinemachine Virtual Camera](#cinemachine-virtual-camera)
|
||||||
|
- [Tips & Best Practices](#tips--best-practices)
|
||||||
|
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||||
|
- [Paths & Namespaces](#paths--namespaces)
|
||||||
|
- [Change Log](#change-log)
|
||||||
|
|
||||||
|
## What This Solves
|
||||||
|
- Consistent horizontal framing across devices: a fixed target world-space width always fits the screen.
|
||||||
|
- Stable world-space margins from the physical screen edges for placing HUD/world elements.
|
||||||
|
- Works with both a regular `Camera` and `Cinemachine`'s `CinemachineCamera`.
|
||||||
|
|
||||||
|
## Architecture at a Glance
|
||||||
|
- Reference: `ScreenReferenceMarker` defines `targetWidth` and world-space margins (`topMargin`, `bottomMargin`, `leftMargin`, `rightMargin`).
|
||||||
|
- Adapter: `CameraScreenAdapter` computes orthographic size from `targetWidth` and aspect and applies it to the controlled camera. Emits `OnCameraAdjusted`.
|
||||||
|
- Anchoring: `EdgeAnchor` positions objects relative to the computed screen edges using the same marker margins.
|
||||||
|
|
||||||
|
## Quick Start (Code-First)
|
||||||
|
|
||||||
|
### Create and Wire Components in Code
|
||||||
|
```csharp
|
||||||
|
using AppleHillsCamera;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class CameraAdaptationBootstrap : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Camera mainCamera; // assign in inspector or find at runtime
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
// 1) Create/locate the marker
|
||||||
|
var markerGO = new GameObject("ScreenReferenceMarker");
|
||||||
|
var marker = markerGO.AddComponent<ScreenReferenceMarker>();
|
||||||
|
marker.targetWidth = 16f; // your desired world-space width
|
||||||
|
marker.topMargin = 1f; // tune margins for your HUD
|
||||||
|
|
||||||
|
// 2) Set up the adapter on your camera
|
||||||
|
var adapter = mainCamera.gameObject.AddComponent<CameraScreenAdapter>();
|
||||||
|
adapter.referenceMarker = marker;
|
||||||
|
adapter.adjustOnStart = true;
|
||||||
|
adapter.adjustOnScreenResize = true;
|
||||||
|
|
||||||
|
// 3) Anchor a sample HUD object to the top edge
|
||||||
|
var hud = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||||||
|
var anchor = hud.AddComponent<EdgeAnchor>();
|
||||||
|
anchor.referenceMarker = marker;
|
||||||
|
anchor.anchor = EdgeAnchor.AnchorEdge.Top;
|
||||||
|
anchor.additionalOffset = 0.25f; // extra spacing from the top margin
|
||||||
|
anchor.continuousUpdate = true; // keep tracking when aspect changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Recalculate / Listen for Changes
|
||||||
|
```csharp
|
||||||
|
using AppleHillsCamera;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class CameraAdaptationHooks : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private CameraScreenAdapter adapter;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
// Listen when the camera size is applied
|
||||||
|
adapter.OnCameraAdjusted += OnAdjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
adapter.OnCameraAdjusted -= OnAdjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForceRecalculateNow()
|
||||||
|
{
|
||||||
|
adapter.AdjustCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAdjusted()
|
||||||
|
{
|
||||||
|
Debug.Log("Camera adjusted to match target width.");
|
||||||
|
// Reposition custom elements if you don’t use EdgeAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
### 1. ScreenReferenceMarker
|
### `ScreenReferenceMarker`
|
||||||
|
Defines the reference sizes and margins used by both the adapter and anchors.
|
||||||
|
|
||||||
This component defines the target width and edge margins for your level content.
|
Key fields:
|
||||||
|
- `targetWidth` (float): world-space width that should fully fit the viewport.
|
||||||
|
- `topMargin`, `bottomMargin`, `leftMargin`, `rightMargin` (float): world-space distances from the corresponding screen edges.
|
||||||
|
- Scene gizmos: visualizes width line, screen rectangle, and margin guides in the editor.
|
||||||
|
|
||||||
- **Target Width**: The desired width to be displayed on screen
|
Typical setup:
|
||||||
- **Edge Margins**: Distances from screen edges for anchoring objects
|
- Place at the world-space center of your playable area or camera focus.
|
||||||
- **Visualization**: Editor-only visualization of the reference sizes
|
- Tune margins to match your HUD/world layout needs.
|
||||||
|
|
||||||
### 2. CameraScreenAdapter
|
### `CameraScreenAdapter`
|
||||||
|
Computes and applies orthographic size to a `Camera` or `CinemachineCamera` so that the screen horizontally fits `targetWidth`.
|
||||||
|
|
||||||
Automatically adjusts the camera's orthographic size to ensure the target width (defined by the ScreenReferenceMarker) matches the screen width.
|
Highlights:
|
||||||
|
- Fields: `referenceMarker`, `adjustOnStart`, `adjustOnScreenResize`.
|
||||||
|
- Event: `OnCameraAdjusted` fires after a size update.
|
||||||
|
- Works with built-in `Camera` (must be orthographic) or with `CinemachineCamera`.
|
||||||
|
|
||||||
- **Reference Marker**: Link to your ScreenReferenceMarker
|
Formula:
|
||||||
- **Adapt on Resolution Change**: Automatically update when screen resolution changes
|
- `orthoSize = (targetWidth / 2f) * (Screen.height / Screen.width)`.
|
||||||
|
|
||||||
### 3. EdgeAnchor
|
Public helpers:
|
||||||
|
- `AdjustCamera()` — recalculate and apply now.
|
||||||
|
- `GetControlledCamera()` — returns the underlying `Camera` instance.
|
||||||
|
|
||||||
Anchors game objects to screen edges, maintaining consistent distance when the camera's orthographic size changes.
|
### `EdgeAnchor`
|
||||||
|
Anchors a `Transform` to a chosen screen edge using the same marker margins.
|
||||||
|
|
||||||
- **Anchor Edge**: Which edge to anchor to (Top, Bottom, Left, Right)
|
Common fields (names based on code):
|
||||||
- **Reference Marker**: Uses the same marker as the CameraScreenAdapter
|
- `referenceMarker` (`ScreenReferenceMarker`)
|
||||||
- **Additional Offset**: Extra distance from the edge margin
|
- `anchor` (`EdgeAnchor.AnchorEdge`): `Top`, `Bottom`, `Left`, `Right`
|
||||||
- **Continuous Update**: Whether to update position every frame
|
- `additionalOffset` (float): extra spacing beyond the margin
|
||||||
|
- `continuousUpdate` (bool): update every frame and on camera adjustments
|
||||||
|
|
||||||
### 4. CameraScreenVisualizer
|
Usage tips:
|
||||||
|
- Use `continuousUpdate` for elements that must track aspect changes at runtime.
|
||||||
|
- Combine with local offsets/parenting for precise UI/HUD placements in world space.
|
||||||
|
|
||||||
Helper component for visualizing camera boundaries in the editor.
|
## Common Case Studies
|
||||||
|
|
||||||
## Setup Instructions
|
### HUD Top Bar (safe top margin)
|
||||||
|
- Set `marker.topMargin` to the vertical padding you want from the top edge.
|
||||||
|
- Add `EdgeAnchor` to your top bar root:
|
||||||
|
```csharp
|
||||||
|
anchor.anchor = EdgeAnchor.AnchorEdge.Top;
|
||||||
|
anchor.additionalOffset = 0.1f; // small breathing room
|
||||||
|
```
|
||||||
|
- Keep `continuousUpdate = true` for device rotation/platform switches.
|
||||||
|
|
||||||
### Basic Setup
|
### Side Buttons (left/right margins)
|
||||||
|
- For a left button column:
|
||||||
|
```csharp
|
||||||
|
anchor.anchor = EdgeAnchor.AnchorEdge.Left;
|
||||||
|
anchor.additionalOffset = 0.2f;
|
||||||
|
```
|
||||||
|
- Mirror for right side with `Right`.
|
||||||
|
- Adjust `marker.leftMargin/rightMargin` to push them inward safely.
|
||||||
|
|
||||||
1. **Create the Reference Marker:**
|
### Cinemachine Virtual Camera
|
||||||
- Add a new empty GameObject to your scene
|
- Add `CameraScreenAdapter` to the GameObject with `CinemachineCamera`.
|
||||||
- Add the `ScreenReferenceMarker` component to it
|
- The adapter detects Cinemachine and writes to `Lens.OrthographicSize`.
|
||||||
- Position it at the center of your target view
|
- Everything else (margins and `EdgeAnchor`) works the same.
|
||||||
- Set the `targetWidth` to match your level's ideal width
|
|
||||||
- Adjust margin values as needed
|
|
||||||
|
|
||||||
2. **Configure the Camera:**
|
## Tips & Best Practices
|
||||||
- Add the `CameraScreenAdapter` component to your main camera
|
- Pick a `targetWidth` that matches the core gameplay area horizontally; verify extremes (16:9, 19.5:9, 4:3).
|
||||||
- Reference the ScreenReferenceMarker you created
|
- Prefer `EdgeAnchor` for HUD/world labels instead of manual scripts—anchors auto-update on resize and on `OnCameraAdjusted`.
|
||||||
- Optionally, add `CameraScreenVisualizer` for better editor visualization
|
- For one-off bespoke layouts, listen to `OnCameraAdjusted` and move objects yourself.
|
||||||
|
- Ensure regular cameras are set to `orthographic`; the adapter warns if not.
|
||||||
|
|
||||||
3. **Anchor Game Objects:**
|
## Troubleshooting / FAQ
|
||||||
- For any objects that should maintain position relative to screen edges:
|
- My camera didn’t change size:
|
||||||
- Add the `EdgeAnchor` component
|
- Ensure `referenceMarker` is assigned.
|
||||||
- Set the desired anchor edge (e.g., Top)
|
- For built-in `Camera`, confirm `orthographic` is enabled.
|
||||||
- Reference the same ScreenReferenceMarker
|
- If using Cinemachine, verify the component is `CinemachineCamera` on the same GameObject.
|
||||||
- Adjust additional offset if needed
|
- Anchored objects are offset oddly:
|
||||||
|
- Check `additionalOffset` and your object’s pivot/bounds.
|
||||||
|
- Verify marker margins and that the marker sits at the world center you expect.
|
||||||
|
- It works in Play but gizmos look wrong in Scene:
|
||||||
|
- Scene gizmos approximate using Scene/Game view sizes; rely on Play Mode for truth.
|
||||||
|
|
||||||
### Example: Top-Edge Anchoring
|
## Paths & Namespaces
|
||||||
|
- Scripts: `Assets/Scripts/AppleHillsCamera/`
|
||||||
|
- `ScreenReferenceMarker.cs`
|
||||||
|
- `CameraScreenAdapter.cs`
|
||||||
|
- `EdgeAnchor.cs`
|
||||||
|
- Namespace: `AppleHillsCamera`
|
||||||
|
|
||||||
For objects that need to stay a fixed distance from the top of the screen:
|
## Change Log
|
||||||
|
- v1.1: Added Table of Contents, code-first snippets, case studies, and aligned terms with actual APIs (`OnCameraAdjusted`, Cinemachine support).
|
||||||
1. Set up your ScreenReferenceMarker with an appropriate topMargin value
|
- v1.0: Original overview and setup guide.
|
||||||
2. Add EdgeAnchor to the object you want to anchor
|
|
||||||
3. Set AnchorEdge to Top
|
|
||||||
4. Adjust additionalOffset for fine-tuning
|
|
||||||
|
|
||||||
## Tips for Level Design
|
|
||||||
|
|
||||||
- Place the ScreenReferenceMarker at the center of your scene's critical content
|
|
||||||
- Use the visual gizmos to see how your reference width relates to camera boundaries
|
|
||||||
- Test your scene in different aspect ratios to ensure proper adaptation
|
|
||||||
- For complex levels, you might want multiple reference markers for different sections
|
|
||||||
|
|
||||||
## Runtime Considerations
|
|
||||||
|
|
||||||
- The system automatically adapts when the screen size changes
|
|
||||||
- The CameraScreenAdapter can be disabled if you need to temporarily override the automatic sizing
|
|
||||||
- EdgeAnchor components can be disabled when their objects don't need to maintain edge alignment
|
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
# Card System Implementation Plan
|
|
||||||
|
|
||||||
## Current Implementation Analysis
|
|
||||||
|
|
||||||
### Data Layer
|
|
||||||
|
|
||||||
1. **CardData.cs**:
|
|
||||||
- Represents an instance of a card in the player's collection
|
|
||||||
- Contains unique ID, rarity, and copies owned
|
|
||||||
- Has a reference to its CardDefinition for display information
|
|
||||||
- Includes methods for rarity upgrades when collecting duplicates
|
|
||||||
- Well-structured with proper serialization support
|
|
||||||
|
|
||||||
2. **CardDefinition.cs**:
|
|
||||||
- ScriptableObject template for creating card instances
|
|
||||||
- Contains basic info (name, description), visuals, and collection data
|
|
||||||
- Provides helper methods like CreateCardData() and GetBackgroundColor()
|
|
||||||
- Supports different zones (AppleHills, Quarry, Forest, Mountain, Beach)
|
|
||||||
|
|
||||||
3. **CardSystemManager.cs**:
|
|
||||||
- Singleton manager for the card system
|
|
||||||
- Manages available card definitions and player inventory
|
|
||||||
- Includes event callbacks for booster packs and card collection
|
|
||||||
- Implements functionality for adding and opening booster packs
|
|
||||||
|
|
||||||
4. **CardInventory.cs**:
|
|
||||||
- Fully implemented management of the player's card collection
|
|
||||||
- Includes methods for adding cards and tracking booster packs
|
|
||||||
- Provides filtering methods for different card types
|
|
||||||
- Supports card lookup by various properties (zone, rarity)
|
|
||||||
|
|
||||||
5. **CardVisualConfig.cs**:
|
|
||||||
- ScriptableObject for configuring the visual appearance of cards
|
|
||||||
- Maps rarities to colors and zones to colors/shapes
|
|
||||||
- Uses dictionary lookups for efficient access
|
|
||||||
|
|
||||||
### UI Layer
|
|
||||||
|
|
||||||
1. **CardUIElement.cs**:
|
|
||||||
- Handles displaying a single card in the UI
|
|
||||||
- Updates visuals based on card data (rarity, zone)
|
|
||||||
- References UI elements like card name text, images, frames, etc.
|
|
||||||
|
|
||||||
2. **UIPageController.cs**:
|
|
||||||
- Manages UI page transitions with a stack-based navigation system
|
|
||||||
- Provides methods for pushing, popping, and clearing the page stack
|
|
||||||
- Uses events to notify when pages change
|
|
||||||
|
|
||||||
3. **UI Page Components**:
|
|
||||||
- **CardMenuPage.cs**: Main menu for card system navigation
|
|
||||||
- **AlbumViewPage.cs**: Simplified implementation that shows filtered cards in grid
|
|
||||||
- **BoosterOpeningPage.cs**: Interactive card reveal experience
|
|
||||||
- **CardAlbumUI.cs**: Main controller for the card system UI
|
|
||||||
- **UIPage.cs**: Base class for common page functionality
|
|
||||||
|
|
||||||
## Implementation Status
|
|
||||||
|
|
||||||
### Completed
|
|
||||||
1. **Complete CardInventory Implementation**:
|
|
||||||
- Full implementation with methods to manage the player's card collection
|
|
||||||
- Filtering, sorting, and organization features
|
|
||||||
- Integration with CardSystemManager
|
|
||||||
|
|
||||||
2. **Booster Pack Generation**:
|
|
||||||
- Logic to generate random booster packs with appropriate rarity distribution
|
|
||||||
- Methods for awarding booster packs to the player
|
|
||||||
- Events for notifying UI when booster packs are obtained
|
|
||||||
|
|
||||||
3. **Card Collection UI**:
|
|
||||||
- Simplified album browsing UI with filtering and sorting options
|
|
||||||
- Removed unnecessary card stack and slot system
|
|
||||||
- Updated AlbumViewPage to use TextMeshPro components
|
|
||||||
|
|
||||||
4. **Booster Pack Opening UI (In Progress)**:
|
|
||||||
- Implemented interactive card reveal system
|
|
||||||
- Added card back prefabs for clickable reveal experience
|
|
||||||
- Created flip animations and particle effects for card reveals
|
|
||||||
- Updated to support revealing three cards individually
|
|
||||||
|
|
||||||
### Still Needed
|
|
||||||
1. **Booster Opening UI Refinement**:
|
|
||||||
- Fine-tune animation timings and transitions
|
|
||||||
- Implement custom card back designs
|
|
||||||
- Add more visual/audio feedback for different rarities
|
|
||||||
|
|
||||||
2. **Save/Load System**:
|
|
||||||
- Implement persistence for the player's card collection
|
|
||||||
- Add autosave functionality for cards and booster packs
|
|
||||||
|
|
||||||
3. **Card Collection Statistics**:
|
|
||||||
- Add UI for tracking collection completion
|
|
||||||
- Implement achievements for collecting sets
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
### 1. Complete the Booster Opening Experience
|
|
||||||
- Create proper card back prefab with appropriate design
|
|
||||||
- Test and refine the card reveal animations
|
|
||||||
- Ensure particle effects and sounds play correctly
|
|
||||||
|
|
||||||
### 2. Add Save/Load System
|
|
||||||
- Implement JSON serialization for CardInventory
|
|
||||||
- Add autosave triggers after obtaining new cards
|
|
||||||
- Create save/load methods in CardSystemManager
|
|
||||||
|
|
||||||
### 3. Polish the Overall Experience
|
|
||||||
- Add transition animations between pages
|
|
||||||
- Improve visual feedback for obtaining new cards
|
|
||||||
- Implement collection statistics display
|
|
||||||
|
|
||||||
### 4. Additional Features (Optional)
|
|
||||||
- Special collection bonuses for completing sets
|
|
||||||
- Card trading system with NPCs
|
|
||||||
- Card usage mechanics beyond collection
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
# Card System Integration and Testing Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document outlines the integration between the data and UI layers of the Apple Hills card collection system, as well as the testing tools available for development purposes.
|
|
||||||
|
|
||||||
## Architecture Summary
|
|
||||||
|
|
||||||
The card system follows a clean separation of concerns:
|
|
||||||
|
|
||||||
1. **Data Layer** (Assets/Scripts/Data/CardSystem/)
|
|
||||||
- `CardSystemManager`: Singleton for managing card data, booster packs, and collection
|
|
||||||
- `CardInventory`: Player's collection of cards and booster packs
|
|
||||||
- `CardData`: Runtime instance of a collected card
|
|
||||||
- `CardDefinition`: ScriptableObject template defining card properties
|
|
||||||
- `CardVisualConfig`: Visual settings for cards based on rarity/zone
|
|
||||||
|
|
||||||
2. **UI Layer** (Assets/Scripts/UI/CardSystem/)
|
|
||||||
- `CardAlbumUI`: Main controller for the card UI system
|
|
||||||
- `UIPageController`: Stack-based navigation system
|
|
||||||
- `UIPage`: Base class for UI pages with transition animations
|
|
||||||
- Page Components:
|
|
||||||
- `CardMenuPage`: Main menu for the card system
|
|
||||||
- `AlbumViewPage`: Grid display of collected cards with filtering
|
|
||||||
- `BoosterOpeningPage`: Interactive card reveal experience
|
|
||||||
- `CardUIElement`: Individual card visual representation
|
|
||||||
- `BoosterNotificationDot`: UI element showing booster pack counts
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
### Initialization Process
|
|
||||||
|
|
||||||
1. `CardSystemManager` initializes during the bootstrap process using `BootCompletionService`
|
|
||||||
2. UI components register with `BootCompletionService.RegisterInitAction()` for post-boot initialization
|
|
||||||
3. UI components access card data through `CardSystemManager.Instance`
|
|
||||||
|
|
||||||
### Data Flow
|
|
||||||
|
|
||||||
1. **Card Collection**:
|
|
||||||
- `CardSystemManager` manages the player's `CardInventory`
|
|
||||||
- Cards are added via `AddCardToInventory()`
|
|
||||||
- Card collection triggers `OnCardCollected` event
|
|
||||||
|
|
||||||
2. **Booster Packs**:
|
|
||||||
- Added via `CardSystemManager.AddBoosterPack()`
|
|
||||||
- Opened via `CardSystemManager.OpenBoosterPack()`
|
|
||||||
- Booster count changes trigger `OnBoosterCountChanged` event
|
|
||||||
- Opening boosters triggers `OnBoosterOpened` event
|
|
||||||
|
|
||||||
3. **Card Display**:
|
|
||||||
- `CardUIElement` displays a card using `SetupCard(cardData)`
|
|
||||||
- `AlbumViewPage` displays cards in a grid with filtering options
|
|
||||||
- `BoosterOpeningPage` handles interactive card reveals
|
|
||||||
|
|
||||||
### Event System
|
|
||||||
|
|
||||||
Key events that connect data and UI layers:
|
|
||||||
|
|
||||||
- `OnBoosterCountChanged`: Triggered when booster count changes
|
|
||||||
- `OnBoosterOpened`: Triggered when a booster is opened
|
|
||||||
- `OnCardCollected`: Triggered when a new card is added to collection
|
|
||||||
- `OnCardRarityUpgraded`: Triggered when duplicates upgrade a card's rarity
|
|
||||||
|
|
||||||
## Testing Tool: CardSystemTester
|
|
||||||
|
|
||||||
The `CardSystemTester` provides a simple interface for testing the card system without requiring full game integration.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Adding booster packs
|
|
||||||
- Opening the card menu
|
|
||||||
- Opening booster packs
|
|
||||||
- Browsing the album view
|
|
||||||
- Generating random test cards
|
|
||||||
- Clearing the card collection
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
1. Add `CardSystemTester` component to a GameObject in your test scene
|
|
||||||
2. Reference the `CardAlbumUI` component
|
|
||||||
3. Enter Play mode and use the inspector buttons
|
|
||||||
4. All buttons are disabled in edit mode
|
|
||||||
|
|
||||||
### Implementation Notes
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class CardSystemTester : MonoBehaviour
|
|
||||||
{
|
|
||||||
// Only need reference to CardAlbumUI, the CardSystemManager is accessed via singleton
|
|
||||||
[SerializeField] private CardAlbumUI cardAlbumUI;
|
|
||||||
|
|
||||||
// Other variables for test configuration
|
|
||||||
[SerializeField] [Range(1, 10)] private int boosterPacksToAdd = 3;
|
|
||||||
[SerializeField] [Range(1, 100)] private int cardsToGenerate = 10;
|
|
||||||
|
|
||||||
// All operations use CardSystemManager.Instance
|
|
||||||
public void AddBoosterPacks() {
|
|
||||||
CardSystemManager.Instance.AddBoosterPack(boosterPacksToAdd);
|
|
||||||
// Other operations...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uses CardAlbumUI to simulate UI interactions
|
|
||||||
public void SimulateBackpackClick() {
|
|
||||||
// Trigger the backpack button's onClick event
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional test methods...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Scene Setup
|
|
||||||
|
|
||||||
1. Create a new scene or use an existing test scene
|
|
||||||
2. Add a Canvas with proper UI configuration
|
|
||||||
3. Add the CardSystem prefab (containing CardAlbumUI)
|
|
||||||
4. Add the CardSystemTester component
|
|
||||||
5. Make sure you have card definitions configured
|
|
||||||
|
|
||||||
## Current Implementation Status
|
|
||||||
|
|
||||||
1. **Complete**:
|
|
||||||
- Core data management through `CardSystemManager`
|
|
||||||
- Card collection and inventory management
|
|
||||||
- Basic UI navigation system
|
|
||||||
- Booster pack opening flow
|
|
||||||
- Album view with filtering
|
|
||||||
|
|
||||||
2. **Added API Methods**:
|
|
||||||
- `GetAllCardDefinitions()`: Returns list of all card definitions
|
|
||||||
- `GetCardInventory()`: Returns reference to player's card inventory
|
|
||||||
- `ClearAllCards()`: Resets the player's collection
|
|
||||||
- Various utility methods for filtering and statistics
|
|
||||||
|
|
||||||
3. **UI Improvements**:
|
|
||||||
- Simplified card display in album view
|
|
||||||
- Properly integrated card reveal animations
|
|
||||||
- Back button functionality for all pages
|
|
||||||
|
|
||||||
## Known Issues and Considerations
|
|
||||||
|
|
||||||
1. **Performance**: Large collections may require optimization
|
|
||||||
2. **Save System**: Currently not implemented, needs integration with game save system
|
|
||||||
3. **Card Definition Loading**: Ensure card definitions are loaded before use
|
|
||||||
4. **Testing Workflow**: Test specific state scenarios using the CardSystemTester
|
|
||||||
5. **Animation Timing**: May need adjustment for smoother experience
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Implement save/load system for card collection
|
|
||||||
2. Add collection statistics display
|
|
||||||
3. Implement rarity-specific effects for card reveals
|
|
||||||
4. Create more comprehensive test coverage
|
|
||||||
|
|
||||||
## Technical References
|
|
||||||
|
|
||||||
- Bootstrap system: Use `BootCompletionService.RegisterInitAction(InitializePostBoot)` for initialization
|
|
||||||
- Data access: Always use `CardSystemManager.Instance` for data access
|
|
||||||
- Page navigation: Use `UIPageController.Instance.PushPage()` and `PopPage()`
|
|
||||||
- Card UI: Use `CardUIElement.SetupCard()` to configure card display
|
|
||||||
340
docs/card_system_playbook.md
Normal file
340
docs/card_system_playbook.md
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
# Apple Hills Card System – Designer Playbook
|
||||||
|
|
||||||
|
This playbook is for designers working with the rarity‑based Card System in Apple Hills. It provides a high‑level architecture overview, fast TL;DR playbooks for common tasks (with code-first snippets), and deeper guidance on how the system fits together.
|
||||||
|
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Architecture at a Glance](#architecture-at-a-glance)
|
||||||
|
- [TL;DR Playbooks (Code-First)](#tldr-playbooks-code-first)
|
||||||
|
- [Open Booster Packs](#open-booster-packs)
|
||||||
|
- [Grant a Specific Card](#grant-a-specific-card)
|
||||||
|
- [Query the Collection](#query-the-collection)
|
||||||
|
- [Configure Visuals by Code (optional)](#configure-visuals-by-code-optional)
|
||||||
|
- [Clear the Collection](#clear-the-collection)
|
||||||
|
- [Authoring with the Card Editor (No Manual Setup)](#authoring-with-the-card-editor-no-manual-setup)
|
||||||
|
- [What happens automatically (under the hood)](#what-happens-automatically-under-the-hood)
|
||||||
|
- [In-Depth Overview](#in-depth-overview)
|
||||||
|
- [Designer How‑Tos (Quick Reference)](#designer-how-tos-quick-reference)
|
||||||
|
- [Best Practices & Tips](#best-practices--tips)
|
||||||
|
- [Known Gaps](#known-gaps)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [Technical Reference (Quick)](#technical-reference-quick)
|
||||||
|
- [Change Log](#change-log)
|
||||||
|
|
||||||
|
|
||||||
|
## Architecture at a Glance
|
||||||
|
|
||||||
|
The system is split into two main layers with clear responsibilities and a lightweight event bridge.
|
||||||
|
|
||||||
|
- Data Layer (`Assets/Scripts/Data/CardSystem`)
|
||||||
|
- `CardSystemManager` (singleton)
|
||||||
|
- Source of truth for all card definitions, booster logic, and player `CardInventory`.
|
||||||
|
- Emits events on collection changes and booster activity.
|
||||||
|
- `CardInventory`
|
||||||
|
- Player’s owned cards and booster pack counters.
|
||||||
|
- Utility APIs for filtering cards (by rarity, zone, etc.).
|
||||||
|
- `CardDefinition` (`ScriptableObject`)
|
||||||
|
- Authoring asset that defines a card (name, visuals, zone, rarity tiering).
|
||||||
|
- `CardData`
|
||||||
|
- Runtime instance of a card in the player collection (ID, rarity, copies owned, links back to `CardDefinition`).
|
||||||
|
- `CardVisualConfig` (`ScriptableObject`)
|
||||||
|
- Central mapping for rarity/zone → visual treatment (colors, frames, shapes).
|
||||||
|
|
||||||
|
- UI Layer (`Assets/Scripts/UI/CardSystem`)
|
||||||
|
- `CardAlbumUI`
|
||||||
|
- Entry point for the card UI (menu, album browser, booster opening).
|
||||||
|
- `UIPageController` + `UIPage`
|
||||||
|
- Stack-based navigation (push/pop) and shared transitions.
|
||||||
|
- Page Components
|
||||||
|
- `CardMenuPage` – Card hub/menu
|
||||||
|
- `AlbumViewPage` – Grid album view with filter/sort options
|
||||||
|
- `BoosterOpeningPage` – Interactive three-card reveal
|
||||||
|
- `CardUIElement`
|
||||||
|
- Renders a single card using `CardData` and `CardVisualConfig`
|
||||||
|
- `BoosterNotificationDot`
|
||||||
|
- Shows available booster pack count
|
||||||
|
|
||||||
|
- Events (Data ↔ UI)
|
||||||
|
- `OnBoosterCountChanged`
|
||||||
|
- `OnBoosterOpened`
|
||||||
|
- `OnCardCollected`
|
||||||
|
- `OnCardRarityUpgraded`
|
||||||
|
|
||||||
|
- Lifecycle
|
||||||
|
- Initialize during boot: `BootCompletionService.RegisterInitAction(...)`
|
||||||
|
- `CardSystemManager.Instance` is the access point from UI and tools (e.g., `CardSystemTester`)
|
||||||
|
|
||||||
|
|
||||||
|
## TL;DR Playbooks (Code-First)
|
||||||
|
|
||||||
|
These are the most common tasks you’ll perform in code. Paste the snippets into a temporary test `MonoBehaviour`, a unit test, or hook them into your gameplay systems.
|
||||||
|
|
||||||
|
### Open Booster Packs
|
||||||
|
```csharp
|
||||||
|
using AppleHills.Data.CardSystem;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class BoosterOpenerSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
// Subscribe to events (optional, for feedback/UI updates)
|
||||||
|
CardSystemManager.Instance.OnBoosterOpened += cards =>
|
||||||
|
{
|
||||||
|
Debug.Log($"Opened booster with {cards.Count} cards");
|
||||||
|
};
|
||||||
|
CardSystemManager.Instance.OnCardCollected += card =>
|
||||||
|
{
|
||||||
|
Debug.Log($"Collected: {card.Definition.Name} (rarity: {card.Rarity})");
|
||||||
|
};
|
||||||
|
CardSystemManager.Instance.OnCardRarityUpgraded += card =>
|
||||||
|
{
|
||||||
|
Debug.Log($"Upgraded rarity: {card.Definition.Name} -> {card.Rarity}");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Give the player some boosters and open one
|
||||||
|
CardSystemManager.Instance.AddBoosterPack(1);
|
||||||
|
var revealed = CardSystemManager.Instance.OpenBoosterPack();
|
||||||
|
foreach (var cd in revealed)
|
||||||
|
{
|
||||||
|
Debug.Log($"Revealed: {cd.Definition.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grant a Specific Card
|
||||||
|
```csharp
|
||||||
|
using System.Linq;
|
||||||
|
using AppleHills.Data.CardSystem;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class GrantCardSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private string cardName = "Apple Picker"; // example
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
var def = CardSystemManager.Instance
|
||||||
|
.GetAllCardDefinitions()
|
||||||
|
.FirstOrDefault(d => d.Name == cardName);
|
||||||
|
if (def == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"CardDefinition not found: {cardName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cardData = CardSystemManager.Instance.AddCardToInventory(def);
|
||||||
|
Debug.Log($"Granted: {cardData.Definition.Name} (copies: {cardData.CopiesOwned})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query the Collection
|
||||||
|
```csharp
|
||||||
|
using AppleHills.Data.CardSystem;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class QueryCollectionSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
var inv = CardSystemManager.Instance.GetCardInventory();
|
||||||
|
|
||||||
|
// All cards
|
||||||
|
var all = inv.GetAllCards();
|
||||||
|
Debug.Log($"Total cards owned: {all.Count}");
|
||||||
|
|
||||||
|
// By rarity (example)
|
||||||
|
var rares = inv.GetByRarity(CardRarity.Rare);
|
||||||
|
Debug.Log($"Rares: {rares.Count}");
|
||||||
|
|
||||||
|
// By zone (example)
|
||||||
|
var forest = inv.GetByZone(CardZone.Forest);
|
||||||
|
Debug.Log($"Forest: {forest.Count}");
|
||||||
|
|
||||||
|
// Counts (progression checks)
|
||||||
|
var byRarity = inv.CountByRarity();
|
||||||
|
var byZone = inv.CountByZone();
|
||||||
|
// Iterate dictionaries as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clear the Collection
|
||||||
|
```csharp
|
||||||
|
using AppleHills.Data.CardSystem;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class ClearCollectionSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
CardSystemManager.Instance.ClearAllCards();
|
||||||
|
Debug.Log("Card collection cleared (dev/test only).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Authoring with the Card Editor (No Manual Setup)
|
||||||
|
Designers should use the dedicated editor window: `AppleHills/Card Editor`.
|
||||||
|
|
||||||
|
- Open via Unity menu: `AppleHills/Card Editor` (menu path defined in `CardEditorWindow`)
|
||||||
|
- The window provides a card list, detail editor, and a live preview of the UI card.
|
||||||
|
- You don’t need to create folders or assets manually—the tool handles setup and discovery.
|
||||||
|
|
||||||
|
### What happens automatically (under the hood)
|
||||||
|
The `Assets/Editor/CardSystem/CardEditorWindow.cs` implements the following behaviors:
|
||||||
|
- Ensures the data directory exists: `Assets/Data/Cards` (creates it on first run).
|
||||||
|
- Discovers all `CardDefinition` assets under `Assets/Data/Cards` automatically via `AssetDatabase.FindAssets("t:CardDefinition")` and keeps the list sorted by `Name`.
|
||||||
|
- Creates new card assets as `Card_<Name>.asset` inside `Assets/Data/Cards`.
|
||||||
|
- Loads a UI preview prefab from `Assets/Prefabs/UI/Cards/SIngleCardDisplayUI.prefab` to render a live card preview inside the editor using `PreviewRenderUtility`.
|
||||||
|
- Tries to load `CardVisualConfig` at `Assets/Data/Cards/CardVisualConfig.asset` for consistent rarity/zone styling in the preview.
|
||||||
|
- Responds to Undo/Redo and Editor recompiles to stay in sync.
|
||||||
|
- When you duplicate or delete cards from the window, the corresponding assets are created/removed accordingly.
|
||||||
|
|
||||||
|
Result: Designers focus on content (name, description, zone, art, etc.) in the Card Editor. The system takes care of folders, discovery, previewing, and syncing definitions with runtime.
|
||||||
|
|
||||||
|
|
||||||
|
## In-Depth Overview
|
||||||
|
|
||||||
|
Data Layer Details
|
||||||
|
- `CardDefinition` (authoring)
|
||||||
|
- What it is: a `ScriptableObject` that defines a card’s identity and static presentation (name, description, zone, default visuals).
|
||||||
|
- Typical fields: `Name`, `Description`, `Zone`, rarity metadata, sprites/textures, optional tags.
|
||||||
|
- Helpers: `CreateCardData()` to generate a runtime `CardData` instance for inventory.
|
||||||
|
|
||||||
|
- `CardData` (runtime instance)
|
||||||
|
- Purpose: Represents what the player owns – unique ID, rarity state, copies owned.
|
||||||
|
- Behavior: When duplicates are collected, `CardData` may upgrade rarity (e.g., duplicate thresholds).
|
||||||
|
- Links: Holds reference to its `CardDefinition` for display and classification.
|
||||||
|
|
||||||
|
- `CardInventory` (player collection)
|
||||||
|
- Responsibilities: Store cards, track counts, support filters/lookups (by rarity/zone), manage booster counters.
|
||||||
|
- Common operations: `AddCard(def)`, `GetAllCards()`, `GetByRarity()`, `GetByZone()`, `CountByRarity()`, `CountByZone()`.
|
||||||
|
- Notes: Designed for efficiency and clarity; returns collections for the UI to display.
|
||||||
|
|
||||||
|
- `CardSystemManager` (singleton coordinator)
|
||||||
|
- Responsibilities: Owns `CardInventory`, holds the card definition catalog, controls booster generation/opening, exposes public APIs.
|
||||||
|
- Events: `OnBoosterCountChanged`, `OnBoosterOpened`, `OnCardCollected`, `OnCardRarityUpgraded`.
|
||||||
|
- Typical APIs:
|
||||||
|
- `GetAllCardDefinitions()` → `IReadOnlyList<CardDefinition>`
|
||||||
|
- `GetCardInventory()` → `CardInventory`
|
||||||
|
- `AddBoosterPack(int count)`
|
||||||
|
- `OpenBoosterPack()` → `List<CardData>` (or similar)
|
||||||
|
- `AddCardToInventory(CardDefinition def)`
|
||||||
|
- `ClearAllCards()`
|
||||||
|
|
||||||
|
- `CardVisualConfig` (visual mappings)
|
||||||
|
- Purpose: Central place to establish the brand/look per rarity and zone.
|
||||||
|
- Implementation: `ScriptableObject` with dictionaries for quick lookups by rarity/zone.
|
||||||
|
|
||||||
|
UI Layer Details
|
||||||
|
- `UIPageController` + `UIPage`
|
||||||
|
- Navigation: `PushPage(page)`, `PopPage()`, `Clear()`; optional transition animations.
|
||||||
|
- Pattern: Each page is a `UIPage` subclass registered under `CardAlbumUI`.
|
||||||
|
|
||||||
|
- `CardAlbumUI` (system entry)
|
||||||
|
- Role: Wires pages together, responds to `CardSystemManager` events, routes to `AlbumViewPage` or `BoosterOpeningPage`.
|
||||||
|
|
||||||
|
- `AlbumViewPage`
|
||||||
|
- Role: Displays a grid of `CardUIElement` items from `CardInventory` with filtering/sorting UI.
|
||||||
|
- Notes: Uses TextMeshPro components and simplified presentation (no stack/slot system).
|
||||||
|
|
||||||
|
- `BoosterOpeningPage`
|
||||||
|
- Role: Handles the interactive reveal flow for 3 cards per booster; flip animations, particle FX, rarity feedback.
|
||||||
|
- Notes: Timings and art are still under refinement; supports individual reveal clicks.
|
||||||
|
|
||||||
|
- `CardUIElement`
|
||||||
|
- Role: Displays a single `CardData` – name, art, frame, rarity chips, zone styling.
|
||||||
|
- Data Source: Uses `CardVisualConfig` for color/frame mapping.
|
||||||
|
|
||||||
|
- `BoosterNotificationDot`
|
||||||
|
- Role: Indicates current booster count; listens to `OnBoosterCountChanged`.
|
||||||
|
|
||||||
|
Event Flow Examples
|
||||||
|
- Opening a booster
|
||||||
|
- Player code calls `CardSystemManager.OpenBoosterPack()` → generates/awards 3 cards → raises `OnBoosterOpened`.
|
||||||
|
- Inventory updated; for each card, `OnCardCollected` (and maybe `OnCardRarityUpgraded`) fires.
|
||||||
|
- `BoosterOpeningPage` animates reveals; `AlbumViewPage` updates when returning.
|
||||||
|
|
||||||
|
- Adding a card via quest reward
|
||||||
|
- Gameplay script calls `AddCardToInventory(def)`.
|
||||||
|
- Inventory updates; events fire to update UI.
|
||||||
|
|
||||||
|
Rarity & Duplicates
|
||||||
|
- The system tracks copies owned per card.
|
||||||
|
- Duplicates can trigger rarity upgrades based on thresholds (implemented in `CardData`).
|
||||||
|
- UI can react with special effects via `OnCardRarityUpgraded` (e.g., distinct VFX/SFX per rarity).
|
||||||
|
|
||||||
|
|
||||||
|
## Designer How‑Tos (Quick Reference)
|
||||||
|
|
||||||
|
Author a New Card (use the editor tool)
|
||||||
|
1) Open `AppleHills/Card Editor`.
|
||||||
|
2) Click New/Duplicate, edit `Name`, `Description`, `Zone`, and assign art.
|
||||||
|
3) Save—asset is created as `Assets/Data/Cards/Card_<Name>.asset`. It’s auto-discovered by runtime.
|
||||||
|
|
||||||
|
Grant a Card by Code (quest reward)
|
||||||
|
```csharp
|
||||||
|
CardSystemManager.Instance.AddCardToInventory(rewardDefinition);
|
||||||
|
```
|
||||||
|
|
||||||
|
Open a Booster by Code
|
||||||
|
```csharp
|
||||||
|
CardSystemManager.Instance.AddBoosterPack(1);
|
||||||
|
CardSystemManager.Instance.OpenBoosterPack();
|
||||||
|
```
|
||||||
|
|
||||||
|
Filter Cards for a Page
|
||||||
|
```csharp
|
||||||
|
var inv = CardSystemManager.Instance.GetCardInventory();
|
||||||
|
var list = inv.GetByZone(CardZone.Quarry);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Best Practices & Tips
|
||||||
|
|
||||||
|
- Always access data via `CardSystemManager.Instance` to keep a single source of truth.
|
||||||
|
- Keep `CardDefinition` assets lightweight and consistent; use tags/zones for filtering.
|
||||||
|
- When adding new rarities/zones, update `CardVisualConfig` and verify UI styling.
|
||||||
|
- For smoother reveals, coordinate with VFX/SFX to differentiate rarities.
|
||||||
|
- Large collections: consider paging or virtualized grids if performance becomes a concern.
|
||||||
|
|
||||||
|
|
||||||
|
## Known Gaps
|
||||||
|
|
||||||
|
- Save/Load not implemented – test sessions won’t persist player collection.
|
||||||
|
- Booster Opening polish (timings/art/audio) is ongoing.
|
||||||
|
- Collection statistics/achievements UI pending.
|
||||||
|
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
- Q: My new `CardDefinition` doesn’t show up in the album.
|
||||||
|
- A: Ensure the asset is in `Assets/Data/Cards` (the editor tool places it there) and that definitions load before UI opens. Check the `CardSystemManager` discovery and your zones/rarities.
|
||||||
|
|
||||||
|
- Q: How do I trigger the card UI from another part of the game?
|
||||||
|
- A: Use `CardAlbumUI`’s public entry points or route through your UI flow to push the relevant page via `UIPageController`.
|
||||||
|
|
||||||
|
- Q: Can I guarantee a specific rarity in a booster for a tutorial?
|
||||||
|
- A: Add a temporary tutorial hook in `CardSystemManager` to override booster generation rules for guided moments, or directly grant specific `CardDefinition`s for the tutorial step.
|
||||||
|
|
||||||
|
- Q: How do duplicates upgrade rarity?
|
||||||
|
- A: The logic lives in `CardData`; when copies cross thresholds, it fires `OnCardRarityUpgraded`. Coordinate with design to set thresholds and with UI to show feedback.
|
||||||
|
|
||||||
|
|
||||||
|
## Technical Reference (Quick)
|
||||||
|
|
||||||
|
- Initialize after boot: `BootCompletionService.RegisterInitAction(InitializePostBoot)`
|
||||||
|
- Data access: `CardSystemManager.Instance`
|
||||||
|
- Navigation: `UIPageController.Instance.PushPage()` / `PopPage()`
|
||||||
|
- Show a card in UI: `CardUIElement.SetupCard(cardData)`
|
||||||
|
- Test harness: `CardSystemTester` (add to scene, link `CardAlbumUI`)
|
||||||
|
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
- v1.1: Added Table of Contents, inline code formatting, code-first TL;DR playbooks, and an authoring section based on `CardEditorWindow`.
|
||||||
|
- v1.0: Initial designer playbook, aligned with Implementation Plan and Integration & Testing Guide.
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
# Card System UI Setup Guide
|
|
||||||
|
|
||||||
This guide provides detailed instructions for setting up the Card System UI in Unity, organized into prefabs that can be easily maintained and modified.
|
|
||||||
|
|
||||||
## Prefab Structure Overview
|
|
||||||
|
|
||||||
The card system UI should be structured with a modular prefab approach:
|
|
||||||
|
|
||||||
1. **Main Card System Prefab** - Contains the CardAlbumUI component and core structure
|
|
||||||
2. **Individual UI Page Prefabs** - Each page as its own prefab
|
|
||||||
3. **Card UI Prefab** - For card display and interaction
|
|
||||||
|
|
||||||
## Detailed Prefab Structure
|
|
||||||
|
|
||||||
### Main CardSystem Prefab
|
|
||||||
|
|
||||||
```
|
|
||||||
CardSystem (with CardAlbumUI component)
|
|
||||||
├── BackpackIcon (GameObject)
|
|
||||||
│ ├── BackpackImage (Image)
|
|
||||||
│ ├── BoosterCountText (TextMeshProUGUI)
|
|
||||||
│ └── NotificationDot (GameObject with Image)
|
|
||||||
├── UIPageController (automatically added by CardAlbumUI if not present)
|
|
||||||
└── Notifications (GameObject)
|
|
||||||
└── NewCardNotification (GameObject)
|
|
||||||
├── Background (Image)
|
|
||||||
└── Text (TextMeshProUGUI)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Page Prefabs
|
|
||||||
|
|
||||||
**1. MainMenuPage Prefab:**
|
|
||||||
```
|
|
||||||
MainMenuPage (with CardMenuPage component & CanvasGroup)
|
|
||||||
├── Background (Image)
|
|
||||||
├── Title (TextMeshProUGUI)
|
|
||||||
├── OpenBoosterButton (Button)
|
|
||||||
│ └── Text (TextMeshProUGUI)
|
|
||||||
├── ViewAlbumButton (Button)
|
|
||||||
│ └── Text (TextMeshProUGUI)
|
|
||||||
├── ChangeClothesButton (Button)
|
|
||||||
│ └── Text (TextMeshProUGUI)
|
|
||||||
└── NoBoostersMessage (GameObject)
|
|
||||||
└── Text (TextMeshProUGUI)
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. AlbumViewPage Prefab:**
|
|
||||||
```
|
|
||||||
AlbumViewPage (with AlbumViewPage component & CanvasGroup)
|
|
||||||
├── Background (Image)
|
|
||||||
├── FilterPanel (GameObject)
|
|
||||||
│ ├── ZoneFilterDropdown (TMP_Dropdown)
|
|
||||||
│ │ ├── Label (TextMeshProUGUI)
|
|
||||||
│ │ └── Arrow (Image)
|
|
||||||
│ ├── RarityFilterDropdown (TMP_Dropdown)
|
|
||||||
│ │ ├── Label (TextMeshProUGUI)
|
|
||||||
│ │ └── Arrow (Image)
|
|
||||||
│ └── ResetFiltersButton (Button)
|
|
||||||
│ └── Text (TextMeshProUGUI)
|
|
||||||
├── AlbumScrollView (ScrollRect)
|
|
||||||
│ └── AlbumGrid (GridLayoutGroup)
|
|
||||||
```
|
|
||||||
|
|
||||||
**3. BoosterOpeningPage Prefab:**
|
|
||||||
```
|
|
||||||
BoosterOpeningPage (with BoosterOpeningPage component & CanvasGroup)
|
|
||||||
├── Background (Image)
|
|
||||||
├── BoosterPackObject (GameObject)
|
|
||||||
│ └── BoosterImage (Image)
|
|
||||||
├── CardRevealContainer (RectTransform)
|
|
||||||
│ ├── CardRevealPosition1 (Transform)
|
|
||||||
│ ├── CardRevealPosition2 (Transform)
|
|
||||||
│ └── CardRevealPosition3 (Transform)
|
|
||||||
├── OpenBoosterButton (Button)
|
|
||||||
│ └── Text (TextMeshProUGUI)
|
|
||||||
├── ContinueButton (Button)
|
|
||||||
│ └── Text (TextMeshProUGUI)
|
|
||||||
├── CardRevealSound (AudioSource)
|
|
||||||
├── RareCardSound (AudioSource)
|
|
||||||
├── CardRevealParticles (ParticleSystem)
|
|
||||||
└── RareCardParticles (ParticleSystem)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Card-Related Prefabs
|
|
||||||
|
|
||||||
**1. CardPrefab:**
|
|
||||||
```
|
|
||||||
Card (with CardUIElement component & Button)
|
|
||||||
├── CardBackground (Image)
|
|
||||||
├── CardFrame (Image)
|
|
||||||
├── CardImage (Image)
|
|
||||||
├── CardNameText (TextMeshProUGUI)
|
|
||||||
├── CardDescriptionText (TextMeshProUGUI)
|
|
||||||
├── ZoneLabel (TextMeshProUGUI)
|
|
||||||
└── RarityIndicator (Image)
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. CardBackPrefab:**
|
|
||||||
```
|
|
||||||
CardBack (with Button component)
|
|
||||||
├── CardBackImage (Image)
|
|
||||||
└── BackDesign (Image)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Component Setup Guide
|
|
||||||
|
|
||||||
### CardAlbumUI Component Setup
|
|
||||||
|
|
||||||
This is the central controller for the card system:
|
|
||||||
|
|
||||||
```
|
|
||||||
CardAlbumUI Component Inspector:
|
|
||||||
- UI References:
|
|
||||||
- Backpack Icon: [BackpackIcon GameObject]
|
|
||||||
- Main Menu Page: [MainMenuPage Prefab]
|
|
||||||
- Album View Page: [AlbumViewPage Prefab]
|
|
||||||
- Booster Opening Page: [BoosterOpeningPage Prefab]
|
|
||||||
- UI Elements:
|
|
||||||
- Backpack Button: [BackpackIcon Button Component]
|
|
||||||
- Booster Count Text: [BoosterCountText Component]
|
|
||||||
- Notification Dot: [NotificationDot GameObject]
|
|
||||||
- Backpack Animation Target: [BackpackIcon Transform]
|
|
||||||
- New Card Notification: [NewCardNotification GameObject]
|
|
||||||
- Notification Settings:
|
|
||||||
- Notification Duration: 3 seconds (adjustable)
|
|
||||||
- Notification Sound: [AudioSource Component]
|
|
||||||
```
|
|
||||||
|
|
||||||
### CardMenuPage Component Setup
|
|
||||||
|
|
||||||
```
|
|
||||||
CardMenuPage Component Inspector:
|
|
||||||
- Menu Options:
|
|
||||||
- Open Booster Button: [OpenBoosterButton]
|
|
||||||
- View Album Button: [ViewAlbumButton]
|
|
||||||
- Change Clothes Button: [ChangeClothesButton]
|
|
||||||
- UI Elements:
|
|
||||||
- Booster Count Text: [BoosterCountText]
|
|
||||||
- No Boosters Message: [NoBoostersMessage]
|
|
||||||
- Canvas Group: [CanvasGroup on this GameObject]
|
|
||||||
```
|
|
||||||
|
|
||||||
### AlbumViewPage Component Setup
|
|
||||||
|
|
||||||
```
|
|
||||||
AlbumViewPage Component Inspector:
|
|
||||||
- Album UI Elements:
|
|
||||||
- Album Grid: [AlbumGrid Component]
|
|
||||||
- Card Prefab: [CardPrefab Asset]
|
|
||||||
- Filter UI:
|
|
||||||
- Zone Filter Dropdown: [ZoneFilterDropdown]
|
|
||||||
- Rarity Filter Dropdown: [RarityFilterDropdown]
|
|
||||||
- Reset Filters Button: [ResetFiltersButton]
|
|
||||||
- Canvas Group: [CanvasGroup on this GameObject]
|
|
||||||
```
|
|
||||||
|
|
||||||
### BoosterOpeningPage Component Setup
|
|
||||||
|
|
||||||
```
|
|
||||||
BoosterOpeningPage Component Inspector:
|
|
||||||
- UI Elements:
|
|
||||||
- Booster Pack Object: [BoosterPackObject]
|
|
||||||
- Card Reveal Container: [CardRevealContainer]
|
|
||||||
- Card Reveal Positions: [Array of 3 Transform positions]
|
|
||||||
- Card Prefab: [CardPrefab Asset]
|
|
||||||
- Card Back Prefab: [CardBackPrefab Asset]
|
|
||||||
- Open Booster Button: [OpenBoosterButton]
|
|
||||||
- Continue Button: [ContinueButton]
|
|
||||||
- Canvas Group: [CanvasGroup on this GameObject]
|
|
||||||
- Open Booster Text: [TextMeshProUGUI]
|
|
||||||
- Continue Text: [TextMeshProUGUI]
|
|
||||||
- Animation Settings:
|
|
||||||
- Card Reveal Delay: 0.3 (adjustable)
|
|
||||||
- Card Move To Backpack Delay: 0.8 (adjustable)
|
|
||||||
- Flip Animation Duration: 0.5 (adjustable)
|
|
||||||
- Backpack Target Transform: [BackpackIcon Transform]
|
|
||||||
- Card Reveal Sound: [CardRevealSound Component]
|
|
||||||
- Rare Card Sound: [RareCardSound Component]
|
|
||||||
- Card Reveal Particles: [CardRevealParticles Component]
|
|
||||||
- Rare Card Particles: [RareCardParticles Component]
|
|
||||||
```
|
|
||||||
|
|
||||||
### CardUIElement Component Setup
|
|
||||||
|
|
||||||
```
|
|
||||||
CardUIElement Component Inspector:
|
|
||||||
- Card UI Elements:
|
|
||||||
- Card Background Image: [CardBackground Image]
|
|
||||||
- Card Frame Image: [CardFrame Image]
|
|
||||||
- Card Image: [CardImage]
|
|
||||||
- Card Name Text: [CardNameText]
|
|
||||||
- Card Description Text: [CardDescriptionText]
|
|
||||||
- Zone Label: [ZoneLabel Text]
|
|
||||||
- Rarity Label: [RarityIndicator Image]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prefab Creation Process
|
|
||||||
|
|
||||||
### 1. Create Card-Related Prefabs First
|
|
||||||
|
|
||||||
Start by creating the card prefabs since other prefabs will reference them:
|
|
||||||
|
|
||||||
1. **Create CardPrefab**:
|
|
||||||
- Create GameObject → UI → Image (for background)
|
|
||||||
- Add child Images for frame and card art
|
|
||||||
- Add TextMeshPro components for name, description, zone
|
|
||||||
- Add CardUIElement script
|
|
||||||
- Configure Inspector references
|
|
||||||
- Add Button component
|
|
||||||
- Save as prefab
|
|
||||||
|
|
||||||
2. **Create CardBackPrefab**:
|
|
||||||
- Create GameObject → UI → Image (for card back)
|
|
||||||
- Add Button component for interaction
|
|
||||||
- Style appropriately to match your game's aesthetics
|
|
||||||
- Save as prefab
|
|
||||||
|
|
||||||
### 2. Create UI Page Prefabs
|
|
||||||
|
|
||||||
Create each page prefab individually:
|
|
||||||
|
|
||||||
1. **Create MainMenuPage Prefab**:
|
|
||||||
- Set up UI elements as shown in structure
|
|
||||||
- Add CardMenuPage script
|
|
||||||
- Add CanvasGroup component
|
|
||||||
- Configure all Inspector references
|
|
||||||
- Save as prefab
|
|
||||||
|
|
||||||
2. **Create AlbumViewPage Prefab**:
|
|
||||||
- Set up UI elements as shown in structure
|
|
||||||
- Add AlbumViewPage script
|
|
||||||
- Add CanvasGroup component
|
|
||||||
- Configure all Inspector references
|
|
||||||
- Assign CardPrefab
|
|
||||||
- Save as prefab
|
|
||||||
|
|
||||||
3. **Create BoosterOpeningPage Prefab**:
|
|
||||||
- Set up UI elements as shown in structure
|
|
||||||
- Create CardRevealContainer with three child positions
|
|
||||||
- Add BoosterOpeningPage script
|
|
||||||
- Add CanvasGroup component
|
|
||||||
- Add audio sources and particle systems
|
|
||||||
- Configure all Inspector references
|
|
||||||
- Assign CardPrefab and CardBackPrefab
|
|
||||||
- Save as prefab
|
|
||||||
|
|
||||||
### 3. Create Main CardSystem Prefab
|
|
||||||
|
|
||||||
Finally, create the main controller prefab:
|
|
||||||
|
|
||||||
1. **Create CardSystem GameObject**:
|
|
||||||
- Add CardAlbumUI script
|
|
||||||
- Set up BackpackIcon and Notifications as shown in structure
|
|
||||||
- Assign references to page prefabs (not instances)
|
|
||||||
- Configure all Inspector references
|
|
||||||
- Save as prefab
|
|
||||||
|
|
||||||
## Scene Setup Instructions
|
|
||||||
|
|
||||||
### 1. Add CardSystem to Your Scene
|
|
||||||
|
|
||||||
1. Drag the CardSystem prefab into your scene
|
|
||||||
2. Make sure it's on the UI layer
|
|
||||||
3. Verify it has a Canvas component if needed (or is under a Canvas)
|
|
||||||
|
|
||||||
### 2. Configure the CardSystemManager
|
|
||||||
|
|
||||||
1. Create a GameObject named "CardSystemManager" if not already present
|
|
||||||
2. Add the CardSystemManager script
|
|
||||||
3. Populate it with some test card definitions
|
|
||||||
4. Ensure it's marked as DontDestroyOnLoad if needed
|
|
||||||
|
|
||||||
### 3. Test the System
|
|
||||||
|
|
||||||
1. Enter Play mode
|
|
||||||
2. Test clicking the backpack icon
|
|
||||||
3. Add test booster packs: `CardSystemManager.Instance.AddBoosterPack(3)`
|
|
||||||
4. Test opening boosters and viewing the album
|
|
||||||
|
|
||||||
## Additional Tips
|
|
||||||
|
|
||||||
1. **Canvas Settings**:
|
|
||||||
- Set your Canvas to Screen Space - Overlay
|
|
||||||
- Use Scale With Screen Size with reference resolution 1920x1080
|
|
||||||
- Configure the Canvas Scaler for proper UI scaling
|
|
||||||
|
|
||||||
2. **UI Navigation**:
|
|
||||||
- Configure button navigation for keyboard/controller support
|
|
||||||
- Set tab order for accessibility
|
|
||||||
|
|
||||||
3. **Audio Setup**:
|
|
||||||
- Keep audio sources on the respective UI elements
|
|
||||||
- Adjust spatial blend to 0 (2D) for UI sounds
|
|
||||||
- Use appropriate volume for notification sounds
|
|
||||||
|
|
||||||
4. **Animation Considerations**:
|
|
||||||
- All animations use Pixelplacement.Tween
|
|
||||||
- Adjust animation durations in Inspector for fine-tuning
|
|
||||||
- Consider creating animation presets for consistent feel
|
|
||||||
|
|
||||||
5. **Testing on Different Devices**:
|
|
||||||
- Test the UI on different aspect ratios
|
|
||||||
- Ensure UI elements are anchored properly for different resolutions
|
|
||||||
- Use the Device Simulator to test mobile layouts if targeting mobile
|
|
||||||
|
|
||||||
6. **Performance Tips**:
|
|
||||||
- Consider object pooling for cards in large collections
|
|
||||||
- Disable raycast targets on non-interactive elements
|
|
||||||
- Use sprite atlases for card images
|
|
||||||
@@ -2,10 +2,73 @@
|
|||||||
|
|
||||||
This document provides an overview of the dialogue system used in Apple Hills, intended primarily for designers (Damian) working with the dialogue creation tools.
|
This document provides an overview of the dialogue system used in Apple Hills, intended primarily for designers (Damian) working with the dialogue creation tools.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Architecture at a Glance](#architecture-at-a-glance)
|
||||||
|
- [Quick Start (Code-First)](#quick-start-code-first)
|
||||||
|
- [QuickStart Guide (Editor-First)](#quickstart-guide)
|
||||||
|
- [Dialogue Structure](#dialogue-structure)
|
||||||
|
- [Node Types](#node-types)
|
||||||
|
- [Dialogue Editor](#dialogue-editor)
|
||||||
|
- [Designer Tips](#designer-tips)
|
||||||
|
- [Technical Details](#technical-details)
|
||||||
|
- [Paths & Namespaces](#paths--namespaces)
|
||||||
|
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||||
|
- [Change Log](#change-log)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The Apple Hills dialogue system is a node-based dialogue management system that allows for interactive conversations with NPCs. The system currently supports linear, condition-guarded dialogue paths that can respond to various game conditions such as puzzle completion, item pickups, and item combinations. While the architecture was designed to facilitate branching dialogue in future extensions, the current implementation follows a linear progression through nodes.
|
The Apple Hills dialogue system is a node-based dialogue management system that allows for interactive conversations with NPCs. The system currently supports linear, condition-guarded dialogue paths that can respond to various game conditions such as puzzle completion, item pickups, and item combinations. While the architecture was designed to facilitate branching dialogue in future extensions, the current implementation follows a linear progression through nodes.
|
||||||
|
|
||||||
|
## Architecture at a Glance
|
||||||
|
- Authoring Asset: `RuntimeDialogueGraph` (`ScriptableObject`) defines speaker name, entry node (`entryNodeID`), and all nodes (`allNodes`). Each `RuntimeDialogueNode` has a `nodeType`, `nextNodeID`, and content fields (`dialogueContent` for text/images and legacy `dialogueLines`).
|
||||||
|
- Runtime Driver: `DialogueComponent` sits on an NPC/prop, subscribes to gameplay events (puzzle steps, item pickups/slots/combinations), and advances the graph. Exposes `OnDialogueChanged`, `IsActive`, `IsCompleted`, and helpers like `StartDialogue()`, `HasAnyLines()`, `GetCurrentDialogueLine()`, `SetDialogueGraph(...)`.
|
||||||
|
- Presentation: `SpeechBubble` renders text or images with instant/typewriter modes and manages a prompt icon when dialogue is available.
|
||||||
|
- External Systems: `PuzzleManager` and `ItemManager` raise events that automatically progress `WaitOn*` nodes.
|
||||||
|
|
||||||
|
## Quick Start (Code-First)
|
||||||
|
|
||||||
|
### Minimal bootstrap in code
|
||||||
|
```csharp
|
||||||
|
using Dialogue;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class DialogueBootstrap : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private DialogueComponent npcDialogue;
|
||||||
|
[SerializeField] private RuntimeDialogueGraph graphAsset; // assign via Inspector
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
// 1) Assign a graph at runtime (optional; can also be preassigned in Inspector)
|
||||||
|
npcDialogue.SetDialogueGraph(graphAsset);
|
||||||
|
|
||||||
|
// 2) Optionally subscribe to changes (for analytics/UI hooks)
|
||||||
|
npcDialogue.OnDialogueChanged += line => Debug.Log($"[Dialogue] {npcDialogue.CurrentSpeakerName}: {line}");
|
||||||
|
|
||||||
|
// 3) Start the dialogue flow programmatically (optional)
|
||||||
|
npcDialogue.StartDialogue();
|
||||||
|
|
||||||
|
// Speech bubble prompt visibility is managed automatically via HasAnyLines()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
npcDialogue.OnDialogueChanged -= null; // Clean up if you subscribed with a lambda, store a delegate instead in real code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trigger an interaction by code (bypass Interactable)
|
||||||
|
The component usually advances when the player arrives and interacts (via `Interactable`). If you need to force-show the next line (e.g., a cutscene), you can invoke the same display pathway by starting the dialogue and letting the component manage advancement when it next receives input. For custom flows, prefer wiring your input to call `StartDialogue()` initially and rely on the built-in progression.
|
||||||
|
|
||||||
|
### Switch graphs at runtime
|
||||||
|
```csharp
|
||||||
|
// Swap to a new conversation (e.g., after quest completes)
|
||||||
|
npcDialogue.SetDialogueGraph(newGraph);
|
||||||
|
npcDialogue.StartDialogue();
|
||||||
|
```
|
||||||
|
|
||||||
## Dialogue Structure
|
## Dialogue Structure
|
||||||
|
|
||||||
The dialogue system uses a graph-based structure with different types of nodes that control the flow of conversation. Each dialogue is represented as a graph containing multiple nodes connected through defined paths.
|
The dialogue system uses a graph-based structure with different types of nodes that control the flow of conversation. Each dialogue is represented as a graph containing multiple nodes connected through defined paths.
|
||||||
@@ -14,21 +77,20 @@ The dialogue system uses a graph-based structure with different types of nodes t
|
|||||||
|
|
||||||
### Core Components
|
### Core Components
|
||||||
|
|
||||||
1. **RuntimeDialogueGraph**: The container for all dialogue nodes that make up a conversation
|
1. `RuntimeDialogueGraph`: Container for dialogue nodes that make up a conversation
|
||||||
- Defined in `Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs`
|
- File: `Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs`
|
||||||
- Contains the entry point node ID and a list of all dialogue nodes
|
- Fields: `entryNodeID`, `speakerName`, `allNodes`
|
||||||
- Holds the speaker name that appears in dialogue UI
|
- Helper: `GetNodeByID(string id)`
|
||||||
|
|
||||||
2. **DialogueComponent**: Attached to game objects to manage dialogue state and progression
|
2. `DialogueComponent`: Manages dialogue state and progression on an NPC/prop
|
||||||
- Defined in `Assets/Scripts/Dialogue/DialogueComponent.cs`
|
- File: `Assets/Scripts/Dialogue/DialogueComponent.cs`
|
||||||
- Manages the current state of dialogue (active node, line index)
|
- Tracks current node/line; responds to puzzle/item events
|
||||||
- Responds to game events like puzzle completion, item pickup, etc.
|
- Public: `StartDialogue()`, `GetCurrentDialogueLine()`, `HasAnyLines()`, `SetDialogueGraph(RuntimeDialogueGraph)`
|
||||||
- Controls dialogue advancement through player interaction
|
- Events/Props: `OnDialogueChanged`, `IsActive`, `IsCompleted`, `CurrentSpeakerName`
|
||||||
|
|
||||||
3. **SpeechBubble**: Handles the visual presentation of dialogue text
|
3. `SpeechBubble`: Visual presentation of dialogue text/images and prompt
|
||||||
- Defined in `Assets/Scripts/Dialogue/SpeechBubble.cs`
|
- File: `Assets/Scripts/Dialogue/SpeechBubble.cs`
|
||||||
- Manages the dialogue UI elements and text display
|
- Public: `Show()`, `Hide()`, `Toggle()`, `SetText(string)`, `DisplayDialogueLine(string,bool)`, `DisplayDialogueContent(DialogueContent,bool)`, `UpdatePromptVisibility(bool)`, `SetDisplayMode(TextDisplayMode)`, `SkipTypewriter()`, `SetTypewriterSpeed(float)`
|
||||||
- Implements typewriter effects and visual prompts
|
|
||||||
|
|
||||||
## QuickStart Guide
|
## QuickStart Guide
|
||||||
|
|
||||||
@@ -275,3 +337,35 @@ The dialogue system integrates with several other game systems:
|
|||||||
3. Assign the graph to a DialogueComponent on an NPC GameObject
|
3. Assign the graph to a DialogueComponent on an NPC GameObject
|
||||||
4. Ensure a SpeechBubble component is available (as a child object or referenced)
|
4. Ensure a SpeechBubble component is available (as a child object or referenced)
|
||||||
5. Set up any necessary puzzle steps, items, or slots that the dialogue will reference
|
5. Set up any necessary puzzle steps, items, or slots that the dialogue will reference
|
||||||
|
|
||||||
|
|
||||||
|
## Paths & Namespaces
|
||||||
|
- Scripts: `Assets/Scripts/Dialogue/`
|
||||||
|
- `RuntimeDialogueGraph.cs`
|
||||||
|
- `DialogueComponent.cs`
|
||||||
|
- `SpeechBubble.cs`
|
||||||
|
- Editor tooling: `Assets/Editor/Dialogue/`
|
||||||
|
- `DialogueGraph.cs` (graph editor)
|
||||||
|
- `DialogueNodes.cs` (node types for editor)
|
||||||
|
- `DialogueGraphImporter.cs` (asset importer)
|
||||||
|
- Namespace: `Dialogue`
|
||||||
|
|
||||||
|
## Troubleshooting / FAQ
|
||||||
|
- The prompt (“…”) never shows:
|
||||||
|
- Ensure a `SpeechBubble` exists under the same hierarchy and its serialized references are assigned.
|
||||||
|
- Check `DialogueComponent.HasAnyLines()` returns true; verify your graph’s `entryNodeID` and that nodes have content.
|
||||||
|
- Confirm `Interactable` is present and wired if you rely on proximity to trigger dialogue.
|
||||||
|
- Conditions don’t trigger (puzzle/item):
|
||||||
|
- Make sure `PuzzleManager` and `ItemManager` are present in the scene and raise the expected events.
|
||||||
|
- Double-check IDs used in nodes (`puzzleStepID`, `pickupItemID`, `slotItemID`, `combinationResultItemID`).
|
||||||
|
- Text doesn’t animate:
|
||||||
|
- Use `SpeechBubble.SetDisplayMode(TextDisplayMode.Typewriter)` and adjust with `SetTypewriterSpeed()`.
|
||||||
|
- Call `SkipTypewriter()` to reveal the full text instantly.
|
||||||
|
- I want to switch to image-based dialogue:
|
||||||
|
- Use `RuntimeDialogueNode.dialogueContent` and set `DialogueContentType.Image` entries instead of legacy `dialogueLines`.
|
||||||
|
- Can I drive it purely by code?
|
||||||
|
- Yes. Assign a `RuntimeDialogueGraph` via `SetDialogueGraph(...)`, call `StartDialogue()`, and let `DialogueComponent` progress based on input or external events.
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
- v1.1: Added Table of Contents, Architecture at a Glance, code-first snippets, standardized inline code formatting, and appended Paths/Troubleshooting. Preserved all existing image references.
|
||||||
|
- v1.0: Original overview and setup guide.
|
||||||
|
|||||||
@@ -1,275 +1,253 @@
|
|||||||
# Apple Hills Interaction System
|
# Apple Hills Interaction System
|
||||||
|
|
||||||
This document provides a comprehensive overview of the interaction system in Apple Hills, detailing how interactions are structured, configured, and extended with custom actions.
|
A concise, code-first guide to creating and extending interactions using `Interactable` and modular action/requirement components. Designed to match the style of the other updated docs (TOC, inline code, case studies).
|
||||||
|
|
||||||
## Overview
|
## Table of Contents
|
||||||
|
- [What This Solves](#what-this-solves)
|
||||||
|
- [Architecture at a Glance](#architecture-at-a-glance)
|
||||||
|
- [Quick Start (Code-First)](#quick-start-code-first)
|
||||||
|
- [Subscribe to Interaction Events](#subscribe-to-interaction-events)
|
||||||
|
- [Create a Custom Action](#create-a-custom-action)
|
||||||
|
- [Trigger Programmatically](#trigger-programmatically)
|
||||||
|
- [Core Components](#core-components)
|
||||||
|
- [`Interactable`](#interactable)
|
||||||
|
- [`CharacterMoveToTarget`](#charactermovetotarget)
|
||||||
|
- [`InteractionActionBase` and concrete actions](#interactionactionbase-and-concrete-actions)
|
||||||
|
- [`InteractionRequirementBase`](#interactionrequirementbase)
|
||||||
|
- [Interaction Event Flow](#interaction-event-flow)
|
||||||
|
- [Case Studies](#case-studies)
|
||||||
|
- [Open a Door on Arrival](#open-a-door-on-arrival)
|
||||||
|
- [Pick Up an Item then Play Timeline](#pick-up-an-item-then-play-timeline)
|
||||||
|
- [Kick Off Dialogue When Player Arrives](#kick-off-dialogue-when-player-arrives)
|
||||||
|
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||||
|
- [Paths & Namespaces](#paths--namespaces)
|
||||||
|
- [Change Log](#change-log)
|
||||||
|
|
||||||
The Apple Hills interaction system allows players to interact with objects in the game world. It supports character movement to interaction points, timed and conditional interactions, and complex behaviors through a component-based architecture. The system is particularly powerful when combined with the Timeline feature for creating cinematic sequences during interactions.
|
## What This Solves
|
||||||
|
- Standardized interaction lifecycle with reliable events (`InteractionStarted`, `PlayerArrived`, `InteractingCharacterArrived`, `InteractionComplete`, `InteractionInterrupted`).
|
||||||
|
- Composable behavior via components derived from `InteractionActionBase` and `InteractionRequirementBase`.
|
||||||
|
- Clean separation of input, locomotion-to-target, cinematic timelines, and game logic.
|
||||||
|
|
||||||
|
## Architecture at a Glance
|
||||||
|
- Driver: `Interactable` — owns lifecycle, input hook, character selection via `CharacterToInteract`, one‑shot/cooldown, and event dispatch.
|
||||||
|
- Targets: `CharacterMoveToTarget` — editor-authored world points for `Trafalgar`/`Pulver` to path to before executing actions.
|
||||||
|
- Actions: `InteractionActionBase` (abstract) — modular responses to specific `InteractionEventType` values; can pause the flow with async tasks.
|
||||||
|
- Requirements: `InteractionRequirementBase` (abstract) — gatekeepers for availability; multiple can be attached.
|
||||||
|
- Cinematics: `InteractionTimelineAction` — plays one or more `PlayableAsset` timelines per event; optional character auto-binding.
|
||||||
|
|
||||||
|
## Quick Start (Code-First)
|
||||||
|
|
||||||
|
### Subscribe to Interaction Events
|
||||||
|
```csharp
|
||||||
|
using Interactions;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class InteractDebugHooks : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Interactable interactable;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
interactable.interactionStarted.AddListener(OnStarted);
|
||||||
|
interactable.characterArrived.AddListener(OnCharacterArrived);
|
||||||
|
interactable.interactionInterrupted.AddListener(OnInterrupted);
|
||||||
|
interactable.interactionComplete.AddListener(OnComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
interactable.interactionStarted.RemoveListener(OnStarted);
|
||||||
|
interactable.characterArrived.RemoveListener(OnCharacterArrived);
|
||||||
|
interactable.interactionInterrupted.RemoveListener(OnInterrupted);
|
||||||
|
interactable.interactionComplete.RemoveListener(OnComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStarted(Input.PlayerTouchController player, FollowerController follower)
|
||||||
|
=> Debug.Log("Interaction started");
|
||||||
|
|
||||||
|
private void OnCharacterArrived() => Debug.Log("Character arrived");
|
||||||
|
private void OnInterrupted() => Debug.Log("Interaction interrupted");
|
||||||
|
private void OnComplete(bool success) => Debug.Log($"Interaction complete: {success}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a Custom Action
|
||||||
|
```csharp
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Interactions;
|
||||||
|
using Input;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class PlaySfxOnArrivalAction : InteractionActionBase
|
||||||
|
{
|
||||||
|
[SerializeField] private AudioSource sfx;
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
// React to the arrival event; don't block the flow
|
||||||
|
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
|
||||||
|
pauseInteractionFlow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ShouldExecute(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
|
||||||
|
{
|
||||||
|
return sfx != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
|
||||||
|
{
|
||||||
|
sfx.Play();
|
||||||
|
// non-blocking action returns immediately when pauseInteractionFlow == false
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Attach this component under the same hierarchy as an `Interactable`. Registration is automatic via `OnEnable()`/`OnDisable()` in `InteractionActionBase`.
|
||||||
|
|
||||||
|
### Trigger Programmatically
|
||||||
|
Normally input goes through `ITouchInputConsumer.OnTap(...)`. For testing, you can call the public tap handler:
|
||||||
|
```csharp
|
||||||
|
using UnityEngine;
|
||||||
|
using Interactions;
|
||||||
|
|
||||||
|
public class TestTrigger : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Interactable interactable;
|
||||||
|
|
||||||
|
[ContextMenu("Trigger Interact (dev)")]
|
||||||
|
private void Trigger()
|
||||||
|
{
|
||||||
|
interactable.OnTap(interactable.transform.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Core Components
|
## Core Components
|
||||||
|
|
||||||
The interaction system consists of several key components that work together:
|
### `Interactable`
|
||||||
|
- Handles input, cooldowns (`cooldown`), one‑shot (`isOneTime`), and which character participates (`characterToInteract`).
|
||||||
### Interactable
|
- Exposes events: `interactionStarted`, `characterArrived`, `interactionInterrupted`, `interactionComplete`.
|
||||||
|
- Discovers and dispatches to child `InteractionActionBase` components; awaits those that request to pause.
|
||||||
The `Interactable` component is the foundation of the interaction system. It:
|
|
||||||
- Handles player input (tapping/clicking)
|
|
||||||
- Manages which character(s) should interact (Trafalgar, Pulver, or both)
|
|
||||||
- Coordinates character movement to interaction points
|
|
||||||
- Dispatches events during the interaction lifecycle
|
|
||||||
- Manages one-time interactions and cooldowns
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### CharacterMoveToTarget
|
### `CharacterMoveToTarget`
|
||||||
|
Defines the world positions characters should reach before actions evaluate.
|
||||||
The `CharacterMoveToTarget` component defines positions where characters should move when interacting:
|
- Can target `Trafalgar`, `Pulver`, or `Both` via configuration.
|
||||||
- Can be configured for specific characters (Trafalgar, Pulver, or both)
|
- Supports offsets and editor gizmos; multiple instances allowed.
|
||||||
- Supports position offsets for fine-tuning
|
|
||||||
- Provides visual gizmos in the editor for easy positioning
|
|
||||||
- Multiple targets can be set up for complex interactions
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Interaction Actions
|
### `InteractionActionBase` and concrete actions
|
||||||
|
- Filter by `InteractionEventType` using `respondToEvents`.
|
||||||
Actions are components that respond to interaction events and execute custom behavior:
|
- Control flow with `pauseInteractionFlow` and async `ExecuteAsync(...)`.
|
||||||
- Derive from the abstract `InteractionActionBase` class
|
- Built‑in example: `InteractionTimelineAction` for cinematics.
|
||||||
- Can be attached to interactable objects
|
|
||||||
- Multiple actions can be added to a single interactable
|
|
||||||
- Actions can optionally block the interaction flow until completion
|
|
||||||
|
|
||||||
The inspector for all interaction action components shows the key parameters from InteractionActionBase, with custom configuration options for specific action types:
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Interaction Requirements
|
### `InteractionRequirementBase`
|
||||||
|
- Attach one or more to gate the interaction based on items, puzzles, proximity, etc.
|
||||||
Requirements are components that determine whether an interaction can occur:
|
|
||||||
- Derive from the abstract `InteractionRequirementBase` class
|
|
||||||
- Can prevent interactions based on custom conditions
|
|
||||||
- Multiple requirements can be added to a single interactable
|
|
||||||
- Used for creating conditional interactions (e.g., requiring an item)
|
|
||||||
|
|
||||||
## Interaction Event Flow
|
## Interaction Event Flow
|
||||||
|
1. `InteractionStarted`
|
||||||
|
2. `PlayerArrived`
|
||||||
|
3. `InteractingCharacterArrived`
|
||||||
|
4. `InteractionComplete` (bool success)
|
||||||
|
5. `InteractionInterrupted`
|
||||||
|
|
||||||
Interactions follow a defined event flow:
|
Actions receive these events in order and may run concurrently; those with `pauseInteractionFlow` true are awaited.
|
||||||
|
|
||||||
1. **InteractionStarted**: Triggered when the player initiates an interaction
|
## Case Studies
|
||||||
2. **PlayerArrived**: Triggered when the player character reaches the interaction point
|
|
||||||
3. **InteractingCharacterArrived**: Triggered when the interacting character (often Pulver) reaches the interaction point
|
|
||||||
4. **InteractionComplete**: Triggered when the interaction is completed
|
|
||||||
5. **InteractionInterrupted**: Triggered if the interaction is interrupted before completion
|
|
||||||
|
|
||||||
## InteractionActionBase
|
|
||||||
|
|
||||||
The `InteractionActionBase` is the abstract base class for all interaction actions. It provides the framework for creating custom behaviors that respond to interaction events.
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
|
|
||||||
- **Event Filtering**: Actions can choose which interaction events to respond to
|
|
||||||
- **Flow Control**: Actions can optionally pause the interaction flow until completion
|
|
||||||
- **Asynchronous Execution**: Actions use `async/await` pattern for time-consuming operations
|
|
||||||
- **Character References**: Actions receive references to both player and follower characters
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
|
### Open a Door on Arrival
|
||||||
```csharp
|
```csharp
|
||||||
public abstract class InteractionActionBase : MonoBehaviour
|
using System.Threading.Tasks;
|
||||||
|
using Interactions;
|
||||||
|
using Input;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class DoorOpenOnArrival : InteractionActionBase
|
||||||
{
|
{
|
||||||
// Which events this action should respond to
|
[SerializeField] private Animator animator; // expects a bool parameter "Open"
|
||||||
public List<InteractionEventType> respondToEvents;
|
|
||||||
|
|
||||||
// Whether to pause the interaction flow during execution
|
private void Reset()
|
||||||
public bool pauseInteractionFlow;
|
|
||||||
|
|
||||||
// The main execution method that must be implemented by derived classes
|
|
||||||
protected abstract Task<bool> ExecuteAsync(
|
|
||||||
InteractionEventType eventType,
|
|
||||||
PlayerTouchController player,
|
|
||||||
FollowerController follower);
|
|
||||||
|
|
||||||
// Optional method for adding execution conditions
|
|
||||||
protected virtual bool ShouldExecute(
|
|
||||||
InteractionEventType eventType,
|
|
||||||
PlayerTouchController player,
|
|
||||||
FollowerController follower)
|
|
||||||
{
|
{
|
||||||
|
respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
|
||||||
|
pauseInteractionFlow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
|
||||||
|
{
|
||||||
|
animator.SetBool("Open", true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## InteractionTimelineAction
|
### Pick Up an Item then Play Timeline
|
||||||
|
Attach two actions: your `PickupItemAction` that pauses until the item is collected, and an `InteractionTimelineAction` mapped to `InteractionEventType.InteractionComplete` to celebrate.
|
||||||
The `InteractionTimelineAction` is a powerful action that plays Unity Timeline sequences during interactions. It enables cinematic sequences, character animations, camera movements, and more.
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
|
|
||||||
- **Multiple Timeline Support**: Can play different timelines for different interaction events
|
|
||||||
- **Timeline Sequences**: Can play multiple timelines in sequence for a single event
|
|
||||||
- **Character Binding**: Automatically binds player and follower characters to timeline tracks
|
|
||||||
- **Flow Control**: Waits for timeline completion before continuing interaction flow
|
|
||||||
- **Timeout Safety**: Includes a configurable timeout to prevent interactions from getting stuck
|
|
||||||
- **Looping Options**: Supports looping all timelines or just the last timeline in a sequence
|
|
||||||
|
|
||||||
### Timeline Event Mapping
|
|
||||||
|
|
||||||
Each mapping connects an interaction event to one or more timeline assets:
|
|
||||||
|
|
||||||
|
### Kick Off Dialogue When Player Arrives
|
||||||
```csharp
|
```csharp
|
||||||
public class TimelineEventMapping
|
using System.Threading.Tasks;
|
||||||
|
using Dialogue;
|
||||||
|
using Input;
|
||||||
|
using Interactions;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class StartDialogueOnArrival : InteractionActionBase
|
||||||
{
|
{
|
||||||
// The event that triggers this timeline
|
[SerializeField] private DialogueComponent dialogue;
|
||||||
public InteractionEventType eventType;
|
|
||||||
|
|
||||||
// The timeline assets to play (in sequence)
|
private void Reset()
|
||||||
public PlayableAsset[] timelines;
|
{
|
||||||
|
respondToEvents = new() { InteractionEventType.PlayerArrived };
|
||||||
|
pauseInteractionFlow = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Character binding options
|
protected override async Task<bool> ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
|
||||||
public bool bindPlayerCharacter;
|
{
|
||||||
public bool bindPulverCharacter;
|
dialogue.StartDialogue();
|
||||||
public string playerTrackName = "Player";
|
return true;
|
||||||
public string pulverTrackName = "Pulver";
|
}
|
||||||
|
|
||||||
// Playback options
|
|
||||||
public float timeoutSeconds = 30f;
|
|
||||||
public bool loopLast = false;
|
|
||||||
public bool loopAll = false;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom Editor
|
## Troubleshooting / FAQ
|
||||||
|
- Interaction doesn’t fire:
|
||||||
|
- Confirm `Interactable` is active and not in cooldown or already completed (`isOneTime`).
|
||||||
|
- Ensure `CharacterMoveToTarget` exists for the selected `CharacterToInteract`.
|
||||||
|
- Actions not running:
|
||||||
|
- Verify `respondToEvents` includes the lifecycle moment you expect.
|
||||||
|
- Check that the component sits under the same hierarchy so it registers with the `Interactable`.
|
||||||
|
- Timeline never finishes:
|
||||||
|
- Make sure `InteractionTimelineAction` has valid `PlayableAsset` entries and binding flags.
|
||||||
|
- Double triggers:
|
||||||
|
- Guard reentry in your actions or check `_interactionInProgress` usage in `Interactable` by following logs.
|
||||||
|
|
||||||
The `InteractionTimelineAction` includes a custom editor that makes it easy to configure:
|
## Paths & Namespaces
|
||||||
- Quick buttons to add mappings for common events
|
- Scripts: `Assets/Scripts/Interactions/`
|
||||||
- Character binding options
|
- `Interactable.cs`
|
||||||
- Timeline sequence configuration
|
- `InteractionActionBase.cs`
|
||||||
- Validation warnings for misconfigured timelines
|
- `InteractionTimelineAction.cs`
|
||||||
|
- `InteractionEventType.cs`
|
||||||
|
- `InteractionRequirementBase.cs`
|
||||||
|
- Editor tooling: `Assets/Editor/InteractableEditor.cs`
|
||||||
|
- Primary namespace: `Interactions`
|
||||||
|
|
||||||
|
## Additional Editor Visuals
|
||||||
|
- Timeline mapping configuration UI:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Implementation Pattern
|
- Unity Timeline editor when authoring cinematics for interactions:
|
||||||
|
|
||||||
For a cinematic interaction with timelines:
|
|
||||||
|
|
||||||
1. Add an `Interactable` component to your object
|
|
||||||
2. Add an `InteractionTimelineAction` component to the same object
|
|
||||||
3. Set up character move targets if needed
|
|
||||||
4. Create timeline assets for each interaction phase
|
|
||||||
5. Configure the timeline mappings in the inspector
|
|
||||||
6. Test the interaction in play mode
|
|
||||||
|
|
||||||
### Timeline Configuration
|
|
||||||
|
|
||||||
When setting up a timeline for interaction, you'll need to create a Timeline asset and configure it in Unity's Timeline editor:
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Working with the Interactable Editor
|
- Example target placement in Scene view:
|
||||||
|
|
||||||
The `Interactable` component includes a custom editor that enhances the workflow:
|
|
||||||
|
|
||||||
### Character Move Target Creation
|
|
||||||
|
|
||||||
The editor provides buttons to easily create character move targets:
|
|
||||||
- "Add Trafalgar Target" - Creates a move target for the player character
|
|
||||||
- "Add Pulver Target" - Creates a move target for the follower character
|
|
||||||
- "Add Both Characters Target" - Creates a move target for both characters
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Target Visualization
|
|
||||||
|
|
||||||
The editor displays the number of targets for each character type and warns about potential conflicts:
|
|
||||||
```
|
|
||||||
Trafalgar Targets: 1, Pulver Targets: 1, Both Targets: 0
|
|
||||||
```
|
|
||||||
|
|
||||||
If multiple conflicting targets are detected, a warning is displayed to help prevent unexpected behavior.
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Target Positioning
|
|
||||||
|
|
||||||
- Place character targets with appropriate spacing to prevent characters from overlapping
|
|
||||||
- Consider the character's facing direction (targets automatically make characters face the interactable)
|
|
||||||
- Use the position offset for fine-tuning without moving the actual target GameObject
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Timeline Design
|
## Change Log
|
||||||
|
- v1.1: Added Table of Contents, code-first snippets, case studies, standardized inline code references, preserved existing editor images, and added troubleshooting/paths.
|
||||||
- Keep timelines modular and focused on specific actions
|
- v1.0: Original overview and setup guide.
|
||||||
- Use signals to trigger game events from timelines
|
|
||||||
- Consider using director notification tracks for advanced timeline integration
|
|
||||||
- Test timelines with actual characters to ensure animations blend correctly
|
|
||||||
|
|
||||||
### Action Combinations
|
|
||||||
|
|
||||||
- Combine multiple actions for complex behaviors (e.g., dialogue + timeline)
|
|
||||||
- Order actions in the Inspector to control execution priority
|
|
||||||
- Use the `pauseInteractionFlow` option strategically to control sequence flow
|
|
||||||
|
|
||||||
## Technical Reference
|
|
||||||
|
|
||||||
### InteractionEventType
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public enum InteractionEventType
|
|
||||||
{
|
|
||||||
InteractionStarted, // When interaction is first triggered
|
|
||||||
PlayerArrived, // When player arrives at interaction point
|
|
||||||
InteractingCharacterArrived, // When interacting character arrives
|
|
||||||
InteractionComplete, // When interaction is successfully completed
|
|
||||||
InteractionInterrupted // When interaction is interrupted
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### CharacterToInteract
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public enum CharacterToInteract
|
|
||||||
{
|
|
||||||
None, // No character interactions
|
|
||||||
Trafalgar, // Player character only
|
|
||||||
Pulver, // Follower character only
|
|
||||||
Both // Both characters
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
- **Characters not moving to targets**: Ensure targets have the correct CharacterToInteract type set
|
|
||||||
- **Timeline not playing**: Check that the PlayableDirector has a reference to the timeline asset
|
|
||||||
- **Characters not appearing in timeline**: Verify the track names match the binding configuration
|
|
||||||
- **Interaction getting stuck**: Make sure timelines have a reasonable timeout value set
|
|
||||||
- **Multiple timelines playing simultaneously**: Check that the event mappings are correctly configured
|
|
||||||
|
|
||||||
|
|
||||||
## Setup Example
|
|
||||||
|
|
||||||
Here's how a typical interaction setup might look in the Inspector:
|
|
||||||
|
|
||||||
1. Interactable component with appropriate settings
|
|
||||||
2. Character move targets positioned in the scene
|
|
||||||
3. InteractionTimelineAction component configured with timeline mappings
|
|
||||||
4. PlayableDirector component referencing timeline assets
|
|
||||||
|
|
||||||
## Advanced Topics
|
|
||||||
|
|
||||||
### Timeline Integration with Dialogue
|
|
||||||
|
|
||||||
Timeline actions can be synchronized with dialogue using:
|
|
||||||
- Animation tracks to trigger dialogue displays
|
|
||||||
- Signal tracks to advance dialogue
|
|
||||||
- Custom markers to synchronize dialogue with character animations
|
|
||||||
|
|
||||||
### Interaction Sequences
|
|
||||||
|
|
||||||
Complex interaction sequences can be created by:
|
|
||||||
- Using multiple interactables in sequence
|
|
||||||
- Enabling/disabling interactables based on game state
|
|
||||||
- Using timelines to guide the player through sequential interactions
|
|
||||||
|
|||||||
@@ -1,398 +1,186 @@
|
|||||||
# Apple Hills Settings System
|
# Apple Hills Settings System
|
||||||
|
|
||||||

|
Centralized, designer-friendly configuration using `ScriptableObject` assets, with runtime access via `SettingsProvider` (Addressables-backed) and editor/live-preview access via `SettingsAccess`. This page follows the style of other updated docs (TOC, inline code, code-first usage, case studies).
|
||||||
|
|
||||||
## Overview
|
## Table of Contents
|
||||||
|
- [What This Solves](#what-this-solves)
|
||||||
|
- [Architecture at a Glance](#architecture-at-a-glance)
|
||||||
|
- [Quick Start (Code-First)](#quick-start-code-first)
|
||||||
|
- [Get Settings at Runtime](#get-settings-at-runtime)
|
||||||
|
- [Use Editor-Time Values via `SettingsAccess`](#use-editor-time-values-via-settingsaccess)
|
||||||
|
- [Access Example Fields](#access-example-fields)
|
||||||
|
- [Authoring in the Editor](#authoring-in-the-editor)
|
||||||
|
- [Creating/Editing Settings Assets](#creatingediting-settings-assets)
|
||||||
|
- [Addressables Keys & Loading](#addressables-keys--loading)
|
||||||
|
- [Available Settings Types](#available-settings-types)
|
||||||
|
- [Case Studies](#case-studies)
|
||||||
|
- [Tune Interaction Distances](#tune-interaction-distances)
|
||||||
|
- [Follower Handling & Movement](#follower-handling--movement)
|
||||||
|
- [Diving Minigame Tuning](#diving-minigame-tuning)
|
||||||
|
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
||||||
|
- [Paths & Namespaces](#paths--namespaces)
|
||||||
|
- [Change Log](#change-log)
|
||||||
|
|
||||||
The Apple Hills settings system consists of two main categories:
|
## What This Solves
|
||||||
- **Game Settings**: Designed for gameplay parameters and configuration values that affect the player experience
|
- Consistent, centralized configuration across gameplay systems.
|
||||||
- **Developer Settings**: Technical settings used for development, debugging, and behind-the-scenes functionality that we don't want Angry Argentinians to accidentaly overwrite
|
- Safe, designer-editable `ScriptableObject` assets with validation (`OnValidate`).
|
||||||
|
- Simple, Addressables-based runtime loading and caching via `SettingsProvider`.
|
||||||
|
- Editor-time overrides and scene gizmo feedback via `SettingsAccess` helpers.
|
||||||
|
|
||||||
The settings framework uses ScriptableObjects to store configuration data, making it easy to modify values in the editor and reference them in code. Settings are organized into logical groups and can be accessed through custom editor windows or directly in code.
|
## Architecture at a Glance
|
||||||
|
- Base: `BaseSettings` (`ScriptableObject`) — common parent for all settings. Implements optional `OnValidate()`.
|
||||||
|
- Access (runtime): `SettingsProvider` (singleton `MonoBehaviour`) — loads assets synchronously via Addressables at keys `Settings/<TypeName>` and caches them.
|
||||||
|
- Access (editor/dev): `SettingsAccess` (static) — editor-friendly shim for reading selected values even when not in Play Mode, falling back to `GameManager` at runtime.
|
||||||
|
- Contracts: `SettingsInterfaces` (`IPlayerFollowerSettings`, `IInteractionSettings`, `IDivingMinigameSettings`) used by systems to remain decoupled from concrete assets.
|
||||||
|
- Editor tooling: `AppleHills/Settings Editor` — finds/creates assets under `Assets/Settings` and provides a tabbed UI.
|
||||||
|
|
||||||
Benefits of this approach include:
|
## Quick Start (Code-First)
|
||||||
- Central location for all configuration values
|
|
||||||
- Type-safe access to settings in code
|
|
||||||
- Custom inspector support for better editing experience
|
|
||||||
- Settings validation through the OnValidate method
|
|
||||||
- Support for both runtime and development-time settings
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### How to Access and Edit Settings in Editor
|
|
||||||
|
|
||||||
#### Game Settings
|
|
||||||
1. Open the Game Settings Editor window through the menu: **AppleHills → Game Settings Editor**
|
|
||||||
2. Navigate through the tabs to find the settings group you want to edit
|
|
||||||
3. Modify values directly in the inspector
|
|
||||||
4. Click "Save All" to apply changes
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### Developer Settings
|
|
||||||
1. Open the Developer Settings Editor window through the menu: **AppleHills → Developer Settings Editor**
|
|
||||||
2. Select the appropriate tab (Diving, Debug, etc.)
|
|
||||||
3. Modify values directly in the inspector
|
|
||||||
4. Click "Save All" to apply changes
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### How to Access Settings from Code
|
|
||||||
|
|
||||||
#### Game Settings
|
|
||||||
|
|
||||||
|
### Get Settings at Runtime
|
||||||
```csharp
|
```csharp
|
||||||
// Get player follower settings
|
|
||||||
var playerSettings = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
|
||||||
float moveSpeed = playerSettings.MoveSpeed;
|
|
||||||
|
|
||||||
// Get interaction settings
|
|
||||||
var interactionSettings = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
|
||||||
float stopDistance = interactionSettings.PlayerStopDistance;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Developer Settings
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Get diving developer settings
|
|
||||||
var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>();
|
|
||||||
bool useObjectPooling = divingDevSettings.BubbleUseObjectPooling;
|
|
||||||
|
|
||||||
// Get debug settings
|
|
||||||
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
|
|
||||||
if (debugSettings.GodMode) {
|
|
||||||
// Apply god mode functionality
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating Your Own Settings
|
|
||||||
|
|
||||||
#### 1. Create the Settings Class
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// For gameplay settings
|
|
||||||
[CreateAssetMenu(fileName = "MyGameplaySettings", menuName = "AppleHills/Settings/My Gameplay Settings", order = 1)]
|
|
||||||
public class MyGameplaySettings : BaseSettings
|
|
||||||
{
|
|
||||||
[Header("Movement")]
|
|
||||||
[Tooltip("Base movement speed")]
|
|
||||||
[SerializeField] private float baseSpeed = 5f;
|
|
||||||
|
|
||||||
public float BaseSpeed => baseSpeed;
|
|
||||||
|
|
||||||
public override void OnValidate()
|
|
||||||
{
|
|
||||||
// Validation logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For developer settings
|
|
||||||
[CreateAssetMenu(fileName = "MyDeveloperSettings", menuName = "AppleHills/Developer Settings/My Feature", order = 3)]
|
|
||||||
public class MyDeveloperSettings : BaseDeveloperSettings
|
|
||||||
{
|
|
||||||
[Header("Configuration")]
|
|
||||||
[Tooltip("Enable advanced features")]
|
|
||||||
[SerializeField] private bool enableAdvancedFeatures = false;
|
|
||||||
|
|
||||||
public bool EnableAdvancedFeatures => enableAdvancedFeatures;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Create the Settings Asset
|
|
||||||
- Right-click in the Project window
|
|
||||||
- Select Create → AppleHills → Settings → My Gameplay Settings (or Developer Settings → My Feature)
|
|
||||||
- Save the asset in the appropriate location (Assets/Settings or Assets/Settings/Developer)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### 3. Register in the Editor Window (for custom tabs)
|
|
||||||
See the detailed workflow section for a complete guide.
|
|
||||||
|
|
||||||
## Architecture Breakdown
|
|
||||||
|
|
||||||
The settings system consists of several key components:
|
|
||||||
|
|
||||||
### Base Classes
|
|
||||||
|
|
||||||
- **BaseSettings**: The root class for all game settings ScriptableObjects
|
|
||||||
- **BaseDeveloperSettings**: The root class for all developer settings ScriptableObjects
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Provider Classes
|
|
||||||
|
|
||||||
- **SettingsProvider**: Singleton for accessing game settings
|
|
||||||
- **DeveloperSettingsProvider**: Singleton for accessing developer settings
|
|
||||||
|
|
||||||
Both providers handle loading settings from Addressables or Resources, caching them for efficient access, and providing type-safe retrieval methods.
|
|
||||||
|
|
||||||
### Editor Windows
|
|
||||||
|
|
||||||
- **GameSettingsEditorWindow**: Custom editor window for editing game settings
|
|
||||||
- **DeveloperSettingsEditorWindow**: Custom editor window for editing developer settings
|
|
||||||
|
|
||||||
These windows provide a clean interface for browsing and modifying settings, organized into tabs by category.
|
|
||||||
|
|
||||||
### Settings Storage
|
|
||||||
|
|
||||||
Settings assets are stored in:
|
|
||||||
- **Assets/Settings**: For game settings
|
|
||||||
- **Assets/Settings/Developer**: For developer settings
|
|
||||||
|
|
||||||
These assets are loaded via Addressables during runtime, with a fallback to Resources if needed.
|
|
||||||
|
|
||||||
## Detailed Workflows
|
|
||||||
|
|
||||||
### Creating a New Settings Class
|
|
||||||
|
|
||||||
1. **Determine the Type of Settings**
|
|
||||||
- Game settings: Inherit from `BaseSettings`
|
|
||||||
- Developer settings: Inherit from `BaseDeveloperSettings`
|
|
||||||
|
|
||||||
2. **Create the Class File**
|
|
||||||
- Create a new script in the `Assets/Scripts/Core/Settings` folder
|
|
||||||
- Name it appropriately, e.g., `MyFeatureSettings.cs` or `MyFeatureDeveloperSettings.cs`
|
|
||||||
|
|
||||||
3. **Implement the Settings Class**
|
|
||||||
|
|
||||||
Here's an example of how to implement a settings class with proper structure:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using UnityEngine;
|
|
||||||
using AppleHills.Core.Settings;
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
namespace AppleHills.Core.Settings
|
var playerFollower = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
||||||
{
|
float speed = playerFollower.MoveSpeed;
|
||||||
[CreateAssetMenu(fileName = "MyFeatureSettings", menuName = "AppleHills/Settings/My Feature", order = 1)]
|
|
||||||
public class MyFeatureSettings : BaseSettings
|
|
||||||
{
|
|
||||||
[Header("Main Configuration")]
|
|
||||||
[Tooltip("Description of this setting")]
|
|
||||||
[SerializeField] private float mainValue = 10f;
|
|
||||||
|
|
||||||
[Tooltip("Another important setting")]
|
|
||||||
[Range(0, 100)]
|
|
||||||
[SerializeField] private int secondaryValue = 50;
|
|
||||||
|
|
||||||
[Header("Advanced Settings")]
|
|
||||||
[Tooltip("Enable special features")]
|
|
||||||
[SerializeField] private bool enableSpecialFeatures = false;
|
|
||||||
|
|
||||||
// Public property accessors (follow this pattern)
|
|
||||||
public float MainValue => mainValue;
|
|
||||||
public int SecondaryValue => secondaryValue;
|
|
||||||
public bool EnableSpecialFeatures => enableSpecialFeatures;
|
|
||||||
|
|
||||||
// Optional validation
|
|
||||||
public override void OnValidate()
|
|
||||||
{
|
|
||||||
// Ensure values are within acceptable ranges
|
|
||||||
mainValue = Mathf.Max(0, mainValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
Or fetch interaction settings once and reuse:
|
||||||
The key elements to include are:
|
|
||||||
- Proper attribute decorations (`Header`, `Tooltip`, `Range`, etc.)
|
|
||||||
- Serialized private fields with default values
|
|
||||||
- Public property accessors using the expression-bodied syntax
|
|
||||||
- Validation in the OnValidate method when needed
|
|
||||||
|
|
||||||
### Creating Settings Assets
|
|
||||||
|
|
||||||
1. **Create the Asset**
|
|
||||||
- Right-click in the Project window
|
|
||||||
- Navigate to Create → AppleHills → Settings → My Feature (or Developer Settings → My Feature)
|
|
||||||
- A new settings asset will be created
|
|
||||||
|
|
||||||
2. **Configure the Asset**
|
|
||||||
- Set the default values for your settings
|
|
||||||
- Organize the asset in the correct folder:
|
|
||||||
- Game settings: `Assets/Settings/`
|
|
||||||
- Developer settings: `Assets/Settings/Developer/`
|
|
||||||
|
|
||||||
3. **Add to Addressables**
|
|
||||||
- Open the Addressables Groups window (Window → Asset Management → Addressables → Groups)
|
|
||||||
- Drag your settings asset to the Settings group
|
|
||||||
- For game settings, ensure the addressable address is `Settings/YourSettingsClassName`
|
|
||||||
- For developer settings, ensure the addressable address is `Settings/Developer/YourSettingsClassName`
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Adding to the Settings Editor Window
|
|
||||||
|
|
||||||
For game settings:
|
|
||||||
|
|
||||||
1. Open the `GameSettingsEditorWindow.cs` file in `Assets/Editor`
|
|
||||||
2. Update the tab names array to include your new settings category
|
|
||||||
3. Add a case to the switch statement in the `OnGUI` method to draw your settings
|
|
||||||
4. Add your settings type to the `LoadAllSettings` method's `CreateSettingsIfMissing` calls
|
|
||||||
|
|
||||||
Here's an example of the code changes needed:
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// 1. Update tab names array
|
using AppleHills.Core.Settings;
|
||||||
private string[] tabNames = new string[] { "Player", "Interaction", "My Feature" }; // Add your tab
|
|
||||||
|
|
||||||
// 2. Add to switch statement in OnGUI
|
private IInteractionSettings _interaction;
|
||||||
switch (selectedTab)
|
|
||||||
|
void Awake()
|
||||||
{
|
{
|
||||||
case 0: // Player
|
_interaction = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||||
DrawSettingsEditor<PlayerFollowerSettings>();
|
|
||||||
break;
|
|
||||||
case 1: // Interaction
|
|
||||||
DrawSettingsEditor<InteractionSettings>();
|
|
||||||
break;
|
|
||||||
case 2: // My Feature
|
|
||||||
DrawSettingsEditor<MyFeatureSettings>(); // Add your case
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Add to LoadAllSettings method
|
void UseIt()
|
||||||
private void LoadAllSettings()
|
|
||||||
{
|
{
|
||||||
// ... existing code ...
|
float stopDist = _interaction.PlayerStopDistance;
|
||||||
|
|
||||||
// Add your settings type
|
|
||||||
CreateSettingsIfMissing<MyFeatureSettings>("MyFeatureSettings");
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For developer settings:
|
### Use Editor-Time Values via `SettingsAccess`
|
||||||
|
For scene tools, gizmos, or editors that should reflect current settings outside Play Mode:
|
||||||
1. Open the `DeveloperSettingsEditorWindow.cs` file in `Assets/Editor`
|
|
||||||
2. Update the tab names array to include your new settings category
|
|
||||||
3. Add a case to the switch statement in the `OnGUI` method to draw your settings
|
|
||||||
4. Add your settings type to the `LoadAllSettings` method's `CreateSettingsIfMissing` calls
|
|
||||||
|
|
||||||
As seen in your current implementation for the Debug tab:
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// 1. Tab names include the Debug category
|
// Returns editor-sourced values in Edit Mode; GameManager-backed in Play Mode
|
||||||
private string[] tabNames = new string[] { "Diving", "Debug", "Other Systems" };
|
float stopDist = AppleHills.SettingsAccess.GetPlayerStopDistance();
|
||||||
|
float directStop = AppleHills.SettingsAccess.GetPlayerStopDistanceDirectInteraction();
|
||||||
// 2. Switch statement handles the Debug tab
|
float puzzleRange = AppleHills.SettingsAccess.GetPuzzlePromptRange();
|
||||||
switch (selectedTab)
|
|
||||||
{
|
|
||||||
case 0: // Diving
|
|
||||||
DrawSettingsEditor<DivingDeveloperSettings>();
|
|
||||||
break;
|
|
||||||
case 1: // Debug
|
|
||||||
DrawSettingsEditor<DebugSettings>();
|
|
||||||
break;
|
|
||||||
case 2: // Other Systems
|
|
||||||
// ... existing code ...
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. LoadAllSettings includes DebugSettings
|
|
||||||
private void LoadAllSettings()
|
|
||||||
{
|
|
||||||
// ... existing code ...
|
|
||||||
CreateSettingsIfMissing<DivingDeveloperSettings>("DivingDeveloperSettings");
|
|
||||||
CreateSettingsIfMissing<DebugSettings>("DebugSettings");
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Settings in Game Code
|
### Access Example Fields
|
||||||
|
|
||||||
1. **Access game settings**:
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public class MyGameplaySystem : MonoBehaviour
|
using AppleHills.Core.Settings;
|
||||||
{
|
|
||||||
private MyFeatureSettings settings;
|
|
||||||
|
|
||||||
private void Awake()
|
var pf = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
||||||
{
|
// Player
|
||||||
// Get settings from provider
|
float moveSpeed = pf.MoveSpeed;
|
||||||
settings = SettingsProvider.Instance.GetSettings<MyFeatureSettings>();
|
float accel = pf.MaxAcceleration;
|
||||||
|
bool useRb = pf.UseRigidbody;
|
||||||
|
// Follower
|
||||||
|
float followDist = pf.FollowDistance;
|
||||||
|
float near = pf.ThresholdNear;
|
||||||
|
|
||||||
if (settings == null)
|
var inter = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||||
{
|
LayerMask interactMask = inter.InteractableLayerMask;
|
||||||
Debug.LogError("Failed to load MyFeatureSettings!");
|
GameObject pickupPrefab = inter.BasePickupPrefab;
|
||||||
return;
|
float promptRange = inter.DefaultPuzzlePromptRange;
|
||||||
}
|
|
||||||
|
|
||||||
// Use the settings
|
|
||||||
float value = settings.MainValue;
|
|
||||||
bool specialEnabled = settings.EnableSpecialFeatures;
|
|
||||||
|
|
||||||
// Configure components based on settings
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Access developer settings**:
|
## Authoring in the Editor
|
||||||
|
|
||||||
|
### Creating/Editing Settings Assets
|
||||||
|
- Open via menu: `AppleHills/Settings Editor`.
|
||||||
|
- The window discovers all assets of type `BaseSettings` and provides tabbed editing for:
|
||||||
|
- `PlayerFollowerSettings`
|
||||||
|
- `InteractionSettings`
|
||||||
|
- `DivingMinigameSettings`
|
||||||
|
- If an asset is missing, the tool auto-creates it under `Assets/Settings/`:
|
||||||
|
- `Assets/Settings/PlayerFollowerSettings.asset`
|
||||||
|
- `Assets/Settings/InteractionSettings.asset`
|
||||||
|
- `Assets/Settings/DivingMinigameSettings.asset`
|
||||||
|
- Click “Save All” to persist and refresh editor providers/gizmos.
|
||||||
|
|
||||||
|
### Addressables Keys & Loading
|
||||||
|
At runtime, `SettingsProvider` synchronously loads settings via Addressables with keys:
|
||||||
|
- `Settings/PlayerFollowerSettings`
|
||||||
|
- `Settings/InteractionSettings`
|
||||||
|
- `Settings/DivingMinigameSettings`
|
||||||
|
|
||||||
|
Ensure these assets are marked as Addressables with the exact keys above. The provider caches objects, so subsequent `GetSettings<T>()` calls are fast.
|
||||||
|
|
||||||
|
## Available Settings Types
|
||||||
|
- `PlayerFollowerSettings` (`IPlayerFollowerSettings`)
|
||||||
|
- Player: `MoveSpeed`, `MaxAcceleration`, `StopDistance`, `UseRigidbody`, `DefaultHoldMovementMode`.
|
||||||
|
- Follower: `FollowDistance`, `ManualMoveSmooth`, `ThresholdFar`, `ThresholdNear`, `StopThreshold`.
|
||||||
|
- Backend: `FollowUpdateInterval`, `FollowerSpeedMultiplier`, `HeldIconDisplayHeight`.
|
||||||
|
- `InteractionSettings` (`IInteractionSettings`)
|
||||||
|
- Interactions: `PlayerStopDistance`, `PlayerStopDistanceDirectInteraction`, `FollowerPickupDelay`.
|
||||||
|
- Input/Layering: `InteractableLayerMask`.
|
||||||
|
- Prefabs: `BasePickupPrefab`, `LevelSwitchMenuPrefab`, `DefaultPuzzleIndicatorPrefab`.
|
||||||
|
- Puzzle/UI: `DefaultPuzzlePromptRange`.
|
||||||
|
- Items: `CombinationRules`, `SlotItemConfigs` plus helpers `GetCombinationRule(...)`, `GetSlotItemConfig(...)`.
|
||||||
|
- `DivingMinigameSettings` (`IDivingMinigameSettings`)
|
||||||
|
- Movement, spawning, scoring, surfacing, normalized movement, tile generation, obstacles, camera viewfinder settings, photo input mode (`PhotoInputModes`).
|
||||||
|
|
||||||
|
## Case Studies
|
||||||
|
|
||||||
|
### Tune Interaction Distances
|
||||||
```csharp
|
```csharp
|
||||||
public class MyDevelopmentTool : MonoBehaviour
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
|
public class InteractDistanceExample
|
||||||
{
|
{
|
||||||
private MyFeatureDeveloperSettings devSettings;
|
private readonly IInteractionSettings _s = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
||||||
|
public bool IsInRange(float dist) => dist <= _s.PlayerStopDistance;
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
// Get developer settings
|
|
||||||
devSettings = DeveloperSettingsProvider.Instance.GetSettings<MyFeatureDeveloperSettings>();
|
|
||||||
|
|
||||||
if (devSettings == null)
|
|
||||||
{
|
|
||||||
Debug.LogError("Failed to load MyFeatureDeveloperSettings!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the settings
|
|
||||||
bool advancedEnabled = devSettings.EnableAdvancedFeatures;
|
|
||||||
|
|
||||||
// Configure development tools based on settings
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Best Practices
|
### Follower Handling & Movement
|
||||||
|
```csharp
|
||||||
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
1. **Organization**
|
public class FollowerMover
|
||||||
- Group related settings into a single settings class
|
{
|
||||||
- Use `[Header]` attributes to create logical sections
|
private readonly IPlayerFollowerSettings _pf = SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>();
|
||||||
- Use `[Tooltip]` attributes to document settings
|
public float TargetSpeed(float error) => Mathf.Clamp(error * _pf.FollowerSpeedMultiplier, 0f, _pf.MoveSpeed);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
2. **Validation**
|
### Diving Minigame Tuning
|
||||||
- Implement `OnValidate()` to ensure values are within acceptable ranges
|
```csharp
|
||||||
- Consider dependencies between settings
|
using AppleHills.Core.Settings;
|
||||||
|
|
||||||
3. **Naming**
|
public class SpawnController
|
||||||
- Use descriptive names for settings properties
|
{
|
||||||
- Follow a consistent naming pattern
|
private readonly IDivingMinigameSettings _m = SettingsProvider.Instance.GetSettings<DivingMinigameSettings>();
|
||||||
|
public float NextCooldown(float baseCooldown) => Mathf.Clamp(baseCooldown + Random.Range(-_m.ObstacleSpawnIntervalVariation, _m.ObstacleSpawnIntervalVariation), 0.1f, 99);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
4. **Performance**
|
## Troubleshooting / FAQ
|
||||||
- Cache settings references rather than calling `GetSettings<T>()` repeatedly
|
- Settings return null at runtime:
|
||||||
- Only access settings when needed
|
- Ensure assets are Addressable with keys `Settings/<TypeName>` and Addressables are initialized before first access.
|
||||||
|
- Editor changes don’t reflect in scene gizmos:
|
||||||
|
- Click “Save All” in `AppleHills/Settings Editor`; the editor provider refresh call updates views.
|
||||||
|
- Which API to use: `SettingsProvider` vs `SettingsAccess`?
|
||||||
|
- Use `SettingsProvider` in runtime code. Use `SettingsAccess` in editor tools/gizmos or shared code that runs both in Edit and Play Modes.
|
||||||
|
|
||||||
5. **Defaults**
|
## Paths & Namespaces
|
||||||
- Provide sensible default values for all settings
|
- Scripts: `Assets/Scripts/Core/Settings/`
|
||||||
- Document the expected ranges for numeric values
|
- `BaseSettings.cs`
|
||||||
|
- `SettingsInterfaces.cs`
|
||||||
|
- `SettingsProvider.cs`
|
||||||
|
- `PlayerFollowerSettings.cs`
|
||||||
|
- `InteractionSettings.cs`
|
||||||
|
- `DivingMinigameSettings.cs`
|
||||||
|
- Editor tooling: `Assets/Editor/SettingsEditorWindow.cs`
|
||||||
|
- Editor-time facade: `Assets/Scripts/Core/SettingsAccess.cs`
|
||||||
|
- Namespaces:
|
||||||
|
- Runtime: `AppleHills.Core.Settings`
|
||||||
|
- Editor window: `AppleHills.Core.Settings.Editor`
|
||||||
|
- Facade: `AppleHills`
|
||||||
|
|
||||||
## Required Screenshots
|
## Change Log
|
||||||
|
- v1.1: New page with TOC, code-first usage, authoring workflow, Addressables keys, case studies, troubleshooting, and paths.
|
||||||
To complete this documentation, you'll need to take the following screenshots:
|
|
||||||
|
|
||||||
1. **settings_system_overview.png**
|
|
||||||
- Screenshot of both settings editor windows side by side to show the full system
|
|
||||||
|
|
||||||
2. **game_settings_editor.png**
|
|
||||||
- Screenshot of the Game Settings Editor window with the PlayerFollowerSettings tab selected
|
|
||||||
|
|
||||||
3. **developer_settings_editor.png**
|
|
||||||
- Screenshot of the Developer Settings Editor window with the Debug tab selected
|
|
||||||
|
|
||||||
4. **create_settings_asset.png**
|
|
||||||
- Screenshot of the right-click Create menu showing the AppleHills/Settings and AppleHills/Developer Settings options
|
|
||||||
|
|
||||||
5. **settings_class_hierarchy.png**
|
|
||||||
- Screenshot of the Project window showing the folder structure with expanded Settings folder highlighting the base classes
|
|
||||||
|
|
||||||
6. **addressables_configuration.png**
|
|
||||||
- Screenshot of the Addressables Groups window showing settings assets properly configured
|
|
||||||
|
|||||||
194
docs/ui_page_navigation.md
Normal file
194
docs/ui_page_navigation.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# UI Page Navigation System
|
||||||
|
|
||||||
|
A brief guide to the stack-based page navigation used across the project. It is built around `UIPageController` (the navigator) and `UIPage` (the base page with transition hooks). This system is used by the card collection UI and can be reused for any feature that needs push/pop navigation with transitions.
|
||||||
|
|
||||||
|
- Source files:
|
||||||
|
- `Assets/Scripts/UI/Core/UIPageController.cs`
|
||||||
|
- `Assets/Scripts/UI/Core/UIPage.cs`
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Concepts](#concepts)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Transitions and Events](#transitions-and-events)
|
||||||
|
- [Card System Case Studies](#card-system-case-studies)
|
||||||
|
- [Best Practices](#best-practices)
|
||||||
|
- [API Reference (Essentials)](#api-reference-essentials)
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
- `UIPageController`
|
||||||
|
- A singleton navigator that maintains a stack of `UIPage` instances.
|
||||||
|
- Provides `PushPage`, `PopPage`, and `ClearStack`.
|
||||||
|
- Fires `OnPageChanged` when the top page changes.
|
||||||
|
- `UIPage`
|
||||||
|
- Base class for pages with overridable transition hooks.
|
||||||
|
- Implements `TransitionIn`, `TransitionOut`, and `OnBackPressed`.
|
||||||
|
- Comes with start/complete events for both in/out transitions.
|
||||||
|
|
||||||
|
Stack behavior in short:
|
||||||
|
- `PushPage(page)` hides the current page (transition out), shows the new page (transition in), and puts it on top.
|
||||||
|
- `PopPage()` hides the current page (transition out), reveals the previous top page (transition in).
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1) Showing a page
|
||||||
|
```csharp
|
||||||
|
// Assuming you have a reference to a concrete UIPage (via scene object or instantiated prefab)
|
||||||
|
[SerializeField] private UIPage albumViewPage;
|
||||||
|
|
||||||
|
void OpenAlbum()
|
||||||
|
{
|
||||||
|
UIPageController.Instance.PushPage(albumViewPage);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Going back
|
||||||
|
```csharp
|
||||||
|
void Back()
|
||||||
|
{
|
||||||
|
UIPageController.Instance.PopPage();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) Custom page with simple transitions
|
||||||
|
```csharp
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class FadePage : UIPage
|
||||||
|
{
|
||||||
|
[SerializeField] private CanvasGroup canvasGroup;
|
||||||
|
|
||||||
|
protected override void DoTransitionIn(Action onComplete)
|
||||||
|
{
|
||||||
|
// Example: simple fade in (pseudo-code; replace with your tween/anim system)
|
||||||
|
canvasGroup.alpha = 0f;
|
||||||
|
gameObject.SetActive(true);
|
||||||
|
// Tween to alpha 1 over transitionDuration, then:
|
||||||
|
onComplete?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoTransitionOut(Action onComplete)
|
||||||
|
{
|
||||||
|
// Example: simple fade out
|
||||||
|
// Tween to alpha 0 over transitionDuration, then:
|
||||||
|
onComplete?.Invoke();
|
||||||
|
// The base class will SetActive(false) after completion.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4) Wiring a generic Cancel/Back input (optional)
|
||||||
|
`UIPageController` has placeholders to wire a Cancel action from the Input System. If you want a global back action:
|
||||||
|
```csharp
|
||||||
|
// In your bootstrap or scene initializer
|
||||||
|
var controller = UIPageController.Instance; // ensure it exists in the scene
|
||||||
|
// Optionally hook up your own input and call:
|
||||||
|
if (controller.CurrentPage != null)
|
||||||
|
{
|
||||||
|
controller.CurrentPage.OnBackPressed();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transitions and Events
|
||||||
|
|
||||||
|
`UIPage` exposes these events:
|
||||||
|
- `OnTransitionInStarted`
|
||||||
|
- `OnTransitionInCompleted`
|
||||||
|
- `OnTransitionOutStarted`
|
||||||
|
- `OnTransitionOutCompleted`
|
||||||
|
|
||||||
|
Use them to coordinate animation, audio, or deferred loading.
|
||||||
|
|
||||||
|
Example: enable input only after a page is visible.
|
||||||
|
```csharp
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
albumViewPage.OnTransitionInCompleted += EnableAlbumInput;
|
||||||
|
albumViewPage.OnTransitionOutStarted += DisableAlbumInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable()
|
||||||
|
{
|
||||||
|
albumViewPage.OnTransitionInCompleted -= EnableAlbumInput;
|
||||||
|
albumViewPage.OnTransitionOutStarted -= DisableAlbumInput;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Card System Case Studies
|
||||||
|
|
||||||
|
These examples show how the card UI uses the page stack.
|
||||||
|
|
||||||
|
### Open the Card Menu from gameplay HUD
|
||||||
|
```csharp
|
||||||
|
[SerializeField] private UIPage cardMenuPage;
|
||||||
|
|
||||||
|
public void OnBackpackButton()
|
||||||
|
{
|
||||||
|
UIPageController.Instance.PushPage(cardMenuPage);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigate from Card Menu to Album view
|
||||||
|
```csharp
|
||||||
|
[SerializeField] private UIPage albumViewPage;
|
||||||
|
|
||||||
|
public void OnAlbumButton()
|
||||||
|
{
|
||||||
|
UIPageController.Instance.PushPage(albumViewPage);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enter Booster Opening flow and then return
|
||||||
|
```csharp
|
||||||
|
[SerializeField] private UIPage boosterOpeningPage;
|
||||||
|
|
||||||
|
public void OnOpenBooster()
|
||||||
|
{
|
||||||
|
// Assume booster count was verified via `CardSystemManager`
|
||||||
|
UIPageController.Instance.PushPage(boosterOpeningPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside the booster opening page, when user taps Back or after completion
|
||||||
|
public override void OnBackPressed()
|
||||||
|
{
|
||||||
|
if (!_isTransitioning)
|
||||||
|
{
|
||||||
|
// Optional: guard against leaving mid-reveal
|
||||||
|
// if (!revealComplete) return;
|
||||||
|
UIPageController.Instance.PopPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Close all card UI and return to HUD
|
||||||
|
```csharp
|
||||||
|
public void CloseAllCardUI()
|
||||||
|
{
|
||||||
|
UIPageController.Instance.ClearStack();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- One active `UIPageController` per UI context (usually one in the main UI scene). Avoid multiple competing controllers.
|
||||||
|
- Keep transitions short and responsive; the base class already handles `SetActive` lifecycle for you.
|
||||||
|
- Always check `_isTransitioning` (available in `UIPage`) before triggering back/forward actions to avoid double navigations.
|
||||||
|
- Use `OnPageChanged` on `UIPageController` to manage global state (e.g., pause input when a modal page is shown).
|
||||||
|
- If you instantiate pages at runtime, ensure their `GameObject` is inactive by default and let `TransitionIn` activate them.
|
||||||
|
|
||||||
|
## API Reference (Essentials)
|
||||||
|
|
||||||
|
From `UIPageController`:
|
||||||
|
- `UIPage CurrentPage` — top of the stack or `null`.
|
||||||
|
- `event Action<UIPage> OnPageChanged` — fired after a push/pop resolves.
|
||||||
|
- `void PushPage(UIPage page)` — transitions out current (if any), transitions in the new page, pushes it on the stack.
|
||||||
|
- `void PopPage()` — transitions out current, reveals previous page with a transition in.
|
||||||
|
- `void ClearStack()` — transitions out current and empties the stack.
|
||||||
|
|
||||||
|
From `UIPage`:
|
||||||
|
- `string PageName` — a designer-friendly name for logs/diagnostics.
|
||||||
|
- `float transitionDuration` (protected) — your animations can use this.
|
||||||
|
- `virtual void TransitionIn()` / `TransitionOut()` — call the overridable `DoTransitionIn`/`DoTransitionOut`.
|
||||||
|
- `virtual void OnBackPressed()` — default pops the page (calls `UIPageController.Instance.PopPage()`). Override to customize.
|
||||||
|
- Events: `OnTransitionInStarted`, `OnTransitionInCompleted`, `OnTransitionOutStarted`, `OnTransitionOutCompleted`.
|
||||||
|
|
||||||
|
For additional context and examples, see `docs/card_system_playbook.md` and `docs/card_system_integration_and_testing.md`.
|
||||||
Reference in New Issue
Block a user