Skip to content

Commit 88e7a8d

Browse files
author
quyong
committed
feat: add option to remove from preloadedAssets when build player
1 parent 70937d2 commit 88e7a8d

6 files changed

Lines changed: 208 additions & 8 deletions

File tree

Packages/src/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## [Unreleased]
2+
3+
### Features
4+
5+
* add `Exclude From Preloaded Assets When Build Player` option: temporarily removes settings from `PlayerSettings.preloadedAssets` during Player Build only, enabling AssetBundle / Addressables hot-update workflows while keeping normal Editor behavior unchanged
6+
17
## [5.10.8](https://github.com/mob-sakai/UIEffect/compare/5.10.7...5.10.8) (2026-01-16)
28

39

Packages/src/Editor/UIEffectProjectSettingsEditor.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,33 @@ public override void OnInspectorGUI()
9595
}
9696

9797
_shaderVariantRegistryEditor.Draw();
98+
99+
EditorGUILayout.Space();
100+
EditorGUILayout.LabelField("Advanced", EditorStyles.boldLabel);
101+
var excludeProp = serializedObject.FindProperty("m_ExcludeFromPreloadedAssetsWhenBuildPlayer");
102+
EditorGUILayout.PropertyField(excludeProp);
103+
104+
if (excludeProp.boolValue)
105+
{
106+
var shadersProp = serializedObject.FindProperty("m_ShaderVariantRegistry")
107+
.FindPropertyRelative("m_RegisteredShaders");
108+
EditorGUI.BeginDisabledGroup(true);
109+
EditorGUI.indentLevel++;
110+
for (var i = 0; i < shadersProp.arraySize; i++)
111+
{
112+
EditorGUILayout.PropertyField(shadersProp.GetArrayElementAtIndex(i), GUIContent.none);
113+
}
114+
EditorGUI.indentLevel--;
115+
EditorGUI.EndDisabledGroup();
116+
117+
if (shadersProp.arraySize == 0)
118+
{
119+
EditorGUILayout.HelpBox(
120+
"No shaders registered. Re-import or modify the settings asset to trigger sync.",
121+
MessageType.Warning);
122+
}
123+
}
124+
98125
GUILayout.FlexibleSpace();
99126
serializedObject.ApplyModifiedProperties();
100127
}

