Skip to content

Commit d47c0a0

Browse files
author
quyong
committed
feat: add option to remove from preloadedAssets when build player
1 parent 9a7535a commit d47c0a0

8 files changed

Lines changed: 226 additions & 11 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
# [3.5.0](https://github.com/mob-sakai/SoftMaskForUGUI/compare/3.4.1...3.5.0) (2025-11-08)
28

39

Packages/src/Editor/UISoftMaskProjectSettingsEditor.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,20 @@ internal class UISoftMaskProjectSettingsEditor : Editor
1111

1212
public override void OnInspectorGUI()
1313
{
14+
serializedObject.Update();
1415
EditorGUIUtility.labelWidth = 180;
15-
base.OnInspectorGUI();
16+
17+
// Setting
18+
EditorGUILayout.LabelField("Setting", EditorStyles.boldLabel);
19+
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_SoftMaskEnabled"));
20+
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_StereoEnabled"));
21+
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_TransformSensitivity"));
22+
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_SoftMaskable"));
23+
24+
// Editor
25+
EditorGUILayout.Space();
26+
EditorGUILayout.LabelField("Editor", EditorStyles.boldLabel);
27+
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_HideGeneratedComponents"));
1628

1729
// Shader registry.
1830
EditorGUILayout.Space();
@@ -29,6 +41,34 @@ public override void OnInspectorGUI()
2941
}
3042

3143
_shaderVariantRegistryEditor.Draw();
44+
45+
// Advanced
46+
EditorGUILayout.Space();
47+
EditorGUILayout.LabelField("Advanced", EditorStyles.boldLabel);
48+
var excludeProp = serializedObject.FindProperty("m_ExcludeFromPreloadedAssetsWhenBuildPlayer");
49+
EditorGUILayout.PropertyField(excludeProp);
50+
51+
if (excludeProp.boolValue)
52+
{
53+
var shadersProp = serializedObject.FindProperty("m_ShaderVariantRegistry")
54+
.FindPropertyRelative("m_RegisteredShaders");
55+
EditorGUI.BeginDisabledGroup(true);
56+
EditorGUI.indentLevel++;
57+
for (var i = 0; i < shadersProp.arraySize; i++)
58+
{
59+
EditorGUILayout.PropertyField(shadersProp.GetArrayElementAtIndex(i), GUIContent.none);
60+
}
61+
EditorGUI.indentLevel--;
62+
EditorGUI.EndDisabledGroup();
63+
64+
if (shadersProp.arraySize == 0)
65+
{
66+
EditorGUILayout.HelpBox(
67+
"No shaders registered. Re-import or modify the settings asset to trigger sync.",
68+
MessageType.Warning);
69+
}
70+
}
71+
3272
serializedObject.ApplyModifiedProperties();
3373

3474
// Upgrade All Assets For V3.

