Compare commits

..

5 Commits

Author SHA1 Message Date
semantic-release-bot
bba0c5ae12 chore(release): 5.0.0-preview.5 [skip ci]
# [5.0.0-preview.5](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v5.0.0-preview.4...v5.0.0-preview.5) (2024-07-18)

### Features

* ParticleAttractor supports multiple ParticleSystems ([e0a6381](e0a63817e1))
2024-07-18 06:06:22 +00:00
mob-sakai
a0d3827c88 feat: ParticleAttractor supports multiple ParticleSystems 2024-07-18 14:30:15 +09:00
mob-sakai
a7e47613f6 docs: update readme 2024-07-18 14:29:59 +09:00
mob-sakai
94680839a3 update coffee.internal 2024-07-18 14:29:49 +09:00
mob-sakai
f68e59eefe demos: update demos 2024-07-18 14:29:34 +09:00
13 changed files with 332 additions and 208 deletions

View File

@@ -1,3 +1,10 @@
# [5.0.0-preview.5](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v5.0.0-preview.4...v5.0.0-preview.5) (2024-07-18)
### Features
* ParticleAttractor supports multiple ParticleSystems ([e0a6381](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e0a63817e1e31b0f7c400dcdc67e255f0714273d))
# [5.0.0-preview.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v5.0.0-preview.3...v5.0.0-preview.4) (2024-06-27)

View File

@@ -317,7 +317,6 @@ namespace Coffee.UIExtensions
}
#endif
Profiler.EndSample();
EditorApplication.delayCall += () => Profiler.enabled = false;
}
private static bool IsBuiltInObject(Object obj)

View File