Packages/src/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,13 @@ You can adjust the project-wide settings for UIEffect. (`Edit > Project Settings
629629
> - The setting file is usually saved in `Assets/ProjectSettings/UIEffectProjectSettings.asset`. Include this file in your version control system.
630630
> - The setting file is automatically added as a preloaded asset in `ProjectSettings/ProjectSettings.asset`.
631631
632+
#### Advanced
633+
634+
- **Exclude From Preloaded Assets When Build Player**: When enabled, the settings asset will be **temporarily removed** from `PlayerSettings.preloadedAssets` **during Player Build only**, so it is NOT included in the built player. The asset remains in PreloadedAssets during normal Editor operation.
635+
- Use this for AssetBundle / Addressables based hot-update workflows where settings and shaders are delivered externally instead of being built into the player.
636+
- Shader references are serialized from the `ShaderVariantCollection` at edit-time, so `Shader.Find()` is not required at runtime.
637+
- When toggled off, serialized shader references are automatically cleared.
638+
632639
<br><br>
633640

634641
### :warning: Limitations

Packages/src/Runtime/Internal/ProjectSettings/PreloadedProjectSettings.cs

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,37 @@ namespace Coffee.UIEffectInternal
1414
public abstract class PreloadedProjectSettings : ScriptableObject
1515
#if UNITY_EDITOR
1616
{
17+
[Tooltip("When enabled, this settings asset will be temporarily removed from " +
18+
"PlayerSettings.preloadedAssets during Player Build, so it is NOT included " +
19+
"in the built player. The asset remains in PreloadedAssets during normal " +
20+
"Editor operation.\n\n" +
21+
"Enable this when you deliver settings and shaders via AssetBundles or " +
22+
"Addressables for hot-update support. Shaders are then resolved via direct " +
23+
"serialized references instead of Shader.Find().")]
24+
[SerializeField]
25+
private bool m_ExcludeFromPreloadedAssetsWhenBuildPlayer;
26+
27+
/// <summary>
28+
/// When enabled, this settings asset will be temporarily removed from
29+
/// <see cref="PlayerSettings.preloadedAssets"/> only during Player Build,
30+
/// so it is NOT included in the built player. The asset remains in
31+
/// PreloadedAssets during normal Editor operation.
32+
/// <para>
33+
/// Enable this when you deliver settings and shaders via AssetBundles or
34+
/// Addressables for hot-update support. Shaders are resolved via direct
35+
/// serialized references in the shader variant registry instead of
36+
/// <see cref="Shader.Find"/>, since AB-loaded shaders are not registered
37+
/// in the global shader name table.
38+
/// </para>
39+
/// </summary>
40+
public bool excludeFromPreloadedAssetsWhenBuildPlayer
41+
{
42+
get => m_ExcludeFromPreloadedAssetsWhenBuildPlayer;
43+
set => m_ExcludeFromPreloadedAssetsWhenBuildPlayer = value;
44+
}
45+
46+
protected static bool s_BuildingPlayer;
47+
1748
private class Postprocessor : AssetPostprocessor
1849
{
1950
private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____)
@@ -22,6 +53,47 @@ private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___
2253
}
2354
}
2455

56+
private class ExcludeFromBuild : IPreprocessBuildWithReport, IPostprocessBuildWithReport
57+
{
58+
int IOrderedCallback.callbackOrder => -1;
59+
60+
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
61+
{
62+
s_BuildingPlayer = true;
63+
64+
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
65+
{
66+
var settings = GetDefaultSettings(t);
67+
if (!settings || !settings.m_ExcludeFromPreloadedAssetsWhenBuildPlayer) continue;
68+
69+
PlayerSettings.SetPreloadedAssets(
70+
PlayerSettings.GetPreloadedAssets()
71+
.Where(x => x && x.GetType() != t)
72+
.ToArray());
73+
74+
Debug.Log($"[PreloadedProjectSettings] Build started: removed '{settings.name}' " +
75+
$"({t.Name}) from PreloadedAssets. " +
76+
$"It will be restored after build completes.");
77+
}
78+
}
79+
80+
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report)
81+
{
82+
s_BuildingPlayer = false;
83+
84+
Initialize();
85+
86+
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
87+
{
88+
var settings = GetDefaultSettings(t);
89+
if (!settings || !settings.m_ExcludeFromPreloadedAssetsWhenBuildPlayer) continue;
90+
91+
Debug.Log($"[PreloadedProjectSettings] Build finished: restored '{settings.name}' " +
92+
$"({t.Name}) to PreloadedAssets.");
93+
}
94+
}
95+
}
96+
2597
private class PreprocessBuildWithReport : IPreprocessBuildWithReport
2698
{
2799
int IOrderedCallback.callbackOrder => 0;
@@ -41,11 +113,11 @@ private static void Initialize()
41113
{
42114
// When create a new instance, automatically set it as default settings.
43115
defaultSettings = CreateInstance(t) as PreloadedProjectSettings;
44-
SetDefaultSettings(defaultSettings);
116+
if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings);
45117
}
46118
else if (GetPreloadedSettings(t).Length != 1)
47119
{
48-
SetDefaultSettings(defaultSettings);
120+
if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings);
49121
}
50122

51123
if (defaultSettings)
@@ -151,7 +223,7 @@ public static T instance
151223
return s_Instance;
152224
}
153225

154-
SetDefaultSettings(s_Instance);
226+
if (!s_BuildingPlayer) SetDefaultSettings(s_Instance);
155227
return s_Instance;
156228
}
157229
}

Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ public override int GetHashCode()
4545
}
4646

4747
private Dictionary<int, string> _cachedOptionalShaders = new Dictionary<int, string>();
48+
private Dictionary<string, Shader> _shaderByName;
4849

4950
[SerializeField]
5051
private List<StringPair> m_OptionalShaders = new List<StringPair>();
5152

5253
[SerializeField]
5354
internal ShaderVariantCollection m_Asset;
5455

56+
[SerializeField]
57+
private List<Shader> m_RegisteredShaders = new List<Shader>();
58+
5559
#if UNITY_EDITOR
5660
[SerializeField]
5761
private bool m_ErrorOnUnregisteredVariant = false;
@@ -63,6 +67,43 @@ public override int GetHashCode()
6367
public ShaderVariantCollection shaderVariantCollection => m_Asset;
6468
public Func<string, bool> onShaderRequested;
6569

70+
/// <summary>
71+
/// Build the runtime name-to-shader lookup from <see cref="m_RegisteredShaders"/>.
72+
/// When shaders are delivered via AssetBundles (i.e. not in the player build),
73+
/// <see cref="Shader.Find"/> cannot locate them by name. This lookup provides
74+
/// direct references serialized at edit-time as a reliable alternative.
75+
/// </summary>
76+
public void InitializeShaderLookup()
77+
{
78+
var count = m_RegisteredShaders.Count;
79+
if (count == 0) return;
80+
81+
if (_shaderByName == null)
82+
_shaderByName = new Dictionary<string, Shader>(count);
83+
else
84+
_shaderByName.Clear();
85+
86+
for (var i = 0; i < count; i++)
87+
{
88+
var s = m_RegisteredShaders[i];
89+
if (s)
90+
_shaderByName[s.name] = s;
91+
}
92+
}
93+
94+
/// <summary>
95+
/// Find a shader by name. Prefers the registered direct reference from
96+
/// <see cref="m_RegisteredShaders"/> when available, falls back to
97+
/// <see cref="Shader.Find"/> otherwise. This ensures correct behavior in both
98+
/// built-in delivery (PreloadedAssets) and external delivery (AssetBundle) modes.
99+
/// </summary>
100+
public Shader FindShaderByName(string name)
101+
{
102+
if (_shaderByName != null && _shaderByName.TryGetValue(name, out var shader) && shader)
103+
return shader;
104+
return Shader.Find(name);
105+
}
106+
66107
public Shader FindOptionalShader(Shader shader,
67108
string requiredName,
68109
string format,
@@ -74,7 +115,7 @@ public Shader FindOptionalShader(Shader shader,
74115
var id = shader.GetInstanceID();
75116
if (_cachedOptionalShaders.TryGetValue(id, out var optionalShaderName))
76117
{
77-
return Shader.Find(optionalShaderName);
118+
return FindShaderByName(optionalShaderName);
78119
}
79120

80121
// The shader has required name.
@@ -92,15 +133,15 @@ public Shader FindOptionalShader(Shader shader,
92133
{
93134
var pair = m_OptionalShaders[i];
94135
if (pair.key != shaderName) continue;
95-
optionalShader = Shader.Find(pair.value);
136+
optionalShader = FindShaderByName(pair.value);
96137
if (!optionalShader) continue;
97138
_cachedOptionalShaders[id] = pair.value;
98139
return optionalShader;
99140
}
100141

101142
// Find optional shader by format.
102143
optionalShaderName = string.Format(format, shaderName);
103-
optionalShader = Shader.Find(optionalShaderName);
144+
optionalShader = FindShaderByName(optionalShaderName);
104145
if (optionalShader)
105146
{
106147
_cachedOptionalShaders[id] = optionalShaderName;
@@ -110,13 +151,13 @@ public Shader FindOptionalShader(Shader shader,
110151
#if UNITY_EDITOR
111152
if (onShaderRequested?.Invoke(optionalShaderName) ?? false)
112153
{
113-
return Shader.Find(defaultOptionalShaderName);
154+
return FindShaderByName(defaultOptionalShaderName);
114155
}
115156
#endif
116157

117158
// Find default optional shader.
118159
_cachedOptionalShaders[id] = defaultOptionalShaderName;
119-
return Shader.Find(defaultOptionalShaderName);
160+
return FindShaderByName(defaultOptionalShaderName);
120161
}
121162

122163
#if UNITY_EDITOR
@@ -208,10 +249,48 @@ public void InitializeIfNeeded(Object owner)
208249
AssetDatabase.SaveAssets();
209250
}
210251

252+
SyncRegisteredShaders(owner);
211253
ClearCache();
212254
Profiler.EndSample();
213255
}
214256

257+
/// <summary>
258+
/// Sync <see cref="m_RegisteredShaders"/> based on the current delivery mode.
259+
/// When <see cref="PreloadedProjectSettings.excludeFromPreloadedAssetsWhenBuildPlayer"/> is enabled,
260+
/// extracts shader references from the SVC so they can be resolved at runtime
261+
/// without <see cref="Shader.Find"/>. Otherwise clears the list to avoid
262+
/// stale references and unnecessary asset dependencies.
263+
/// </summary>
264+
private void SyncRegisteredShaders(Object owner)
265+
{
266+
var settings = owner as PreloadedProjectSettings;
267+
var needsDirectRef = settings && settings.excludeFromPreloadedAssetsWhenBuildPlayer;
268+
269+
if (!needsDirectRef || !m_Asset)
270+
{
271+
if (m_RegisteredShaders.Count > 0)
272+
{
273+
m_RegisteredShaders.Clear();
274+
EditorUtility.SetDirty(owner);
275+
}
276+
return;
277+
}
278+
279+
var so = new SerializedObject(m_Asset);
280+
var shaders = so.FindProperty("m_Shaders");
281+
m_RegisteredShaders.Clear();
282+
for (var i = 0; i < shaders.arraySize; i++)
283+
{
284+
var shaderRef = shaders.GetArrayElementAtIndex(i)
285+
.FindPropertyRelative("first")
286+
.objectReferenceValue as Shader;
287+
if (shaderRef && !m_RegisteredShaders.Contains(shaderRef))
288+
m_RegisteredShaders.Add(shaderRef);
289+
}
290+
291+
EditorUtility.SetDirty(owner);
292+
}
293+
215294
internal void RegisterVariant(Material material, string path)
216295
{
217296
if (!material || !material.shader || !m_Asset) return;

Packages/src/Runtime/UIEffectProjectSettings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ public static Object LoadPreset(string presetName)
8383
#pragma warning restore CS0618
8484
}
8585

86+
#if !UNITY_EDITOR
87+
protected override void OnEnable()
88+
{
89+
base.OnEnable();
90+
m_ShaderVariantRegistry.InitializeShaderLookup();
91+
}
92+
#endif
93+
8694
#if UNITY_EDITOR
8795
private const string k_PresetDir = "UIEffectPresets";
8896
private const string k_PresetSaveDir = "Assets/ProjectSettings/" + k_PresetDir;
@@ -212,6 +220,7 @@ private static IEnumerable<UIEffectBase> FindAll()
212220
protected override void OnEnable()
213221
{
214222
base.OnEnable();
223+
m_ShaderVariantRegistry.InitializeShaderLookup();
215224
m_ShaderVariantRegistry.onShaderRequested = ShaderSampleImporter.ImportShaderIfSelected;
216225
m_ShaderVariantRegistry.ClearCache();
217226
}

0 commit comments

Comments
 (0)