Packages/src/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,13 @@ You can adjust the project-wide settings for SoftMaskForUGUI. (`Edit > Project S
415415
> - The setting file is usually saved in `Assets/ProjectSettings/UISoftMaskProjectSettings.asset`. Include this file in your version control system.
416416
> - The setting file is automatically added as a preloaded asset in `ProjectSettings/ProjectSettings.asset`.
417417
418+
#### Advanced
419+
420+
- **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.
421+
- Use this for AssetBundle / Addressables based hot-update workflows where settings and shaders are delivered externally instead of being built into the player.
422+
- Shader references are serialized from the `ShaderVariantCollection` at edit-time, so `Shader.Find()` is not required at runtime.
423+
- When toggled off, serialized shader references are automatically cleared.
424+
418425
<br><br>
419426

420427
### Usage with Scripts

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.UISoftMaskInternal
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
@@ -46,13 +46,17 @@ public override int GetHashCode()
4646
}
4747

4848
private Dictionary<int, string> _cachedOptionalShaders = new Dictionary<int, string>();
49+
private Dictionary<string, Shader> _shaderByName;
4950

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

5354
[SerializeField]
5455
internal ShaderVariantCollection m_Asset;
5556

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

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

81122
// The shader has required name.
@@ -91,7 +132,7 @@ public Shader FindOptionalShader(Shader shader,
91132
foreach (var pair in m_OptionalShaders)
92133
{
93134
if (pair.key != shaderName) continue;
94-
optionalShader = Shader.Find(pair.value);
135+
optionalShader = FindShaderByName(pair.value);
95136
if (optionalShader)
96137
{
97138
_cachedOptionalShaders[id] = pair.value;
@@ -101,7 +142,7 @@ public Shader FindOptionalShader(Shader shader,
101142

102143
// Find optional shader by format.
103144
optionalShaderName = string.Format(format, shaderName);
104-
optionalShader = Shader.Find(optionalShaderName);
145+
optionalShader = FindShaderByName(optionalShaderName);
105146
if (optionalShader)
106147
{
107148
_cachedOptionalShaders[id] = optionalShaderName;
@@ -111,13 +152,13 @@ public Shader FindOptionalShader(Shader shader,
111152
#if UNITY_EDITOR
112153
if (onShaderRequested?.Invoke(optionalShaderName) ?? false)
113154
{
114-
return Shader.Find(defaultOptionalShaderName);
155+
return FindShaderByName(defaultOptionalShaderName);
115156
}
116157
#endif
117158

118159
// Find default optional shader.
119160
_cachedOptionalShaders[id] = defaultOptionalShaderName;
120-
return Shader.Find(defaultOptionalShaderName);
161+
return FindShaderByName(defaultOptionalShaderName);
121162
}
122163

123164
#if UNITY_EDITOR
@@ -204,10 +245,48 @@ public void InitializeIfNeeded(Object owner)
204245
AssetDatabase.SaveAssets();
205246
}
206247

248+
SyncRegisteredShaders(owner);
207249
ClearCache();
208250
Profiler.EndSample();
209251
}
210252

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

Packages/src/Runtime/MaskingShape/TerminalMaskingShape.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ protected override void OnEnable()
3131
{
3232
if (!s_SharedTerminalMaterial)
3333
{
34-
s_SharedTerminalMaterial = new Material(Shader.Find("Hidden/UI/TerminalMaskingShape"))
34+
s_SharedTerminalMaterial = new Material(UISoftMaskProjectSettings.shaderRegistry
35+
.FindShaderByName("Hidden/UI/TerminalMaskingShape"))
3536
{
3637
hideFlags = HideFlags.DontSave | HideFlags.NotEditable
3738
};

Packages/src/Runtime/UISoftMaskProjectSettings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ private static void ResetAllSoftMasks()
118118
InternalListPool<SoftMask>.Return(ref softMasks);
119119
}
120120

121+
#if !UNITY_EDITOR
122+
protected override void OnEnable()
123+
{
124+
base.OnEnable();
125+
m_ShaderVariantRegistry.InitializeShaderLookup();
126+
}
127+
#endif
128+
121129
#if UNITY_EDITOR
122130
[InitializeOnLoadMethod]
123131
private static void InitializeOnLoadMethod()
@@ -172,6 +180,7 @@ private static void InitializeOnLoadMethod()
172180
protected override void OnEnable()
173181
{
174182
base.OnEnable();
183+
m_ShaderVariantRegistry.InitializeShaderLookup();
175184
m_ShaderVariantRegistry.onShaderRequested = ShaderSampleImporter.ImportShaderIfSelected;
176185
m_ShaderVariantRegistry.ClearCache();
177186
}

Packages/src/Runtime/Utilities/SoftMaskUtils.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ private static Material GetSoftMaskingMaterial(ref Material mat, BlendOp op)
152152
{
153153
if (mat) return mat;
154154

155-
mat = new Material(Shader.Find("Hidden/UI/SoftMask"))
155+
mat = new Material(UISoftMaskProjectSettings.shaderRegistry
156+
.FindShaderByName("Hidden/UI/SoftMask"))
156157
{
157158
hideFlags = HideFlags.DontSave | HideFlags.NotEditable
158159
};

0 commit comments

Comments
 (0)