Compare commits

..

8 Commits

Author SHA1 Message Date
mob-sakai
64a050f17c fix: Support for skipping "reload domain"
close #406
2026-06-24 19:56:50 +09:00
mob-sakai
d6d36006ad docs: update readme 2026-06-24 19:56:50 +09:00
mob-sakai
63d5eedde1 fix: rename UIParticleProjectSettings.enableLinearToGamma to autoColorCorrection 2026-06-24 19:56:50 +09:00
mob-sakai
c05c1d5f21 fix: UI/Additive shader does not support RectMask2D softness. 2026-06-24 19:56:50 +09:00
mob-sakai
738021a9b3 fix: there is a compilation error in Unity 2019.2 or earlier
close #407
2026-06-24 19:56:50 +09:00
mob-sakai
b4cac73fb1 chore: update internal 2026-06-24 19:56:50 +09:00
mob-sakai
32c9926f10 feat: preview the ParticleSystem playback when selecting a UIParticle in the Editor 2026-06-24 19:56:50 +09:00
mob-sakai
f961b8dc89 chore: fix workflow for pwn request 2026-06-12 16:27:44 +09:00
23 changed files with 644 additions and 73 deletions

View File

@@ -38,7 +38,7 @@ jobs:
@semantic-release/changelog @semantic-release/changelog
@semantic-release/git @semantic-release/git
env: env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GITHUB_TOKEN: ${{ github.token }}
- id: summary - id: summary
run: | run: |

View File

@@ -27,7 +27,7 @@ on:
- "!*" - "!*"
paths-ignore: paths-ignore:
- "**.md" - "**.md"
pull_request_target: pull_request:
types: types:
- opened - opened
- reopened - reopened
@@ -39,16 +39,29 @@ jobs:
setup: setup:
name: ⚙️ Setup name: ⚙️ Setup
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: {} # No permissions needed for setup job
outputs: outputs:
unityVersions: ${{ steps.setup.outputs.unityVersions }} unityVersions: ${{ steps.setup.outputs.unityVersions }}
steps: steps:
- name: 🔑 Secrets check
run: |
if [ -z "$UNITY_EMAIL" ] || [ -z "$UNITY_PASSWORD" ] || [ -z "UNITY_LICENSE" ]; then
echo "Error: UNITY_EMAIL, UNITY_PASSWORD, and UNITY_LICENSE secrets must be set." | tee -a $GITHUB_STEP_SUMMARY >&2
echo "Error: See https://game.ci/docs/github/test-runner#basic-setup" | tee -a $GITHUB_STEP_SUMMARY >&2
echo "Error: Set the secrets at ${{ github.server_url }}/${{ github.repository }}/settings/secrets/actions" | tee -a $GITHUB_STEP_SUMMARY >&2
exit 1
fi
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
- name: ⚙️ Find target Unity versions - name: ⚙️ Find target Unity versions
id: setup id: setup
run: | run: |
echo "==== Target Unity Versions ====" echo "==== Target Unity Versions ===="
LATEST_VERSIONS=`npx unity-changeset@latest list --versions --latest-patch --min ${MINIMUM_VERSION} --json --all --ignore-alpha` LATEST_VERSIONS=`npx -y unity-changeset@latest list --json --versions --all --latest-patch --ignore-alpha --min ${MINIMUM_VERSION}`
if [ "${{ inputs.usePeriodVersions }}" = "true" ]; then if [ "${{ inputs.usePeriodVersions }}" = "true" ]; then
ADDITIONAL_VERSIONS=`npx unity-changeset list --versions --grep '0f' --min ${MINIMUM_VERSION} --json --ignore-alpha` ADDITIONAL_VERSIONS=`npx -y unity-changeset@latest list --json --versions --ignore-alpha --min ${MINIMUM_VERSION} --grep '0f'`
else else
ADDITIONAL_VERSIONS=[] ADDITIONAL_VERSIONS=[]
fi fi
@@ -66,29 +79,13 @@ jobs:
needs: setup needs: setup
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 6 max-parallel: 8
matrix: matrix:
unityVersion: ${{ fromJson(needs.setup.outputs.unityVersions) }} unityVersion: ${{ fromJson(needs.setup.outputs.unityVersions) }}
steps: steps:
- name: 🚚 Checkout ($${{ github.ref }}) - name: 🚚 Checkout ($${{ github.ref }})
if: github.event_name == 'push'
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: 🚚 Checkout pull request (pull_request_target)
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: 🚚 Marge pull request (pull_request_target)
if: github.event_name == 'pull_request_target'
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git rebase ${{ github.event.pull_request.base.sha }}
git log --oneline -n 10
- name: 📥 Cache library - name: 📥 Cache library
uses: actions/cache@v5 uses: actions/cache@v5
with: with:

View File

@@ -3,7 +3,7 @@
"com.coffee.development": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/Development", "com.coffee.development": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/Development",
"com.coffee.minimal-resource": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/MinimalResource", "com.coffee.minimal-resource": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/MinimalResource",
"com.coffee.nano-monitor": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/NanoMonitor", "com.coffee.nano-monitor": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/NanoMonitor",
"com.unity.ide.rider": "3.0.31", "com.unity.ide.rider": "3.0.36",
"com.unity.test-framework": "1.1.33", "com.unity.test-framework": "1.1.33",
"com.unity.modules.animation": "1.0.0", "com.unity.modules.animation": "1.0.0",
"com.unity.modules.physics": "1.0.0" "com.unity.modules.physics": "1.0.0"

View File

@@ -40,7 +40,7 @@
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
}, },
"com.unity.ide.rider": { "com.unity.ide.rider": {
"version": "3.0.31", "version": "3.0.36",
"depth": 0, "depth": 0,
"source": "registry", "source": "registry",
"dependencies": { "dependencies": {

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -8,12 +9,11 @@ using UnityEngine;
using UnityEngine.Profiling; using UnityEngine.Profiling;
using UnityEngine.UI; using UnityEngine.UI;
using Coffee.UIParticleInternal; using Coffee.UIParticleInternal;
using Object = UnityEngine.Object;
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.Overlays; using UnityEditor.Overlays;
#else #else
using System;
using System.Reflection; using System.Reflection;
using Object = UnityEngine.Object;
#endif #endif
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
@@ -66,6 +66,7 @@ namespace Coffee.UIExtensions
private ReorderableList _ro; private ReorderableList _ro;
private bool _showMax; private bool _showMax;
private bool _is3DScaleMode; private bool _is3DScaleMode;
private GameObject[] _gameObjects;
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>(); private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
#if UNITY_2018 || UNITY_2019 #if UNITY_2018 || UNITY_2019
@@ -81,6 +82,18 @@ namespace Coffee.UIExtensions
"_ColorMask" "_ColorMask"
}; };
/// <summary>
/// ドメインリロード無効化対応:静的キャッシュをクリア
/// </summary>
[InitializeOnLoadMethod]
private static void OnDomainReload()
{
s_Shaders.Clear();
#if UNITY_2018 || UNITY_2019
s_Streams.Clear();
#endif
}
//################################ //################################
// Public/Protected Members. // Public/Protected Members.
//################################ //################################
@@ -183,6 +196,13 @@ namespace Coffee.UIExtensions
y.hasMultipleDifferentValues || y.hasMultipleDifferentValues ||
z.hasMultipleDifferentValues; z.hasMultipleDifferentValues;
} }
// Add temporary ParticleSystem for preview if enabled.
if (UIParticleProjectSettings.s_PreviewOnSelectOnSelect)
{
_gameObjects = targets.OfType<UIParticle>().Select(x => x.gameObject).ToArray();
ParticleSystemPreviewSystem.Register(_gameObjects);
}
} }
/// <summary> /// <summary>
@@ -197,7 +217,10 @@ namespace Coffee.UIExtensions
serializedObject.Update(); serializedObject.Update();
// Maskable // Maskable
EditorGUILayout.PropertyField(_maskable); if (_maskable != null)
{
EditorGUILayout.PropertyField(_maskable);
}
// Scale // Scale
EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4); EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4);
@@ -344,6 +367,10 @@ namespace Coffee.UIExtensions
} }
} }
#endif #endif
// Remove the temporary ParticleSystem.
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
Profiler.EndSample(); Profiler.EndSample();
} }
@@ -482,7 +509,7 @@ namespace Coffee.UIExtensions
#endif #endif
} }
private static bool FixButton(bool show, string text) private static bool FixButton(bool show, string text, GUIContent buttonText = null)
{ {
if (!show) return false; if (!show) return false;
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true))) using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true)))
@@ -490,7 +517,7 @@ namespace Coffee.UIExtensions
EditorGUILayout.HelpBox(text, MessageType.Warning, true); EditorGUILayout.HelpBox(text, MessageType.Warning, true);
using (new EditorGUILayout.VerticalScope()) using (new EditorGUILayout.VerticalScope())
{ {
return GUILayout.Button(s_ContentFix, GUILayout.Width(30)); return GUILayout.Button(buttonText ?? s_ContentFix, GUILayout.ExpandWidth(false));
} }
} }
} }

View File

