Compare commits

..

46 Commits

Author SHA1 Message Date
semantic-release-bot
2b3cec69ac chore(release): 4.10.2 [skip ci]
## [4.10.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.1...v4.10.2) (2024-11-01)

### Bug Fixes

* trail incorrect offset ([afe00a1](afe00a1dde)), closes [#335](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/335)
2024-11-01 02:15:40 +00:00
Jie
a4dd8c5776 fix: trail incorrect offset
close #335
2024-11-01 11:14:03 +09:00
mob-sakai
dd94bba43c refactor 2024-09-30 03:01:48 +09:00
semantic-release-bot
2e813c5313 chore(release): 4.10.1 [skip ci]
## [4.10.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.0...v4.10.1) (2024-09-29)

### Bug Fixes

* mainTex will be ignored ([2ee69d0](2ee69d0424))
2024-09-29 17:46:35 +00:00
mob-sakai
1b61904018 fix: mainTex will be ignored 2024-09-30 02:36:45 +09:00
semantic-release-bot
672cb0abbb chore(release): 4.10.0 [skip ci]
# [4.10.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.9.1...v4.10.0) (2024-09-29)

### Bug Fixes

* component icon is not set ([5ff6ec8](5ff6ec815a))

### Features

* add project settings ([1ce4e31](1ce4e31a96))
2024-09-29 17:14:48 +00:00
mob-sakai
54da6b3bbf fix: component icon is not set 2024-09-30 01:56:54 +09:00
mob-sakai
c00f5c8806 docs: update readme 2024-09-30 01:56:54 +09:00
mob-sakai
dddbeff155 style: format 2024-09-30 01:56:54 +09:00
mob-sakai
20b9085ca1 feat: add project settings 2024-09-30 01:56:54 +09:00
mob-sakai
8c1bef4373 refactor: using Coffee.Internal 2024-09-30 01:48:06 +09:00
mob-sakai
a96b6915ef import Coffee.Internal 2024-09-30 01:48:06 +09:00
semantic-release-bot
025efcbc5d chore(release): 4.9.1 [skip ci]
## [4.9.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.9.0...v4.9.1) (2024-08-07)

### Bug Fixes

* ParticleSystem trails gain offset on parent canvas change ([2a1cd50](2a1cd502b4)), closes [#323](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/323)
2024-08-07 17:13:07 +00:00
mob-sakai
162ee53180 fix: ParticleSystem trails gain offset on parent canvas change
close #323
2024-08-08 02:00:40 +09:00
semantic-release-bot
4dfcb9075e chore(release): 4.9.0 [skip ci]
# [4.9.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.1...v4.9.0) (2024-07-18)

### Features

* ParticleAttractor supports multiple ParticleSystems ([3834780](3834780fdb))
2024-07-18 05:09:44 +00:00
mob-sakai
7916056868 demos: update demo 2024-07-18 13:40:58 +09:00
mob-sakai
b87a0a02af docs: update readme 2024-07-18 09:33:42 +09:00
mob-sakai
3052cab41b refactor: refactor multiple attractor
- Convert 'm_ParticleSystem' to 'm_ParticleSystems'
- Collect parent UIParticles on attract
- Improve performance
2024-07-18 00:56:13 +09:00
dogramacigokhan
a439248e1a feat: ParticleAttractor supports multiple ParticleSystems 2024-07-18 00:56:13 +09:00
semantic-release-bot
686fff8690 chore(release): 4.8.1 [skip ci]
## [4.8.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.0...v4.8.1) (2024-06-27)

### Bug Fixes

* remove debug code ([669deb4](669deb41d4))
2024-06-27 09:27:20 +00:00
mob-sakai
3638df524f fix: remove debug code 2024-06-27 18:26:47 +09:00
semantic-release-bot
592d871da2 chore(release): 4.8.0 [skip ci]
# [4.8.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.2...v4.8.0) (2024-06-27)

### Bug Fixes

* generated baking-camera object remains in the prefab or scene (again) ([de35cba](de35cba34c))
* SetParticleSystemInstance/Prefab APIs destroy generated objects ([ae3f3a8](ae3f3a8e62))

### Features

* add 'custom view' option. ([a703c29](a703c2921c))
* remove overlay window (editor) ([8358170](835817049f))
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` (again) ([88a970d](88a970d93a))
* the rendering order list in inspector is now more compact ([be90172](be901724e0))
2024-06-27 06:40:30 +00:00
mob-sakai
4d0cfd3510 refactor 2024-06-27 14:12:53 +09:00
mob-sakai
4c5251a5ba feat: the rendering order list in inspector is now more compact 2024-06-27 10:48:34 +09:00
mob-sakai
cbb37b0b3a feat: restore Transform.localScale when setting autoScalingMode to something other than Transform (again) 2024-06-27 10:48:27 +09:00
mob-sakai
3c54f6dc8d feat: add 'custom view' option.
Use this if the particles are not displayed correctly due to min/max particle size.
2024-06-27 10:48:05 +09:00
mob-sakai
d3532b9708 fix: generated baking-camera object remains in the prefab or scene (again) 2024-06-27 10:46:25 +09:00
mob-sakai
2ac8a1175b fix: SetParticleSystemInstance/Prefab APIs destroy generated objects
# Conflicts:
#	Packages/src/Runtime/UIParticle.cs
2024-06-27 10:46:10 +09:00
mob-sakai
9a37e64b20 feat: remove overlay window (editor) 2024-06-27 10:44:13 +09:00
semantic-release-bot
bc0c2e4e63 chore(release): 4.7.2 [skip ci]
## [4.7.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.1...v4.7.2) (2024-06-21)

### Bug Fixes

* generated baking-camera object remains in the prefab or scene ([0bb8438](0bb8438301))
2024-06-21 05:22:49 +00:00
mob-sakai
929c0d686b fix: generated baking-camera object remains in the prefab or scene 2024-06-21 14:21:53 +09:00
semantic-release-bot
1674e850e0 chore(release): 4.7.1 [skip ci]
## [4.7.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.0...v4.7.1) (2024-06-20)

### Bug Fixes

* despite not using the size module, particles become smaller based on their z position ([a8ed6e6](a8ed6e6858)), closes [#316](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/316)
2024-06-20 03:55:01 +00:00
mob-sakai
c43ba67a9b fix: despite not using the size module, particles become smaller based on their z position
close #316
2024-06-20 12:51:02 +09:00
mob-sakai
4c285fde14 docs: update license 2024-06-19 13:47:05 +09:00
semantic-release-bot
22795fca9a chore(release): 4.7.0 [skip ci]
# [4.7.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.8...v4.7.0) (2024-06-19)

### Bug Fixes

* `UIParticle.transform.localScale` does not scale particles ([1d40e24](1d40e24c74)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
* UIParticle is scaled by canvas size even when `AutoScalingMode.None` and `ScalingMode.Local` ([54a4b1c](54a4b1cdfd)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
* UIParticle is scaled incorrectly with nested canvases ([f26920f](f26920f982)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)

### Features

* reset previous position on start play for world space simulation ([3880484](3880484ce5)), closes [#303](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/303)
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` ([5505247](5505247a94))
2024-06-19 04:30:47 +00:00
mob-sakai
1aca69bbb7 fix demo 2024-06-19 13:23:40 +09:00
mob-sakai
3619b4ff94 feat: reset previous position on start play for world space simulation
close #303
2024-06-19 11:07:29 +09:00
mob-sakai
cc3e95d8c7 fix: UIParticle.transform.localScale does not scale particles
close #313
2024-06-19 11:07:29 +09:00
mob-sakai
5c28e9a2f3 fix: UIParticle is scaled by canvas size even when AutoScalingMode.None and ScalingMode.Local
close #313
2024-06-19 11:07:29 +09:00
mob-sakai
b82f79c2de feat: restore Transform.localScale when setting autoScalingMode to something other than Transform 2024-06-19 11:07:29 +09:00
mob-sakai
f029b7508e fix: UIParticle is scaled incorrectly with nested canvases
close #313
2024-06-18 19:44:00 +09:00
mob-sakai
5c41c39847 refactor: refactor 2024-06-18 17:11:14 +09:00
semantic-release-bot
49f46d5c9e chore(release): 4.6.8 [skip ci]
## [4.6.8](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.7...v4.6.8) (2024-06-14)

### Bug Fixes

* 'Resource ID out of range in GetResource' error in overlay rendering mode ([05286ce](05286cedfd)), closes [#308](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/308)
2024-06-14 01:48:23 +00:00
SAMYTHEBIGJUICY
628b9582b2 fix: 'Resource ID out of range in GetResource' error in overlay rendering mode
close #308
2024-06-14 10:47:32 +09:00
semantic-release-bot
e8c9873201 chore(release): 4.6.7 [skip ci]
## [4.6.7](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.6...v4.6.7) (2024-05-24)

### Bug Fixes

* the ParticleSystem's localPosition drifts at certain scales due to floating-point precision issues ([e924eb4](e924eb4596)), closes [#299](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/299) [#312](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/312)
2024-05-24 08:38:56 +00:00
mob-sakai
fa6881a028 fix: the ParticleSystem's localPosition drifts at certain scales due to floating-point precision issues
close #299, close #312
2024-05-24 17:37:32 +09:00
29 changed files with 776 additions and 546 deletions

View File

@@ -1,35 +1,108 @@
# [5.0.0-preview.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v5.0.0-preview.1...v5.0.0-preview.2) (2024-06-20) ## [4.10.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.1...v4.10.2) (2024-11-01)
### Bug Fixes ### Bug Fixes
* 'Resource ID out of range in GetResource' error in overlay rendering mode ([ff78b6f](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/ff78b6fe32ceed8ddad50e63dcb7a202eab95266)), closes [#308](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/308) * trail incorrect offset ([afe00a1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/afe00a1dde80eb1c0a7bb668b75f4c3733d3fa43)), closes [#335](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/335)
* `UIParticle.transform.localScale` does not scale particles ([491ee7b](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/491ee7b0c3e528e1e577ae5ff2588d7c3bd8ecdb)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
* despite not using the size module, particles become smaller based on their z position ([c96ddf2](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/c96ddf293e855f7ebccaaaf3b112092955545e61)), closes [#316](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/316) ## [4.10.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.0...v4.10.1) (2024-09-29)
* the ParticleSystem's localPosition drifts at certain scales due to floating-point precision issues ([a9c2b19](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a9c2b19edf00e1c86c928ef23405906952ede852)), closes [#299](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/299) [#312](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/312)
* UIParticle is scaled by canvas size even when `AutoScalingMode.None` and `ScalingMode.Local` ([63b24d8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/63b24d8a8b478b3165733ece3eec524e88b28855)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
* UIParticle is scaled incorrectly with nested canvases ([c95d8c6](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/c95d8c6b1774396ff252d13121ad32a3cab0fe5c)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313) ### Bug Fixes
* mainTex will be ignored ([2ee69d0](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/2ee69d04245fabce185f67dc9bd68c870e556564))
# [4.10.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.9.1...v4.10.0) (2024-09-29)
### Bug Fixes
* component icon is not set ([5ff6ec8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/5ff6ec815a174de5d3f16d424f1204c60912a8d8))
### Features ### Features
* remove overlay window (editor) ([fc3fbdd](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/fc3fbdd230595ad3471875ec6fec384a3dad0d17)) * add project settings ([1ce4e31](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/1ce4e31a9681bf1a201d2723c8d97e07ecc16592))
* reset previous position on start play for world space simulation ([e741584](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e7415845074143abae23e3ae7eedc767a01d020d)), closes [#303](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/303)
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` ([dfb94f4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/dfb94f4badfb3035a4374579c53293460b4fd946))
# [5.0.0-preview.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.6...v5.0.0-preview.1) (2024-05-23) ## [4.9.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.9.0...v4.9.1) (2024-08-07)
### Bug Fixes
* ParticleSystem trails gain offset on parent canvas change ([2a1cd50](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/2a1cd502b452b5b56edf8bcfe91adf99d1bb5147)), closes [#323](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/323)
# [4.9.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.1...v4.9.0) (2024-07-18)
### Features ### Features
* add project settings for UIParticle ([b6e6185](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b6e6185c521fef0f118f61cfdfdac07b87555c01)) * ParticleAttractor supports multiple ParticleSystems ([3834780](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/3834780fdb43443fe6e1ef89df54d26a24d62a91))
* change the default value of `UIParticle.scale` from `10` to `1` ([1b3c0f9](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/1b3c0f92dcc6a1974d1ea074821e5264200e9711)), closes [#310](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/310)
* UIParticle no longer inherits from MaskableGraphic ([b6d921b](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b6d921b3e47f749b5028d22b0e89b6eb3a1af7de)) ## [4.8.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.0...v4.8.1) (2024-06-27)
### BREAKING CHANGES ### Bug Fixes
* Some members inherited from MaskableGraphic will no longer be available. * remove debug code ([669deb4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/669deb41d4ac589d9db93b29bc8e95383e7f28a5))
# [4.8.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.2...v4.8.0) (2024-06-27)
### Bug Fixes
* generated baking-camera object remains in the prefab or scene (again) ([de35cba](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/de35cba34c6312c1405ed522e9927c620c78e72d))
* SetParticleSystemInstance/Prefab APIs destroy generated objects ([ae3f3a8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/ae3f3a8e62cc733420354d237ab765ac777127c8))
### Features
* add 'custom view' option. ([a703c29](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a703c2921ca08c2280d0c8fde01e4c0b33b5c69e))
* remove overlay window (editor) ([8358170](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/835817049f4fcf00dd2bf98dbada14f041ad3544))
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` (again) ([88a970d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/88a970d93a2b69cf011d86bd1807569e90538e0e))
* the rendering order list in inspector is now more compact ([be90172](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/be901724e064befacf617f4940b0331e1d31e1ca))
## [4.7.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.1...v4.7.2) (2024-06-21)
### Bug Fixes
* generated baking-camera object remains in the prefab or scene ([0bb8438](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/0bb843830197d8c1252232928becc211c0ada08d))
## [4.7.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.0...v4.7.1) (2024-06-20)
### Bug Fixes
* despite not using the size module, particles become smaller based on their z position ([a8ed6e6](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a8ed6e68584e1d9e45ed852eefcc03979ea7e0e1)), closes [#316](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/316)
# [4.7.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.8...v4.7.0) (2024-06-19)
### Bug Fixes
* `UIParticle.transform.localScale` does not scale particles ([1d40e24](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/1d40e24c742741e97f03c55468ccb1e505341133)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
* UIParticle is scaled by canvas size even when `AutoScalingMode.None` and `ScalingMode.Local` ([54a4b1c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/54a4b1cdfd06400c7be89c1ee704bb42a659c7c2)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
* UIParticle is scaled incorrectly with nested canvases ([f26920f](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f26920f9825547222a4afbb31cc5dc5a002c3e9b)), closes [#313](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/313)
### Features
* reset previous position on start play for world space simulation ([3880484](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/3880484ce5190c42fc79c81d0b69e3fbeda09dd0)), closes [#303](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/303)
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` ([5505247](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/5505247a94a929ff89635fde512a9b95691e0043))
## [4.6.8](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.7...v4.6.8) (2024-06-14)
### Bug Fixes
* 'Resource ID out of range in GetResource' error in overlay rendering mode ([05286ce](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/05286cedfd17b1a0cb90a5e918513644f47cd831)), closes [#308](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/308)
## [4.6.7](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.6...v4.6.7) (2024-05-24)
### Bug Fixes
* the ParticleSystem's localPosition drifts at certain scales due to floating-point precision issues ([e924eb4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e924eb45968a112347471cabaeabc274e4c37ce4)), closes [#299](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/299) [#312](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/312)
## [4.6.6](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.5...v4.6.6) (2024-05-23) ## [4.6.6](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.6.5...v4.6.6) (2024-05-23)

View File

@@ -31,35 +31,35 @@ namespace Coffee.UIExtensions
} }
else else
{ {
result.Aggregate(s_Sb, (a, b) => s_Sb.AppendFormat("{0}, ", b)); result.Aggregate(s_Sb, (a, b) =>
{
s_Sb.Append(b);
return s_Sb.Append(", ");
});
s_Sb.Length -= 2; s_Sb.Length -= 2;
} }
return s_Sb.ToString(); return s_Sb.ToString();
} }
public static void Draw(SerializedProperty sp, Material[] mats) public static void Draw(SerializedProperty sp, List<Material> mats)
{ {
bool isClicked; var pos = EditorGUILayout.GetControlRect(true);
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false))) var label = new GUIContent(sp.displayName, sp.tooltip);
{ var rect = EditorGUI.PrefixLabel(pos, label);
var pos = EditorGUILayout.GetControlRect(true); var text = sp.hasMultipleDifferentValues
var label = new GUIContent(sp.displayName, sp.tooltip); ? "-"
var rect = EditorGUI.PrefixLabel(pos, label); : CollectActiveNames(sp, s_ActiveNames);
var text = sp.hasMultipleDifferentValues
? "-"
: CollectActiveNames(sp, s_ActiveNames);
isClicked = GUI.Button(rect, text, EditorStyles.popup);
}
if (!isClicked) return; if (!GUI.Button(rect, text, EditorStyles.popup)) return;
var gm = new GenericMenu(); var gm = new GenericMenu();
gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, () => gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x =>
{ {
sp.ClearArray(); var current = (SerializedProperty)x;
sp.serializedObject.ApplyModifiedProperties(); current.ClearArray();
}); current.serializedObject.ApplyModifiedProperties();
}, sp);
if (!sp.hasMultipleDifferentValues) if (!sp.hasMultipleDifferentValues)
{ {
@@ -73,7 +73,7 @@ namespace Coffee.UIExtensions
} }
s_Names.Clear(); s_Names.Clear();
for (var j = 0; j < mats.Length; j++) for (var j = 0; j < mats.Count; j++)
{ {
var mat = mats[j]; var mat = mats[j];
if (!mat || !mat.shader) continue; if (!mat || !mat.shader) continue;
@@ -82,8 +82,7 @@ namespace Coffee.UIExtensions
{ {
var name = ShaderUtil.GetPropertyName(mat.shader, i); var name = ShaderUtil.GetPropertyName(mat.shader, i);
var type = (AnimatableProperty.ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i); var type = (AnimatableProperty.ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i);
if (s_Names.Contains(name)) continue; if (!s_Names.Add(name)) continue;
s_Names.Add(name);
AddMenu(gm, sp, new ShaderProperty(name, type), true); AddMenu(gm, sp, new ShaderProperty(name, type), true);

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 3e440931f761e4e888510a4e6045287a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,19 +2,19 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using UnityEditor; using UnityEditor;
using UnityEditor.UI;
using UnityEditorInternal; using UnityEditorInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.UI; using UnityEngine.UI;
using Coffee.UIParticleInternal;
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.Overlays; using UnityEditor.Overlays;
#else #else
using System; using System;
using System.Reflection; using System.Reflection;
using Coffee.UIParticleInternal;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
#endif #endif
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
@@ -26,19 +26,26 @@ namespace Coffee.UIExtensions
{ {
[CustomEditor(typeof(UIParticle))] [CustomEditor(typeof(UIParticle))]
[CanEditMultipleObjects] [CanEditMultipleObjects]
internal class UIParticleEditor : Editor internal class UIParticleEditor : GraphicEditor
{ {
//################################ //################################
// Constant or Static Members. // Constant or Static Members.
//################################ //################################
private static readonly GUIContent[] s_ContentMaterials = new[]
{
new GUIContent("Material"),
new GUIContent("Trail Material")
};
private static readonly GUIContent s_ContentRenderingOrder = new GUIContent("Rendering Order"); private static readonly GUIContent s_ContentRenderingOrder = new GUIContent("Rendering Order");
private static readonly GUIContent s_ContentRefresh = new GUIContent("Refresh"); private static readonly GUIContent s_ContentRefresh = new GUIContent("Refresh");
private static readonly GUIContent s_ContentFix = new GUIContent("Fix"); private static readonly GUIContent s_ContentFix = new GUIContent("Fix");
private static readonly GUIContent s_ContentMaterial = new GUIContent("Material");
private static readonly GUIContent s_ContentTrailMaterial = new GUIContent("Trail Material");
private static readonly GUIContent s_Content3D = new GUIContent("3D"); private static readonly GUIContent s_Content3D = new GUIContent("3D");
private static readonly GUIContent s_ContentRandom = new GUIContent("Random"); private static readonly GUIContent s_ContentRandom = new GUIContent("Random");
private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
private static readonly GUIContent s_ContentPrimary = new GUIContent("Primary");
private static readonly Regex s_RegexBuiltInGuid = new Regex(@"^0{16}.0{15}$", RegexOptions.Compiled);
private static readonly List<Material> s_TempMaterials = new List<Material>();
private static bool s_XYZMode; private static bool s_XYZMode;
private SerializedProperty _maskable; private SerializedProperty _maskable;
@@ -49,6 +56,8 @@ namespace Coffee.UIExtensions
private SerializedProperty _groupMaxId; private SerializedProperty _groupMaxId;
private SerializedProperty _positionMode; private SerializedProperty _positionMode;
private SerializedProperty _autoScalingMode; private SerializedProperty _autoScalingMode;
private SerializedProperty _useCustomView;
private SerializedProperty _customViewSize;
private ReorderableList _ro; private ReorderableList _ro;
private bool _showMax; private bool _showMax;
@@ -72,8 +81,10 @@ namespace Coffee.UIExtensions
/// <summary> /// <summary>
/// This function is called when the object becomes enabled and active. /// This function is called when the object becomes enabled and active.
/// </summary> /// </summary>
private void OnEnable() protected override void OnEnable()
{ {
base.OnEnable();
_maskable = serializedObject.FindProperty("m_Maskable"); _maskable = serializedObject.FindProperty("m_Maskable");
_scale3D = serializedObject.FindProperty("m_Scale3D"); _scale3D = serializedObject.FindProperty("m_Scale3D");
_animatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); _animatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
@@ -82,31 +93,43 @@ namespace Coffee.UIExtensions
_groupMaxId = serializedObject.FindProperty("m_GroupMaxId"); _groupMaxId = serializedObject.FindProperty("m_GroupMaxId");
_positionMode = serializedObject.FindProperty("m_PositionMode"); _positionMode = serializedObject.FindProperty("m_PositionMode");
_autoScalingMode = serializedObject.FindProperty("m_AutoScalingMode"); _autoScalingMode = serializedObject.FindProperty("m_AutoScalingMode");
_useCustomView = serializedObject.FindProperty("m_UseCustomView");
_customViewSize = serializedObject.FindProperty("m_CustomViewSize");
var sp = serializedObject.FindProperty("m_Particles"); var sp = serializedObject.FindProperty("m_Particles");
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true) _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true)
{ {
elementHeight = EditorGUIUtility.singleLineHeight * 3 + 4, elementHeightCallback = index =>
elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2), {
var ps = sp.GetArrayElementAtIndex(index).objectReferenceValue as ParticleSystem;
var materialCount = 0;
if (ps && ps.TryGetComponent<ParticleSystemRenderer>(out var psr))
{
materialCount = psr.sharedMaterials.Length;
}
return (materialCount + 1) * (EditorGUIUtility.singleLineHeight + 2);
},
drawElementCallback = (rect, index, _, __) => drawElementCallback = (rect, index, _, __) =>
{ {
EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues); rect.y += 2;
rect.y += 1;
rect.height = EditorGUIUtility.singleLineHeight; rect.height = EditorGUIUtility.singleLineHeight;
var p = sp.GetArrayElementAtIndex(index); var p = sp.GetArrayElementAtIndex(index);
EditorGUI.ObjectField(rect, p, GUIContent.none); EditorGUI.ObjectField(rect, p, GUIContent.none);
var ps = p.objectReferenceValue as ParticleSystem;
if (!ps || !ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) return;
rect.x += 15; rect.x += 15;
rect.width -= 15; rect.width -= 15;
var ps = p.objectReferenceValue as ParticleSystem; var materials = new SerializedObject(psr).FindProperty("m_Materials");
var materials = ps var count = Mathf.Min(materials.arraySize, 2);
? new SerializedObject(ps.GetComponent<ParticleSystemRenderer>()).FindProperty("m_Materials") for (var i = 0; i < count; i++)
: null; {
rect.y += rect.height + 1; rect.y += rect.height + 2;
MaterialField(rect, s_ContentMaterial, materials, 0); EditorGUI.PropertyField(rect, materials.GetArrayElementAtIndex(i), s_ContentMaterials[i]);
rect.y += rect.height + 1; }
MaterialField(rect, s_ContentTrailMaterial, materials, 1);
EditorGUI.EndDisabledGroup(); if (materials.serializedObject.hasModifiedProperties)
if (materials != null && materials.serializedObject.hasModifiedProperties)
{ {
materials.serializedObject.ApplyModifiedProperties(); materials.serializedObject.ApplyModifiedProperties();
} }
@@ -142,20 +165,6 @@ namespace Coffee.UIExtensions
} }
} }
private static void MaterialField(Rect rect, GUIContent label, SerializedProperty sp, int index)
{
if (sp == null || sp.arraySize <= index)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUI.ObjectField(rect, label, null, typeof(Material), true);
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(rect, sp.GetArrayElementAtIndex(index), label);
}
}
/// <summary> /// <summary>
/// Implement this function to make a custom inspector. /// Implement this function to make a custom inspector.
/// </summary> /// </summary>
@@ -164,6 +173,7 @@ namespace Coffee.UIExtensions
var current = target as UIParticle; var current = target as UIParticle;
if (!current) return; if (!current) return;
Profiler.BeginSample("(UIP:E) OnInspectorGUI");
serializedObject.Update(); serializedObject.Update();
// Maskable // Maskable
@@ -175,13 +185,8 @@ namespace Coffee.UIExtensions
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
// AnimatableProperties // AnimatableProperties
var mats = current.particles current.GetMaterials(s_TempMaterials);
.Where(x => x) AnimatablePropertyEditor.Draw(_animatableProperties, s_TempMaterials);
.Select(x => x.GetComponent<ParticleSystemRenderer>().sharedMaterial)
.Where(x => x)
.ToArray();
AnimatablePropertyEditor.Draw(_animatableProperties, mats);
// Mesh sharing // Mesh sharing
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
@@ -189,9 +194,12 @@ namespace Coffee.UIExtensions
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
{ {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
foreach (var uip in targets.OfType<UIParticle>()) foreach (var t in targets)
{ {
uip.ResetGroupId(); if (t is UIParticle uip)
{
uip.ResetGroupId();
}
} }
} }
@@ -199,16 +207,29 @@ namespace Coffee.UIExtensions
EditorGUILayout.PropertyField(_positionMode); EditorGUILayout.PropertyField(_positionMode);
// Auto Scaling // Auto Scaling
DrawAutoScaling(_autoScalingMode, targets.OfType<UIParticle>()); EditorGUILayout.PropertyField(_autoScalingMode);
// Custom View Size
EditorGUILayout.PropertyField(_useCustomView);
EditorGUI.BeginChangeCheck();
EditorGUI.BeginDisabledGroup(!_useCustomView.boolValue);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_customViewSize);
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
if (EditorGUI.EndChangeCheck())
{
_customViewSize.floatValue = Mathf.Max(0.1f, _customViewSize.floatValue);
}
// Target ParticleSystems. // Target ParticleSystems.
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
EditorGUI.BeginDisabledGroup(targets.OfType<UIParticle>().Any(x => !x.canvas));
_ro.DoLayoutList(); _ro.DoLayoutList();
EditorGUI.EndDisabledGroup();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
{ {
EditorApplication.QueuePlayerLoopUpdate();
foreach (var uip in targets.OfType<UIParticle>()) foreach (var uip in targets.OfType<UIParticle>())
{ {
uip.RefreshParticles(uip.particles); uip.RefreshParticles(uip.particles);
@@ -216,7 +237,8 @@ namespace Coffee.UIExtensions
} }
// Non-UI built-in shader is not supported. // Non-UI built-in shader is not supported.
foreach (var mat in current.materials) Profiler.BeginSample("(UIP:E) Non-UI built-in shader is not supported.");
foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (!mat || !mat.shader) continue;
var shader = mat.shader; var shader = mat.shader;
@@ -229,15 +251,18 @@ namespace Coffee.UIExtensions
} }
} }
Profiler.EndSample();
// Does the shader support UI masks? // Does the shader support UI masks?
Profiler.BeginSample("(UIP:E) Does the shader support UI masks?");
if (current.maskable && current.GetComponentInParent<Mask>(false)) if (current.maskable && current.GetComponentInParent<Mask>(false))
{ {
foreach (var mat in current.materials) foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (!mat || !mat.shader) continue;
var shader = mat.shader; var shader = mat.shader;
if (s_Shaders.Contains(shader)) continue; if (!s_Shaders.Add(shader)) continue;
s_Shaders.Add(shader);
foreach (var propName in s_MaskablePropertyNames) foreach (var propName in s_MaskablePropertyNames)
{ {
if (mat.HasProperty(propName)) continue; if (mat.HasProperty(propName)) continue;
@@ -251,7 +276,9 @@ namespace Coffee.UIExtensions
} }
} }
s_TempMaterials.Clear();
s_Shaders.Clear(); s_Shaders.Clear();
Profiler.EndSample();
// UIParticle for trail should be removed. // UIParticle for trail should be removed.
var label = "This UIParticle component should be removed. The UIParticle for trails is no longer needed."; var label = "This UIParticle component should be removed. The UIParticle for trails is no longer needed.";
@@ -290,12 +317,14 @@ namespace Coffee.UIExtensions
} }
} }
#endif #endif
Profiler.EndSample();
} }
private bool IsBuiltInObject(Object obj) private static bool IsBuiltInObject(Object obj)
{ {
return AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _) return AssetDatabase.IsMainAsset(obj)
&& Regex.IsMatch(guid, "^0{16}.0{15}$", RegexOptions.Compiled); && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _)
&& s_RegexBuiltInGuid.IsMatch(guid);
} }
#if UNITY_2018 || UNITY_2019 #if UNITY_2018 || UNITY_2019
@@ -389,7 +418,7 @@ namespace Coffee.UIExtensions
{ {
EditorGUI.BeginDisabledGroup(true); EditorGUI.BeginDisabledGroup(true);
var obj = UIParticleUpdater.GetPrimary(spGroupId.intValue); var obj = UIParticleUpdater.GetPrimary(spGroupId.intValue);
EditorGUILayout.ObjectField("Primary", obj, typeof(UIParticle), false); EditorGUILayout.ObjectField(s_ContentPrimary, obj, typeof(UIParticle), false);
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
} }
@@ -399,7 +428,7 @@ namespace Coffee.UIExtensions
return showMax; return showMax;
} }
private static void DrawAutoScaling(SerializedProperty prop, IEnumerable<UIParticle> uiParticles) private static void DrawAutoScaling(SerializedProperty prop)
{ {
EditorGUILayout.PropertyField(prop); EditorGUILayout.PropertyField(prop);
} }
@@ -408,7 +437,9 @@ namespace Coffee.UIExtensions
{ {
if (!p || (ignoreCurrent && target == p)) return; if (!p || (ignoreCurrent && target == p)) return;
var cr = p.canvasRenderer;
DestroyImmediate(p); DestroyImmediate(p);
DestroyImmediate(cr);
#if UNITY_2018_3_OR_NEWER #if UNITY_2018_3_OR_NEWER
var stage = PrefabStageUtility.GetCurrentPrefabStage(); var stage = PrefabStageUtility.GetCurrentPrefabStage();

228
README.md
View File

@@ -1,61 +1,85 @@
# <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) # <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 -->
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.
[![](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/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/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.txt) [![](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/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) ![](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) [![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) [![](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) | [🚀 Usage](#-usage) | [🛠 Development Note](#-development-note) | [🤝 Contributing](#-contributing) >>
<br><br> ## 📝 Description <!-- omit in toc -->
## 📝 Description
![](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif) ![](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif)
This package utilizes the new APIs `MeshBake/MashTrailBake` (introduced with Unity 2018.2) to render particles through This package uses the new APIs `MeshBake/MeshTrailBake` (introduced in Unity 2018.2) to render particles through `CanvasRenderer`.
CanvasRenderer. You can render, mask, and sort your `ParticleSystems` for UI without the need for an additional `Camera`, `RenderTexture`, or `Canvas`.
You can render, mask, and sort your ParticleSystems for UI without the necessity of an additional Camera, RenderTexture,
or Canvas.
### Features - [📌 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)
- [🚀 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)
- [Project Settings](#project-settings)
- [🛠 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)
* Easy to use: The package is ready to use out of the box. <br><br>
* Sort particle effects and other UI by sibling index.
* No extra Camera, RenderTexture, or Canvas required.
* Masking options for Mask or RectMask2D.
* Support for the Trail module.
* Support for CanvasGroup alpha.
* No allocations needed to render particles.
* Compatibility with overlay, camera space, and world space.
* Support for Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP).
* Support for disabling `Enter Play Mode Options > Reload Domain`.
* Support for changing material property with AnimationClip (AnimatableProperty).
![AnimatableProperty.gif][AnimatableProperty.gif]
* [4.0.0+] Support for 8+ materials.
* [4.0.0+] Correct world space particle position adjustment when changing window size for standalone platforms (Windows,
MacOSX, and Linux).
* [4.0.0+] Adaptive scaling for UI.
* [4.0.0+] Mesh sharing group to improve performance.
![MeshSharing.gif][MeshSharing.gif]
* [4.0.0+] Particle attractor component.
![ParticleAttractor.gif][ParticleAttractor.gif]
* [4.1.0+] Relative/Absolute particle position mode.
![AbsolutePosition.gif][AbsolutePosition.gif]
[AnimatableProperty.gif]: https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif ## 📌 Key Features
[MeshSharing.gif]: https://user-images.githubusercontent.com/12690315/174311048-c882df81-6c34-4eba-b0aa-5645457692f1.gif * **Easy to use:** The package is ready to use out of the box.
* **Sortable:** Sort particle effects and other UI elements by sibling index.
[ParticleAttractor.gif]: https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif * **Maskable:** Supports `Mask` or `RectMask2D`.
* **No extra components required:** No need for an additional `Camera`, `RenderTexture`, or `Canvas`.
[AbsolutePosition.gif]: https://user-images.githubusercontent.com/12690315/175751579-5a2357e8-2ecf-4afd-83c8-66e9771bde39.gif * **Trail module support:** Fully supports the Trail module.
* **CanvasGroup alpha support:** Integrates with `CanvasGroup` alpha.
* **No allocations:** Efficiently renders particles without allocations.
* **Any canvas render mode support:** Works with overlay, camera space, and world space.
* **Any Render pipeline support:** Compatible with Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP).
* **Disabling domain reload support:** Supports disabling `Enter Play Mode Options > Reload Domain`.
* **Animatable material properties:** Supports changing material properties with AnimationClip (AnimatableProperty).
![AnimatableProperty.gif](https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif)
* **Multiple materials:** Supports 8+ materials.
* **Correct positioning:** Adjusts world space particle positions correctly when changing window size for standalone platforms (Windows, MacOSX, and Linux).
* **Adaptive scaling:** Provides adaptive scaling for UI (AutoScalingMode).
* **Performance optimization:** Mesh sharing group to improve performance.
<img alt="MeshSharing.gif" src="https://user-images.githubusercontent.com/12690315/174311048-c882df81-6c34-4eba-b0aa-5645457692f1.gif" width="450"/>
* **Particle attractor:** Includes a particle attractor component.
<img alt="ParticleAttractor.gif" src="https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif" width="450"/>
* **Emission position mode:** Supports relative/absolute particle emission position modes.
<img alt="AbsolutePosition.gif" src="https://user-images.githubusercontent.com/12690315/175751579-5a2357e8-2ecf-4afd-83c8-66e9771bde39.gif" width="450"/>
* **Custom view size:** Fixes min/max particle size mismatch.
![CustomViewSize.gif](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/dd929959-1a37-420b-b13d-e849022b9c9d)
<br><br> <br><br>
@@ -76,76 +100,86 @@ or Canvas.
[JMO]: https://assetstore.unity.com/publishers/1669 [JMO]: https://assetstore.unity.com/publishers/1669
<br><br> <br><br>
## ⚙ Installation ## ⚙ Installation
_This package requires Unity 2018.3 or later._ _This package requires **Unity 2018.3 or later**._
#### Install via OpenUPM #### Install via OpenUPM
This package is available on [OpenUPM](https://openupm.com) package registry. - This package is available on [OpenUPM](https://openupm.com) package registry.
This is the preferred method of installation, as you can easily receive updates as they're released. - This is the preferred method of installation, as you can easily receive updates as they're released.
- If you have [openupm-cli](https://github.com/openupm/openupm-cli) installed, then run the following command in your project's directory:
```
openupm add com.coffee.ui-particle
```
- 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.9.0
```
If you have [openupm-cli](https://github.com/openupm/openupm-cli) installed, then run the following command in your #### Install via UPM (with Package Manager UI)
project's directory:
```sh - Click `Window > Package Manager` to open Package Manager UI.
openupm add com.coffee.ui-particle - Click `+ > Add package from git URL...` and input the repository URL: `https://github.com/mob-sakai/ParticleEffectForUGUI.git`
``` ![](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.9.0`
#### Install via UPM (using Git URL) #### Install via UPM (Manually)
Navigate to your project's Packages folder and open the `manifest.json` file. Then add this package somewhere in - Open the `Packages/manifest.json` file in your project. Then add this package somewhere in the `dependencies` block:
the `dependencies` block: ```json
{
```json "dependencies": {
{ "com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git",
"dependencies": { ...
"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git", }
...
} }
} ```
```
To update the package, change suffix `#{version}` to the target version. - 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.9.0",`
* e.g. `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.6.0",` #### Install as Embedded Package
Or, use [UpmGitExtension](https://github.com/mob-sakai/UpmGitExtension) to install and update the package. 1. Download a source code zip file from [Releases](https://github.com/mob-sakai/ParticleEffectForUGUI.git/releases) and extract it.
2. Place it in your project's `Packages` directory.
<br><br> ![](https://github.com/mob-sakai/mob-sakai/assets/12690315/0b7484b4-5fca-43b0-a9ef-e5dbd99bcdb4)
- If you want to fix bugs or add features, install it as an embedded package.
## ⚙ Upgrading from 3.x/4.x to 5.x - To update the package, you need to re-download it and replace the contents.
### Breaking Changes
- The default value of `UIParticle.scale` has been changed from `10` to `1`.
- `UIParticle` no longer inherits from `MaskableGraphic`.
-
<br><br> <br><br>
## 🚀 Usage ## 🚀 Usage
### UIParticle Component ### Component: UIParticle
`UIParticle` controls the ParticleSystems that are attached to its own game objects and child game objects. `UIParticle` controls the ParticleSystems that are attached to its own game objects and child game objects.
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/3559df45-63e7-4c4c-9233-f455779efa29) ![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/1cf5753b-33fc-4cef-91c3-413c515a954f)
- **Maskable**: Does this graphic allow masking. - **Maskable**: Does this graphic allow maskable.
- **Scale**: Scale the rendering. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported. - **Scale**: Scale the rendering particles. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported.
- **Animatable Properties**: If you want to update material properties (e.g., `_MainTex_ST`, `_Color`) in AnimationClip, - **Animatable Properties**: If you want to update material properties (e.g., `_MainTex_ST`, `_Color`) in AnimationClip,
use this to mark the changes. use this to mark as animatable.
- **Mesh Sharing**: Particle simulation results are shared within the same group. A large number of the same effects can - **Mesh Sharing**: Particle simulation results are shared within the same group. A large number of the same effects can
be displayed with a small load. When the `Random` toggle is enabled, it will be grouped randomly. be displayed with a small load. When the `Random` toggle is enabled, it will be grouped randomly.
- **None:** Disable mesh sharing.
- **Auto:** Automatically select Primary/Replica.
- **Primary:** Provides particle simulation results to the same group.
- **Primary Simulator:** Primary, but do not render the particle (simulation only).
- **Replica:** Render simulation results provided by the primary.
- **Position Mode**: Emission position mode. - **Position Mode**: Emission position mode.
- **Absolute:** Emit from the world position of the `ParticleSystem`. - **Absolute:** The particles will be emitted from the world position.
- **Relative:** Emit from the scaled position of the `ParticleSystem`. - **Relative:** The particles will be emitted from the scaled position.
- **Auto Scaling**: `Transform.lossyScale` (=world scale) will be set to `(1, 1, 1)` on update. It prevents the - **Auto Scaling Mode**: How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution.
root-Canvas scale from affecting the hierarchy-scaled `ParticleSystem`. - **None:** Do nothing.
- **Transform:** Transform.lossyScale (=world scale) will be set to (1, 1, 1).
- **UIParticle:** UIParticle.scale will be adjusted.
- **Use Custom View:** Use this if the particles are not displayed correctly due to min/max particle size.
- **Custom view size:** Change the bake view size.
- **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 **NOTE:** Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order
@@ -153,7 +187,7 @@ and z-position.
<br><br> <br><br>
#### Basic Usage ### Basic Usage
1. Select `GameObject/UI/ParticleSystem` to create UIParticle with a ParticleSystem. 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) ![particle](https://user-images.githubusercontent.com/12690315/95007361-cad0e880-0649-11eb-8835-f145d62c5977.png)
@@ -162,7 +196,7 @@ and z-position.
<br> <br>
#### With Your Existing ParticleSystem Prefab ### Usage with Your Existing ParticleSystem Prefab
1. Select `GameObject/UI/ParticleSystem (Empty)` to create UIParticle. 1. Select `GameObject/UI/ParticleSystem (Empty)` to create UIParticle.
![empty](https://user-images.githubusercontent.com/12690315/95007362-cb697f00-0649-11eb-8a09-29b0a13791e4.png) ![empty](https://user-images.githubusercontent.com/12690315/95007362-cb697f00-0649-11eb-8a09-29b0a13791e4.png)
@@ -171,7 +205,7 @@ and z-position.
<br> <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 If you want to mask particles, set a stencil-supported shader (such as `UI/UIAdditive`) to the material for
ParticleSystem. ParticleSystem.
@@ -183,7 +217,7 @@ section.
<br><br> <br><br>
### Script usage ### Usage with Script
```cs ```cs
// Instantiate ParticleSystem prefab with UIParticle on runtime. // Instantiate ParticleSystem prefab with UIParticle on runtime.
@@ -202,14 +236,14 @@ uiParticle.Stop();
<br><br> <br><br>
### UIParticleAttractor component ### Component: UIParticleAttractor
`UIParticleAttractor` attracts particles generated by the specified ParticleSystem. `UIParticleAttractor` attracts particles generated by the specified ParticleSystem.
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/ea6ae0ed-f9a8-437c-8baa-47526303391e) ![](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) ![](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` - **Destination Radius**: Once the particle is within the radius, the particle lifetime will become 0, and `OnAttracted`
will be called. will be called.
- **Delay Rate**: Delay to start attracting. It is a percentage of the particle's start lifetime. - **Delay Rate**: Delay to start attracting. It is a percentage of the particle's start lifetime.
@@ -223,6 +257,14 @@ uiParticle.Stop();
<br><br> <br><br>
### Project Settings
![](https://github.com/user-attachments/assets/befc7f34-fb47-4006-831a-eba79fda11ca)
- Click `Edit > Project Settings` to open the Project Settings window and then select `UI > UI Particle` category.
<br><br>
## 🛠 Development Note ## 🛠 Development Note
### Compares the Baking mesh approach with the conventional approach ### Compares the Baking mesh approach with the conventional approach
@@ -354,7 +396,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 - Consider a single material, atlasing the sprites, and using `Sprite` mode in the `Texture Sheet Animation` module
in the ParticleSystem. 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> <details>
<summary>Shader tips</summary> <summary>Shader tips</summary>

View File

@@ -50,7 +50,7 @@ namespace Coffee.UIParticleInternal
var childCount = tr.childCount; var childCount = tr.childCount;
for (var i = 0; i < childCount; i++) for (var i = 0; i < childCount; i++)
{ {
tr.GetChild(i).GetComponentsInChildren(results, depth - 1); tr.GetChild(i).GetComponentsInChildren_Internal(results, depth - 1);
} }
} }

View File

@@ -1,19 +0,0 @@
using System.Collections.Generic;
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Extension methods for Component class.
/// </summary>
internal static class ListExtensions
{
public static void RemoveAtFast<T>(this List<T> self, int index)
{
if (self == null) return;
var lastIndex = self.Count - 1;
self[index] = self[lastIndex];
self.RemoveAt(lastIndex);
}
}
}

View File

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

View File

@@ -13,13 +13,15 @@ namespace Coffee.UIParticleInternal
{ {
public abstract class PreloadedProjectSettings : ScriptableObject public abstract class PreloadedProjectSettings : ScriptableObject
#if UNITY_EDITOR #if UNITY_EDITOR
, IPreprocessBuildWithReport
{ {
int IOrderedCallback.callbackOrder => 0; private class PreprocessBuildWithReport : IPreprocessBuildWithReport
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
{ {
Initialize(); int IOrderedCallback.callbackOrder => 0;
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
{
Initialize();
}
} }
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
@@ -32,9 +34,9 @@ namespace Coffee.UIParticleInternal
var defaultSettings = GetDefaultSettings(t); var defaultSettings = GetDefaultSettings(t);
if (!defaultSettings) if (!defaultSettings)
{ {
// When create a new instance, automatically set it as default settings.
defaultSettings = t.GetProperty("instance", flags) defaultSettings = t.GetProperty("instance", flags)
?.GetValue(null, null) as PreloadedProjectSettings; ?.GetValue(null, null) as PreloadedProjectSettings;
SetDefaultSettings(defaultSettings);
} }
else if (GetPreloadedSettings(t).Length != 1) else if (GetPreloadedSettings(t).Length != 1)
{ {
@@ -71,6 +73,7 @@ namespace Coffee.UIParticleInternal
protected static void SetDefaultSettings(PreloadedProjectSettings asset) protected static void SetDefaultSettings(PreloadedProjectSettings asset)
{ {
if (!asset) return;
var type = asset.GetType(); var type = asset.GetType();
if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(asset))) if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(asset)))
{ {
@@ -101,7 +104,6 @@ namespace Coffee.UIParticleInternal
} }
#endif #endif
public abstract class PreloadedProjectSettings<T> : PreloadedProjectSettings public abstract class PreloadedProjectSettings<T> : PreloadedProjectSettings
where T : PreloadedProjectSettings<T> where T : PreloadedProjectSettings<T>
{ {

View File

@@ -20,6 +20,7 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
public void Add(T rhs) public void Add(T rhs)
{ {
if (rhs == null) return;
Profiler.BeginSample("(COF)[FastAction] Add Action"); Profiler.BeginSample("(COF)[FastAction] Add Action");
var node = s_NodePool.Rent(); var node = s_NodePool.Rent();
node.Value = rhs; node.Value = rhs;
@@ -32,6 +33,7 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
public void Remove(T rhs) public void Remove(T rhs)
{ {
if (rhs == null) return;
Profiler.BeginSample("(COF)[FastAction] Remove Action"); Profiler.BeginSample("(COF)[FastAction] Remove Action");
var node = _delegates.Find(rhs); var node = _delegates.Find(rhs);
if (node != null) if (node != null)
@@ -63,6 +65,11 @@ namespace Coffee.UIParticleInternal
node = node.Next; node = node.Next;
} }
} }
public void Clear()
{
_delegates.Clear();
}
} }
/// <summary> /// <summary>

View File

@@ -46,7 +46,6 @@ namespace Coffee.UIParticleInternal
GetFrameCache<T>().Set((key1.GetHashCode(), key2.GetHashCode()), result); GetFrameCache<T>().Set((key1.GetHashCode(), key2.GetHashCode()), result);
} }
/// <summary> /// <summary>
/// Sets a value in the frame cache with a specified key. /// Sets a value in the frame cache with a specified key.
/// </summary> /// </summary>

View File

@@ -5,7 +5,6 @@ using Object = UnityEngine.Object;
#if ENABLE_COFFEE_LOGGER #if ENABLE_COFFEE_LOGGER
using System.Reflection; using System.Reflection;
using System.Collections.Generic; using System.Collections.Generic;
#else #else
using Conditional = System.Diagnostics.ConditionalAttribute; using Conditional = System.Diagnostics.ConditionalAttribute;
#endif #endif
@@ -43,7 +42,6 @@ namespace Coffee.UIParticleInternal
#endif #endif
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif
@@ -53,7 +51,6 @@ namespace Coffee.UIParticleInternal
Log_Internal(LogType.Log, tag, message, context ? context : tag as Object); Log_Internal(LogType.Log, tag, message, context ? context : tag as Object);
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif
@@ -62,7 +59,6 @@ namespace Coffee.UIParticleInternal
Log_Internal(LogType.Log, tag, message, context ? context : tag as Object); Log_Internal(LogType.Log, tag, message, context ? context : tag as Object);
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif
@@ -80,7 +76,6 @@ namespace Coffee.UIParticleInternal
#endif #endif
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif
@@ -124,7 +119,6 @@ namespace Coffee.UIParticleInternal
#endif #endif
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif
@@ -141,6 +135,9 @@ namespace Coffee.UIParticleInternal
switch (tag) switch (tag)
{ {
case string name:
sb.Append(name);
break;
case Type type: case Type type:
AppendType(sb, type); AppendType(sb, type);
break; break;
@@ -167,7 +164,6 @@ namespace Coffee.UIParticleInternal
#endif #endif
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif
@@ -206,7 +202,6 @@ namespace Coffee.UIParticleInternal
#endif #endif
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)] [Conditional(k_DisableSymbol)]
#endif #endif

View File

@@ -55,7 +55,6 @@ namespace Coffee.UIParticleInternal
Profiler.EndSample(); Profiler.EndSample();
} }
/// <summary> /// <summary>
/// Adds or retrieves a cached material based on the hash. /// Adds or retrieves a cached material based on the hash.
/// </summary> /// </summary>

View File

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

View File

@@ -64,11 +64,12 @@ namespace Coffee.UIParticleInternal
#if UNITY_EDITOR #if UNITY_EDITOR
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif #endif
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {
Canvas.willRenderCanvases -= OnAfterCanvasRebuild;
s_IsInitializedAfterCanvasRebuild = false;
} }
/// <summary> /// <summary>

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Coffee.UIParticleInternal; using Coffee.UIParticleInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Rendering; using UnityEngine.Rendering;
using UnityEngine.Serialization; using UnityEngine.Serialization;
using UnityEngine.UI;
using Random = UnityEngine.Random; using Random = UnityEngine.Random;
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] [assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
@@ -18,7 +18,7 @@ namespace Coffee.UIExtensions
[ExecuteAlways] [ExecuteAlways]
[RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(CanvasRenderer))]
public class UIParticle : UIBehaviour, ISerializationCallbackReceiver public class UIParticle : MaskableGraphic, ISerializationCallbackReceiver
{ {
public enum AutoScalingMode public enum AutoScalingMode
{ {
@@ -58,12 +58,12 @@ namespace Coffee.UIExtensions
[Obsolete] [Obsolete]
internal bool m_AbsoluteMode; internal bool m_AbsoluteMode;
[Tooltip("Particle effect scale")] [Tooltip("Scale the rendering particles. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported.")]
[SerializeField] [SerializeField]
private Vector3 m_Scale3D = new Vector3(1, 1, 1); private Vector3 m_Scale3D = new Vector3(10, 10, 10);
[Tooltip("Animatable material properties.\n" + [Tooltip("If you want to update material properties (e.g. _MainTex_ST, _Color) in AnimationClip, " +
"If you want to change the material properties of the ParticleSystem in Animation, enable it.")] "use this to mark as animatable.")]
[SerializeField] [SerializeField]
internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
@@ -71,12 +71,13 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private List<ParticleSystem> m_Particles = new List<ParticleSystem>(); private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
[Tooltip("Mesh sharing.\n" + [Tooltip("Particle simulation results are shared within the same group. " +
"None: disable mesh sharing.\n" + "A large number of the same effects can be displayed with a small load.\n" +
"Auto: automatically select Primary/Replica.\n" + "None: Disable mesh sharing.\n" +
"Primary: provides particle simulation results to the same group.\n" + "Auto: Automatically select Primary/Replica.\n" +
"Primary: Provides particle simulation results to the same group.\n" +
"Primary Simulator: Primary, but do not render the particle (simulation only).\n" + "Primary Simulator: Primary, but do not render the particle (simulation only).\n" +
"Replica: render simulation results provided by the primary.")] "Replica: Render simulation results provided by the primary.")]
[SerializeField] [SerializeField]
private MeshSharing m_MeshSharing = MeshSharing.None; private MeshSharing m_MeshSharing = MeshSharing.None;
@@ -88,67 +89,53 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private int m_GroupMaxId; private int m_GroupMaxId;
[Tooltip("Relative: The particles will be emitted from the scaled position of ParticleSystem.\n" + [Tooltip("Emission position mode.\n" +
"Absolute: The particles will be emitted from the world position of ParticleSystem.")] "Relative: The particles will be emitted from the scaled position.\n" +
"Absolute: The particles will be emitted from the world position.")]
[SerializeField] [SerializeField]
private PositionMode m_PositionMode = PositionMode.Relative; private PositionMode m_PositionMode = PositionMode.Relative;
[SerializeField] [SerializeField]
[Tooltip("Prevent the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.")]
[Obsolete] [Obsolete]
internal bool m_AutoScaling; internal bool m_AutoScaling;
[SerializeField] [SerializeField]
[Tooltip("Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1)." + [Tooltip(
"UIParticle: UIParticle.scale will be adjusted.")] "How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution.\n" +
"None: Do nothing.\n" +
"Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1).\n" +
"UIParticle: UIParticle.scale will be adjusted.")]
private AutoScalingMode m_AutoScalingMode = AutoScalingMode.Transform; private AutoScalingMode m_AutoScalingMode = AutoScalingMode.Transform;
[SerializeField] [SerializeField]
private bool m_Maskable = true; [Tooltip("Use a custom view.\n" +
"Use this if the particles are not displayed correctly due to min/max particle size.")]
private bool m_UseCustomView;
[SerializeField]
[Tooltip("Custom view size.\n" +
"Change the bake view size.")]
private float m_CustomViewSize = 10;
private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>(); private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>();
private Canvas _canvas;
private int _groupId;
private Camera _bakeCamera; private Camera _bakeCamera;
private DrivenRectTransformTracker _tracker; private int _groupId;
private Vector3 _storedScale;
private bool _isScaleStored; private bool _isScaleStored;
private Vector3 _storedScale;
public RectTransform rectTransform => transform as RectTransform; private DrivenRectTransformTracker _tracker;
public Canvas canvas
{
get
{
if (_canvas) return _canvas;
var tr = transform;
while (tr && !_canvas)
{
if (tr.TryGetComponent(out _canvas)) return _canvas;
tr = tr.parent;
}
return null;
}
}
/// <summary> /// <summary>
/// Does this graphic allow masking. /// Should this graphic be considered a target for ray-casting?
/// </summary> /// </summary>
public bool maskable public override bool raycastTarget
{ {
get => m_Maskable; get => false;
set set { }
{
if (value == m_Maskable) return;
m_Maskable = value;
UpdateRendererMaterial();
}
} }
/// <summary> /// <summary>
/// Mesh sharing. /// Particle simulation results are shared within the same group.
/// A large number of the same effects can be displayed with a small load.
/// None: disable mesh sharing. /// None: disable mesh sharing.
/// Auto: automatically select Primary/Replica. /// Auto: automatically select Primary/Replica.
/// Primary: provides particle simulation results to the same group. /// Primary: provides particle simulation results to the same group.
@@ -191,9 +178,9 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// Particle position mode. /// Emission position mode.
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem. /// Relative: The particles will be emitted from the scaled position.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem. /// Absolute: The particles will be emitted from the world position.
/// </summary> /// </summary>
public PositionMode positionMode public PositionMode positionMode
{ {
@@ -206,6 +193,7 @@ namespace Coffee.UIExtensions
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem. /// Relative: The particles will be emitted from the scaled position of the ParticleSystem.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem. /// Absolute: The particles will be emitted from the world position of the ParticleSystem.
/// </summary> /// </summary>
[Obsolete("The absoluteMode is now obsolete. Please use the autoScalingMode instead.", false)]
public bool absoluteMode public bool absoluteMode
{ {
get => m_PositionMode == PositionMode.Absolute; get => m_PositionMode == PositionMode.Absolute;
@@ -223,8 +211,12 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// Auto scaling mode. /// How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution.
/// <para/>
/// None: Do nothing.
/// <para/>
/// Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1). /// Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1).
/// <para/>
/// UIParticle: UIParticle.scale will be adjusted. /// UIParticle: UIParticle.scale will be adjusted.
/// </summary> /// </summary>
public AutoScalingMode autoScalingMode public AutoScalingMode autoScalingMode
@@ -243,6 +235,26 @@ namespace Coffee.UIExtensions
} }
} }
/// <summary>
/// Use a custom view.
/// Use this if the particles are not displayed correctly due to min/max particle size.
/// </summary>
public bool useCustomView
{
get => m_UseCustomView;
set => m_UseCustomView = value;
}
/// <summary>
/// Custom view size.
/// Change the bake view size.
/// </summary>
public float customViewSize
{
get => m_CustomViewSize;
set => m_CustomViewSize = Mathf.Max(0.1f, value);
}
internal bool useMeshSharing => m_MeshSharing != MeshSharing.None; internal bool useMeshSharing => m_MeshSharing != MeshSharing.None;
internal bool isPrimary => internal bool isPrimary =>
@@ -288,22 +300,6 @@ namespace Coffee.UIExtensions
public List<ParticleSystem> particles => m_Particles; public List<ParticleSystem> particles => m_Particles;
/// <summary>
/// Get all base materials to render.
/// </summary>
public IEnumerable<Material> materials
{
get
{
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r || !r.material) continue;
yield return r.material;
}
}
}
/// <summary> /// <summary>
/// Paused. /// Paused.
/// </summary> /// </summary>
@@ -311,15 +307,15 @@ namespace Coffee.UIExtensions
public Vector3 parentScale { get; private set; } public Vector3 parentScale { get; private set; }
private Vector3 canvasScale { get; set; } public Vector3 canvasScale { get; private set; }
protected override void OnEnable() protected override void OnEnable()
{ {
_isScaleStored = false; _isScaleStored = false;
ResetGroupId(); ResetGroupId();
UIParticleUpdater.Register(this); UIParticleUpdater.Register(this);
RegisterDirtyMaterialCallback(UpdateRendererMaterial);
//
if (0 < particles.Count) if (0 < particles.Count)
{ {
RefreshParticles(particles); RefreshParticles(particles);
@@ -329,7 +325,7 @@ namespace Coffee.UIExtensions
RefreshParticles(); RefreshParticles();
} }
UpdateRendererMaterial(); base.OnEnable();
} }
/// <summary> /// <summary>
@@ -346,15 +342,9 @@ namespace Coffee.UIExtensions
_isScaleStored = false; _isScaleStored = false;
UIParticleUpdater.Unregister(this); UIParticleUpdater.Unregister(this);
_renderers.ForEach(r => r.Reset()); _renderers.ForEach(r => r.Reset());
_canvas = null; UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
}
/// <summary> base.OnDisable();
/// Called when the state of the parent Canvas is changed.
/// </summary>
protected override void OnCanvasHierarchyChanged()
{
_canvas = null;
} }
/// <summary> /// <summary>
@@ -364,14 +354,6 @@ namespace Coffee.UIExtensions
{ {
} }
/// <summary>
/// This function is called when a direct or indirect parent of the transform of the GameObject has changed.
/// </summary>
protected override void OnTransformParentChanged()
{
_canvas = null;
}
void ISerializationCallbackReceiver.OnBeforeSerialize() void ISerializationCallbackReceiver.OnBeforeSerialize()
{ {
} }
@@ -462,6 +444,21 @@ namespace Coffee.UIExtensions
isPaused = true; isPaused = true;
} }
/// <summary>
/// Get all base materials to render.
/// </summary>
public void GetMaterials(List<Material> result)
{
if (result == null) return;
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r || !r.material) continue;
result.Add(r.material);
}
}
/// <summary> /// <summary>
/// Refresh UIParticle using the ParticleSystem instance. /// Refresh UIParticle using the ParticleSystem instance.
/// </summary> /// </summary>
@@ -481,6 +478,9 @@ namespace Coffee.UIExtensions
for (var i = 0; i < childCount; i++) for (var i = 0; i < childCount; i++)
{ {
var go = transform.GetChild(i).gameObject; var go = transform.GetChild(i).gameObject;
if (go.TryGetComponent<Camera>(out var cam) && cam == _bakeCamera) continue;
if (go.TryGetComponent<UIParticleRenderer>(out var _)) continue;
go.SetActive(false); go.SetActive(false);
if (destroyOldParticles) if (destroyOldParticles)
{ {
@@ -602,8 +602,12 @@ namespace Coffee.UIExtensions
} }
var currentScale = transform.localScale; var currentScale = transform.localScale;
_storedScale = currentScale; if (!_isScaleStored)
_isScaleStored = true; {
_storedScale = currentScale.IsVisible() ? currentScale : Vector3.one;
_isScaleStored = true;
}
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
var newScale = parentScale.Inverse(); var newScale = parentScale.Inverse();
if (currentScale != newScale) if (currentScale != newScale)
@@ -642,6 +646,17 @@ namespace Coffee.UIExtensions
: Random.Range(m_GroupId, m_GroupMaxId + 1); : Random.Range(m_GroupId, m_GroupMaxId + 1);
} }
protected override void UpdateMaterial()
{
}
/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
protected override void UpdateGeometry()
{
}
private void UpdateRendererMaterial() private void UpdateRendererMaterial()
{ {
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
@@ -671,7 +686,16 @@ namespace Coffee.UIExtensions
private Camera GetBakeCamera() private Camera GetBakeCamera()
{ {
if (!canvas) return Camera.main; if (!canvas) return Camera.main;
if (_bakeCamera) return _bakeCamera; if (!useCustomView && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.rootCanvas.worldCamera)
{
return canvas.rootCanvas.worldCamera;
}
if (_bakeCamera)
{
_bakeCamera.orthographicSize = useCustomView ? customViewSize : 10;
return _bakeCamera;
}
// Find existing baking camera. // Find existing baking camera.
var childCount = transform.childCount; var childCount = transform.childCount;
@@ -688,11 +712,7 @@ namespace Coffee.UIExtensions
// Create baking camera. // Create baking camera.
if (!_bakeCamera) if (!_bakeCamera)
{ {
var go = new GameObject("[generated] UIParticle BakingCamera") var go = new GameObject("[generated] UIParticle BakingCamera");
{
// hideFlags = HideFlags.HideAndDontSave
hideFlags = HideFlags.DontSave
};
go.SetActive(false); go.SetActive(false);
go.transform.SetParent(transform, false); go.transform.SetParent(transform, false);
_bakeCamera = go.AddComponent<Camera>(); _bakeCamera = go.AddComponent<Camera>();
@@ -700,7 +720,7 @@ namespace Coffee.UIExtensions
// Setup baking camera. // Setup baking camera.
_bakeCamera.enabled = false; _bakeCamera.enabled = false;
_bakeCamera.orthographicSize = 1000; _bakeCamera.orthographicSize = useCustomView ? customViewSize : 10;
_bakeCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity); _bakeCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity);
_bakeCamera.orthographic = true; _bakeCamera.orthographic = true;
_bakeCamera.farClipPlane = 2000f; _bakeCamera.farClipPlane = 2000f;
@@ -711,6 +731,9 @@ namespace Coffee.UIExtensions
_bakeCamera.renderingPath = RenderingPath.Forward; _bakeCamera.renderingPath = RenderingPath.Forward;
_bakeCamera.useOcclusionCulling = false; _bakeCamera.useOcclusionCulling = false;
_bakeCamera.gameObject.SetActive(false);
_bakeCamera.gameObject.hideFlags = UIParticleProjectSettings.globalHideFlags;
return _bakeCamera; return _bakeCamera;
} }
} }

View File

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

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0
icon: {instanceID: 0} icon: {fileID: 2800000, guid: 5f0675613942149309588d556e33d990, type: 3}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@@ -17,6 +17,18 @@ namespace Coffee.UIExtensions
set => instance.m_EnableLinearToGamma = value; set => instance.m_EnableLinearToGamma = value;
} }
[Header("Editor")]
[Tooltip("Hide the automatically generated objects.\n" +
" - UIParticleRenderer\n" +
" - UIParticle BakingCamera")]
[SerializeField]
private bool m_HideGeneratedObjects = true;
public static HideFlags globalHideFlags => instance.m_HideGeneratedObjects
? HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInHierarchy | HideFlags.HideInInspector
: HideFlags.DontSave | HideFlags.NotEditable;
#if UNITY_EDITOR #if UNITY_EDITOR
[SettingsProvider] [SettingsProvider]
private static SettingsProvider CreateSettingsProvider() private static SettingsProvider CreateSettingsProvider()

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0
icon: {instanceID: 0} icon: {fileID: 2800000, guid: 5f0675613942149309588d556e33d990, type: 3}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@@ -4,7 +4,6 @@
#elif UNITY_2022_3_OR_NEWER #elif UNITY_2022_3_OR_NEWER
#define PS_BAKE_API_V2 #define PS_BAKE_API_V2
#endif #endif
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Coffee.UIParticleInternal; using Coffee.UIParticleInternal;
@@ -25,10 +24,10 @@ namespace Coffee.UIExtensions
private static readonly CombineInstance[] s_CombineInstances = { new CombineInstance() }; private static readonly CombineInstance[] s_CombineInstances = { new CombineInstance() };
private static readonly List<Material> s_Materials = new List<Material>(2); private static readonly List<Material> s_Materials = new List<Material>(2);
private static MaterialPropertyBlock s_Mpb; private static MaterialPropertyBlock s_Mpb;
private static readonly List<UIParticleRenderer> s_Renderers = new List<UIParticleRenderer>(8);
private static readonly Vector3[] s_Corners = new Vector3[4]; private static readonly Vector3[] s_Corners = new Vector3[4];
private bool _delay; private bool _delay;
private int _index; private int _index;
private bool _isPrevStored;
private bool _isTrail; private bool _isTrail;
private Bounds _lastBounds; private Bounds _lastBounds;
private Material _materialForRendering; private Material _materialForRendering;
@@ -38,7 +37,6 @@ namespace Coffee.UIExtensions
private float _prevCanvasScale; private float _prevCanvasScale;
private Vector3 _prevPsPos; private Vector3 _prevPsPos;
private Vector3 _prevScale; private Vector3 _prevScale;
private bool _isPrevStored;
private Vector2Int _prevScreenSize; private Vector2Int _prevScreenSize;
private bool _preWarm; private bool _preWarm;
private ParticleSystemRenderer _renderer; private ParticleSystemRenderer _renderer;
@@ -138,6 +136,7 @@ namespace Coffee.UIExtensions
{ {
base.OnEnable(); base.OnEnable();
hideFlags = UIParticleProjectSettings.globalHideFlags;
if (!s_CombineInstances[0].mesh) if (!s_CombineInstances[0].mesh)
{ {
s_CombineInstances[0].mesh = new Mesh s_CombineInstances[0].mesh = new Mesh
@@ -162,7 +161,7 @@ namespace Coffee.UIExtensions
// Create renderer object. // Create renderer object.
var go = new GameObject("[generated] UIParticleRenderer", typeof(UIParticleRenderer)) var go = new GameObject("[generated] UIParticleRenderer", typeof(UIParticleRenderer))
{ {
hideFlags = HideFlags.HideAndDontSave, hideFlags = UIParticleProjectSettings.globalHideFlags,
layer = parent.gameObject.layer layer = parent.gameObject.layer
}; };
@@ -202,7 +201,6 @@ namespace Coffee.UIExtensions
return modifiedMaterial; return modifiedMaterial;
} }
//
var hash = new Hash128( var hash = new Hash128(
modifiedMaterial ? (uint)modifiedMaterial.GetInstanceID() : 0, modifiedMaterial ? (uint)modifiedMaterial.GetInstanceID() : 0,
texture ? (uint)texture.GetInstanceID() : 0, texture ? (uint)texture.GetInstanceID() : 0,
@@ -425,13 +423,12 @@ namespace Coffee.UIExtensions
var components = ListPool<Component>.Rent(); var components = ListPool<Component>.Rent();
GetComponents(typeof(IMeshModifier), components); GetComponents(typeof(IMeshModifier), components);
#pragma warning disable CS0618 // Type or member is obsolete
for (var i = 0; i < components.Count; i++) for (var i = 0; i < components.Count; i++)
{ {
#pragma warning disable CS0618 // Type or member is obsolete
((IMeshModifier)components[i]).ModifyMesh(workerMesh); ((IMeshModifier)components[i]).ModifyMesh(workerMesh);
}
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
}
ListPool<Component>.Return(ref components); ListPool<Component>.Return(ref components);
} }
@@ -445,22 +442,25 @@ namespace Coffee.UIExtensions
// Get grouped renderers. // Get grouped renderers.
Profiler.BeginSample("[UIParticleRenderer] Set Mesh"); Profiler.BeginSample("[UIParticleRenderer] Set Mesh");
s_Renderers.Clear(); var renderers = ListPool<UIParticleRenderer>.Rent();
if (_parent.useMeshSharing) if (_parent.useMeshSharing)
{ {
UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers); UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, renderers);
} }
for (var i = 0; i < s_Renderers.Count; i++) for (var i = 0; i < renderers.Count; i++)
{ {
if (s_Renderers[i] == this) continue; var r = renderers[i];
if (r == this) continue;
s_Renderers[i].canvasRenderer.SetMesh(workerMesh); r.canvasRenderer.SetMesh(workerMesh);
s_Renderers[i]._lastBounds = _lastBounds; r._lastBounds = _lastBounds;
s_Renderers[i].canvasRenderer.materialCount = 1; r.canvasRenderer.materialCount = 1;
s_Renderers[i].canvasRenderer.SetMaterial(materialForRendering, 0); r.canvasRenderer.SetMaterial(materialForRendering, 0);
} }
ListPool<UIParticleRenderer>.Return(ref renderers);
if (_parent.canRender) if (_parent.canRender)
{ {
canvasRenderer.SetMesh(workerMesh); canvasRenderer.SetMesh(workerMesh);
@@ -471,8 +471,6 @@ namespace Coffee.UIExtensions
} }
Profiler.EndSample(); Profiler.EndSample();
s_Renderers.Clear();
} }
public override void SetMaterialDirty() public override void SetMaterialDirty()
@@ -544,6 +542,12 @@ namespace Coffee.UIExtensions
return Matrix4x4.Translate(psPos) return Matrix4x4.Translate(psPos)
* Matrix4x4.Scale(scale); * Matrix4x4.Scale(scale);
case ParticleSystemSimulationSpace.World: case ParticleSystemSimulationSpace.World:
if (_isTrail)
{
return Matrix4x4.Translate(psPos)
* Matrix4x4.Scale(scale)
* Matrix4x4.Translate(-psPos);
}
return Matrix4x4.Scale(scale); return Matrix4x4.Scale(scale);
case ParticleSystemSimulationSpace.Custom: case ParticleSystemSimulationSpace.Custom:
return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale)) return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale))

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0
icon: {instanceID: 0} icon: {fileID: 2800000, guid: 5f0675613942149309588d556e33d990, type: 3}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,7 +40,10 @@
Lighting Off Lighting Off
ZWrite Off ZWrite Off
ZTest [unity_GUIZTestMode] ZTest [unity_GUIZTestMode]
Fog { Mode Off } Fog
{
Mode Off
}
Blend One One Blend One One
ColorMask [_ColorMask] ColorMask [_ColorMask]
@@ -61,18 +64,18 @@
struct appdata_t struct appdata_t
{ {
float4 vertex : POSITION; float4 vertex : POSITION;
float4 color : COLOR; float4 color : COLOR;
float2 texcoord : TEXCOORD0; float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_INPUT_INSTANCE_ID
}; };
struct v2f struct v2f
{ {
float4 vertex : SV_POSITION; float4 vertex : SV_POSITION;
fixed4 color : COLOR; fixed4 color : COLOR;
float2 texcoord : TEXCOORD0; float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1; float4 worldPosition : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO UNITY_VERTEX_OUTPUT_STEREO
}; };

View File

@@ -2,7 +2,7 @@
"name": "com.coffee.ui-particle", "name": "com.coffee.ui-particle",
"displayName": "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.", "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.2", "version": "4.10.2",
"unity": "2018.2", "unity": "2018.2",
"license": "MIT", "license": "MIT",
"repository": { "repository": {