1
This commit is contained in:
@@ -362,6 +362,12 @@
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.utj.profilerreader": {
|
||||
"version": "file:jp.unity3d.profilerreader",
|
||||
"depth": 0,
|
||||
"source": "embedded",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.ai": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using UnityEditor.SceneManagement;
|
||||
#elif UNITY_2018_3_OR_NEWER
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor window that batch-assigns multiple ParticleSystem GameObjects/effects
|
||||
/// into a UIParticle's "Particles" list in one click.
|
||||
/// </summary>
|
||||
public class ParticleBatchAssigner : EditorWindow
|
||||
{
|
||||
private UIParticle _target;
|
||||
private readonly List<Object> _sources = new List<Object>();
|
||||
private Vector2 _scroll;
|
||||
private bool _expandChildren;
|
||||
|
||||
[MenuItem("Tools/UIParticle/Batch Particle Assigner")]
|
||||
private static void Open()
|
||||
{
|
||||
var win = GetWindow<ParticleBatchAssigner>("Particle Batch");
|
||||
win.minSize = new Vector2(340, 460);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("UIParticle 批量特效配置", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"选中多个特效 GameObject(或 ParticleSystem),拖到下方源区;选好目标 UIParticle 后一键批量添加。",
|
||||
MessageType.None);
|
||||
|
||||
// ---- 目标 ----
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
_target = (UIParticle)EditorGUILayout.ObjectField(
|
||||
"目标 UIParticle", _target, typeof(UIParticle), true);
|
||||
if (GUILayout.Button("用选中", GUILayout.Width(60)))
|
||||
{
|
||||
_target = Selection.objects
|
||||
.Select(o => (o as UIParticle) ?? (o as GameObject)?.GetComponent<UIParticle>())
|
||||
.FirstOrDefault(u => u != null);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// ---- 特效源 ----
|
||||
EditorGUILayout.LabelField("特效源", EditorStyles.boldLabel);
|
||||
_expandChildren = EditorGUILayout.ToggleLeft(
|
||||
"展开子 ParticleSystem(默认关:只取根,与原 Inspector 拖动一致)", _expandChildren);
|
||||
DrawDropArea();
|
||||
DrawSourceList();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// ---- 操作 ----
|
||||
using (new EditorGUI.DisabledGroupScope(_target == null || _sources.Count == 0))
|
||||
{
|
||||
if (GUILayout.Button("批量添加到 UIParticle", GUILayout.Height(32)))
|
||||
{
|
||||
ApplyToTarget(true);
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("替换目标列表"))
|
||||
{
|
||||
ApplyToTarget(false);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("清空源"))
|
||||
{
|
||||
_sources.Clear();
|
||||
GUI.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_target == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("请先指定一个目标 UIParticle。", MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDropArea()
|
||||
{
|
||||
var rect = GUILayoutUtility.GetRect(0, 44, GUILayout.ExpandWidth(true));
|
||||
var style = new GUIStyle(GUI.skin.box)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
normal = { textColor = new Color(0.6f, 0.6f, 0.6f) }
|
||||
};
|
||||
GUI.Box(rect, "把多个特效 GameObject / ParticleSystem 拖到这里", style);
|
||||
|
||||
var evt = Event.current;
|
||||
if (rect.Contains(evt.mousePosition)
|
||||
&& (evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform))
|
||||
{
|
||||
var refs = DragAndDrop.objectReferences;
|
||||
var valid = refs.Any(IsValidSource);
|
||||
DragAndDrop.visualMode = valid
|
||||
? DragAndDropVisualMode.Copy
|
||||
: DragAndDropVisualMode.Rejected;
|
||||
if (evt.type == EventType.DragPerform && valid)
|
||||
{
|
||||
DragAndDrop.AcceptDrag();
|
||||
foreach (var o in refs)
|
||||
{
|
||||
AddSource(o);
|
||||
}
|
||||
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSourceList()
|
||||
{
|
||||
var expanded = GetParticleSystems(_sources, _expandChildren).Count();
|
||||
EditorGUILayout.LabelField(
|
||||
$"源 {_sources.Count} 个 → 展开出 {expanded} 个 ParticleSystem",
|
||||
EditorStyles.miniLabel);
|
||||
|
||||
_scroll = EditorGUILayout.BeginScrollView(_scroll, GUILayout.MinHeight(90));
|
||||
for (var i = _sources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
EditorGUILayout.ObjectField(_sources[i], typeof(Object), true);
|
||||
if (GUILayout.Button("×", GUILayout.Width(24)))
|
||||
{
|
||||
_sources.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void AddSource(Object o)
|
||||
{
|
||||
if (o == null || !IsValidSource(o)) return;
|
||||
if (_sources.Contains(o)) return;
|
||||
_sources.Add(o);
|
||||
}
|
||||
|
||||
private static bool IsValidSource(Object o)
|
||||
{
|
||||
return o is ParticleSystem || o is GameObject;
|
||||
}
|
||||
|
||||
private static IEnumerable<ParticleSystem> GetParticleSystems(IEnumerable<Object> sources, bool expandChildren)
|
||||
{
|
||||
var seen = new HashSet<ParticleSystem>();
|
||||
foreach (var o in sources)
|
||||
{
|
||||
if (o == null) continue;
|
||||
if (o is ParticleSystem ps)
|
||||
{
|
||||
if (seen.Add(ps)) yield return ps;
|
||||
}
|
||||
else if (o is GameObject go)
|
||||
{
|
||||
if (expandChildren)
|
||||
{
|
||||
// 展开所有子 ParticleSystem(含禁用/嵌套):用于一个 GameObject 含多个并联粒子的复杂特效
|
||||
foreach (var c in go.GetComponentsInChildren<ParticleSystem>(true))
|
||||
{
|
||||
if (seen.Add(c)) yield return c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认:只取根节点上的 ParticleSystem,与「拖 GameObject 到 Inspector 列表」行为完全一致
|
||||
var rootPs = go.GetComponent<ParticleSystem>();
|
||||
if (rootPs != null && seen.Add(rootPs)) yield return rootPs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyToTarget(bool append)
|
||||
{
|
||||
if (_target == null) return;
|
||||
|
||||
Undo.RecordObject(_target, append ? "Batch add particles" : "Replace particles");
|
||||
|
||||
var so = new SerializedObject(_target);
|
||||
var sp = so.FindProperty("m_Particles");
|
||||
so.Update();
|
||||
|
||||
if (!append)
|
||||
{
|
||||
sp.ClearArray();
|
||||
}
|
||||
|
||||
// 现有引用去重
|
||||
var existing = new HashSet<Object>();
|
||||
for (var i = 0; i < sp.arraySize; i++)
|
||||
{
|
||||
var r = sp.GetArrayElementAtIndex(i).objectReferenceValue;
|
||||
if (r != null) existing.Add(r);
|
||||
}
|
||||
|
||||
foreach (var ps in GetParticleSystems(_sources, _expandChildren))
|
||||
{
|
||||
if (!existing.Add(ps)) continue;
|
||||
sp.InsertArrayElementAtIndex(sp.arraySize);
|
||||
sp.GetArrayElementAtIndex(sp.arraySize - 1).objectReferenceValue = ps;
|
||||
}
|
||||
|
||||
so.ApplyModifiedProperties();
|
||||
|
||||
// 刷新渲染器(不会清空列表)
|
||||
_target.RefreshParticles(_target.particles);
|
||||
|
||||
// Prefab 阶段保存
|
||||
SaveIfInPrefabStage(_target);
|
||||
|
||||
EditorGUIUtility.PingObject(_target);
|
||||
}
|
||||
|
||||
private static void SaveIfInPrefabStage(Object obj)
|
||||
{
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
var go = (obj as Component)?.gameObject;
|
||||
if (go == null) return;
|
||||
var stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
if (stage != null && stage.scene.isLoaded && go.scene == stage.scene)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
var assetPath = stage.assetPath;
|
||||
#else
|
||||
var assetPath = stage.prefabAssetPath;
|
||||
#endif
|
||||
PrefabUtility.SaveAsPrefabAsset(stage.prefabContentsRoot, assetPath);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f7e81e1435d7274796a28b07c081f75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// One-click reset of UIParticle.m_Scale3D to (1,1,1).
|
||||
/// The C# default was changed from (10,10,10) to (1,1,1), but already-serialized
|
||||
/// UIParticle assets/scene objects keep their old (10,10,10) value. This menu fixes them.
|
||||
/// </summary>
|
||||
public static class ParticleScale3DResetter
|
||||
{
|
||||
[MenuItem("Tools/UIParticle/Reset Scale3D to 1")]
|
||||
private static void ResetScale3D()
|
||||
{
|
||||
var done = new HashSet<Object>();
|
||||
var count = 0;
|
||||
|
||||
// Loaded scene objects (including inactive).
|
||||
foreach (var uip in Object.FindObjectsOfType<UIParticle>(true))
|
||||
{
|
||||
if (ResetOne(uip)) { done.Add(uip); count++; }
|
||||
}
|
||||
|
||||
// Selected prefab assets in the Project window.
|
||||
foreach (var obj in Selection.objects)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(obj);
|
||||
if (string.IsNullOrEmpty(path)) continue;
|
||||
var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
if (asset == null) continue;
|
||||
foreach (var uip in asset.GetComponentsInChildren<UIParticle>(true))
|
||||
{
|
||||
if (!done.Contains(uip) && ResetOne(uip)) { done.Add(uip); count++; }
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
Debug.Log($"[UIParticle] Reset Scale3D to (1,1,1) on {count} UIParticle(s).");
|
||||
}
|
||||
|
||||
private static bool ResetOne(Object uip)
|
||||
{
|
||||
var so = new SerializedObject(uip);
|
||||
so.Update();
|
||||
var sp = so.FindProperty("m_Scale3D");
|
||||
if (sp == null) return false;
|
||||
if (sp.vector3Value == new Vector3(1, 1, 1)) return false;
|
||||
|
||||
sp.vector3Value = new Vector3(1, 1, 1);
|
||||
so.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(uip);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3670b336d10c266418ac9ad2d6747479
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -64,7 +64,7 @@ namespace Coffee.UIExtensions
|
||||
|
||||
[Tooltip("Scale the rendering particles. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported.")]
|
||||
[SerializeField]
|
||||
private Vector3 m_Scale3D = new Vector3(10, 10, 10);
|
||||
private Vector3 m_Scale3D = new Vector3(1, 1, 1);
|
||||
|
||||
[Tooltip("If you want to update material properties (e.g. _MainTex_ST, _Color) in AnimationClip, " +
|
||||
"use this to mark as animatable.")]
|
||||
|
||||
@@ -417,10 +417,11 @@ namespace Coffee.UIExtensions
|
||||
}
|
||||
else
|
||||
{
|
||||
var diff = _particleSystem.transform.position - _parent.transform.position;
|
||||
// Relative mode: place the ParticleSystem at its true local offset from the
|
||||
// UIParticle. No (scale-1) amplification, so the on-UI position matches the
|
||||
// in-scene relative position regardless of scale3D.
|
||||
s_CombineInstances[0].transform =
|
||||
canvasRenderer.transform.worldToLocalMatrix
|
||||
* Matrix4x4.Translate(diff.GetScaled(scale - Vector3.one))
|
||||
* GetWorldMatrix(psPos, scale);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user