@@ -183,9 +183,14 @@ _This package requires **Unity 2018.3 or later**._
- **Time Scale Multiplier:** Time scale multiplier. - **Time Scale Multiplier:** Time scale multiplier.
- **Rendering Order**: The ParticleSystem list to be rendered. You can change the order and the materials. - **Rendering Order**: The ParticleSystem list to be rendered. You can change the order and the materials.
**NOTE:** Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order > [!TIPS]
> Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order
and z-position. and z-position.
> [!TIPS]
> When a `GameObject` with this component is selected in the editor, a temporary `ParticleSystem` is added if needed so you can preview the effect in the Scene view.
> The generated `ParticleSystem` is marked with `HideFlags.DontSave`, so it is neither saved nor included in builds.
<br><br> <br><br>
### Basic Usage ### Basic Usage
@@ -195,6 +200,10 @@ and z-position.
2. Adjust the ParticleSystem as you like. 2. Adjust the ParticleSystem as you like.
![particle1](https://user-images.githubusercontent.com/12690315/95007359-ca385200-0649-11eb-8383-627c9750bda8.png) ![particle1](https://user-images.githubusercontent.com/12690315/95007359-ca385200-0649-11eb-8383-627c9750bda8.png)
> [!Tips]
> Adding a `UIParticle` to the parent is the recommended setup rather than attaching it directly to the `ParticleSystem`.
> When using `ParticleSystem.emission.rateOverDistance`, it is recommended to move the transform of `UIParticle` rather than the `ParticleSystem`.
<br> <br>
### Usage with Your Existing ParticleSystem Prefab ### Usage with Your Existing ParticleSystem Prefab
@@ -256,13 +265,34 @@ uiParticle.Stop();
- **Unscaled Time:** Update with unscaled delta time. - **Unscaled Time:** Update with unscaled delta time.
- **OnAttracted**: An event called when attracting is complete (per particle). - **OnAttracted**: An event called when attracting is complete (per particle).
<br><br>
### Component: ParticleSystemPreviewer
`ParticleSystemPreviewer` is used to preview a ParticleSystem in the editor.
- When a `GameObject` with this component is selected in the editor, a temporary `ParticleSystem` is added if needed so you can preview the effect in the Scene view.
- The generated `ParticleSystem` is marked with `HideFlags.DontSave`, so it is neither saved nor included in builds.
<br><br> <br><br>
### Project Settings ### Project Settings
![](https://github.com/user-attachments/assets/befc7f34-fb47-4006-831a-eba79fda11ca) You can adjust the project-wide settings for `UI Particle`. (`Edit > Project Settings > UI > UI Particle`)
- Click `Edit > Project Settings` to open the Project Settings window and then select `UI > UI Particle` category. ![](https://github.com/mob-sakai/mob-sakai/releases/download/docs/1782270746550.png)
#### Settings
- **Enable Linear To Gamma**: Automatically correct the color space of the mesh.
#### Editor
- **Hide Generated Component**: Automatically hide the generated `UIParticleRenderer` component and `UIParticle BakingCamera`.
- **Preview On Select**: When selecting UIParticle, a temporary ParticleSystem is generated for preview.
<br><br> <br><br>

View File

@@ -44,7 +44,7 @@ namespace Coffee.UIExtensions
break; break;
case ShaderPropertyType.Float: case ShaderPropertyType.Float:
case ShaderPropertyType.Range: case ShaderPropertyType.Range:
material.SetFloat(id, mpb.GetFloat(id)); material.SetFloat(id, mpb.GetFloat(id));
break; break;
case ShaderPropertyType.Texture: case ShaderPropertyType.Texture:
material.SetTexture(id, mpb.GetTexture(id)); material.SetTexture(id, mpb.GetTexture(id));

View File

@@ -12,6 +12,33 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
internal static class ComponentExtensions internal static class ComponentExtensions
{ {
#if !UNITY_2019_2_OR_NEWER
public static bool TryGetComponent<T>(this GameObject self, out T component)
where T : Component
{
if (self == null)
{
component = null;
return false;
}
component = self.GetComponent<T>();
return component != null;
}
public static bool TryGetComponent<T>(this Component self, out T component)
where T : Component
{
if (self == null)
{
component = null;
return false;
}
return self.gameObject.TryGetComponent(out component);
}
#endif
/// <summary> /// <summary>
/// Get components in children of a specific type in the hierarchy of a GameObject. /// Get components in children of a specific type in the hierarchy of a GameObject.
/// </summary> /// </summary>

View File

@@ -14,6 +14,15 @@ namespace Coffee.UIParticleInternal
public abstract class PreloadedProjectSettings : ScriptableObject public abstract class PreloadedProjectSettings : ScriptableObject
#if UNITY_EDITOR #if UNITY_EDITOR
{ {
[Tooltip("When enabled, this settings asset will be added to PlayerSettings.preloadedAssets in build.\n\n" +
"When disable, you should load this settings via Resources, AssetBundles or Addressables to use.")]
[SerializeField]
[Header("Advanced")]
[HideInInspector]
private bool m_PreLoadSettingsInBuild = true;
protected static bool s_BuildingPlayer;
private class Postprocessor : AssetPostprocessor private class Postprocessor : AssetPostprocessor
{ {
private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____) private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____)
@@ -22,12 +31,35 @@ namespace Coffee.UIParticleInternal
} }
} }
private class PreprocessBuildWithReport : IPreprocessBuildWithReport private class ExcludeFromBuild : IPreprocessBuildWithReport, IPostprocessBuildWithReport
{ {
int IOrderedCallback.callbackOrder => 0; int IOrderedCallback.callbackOrder => 0;
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report) void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
{ {
AssetDatabase.Refresh();
Initialize();
s_BuildingPlayer = true;
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{
var settings = GetDefaultSettings(t);
if (!settings || settings.m_PreLoadSettingsInBuild) continue;
PlayerSettings.SetPreloadedAssets(
PlayerSettings.GetPreloadedAssets()
.Where(x => x && x.GetType() != t)
.ToArray());
Debug.Log($"[PreloadedProjectSettings] Build started: removed '{settings.name}' " +
$"({t.Name}) from PreloadedAssets. " +
$"It will be restored after build completes.");
}
}
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report)
{
s_BuildingPlayer = false;
Initialize(); Initialize();
} }
} }
@@ -39,13 +71,16 @@ namespace Coffee.UIParticleInternal
var defaultSettings = GetDefaultSettings(t); var defaultSettings = GetDefaultSettings(t);
if (defaultSettings == null) if (defaultSettings == null)
{ {
// When create a new instance, automatically set it as default settings. if (!s_BuildingPlayer)
defaultSettings = CreateInstance(t) as PreloadedProjectSettings; {
SetDefaultSettings(defaultSettings); // When create a new instance, automatically set it as default settings.
defaultSettings = CreateInstance(t) as PreloadedProjectSettings;
SetDefaultSettings(defaultSettings);
}
} }
else if (GetPreloadedSettings(t).Length != 1) else if (GetPreloadedSettings(t).Length != 1)
{ {
SetDefaultSettings(defaultSettings); if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings);
} }
if (defaultSettings != null) if (defaultSettings != null)
@@ -73,7 +108,7 @@ namespace Coffee.UIParticleInternal
protected static PreloadedProjectSettings GetDefaultSettings(Type type) protected static PreloadedProjectSettings GetDefaultSettings(Type type)
{ {
return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings
?? AssetDatabase.FindAssets($"t:{nameof(PreloadedProjectSettings)}") ?? AssetDatabase.FindAssets($"t:{type.Name}")
.Select(AssetDatabase.GUIDToAssetPath) .Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<PreloadedProjectSettings>) .Select(AssetDatabase.LoadAssetAtPath<PreloadedProjectSettings>)
.FirstOrDefault(x => x != null && x.GetType() == type); .FirstOrDefault(x => x != null && x.GetType() == type);
@@ -120,6 +155,35 @@ namespace Coffee.UIParticleInternal
{ {
} }
} }
internal abstract class PreloadedProjectSettingsEditor : Editor
{
private SerializedProperty _preLoadSettingsInBuild;
protected virtual void OnEnable()
{
_preLoadSettingsInBuild = serializedObject.FindProperty("m_PreLoadSettingsInBuild");
}
protected void DrawPreLoadSettingsInBuild(string packageName)
{
EditorGUILayout.PropertyField(_preLoadSettingsInBuild);
if (!_preLoadSettingsInBuild.boolValue)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.HelpBox(
$"{target.GetType().Name} asset will not be built in.\n" +
$"please load manually from Resources, AssetBundle, or Addressables before using {packageName}.",
MessageType.Warning);
if (GUILayout.Button("Ping"))
{
EditorGUIUtility.PingObject(target);
}
EditorGUILayout.EndHorizontal();
}
}
}
#else #else
{ {
} }
@@ -131,6 +195,13 @@ namespace Coffee.UIParticleInternal
private static T s_Instance; private static T s_Instance;
#if UNITY_EDITOR #if UNITY_EDITOR
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnDomainReload()
{
s_Instance = null;
}
#endif
private string _jsonText; private string _jsonText;
public static bool hasInstance => s_Instance != null; public static bool hasInstance => s_Instance != null;
@@ -151,7 +222,7 @@ namespace Coffee.UIParticleInternal
return s_Instance; return s_Instance;
} }
SetDefaultSettings(s_Instance); if (!s_BuildingPlayer) SetDefaultSettings(s_Instance);
return s_Instance; return s_Instance;
} }
} }
@@ -193,9 +264,12 @@ namespace Coffee.UIParticleInternal
} }
EditorApplication.playModeStateChanged += OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#else
if (s_Instance && s_Instance != this)
{
Destroy(s_Instance);
}
#endif #endif
if (s_Instance != null) return;
s_Instance = this as T; s_Instance = this as T;
} }