@@ -1,28 +1,62 @@
# <img alt="UIParticleIcon" src="https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/d76e105e-a840-4f61-a1f6-8cf311c0812d" width="26"/> Particle Effect For UGUI (UI Particle)
This package provides a component to render particle effects for uGUI in Unity 2018.2 or later.
The particle rendering is maskable and sortable, without the need for an extra Camera, RenderTexture, or Canvas.
# <img alt="UIParticleIcon" src="https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/d76e105e-a840-4f61-a1f6-8cf311c0812d" width="26"/> Particle Effect For UGUI (UI Particle) <!-- omit in toc -->
[![](https://img.shields.io/npm/v/com.coffee.ui-particle?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.coffee.ui-particle/)
[![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI?include_prereleases)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases)
[![](https://img.shields.io/github/license/mob-sakai/ParticleEffectForUGUI.svg)](https://github.com/mob-sakai/ParticleEffectForUGUI/blob/main/LICENSE.txt)
[![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases)
[![](https://img.shields.io/github/license/mob-sakai/ParticleEffectForUGUI.svg)](https://github.com/mob-sakai/ParticleEffectForUGUI/blob/main/LICENSE.md)
![](https://img.shields.io/badge/Unity-2018.2+-57b9d3.svg?style=flat&logo=unity)
![](https://img.shields.io/badge/uGUI_2.0_Ready-57b9d3.svg?style=flat)
![](https://img.shields.io/badge/UPR%2FHDPR_Ready-57b9d3.svg?style=flat)
![](https://github.com/mob-sakai/ParticleEffectForUGUI/actions/workflows/test.yml/badge.svg?branch=develop)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](http://makeapullrequest.com)
[![](https://img.shields.io/github/watchers/mob-sakai/ParticleEffectForUGUI.svg?style=social&label=Watch)](https://github.com/mob-sakai/ParticleEffectForUGUI/subscription)
[![](https://img.shields.io/twitter/follow/mob_sakai.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=mob_sakai)
<< [📝 Description](#-description) | [🎮 Demo](#-demo) | [⚙ Installation](#-installation) | [🚀 Usage](#-usage) | [🛠 Development Note](#-development-note) | [🤝 Contributing](#-contributing) >>
<< [📝 Description](#-description-) | [📌 Key Features](#-key-features) | [🎮 Demo](#-demo) | [⚙ Installation](#-installation) | [🔄 Upgrading to 5.x](#-upgrading-from-3x4x-to-5x) | [🚀 Usage](#-usage) | [🛠 Development Note](#-development-note) | [🤝 Contributing](#-contributing) >>
## 📝 Description <!-- omit in toc -->
![](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif)
This package uses the new APIs `MeshBake/MeshTrailBake` (introduced in Unity 2018.2) to render particles through `CanvasRenderer`.
You can render, mask, and sort your `ParticleSystems` for UI without the need for an additional `Camera`, `RenderTexture`, or `Canvas`.
- [📌 Key Features](#-key-features)
- [🎮 Demo](#-demo)
- [⚙ Installation](#-installation)
- [Install via OpenUPM](#install-via-openupm)
- [Install via UPM (with Package Manager UI)](#install-via-upm-with-package-manager-ui)
- [Install via UPM (Manually)](#install-via-upm-manually)
- [Install as Embedded Package](#install-as-embedded-package)
- [🔄 Upgrading from 3.x/4.x to 5.x](#-upgrading-from-3x4x-to-5x)
- [Breaking Changes](#breaking-changes)
- [🚀 Usage](#-usage)
- [Component: UIParticle](#component-uiparticle)
- [Basic Usage](#basic-usage)
- [Usage with Your Existing ParticleSystem Prefab](#usage-with-your-existing-particlesystem-prefab)
- [Usage with `Mask` or `RectMask2D` Component](#usage-with-mask-or-rectmask2d-component)
- [Usage with Script](#usage-with-script)
- [Component: UIParticleAttractor](#component-uiparticleattractor)
- [🛠 Development Note](#-development-note)
- [Compares the Baking mesh approach with the conventional approach](#compares-the-baking-mesh-approach-with-the-conventional-approach)
- [Performance test results](#performance-test-results)
- [🔍 FAQ: Why Are My UIParticles Not Displayed Correctly?](#-faq-why-are-my-uiparticles-not-displayed-correctly)
- [Shader Limitation](#shader-limitation)
- [Built-in shaders are not supported](#built-in-shaders-are-not-supported)
- [(Unity 2018 or 2019) UV.zw components will be discarded](#unity-2018-or-2019-uvzw-components-will-be-discarded)
- [(Unity 2018 or 2019) Custom vertex streams](#unity-2018-or-2019-custom-vertex-streams)
- [Overheads](#overheads)
- [How to Make a Custom Shader to Support `Mask` and `RectMask2D` Component](#how-to-make-a-custom-shader-to-support-mask-and-rectmask2d-component)
- [🤝 Contributing](#-contributing)
- [Issues](#issues)
- [Pull Requests](#pull-requests)
- [Support](#support)
- [License](#license)
- [Author](#author)
- [See Also](#see-also)
<br><br>
## 📝 Description
![Demo](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif)
This package uses the new APIs `MeshBake/MeshTrailBake` (introduced in Unity 2018.2) to render particles through CanvasRenderer.
You can render, mask, and sort your ParticleSystems for UI without the need for an additional Camera, RenderTexture, or Canvas.
### Key Features
## 📌 Key Features
* **Easy to use:** The package is ready to use out of the box.
* **Sortable:** Sort particle effects and other UI elements by sibling index.
@@ -83,16 +117,16 @@ _This package requires **Unity 2018.3 or later**._
```
- To update the package, use Package Manager UI (`Window > Package Manager`) or run the following command with `@{version}`:
```
openupm add com.coffee.ui-particle@4.8.0
openupm add com.coffee.ui-particle@4.9.0
```
#### Install via UPM (with Package Manager UI)
- Click `Window > Package Manager` to open Package Manager UI.
- Click `+ > Add package from git URL...` and input the repository URL: `https://github.com/mob-sakai/ParticleEffectForUGUI.git`
![](https://gist.github.com/assets/12690315/24af63ed-8a2e-483d-9023-7aa53d913330)
![](https://github.com/user-attachments/assets/f88f47ad-c606-44bd-9e86-ee3f72eac548)
- To update the package, change suffix `#{version}` to the target version.
- e.g. `https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.8.0`
- e.g. `https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.9.0`
#### Install via UPM (Manually)
@@ -107,7 +141,7 @@ _This package requires **Unity 2018.3 or later**._
```
- To update the package, change suffix `#{version}` to the target version.
- e.g. `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.8.0",`
- e.g. `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.9.0",`
#### Install as Embedded Package
@@ -119,7 +153,7 @@ _This package requires **Unity 2018.3 or later**._
<br><br>
## Upgrading from 3.x/4.x to 5.x
## 🔄 Upgrading from 3.x/4.x to 5.x
### Breaking Changes
@@ -132,7 +166,7 @@ _This package requires **Unity 2018.3 or later**._
## 🚀 Usage
### UIParticle Component
### Component: UIParticle
`UIParticle` controls the ParticleSystems that are attached to its own game objects and child game objects.
@@ -165,7 +199,7 @@ and z-position.
<br><br>
#### Basic Usage
### Basic Usage
1. Select `GameObject/UI/ParticleSystem` to create UIParticle with a ParticleSystem.
![particle](https://user-images.githubusercontent.com/12690315/95007361-cad0e880-0649-11eb-8835-f145d62c5977.png)
@@ -174,7 +208,7 @@ and z-position.
<br>
#### With Your Existing ParticleSystem Prefab
### Usage with Your Existing ParticleSystem Prefab
1. Select `GameObject/UI/ParticleSystem (Empty)` to create UIParticle.
![empty](https://user-images.githubusercontent.com/12690315/95007362-cb697f00-0649-11eb-8a09-29b0a13791e4.png)
@@ -183,7 +217,7 @@ and z-position.
<br>
#### With `Mask` or `RectMask2D` Component
### Usage with `Mask` or `RectMask2D` Component
If you want to mask particles, set a stencil-supported shader (such as `UI/UIAdditive`) to the material for
ParticleSystem.
@@ -195,7 +229,7 @@ section.
<br><br>
### Script usage
### Usage with Script
```cs
// Instantiate ParticleSystem prefab with UIParticle on runtime.
@@ -214,14 +248,14 @@ uiParticle.Stop();
<br><br>
### UIParticleAttractor component
### Component: UIParticleAttractor
`UIParticleAttractor` attracts particles generated by the specified ParticleSystem.
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/5c20ad73-4b9a-4f38-9cdc-119df5cce077)
![](https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif)
- **Particle System**: Attracts particles generated by the specified particle system.
- **Particle Systems**: Attracts particles generated by the specified ParticleSystems.
- **Destination Radius**: Once the particle is within the radius, the particle lifetime will become 0, and `OnAttracted`
will be called.
- **Delay Rate**: Delay to start attracting. It is a percentage of the particle's start lifetime.
@@ -366,7 +400,7 @@ When improving performance, keep the following in mind:
- Consider a single material, atlasing the sprites, and using `Sprite` mode in the `Texture Sheet Animation` module
in the ParticleSystem.
### How to Make a Custom Shader to Support Mask/RectMask2D Component
### How to Make a Custom Shader to Support `Mask` and `RectMask2D` Component
<details>
<summary>Shader tips</summary>

View File

@@ -13,13 +13,15 @@ namespace Coffee.UIParticleInternal
{
public abstract class PreloadedProjectSettings : ScriptableObject
#if UNITY_EDITOR
, IPreprocessBuildWithReport
{
int IOrderedCallback.callbackOrder => 0;
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
private class PreprocessBuildWithReport : IPreprocessBuildWithReport
{
Initialize();
int IOrderedCallback.callbackOrder => 0;
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
{
Initialize();
}
}
[InitializeOnLoadMethod]

View File

@@ -5,6 +5,7 @@ using Object = UnityEngine.Object;
#if ENABLE_COFFEE_LOGGER
using System.Reflection;
using System.Collections.Generic;
#else
using Conditional = System.Diagnostics.ConditionalAttribute;
#endif
@@ -140,6 +141,9 @@ namespace Coffee.UIParticleInternal
switch (tag)
{
case string name:
sb.Append(name);
break;
case Type type:
AppendType(sb, type);
break;

View File

@@ -8,10 +8,11 @@ namespace Coffee.UIParticleInternal
{
internal class ObjectRepository<T> where T : Object
{
private readonly List<Entry> _cache = new List<Entry>();
private readonly Dictionary<Hash128, Entry> _cache = new Dictionary<Hash128, Entry>(8);
private readonly Dictionary<int, Hash128> _objectKey = new Dictionary<int, Hash128>(8);
private readonly string _name;
private readonly Action<T> _onRelease;
private readonly Stack<Entry> _pool = new Stack<Entry>();
private readonly Stack<Entry> _pool = new Stack<Entry>(8);
public ObjectRepository(Action<T> onRelease = null)
{
@@ -36,40 +37,33 @@ namespace Coffee.UIParticleInternal
{
_onRelease = onRelease;
}
for (var i = 0; i < 8; i++)
{
_pool.Push(new Entry());
}
}
public int count => _cache.Count;
public void Clear()
{
for (var i = 0; i < _cache.Count; i++)
foreach (var kv in _cache)
{
var entry = _cache[i];
var entry = kv.Value;
if (entry == null) continue;
entry.Release(_onRelease);
_pool.Push(entry);
}
_cache.Clear();
_objectKey.Clear();
}
public bool Valid(Hash128 hash, T obj)
{
// Find existing entry.
Profiler.BeginSample("(COF)[ObjectRepository] Valid > Find existing entry");
for (var i = 0; i < _cache.Count; ++i)
{
var entry = _cache[i];
if (entry.hash != hash) continue;
Profiler.EndSample();
// Existing entry found.
return entry.storedObject == obj;
}
Profiler.EndSample();
return false;
return _cache.TryGetValue(hash, out var entry) && entry.storedObject == obj;
}
/// <summary>
@@ -77,41 +71,8 @@ namespace Coffee.UIParticleInternal
/// </summary>
public void Get(Hash128 hash, ref T obj, Func<T> onCreate)
{
// Find existing entry.
Profiler.BeginSample("(COF)[ObjectRepository] Get > Find existing entry");
for (var i = 0; i < _cache.Count; ++i)
{
var entry = _cache[i];
if (entry.hash != hash) continue;
// Existing entry found.
if (entry.storedObject != obj)
{
// if the object is different, release the old one.
Release(ref obj);
++entry.reference;
obj = entry.storedObject;
Logging.Log(_name, $"Get(#{count}): {entry}");
}
Profiler.EndSample();
return;
}
Profiler.EndSample();
// Create new entry.
Profiler.BeginSample("(COF)[ObjectRepository] Get > Create new entry");
var newEntry = 0 < _pool.Count ? _pool.Pop() : new Entry();
newEntry.storedObject = onCreate();
newEntry.hash = hash;
newEntry.reference = 1;
_cache.Add(newEntry);
Logging.Log(_name, $"Get(#{count}): {newEntry}");
Release(ref obj);
obj = newEntry.storedObject;
Profiler.EndSample();
if (GetFromCache(hash, ref obj)) return;
Add(hash, ref obj, onCreate());
}
/// <summary>
@@ -119,40 +80,60 @@ namespace Coffee.UIParticleInternal
/// </summary>
public void Get<TS>(Hash128 hash, ref T obj, Func<TS, T> onCreate, TS source)
{
// Find existing entry.
Profiler.BeginSample("(COF)[ObjectRepository] Get > Find existing entry");
for (var i = 0; i < _cache.Count; ++i)
{
var entry = _cache[i];
if (entry.hash != hash) continue;
if (GetFromCache(hash, ref obj)) return;
Add(hash, ref obj, onCreate(source));
}
private bool GetFromCache(Hash128 hash, ref T obj)
{
// Find existing entry.
Profiler.BeginSample("(COF)[ObjectRepository] GetFromCache");
if (_cache.TryGetValue(hash, out var entry))
{
if (!entry.storedObject)
{
Release(ref entry.storedObject);
Profiler.EndSample();
return false;
}
// Existing entry found.
if (entry.storedObject != obj)
{
// if the object is different, release the old one.
Release(ref obj);
++entry.reference;
obj = entry.storedObject;
Logging.Log(_name, $"Get(#{count}): {entry}");
Logging.Log(_name, $"Get(total#{count}): {entry}");
}
Profiler.EndSample();
return;
return true;
}
Profiler.EndSample();
return false;
}
// Create new entry.
Profiler.BeginSample("(COF)[ObjectRepository] Get > Create new entry");
private void Add(Hash128 hash, ref T obj, T newObject)
{
if (!newObject)
{
Release(ref obj);
obj = newObject;
return;
}
// Create and add a new entry.
Profiler.BeginSample("(COF)[ObjectRepository] Add");
var newEntry = 0 < _pool.Count ? _pool.Pop() : new Entry();
newEntry.storedObject = onCreate(source);
newEntry.storedObject = newObject;
newEntry.hash = hash;
newEntry.reference = 1;
_cache.Add(newEntry);
Logging.Log(_name, $"Get(#{count}): {newEntry}");
_cache[hash] = newEntry;
_objectKey[newObject.GetInstanceID()] = hash;
Logging.Log(_name, $"<color=#03c700>Add</color>(total#{count}): {newEntry}");
Release(ref obj);
obj = newEntry.storedObject;
obj = newObject;
Profiler.EndSample();
}
@@ -163,35 +144,45 @@ namespace Coffee.UIParticleInternal
{
if (ReferenceEquals(obj, null)) return;
// Find and release the entry.
Profiler.BeginSample("(COF)[ObjectRepository] Release");
for (var i = 0; i < _cache.Count; i++)
var id = obj.GetInstanceID();
if (_objectKey.TryGetValue(id, out var hash)
&& _cache.TryGetValue(hash, out var entry))
{
var entry = _cache[i];
if (entry.storedObject != obj)
entry.reference--;
if (entry.reference <= 0 || !entry.storedObject)
{
continue;
Remove(entry);
}
if (--entry.reference <= 0)
else
{
Profiler.BeginSample("(COF)[ObjectRepository] Release > RemoveAt");
_cache.RemoveAtFast(i);
Logging.Log(_name, $"Release(#{_cache.Count}): {entry}");
entry.Release(_onRelease);
_pool.Push(entry);
Profiler.EndSample();
break;
Logging.Log(_name, $"Release(total#{_cache.Count}): {entry}");
}
Logging.Log(_name, $"Release(#{count}): {entry}");
break;
}
else
{
Logging.Log(_name, $"Release(total#{_cache.Count}): <color=red>Already released: {obj}</color>");
}
obj = null;
Profiler.EndSample();
}
private void Remove(Entry entry)
{
if (ReferenceEquals(entry, null)) return;
Profiler.BeginSample("(COF)[ObjectRepository] Remove");
_cache.Remove(entry.hash);
_objectKey.Remove(entry.storedObject.GetInstanceID());
_pool.Push(entry);
entry.reference = 0;
Logging.Log(_name, $"<color=#f29e03>Remove</color>(total#{_cache.Count}): {entry}");
entry.Release(_onRelease);
Profiler.EndSample();
}
private class Entry
{
public Hash128 hash;
@@ -211,7 +202,7 @@ namespace Coffee.UIParticleInternal
public override string ToString()
{
return $"h{(uint)hash.GetHashCode()} (#{reference}), {storedObject}";
return $"h{(uint)hash.GetHashCode()} (refs#{reference}), {storedObject}";
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Coffee.UIParticleInternal;
using UnityEngine;
using UnityEngine.Events;
@@ -6,7 +7,7 @@ using UnityEngine.Events;
namespace Coffee.UIExtensions
{
[ExecuteAlways]
public class UIParticleAttractor : MonoBehaviour
public class UIParticleAttractor : MonoBehaviour, ISerializationCallbackReceiver
{
public enum Movement
{
@@ -22,8 +23,12 @@ namespace Coffee.UIExtensions
}
[SerializeField]
[HideInInspector]
private ParticleSystem m_ParticleSystem;
[SerializeField]
private List<ParticleSystem> m_ParticleSystems = new List<ParticleSystem>();
[Range(0.1f, 10f)]
[SerializeField]
private float m_DestinationRadius = 1;
@@ -45,7 +50,7 @@ namespace Coffee.UIExtensions
[SerializeField]
private UnityEvent m_OnAttracted;
private UIParticle _uiParticle;
private List<UIParticle> _uiParticles = new List<UIParticle>();
public float destinationRadius
{
@@ -84,25 +89,46 @@ namespace Coffee.UIExtensions
}
/// <summary>
/// The target ParticleSystem to attract.
/// The target ParticleSystems to attract. Use <see cref="AddParticleSystem"/> and
/// <see cref="RemoveParticleSystem"/> to modify the list.
/// </summary>
#if UNITY_EDITOR
public new ParticleSystem particleSystem
#else
public ParticleSystem particleSystem
#endif
public IReadOnlyList<ParticleSystem> particleSystems => m_ParticleSystems;
public void AddParticleSystem(ParticleSystem ps)
{
get => m_ParticleSystem;
set
if (m_ParticleSystems == null)
{
m_ParticleSystem = value;
ApplyParticleSystem();
m_ParticleSystems = new List<ParticleSystem>();
}
var i = m_ParticleSystems.IndexOf(ps);
if (0 <= i) return; // Already added: skip
m_ParticleSystems.Add(ps);
_uiParticles.Clear();
}
public void RemoveParticleSystem(ParticleSystem ps)
{
if (m_ParticleSystems == null)
{
return;
}
var i = m_ParticleSystems.IndexOf(ps);
if (i < 0) return; // Not found. skip
m_ParticleSystems.RemoveAt(i);
_uiParticles.Clear();
}
private void Awake()
{
UpgradeIfNeeded();
}
private void OnEnable()
{
ApplyParticleSystem();
UIParticleUpdater.Register(this);
}
@@ -113,85 +139,96 @@ namespace Coffee.UIExtensions
private void OnDestroy()
{
_uiParticle = null;
m_ParticleSystem = null;
_uiParticles = null;
m_ParticleSystems = null;
}
internal void Attract()
{
if (m_ParticleSystem == null) return;
// Collect UIParticle if needed (same size as m_ParticleSystems)
CollectUIParticlesIfNeeded();
var count = m_ParticleSystem.particleCount;
if (count == 0) return;
var particles = ParticleSystemExtensions.GetParticleArray(count);
m_ParticleSystem.GetParticles(particles, count);
var dstPos = GetDestinationPosition();
for (var i = 0; i < count; i++)
for (var particleIndex = 0; particleIndex < this.m_ParticleSystems.Count; particleIndex++)
{
// Attracted
var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < m_DestinationRadius)
{
p.remainingLifetime = 0f;
particles[i] = p;
var particleSystem = m_ParticleSystems[particleIndex];
if (m_OnAttracted != null)
// Skip: The ParticleSystem is not active
if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy) continue;
// Skip: No active particles
var count = particleSystem.particleCount;
if (count == 0) continue;
var particles = ParticleSystemExtensions.GetParticleArray(count);
particleSystem.GetParticles(particles, count);
var uiParticle = _uiParticles[particleIndex];
var dstPos = this.GetDestinationPosition(uiParticle, particleSystem);
for (var i = 0; i < count; i++)
{
// Attracted
var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < this.m_DestinationRadius)
{
try
p.remainingLifetime = 0f;
particles[i] = p;
if (this.m_OnAttracted != null)
{
m_OnAttracted.Invoke();
}
catch (Exception e)
{
Debug.LogException(e);
try
{
this.m_OnAttracted.Invoke();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
continue;
}
continue;
// Calc attracting time
var delayTime = p.startLifetime * this.m_DelayRate;
var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
// Delay
if (time <= 0) continue;
// Attract
p.position = this.GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
}
// Calc attracting time
var delayTime = p.startLifetime * m_DelayRate;
var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
// Delay
if (time <= 0) continue;
// Attract
p.position = GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
particleSystem.SetParticles(particles, count);
}
m_ParticleSystem.SetParticles(particles, count);
}
private Vector3 GetDestinationPosition()
private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem)
{
var isUI = _uiParticle && _uiParticle.enabled;
var psPos = m_ParticleSystem.transform.position;
var isUI = uiParticle && uiParticle.enabled;
var psPos = particleSystem.transform.position;
var attractorPos = transform.position;
var dstPos = attractorPos;
var isLocalSpace = m_ParticleSystem.IsLocalSpace();
var isLocalSpace = particleSystem.IsLocalSpace();
if (isLocalSpace)
{
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos);
dstPos = particleSystem.transform.InverseTransformPoint(dstPos);
}
if (isUI)
{
var inverseScale = _uiParticle.parentScale.Inverse();
var scale3d = _uiParticle.scale3DForCalc;
var inverseScale = uiParticle.parentScale.Inverse();
var scale3d = uiParticle.scale3DForCalc;
dstPos = dstPos.GetScaled(inverseScale, scale3d.Inverse());
// Relative mode
if (_uiParticle.positionMode == UIParticle.PositionMode.Relative)
if (uiParticle.positionMode == UIParticle.PositionMode.Relative)
{
var diff = _uiParticle.transform.position - psPos;
var diff = uiParticle.transform.position - psPos;
diff.Scale(scale3d - inverseScale);
diff.Scale(scale3d.Inverse());
dstPos += diff;
@@ -237,25 +274,59 @@ namespace Coffee.UIExtensions
return Vector3.MoveTowards(current, target, speed);
}
private void ApplyParticleSystem()
private void CollectUIParticlesIfNeeded()
{
_uiParticle = null;
if (m_ParticleSystem == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
Debug.LogError("No particle system attached to particle attractor script", this);
}
if (m_ParticleSystems.Count == 0 || _uiParticles.Count != 0) return;
return;
// Expand capacity
if (_uiParticles.Capacity < m_ParticleSystems.Capacity)
{
_uiParticles.Capacity = m_ParticleSystems.Capacity;
}
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(true);
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
// Find UIParticle that controls the ParticleSystem
for (var i = 0; i < m_ParticleSystems.Count; i++)
{
_uiParticle = null;
var ps = m_ParticleSystems[i];
if (ps == null)
{
_uiParticles.Add(null);
continue;
}
var uiParticle = ps.GetComponentInParent<UIParticle>(true);
_uiParticles.Add(uiParticle.particles.Contains(ps) ? uiParticle : null);
}
}
#if UNITY_EDITOR
private void OnValidate()
{
_uiParticles.Clear();
}
#endif
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
UpgradeIfNeeded();
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
}
private void UpgradeIfNeeded()
{
// Multiple ParticleSystems support: from 'm_ParticleSystem' to 'm_ParticleSystems'
if (m_ParticleSystem != null)
{
if (!m_ParticleSystems.Contains(m_ParticleSystem))
{
m_ParticleSystems.Add(m_ParticleSystem);
}
m_ParticleSystem = null;
Debug.Log($"Upgraded!");
}
}
}

View File

@@ -51,7 +51,11 @@ namespace Coffee.UIExtensions.Demo
public void EnableAnimations(bool flag)
{
#if UNITY_2023_1_OR_NEWER
foreach (var animator in FindObjectsByType<Animator>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var animator in FindObjectsOfType<Animator>())
#endif
{
animator.enabled = flag;
}
@@ -79,7 +83,11 @@ namespace Coffee.UIExtensions.Demo
public void UIParticle_Scale(float scale)
{
#if UNITY_2023_1_OR_NEWER
foreach (var uip in FindObjectsByType<UIParticle>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var uip in FindObjectsOfType<UIParticle>())
#endif
{
uip.scale = scale;
}
@@ -87,7 +95,11 @@ namespace Coffee.UIExtensions.Demo
public void ParticleSystem_WorldSpaseSimulation(bool flag)
{
#if UNITY_2023_1_OR_NEWER
foreach (var p in FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var p in FindObjectsOfType<ParticleSystem>())
#endif
{
var main = p.main;
main.simulationSpace = flag
@@ -123,7 +135,11 @@ namespace Coffee.UIExtensions.Demo
public void ParticleSystem_SetScale(float scale)
{
#if UNITY_2023_1_OR_NEWER
foreach (var ps in FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var ps in FindObjectsOfType<ParticleSystem>())
#endif
{
ps.transform.localScale = new Vector3(scale, scale, scale);
}

View File

@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1

View File

@@ -27,7 +27,7 @@ TextureImporter:
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
textureFormat: 4
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1

View File

@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 0
alphaIsTransparency: 1
spriteTessellationDetail: -1

View File

@@ -27,7 +27,7 @@ TextureImporter:
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: -1
textureFormat: 4
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1

View File

@@ -2,7 +2,7 @@
"name": "com.coffee.ui-particle",
"displayName": "UI Particle",
"description": "This package provides a component to render particle effects for uGUI.\nThe particle rendering is maskable and sortable, without the need for an extra Camera, RenderTexture, or Canvas.",
"version": "5.0.0-preview.4",
"version": "5.0.0-preview.5",
"unity": "2018.2",
"license": "MIT",
"repository": {