View File

@@ -14,7 +14,7 @@ namespace Coffee.UIParticleInternal
UIExtraCallbacks.onLateAfterCanvasRebuild += ClearAllCache; UIExtraCallbacks.onLateAfterCanvasRebuild += ClearAllCache;
} }
#if UNITY_EDITOR #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Clear() private static void Clear()
{ {

View File

@@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 4f9f22bb079324476b1473030ad9fec3 guid: 4f9f22bb079324476b1473030ad9fec3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -13,7 +13,9 @@ namespace Coffee.UIParticleInternal
public static int count => s_Repository.count; public static int count => s_Repository.count;
#if UNITY_EDITOR public static Func<string, Shader> onShaderFind = Shader.Find;
#if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Clear() public static void Clear()
{ {
@@ -48,7 +50,7 @@ namespace Coffee.UIParticleInternal
public static void Get(Hash128 hash, ref Material material, string shaderName) public static void Get(Hash128 hash, ref Material material, string shaderName)
{ {
Profiler.BeginSample("(COF)[MaterialRepository] Get"); Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, x => new Material(Shader.Find(x)) s_Repository.Get(hash, ref material, x => new Material(onShaderFind(x))
{ {
hideFlags = HideFlags.DontSave | HideFlags.NotEditable hideFlags = HideFlags.DontSave | HideFlags.NotEditable
}, shaderName); }, shaderName);
@@ -61,7 +63,7 @@ namespace Coffee.UIParticleInternal
public static void Get(Hash128 hash, ref Material material, string shaderName, string[] keywords) public static void Get(Hash128 hash, ref Material material, string shaderName, string[] keywords)
{ {
Profiler.BeginSample("(COF)[MaterialRepository] Get"); Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, x => new Material(Shader.Find(x.shaderName)) s_Repository.Get(hash, ref material, x => new Material(onShaderFind(x.shaderName))
{ {
hideFlags = HideFlags.DontSave | HideFlags.NotEditable, hideFlags = HideFlags.DontSave | HideFlags.NotEditable,
shaderKeywords = x.keywords shaderKeywords = x.keywords

View File

@@ -48,15 +48,10 @@ namespace Coffee.UIParticleInternal
{ {
if (obj == null) return; if (obj == null) return;
#if UNITY_EDITOR #if UNITY_EDITOR
if (Application.isEditor) Object.DestroyImmediate(obj, true);
{ #else
Object.DestroyImmediate(obj); Object.Destroy(obj);
}
else
#endif #endif
{
Object.Destroy(obj);
}
} }
[Conditional("UNITY_EDITOR")] [Conditional("UNITY_EDITOR")]
@@ -114,7 +109,7 @@ namespace Coffee.UIParticleInternal
{ {
if (Misc.isBatchOrBuilding) return; if (Misc.isBatchOrBuilding) return;
var types = TypeCache.GetTypesWithAttribute<IconAttribute>(); var types = TypeCache.GetTypesWithAttribute(typeof(IconAttribute));
var scripts = MonoImporter.GetAllRuntimeMonoScripts(); var scripts = MonoImporter.GetAllRuntimeMonoScripts();
foreach (var type in types) foreach (var type in types)
{ {

View File

@@ -0,0 +1,88 @@
#if !UNITY_2019_2_OR_NEWER
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Coffee.UIParticleInternal
{
public static class TypeCache
{
private static readonly object s_Lock = new object();
private static readonly Dictionary<Type, Type[]> s_DerivedTypesCache = new Dictionary<Type, Type[]>();
private static readonly Dictionary<Type, Type[]> s_AttributeTypesCache = new Dictionary<Type, Type[]>();
public static IEnumerable<Type> GetTypesDerivedFrom(Type baseType)
{
lock (s_Lock)
{
if (s_DerivedTypesCache.TryGetValue(baseType, out var cached))
{
return cached;
}
var types = new List<Type>();
foreach (var t in GetAllLoadableTypes())
{
if (t != baseType && baseType.IsAssignableFrom(t))
{
types.Add(t);
}
}
return s_DerivedTypesCache[baseType] = types.ToArray();
}
}
public static IEnumerable<Type> GetTypesWithAttribute(Type attr)
{
lock (s_Lock)
{
if (s_AttributeTypesCache.TryGetValue(attr, out var cached))
{
return cached;
}
var types = new List<Type>();
foreach (var t in GetAllLoadableTypes())
{
if (t.GetCustomAttributes(attr, inherit: true).Length > 0)
{
types.Add(t);
}
}
return s_AttributeTypesCache[attr] = types.ToArray();
}
}
private static IEnumerable<Type> GetAllLoadableTypes()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type[] types;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types;
}
if (types == null)
{
continue;
}
foreach (var t in types)
{
if (t != null)
{
yield return t;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc1207b657ed74ec19e459664d06925f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -82,14 +82,22 @@ namespace Coffee.UIParticleInternal
message: "InitializeAfterCanvasRebuild"); message: "InitializeAfterCanvasRebuild");
} }
#if UNITY_EDITOR #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#elif UNITY_EDITOR
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
#endif #else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {
Canvas.willRenderCanvases -= OnAfterCanvasRebuild; Canvas.willRenderCanvases -= OnAfterCanvasRebuild;
s_IsInitializedAfterCanvasRebuild = false; s_IsInitializedAfterCanvasRebuild = false;
s_AfterCanvasRebuildAction.Clear();
s_LateAfterCanvasRebuildAction.Clear();
s_BeforeCanvasRebuildAction.Clear();
s_OnScreenSizeChangedAction.Clear();
s_LastScreenSize = default;
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,236 @@
using System.Collections.Generic;
using System.Linq;
using Coffee.UIParticleInternal;
using UnityEditor;
using UnityEngine;
namespace Coffee.UIExtensions
{
[Icon("Packages/com.coffee.ui-particle/Editor/UIParticleIcon.png")]
[ExecuteAlways]
internal class ParticleSystemPreviewer : MonoBehaviour
{
// Do nothing.
}
#if UNITY_EDITOR
[CustomEditor(typeof(ParticleSystemPreviewer))]
[CanEditMultipleObjects]
internal class ParticleSystemPreviewerEditor : Editor
{
private GameObject[] _gameObjects;
private void OnEnable()
{
_gameObjects = targets.OfType<ParticleSystemPreviewer>().Select(x => x.gameObject).ToArray();
ParticleSystemPreviewSystem.Register(_gameObjects);
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
ParticleSystemPreviewSystem.DrawWarningForPermanent(_gameObjects);
}
}
/// <summary>
/// This class manages temporary ParticleSystems for preview purposes.
/// When previewing in the editor, it is common to place an empty ParticleSystem as the root, but it consumes memory at runtime if included in the build.
/// The temporary ParticleSystems created by this class only exist when the specified GameObject is selected, and are automatically deleted when the selection is cleared.
/// </summary>
internal class ParticleSystemPreviewSystem : ScriptableSingleton<ParticleSystemPreviewSystem>
{
private const HideFlags k_TemporaryHideFlags = HideFlags.DontSave | HideFlags.NotEditable;
[SerializeField]
private List<GameObject> m_PreviewObjects = new List<GameObject>();
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#else
[InitializeOnLoadMethod]
#endif
public static void Initialize()
{
instance.OnSelectionChanged();
Selection.selectionChanged -= instance.OnSelectionChanged;
Selection.selectionChanged += instance.OnSelectionChanged;
}
/// <summary>
/// Adds a temporary ParticleSystem to the specified GameObject for preview purposes.
/// </summary>
public static void Register(GameObject[] targets)
{
foreach (var target in targets)
{
Register(target);
}
}
/// <summary>
/// Adds a temporary ParticleSystem to the specified GameObject for preview purposes.
/// </summary>
public static void Register(GameObject target)
{
if (!target) return;
if (EditorApplication.isPlaying) return;
if (instance.m_PreviewObjects.Contains(target)) return;
if (target.TryGetComponent<ParticleSystem>(out var ps))
{
if (ps.hideFlags == k_TemporaryHideFlags)
{
RegisterParticleSystem(ps);
}
return;
}
// Create temporary ParticleSystem for preview.
RegisterParticleSystem(target.AddComponent<ParticleSystem>());
}
/// <summary>
/// Removes the temporary ParticleSystem associated with the specified GameObject.
/// </summary>
/// <param name="target"></param>
public static void Unregister(GameObject target)
{
if (!target) return;
var index = instance.m_PreviewObjects.IndexOf(target);
if (index < 0) return;
instance.m_PreviewObjects.RemoveAt(index);
if (HasTemporaryParticleSystem(target))
{
RemoveParticleSystem(target);
}
}
private static void RegisterParticleSystem(ParticleSystem ps)
{
if (!ps) return;
ps.hideFlags = k_TemporaryHideFlags;
var emission = ps.emission;
emission.enabled = false;
var shape = ps.shape;
shape.enabled = false;
if (ps.TryGetComponent<ParticleSystemRenderer>(out var psr))
{
psr.enabled = false;
psr.hideFlags = k_TemporaryHideFlags;
}
instance.m_PreviewObjects.Add(ps.gameObject);
EditorUtility.SetDirty(ps.gameObject);
}
/// <summary>
/// Removes the temporary ParticleSystem associated with the specified GameObject.
/// </summary>
/// <param name="target"></param>
private static void RemoveParticleSystem(GameObject target)
{
if (target.TryGetComponent<ParticleSystem>(out var ps))
{
Misc.DestroyImmediate(ps);
EditorUtility.SetDirty(target);
}
if (target.TryGetComponent<ParticleSystem>(out var psr))
{
Misc.DestroyImmediate(psr);
EditorUtility.SetDirty(target);
}
}
/// <summary>
/// Checks if the specified GameObject has a temporary ParticleSystem.
/// </summary>
private static bool HasTemporaryParticleSystem(GameObject target)
{
return target
&& instance.m_PreviewObjects.Contains(target)
&& target.TryGetComponent<ParticleSystem>(out var ps)
&& ps.hideFlags == k_TemporaryHideFlags;
}
/// <summary>
/// Checks if the specified GameObject has a permanent ParticleSystem.
/// </summary>
private static bool HasPermanentParticleSystem(GameObject target)
{
return target
&& target.TryGetComponent<ParticleSystem>(out var ps)
&& ps.hideFlags != k_TemporaryHideFlags;
}
private void OnSelectionChanged()
{
var selectedGameObjects = Selection.gameObjects;
for (var i = m_PreviewObjects.Count - 1; 0 <= i; i--)
{
var go = m_PreviewObjects[i];
if (!go)
{
m_PreviewObjects.RemoveAt(i);
}
else if (!selectedGameObjects.Contains(go))
{
Unregister(go);
}
}
}
public static void DrawWarningForTemporary(GameObject[] gameObjects)
{
if (gameObjects == null || gameObjects.Length == 0 || !gameObjects.Any(HasTemporaryParticleSystem)) return;
if (WarningButton("The temporary ParticleSystem for preview is attached.\n" +
"It will be removed when exiting edit mode.", "Remove"))
{
foreach (var go in gameObjects)
{
if (HasTemporaryParticleSystem(go))
{
RemoveParticleSystem(go);
}
}
}
}
public static void DrawWarningForPermanent(GameObject[] gameObjects)
{
if (gameObjects == null || gameObjects.Length == 0 || !gameObjects.Any(HasPermanentParticleSystem)) return;
if (WarningButton("The permanent ParticleSystem is attached.\n" +
"It will be included in build.", "Remove"))
{
foreach (var go in gameObjects)
{
if (HasPermanentParticleSystem(go))
{
RemoveParticleSystem(go);
Unregister(go);
Register(go);
}
}
}
}
private static bool WarningButton(string message, string buttonText)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.HelpBox(message, MessageType.Warning, true);
var clicked = GUILayout.Button(EditorGUIUtility.TrTempContent(buttonText));
EditorGUILayout.EndHorizontal();
return clicked;
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b171deb49fb7b471291108ad7e1c9baa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -545,7 +545,10 @@ namespace Coffee.UIExtensions
{ {
var ps = particles[i]; var ps = particles[i];
if (!ps if (!ps
#if UNITY_EDITOR
|| (ps.hideFlags & HideFlags.DontSave) != 0 // Dummy ParticleSystems for preview.
|| ps.gameObject.CompareTag("EditorOnly") // Ignore "EditorOnly" tagged ParticleSystems. || ps.gameObject.CompareTag("EditorOnly") // Ignore "EditorOnly" tagged ParticleSystems.
#endif
|| ps.GetComponentInParent<UIParticle>(true) != this) // Ignore ParticleSystems that are not under this UIParticle. || ps.GetComponentInParent<UIParticle>(true) != this) // Ignore ParticleSystems that are not under this UIParticle.
{ {
particles.RemoveAt(i); particles.RemoveAt(i);

View File

@@ -2,6 +2,7 @@
using Coffee.UIParticleInternal; using Coffee.UIParticleInternal;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
@@ -9,12 +10,14 @@ namespace Coffee.UIExtensions
{ {
[Header("Setting")] [Header("Setting")]
[SerializeField] [SerializeField]
internal bool m_EnableLinearToGamma = true; [Tooltip("Automatically correct the color space of the mesh.")]
[FormerlySerializedAs("m_EnableLinearToGamma")]
internal bool m_AutoColorCorrection = true;
public static bool enableLinearToGamma public static bool autoColorCorrection
{ {
get => instance.m_EnableLinearToGamma; get => instance.m_AutoColorCorrection;
set => instance.m_EnableLinearToGamma = value; set => instance.m_AutoColorCorrection = value;
} }
@@ -25,10 +28,16 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private bool m_HideGeneratedObjects = true; private bool m_HideGeneratedObjects = true;
public static HideFlags globalHideFlags => instance.m_HideGeneratedObjects [Tooltip("When selecting UIParticle, a temporary ParticleSystem is generated for preview.")]
[SerializeField]
private bool m_PreviewOnSelect = true;
internal static HideFlags globalHideFlags => instance.m_HideGeneratedObjects
? HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInHierarchy | HideFlags.HideInInspector ? HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInHierarchy | HideFlags.HideInInspector
: HideFlags.DontSave | HideFlags.NotEditable; : HideFlags.DontSave | HideFlags.NotEditable;
internal static bool s_PreviewOnSelectOnSelect => instance.m_PreviewOnSelect;
#if UNITY_EDITOR #if UNITY_EDITOR
[SettingsProvider] [SettingsProvider]
private static SettingsProvider CreateSettingsProvider() private static SettingsProvider CreateSettingsProvider()

View File

@@ -444,7 +444,7 @@ namespace Coffee.UIExtensions
_lastBounds = bounds; _lastBounds = bounds;
// Convert linear color to gamma color. // Convert linear color to gamma color.
if (UIParticleProjectSettings.enableLinearToGamma && canvas.ShouldGammaToLinearInMesh()) if (UIParticleProjectSettings.autoColorCorrection && canvas.ShouldGammaToLinearInMesh())
{ {
workerMesh.LinearToGamma(); workerMesh.LinearToGamma();
} }

View File

@@ -39,6 +39,17 @@ namespace Coffee.UIExtensions
} }
#if UNITY_EDITOR #if UNITY_EDITOR
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnDomainReload()
{
s_ActiveParticles.Clear();
s_ActiveAttractors.Clear();
s_UpdatedGroupIds.Clear();
s_FrameCount = 0;
}
#endif
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {

View File

@@ -40,12 +40,8 @@
Lighting Off Lighting Off
ZWrite Off ZWrite Off
ZTest [unity_GUIZTestMode] ZTest [unity_GUIZTestMode]
Fog Fog { Mode Off }
{
Mode Off
}
Blend One One Blend One One
ColorMask [_ColorMask] ColorMask [_ColorMask]
Pass Pass
@@ -76,6 +72,7 @@
fixed4 color : COLOR; fixed4 color : COLOR;
float2 texcoord : TEXCOORD0; float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1; float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO UNITY_VERTEX_OUTPUT_STEREO
}; };
@@ -84,16 +81,43 @@
float4 _MainTex_ST; float4 _MainTex_ST;
fixed4 _TextureSampleAdd; fixed4 _TextureSampleAdd;
float4 _ClipRect; float4 _ClipRect;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
int _UIVertexColorAlwaysGammaSpace;
half3 _UIGammaToLinear(half3 value)
{
half3 low = 0.0849710 * value - 0.000163029;
half3 high = value * (value * (value * 0.265885 + 0.736584) - 0.00980184) + 0.00319697;
// We should be 0.5 away from any actual gamma value stored in an 8 bit channel
const half3 split = (half3)0.0725490; // Equals 18.5 / 255
return (value < split) ? low : high;
}
v2f vert(appdata_t v) v2f vert(appdata_t v)
{ {
v2f OUT; v2f OUT;
UNITY_SETUP_INSTANCE_ID(v); UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex; OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.vertex = vPosition;
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
if (_UIVertexColorAlwaysGammaSpace)
{
#ifndef UNITY_COLORSPACE_GAMMA
v.color.rgb = _UIGammaToLinear(v.color.rgb);
#endif
}
OUT.color = v.color * _Color; OUT.color = v.color * _Color;
return OUT; return OUT;
@@ -101,17 +125,26 @@
fixed4 frag(v2f IN) : SV_Target fixed4 frag(v2f IN) : SV_Target
{ {
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; //Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0 / alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);
#ifdef UNITY_UI_CLIP_RECT #ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif #endif
#ifdef UNITY_UI_ALPHACLIP #ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001); clip(color.a - 0.001);
#endif #endif
color.rgb *= color.a; color.rgb *= color.a;
return color; return color;
} }
ENDCG ENDCG