Compare commits

...

64 Commits

Author SHA1 Message Date
semantic-release-bot
51a4708b7b chore(release): 4.13.0 [skip ci]
# [4.13.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.2...v4.13.0) (2026-06-25)

### Bug Fixes

* `UI/Additive` shader does not support RectMask2D softness. ([83145d3](83145d3a6e))
* rename `UIParticleProjectSettings.enableLinearToGamma` to `autoColorCorrection` ([10f82a8](10f82a8579))
* Support for skipping "reload domain" ([b7bb112](b7bb1124f6)), closes [#406](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/406)
* there is a compilation error in Unity 2019.2 or earlier ([c327632](c327632e1e)), closes [#407](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/407)

### Features

* add fields located outside the `Properties` block in the shader as `Animatable Properties` ([ea2fcfd](ea2fcfd809)), closes [#399](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/399)
* preview the ParticleSystem playback when selecting a UIParticle in the Editor ([108bcfb](108bcfbd24))
* project-wide default view size for baking ([8e0ff10](8e0ff10c73)), closes [#360](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/360)
2026-06-25 11:18:58 +00:00
mob-sakai
ba79bcca56 docs: update doc 2026-06-25 20:07:03 +09:00
mob-sakai
b9b664a11c feat: add fields located outside the Properties block in the shader as Animatable Properties
close #399
2026-06-25 19:40:46 +09:00
mob-sakai
bc5a1f4435 feat: project-wide default view size for baking
close #360
2026-06-25 19:40:46 +09:00
mob-sakai
733901436d fix: Support for skipping "reload domain"
close #406
2026-06-25 13:07:47 +09:00
mob-sakai
f0b036de65 docs: update readme 2026-06-25 13:07:47 +09:00
mob-sakai
d93cacc70d fix: rename UIParticleProjectSettings.enableLinearToGamma to autoColorCorrection 2026-06-25 13:07:47 +09:00
mob-sakai
5aff2b480a fix: UI/Additive shader does not support RectMask2D softness. 2026-06-25 13:07:46 +09:00
mob-sakai
de80249552 fix: there is a compilation error in Unity 2019.2 or earlier
close #407
2026-06-25 13:07:46 +09:00
mob-sakai
c0a8e322b1 chore: update internal 2026-06-25 13:07:46 +09:00
mob-sakai
1ac1b347af feat: preview the ParticleSystem playback when selecting a UIParticle in the Editor 2026-06-25 13:07:46 +09:00
semantic-release-bot
6b956b92e0 chore(release): 4.12.2 [skip ci]
## [4.12.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.1...v4.12.2) (2026-06-08)

### Bug Fixes

* add `meshCleared` flag to optimize mesh clearing ([859fa20](859fa20d29))
* fix Unity6.5 compile errors and warnings ([a5ee687](a5ee687821)), closes [#400](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/400)
* potential access to UIParticleRenderer that has already been destroyed ([b740dd6](b740dd662d)), closes [#403](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/403)
* updated support for some changed menu paths ([f8ac986](f8ac9869f1)), closes [#397](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/397)
2026-06-08 13:33:41 +00:00
mob-sakai
d03bf19b08 update internal code 2026-06-08 15:55:42 +09:00
mob-sakai
00be26d4ce fix: potential access to UIParticleRenderer that has already been destroyed
close #403
2026-06-08 13:28:24 +09:00
tako
34588b6e5c fix: updated support for some changed menu paths
close #397
2026-06-08 13:28:24 +09:00
tako
c2fb60a988 fix: fix Unity6.5 compile errors and warnings
close #400
2026-06-08 13:28:24 +09:00
Minhyuk Kim
babd2a7275 fix: add meshCleared flag to optimize mesh clearing 2026-06-08 11:33:57 +09:00
semantic-release-bot
92fb173507 chore(release): 4.12.1 [skip ci]
## [4.12.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.0...v4.12.1) (2026-03-24)

### Bug Fixes

* ignore "EditorOnly" tagged gameObjects on refresh ([031d46a](031d46a321))
2026-03-24 08:53:33 +00:00
mob-sakai
298dc9a093 fix: ignore "EditorOnly" tagged gameObjects on refresh 2026-03-24 17:53:01 +09:00
semantic-release-bot
143face565 chore(release): 4.12.0 [skip ci]
# [4.12.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.4...v4.12.0) (2026-03-24)

### Features

* explicit null checks ([5384f61](5384f61c56))
2026-03-24 08:34:58 +00:00
mob-sakai
319ab5fe06 feat: explicit null checks 2026-03-24 17:32:31 +09:00
semantic-release-bot
04c1ca72cd chore(release): 4.11.4 [skip ci]
## [4.11.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.3...v4.11.4) (2025-12-24)

### Bug Fixes

* add early return for case where subEmitter module is disabled ([d1386a1](d1386a1221))
* avoid endless loop ([eb2e862](eb2e862e80)), closes [#392](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/392)
2025-12-24 11:17:10 +00:00
mob-sakai
f2df47aeec fix: avoid endless loop
close #392
2025-12-24 20:10:01 +09:00
mob-sakai
1dfd756721 fix: add early return for case where subEmitter module is disabled 2025-12-05 09:58:23 +09:00
semantic-release-bot
c54f63cb18 chore(release): 4.11.3 [skip ci]
## [4.11.3](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.2...v4.11.3) (2025-10-14)

### Bug Fixes

* fix icon ([a9461ec](a9461ecb4d))
* fix URL link in README ([1c8c65d](1c8c65d25e)), closes [#376](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/376)
* fix: second and subsequent bursts not displayed when world simulation and non-looping ([df2f3ca](df2f3caafb)), closes [#326](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/326)
* UIParticle in canvas with 0f-0.01f alpha value does not start to play until alpha value is greater than 0.01f, causes play calls to be delayed unintentionally if canvas alpha value is set to mentioned value range ([38aec2e](38aec2ea1a))
2025-10-14 11:53:52 +00:00
mob-sakai
7fb6cda06d fix: fix: second and subsequent bursts not displayed when world simulation and non-looping
close #326
2025-10-14 19:43:29 +09:00
mob-sakai
9441f4100c fix: fix URL link in README
close #376
2025-08-08 17:57:40 +09:00
mob-sakai
ba95fe74ad fix: fix icon 2025-06-14 09:41:44 +09:00
Serkan Şenkal
4532e358db fix: UIParticle in canvas with 0f-0.01f alpha value does not start to play until alpha value is greater than 0.01f, causes play calls to be delayed unintentionally if canvas alpha value is set to mentioned value range 2025-04-09 02:59:26 +09:00
semantic-release-bot
4b98abd746 chore(release): 4.11.2 [skip ci]
## [4.11.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.1...v4.11.2) (2025-03-15)

### Bug Fixes

* IL2CPP build fails on older versions of Unity ([0da6525](0da652520c))
* NRE on enable ([0cff50e](0cff50ef69)), closes [#359](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/359)
2025-03-15 07:59:37 +00:00
mob-sakai
63ec8f61e3 fix: NRE on enable
close #359
2025-03-14 19:44:02 +09:00
mob-sakai
29eebf79fa fix: IL2CPP build fails on older versions of Unity 2025-03-14 19:29:41 +09:00
mob-sakai
4b6da7c218 chore: fix settings icon 2025-03-14 19:24:22 +09:00
semantic-release-bot
7ea0a436d1 chore(release): 4.11.1 [skip ci]
## [4.11.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.0...v4.11.1) (2025-02-21)

### Bug Fixes

* component icons will no longer be displayed in the scene view ([6dfbdae](6dfbdae38d))
2025-02-21 09:35:05 +00:00
mob-sakai
803af8113d fix: component icons will no longer be displayed in the scene view 2025-02-21 18:22:24 +09:00
mob-sakai
8b5f7ff57e doc: update readme 2025-02-21 17:57:12 +09:00
semantic-release-bot
3d0284c630 chore(release): 4.11.0 [skip ci]
# [4.11.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.7...v4.11.0) (2025-02-21)

### Features

* add 'TimeScaleMultiplier' option ([925af0b](925af0b604))
2025-02-21 07:17:09 +00:00
mob-sakai
9832485c04 feat: add 'TimeScaleMultiplier' option 2025-02-21 16:10:06 +09:00
semantic-release-bot
201bd9180e chore(release): 4.10.7 [skip ci]
## [4.10.7](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.6...v4.10.7) (2025-01-14)

### Bug Fixes

* editor crashed on exit play mode (editor, windows) ([47ee45c](47ee45cbbe)), closes [#351](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/351)
2025-01-14 11:49:21 +00:00
mob-sakai
12d604fedd fix: editor crashed on exit play mode (editor, windows)
close #351
2025-01-07 10:25:23 +09:00
semantic-release-bot
4f42996514 chore(release): 4.10.6 [skip ci]
## [4.10.6](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.5...v4.10.6) (2025-01-03)

### Bug Fixes

* sub-emitter particles may not render correctly in certain scenarios ([8276684](8276684c3b)), closes [#348](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/348)
* sub-emitter's `inherit velocity` module doubles at runtime ([67de3d1](67de3d1bd3)), closes [#349](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/349)
2025-01-03 14:24:40 +00:00
mob-sakai
a0a2f4aece chore: update coffee.internal 2025-01-03 23:13:17 +09:00
mob-sakai
23cd448766 fix: sub-emitter particles may not render correctly in certain scenarios
close #348
2025-01-03 23:13:17 +09:00
mob-sakai
1c33dac125 fix: sub-emitter's inherit velocity module doubles at runtime
close #349
2025-01-03 23:13:17 +09:00
semantic-release-bot
f4b28b68b1 chore(release): 4.10.5 [skip ci]
## [4.10.5](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.4...v4.10.5) (2024-12-23)

### Bug Fixes

* '3D' scale toggle in the inspector does not keep on reload ([934f4b8](934f4b8f1c)), closes [#346](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/346)
2024-12-23 03:54:11 +00:00
mob-sakai
ff179f0271 refactor: update coffee.internal 2024-12-23 12:53:15 +09:00
mob-sakai
abdf260352 fix: '3D' scale toggle in the inspector does not keep on reload
close #346
2024-12-22 13:10:21 +09:00
semantic-release-bot
847af6397e chore(release): 4.10.4 [skip ci]
## [4.10.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.3...v4.10.4) (2024-12-19)

### Bug Fixes

* rendering issues when playing with opening a prefab stage ([95235a9](95235a929b)), closes [#345](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/345)
2024-12-19 08:50:19 +00:00
mob-sakai
ac3e147bd9 fix: rendering issues when playing with opening a prefab stage
close #345
2024-12-18 19:46:32 +09:00
semantic-release-bot
598c85e0f4 chore(release): 4.10.3 [skip ci]
## [4.10.3](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.2...v4.10.3) (2024-11-20)

### Bug Fixes

* if not configured as a preloaded asset, the project settings asset will be regenerated ([abe0948](abe09485f6)), closes [#342](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/342)
2024-11-20 16:56:36 +00:00
mob-sakai
5f479902aa fix: if not configured as a preloaded asset, the project settings asset will be regenerated
close #342
2024-11-21 01:39:16 +09:00
mob-sakai
2a66393cb0 refactor: update coffee.internal 2024-11-21 01:36:51 +09:00
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
73 changed files with 3789 additions and 497 deletions

1
.coffee.internal.sed Normal file
View File

@@ -0,0 +1 @@
s/Coffee.Internal/Coffee.UIParticleInternal/g

View File

@@ -1,3 +1,146 @@
# [4.13.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.2...v4.13.0) (2026-06-25)
### Bug Fixes
* `UI/Additive` shader does not support RectMask2D softness. ([83145d3](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/83145d3a6ede3831d1f7c49afde6ef7d8a498d21))
* rename `UIParticleProjectSettings.enableLinearToGamma` to `autoColorCorrection` ([10f82a8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/10f82a8579a2d055f897e7a8d6f3b5ba1fc43ae8))
* Support for skipping "reload domain" ([b7bb112](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b7bb1124f6921ebca1b48991462637767b95e504)), closes [#406](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/406)
* there is a compilation error in Unity 2019.2 or earlier ([c327632](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/c327632e1eb203351ba1a1087a19eb7838c22530)), closes [#407](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/407)
### Features
* add fields located outside the `Properties` block in the shader as `Animatable Properties` ([ea2fcfd](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/ea2fcfd8099a24b92624d34600beb519fb9d2b38)), closes [#399](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/399)
* preview the ParticleSystem playback when selecting a UIParticle in the Editor ([108bcfb](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/108bcfbd246960dc730546dc334424cf2e17bfd4))
* project-wide default view size for baking ([8e0ff10](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/8e0ff10c7344d9f53d2f4f096ddeb7392b67f930)), closes [#360](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/360)
## [4.12.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.1...v4.12.2) (2026-06-08)
### Bug Fixes
* add `meshCleared` flag to optimize mesh clearing ([859fa20](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/859fa20d297c3f44e3361f20dbb7ce966407e03e))
* fix Unity6.5 compile errors and warnings ([a5ee687](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a5ee6878212be2fc4d7b48879426f239e8753009)), closes [#400](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/400)
* potential access to UIParticleRenderer that has already been destroyed ([b740dd6](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b740dd662d423c6bef849662ce1b0bfbb4940ed4)), closes [#403](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/403)
* updated support for some changed menu paths ([f8ac986](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f8ac9869f141238169730e74f5d65c4fc6081f51)), closes [#397](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/397)
## [4.12.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.0...v4.12.1) (2026-03-24)
### Bug Fixes
* ignore "EditorOnly" tagged gameObjects on refresh ([031d46a](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/031d46a3216c942d2d1a6ccfadf5f0b9e3ce3006))
# [4.12.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.4...v4.12.0) (2026-03-24)
### Features
* explicit null checks ([5384f61](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/5384f61c569e9f78ff9d5b45acfc6f5c2f021a87))
## [4.11.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.3...v4.11.4) (2025-12-24)
### Bug Fixes
* add early return for case where subEmitter module is disabled ([d1386a1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/d1386a12216743a6e09f1b9b87bea1dfcf7702e4))
* avoid endless loop ([eb2e862](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/eb2e862e80e549c8cf16ddfed776c101c2413bac)), closes [#392](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/392)
## [4.11.3](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.2...v4.11.3) (2025-10-14)
### Bug Fixes
* fix icon ([a9461ec](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a9461ecb4d40d7fe878e12465d6e38faae7ae65b))
* fix URL link in README ([1c8c65d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/1c8c65d25e7f6fe7b1d20da4461333df8fc7578e)), closes [#376](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/376)
* fix: second and subsequent bursts not displayed when world simulation and non-looping ([df2f3ca](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/df2f3caafbe279f1457d74f8183cb561ac14aa17)), closes [#326](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/326)
* UIParticle in canvas with 0f-0.01f alpha value does not start to play until alpha value is greater than 0.01f, causes play calls to be delayed unintentionally if canvas alpha value is set to mentioned value range ([38aec2e](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/38aec2ea1afd77677d629c86665a3342d92e49d9))
## [4.11.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.1...v4.11.2) (2025-03-15)
### Bug Fixes
* IL2CPP build fails on older versions of Unity ([0da6525](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/0da652520cd165b43de7404c0b0ab1fbcf9349d1))
* NRE on enable ([0cff50e](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/0cff50ef696aa53fb7c46a9a737b7cf3a05b7b9b)), closes [#359](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/359)
## [4.11.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.0...v4.11.1) (2025-02-21)
### Bug Fixes
* component icons will no longer be displayed in the scene view ([6dfbdae](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/6dfbdae38d3822ab9c2c6f0e4ca1ca32ee98a239))
# [4.11.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.7...v4.11.0) (2025-02-21)
### Features
* add 'TimeScaleMultiplier' option ([925af0b](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/925af0b6046f65f23a778f67cefa8ff9cbedb513))
## [4.10.7](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.6...v4.10.7) (2025-01-14)
### Bug Fixes
* editor crashed on exit play mode (editor, windows) ([47ee45c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/47ee45cbbe651a8f87ca2b8a3948f8b88db8211e)), closes [#351](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/351)
## [4.10.6](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.5...v4.10.6) (2025-01-03)
### Bug Fixes
* sub-emitter particles may not render correctly in certain scenarios ([8276684](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/8276684c3b1646f0490ed64557547ba15281664a)), closes [#348](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/348)
* sub-emitter's `inherit velocity` module doubles at runtime ([67de3d1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/67de3d1bd3e16dc9b564625cb990c53d75769506)), closes [#349](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/349)
## [4.10.5](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.4...v4.10.5) (2024-12-23)
### Bug Fixes
* '3D' scale toggle in the inspector does not keep on reload ([934f4b8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/934f4b8f1c61f8ff20228d0ebcea9f636a3758ed)), closes [#346](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/346)
## [4.10.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.3...v4.10.4) (2024-12-19)
### Bug Fixes
* rendering issues when playing with opening a prefab stage ([95235a9](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/95235a929b82cf681365ed6eba837d857f83e3d2)), closes [#345](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/345)
## [4.10.3](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.10.2...v4.10.3) (2024-11-20)
### Bug Fixes
* if not configured as a preloaded asset, the project settings asset will be regenerated ([abe0948](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/abe09485f65dd4efd18e74675e752e0213bdf3be)), closes [#342](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/342)
## [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](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/afe00a1dde80eb1c0a7bb668b75f4c3733d3fa43)), closes [#335](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/335)
## [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](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
* add project settings ([1ce4e31](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/1ce4e31a9681bf1a201d2723c8d97e07ecc16592))
## [4.9.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.9.0...v4.9.1) (2024-08-07) ## [4.9.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.9.0...v4.9.1) (2024-08-07)

View File

@@ -3,15 +3,19 @@ using System.Linq;
using System.Text; using System.Text;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using ShaderPropertyType = Coffee.UIExtensions.AnimatableProperty.ShaderPropertyType;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
internal static class AnimatablePropertyEditor internal static class AnimatablePropertyEditor
{ {
private static readonly GUIContent s_ContentNothing = new GUIContent("Nothing"); private static readonly GUIContent s_ContentNothing = new GUIContent("Nothing");
private static readonly GUIContent s_ContentCustom = new GUIContent("Add Custom...");
private static readonly List<string> s_ActiveNames = new List<string>(); private static readonly List<string> s_ActiveNames = new List<string>();
private static readonly StringBuilder s_Sb = new StringBuilder(); private static readonly StringBuilder s_Sb = new StringBuilder();
private static readonly HashSet<string> s_Names = new HashSet<string>(); private static readonly HashSet<string> s_Names = new HashSet<string>();
private static ShaderProperty s_CustomProperty = new ShaderProperty("", ShaderPropertyType.None);
private static bool s_ShowCustomProperty = false;
private static string CollectActiveNames(SerializedProperty sp, List<string> result) private static string CollectActiveNames(SerializedProperty sp, List<string> result)
{ {
@@ -51,8 +55,19 @@ namespace Coffee.UIExtensions
? "-" ? "-"
: CollectActiveNames(sp, s_ActiveNames); : CollectActiveNames(sp, s_ActiveNames);
if (!GUI.Button(rect, text, EditorStyles.popup)) return; if (GUI.Button(rect, text, EditorStyles.popup))
{
ShowMenu(sp, mats);
}
if (s_ShowCustomProperty)
{
DrawCustomProperty(sp, ref s_CustomProperty);
}
}
private static void ShowMenu(SerializedProperty sp, List<Material> mats)
{
var gm = new GenericMenu(); var gm = new GenericMenu();
gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x => gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x =>
{ {
@@ -61,13 +76,20 @@ namespace Coffee.UIExtensions
current.serializedObject.ApplyModifiedProperties(); current.serializedObject.ApplyModifiedProperties();
}, sp); }, sp);
gm.AddItem(s_ContentCustom, s_ShowCustomProperty, () =>
{
s_ShowCustomProperty = !s_ShowCustomProperty;
s_CustomProperty.Reset();
});
gm.AddSeparator("");
if (!sp.hasMultipleDifferentValues) if (!sp.hasMultipleDifferentValues)
{ {
for (var i = 0; i < sp.arraySize; i++) for (var i = 0; i < sp.arraySize; i++)
{ {
var p = sp.GetArrayElementAtIndex(i); var p = sp.GetArrayElementAtIndex(i);
var name = p.FindPropertyRelative("m_Name").stringValue; var name = p.FindPropertyRelative("m_Name").stringValue;
var type = (AnimatableProperty.ShaderPropertyType)p.FindPropertyRelative("m_Type").intValue; var type = (ShaderPropertyType)p.FindPropertyRelative("m_Type").intValue;
AddMenu(gm, sp, new ShaderProperty(name, type), false); AddMenu(gm, sp, new ShaderProperty(name, type), false);
} }
} }
@@ -76,17 +98,26 @@ namespace Coffee.UIExtensions
for (var j = 0; j < mats.Count; j++) for (var j = 0; j < mats.Count; j++)
{ {
var mat = mats[j]; var mat = mats[j];
if (!mat || !mat.shader) continue; if (mat == null || mat.shader == null) continue;
#if UNITY_6000_5_OR_NEWER
for (var i = 0; i < mat.shader.GetPropertyCount(); i++)
#else
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++) for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
#endif
{ {
#if UNITY_6000_5_OR_NEWER
var name = mat.shader.GetPropertyName(i);
var type = (ShaderPropertyType)mat.shader.GetPropertyType(i);
#else
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 = (ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i);
#endif
if (!s_Names.Add(name)) continue; if (!s_Names.Add(name)) continue;
AddMenu(gm, sp, new ShaderProperty(name, type), true); AddMenu(gm, sp, new ShaderProperty(name, type), true);
if (type != AnimatableProperty.ShaderPropertyType.Texture) continue; if (type != ShaderPropertyType.Texture) continue;
AddMenu(gm, sp, new ShaderProperty($"{name}_ST"), true); AddMenu(gm, sp, new ShaderProperty($"{name}_ST"), true);
AddMenu(gm, sp, new ShaderProperty($"{name}_HDR"), true); AddMenu(gm, sp, new ShaderProperty($"{name}_HDR"), true);
@@ -102,41 +133,82 @@ namespace Coffee.UIExtensions
if (add && s_ActiveNames.Contains(prop.name)) return; if (add && s_ActiveNames.Contains(prop.name)) return;
var label = new GUIContent($"{prop.name} ({prop.type})"); var label = new GUIContent($"{prop.name} ({prop.type})");
menu.AddItem(label, s_ActiveNames.Contains(prop.name), () => menu.AddItem(label, s_ActiveNames.Contains(prop.name), () => AddProp(sp, prop));
{ }
var index = s_ActiveNames.IndexOf(prop.name);
if (0 <= index)
{
sp.DeleteArrayElementAtIndex(index);
}
else
{
sp.InsertArrayElementAtIndex(sp.arraySize);
var p = sp.GetArrayElementAtIndex(sp.arraySize - 1);
p.FindPropertyRelative("m_Name").stringValue = prop.name;
p.FindPropertyRelative("m_Type").intValue = (int)prop.type;
}
sp.serializedObject.ApplyModifiedProperties(); private static void DrawCustomProperty(SerializedProperty sp, ref ShaderProperty prop)
}); {
var r = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 8);
r.xMin += 60;
GUI.Label(r, (Texture)null, EditorStyles.helpBox);
r = new Rect(r.x + 4, r.y + 4, r.width - 8 - 100 - 16, r.height - 8);
prop.name = EditorGUI.TextField(r, prop.name);
r.x += r.width + 2;
r.width = 100 - 2;
prop.type = (ShaderPropertyType)EditorGUI.EnumPopup(r, prop.type);
r.x += r.width;
r.width = 16;
EditorGUI.BeginDisabledGroup(!prop.IsValid(s_ActiveNames));
if (GUI.Button(r, EditorGUIUtility.IconContent("Toolbar Plus"), EditorStyles.label))
{
GUI.FocusControl("");
AddProp(sp, prop);
prop.Reset();
}
EditorGUI.EndDisabledGroup();
}
private static void AddProp(SerializedProperty sp, ShaderProperty prop)
{
var index = s_ActiveNames.IndexOf(prop.name);
if (0 <= index)
{
sp.DeleteArrayElementAtIndex(index);
}
else
{
sp.InsertArrayElementAtIndex(sp.arraySize);
var p = sp.GetArrayElementAtIndex(sp.arraySize - 1);
p.FindPropertyRelative("m_Name").stringValue = prop.name;
p.FindPropertyRelative("m_Type").intValue = (int)prop.type;
}
sp.serializedObject.ApplyModifiedProperties();
} }
private struct ShaderProperty private struct ShaderProperty
{ {
public readonly string name; public string name;
public readonly AnimatableProperty.ShaderPropertyType type; public ShaderPropertyType type;
public ShaderProperty(string name) public ShaderProperty(string name)
{ {
this.name = name; this.name = name;
type = AnimatableProperty.ShaderPropertyType.Vector; type = ShaderPropertyType.Vector;
} }
public ShaderProperty(string name, AnimatableProperty.ShaderPropertyType type) public ShaderProperty(string name, ShaderPropertyType type)
{ {
this.name = name; this.name = name;
this.type = type; this.type = type;
} }
public void Reset()
{
name = "";
type = ShaderPropertyType.None;
}
public bool IsValid(List<string> activeNames)
{
if (string.IsNullOrEmpty(name)) return false;
if (type == ShaderPropertyType.None) return false;
if (activeNames.Contains(name)) return false;
return true;
}
} }
} }
} }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -7,16 +8,16 @@ using UnityEditorInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.Profiling; using UnityEngine.Profiling;
using UnityEngine.UI; using UnityEngine.UI;
using Coffee.UIParticleExtensions; using Coffee.UIParticleInternal;
using Object = UnityEngine.Object;
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.Overlays; using UnityEditor.Overlays;
#else #else
using System;
using System.Reflection; using System.Reflection;
using Object = UnityEngine.Object;
#endif #endif
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
#elif UNITY_2018_3_OR_NEWER #elif UNITY_2018_3_OR_NEWER
using UnityEditor.Experimental.SceneManagement; using UnityEditor.Experimental.SceneManagement;
#endif #endif
@@ -27,6 +28,11 @@ namespace Coffee.UIExtensions
[CanEditMultipleObjects] [CanEditMultipleObjects]
internal class UIParticleEditor : GraphicEditor internal class UIParticleEditor : GraphicEditor
{ {
internal class State : ScriptableSingleton<State>
{
public bool is3DScaleMode;
}
//################################ //################################
// Constant or Static Members. // Constant or Static Members.
//################################ //################################
@@ -43,9 +49,9 @@ namespace Coffee.UIExtensions
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 GUIContent s_ContentPrimary = new GUIContent("Primary");
private static readonly GUIContent s_ViewSize = new GUIContent("View Size");
private static readonly Regex s_RegexBuiltInGuid = new Regex(@"^0{16}.0{15}$", RegexOptions.Compiled); 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 readonly List<Material> s_TempMaterials = new List<Material>();
private static bool s_XYZMode;
private SerializedProperty _maskable; private SerializedProperty _maskable;
private SerializedProperty _scale3D; private SerializedProperty _scale3D;
@@ -57,8 +63,11 @@ namespace Coffee.UIExtensions
private SerializedProperty _autoScalingMode; private SerializedProperty _autoScalingMode;
private SerializedProperty _useCustomView; private SerializedProperty _useCustomView;
private SerializedProperty _customViewSize; private SerializedProperty _customViewSize;
private SerializedProperty _timeScaleMultiplier;
private ReorderableList _ro; private ReorderableList _ro;
private bool _showMax; private bool _showMax;
private bool _is3DScaleMode;
private GameObject[] _gameObjects;
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>(); private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
#if UNITY_2018 || UNITY_2019 #if UNITY_2018 || UNITY_2019
@@ -94,6 +103,7 @@ namespace Coffee.UIExtensions
_autoScalingMode = serializedObject.FindProperty("m_AutoScalingMode"); _autoScalingMode = serializedObject.FindProperty("m_AutoScalingMode");
_useCustomView = serializedObject.FindProperty("m_UseCustomView"); _useCustomView = serializedObject.FindProperty("m_UseCustomView");
_customViewSize = serializedObject.FindProperty("m_CustomViewSize"); _customViewSize = serializedObject.FindProperty("m_CustomViewSize");
_timeScaleMultiplier = serializedObject.FindProperty("m_TimeScaleMultiplier");
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)
@@ -102,7 +112,7 @@ namespace Coffee.UIExtensions
{ {
var ps = sp.GetArrayElementAtIndex(index).objectReferenceValue as ParticleSystem; var ps = sp.GetArrayElementAtIndex(index).objectReferenceValue as ParticleSystem;
var materialCount = 0; var materialCount = 0;
if (ps && ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) if (ps != null && ps.TryGetComponent<ParticleSystemRenderer>(out var psr))
{ {
materialCount = psr.sharedMaterials.Length; materialCount = psr.sharedMaterials.Length;
} }
@@ -116,7 +126,7 @@ namespace Coffee.UIExtensions
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; var ps = p.objectReferenceValue as ParticleSystem;
if (!ps || !ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) return; if (ps == null || !ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) return;
rect.x += 15; rect.x += 15;
rect.width -= 15; rect.width -= 15;
@@ -162,6 +172,26 @@ namespace Coffee.UIExtensions
uip.RefreshParticles(uip.particles); uip.RefreshParticles(uip.particles);
} }
} }
// Initialize 3D scale mode.
_is3DScaleMode = State.instance.is3DScaleMode;
if (!_is3DScaleMode)
{
var x = _scale3D.FindPropertyRelative("x");
var y = _scale3D.FindPropertyRelative("y");
var z = _scale3D.FindPropertyRelative("z");
_is3DScaleMode = !Mathf.Approximately(x.floatValue, y.floatValue) ||
!Mathf.Approximately(y.floatValue, z.floatValue) ||
y.hasMultipleDifferentValues ||
z.hasMultipleDifferentValues;
}
// Add temporary ParticleSystem for preview if enabled.
if (UIParticleProjectSettings.previewOnSelect)
{
_gameObjects = targets.OfType<UIParticle>().Select(x => x.gameObject).ToArray();
ParticleSystemPreviewSystem.Register(_gameObjects);
}
} }
/// <summary> /// <summary>
@@ -170,17 +200,24 @@ namespace Coffee.UIExtensions
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
var current = target as UIParticle; var current = target as UIParticle;
if (!current) return; if (current == null) return;
Profiler.BeginSample("(UIP:E) OnInspectorGUI"); Profiler.BeginSample("(UIP:E) OnInspectorGUI");
serializedObject.Update(); serializedObject.Update();
// Maskable // Maskable
EditorGUILayout.PropertyField(_maskable); if (_maskable != null)
{
EditorGUILayout.PropertyField(_maskable);
}
// Scale // Scale
EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4); EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4);
s_XYZMode = DrawFloatOrVector3Field(_scale3D, s_XYZMode); if (DrawFloatOrVector3Field(_scale3D, _is3DScaleMode) != _is3DScaleMode)
{
State.instance.is3DScaleMode = _is3DScaleMode = !_is3DScaleMode;
}
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
// AnimatableProperties // AnimatableProperties
@@ -210,16 +247,27 @@ namespace Coffee.UIExtensions
// Custom View Size // Custom View Size
EditorGUILayout.PropertyField(_useCustomView); EditorGUILayout.PropertyField(_useCustomView);
EditorGUI.BeginChangeCheck();
EditorGUI.BeginDisabledGroup(!_useCustomView.boolValue);
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_customViewSize); if (_useCustomView.boolValue)
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
if (EditorGUI.EndChangeCheck())
{ {
_customViewSize.floatValue = Mathf.Max(0.1f, _customViewSize.floatValue); EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_customViewSize);
if (EditorGUI.EndChangeCheck())
{
_customViewSize.floatValue = Mathf.Max(0.1f, _customViewSize.floatValue);
}
} }
else
{
EditorGUI.BeginDisabledGroup(!_useCustomView.boolValue);
EditorGUILayout.FloatField(s_ViewSize, UIParticleProjectSettings.defaultViewSizeForBaking);
EditorGUI.EndDisabledGroup();
}
EditorGUI.indentLevel--;
// Time Scale Multiplier
EditorGUILayout.PropertyField(_timeScaleMultiplier);
// Target ParticleSystems. // Target ParticleSystems.
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
@@ -239,7 +287,7 @@ namespace Coffee.UIExtensions
Profiler.BeginSample("(UIP:E) Non-UI built-in shader is not supported."); Profiler.BeginSample("(UIP:E) Non-UI built-in shader is not supported.");
foreach (var mat in s_TempMaterials) foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (mat == null || mat.shader == null) continue;
var shader = mat.shader; var shader = mat.shader;
if (IsBuiltInObject(shader) && !shader.name.StartsWith("UI/")) if (IsBuiltInObject(shader) && !shader.name.StartsWith("UI/"))
{ {
@@ -258,7 +306,7 @@ namespace Coffee.UIExtensions
{ {
foreach (var mat in s_TempMaterials) foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (mat == null || mat.shader == null) continue;
var shader = mat.shader; var shader = mat.shader;
if (!s_Shaders.Add(shader)) continue; if (!s_Shaders.Add(shader)) continue;
@@ -316,6 +364,10 @@ namespace Coffee.UIExtensions
} }
} }
#endif #endif
// Remove the temporary ParticleSystem.
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
Profiler.EndSample(); Profiler.EndSample();
} }
@@ -454,7 +506,7 @@ namespace Coffee.UIExtensions
#endif #endif
} }
private static bool FixButton(bool show, string text) private static bool FixButton(bool show, string text, GUIContent buttonText = null)
{ {
if (!show) return false; if (!show) return false;
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true))) using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true)))
@@ -462,7 +514,7 @@ namespace Coffee.UIExtensions
EditorGUILayout.HelpBox(text, MessageType.Warning, true); EditorGUILayout.HelpBox(text, MessageType.Warning, true);
using (new EditorGUILayout.VerticalScope()) using (new EditorGUILayout.VerticalScope())
{ {
return GUILayout.Button(s_ContentFix, GUILayout.Width(30)); return GUILayout.Button(buttonText ?? s_ContentFix, GUILayout.ExpandWidth(false));
} }
} }
} }

View File

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -6,11 +6,22 @@ namespace Coffee.UIExtensions
{ {
internal class UIParticleMenu internal class UIParticleMenu
{ {
[MenuItem("GameObject/UI/Particle System (Empty)", false, 2018)] #if UNITY_6000_5_OR_NEWER
private const string k_MenuPathToCreateParticleSystem = "GameObject/Visual Effects/Particle System";
#else
private const string k_MenuPathToCreateParticleSystem = "GameObject/Effects/Particle System";
#endif
#if UNITY_6000_3_OR_NEWER
private const string k_MenuPathForUgui = "GameObject/UI (Canvas)";
#else
private const string k_MenuPathForUgui = "GameObject/UI";
#endif
[MenuItem(k_MenuPathForUgui + "/Particle System (Empty)", false, 2018)]
private static void AddParticleEmpty(MenuCommand menuCommand) private static void AddParticleEmpty(MenuCommand menuCommand)
{ {
// Create empty UI element. // Create empty UI element.
EditorApplication.ExecuteMenuItem("GameObject/UI/Image"); EditorApplication.ExecuteMenuItem(k_MenuPathForUgui + "/Image");
var ui = Selection.activeGameObject; var ui = Selection.activeGameObject;
Object.DestroyImmediate(ui.GetComponent<Image>()); Object.DestroyImmediate(ui.GetComponent<Image>());
@@ -21,7 +32,7 @@ namespace Coffee.UIExtensions
uiParticle.rectTransform.sizeDelta = Vector2.zero; uiParticle.rectTransform.sizeDelta = Vector2.zero;
} }
[MenuItem("GameObject/UI/Particle System", false, 2019)] [MenuItem(k_MenuPathForUgui + "/Particle System", false, 2019)]
private static void AddParticle(MenuCommand menuCommand) private static void AddParticle(MenuCommand menuCommand)
{ {
// Create empty UIEffect. // Create empty UIEffect.
@@ -29,7 +40,7 @@ namespace Coffee.UIExtensions
var uiParticle = Selection.activeGameObject.GetComponent<UIParticle>(); var uiParticle = Selection.activeGameObject.GetComponent<UIParticle>();
// Create ParticleSystem. // Create ParticleSystem.
EditorApplication.ExecuteMenuItem("GameObject/Effects/Particle System"); EditorApplication.ExecuteMenuItem(k_MenuPathToCreateParticleSystem);
var ps = Selection.activeGameObject; var ps = Selection.activeGameObject;
ps.transform.SetParent(uiParticle.transform, false); ps.transform.SetParent(uiParticle.transform, false);
ps.transform.localPosition = Vector3.zero; ps.transform.localPosition = Vector3.zero;

View File

@@ -1,4 +1,4 @@
Copyright 2018-2024 mob-sakai Copyright 2018-2026 mob-sakai
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -34,6 +34,7 @@ You can render, mask, and sort your `ParticleSystems` for UI without the need fo
- [Usage with `Mask` or `RectMask2D` Component](#usage-with-mask-or-rectmask2d-component) - [Usage with `Mask` or `RectMask2D` Component](#usage-with-mask-or-rectmask2d-component)
- [Usage with Script](#usage-with-script) - [Usage with Script](#usage-with-script)
- [Component: UIParticleAttractor](#component-uiparticleattractor) - [Component: UIParticleAttractor](#component-uiparticleattractor)
- [Project Settings](#project-settings)
- [🛠 Development Note](#-development-note) - [🛠 Development Note](#-development-note)
- [Compares the Baking mesh approach with the conventional approach](#compares-the-baking-mesh-approach-with-the-conventional-approach) - [Compares the Baking mesh approach with the conventional approach](#compares-the-baking-mesh-approach-with-the-conventional-approach)
- [Performance test results](#performance-test-results) - [Performance test results](#performance-test-results)
@@ -143,7 +144,7 @@ _This package requires **Unity 2018.3 or later**._
#### Install as Embedded Package #### Install as Embedded Package
1. Download a source code zip file from [Releases](https://github.com/mob-sakai/ParticleEffectForUGUI.git/releases) and extract it. 1. Download a source code zip file from [Releases](https://github.com/mob-sakai/ParticleEffectForUGUI/releases) and extract it.
2. Place it in your project's `Packages` directory. 2. Place it in your project's `Packages` directory.
![](https://github.com/mob-sakai/mob-sakai/assets/12690315/0b7484b4-5fca-43b0-a9ef-e5dbd99bcdb4) ![](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. - If you want to fix bugs or add features, install it as an embedded package.
@@ -157,12 +158,18 @@ _This package requires **Unity 2018.3 or later**._
`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/1cf5753b-33fc-4cef-91c3-413c515a954f) ![](https://github.com/user-attachments/assets/bc9eb783-afce-4102-ac61-aee9ea8d6f2f)
- **Maskable**: Does this graphic allow maskable. - **Maskable**: Does this graphic allow maskable.
- **Scale**: Scale the rendering particles. 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 as animatable. use this to mark as animatable.
> [!TIPS]
> (Unity 2021.1 or later) **Custom Animatable Properties**
> From the `Add Custom...` menu in `Animatable Properties`, you can add fields located outside the `Properties` block in the shader as `Animatable Properties`.
> This allows `Matrix`, `Matrix Array`, `Float Array`, and `Vector Array` values modified by the `ParticleSystemRenderer.SetPropertyBlock` method to be automatically reflected in UIParticle.
- **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. - **None:** Disable mesh sharing.
@@ -179,11 +186,17 @@ _This package requires **Unity 2018.3 or later**._
- **UIParticle:** UIParticle.scale will be adjusted. - **UIParticle:** UIParticle.scale will be adjusted.
- **Use Custom View:** Use this if the particles are not displayed correctly due to min/max particle size. - **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. - **Custom view size:** Change the bake view size.
- **Time Scale Multiplier:** Time scale multiplier.
- **Rendering Order**: The ParticleSystem list to be rendered. You can change the order and the materials. - **Rendering Order**: The ParticleSystem list to be rendered. You can change the order and the materials.
**NOTE:** Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order > [!TIPS]
> Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order
and z-position. and z-position.
> [!TIPS]
> When a `GameObject` with this component is selected in the editor, a temporary `ParticleSystem` is added if needed so you can preview the effect in the Scene view.
> The generated `ParticleSystem` is marked with `HideFlags.DontSave`, so it is neither saved nor included in builds.
<br><br> <br><br>
### Basic Usage ### Basic Usage
@@ -193,6 +206,10 @@ and z-position.
2. Adjust the ParticleSystem as you like. 2. Adjust the ParticleSystem as you like.
![particle1](https://user-images.githubusercontent.com/12690315/95007359-ca385200-0649-11eb-8383-627c9750bda8.png) ![particle1](https://user-images.githubusercontent.com/12690315/95007359-ca385200-0649-11eb-8383-627c9750bda8.png)
> [!Tips]
> Adding a `UIParticle` to the parent is the recommended setup rather than attaching it directly to the `ParticleSystem`.
> When using `ParticleSystem.emission.rateOverDistance`, it is recommended to move the transform of `UIParticle` rather than the `ParticleSystem`.
<br> <br>
### Usage with Your Existing ParticleSystem Prefab ### Usage with Your Existing ParticleSystem Prefab
@@ -209,7 +226,7 @@ and z-position.
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.
If you use some custom shaders, see If you use some custom shaders, see
the [How to Make a Custom Shader to Support Mask/RectMask2D Component](#how-to-make-a-custom-shader-to-support-maskrectmask2d-component) the [How to Make a Custom Shader to Support Mask/RectMask2D Component](#how-to-make-a-custom-shader-to-support-mask-and-rectmask2d-component)
section. section.
![](https://user-images.githubusercontent.com/12690315/95017591-3b512700-0695-11eb-864e-04166ea1809a.png) ![](https://user-images.githubusercontent.com/12690315/95017591-3b512700-0695-11eb-864e-04166ea1809a.png)
@@ -254,6 +271,36 @@ uiParticle.Stop();
- **Unscaled Time:** Update with unscaled delta time. - **Unscaled Time:** Update with unscaled delta time.
- **OnAttracted**: An event called when attracting is complete (per particle). - **OnAttracted**: An event called when attracting is complete (per particle).
<br><br>
### Component: ParticleSystemPreviewer
`ParticleSystemPreviewer` is used to preview a ParticleSystem in the editor.
- When a `GameObject` with this component is selected in the editor, a temporary `ParticleSystem` is added if needed so you can preview the effect in the Scene view.
- The generated `ParticleSystem` is marked with `HideFlags.DontSave`, so it is neither saved nor included in builds.
<br><br>
### Project Settings
You can adjust the project-wide settings for `UI Particle`. (`Edit > Project Settings > UI > UI Particle`)
![](https://github.com/mob-sakai/mob-sakai/releases/download/docs/1782360274340.png)
#### Settings
- **Enable Linear To Gamma**: Automatically correct the color space of the mesh.
- **Default View Size For Baking**: Default view size for baking particle systems.
#### Editor
- **Hide Generated Component**: Automatically hide the generated `UIParticleRenderer` component and `UIParticle BakingCamera`.
- **Preview On Select**: When selecting UIParticle, a temporary ParticleSystem is generated for preview.
<br><br> <br><br>
## 🛠 Development Note ## 🛠 Development Note
@@ -317,33 +364,39 @@ uiParticle.Stop();
### 🔍 FAQ: Why Are My UIParticles Not Displayed Correctly? ### 🔍 FAQ: Why Are My UIParticles Not Displayed Correctly?
See [this issue](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/270).
If `ParticleSystem` alone displays particles correctly but `UIParticle` does not, please check the following points: If `ParticleSystem` alone displays particles correctly but `UIParticle` does not, please check the following points:
- [Shader Limitation](#shader-limitation) - [Shader Limitation](https://github.com/mob-sakai/ParticleEffectForUGUI#shader-limitation)
- `UIParticle` does not support all built-in shaders except for `UI/Default`.
- Most cases can be solved by using `UI/Additive` or `UI/Default`. - Most cases can be solved by using `UI/Additive` or `UI/Default`.
- Particles are not masked - Particles are not masked
- `UIParticle` is maskable. - `UIParticle` is maskable.
- Use maskable/clipable shader (such as `UI/Additive` or `UI/Default`)
- Set `Mask` or `RectMask2D` component properly. - Set `Mask` or `RectMask2D` component properly.
- [Use maskable/clipable shader](#how-to-make-a-custom-shader-to-support-maskrectmask2d-component) (such
as `UI/Additive` or `UI/Default`)
- Particles are too small - Particles are too small
- If particles are small enough, they will not appear on the screen. - If particles are small enough, they will not appear on the screen.
- Increase the `Scale` value. - Increase the `Scale` value.
- If you don't want to change the apparent size depending on the resolution, try the `Auto Scaling` option. - If you don't want to change the apparent size depending on the resolution, try the `Auto Scaling` option.
- Particles are too many - Particles are too many
- No more than 65535 vertices can be displayed (for mesh combination limitations). - No more than 65535 vertices can be displayed (for mesh combination limitations).
- Please set `Emission` module and `Max Particles` of ParticleSystem properly. - Please set `Emission` module and `Max Particles` of `ParticleSystem` properly.
- Particles are emitted off-screen. - Particles are emitted off-screen.
- When `Position Mode = Relative`, particles are emitted from the scaled position of the ParticleSystem, not from - When `Position Mode = Relative`, particles are emitted from the scaled position of the `ParticleSystem`, not from
the screen point of the ParticleSystem. the screen point of the `ParticleSystem`.
- Place the ParticleSystem in the proper position or try `Position Mode = Absolute`. - Place the `ParticleSystem` in the proper position or try `Position Mode = Absolute`.
- Attaching `UIParticle` to the same object as `ParticleSystem` - Attaching `UIParticle` to the same object as `ParticleSystem`
- `Transform.localScale` will be overridden by the `Auto Scaling` option. - `Transform.localScale` will be overridden by the `Auto Scaling` option.
- It is recommended to place `ParticleSystem` under `UIParticle`. - It is recommended to place `ParticleSystem` under `UIParticle`.
- If `Transform.localScale` contains 0, rendering will be skipped. - If `Transform.localScale` contains 0, rendering will be skipped.
- If you are using `Shape` module, please check whether the scale includes 0.
- Displayed particles are in the correct position but too large/too small - Displayed particles are in the correct position but too large/too small
- Adjust `ParticleSystem.renderer.Min/MaxParticleSize`. - Adjust `ParticleSystem.renderer.Min/MaxParticleSize`.
- If `Trails.RibbonCount` is greater than 1, the following error occurs frequently.
- `Failed setting triangles. Some indices are referencing out of bounds vertices. IndexCount: 660, VertexCount: 222`
- Adding a `UIParticle` to the parent is the recommended setup rather than attaching it directly to the `ParticleSystem`.
- When using `ParticleSystem.emission.rateOverDistance`, it is recommended to move the transform of `UIParticle` rather than the `ParticleSystem`.
- (v4.13.0, v5.0.0-preview.18) Try changing the `Default View Size For Baking` setting under `ProjectSettings > UI > UI Particle` to a value such as 100 or 1000.
<br> <br>

View File

@@ -8,11 +8,18 @@ namespace Coffee.UIExtensions
{ {
public enum ShaderPropertyType public enum ShaderPropertyType
{ {
None = -1,
Color, Color,
Vector, Vector,
Float, Float,
Range, Range,
Texture Texture,
Int,
Matrix = 100,
MatrixArray = 101,
FloatArray = 102,
VectorArray = 103,
} }
[SerializeField] private string m_Name = ""; [SerializeField] private string m_Name = "";
@@ -32,42 +39,41 @@ namespace Coffee.UIExtensions
public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb) public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb)
{ {
#if UNITY_2021_1_OR_NEWER
if (!mpb.HasProperty(id)) return;
#else
if (!material.HasProperty(id)) return; if (!material.HasProperty(id)) return;
#endif
switch (type) switch (type)
{ {
case ShaderPropertyType.Color: case ShaderPropertyType.Color:
var color = mpb.GetColor(id); material.SetColor(id, mpb.GetColor(id));
if (color != default)
{
material.SetColor(id, color);
}
break; break;
case ShaderPropertyType.Vector: case ShaderPropertyType.Vector:
var vector = mpb.GetVector(id); material.SetVector(id, mpb.GetVector(id));
if (vector != default)
{
material.SetVector(id, vector);
}
break; break;
case ShaderPropertyType.Float: case ShaderPropertyType.Float:
case ShaderPropertyType.Range: case ShaderPropertyType.Range:
var value = mpb.GetFloat(id); material.SetFloat(id, mpb.GetFloat(id));
if (!Mathf.Approximately(value, 0))
{
material.SetFloat(id, value);
}
break; break;
case ShaderPropertyType.Texture: case ShaderPropertyType.Texture:
var tex = mpb.GetTexture(id); material.SetTexture(id, mpb.GetTexture(id));
if (tex != default(Texture)) break;
{ case ShaderPropertyType.Int:
material.SetTexture(id, tex); material.SetInt(id, mpb.GetInt(id));
} break;
case ShaderPropertyType.Matrix:
material.SetMatrix(id, mpb.GetMatrix(id));
break;
case ShaderPropertyType.MatrixArray:
material.SetMatrixArray(id, mpb.GetMatrixArray(id));
break;
case ShaderPropertyType.FloatArray:
material.SetFloatArray(id, mpb.GetFloatArray(id));
break;
case ShaderPropertyType.VectorArray:
material.SetVectorArray(id, mpb.GetVectorArray(id));
break; break;
} }
} }

Binary file not shown.

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 4d73b3825bf044d418ae21bb331d3902
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 7a55e246f37df405bac88eac692e3a86 guid: 53aa3f36032944b3fb1455e774c52396
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

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

View File

@@ -0,0 +1,134 @@
#if UNITY_2021_3_0 || UNITY_2021_3_1 || UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5 || UNITY_2021_3_6 || UNITY_2021_3_7 || UNITY_2021_3_8 || UNITY_2021_3_9
#elif UNITY_2021_3_10 || UNITY_2021_3_11 || UNITY_2021_3_12 || UNITY_2021_3_13 || UNITY_2021_3_14 || UNITY_2021_3_15 || UNITY_2021_3_16 || UNITY_2021_3_17 || UNITY_2021_3_18 || UNITY_2021_3_19
#elif UNITY_2021_3_20 || UNITY_2021_3_21 || UNITY_2021_3_22 || UNITY_2021_3_23 || UNITY_2021_3_24 || UNITY_2021_3_25 || UNITY_2021_3_26 || UNITY_2021_3_27 || UNITY_2021_3_28 || UNITY_2021_3_29
#elif UNITY_2021_3_30 || UNITY_2021_3_31 || UNITY_2021_3_32 || UNITY_2021_3_33
#elif UNITY_2022_2_0 || UNITY_2022_2_1 || UNITY_2022_2_2 || UNITY_2022_2_3 || UNITY_2022_2_4 || UNITY_2022_2_5 || UNITY_2022_2_6 || UNITY_2022_2_7 || UNITY_2022_2_8 || UNITY_2022_2_9
#elif UNITY_2022_2_10 || UNITY_2022_2_11 || UNITY_2022_2_12 || UNITY_2022_2_13 || UNITY_2022_2_14
#elif UNITY_2021_3 || UNITY_2022_2 || UNITY_2022_3 || UNITY_2023_2_OR_NEWER
#define CANVAS_SUPPORT_ALWAYS_GAMMA
#endif
using UnityEngine;
using UnityEngine.Profiling;
#if UNITY_MODULE_VR
using UnityEngine.XR;
#endif
namespace Coffee.UIParticleInternal
{
internal static class CanvasExtensions
{
public static bool ShouldGammaToLinearInShader(this Canvas canvas)
{
return QualitySettings.activeColorSpace == ColorSpace.Linear &&
#if CANVAS_SUPPORT_ALWAYS_GAMMA
canvas.vertexColorAlwaysGammaSpace;
#else
false;
#endif
}
public static bool ShouldGammaToLinearInMesh(this Canvas canvas)
{
return QualitySettings.activeColorSpace == ColorSpace.Linear &&
#if CANVAS_SUPPORT_ALWAYS_GAMMA
!canvas.vertexColorAlwaysGammaSpace;
#else
true;
#endif
}
public static bool IsStereoCanvas(this Canvas canvas)
{
#if UNITY_MODULE_VR
if (FrameCache.TryGet<bool>(canvas, nameof(IsStereoCanvas), out var stereo)) return stereo;
stereo =
canvas != null && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.worldCamera != null
&& XRSettings.enabled && !string.IsNullOrEmpty(XRSettings.loadedDeviceName);
FrameCache.Set(canvas, nameof(IsStereoCanvas), stereo);
return stereo;
#else
return false;
#endif
}
/// <summary>
/// Gets the view-projection matrix for a Canvas.
/// </summary>
public static void GetViewProjectionMatrix(this Canvas canvas, out Matrix4x4 vpMatrix)
{
canvas.GetViewProjectionMatrix(Camera.MonoOrStereoscopicEye.Mono, out vpMatrix);
}
/// <summary>
/// Gets the view-projection matrix for a Canvas.
/// </summary>
public static void GetViewProjectionMatrix(this Canvas canvas, Camera.MonoOrStereoscopicEye eye,
out Matrix4x4 vpMatrix)
{
if (FrameCache.TryGet(canvas, nameof(GetViewProjectionMatrix), out vpMatrix)) return;
canvas.GetViewProjectionMatrix(eye, out var viewMatrix, out var projectionMatrix);
vpMatrix = viewMatrix * projectionMatrix;
FrameCache.Set(canvas, nameof(GetViewProjectionMatrix), vpMatrix);
}
/// <summary>
/// Gets the view and projection matrices for a Canvas.
/// </summary>
public static void GetViewProjectionMatrix(this Canvas canvas, out Matrix4x4 vMatrix, out Matrix4x4 pMatrix)
{
canvas.GetViewProjectionMatrix(Camera.MonoOrStereoscopicEye.Mono, out vMatrix, out pMatrix);
}
/// <summary>
/// Gets the view and projection matrices for a Canvas.
/// </summary>
public static void GetViewProjectionMatrix(this Canvas canvas, Camera.MonoOrStereoscopicEye eye,
out Matrix4x4 vMatrix, out Matrix4x4 pMatrix)
{
if (FrameCache.TryGet(canvas, "GetViewMatrix", (int)eye, out vMatrix) &&
FrameCache.TryGet(canvas, "GetProjectionMatrix", (int)eye, out pMatrix))
{
return;
}
// Get view and projection matrices.
Profiler.BeginSample("(COF)[CanvasExt] GetViewProjectionMatrix");
var rootCanvas = canvas.rootCanvas;
var cam = rootCanvas.worldCamera;
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay && cam != null)
{
if (eye == Camera.MonoOrStereoscopicEye.Mono)
{
vMatrix = cam.worldToCameraMatrix;
pMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false);
}
else
{
pMatrix = cam.GetStereoProjectionMatrix((Camera.StereoscopicEye)eye);
vMatrix = cam.GetStereoViewMatrix((Camera.StereoscopicEye)eye);
pMatrix = GL.GetGPUProjectionMatrix(pMatrix, false);
}
}
else
{
var pos = rootCanvas.transform.position;
vMatrix = Matrix4x4.TRS(
new Vector3(-pos.x, -pos.y, -1000),
Quaternion.identity,
new Vector3(1, 1, -1f));
pMatrix = Matrix4x4.TRS(
new Vector3(0, 0, -1),
Quaternion.identity,
new Vector3(1 / pos.x, 1 / pos.y, -2 / 10000f));
}
FrameCache.Set(canvas, "GetViewMatrix", (int)eye, vMatrix);
FrameCache.Set(canvas, "GetProjectionMatrix", (int)eye, pMatrix);
Profiler.EndSample();
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: d188d31b140094ebc84a9caafbc7ac71 guid: 9dd767b8c0f95478386e7d5079cd44df
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,77 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
namespace Coffee.UIParticleInternal
{
internal static class Color32Extensions
{
private static readonly List<Color32> s_Colors = new List<Color32>();
private static byte[] s_LinearToGammaLut;
private static byte[] s_GammaToLinearLut;
public static byte LinearToGamma(this byte self)
{
if (s_LinearToGammaLut == null)
{
s_LinearToGammaLut = new byte[256];
for (var i = 0; i < 256; i++)
{
s_LinearToGammaLut[i] = (byte)(Mathf.LinearToGammaSpace(i / 255f) * 255f);
}
}
return s_LinearToGammaLut[self];
}
public static byte GammaToLinear(this byte self)
{
if (s_GammaToLinearLut == null)
{
s_GammaToLinearLut = new byte[256];
for (var i = 0; i < 256; i++)
{
s_GammaToLinearLut[i] = (byte)(Mathf.GammaToLinearSpace(i / 255f) * 255f);
}
}
return s_GammaToLinearLut[self];
}
public static void LinearToGamma(this Mesh self)
{
Profiler.BeginSample("(COF)[ColorExt] LinearToGamma (Mesh)");
self.GetColors(s_Colors);
var count = s_Colors.Count;
for (var i = 0; i < count; i++)
{
var c = s_Colors[i];
c.r = c.r.LinearToGamma();
c.g = c.g.LinearToGamma();
c.b = c.b.LinearToGamma();
s_Colors[i] = c;
}
self.SetColors(s_Colors);
Profiler.EndSample();
}
public static void GammaToLinear(this Mesh self)
{
Profiler.BeginSample("(COF)[ColorExt] GammaToLinear (Mesh)");
self.GetColors(s_Colors);
var count = s_Colors.Count;
for (var i = 0; i < count; i++)
{
var c = s_Colors[i];
c.r = c.r.GammaToLinear();
c.g = c.g.GammaToLinear();
c.b = c.b.GammaToLinear();
s_Colors[i] = c;
}
self.SetColors(s_Colors);
Profiler.EndSample();
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b0beae5bb1cb142b9ab90dc0d371f026 guid: 0ef431b9df32c410ea5fa46be81def6b
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Profiling;
using Object = UnityEngine.Object;
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Extension methods for Component class.
/// </summary>
internal static class ComponentExtensions
{
#if !UNITY_2019_2_OR_NEWER
public static bool TryGetComponent<T>(this GameObject self, out T component)
where T : Component
{
if (self == null)
{
component = null;
return false;
}
component = self.GetComponent<T>();
return component != null;
}
public static bool TryGetComponent<T>(this Component self, out T component)
where T : Component
{
if (self == null)
{
component = null;
return false;
}
return self.gameObject.TryGetComponent(out component);
}
#endif
/// <summary>
/// Get components in children of a specific type in the hierarchy of a GameObject.
/// </summary>
public static T[] GetComponentsInChildren<T>(this Component self, int depth)
where T : Component
{
var results = InternalListPool<T>.Rent();
self.GetComponentsInChildren_Internal(results, depth);
var array = results.ToArray();
InternalListPool<T>.Return(ref results);
return array;
}
/// <summary>
/// Get components in children of a specific type in the hierarchy of a GameObject.
/// </summary>
public static void GetComponentsInChildren<T>(this Component self, List<T> results, int depth)
where T : Component
{
results.Clear();
self.GetComponentsInChildren_Internal(results, depth);
}
private static void GetComponentsInChildren_Internal<T>(this Component self, List<T> results, int depth)
where T : Component
{
if (self == null || results == null || depth < 0) return;
var tr = self.transform;
if (tr.TryGetComponent<T>(out var t))
{
results.Add(t);
}
if (depth - 1 < 0) return;
var childCount = tr.childCount;
for (var i = 0; i < childCount; i++)
{
tr.GetChild(i).GetComponentsInChildren_Internal(results, depth - 1);
}
}
/// <summary>
/// Get or add a component of a specific type to a GameObject.
/// </summary>
public static T GetOrAddComponent<T>(this Component self) where T : Component
{
if (self == null) return null;
return self.TryGetComponent<T>(out var component)
? component
: self.gameObject.AddComponent<T>();
}
/// <summary>
/// Get the root component of a specific type in the hierarchy of a GameObject.
/// </summary>
public static T GetRootComponent<T>(this Component self) where T : Component
{
T component = null;
var transform = self.transform;
while (transform != null)
{
if (transform.TryGetComponent<T>(out var c))
{
component = c;
}
transform = transform.parent;
}
return component;
}
/// <summary>
/// Get a component of a specific type in the parent hierarchy of a GameObject.
/// </summary>
public static T GetComponentInParent<T>(this Component self, bool includeSelf, Transform stopAfter,
Predicate<T> valid)
where T : Component
{
var tr = includeSelf ? self.transform : self.transform.parent;
while (tr != null)
{
if (tr.TryGetComponent<T>(out var c) && valid(c)) return c;
if (tr == stopAfter) return null;
tr = tr.parent;
}
return null;
}
/// <summary>
/// Add a component of a specific type to the children of a GameObject.
/// </summary>
public static void AddComponentOnChildren<T>(this Component self, HideFlags hideFlags, bool includeSelf)
where T : Component
{
if (self == null) return;
Profiler.BeginSample("(COF)[ComponentExt] AddComponentOnChildren > Self");
if (includeSelf && !self.TryGetComponent<T>(out _))
{
var c = self.gameObject.AddComponent<T>();
c.hideFlags = hideFlags;
}
Profiler.EndSample();
Profiler.BeginSample("(COF)[ComponentExt] AddComponentOnChildren > Child");
var childCount = self.transform.childCount;
for (var i = 0; i < childCount; i++)
{
var child = self.transform.GetChild(i);
if (child.TryGetComponent<T>(out _)) continue;
var c = child.gameObject.AddComponent<T>();
c.hideFlags = hideFlags;
}
Profiler.EndSample();
}
/// <summary>
/// Add a component of a specific type to the children of a GameObject.
/// </summary>
public static void AddComponentOnChildren<T>(this Component self, bool includeSelf)
where T : Component
{
if (self == null) return;
Profiler.BeginSample("(COF)[ComponentExt] AddComponentOnChildren > Self");
if (includeSelf && !self.TryGetComponent<T>(out _))
{
self.gameObject.AddComponent<T>();
}
Profiler.EndSample();
Profiler.BeginSample("(COF)[ComponentExt] AddComponentOnChildren > Child");
var childCount = self.transform.childCount;
for (var i = 0; i < childCount; i++)
{
var child = self.transform.GetChild(i);
if (child.TryGetComponent<T>(out _)) continue;
child.gameObject.AddComponent<T>();
}
Profiler.EndSample();
}
#if !UNITY_2021_2_OR_NEWER && !UNITY_2020_3_45 && !UNITY_2020_3_46 && !UNITY_2020_3_47 && !UNITY_2020_3_48
public static T GetComponentInParent<T>(this Component self, bool includeInactive) where T : Component
{
if (self == null) return null;
if (!includeInactive) return self.GetComponentInParent<T>();
var current = self.transform;
while (current != null)
{
if (current.TryGetComponent<T>(out var c)) return c;
current = current.parent;
}
return null;
}
#endif
#if UNITY_EDITOR
/// <summary>
/// Verify whether it can be converted to the specified component.
/// </summary>
internal static bool CanConvertTo<T>(this Object context) where T : MonoBehaviour
{
return context != null && context.GetType() != typeof(T);
}
/// <summary>
/// Convert to the specified component.
/// </summary>
internal static void ConvertTo<T>(this Object context) where T : MonoBehaviour
{
var target = context as MonoBehaviour;
if (target == null) return;
var so = new SerializedObject(target);
so.Update();
var oldEnable = target.enabled;
target.enabled = false;
// Find MonoScript of the specified component.
foreach (var script in MonoImporter.GetAllRuntimeMonoScripts())
{
if (script.GetClass() != typeof(T))
{
continue;
}
// Set 'm_Script' to convert.
so.FindProperty("m_Script").objectReferenceValue = script;
so.ApplyModifiedProperties();
break;
}
if (so.targetObject is MonoBehaviour mb)
{
mb.enabled = oldEnable;
}
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,60 @@
using System;
using UnityEngine;
using UnityEngine.U2D;
#if UNITY_EDITOR
using System.Reflection;
#endif
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Extension methods for Sprite class.
/// </summary>
internal static class SpriteExtensions
{
#if UNITY_EDITOR
private static readonly Type s_SpriteEditorExtensionType =
Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor")
?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor");
private static readonly Func<Sprite, Texture2D> s_GetActiveAtlasTextureMethod =
(Func<Sprite, Texture2D>)Delegate.CreateDelegate(typeof(Func<Sprite, Texture2D>),
s_SpriteEditorExtensionType
.GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic));
private static readonly Func<Sprite, SpriteAtlas> s_GetActiveAtlasMethod =
(Func<Sprite, SpriteAtlas>)Delegate.CreateDelegate(typeof(Func<Sprite, SpriteAtlas>),
s_SpriteEditorExtensionType
.GetMethod("GetActiveAtlas", BindingFlags.Static | BindingFlags.NonPublic));
/// <summary>
/// Get the actual texture of a sprite in play mode or edit mode.
/// </summary>
public static Texture2D GetActualTexture(this Sprite self)
{
if (self == null) return null;
var ret = s_GetActiveAtlasTextureMethod(self);
return ret != null ? ret : self.texture;
}
/// <summary>
/// Get the active sprite atlas of a sprite in play mode or edit mode.
/// </summary>
public static SpriteAtlas GetActiveAtlas(this Sprite self)
{
if (self == null) return null;
return s_GetActiveAtlasMethod(self);
}
#else
/// <summary>
/// Get the actual texture of a sprite in play mode.
/// </summary>
internal static Texture2D GetActualTexture(this Sprite self)
{
return self != null ? self.texture : null;
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,46 @@
using UnityEngine;
namespace Coffee.UIParticleInternal
{
internal static class Vector3Extensions
{
public static Vector3 Inverse(this Vector3 self)
{
self.x = Mathf.Approximately(self.x, 0) ? 1 : 1 / self.x;
self.y = Mathf.Approximately(self.y, 0) ? 1 : 1 / self.y;
self.z = Mathf.Approximately(self.z, 0) ? 1 : 1 / self.z;
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1)
{
self.Scale(other1);
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2)
{
self.Scale(other1);
self.Scale(other2);
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2, Vector3 other3)
{
self.Scale(other1);
self.Scale(other2);
self.Scale(other3);
return self;
}
public static bool IsVisible(this Vector3 self)
{
return 0 < Mathf.Abs(self.x * self.y * self.z);
}
public static bool IsVisible2D(this Vector3 self)
{
return 0 < Mathf.Abs(self.x * self.y);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,333 @@
using System;
using System.Linq;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
#endif
namespace Coffee.UIParticleInternal
{
public abstract class PreloadedProjectSettings : ScriptableObject
#if UNITY_EDITOR
{
[Tooltip("When enabled, this settings asset will be added to PlayerSettings.preloadedAssets in build.\n\n" +
"When disable, you should load this settings via Resources, AssetBundles or Addressables to use.")]
[SerializeField]
[Header("Advanced")]
[HideInInspector]
private bool m_PreLoadSettingsInBuild = true;
protected static bool s_BuildingPlayer;
private class EditorEvents : AssetPostprocessor, IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
int IOrderedCallback.callbackOrder => 0;
private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____)
{
Initialize();
}
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
{
AssetDatabase.Refresh();
Initialize();
s_BuildingPlayer = true;
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{
var settings = GetDefaultSettings(t);
if (settings == null || settings.m_PreLoadSettingsInBuild) continue;
PlayerSettings.SetPreloadedAssets(
PlayerSettings.GetPreloadedAssets()
.Where(x => x != null && x.GetType() != t)
.ToArray());
Debug.Log($"[PreloadedProjectSettings] Build started: removed '{settings.name}' " +
$"({t.Name}) from PreloadedAssets. " +
$"It will be restored after build completes.");
}
}
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report)
{
s_BuildingPlayer = false;
Initialize();
}
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnDomainReload()
{
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{
var defaultSettings = GetDefaultSettings(t);
if (defaultSettings != null)
{
defaultSettings.OnDomainReload();
}
}
}
#endif
}
private static void Initialize()
{
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{
var defaultSettings = GetDefaultSettings(t);
if (defaultSettings == null)
{
if (!s_BuildingPlayer)
{
// When create a new instance, automatically set it as default settings.
defaultSettings = CreateInstance(t) as PreloadedProjectSettings;
SetDefaultSettings(defaultSettings);
}
}
else if (GetPreloadedSettings(t).Length != 1)
{
if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings);
}
if (defaultSettings != null)
{
defaultSettings.OnInitialize();
}
}
}
protected static string GetDefaultName(Type type, bool nicify)
{
var typeName = type.Name;
return nicify
? ObjectNames.NicifyVariableName(typeName)
: typeName;
}
private static Object[] GetPreloadedSettings(Type type)
{
return PlayerSettings.GetPreloadedAssets()
.Where(x => x != null && x.GetType() == type)
.ToArray();
}
protected static PreloadedProjectSettings GetDefaultSettings(Type type)
{
return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings
?? AssetDatabase.FindAssets($"t:{type.Name}")
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<PreloadedProjectSettings>)
.FirstOrDefault(x => x != null && x.GetType() == type);
}
protected static void SetDefaultSettings(PreloadedProjectSettings asset)
{
if (asset == null) return;
var type = asset.GetType();
if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(asset)))
{
if (!AssetDatabase.IsValidFolder("Assets/ProjectSettings"))
{
AssetDatabase.CreateFolder("Assets", "ProjectSettings");
}
var assetPath = $"Assets/ProjectSettings/{GetDefaultName(type, false)}.asset";
assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
if (!File.Exists(assetPath))
{
AssetDatabase.CreateAsset(asset, assetPath);
asset.OnCreateAsset();
}
}
var preloadedAssets = PlayerSettings.GetPreloadedAssets();
var projectSettings = GetPreloadedSettings(type);
PlayerSettings.SetPreloadedAssets(preloadedAssets
.Where(x => x != null)
.Except(projectSettings.Except(new[] { asset }))
.Append(asset)
.Distinct()
.ToArray());
AssetDatabase.Refresh();
}
protected virtual void OnCreateAsset()
{
}
protected virtual void OnInitialize()
{
}
protected virtual void OnDomainReload()
{
}
}
internal abstract class PreloadedProjectSettingsEditor : Editor
{
private SerializedProperty _preLoadSettingsInBuild;
protected virtual void OnEnable()
{
_preLoadSettingsInBuild = serializedObject.FindProperty("m_PreLoadSettingsInBuild");
}
protected void DrawPreLoadSettingsInBuild(string packageName)
{
EditorGUILayout.PropertyField(_preLoadSettingsInBuild);
if (!_preLoadSettingsInBuild.boolValue)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.HelpBox(
$"{target.GetType().Name} asset will not be built in.\n" +
$"please load manually from Resources, AssetBundle, or Addressables before using {packageName}.",
MessageType.Warning);
if (GUILayout.Button("Ping"))
{
EditorGUIUtility.PingObject(target);
}
EditorGUILayout.EndHorizontal();
}
}
}
#else
{
}
#endif
public abstract class PreloadedProjectSettings<T> : PreloadedProjectSettings
where T : PreloadedProjectSettings<T>
{
private static T s_Instance;
#if UNITY_EDITOR
private string _jsonText;
public static bool hasInstance => s_Instance != null;
public static T instance
{
get
{
if (s_Instance != null) return s_Instance;
s_Instance = GetDefaultSettings(typeof(T)) as T;
if (s_Instance != null) return s_Instance;
s_Instance = CreateInstance<T>();
if (s_Instance == null)
{
s_Instance = null;
return s_Instance;
}
if (!s_BuildingPlayer) SetDefaultSettings(s_Instance);
return s_Instance;
}
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (this == null) return;
switch (state)
{
case PlayModeStateChange.ExitingEditMode:
_jsonText = EditorJsonUtility.ToJson(this);
break;
case PlayModeStateChange.ExitingPlayMode:
if (_jsonText != null)
{
EditorJsonUtility.FromJsonOverwrite(_jsonText, this);
_jsonText = null;
}
break;
}
}
protected override void OnDomainReload()
{
s_Instance = null;
}
#else
public static T instance => s_Instance != null ? s_Instance : s_Instance = CreateInstance<T>();
#endif
/// <summary>
/// This function is called when the object becomes enabled and active.
/// </summary>
protected virtual void OnEnable()
{
#if UNITY_EDITOR
var isDefaultSettings = s_Instance == null || s_Instance == this || GetDefaultSettings(typeof(T)) == this;
if (!isDefaultSettings)
{
DestroyImmediate(this, true);
return;
}
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#else
if (s_Instance != null && s_Instance != this)
{
Destroy(s_Instance);
}
#endif
s_Instance = this as T;
}
/// <summary>
/// This function is called when the behaviour becomes disabled.
/// </summary>
protected virtual void OnDisable()
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
#endif
if (s_Instance != this) return;
s_Instance = null;
}
#if UNITY_EDITOR
protected sealed class PreloadedProjectSettingsProvider : SettingsProvider
{
private Editor _editor;
private PreloadedProjectSettings<T> _target;
public PreloadedProjectSettingsProvider(string path) : base(path, SettingsScope.Project)
{
}
public override void OnGUI(string searchContext)
{
if (_target == null)
{
if (_editor != null)
{
DestroyImmediate(_editor);
_editor = null;
}
_target = instance;
_editor = Editor.CreateEditor(_target);
}
_editor.OnInspectorGUI();
}
}
#endif
}
}

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Base class for a fast action.
/// </summary>
internal class FastActionBase<T>
{
private static readonly InternalObjectPool<LinkedListNode<T>> s_NodePool =
new InternalObjectPool<LinkedListNode<T>>(() => new LinkedListNode<T>(default), _ => true,
x => x.Value = default);
private readonly LinkedList<T> _delegates = new LinkedList<T>();
/// <summary>
/// Adds a delegate to the action.
/// </summary>
public void Add(T rhs)
{
if (rhs == null) return;
Profiler.BeginSample("(COF)[FastAction] Add Action");
var node = s_NodePool.Rent();
node.Value = rhs;
_delegates.AddLast(node);
Profiler.EndSample();
}
/// <summary>
/// Removes a delegate from the action.
/// </summary>
public void Remove(T rhs)
{
if (rhs == null) return;
Profiler.BeginSample("(COF)[FastAction] Remove Action");
var node = _delegates.Find(rhs);
if (node != null)
{
_delegates.Remove(node);
s_NodePool.Return(ref node);
}
Profiler.EndSample();
}
/// <summary>
/// Invokes the action with a callback function.
/// </summary>
protected void Invoke(Action<T> callback)
{
var node = _delegates.First;
while (node != null)
{
try
{
callback(node.Value);
}
catch (Exception e)
{
Debug.LogException(e);
}
node = node.Next;
}
}
}
/// <summary>
/// A fast action without parameters.
/// </summary>
internal class FastAction : FastActionBase<Action>
{
/// <summary>
/// Invoke all the registered delegates.
/// </summary>
public void Invoke()
{
Invoke(action => action.Invoke());
}
}
}

View File

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

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Coffee.UIParticleInternal
{
internal static class FrameCache
{
private static readonly Dictionary<Type, IFrameCache> s_Caches = new Dictionary<Type, IFrameCache>();
#if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#elif UNITY_EDITOR
[InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif
private static void Clear()
{
s_Caches.Clear();
UIExtraCallbacks.onLateAfterCanvasRebuild -= ClearAllCache;
UIExtraCallbacks.onLateAfterCanvasRebuild += ClearAllCache;
}
/// <summary>
/// Tries to retrieve a value from the frame cache with a specified key.
/// </summary>
public static bool TryGet<T>(object key1, string key2, out T result)
{
return GetFrameCache<T>().TryGet((key1.GetHashCode(), key2.GetHashCode()), out result);
}
/// <summary>
/// Tries to retrieve a value from the frame cache with a specified key.
/// </summary>
public static bool TryGet<T>(object key1, string key2, int key3, out T result)
{
return GetFrameCache<T>().TryGet((key1.GetHashCode(), key2.GetHashCode() + key3), out result);
}
/// <summary>
/// Sets a value in the frame cache with a specified key.
/// </summary>
public static void Set<T>(object key1, string key2, T result)
{
GetFrameCache<T>().Set((key1.GetHashCode(), key2.GetHashCode()), result);
}
/// <summary>
/// Sets a value in the frame cache with a specified key.
/// </summary>
public static void Set<T>(object key1, string key2, int key3, T result)
{
GetFrameCache<T>().Set((key1.GetHashCode(), key2.GetHashCode() + key3), result);
}
private static void ClearAllCache()
{
foreach (var cache in s_Caches.Values)
{
cache.Clear();
}
}
private static FrameCacheContainer<T> GetFrameCache<T>()
{
var t = typeof(T);
if (s_Caches.TryGetValue(t, out var frameCache)) return frameCache as FrameCacheContainer<T>;
frameCache = new FrameCacheContainer<T>();
s_Caches.Add(t, frameCache);
return (FrameCacheContainer<T>)frameCache;
}
private interface IFrameCache
{
void Clear();
}
private class FrameCacheContainer<T> : IFrameCache
{
private readonly Dictionary<(int, int), T> _caches = new Dictionary<(int, int), T>();
public void Clear()
{
_caches.Clear();
}
public bool TryGet((int, int) key, out T result)
{
return _caches.TryGetValue(key, out result);
}
public void Set((int, int) key, T result)
{
_caches[key] = result;
}
}
}
}

View File

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

View File

@@ -0,0 +1,254 @@
using System;
using System.Text;
using UnityEngine;
using Object = UnityEngine.Object;
#if ENABLE_COFFEE_LOGGER
using System.Reflection;
using System.Collections.Generic;
#else
using Conditional = System.Diagnostics.ConditionalAttribute;
#endif
namespace Coffee.UIParticleInternal
{
internal static class Logger
{
#if !ENABLE_COFFEE_LOGGER
private const string k_DisableSymbol = "DISABLE_COFFEE_LOGGER";
[Conditional(k_DisableSymbol)]
#endif
private static void Log_Internal(LogType type, object tag, object message, Object context)
{
#if ENABLE_COFFEE_LOGGER
AppendTag(s_Sb, tag);
s_Sb.Append(message);
switch (type)
{
case LogType.Error:
case LogType.Assert:
case LogType.Exception:
Debug.LogError(s_Sb, context);
break;
case LogType.Warning:
Debug.LogWarning(s_Sb, context);
break;
case LogType.Log:
Debug.Log(s_Sb, context);
break;
}
s_Sb.Length = 0;
#endif
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
public static void LogIf(bool enable, object tag, object message, Object context = null)
{
if (!enable) return;
Log_Internal(LogType.Log, tag, message, context != null ? context : tag as Object);
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
public static void Log(object tag, object message, Object context = null)
{
Log_Internal(LogType.Log, tag, message, context != null ? context : tag as Object);
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
public static void LogWarning(object tag, object message, Object context = null)
{
Log_Internal(LogType.Warning, tag, message, context != null ? context : tag as Object);
}
public static void LogError(object tag, object message, Object context = null)
{
#if ENABLE_COFFEE_LOGGER
Log_Internal(LogType.Error, tag, message, context != null ? context : tag as Object);
#else
Debug.LogError($"{tag}: {message}", context);
#endif
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
public static void LogMulticast(Type type, string fieldName, object instance = null, string message = null)
{
#if ENABLE_COFFEE_LOGGER
AppendTag(s_Sb, instance ?? type);
var handler = type
.GetField(fieldName,
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(instance);
var list = ((MulticastDelegate)handler)?.GetInvocationList() ?? Array.Empty<Delegate>();
s_Sb.Append("<color=orange>");
s_Sb.Append(type.Name);
s_Sb.Append(".");
s_Sb.Append(fieldName);
s_Sb.Append(" has ");
s_Sb.Append(list.Length);
s_Sb.Append(" callbacks");
if (message != null)
{
s_Sb.Append(" (");
s_Sb.Append(message);
s_Sb.Append(")");
}
s_Sb.Append(":</color>");
for (var i = 0; i < list.Length; i++)
{
s_Sb.Append("\n - ");
s_Sb.Append(list[i].Method.DeclaringType?.Name);
s_Sb.Append(".");
s_Sb.Append(list[i].Method.Name);
}
Debug.Log(s_Sb);
s_Sb.Length = 0;
#endif
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
private static void AppendTag(StringBuilder sb, object tag)
{
#if ENABLE_COFFEE_LOGGER
try
{
sb.Append("f");
sb.Append(Time.frameCount);
sb.Append(":<color=#");
AppendReadableCode(sb, tag);
sb.Append("><b>[");
switch (tag)
{
case string name:
sb.Append(name);
break;
case Type type:
AppendType(sb, type);
break;
case Object uObject:
AppendType(sb, tag.GetType());
sb.Append(" #");
sb.Append(uObject.name);
break;
default:
AppendType(sb, tag.GetType());
break;
}
sb.Append("]</b></color> ");
}
catch
{
sb.Append("f");
sb.Append(Time.frameCount);
sb.Append(":<b>[");
sb.Append(tag);
sb.Append("]</b> ");
}
#endif
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
private static void AppendType(StringBuilder sb, Type type)
{
#if ENABLE_COFFEE_LOGGER
if (s_TypeNameCache.TryGetValue(type, out var name))
{
sb.Append(name);
return;
}
// New type found
var start = sb.Length;
if (0 < start && sb[start - 1] == '<' && (type.Name == "Material" || type.Name == "Color"))
{
sb.Append('@');
}
sb.Append(type.Name);
if (type.IsGenericType)
{
sb.Length -= 2;
sb.Append("<");
foreach (var gType in type.GetGenericArguments())
{
AppendType(sb, gType);
sb.Append(", ");
}
sb.Length -= 2;
sb.Append(">");
}
s_TypeNameCache.Add(type, sb.ToString(start, sb.Length - start));
#endif
}
#if !ENABLE_COFFEE_LOGGER
[Conditional(k_DisableSymbol)]
#endif
private static void AppendReadableCode(StringBuilder sb, object tag)
{
#if ENABLE_COFFEE_LOGGER
int hash;
try
{
switch (tag)
{
case string text:
hash = text.GetHashCode();
break;
case Type type:
type = type.IsGenericType ? type.GetGenericTypeDefinition() : type;
hash = type.FullName?.GetHashCode() ?? 0;
break;
default:
hash = tag.GetType().FullName?.GetHashCode() ?? 0;
break;
}
}
catch
{
sb.Append("FFFFFF");
return;
}
hash = hash & (s_Codes.Length - 1);
if (s_Codes[hash] == null)
{
var hue = hash / (float)s_Codes.Length;
var modifier = 1f - Mathf.Clamp01(Mathf.Abs(hue - 0.65f) / 0.2f);
var saturation = 0.7f + modifier * -0.2f;
var value = 0.8f + modifier * 0.3f;
s_Codes[hash] = ColorUtility.ToHtmlStringRGB(Color.HSVToRGB(hue, saturation, value));
}
sb.Append(s_Codes[hash]);
#endif
}
#if ENABLE_COFFEE_LOGGER
private static readonly StringBuilder s_Sb = new StringBuilder();
private static readonly string[] s_Codes = new string[64];
private static readonly Dictionary<Type, string> s_TypeNameCache = new Dictionary<Type, string>();
#endif
}
}

View File

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

View File

@@ -0,0 +1,94 @@
using System;
using UnityEngine;
using UnityEngine.Profiling;
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Provides functionality to manage materials.
/// </summary>
internal static class MaterialRepository
{
private static readonly ObjectRepository<Material> s_Repository = new ObjectRepository<Material>();
public static int count => s_Repository.count;
public static Func<string, Shader> onShaderFind = Shader.Find;
#if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Clear()
{
s_Repository.Clear();
}
#endif
/// <summary>
/// Retrieves a cached material based on the hash.
/// </summary>
public static bool Valid(Hash128 hash, Material material)
{
Profiler.BeginSample("(COF)[MaterialRegistry] Valid");
var ret = s_Repository.Valid(hash, material);
Profiler.EndSample();
return ret;
}
/// <summary>
/// Adds or retrieves a cached material based on the hash.
/// </summary>
public static void Get(Hash128 hash, ref Material material, Func<Material> onCreate)
{
Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, onCreate);
Profiler.EndSample();
}
/// <summary>
/// Adds or retrieves a cached material based on the hash.
/// </summary>
public static void Get(Hash128 hash, ref Material material, string shaderName)
{
Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, x => new Material(onShaderFind(x))
{
hideFlags = HideFlags.DontSave | HideFlags.NotEditable
}, shaderName);
Profiler.EndSample();
}
/// <summary>
/// Adds or retrieves a cached material based on the hash.
/// </summary>
public static void Get(Hash128 hash, ref Material material, string shaderName, string[] keywords)
{
Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, x => new Material(onShaderFind(x.shaderName))
{
hideFlags = HideFlags.DontSave | HideFlags.NotEditable,
shaderKeywords = x.keywords
}, (shaderName, keywords));
Profiler.EndSample();
}
/// <summary>
/// Adds or retrieves a cached material based on the hash.
/// </summary>
public static void Get<T>(Hash128 hash, ref Material material, Func<T, Material> onCreate, T source)
{
Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, onCreate, source);
Profiler.EndSample();
}
/// <summary>
/// Removes a soft mask material from the cache.
/// </summary>
public static void Release(ref Material material)
{
Profiler.BeginSample("(COF)[MaterialRepository] Release");
s_Repository.Release(ref material);
Profiler.EndSample();
}
}
}

View File

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

View File

@@ -0,0 +1,129 @@
using System;
using System.Diagnostics;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using System.IO;
using System.Linq;
using System.Reflection;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.Experimental.SceneManagement;
#endif
#endif
namespace Coffee.UIParticleInternal
{
internal static class Misc
{
public static T[] FindObjectsOfType<T>() where T : Object
{
#if UNITY_6000_4_OR_NEWER
return Object.FindObjectsByType<T>(FindObjectsInactive.Include);
#elif UNITY_2023_1_OR_NEWER
return Object.FindObjectsByType<T>(FindObjectsInactive.Include, FindObjectsSortMode.None);
#else
return Object.FindObjectsOfType<T>();
#endif
}
public static void Destroy(Object obj)
{
if (obj == null) return;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Object.DestroyImmediate(obj);
}
else
#endif
{
Object.Destroy(obj);
}
}
public static void DestroyImmediate(Object obj)
{
if (obj == null) return;
#if UNITY_EDITOR
Object.DestroyImmediate(obj, true);
#else
Object.Destroy(obj);
#endif
}
[Conditional("UNITY_EDITOR")]
public static void SetDirty(Object obj)
{
#if UNITY_EDITOR
if (obj == null) return;
EditorUtility.SetDirty(obj);
#endif
}
#if UNITY_EDITOR
public static T[] GetAllComponentsInPrefabStage<T>() where T : Component
{
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage == null) return Array.Empty<T>();
return prefabStage.prefabContentsRoot.GetComponentsInChildren<T>(true);
}
public static bool isBatchOrBuilding => Application.isBatchMode || BuildPipeline.isBuildingPlayer;
#endif
[Conditional("UNITY_EDITOR")]
public static void QueuePlayerLoopUpdate()
{
#if UNITY_EDITOR
if (!EditorApplication.isPlaying)
{
EditorApplication.QueuePlayerLoopUpdate();
}
#endif
}
}
#if !UNITY_2021_2_OR_NEWER
[AttributeUsage(AttributeTargets.Class)]
[Conditional("UNITY_EDITOR")]
internal class IconAttribute : Attribute
{
private readonly string _path;
public IconAttribute(string path)
{
_path = path;
}
#if UNITY_EDITOR
private static Action<Object, Texture2D> s_SetIconForObject = typeof(EditorGUIUtility)
.GetMethod("SetIconForObject", BindingFlags.Static | BindingFlags.NonPublic)
.CreateDelegate(typeof(Action<Object, Texture2D>), null) as Action<Object, Texture2D>;
[InitializeOnLoadMethod]
private static void InitializeOnLoadMethod()
{
if (Misc.isBatchOrBuilding) return;
var types = TypeCache.GetTypesWithAttribute(typeof(IconAttribute));
var scripts = MonoImporter.GetAllRuntimeMonoScripts();
foreach (var type in types)
{
var script = scripts.FirstOrDefault(x => x.GetClass() == type);
if (script == null) continue;
var path = type.GetCustomAttribute<IconAttribute>()?._path;
var icon = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (icon == null) continue;
s_SetIconForObject(script, icon);
}
}
#endif
}
#endif
}

View File

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

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Object pool.
/// </summary>
internal class InternalObjectPool<T> where T : class
{
#if UNITY_2021_1_OR_NEWER
private readonly Predicate<T> _onValid; // Delegate for checking if instances are valid
private readonly UnityEngine.Pool.ObjectPool<T> _pool;
public InternalObjectPool(Func<T> onCreate, Predicate<T> onValid, Action<T> onReturn)
{
_pool = new UnityEngine.Pool.ObjectPool<T>(onCreate, null, onReturn);
_onValid = onValid;
}
/// <summary>
/// Rent an instance from the pool.
/// When you no longer need it, return it with <see cref="Return" />.
/// </summary>
public T Rent()
{
while (0 < _pool.CountInactive)
{
var instance = _pool.Get();
if (_onValid(instance))
{
return instance;
}
}
// If there are no instances in the pool, create a new one.
Logger.Log(this, $"A new instance is created (pooled: {_pool.CountInactive}, created: {_pool.CountAll}).");
return _pool.Get();
}
/// <summary>
/// Return an instance to the pool and assign null.
/// Be sure to return the instance obtained with <see cref="Rent" /> with this method.
/// </summary>
public void Return(ref T instance)
{
if (instance == null) return; // Ignore if already pooled or null.
_pool.Release(instance);
Logger.Log(this, $"An instance is released (pooled: {_pool.CountInactive}, created: {_pool.CountAll}).");
instance = default; // Set the reference to null.
}
#else
private readonly Func<T> _onCreate; // Delegate for creating instances
private readonly Action<T> _onReturn; // Delegate for returning instances to the pool
private readonly Predicate<T> _onValid; // Delegate for checking if instances are valid
private readonly Stack<T> _pool = new Stack<T>(32); // Object pool
private int _count; // Total count of created instances
public InternalObjectPool(Func<T> onCreate, Predicate<T> onValid, Action<T> onReturn)
{
_onCreate = onCreate;
_onValid = onValid;
_onReturn = onReturn;
}
/// <summary>
/// Rent an instance from the pool.
/// When you no longer need it, return it with <see cref="Return" />.
/// </summary>
public T Rent()
{
while (0 < _pool.Count)
{
var instance = _pool.Pop();
if (_onValid(instance))
{
return instance;
}
}
// If there are no instances in the pool, create a new one.
Logger.Log(this, $"A new instance is created (pooled: {_pool.Count}, created: {++_count}).");
return _onCreate();
}
/// <summary>
/// Return an instance to the pool and assign null.
/// Be sure to return the instance obtained with <see cref="Rent" /> with this method.
/// </summary>
public void Return(ref T instance)
{
if (instance == null || _pool.Contains(instance)) return; // Ignore if already pooled or null.
_onReturn(instance); // Return the instance to the pool.
_pool.Push(instance);
Logger.Log(this, $"An instance is released (pooled: {_pool.Count}, created: {_count}).");
instance = default; // Set the reference to null.
}
#endif
}
/// <summary>
/// Object pool for <see cref="List{T}" />.
/// </summary>
internal static class InternalListPool<T>
{
#if UNITY_2021_1_OR_NEWER
/// <summary>
/// Rent an instance from the pool.
/// When you no longer need it, return it with <see cref="Return" />.
/// </summary>
public static List<T> Rent()
{
return UnityEngine.Pool.ListPool<T>.Get();
}
/// <summary>
/// Return an instance to the pool and assign null.
/// Be sure to return the instance obtained with <see cref="Rent" /> with this method.
/// </summary>
public static void Return(ref List<T> toRelease)
{
if (toRelease != null)
{
UnityEngine.Pool.ListPool<T>.Release(toRelease);
}
toRelease = null;
}
#else
private static readonly InternalObjectPool<List<T>> s_ListPool =
new InternalObjectPool<List<T>>(() => new List<T>(), _ => true, x => x.Clear());
/// <summary>
/// Rent an instance from the pool.
/// When you no longer need it, return it with <see cref="Return" />.
/// </summary>
public static List<T> Rent()
{
return s_ListPool.Rent();
}
/// <summary>
/// Return an instance to the pool and assign null.
/// Be sure to return the instance obtained with <see cref="Rent" /> with this method.
/// </summary>
public static void Return(ref List<T> toRelease)
{
s_ListPool.Return(ref toRelease);
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
using Object = UnityEngine.Object;
namespace Coffee.UIParticleInternal
{
internal class ObjectRepository<T> where T : Object
{
private readonly Dictionary<Hash128, Entry> _cache = new Dictionary<Hash128, Entry>(8);
private readonly Dictionary<int, Hash128> _objectKey = new Dictionary<int, Hash128>(8);
private readonly string _name;
private readonly Action<T> _onRelease;
private readonly Stack<Entry> _pool = new Stack<Entry>(8);
public ObjectRepository(Action<T> onRelease = null)
{
_name = $"{typeof(T).Name}Repository";
if (onRelease == null)
{
_onRelease = x =>
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Object.DestroyImmediate(x, false);
}
else
#endif
{
Object.Destroy(x);
}
};
}
else
{
_onRelease = onRelease;
}
for (var i = 0; i < 8; i++)
{
_pool.Push(new Entry());
}
}
public int count => _cache.Count;
public void Clear()
{
foreach (var kv in _cache)
{
var entry = kv.Value;
if (entry == null) continue;
entry.Release(_onRelease);
_pool.Push(entry);
}
_cache.Clear();
_objectKey.Clear();
}
public bool Valid(Hash128 hash, T obj)
{
return _cache.TryGetValue(hash, out var entry) && entry.storedObject == obj;
}
/// <summary>
/// Adds or retrieves a cached object based on the hash.
/// </summary>
public void Get(Hash128 hash, ref T obj, Func<T> onCreate)
{
if (GetFromCache(hash, ref obj)) return;
Add(hash, ref obj, onCreate());
}
/// <summary>
/// Adds or retrieves a cached object based on the hash.
/// </summary>
public void Get<TS>(Hash128 hash, ref T obj, Func<TS, T> onCreate, TS source)
{
if (GetFromCache(hash, ref obj)) return;
Add(hash, ref obj, onCreate(source));
}
private bool GetFromCache(Hash128 hash, ref T obj)
{
// Find existing entry.
Profiler.BeginSample("(COF)[ObjectRepository] GetFromCache");
if (_cache.TryGetValue(hash, out var entry))
{
if (entry.storedObject == null)
{
Release(ref entry.storedObject);
Profiler.EndSample();
return false;
}
if (entry.storedObject != obj)
{
// if the object is different, release the old one.
Release(ref obj);
++entry.reference;
obj = entry.storedObject;
Logger.Log(_name, $"Get(total#{count}): {entry}");
}
Profiler.EndSample();
return true;
}
Profiler.EndSample();
return false;
}
private void Add(Hash128 hash, ref T obj, T newObject)
{
if (newObject == null)
{
Release(ref obj);
obj = newObject;
return;
}
// Create and add a new entry.
Profiler.BeginSample("(COF)[ObjectRepository] Add");
var newEntry = 0 < _pool.Count ? _pool.Pop() : new Entry();
newEntry.storedObject = newObject;
newEntry.hash = hash;
newEntry.reference = 1;
_cache[hash] = newEntry;
_objectKey[newObject.GetHashCode()] = hash;
Logger.Log(_name, $"<color=#03c700>Add</color>(total#{count}): {newEntry}");
Release(ref obj);
obj = newObject;
Profiler.EndSample();
}
/// <summary>
/// Release a object.
/// </summary>
public void Release(ref T obj)
{
if (ReferenceEquals(obj, null)) return;
// Find and release the entry.
Profiler.BeginSample("(COF)[ObjectRepository] Release");
var id = obj.GetHashCode();
if (_objectKey.TryGetValue(id, out var hash)
&& _cache.TryGetValue(hash, out var entry))
{
entry.reference--;
if (entry.reference <= 0 || entry.storedObject == null)
{
Remove(entry);
}
else
{
Logger.Log(_name, $"Release(total#{_cache.Count}): {entry}");
}
}
else
{
Logger.Log(_name, $"Release(total#{_cache.Count}): <color=red>Already released: {obj}</color>");
}
obj = null;
Profiler.EndSample();
}
private void Remove(Entry entry)
{
if (ReferenceEquals(entry, null)) return;
Profiler.BeginSample("(COF)[ObjectRepository] Remove");
_cache.Remove(entry.hash);
_objectKey.Remove(entry.storedObject.GetHashCode());
_pool.Push(entry);
entry.reference = 0;
Logger.Log(_name, $"<color=#f29e03>Remove</color>(total#{_cache.Count}): {entry}");
entry.Release(_onRelease);
Profiler.EndSample();
}
private class Entry
{
public Hash128 hash;
public int reference;
public T storedObject;
public void Release(Action<T> onRelease)
{
reference = 0;
if (storedObject != null)
{
onRelease?.Invoke(storedObject);
}
storedObject = null;
}
public override string ToString()
{
return $"h{(uint)hash.GetHashCode()} (refs#{reference}), {storedObject}";
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,128 @@
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
namespace Coffee.UIParticleInternal
{
/// <summary>
/// Provides additional callbacks related to canvas and UI system.
/// </summary>
internal static class UIExtraCallbacks
{
private static bool s_IsInitializedAfterCanvasRebuild;
private static readonly FastAction s_AfterCanvasRebuildAction = new FastAction();
private static readonly FastAction s_LateAfterCanvasRebuildAction = new FastAction();
private static readonly FastAction s_BeforeCanvasRebuildAction = new FastAction();
private static readonly FastAction s_OnScreenSizeChangedAction = new FastAction();
private static Vector2Int s_LastScreenSize;
static UIExtraCallbacks()
{
Canvas.willRenderCanvases += OnBeforeCanvasRebuild;
Logger.LogMulticast(typeof(Canvas), "willRenderCanvases", message: "ctor");
}
/// <summary>
/// Event that occurs after canvas rebuilds.
/// </summary>
public static event Action onLateAfterCanvasRebuild
{
add => s_LateAfterCanvasRebuildAction.Add(value);
remove => s_LateAfterCanvasRebuildAction.Remove(value);
}
/// <summary>
/// Event that occurs before canvas rebuilds.
/// </summary>
public static event Action onBeforeCanvasRebuild
{
add => s_BeforeCanvasRebuildAction.Add(value);
remove => s_BeforeCanvasRebuildAction.Remove(value);
}
/// <summary>
/// Event that occurs after canvas rebuilds.
/// </summary>
public static event Action onAfterCanvasRebuild
{
add => s_AfterCanvasRebuildAction.Add(value);
remove => s_AfterCanvasRebuildAction.Remove(value);
}
/// <summary>
/// Event that occurs when the screen size changes.
/// </summary>
public static event Action onScreenSizeChanged
{
add => s_OnScreenSizeChangedAction.Add(value);
remove => s_OnScreenSizeChangedAction.Remove(value);
}
/// <summary>
/// Initializes the UIExtraCallbacks to ensure proper event handling.
/// </summary>
private static void InitializeAfterCanvasRebuild()
{
if (s_IsInitializedAfterCanvasRebuild) return;
s_IsInitializedAfterCanvasRebuild = true;
// Explicitly set `Canvas.willRenderCanvases += CanvasUpdateRegistry.PerformUpdate`.
CanvasUpdateRegistry.IsRebuildingLayout();
#if TMP_ENABLE
// Explicitly set `Canvas.willRenderCanvases += TMP_UpdateManager.DoRebuilds`.
typeof(TMPro.TMP_UpdateManager)
.GetProperty("instance", BindingFlags.NonPublic | BindingFlags.Static)
.GetValue(null);
#endif
Canvas.willRenderCanvases -= OnAfterCanvasRebuild;
Canvas.willRenderCanvases += OnAfterCanvasRebuild;
Logger.LogMulticast(typeof(Canvas), "willRenderCanvases",
message: "InitializeAfterCanvasRebuild");
}
#if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#elif UNITY_EDITOR
[InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif
private static void InitializeOnLoad()
{
Canvas.willRenderCanvases -= OnAfterCanvasRebuild;
s_IsInitializedAfterCanvasRebuild = false;
s_LastScreenSize = default;
}
/// <summary>
/// Callback method called before canvas rebuilds.
/// </summary>
private static void OnBeforeCanvasRebuild()
{
var screenSize = new Vector2Int(Screen.width, Screen.height);
if (s_LastScreenSize != screenSize)
{
if (s_LastScreenSize != default)
{
s_OnScreenSizeChangedAction.Invoke();
}
s_LastScreenSize = screenSize;
}
s_BeforeCanvasRebuildAction.Invoke();
InitializeAfterCanvasRebuild();
}
/// <summary>
/// Callback method called after canvas rebuilds.
/// </summary>
private static void OnAfterCanvasRebuild()
{
s_AfterCanvasRebuildAction.Invoke();
s_LateAfterCanvasRebuildAction.Invoke();
}
}
}

View File

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

View File

@@ -1,72 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
namespace Coffee.UIParticleExtensions
{
internal class ModifiedMaterial
{
private static readonly List<MatEntry> s_Entries = new List<MatEntry>();
public static Material Add(Material baseMat, Texture texture, int id, int props)
{
MatEntry e;
for (var i = 0; i < s_Entries.Count; i++)
{
e = s_Entries[i];
if (e.baseMat != baseMat || e.texture != texture || e.id != id || e.props != props) continue;
++e.count;
return e.customMat;
}
e = new MatEntry
{
count = 1,
baseMat = baseMat,
texture = texture,
id = id,
props = props,
customMat = new Material(baseMat)
{
name = $"{baseMat.name}_{id}",
hideFlags = HideFlags.DontSave | HideFlags.NotEditable,
mainTexture = texture ? texture : baseMat.mainTexture
}
};
s_Entries.Add(e);
//Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count, baseMat, texture, id);
return e.customMat;
}
public static void Remove(Material customMat)
{
if (!customMat) return;
for (var i = 0; i < s_Entries.Count; ++i)
{
var e = s_Entries[i];
if (e.customMat != customMat) continue;
if (--e.count == 0)
{
//Debug.LogFormat(">>>> ModifiedMaterial.Remove -> count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count - 1, e.customMat, e.texture, e.id);
Misc.DestroyImmediate(e.customMat);
e.customMat = null;
e.baseMat = null;
e.texture = null;
s_Entries.RemoveAt(i);
}
break;
}
}
private class MatEntry
{
public Material baseMat;
public int count;
public Material customMat;
public int id;
public int props;
public Texture texture;
}
}
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Coffee.UIParticleExtensions; using Coffee.UIParticleInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.Rendering; using UnityEngine.Rendering;
using UnityEngine.Serialization; using UnityEngine.Serialization;
@@ -9,12 +9,16 @@ using UnityEngine.UI;
using Random = UnityEngine.Random; using Random = UnityEngine.Random;
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] [assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor.Tests")]
[assembly: InternalsVisibleTo("Coffee.UIParticle.PerformanceDemo")]
[assembly: InternalsVisibleTo("Coffee.UIParticle.Demo")]
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
/// <summary> /// <summary>
/// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas. /// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
/// </summary> /// </summary>
[Icon("Packages/com.coffee.ui-particle/Editor/UIParticleIcon.png")]
[ExecuteAlways] [ExecuteAlways]
[RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(CanvasRenderer))]
@@ -117,9 +121,12 @@ namespace Coffee.UIExtensions
"Change the bake view size.")] "Change the bake view size.")]
private float m_CustomViewSize = 10; private float m_CustomViewSize = 10;
[SerializeField]
[Tooltip("Time scale multiplier.")]
private float m_TimeScaleMultiplier = 1;
private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>(); private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>();
private Camera _bakeCamera; private Camera _bakeCamera;
private Canvas _canvas;
private int _groupId; private int _groupId;
private bool _isScaleStored; private bool _isScaleStored;
private Vector3 _storedScale; private Vector3 _storedScale;
@@ -256,6 +263,22 @@ namespace Coffee.UIExtensions
set => m_CustomViewSize = Mathf.Max(0.1f, value); set => m_CustomViewSize = Mathf.Max(0.1f, value);
} }
/// <summary>
/// View size for Baking.
/// </summary>
public float viewSizeForBaking => useCustomView
? m_CustomViewSize
: UIParticleProjectSettings.defaultViewSizeForBaking;
/// <summary>
/// Time scale multiplier.
/// </summary>
public float timeScaleMultiplier
{
get => m_TimeScaleMultiplier;
set => m_TimeScaleMultiplier = value;
}
internal bool useMeshSharing => m_MeshSharing != MeshSharing.None; internal bool useMeshSharing => m_MeshSharing != MeshSharing.None;
internal bool isPrimary => internal bool isPrimary =>
@@ -342,6 +365,7 @@ namespace Coffee.UIExtensions
_isScaleStored = false; _isScaleStored = false;
UIParticleUpdater.Unregister(this); UIParticleUpdater.Unregister(this);
_renderers.RemoveAll(r => r == null);
_renderers.ForEach(r => r.Reset()); _renderers.ForEach(r => r.Reset());
UnregisterDirtyMaterialCallback(UpdateRendererMaterial); UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
@@ -455,7 +479,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
{ {
var r = _renderers[i]; var r = _renderers[i];
if (!r || !r.material) continue; if (r == null || r.material == null) continue;
result.Add(r.material); result.Add(r.material);
} }
} }
@@ -473,7 +497,7 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles) public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
{ {
if (!instance) return; if (instance == null) return;
var childCount = transform.childCount; var childCount = transform.childCount;
for (var i = 0; i < childCount; i++) for (var i = 0; i < childCount; i++)
@@ -502,7 +526,7 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
public void SetParticleSystemPrefab(GameObject prefab) public void SetParticleSystemPrefab(GameObject prefab)
{ {
if (!prefab) return; if (prefab == null) return;
SetParticleSystemInstance(Instantiate(prefab.gameObject), true); SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
} }
@@ -522,12 +546,17 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
private void RefreshParticles(GameObject root) private void RefreshParticles(GameObject root)
{ {
if (!root) return; if (root == null) return;
root.GetComponentsInChildren(true, particles); root.GetComponentsInChildren(true, particles);
for (var i = particles.Count - 1; 0 <= i; i--) for (var i = particles.Count - 1; 0 <= i; i--)
{ {
var ps = particles[i]; var ps = particles[i];
if (!ps || ps.GetComponentInParent<UIParticle>(true) != this) if (!ps
#if UNITY_EDITOR
|| (ps.hideFlags & HideFlags.DontSave) != 0 // Dummy ParticleSystems for preview.
|| ps.gameObject.CompareTag("EditorOnly") // Ignore "EditorOnly" tagged ParticleSystems.
#endif
|| ps.GetComponentInParent<UIParticle>(true) != this) // Ignore ParticleSystems that are not under this UIParticle.
{ {
particles.RemoveAt(i); particles.RemoveAt(i);
} }
@@ -575,13 +604,15 @@ namespace Coffee.UIExtensions
for (var i = 0; i < particleSystems.Count; i++) for (var i = 0; i < particleSystems.Count; i++)
{ {
var ps = particleSystems[i]; var ps = particleSystems[i];
if (!ps) continue; if (ps == null) continue;
GetRenderer(j++).Set(this, ps, false);
var mainEmitter = ps.GetMainEmitter(particleSystems);
GetRenderer(j++).Set(this, ps, false, mainEmitter);
// If the trail is enabled, set it additionally. // If the trail is enabled, set it additionally.
if (ps.trails.enabled) if (ps.trails.enabled)
{ {
GetRenderer(j++).Set(this, ps, true); GetRenderer(j++).Set(this, ps, true, mainEmitter);
} }
} }
} }
@@ -624,7 +655,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
{ {
var r = _renderers[i]; var r = _renderers[i];
if (r) continue; if (r != null) continue;
RefreshParticles(particles); RefreshParticles(particles);
break; break;
@@ -634,7 +665,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
{ {
var r = _renderers[i]; var r = _renderers[i];
if (!r) continue; if (r == null) continue;
r.UpdateMesh(bakeCamera); r.UpdateMesh(bakeCamera);
} }
@@ -663,7 +694,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
{ {
var r = _renderers[i]; var r = _renderers[i];
if (!r) continue; if (r == null) continue;
r.maskable = maskable; r.maskable = maskable;
r.SetMaterialDirty(); r.SetMaterialDirty();
} }
@@ -676,7 +707,7 @@ namespace Coffee.UIExtensions
_renderers.Add(UIParticleRenderer.AddRenderer(this, index)); _renderers.Add(UIParticleRenderer.AddRenderer(this, index));
} }
if (!_renderers[index]) if (_renderers[index] == null)
{ {
_renderers[index] = UIParticleRenderer.AddRenderer(this, index); _renderers[index] = UIParticleRenderer.AddRenderer(this, index);
} }
@@ -686,15 +717,15 @@ namespace Coffee.UIExtensions
private Camera GetBakeCamera() private Camera GetBakeCamera()
{ {
if (!canvas) return Camera.main; if (canvas == null) return Camera.main;
if (!useCustomView && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.rootCanvas.worldCamera) if (!useCustomView && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.rootCanvas.worldCamera)
{ {
return canvas.rootCanvas.worldCamera; return canvas.rootCanvas.worldCamera;
} }
if (_bakeCamera) if (_bakeCamera != null)
{ {
_bakeCamera.orthographicSize = useCustomView ? customViewSize : 10; _bakeCamera.orthographicSize = viewSizeForBaking;
return _bakeCamera; return _bakeCamera;
} }
@@ -711,7 +742,7 @@ namespace Coffee.UIExtensions
} }
// Create baking camera. // Create baking camera.
if (!_bakeCamera) if (_bakeCamera == null)
{ {
var go = new GameObject("[generated] UIParticle BakingCamera"); var go = new GameObject("[generated] UIParticle BakingCamera");
go.SetActive(false); go.SetActive(false);
@@ -721,7 +752,7 @@ namespace Coffee.UIExtensions
// Setup baking camera. // Setup baking camera.
_bakeCamera.enabled = false; _bakeCamera.enabled = false;
_bakeCamera.orthographicSize = useCustomView ? customViewSize : 10; _bakeCamera.orthographicSize = viewSizeForBaking;
_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;
@@ -733,7 +764,7 @@ namespace Coffee.UIExtensions
_bakeCamera.useOcclusionCulling = false; _bakeCamera.useOcclusionCulling = false;
_bakeCamera.gameObject.SetActive(false); _bakeCamera.gameObject.SetActive(false);
_bakeCamera.gameObject.hideFlags = HideFlags.HideAndDontSave; _bakeCamera.gameObject.hideFlags = UIParticleProjectSettings.globalHideFlags;
return _bakeCamera; return _bakeCamera;
} }

View File

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

View File

@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Coffee.UIParticleExtensions; using Coffee.UIParticleInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
@@ -148,7 +148,7 @@ namespace Coffee.UIExtensions
// Collect UIParticle if needed (same size as m_ParticleSystems) // Collect UIParticle if needed (same size as m_ParticleSystems)
CollectUIParticlesIfNeeded(); CollectUIParticlesIfNeeded();
for (var particleIndex = 0; particleIndex < this.m_ParticleSystems.Count; particleIndex++) for (var particleIndex = 0; particleIndex < m_ParticleSystems.Count; particleIndex++)
{ {
var particleSystem = m_ParticleSystems[particleIndex]; var particleSystem = m_ParticleSystems[particleIndex];
@@ -163,21 +163,21 @@ namespace Coffee.UIExtensions
particleSystem.GetParticles(particles, count); particleSystem.GetParticles(particles, count);
var uiParticle = _uiParticles[particleIndex]; var uiParticle = _uiParticles[particleIndex];
var dstPos = this.GetDestinationPosition(uiParticle, particleSystem); var dstPos = GetDestinationPosition(uiParticle, particleSystem);
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
// Attracted // Attracted
var p = particles[i]; var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < this.m_DestinationRadius) if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < m_DestinationRadius)
{ {
p.remainingLifetime = 0f; p.remainingLifetime = 0f;
particles[i] = p; particles[i] = p;
if (this.m_OnAttracted != null) if (m_OnAttracted != null)
{ {
try try
{ {
this.m_OnAttracted.Invoke(); m_OnAttracted.Invoke();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -189,7 +189,7 @@ namespace Coffee.UIExtensions
} }
// Calc attracting time // Calc attracting time
var delayTime = p.startLifetime * this.m_DelayRate; var delayTime = p.startLifetime * m_DelayRate;
var duration = p.startLifetime - delayTime; var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime); var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
@@ -197,7 +197,7 @@ namespace Coffee.UIExtensions
if (time <= 0) continue; if (time <= 0) continue;
// Attract // Attract
p.position = this.GetAttractedPosition(p.position, dstPos, duration, time); p.position = GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f; p.velocity *= 0.5f;
particles[i] = p; particles[i] = p;
} }
@@ -208,7 +208,7 @@ namespace Coffee.UIExtensions
private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem) private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem)
{ {
var isUI = uiParticle && uiParticle.enabled; var isUI = uiParticle != null && uiParticle.enabled;
var psPos = particleSystem.transform.position; var psPos = particleSystem.transform.position;
var attractorPos = transform.position; var attractorPos = transform.position;
var dstPos = attractorPos; var dstPos = attractorPos;

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

@@ -0,0 +1,68 @@
#pragma warning disable CS0414
using Coffee.UIParticleInternal;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions
{
public class UIParticleProjectSettings : PreloadedProjectSettings<UIParticleProjectSettings>
{
[Header("Setting")]
[SerializeField]
[Tooltip("Automatically correct the color space of the mesh.")]
[FormerlySerializedAs("m_EnableLinearToGamma")]
private bool m_AutoColorCorrection = true;
public static bool autoColorCorrection
{
get => instance.m_AutoColorCorrection;
set => instance.m_AutoColorCorrection = value;
}
[SerializeField]
[Tooltip("Default view size for baking particle systems.")]
private float m_DefaultViewSizeForBaking = 10;
public static float defaultViewSizeForBaking
{
get => instance.m_DefaultViewSizeForBaking;
set => instance.m_DefaultViewSizeForBaking = value;
}
[Header("Editor")]
[Tooltip("Hide the automatically generated objects.\n" +
" - UIParticleRenderer\n" +
" - UIParticle BakingCamera")]
[SerializeField]
private bool m_HideGeneratedObjects = true;
[Tooltip("When selecting UIParticle, a temporary ParticleSystem is generated for preview.")]
[SerializeField]
private bool m_PreviewOnSelect = true;
internal static HideFlags globalHideFlags => instance.m_HideGeneratedObjects
? HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInHierarchy | HideFlags.HideInInspector
: HideFlags.DontSave | HideFlags.NotEditable;
internal static bool previewOnSelect => instance.m_PreviewOnSelect;
#if UNITY_EDITOR
[SettingsProvider]
private static SettingsProvider CreateSettingsProvider()
{
return new PreloadedProjectSettingsProvider("Project/UI/UI Particle");
}
[CustomEditor(typeof(UIParticleProjectSettings))]
private class UIParticleProjectSettingsEditor : Editor
{
public override void OnInspectorGUI()
{
EditorGUIUtility.labelWidth = 180;
base.OnInspectorGUI();
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f22a23b9d98e440478697f4adf30e61c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 5f0675613942149309588d556e33d990, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -6,7 +6,7 @@
#endif #endif
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Coffee.UIParticleExtensions; using Coffee.UIParticleInternal;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.Profiling; using UnityEngine.Profiling;
@@ -15,25 +15,24 @@ using UnityEngine.UI;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
[Icon("Packages/com.coffee.ui-particle/Editor/UIParticleIcon.png")]
[ExecuteAlways] [ExecuteAlways]
[RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("")] [AddComponentMenu("")]
internal class UIParticleRenderer : MaskableGraphic internal class UIParticleRenderer : MaskableGraphic
{ {
private static readonly List<Component> s_Components = new List<Component>();
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>();
private static readonly List<Color32> s_Colors = new List<Color32>();
private static readonly Vector3[] s_Corners = new Vector3[4]; private static readonly Vector3[] s_Corners = new Vector3[4];
private Material _currentMaterialForRendering;
private bool _delay; private bool _delay;
private int _index; private int _index;
private bool _isPrevStored; private bool _isPrevStored;
private bool _isTrail; private bool _isTrail;
private bool _meshCleared;
private Bounds _lastBounds; private Bounds _lastBounds;
private Material _materialForRendering;
private Material _modifiedMaterial; private Material _modifiedMaterial;
private UIParticle _parent; private UIParticle _parent;
private ParticleSystem _particleSystem; private ParticleSystem _particleSystem;
@@ -43,6 +42,7 @@ namespace Coffee.UIExtensions
private Vector2Int _prevScreenSize; private Vector2Int _prevScreenSize;
private bool _preWarm; private bool _preWarm;
private ParticleSystemRenderer _renderer; private ParticleSystemRenderer _renderer;
private ParticleSystem _mainEmitter;
public override Texture mainTexture => _isTrail ? null : _particleSystem.GetTextureForSprite(); public override Texture mainTexture => _isTrail ? null : _particleSystem.GetTextureForSprite();
@@ -56,7 +56,7 @@ namespace Coffee.UIExtensions
s_Corners[1] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.max.y, 0); s_Corners[1] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.max.y, 0);
s_Corners[2] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.max.y, 0); s_Corners[2] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.max.y, 0);
s_Corners[3] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.min.y, 0); s_Corners[3] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.min.y, 0);
if (canvas) if (canvas != null)
{ {
var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix; var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix;
for (var i = 0; i < 4; ++i) for (var i = 0; i < 4; ++i)
@@ -92,9 +92,22 @@ namespace Coffee.UIExtensions
} }
} }
public override Material materialForRendering
{
get
{
if (_materialForRendering == null)
{
_materialForRendering = base.materialForRendering;
}
return _materialForRendering;
}
}
public void Reset(int index = -1) public void Reset(int index = -1)
{ {
if (_renderer) if (_renderer != null)
{ {
_renderer.enabled = true; _renderer.enabled = true;
} }
@@ -102,13 +115,14 @@ namespace Coffee.UIExtensions
_parent = null; _parent = null;
_particleSystem = null; _particleSystem = null;
_renderer = null; _renderer = null;
_mainEmitter = null;
if (0 <= index) if (0 <= index)
{ {
_index = index; _index = index;
} }
//_emitter = null; //_emitter = null;
if (this && isActiveAndEnabled) if (isActiveAndEnabled)
{ {
material = null; material = null;
canvasRenderer.Clear(); canvasRenderer.Clear();
@@ -117,9 +131,8 @@ namespace Coffee.UIExtensions
} }
else else
{ {
ModifiedMaterial.Remove(_modifiedMaterial); MaterialRepository.Release(ref _modifiedMaterial);
_modifiedMaterial = null; _materialForRendering = null;
_currentMaterialForRendering = null;
} }
} }
@@ -127,7 +140,8 @@ namespace Coffee.UIExtensions
{ {
base.OnEnable(); base.OnEnable();
if (!s_CombineInstances[0].mesh) hideFlags = UIParticleProjectSettings.globalHideFlags;
if (s_CombineInstances[0].mesh == null)
{ {
s_CombineInstances[0].mesh = new Mesh s_CombineInstances[0].mesh = new Mesh
{ {
@@ -135,17 +149,14 @@ namespace Coffee.UIExtensions
hideFlags = HideFlags.HideAndDontSave hideFlags = HideFlags.HideAndDontSave
}; };
} }
_currentMaterialForRendering = null;
} }
protected override void OnDisable() protected override void OnDisable()
{ {
base.OnDisable(); base.OnDisable();
ModifiedMaterial.Remove(_modifiedMaterial); MaterialRepository.Release(ref _modifiedMaterial);
_modifiedMaterial = null; _materialForRendering = null;
_currentMaterialForRendering = null;
_isPrevStored = false; _isPrevStored = false;
} }
@@ -154,7 +165,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
}; };
@@ -178,12 +189,9 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
public override Material GetModifiedMaterial(Material baseMaterial) public override Material GetModifiedMaterial(Material baseMaterial)
{ {
_currentMaterialForRendering = null;
if (!IsActive() || !_parent) if (!IsActive() || !_parent)
{ {
ModifiedMaterial.Remove(_modifiedMaterial); MaterialRepository.Release(ref _modifiedMaterial);
_modifiedMaterial = null;
return baseMaterial; return baseMaterial;
} }
@@ -193,26 +201,33 @@ namespace Coffee.UIExtensions
var texture = mainTexture; var texture = mainTexture;
if (texture == null && _parent.m_AnimatableProperties.Length == 0) if (texture == null && _parent.m_AnimatableProperties.Length == 0)
{ {
ModifiedMaterial.Remove(_modifiedMaterial); MaterialRepository.Release(ref _modifiedMaterial);
_modifiedMaterial = null;
return modifiedMaterial; return modifiedMaterial;
} }
// var hash = new Hash128(
var id = _parent.m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID(); modifiedMaterial ? (uint)modifiedMaterial.GetHashCode() : 0,
texture ? (uint)texture.GetHashCode() : 0,
0 < _parent.m_AnimatableProperties.Length ? (uint)GetHashCode() : 0,
#if UNITY_EDITOR #if UNITY_EDITOR
var props = EditorJsonUtility.ToJson(modifiedMaterial).GetHashCode(); (uint)EditorJsonUtility.ToJson(modifiedMaterial).GetHashCode()
#else #else
var props = 0; 0
#endif #endif
modifiedMaterial = ModifiedMaterial.Add(modifiedMaterial, texture, id, props); );
ModifiedMaterial.Remove(_modifiedMaterial); if (!MaterialRepository.Valid(hash, _modifiedMaterial))
_modifiedMaterial = modifiedMaterial; {
MaterialRepository.Get(hash, ref _modifiedMaterial, x => new Material(x.mat)
{
hideFlags = HideFlags.HideAndDontSave,
mainTexture = x.texture ? x.texture : x.mat.mainTexture
}, (mat: modifiedMaterial, texture));
}
return modifiedMaterial; return _modifiedMaterial;
} }
public void Set(UIParticle parent, ParticleSystem ps, bool isTrail) public void Set(UIParticle parent, ParticleSystem ps, bool isTrail, ParticleSystem mainEmitter)
{ {
_parent = parent; _parent = parent;
maskable = parent.maskable; maskable = parent.maskable;
@@ -235,10 +250,7 @@ namespace Coffee.UIExtensions
ps.TryGetComponent(out _renderer); ps.TryGetComponent(out _renderer);
_renderer.enabled = false; _renderer.enabled = false;
//_emitter = emitter;
_isTrail = isTrail; _isTrail = isTrail;
_renderer.GetSharedMaterials(s_Materials); _renderer.GetSharedMaterials(s_Materials);
material = s_Materials[isTrail ? 1 : 0]; material = s_Materials[isTrail ? 1 : 0];
s_Materials.Clear(); s_Materials.Clear();
@@ -255,6 +267,7 @@ namespace Coffee.UIExtensions
_prevScreenSize = new Vector2Int(Screen.width, Screen.height); _prevScreenSize = new Vector2Int(Screen.width, Screen.height);
_prevCanvasScale = canvas ? canvas.scaleFactor : 1f; _prevCanvasScale = canvas ? canvas.scaleFactor : 1f;
_delay = true; _delay = true;
_mainEmitter = mainEmitter;
canvasRenderer.SetTexture(null); canvasRenderer.SetTexture(null);
@@ -271,28 +284,47 @@ namespace Coffee.UIExtensions
|| !transform.lossyScale.GetScaled(_parent.scale3DForCalc).IsVisible() // Scale is not visible. || !transform.lossyScale.GetScaled(_parent.scale3DForCalc).IsVisible() // Scale is not visible.
|| (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle. || (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle.
|| (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled. || (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled.
#if UNITY_2018_3_OR_NEWER
|| canvasRenderer.GetInheritedAlpha() <
0.01f // #102: Do not bake particle system to mesh when the alpha is zero.
#endif
) )
{ {
// Skip clearing the mesh if it's already cleared.
if (_meshCleared) return;
Profiler.BeginSample("[UIParticleRenderer] Clear Mesh"); Profiler.BeginSample("[UIParticleRenderer] Clear Mesh");
workerMesh.Clear(); workerMesh.Clear();
canvasRenderer.SetMesh(workerMesh); canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds(); _lastBounds = new Bounds();
_meshCleared = true;
Profiler.EndSample(); Profiler.EndSample();
return; return;
} }
// Reset custom data.
// var customData = _particleSystem.customData;
// if (!customData.enabled || customData.GetMode(ParticleSystemCustomData.Custom1) == ParticleSystemCustomDataMode.Disabled)
// {
// customData.SetVector(ParticleSystemCustomData.Custom1, 0, 0);
// customData.SetVector(ParticleSystemCustomData.Custom1, 1, 0);
// customData.SetVector(ParticleSystemCustomData.Custom1, 2, 0);
// customData.SetVector(ParticleSystemCustomData.Custom1, 3, 0);
// }
//
// if (!customData.enabled || customData.GetMode(ParticleSystemCustomData.Custom2) == ParticleSystemCustomDataMode.Disabled)
// {
// customData.SetVector(ParticleSystemCustomData.Custom2, 0, 0);
// customData.SetVector(ParticleSystemCustomData.Custom2, 1, 0);
// customData.SetVector(ParticleSystemCustomData.Custom2, 2, 0);
// customData.SetVector(ParticleSystemCustomData.Custom2, 3, 0);
// }
_meshCleared = false;
var main = _particleSystem.main; var main = _particleSystem.main;
var scale = GetWorldScale(); var scale = GetWorldScale();
var psPos = _particleSystem.transform.position; var psPos = _particleSystem.transform.position;
// Simulate particles. // Simulate particles.
Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles"); Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles");
if (!_isTrail && _parent.canSimulate) if (!_isTrail && _parent.canSimulate && !_mainEmitter)
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
if (!Application.isPlaying) if (!Application.isPlaying)
@@ -303,6 +335,13 @@ namespace Coffee.UIExtensions
#endif #endif
{ {
ResolveResolutionChange(psPos, scale); ResolveResolutionChange(psPos, scale);
// fix: second and subsequent bursts not displayed when world simulation and non-looping. (#326)
if (!_particleSystem.IsLocalSpace() && !main.loop && _particleSystem.time == 0)
{
_delay = true;
}
Simulate(scale, _parent.isPaused || _delay); Simulate(scale, _parent.isPaused || _delay);
if (_delay && !_parent.isPaused) if (_delay && !_parent.isPaused)
@@ -405,95 +444,67 @@ namespace Coffee.UIExtensions
_lastBounds = bounds; _lastBounds = bounds;
// Convert linear color to gamma color. // Convert linear color to gamma color.
if (QualitySettings.activeColorSpace == ColorSpace.Linear) if (UIParticleProjectSettings.autoColorCorrection && canvas.ShouldGammaToLinearInMesh())
{ {
Profiler.BeginSample("[UIParticleRenderer] Convert Linear to Gamma"); workerMesh.LinearToGamma();
workerMesh.GetColors(s_Colors);
var count_c = s_Colors.Count;
for (var i = 0; i < count_c; i++)
{
var c = s_Colors[i];
c.r = c.r.LinearToGamma();
c.g = c.g.LinearToGamma();
c.b = c.b.LinearToGamma();
s_Colors[i] = c;
}
workerMesh.SetColors(s_Colors);
Profiler.EndSample();
} }
GetComponents(typeof(IMeshModifier), s_Components); var components = InternalListPool<Component>.Rent();
for (var i = 0; i < s_Components.Count; i++) GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++)
{ {
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
((IMeshModifier)s_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
} }
s_Components.Clear(); InternalListPool<Component>.Return(ref components);
} }
Profiler.EndSample(); Profiler.EndSample();
// Get grouped renderers.
s_Renderers.Clear();
if (_parent.useMeshSharing)
{
UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers);
}
// Set mesh to the CanvasRenderer.
Profiler.BeginSample("[UIParticleRenderer] Set Mesh");
for (var i = 0; i < s_Renderers.Count; i++)
{
if (s_Renderers[i] == this) continue;
s_Renderers[i].canvasRenderer.SetMesh(workerMesh);
s_Renderers[i]._lastBounds = _lastBounds;
}
if (!_parent.canRender)
{
workerMesh.Clear();
}
canvasRenderer.SetMesh(workerMesh);
Profiler.EndSample();
// Update animatable material properties. // Update animatable material properties.
Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties"); Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties");
#if UNITY_EDITOR
if (_modifiedMaterial != material)
{
_renderer.GetSharedMaterials(s_Materials);
material = s_Materials[_isTrail ? 1 : 0];
s_Materials.Clear();
SetMaterialDirty();
}
#endif
UpdateMaterialProperties(); UpdateMaterialProperties();
Profiler.EndSample();
// Get grouped renderers.
Profiler.BeginSample("[UIParticleRenderer] Set Mesh");
var renderers = InternalListPool<UIParticleRenderer>.Rent();
if (_parent.useMeshSharing) if (_parent.useMeshSharing)
{ {
if (!_currentMaterialForRendering) UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, renderers);
{ }
_currentMaterialForRendering = materialForRendering;
}
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.materialCount = 1; r.canvasRenderer.SetMesh(workerMesh);
s_Renderers[i].canvasRenderer.SetMaterial(_currentMaterialForRendering, 0); r._lastBounds = _lastBounds;
} r.canvasRenderer.materialCount = 1;
r.canvasRenderer.SetMaterial(materialForRendering, 0);
}
InternalListPool<UIParticleRenderer>.Return(ref renderers);
if (_parent.canRender)
{
canvasRenderer.SetMesh(workerMesh);
}
else
{
workerMesh.Clear();
} }
Profiler.EndSample(); Profiler.EndSample();
}
s_Renderers.Clear(); public override void SetMaterialDirty()
{
_materialForRendering = null;
base.SetMaterialDirty();
} }
/// <summary> /// <summary>
@@ -559,6 +570,30 @@ 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);
}
if (_mainEmitter)
{
if (_mainEmitter.IsLocalSpace())
{
return Matrix4x4.Translate(psPos)
* Matrix4x4.Scale(scale)
* Matrix4x4.Translate(-psPos);
}
else
{
psPos = _particleSystem.transform.position - _mainEmitter.transform.position;
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))
@@ -621,6 +656,7 @@ namespace Coffee.UIExtensions
: main.useUnscaledTime : main.useUnscaledTime
? Time.unscaledDeltaTime ? Time.unscaledDeltaTime
: Time.deltaTime; : Time.deltaTime;
deltaTime *= _parent.timeScaleMultiplier;
// Pre-warm: // Pre-warm:
if (0 < deltaTime && _preWarm) if (0 < deltaTime && _preWarm)
@@ -701,12 +737,12 @@ namespace Coffee.UIExtensions
if (s_Mpb.isEmpty) return; if (s_Mpb.isEmpty) return;
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
if (!_modifiedMaterial) return; if (materialForRendering == null) return;
for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++) for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++)
{ {
var ap = _parent.m_AnimatableProperties[i]; var ap = _parent.m_AnimatableProperties[i];
ap.UpdateMaterialProperties(_modifiedMaterial, s_Mpb); ap.UpdateMaterialProperties(materialForRendering, s_Mpb);
} }
s_Mpb.Clear(); s_Mpb.Clear();

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Coffee.UIParticleInternal;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@@ -15,38 +16,61 @@ namespace Coffee.UIExtensions
public static void Register(UIParticle particle) public static void Register(UIParticle particle)
{ {
if (!particle) return; if (particle == null) return;
s_ActiveParticles.Add(particle); s_ActiveParticles.Add(particle);
} }
public static void Unregister(UIParticle particle) public static void Unregister(UIParticle particle)
{ {
if (!particle) return; if (particle == null) return;
s_ActiveParticles.Remove(particle); s_ActiveParticles.Remove(particle);
} }
public static void Register(UIParticleAttractor attractor) public static void Register(UIParticleAttractor attractor)
{ {
if (!attractor) return; if (attractor == null) return;
s_ActiveAttractors.Add(attractor); s_ActiveAttractors.Add(attractor);
} }
public static void Unregister(UIParticleAttractor attractor) public static void Unregister(UIParticleAttractor attractor)
{ {
if (!attractor) return; if (attractor == null) return;
s_ActiveAttractors.Remove(attractor); s_ActiveAttractors.Remove(attractor);
} }
#if UNITY_EDITOR #if UNITY_EDITOR
[InitializeOnLoadMethod] #if UNITY_2019_3_OR_NEWER
#else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void OnDomainReload()
{
s_ActiveParticles.Clear();
s_ActiveAttractors.Clear();
s_UpdatedGroupIds.Clear();
s_FrameCount = 0;
}
#endif #endif
[InitializeOnLoadMethod]
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {
Canvas.willRenderCanvases -= Refresh; UIExtraCallbacks.onAfterCanvasRebuild += Refresh;
Canvas.willRenderCanvases += Refresh;
EditorApplication.playModeStateChanged += state =>
{
UIExtraCallbacks.onAfterCanvasRebuild -= Refresh;
if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.EnteredPlayMode)
{
UIExtraCallbacks.onAfterCanvasRebuild += Refresh;
}
};
} }
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void InitializeOnLoad()
{
UIExtraCallbacks.onAfterCanvasRebuild += Refresh;
}
#endif
private static void Refresh() private static void Refresh()
{ {
@@ -58,7 +82,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < s_ActiveParticles.Count; i++) for (var i = 0; i < s_ActiveParticles.Count; i++)
{ {
var uip = s_ActiveParticles[i]; var uip = s_ActiveParticles[i];
if (!uip || !uip.canvas || !uip.isPrimary || !s_UpdatedGroupIds.Add(uip.groupId)) continue; if (uip == null || uip.canvas == null || !uip.isPrimary || !s_UpdatedGroupIds.Add(uip.groupId)) continue;
uip.UpdateTransformScale(); uip.UpdateTransformScale();
uip.UpdateRenderers(); uip.UpdateRenderers();
@@ -68,7 +92,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < s_ActiveParticles.Count; i++) for (var i = 0; i < s_ActiveParticles.Count; i++)
{ {
var uip = s_ActiveParticles[i]; var uip = s_ActiveParticles[i];
if (!uip || !uip.canvas) continue; if (uip == null || uip.canvas == null) continue;
uip.UpdateTransformScale(); uip.UpdateTransformScale();
@@ -112,7 +136,7 @@ namespace Coffee.UIExtensions
var uip = s_ActiveParticles[i]; var uip = s_ActiveParticles[i];
if (!uip.useMeshSharing || uip.groupId != groupId) continue; if (!uip.useMeshSharing || uip.groupId != groupId) continue;
if (uip.isPrimary) return uip; if (uip.isPrimary) return uip;
if (!primary && uip.canSimulate) primary = uip; if (primary == null && uip.canSimulate) primary = uip;
} }
return primary; return primary;

8
Runtime/Utilities.meta Normal file
View File

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

View File

@@ -1,96 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace Coffee.UIParticleExtensions namespace Coffee.UIParticleInternal
{ {
public static class Color32Extensions internal static class ParticleSystemExtensions
{
private static byte[] s_LinearToGammaLut;
public static byte LinearToGamma(this byte self)
{
if (s_LinearToGammaLut == null)
{
s_LinearToGammaLut = new byte[256];
for (var i = 0; i < 256; i++)
{
s_LinearToGammaLut[i] = (byte)(Mathf.LinearToGammaSpace(i / 255f) * 255f);
}
}
return s_LinearToGammaLut[self];
}
}
public static class Vector3Extensions
{
public static Vector3 Inverse(this Vector3 self)
{
self.x = Mathf.Approximately(self.x, 0) ? 1 : 1 / self.x;
self.y = Mathf.Approximately(self.y, 0) ? 1 : 1 / self.y;
self.z = Mathf.Approximately(self.z, 0) ? 1 : 1 / self.z;
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1)
{
self.Scale(other1);
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2)
{
self.Scale(other1);
self.Scale(other2);
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2, Vector3 other3)
{
self.Scale(other1);
self.Scale(other2);
self.Scale(other3);
return self;
}
public static bool IsVisible(this Vector3 self)
{
return 0 < Mathf.Abs(self.x * self.y * self.z);
}
}
internal static class SpriteExtensions
{
#if UNITY_EDITOR
private static readonly Type s_SpriteEditorExtensionType =
Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor")
?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor");
private static readonly MethodInfo s_GetActiveAtlasTextureMethodInfo = s_SpriteEditorExtensionType
.GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic);
public static Texture2D GetActualTexture(this Sprite self)
{
if (!self) return null;
if (Application.isPlaying) return self.texture;
var ret = s_GetActiveAtlasTextureMethodInfo.Invoke(null, new object[] { self }) as Texture2D;
return ret
? ret
: self.texture;
}
#else
internal static Texture2D GetActualTexture(this Sprite self)
{
return self ? self.texture : null;
}
#endif
}
public static class ParticleSystemExtensions
{ {
private static ParticleSystem.Particle[] s_TmpParticles = new ParticleSystem.Particle[2048]; private static ParticleSystem.Particle[] s_TmpParticles = new ParticleSystem.Particle[2048];
@@ -98,11 +13,7 @@ namespace Coffee.UIParticleExtensions
{ {
if (s_TmpParticles.Length < size) if (s_TmpParticles.Length < size)
{ {
while (s_TmpParticles.Length < size) size = Mathf.NextPowerOfTwo(size);
{
size = Mathf.NextPowerOfTwo(size);
}
s_TmpParticles = new ParticleSystem.Particle[size]; s_TmpParticles = new ParticleSystem.Particle[size];
} }
@@ -173,15 +84,15 @@ namespace Coffee.UIParticleExtensions
var bRenderer = b.GetComponent<ParticleSystemRenderer>(); var bRenderer = b.GetComponent<ParticleSystemRenderer>();
// Render queue: ascending // Render queue: ascending
var aMat = aRenderer.sharedMaterial ? aRenderer.sharedMaterial : aRenderer.trailMaterial; var aMat = aRenderer.sharedMaterial != null ? aRenderer.sharedMaterial : aRenderer.trailMaterial;
var bMat = bRenderer.sharedMaterial ? bRenderer.sharedMaterial : bRenderer.trailMaterial; var bMat = bRenderer.sharedMaterial != null ? bRenderer.sharedMaterial : bRenderer.trailMaterial;
if (!aMat && !bMat) return 0; if (aMat == null && bMat == null) return 0;
if (!aMat) return -1; if (aMat == null) return -1;
if (!bMat) return 1; if (bMat == null) return 1;
if (sortByMaterial) if (sortByMaterial)
{ {
return aMat.GetInstanceID() - bMat.GetInstanceID(); return aMat.GetHashCode() - bMat.GetHashCode();
} }
if (aMat.renderQueue != bMat.renderQueue) if (aMat.renderQueue != bMat.renderQueue)
@@ -220,7 +131,7 @@ namespace Coffee.UIParticleExtensions
{ {
for (var i = 0; i < list.Count; i++) for (var i = 0; i < list.Count; i++)
{ {
if (list[i].GetInstanceID() == ps.GetInstanceID()) if (list[i].GetHashCode() == ps.GetHashCode())
{ {
return i; return i;
} }
@@ -231,7 +142,7 @@ namespace Coffee.UIParticleExtensions
public static Texture2D GetTextureForSprite(this ParticleSystem self) public static Texture2D GetTextureForSprite(this ParticleSystem self)
{ {
if (!self) return null; if (self == null) return null;
// Get sprite's texture. // Get sprite's texture.
var tsaModule = self.textureSheetAnimation; var tsaModule = self.textureSheetAnimation;
@@ -240,7 +151,7 @@ namespace Coffee.UIParticleExtensions
for (var i = 0; i < tsaModule.spriteCount; i++) for (var i = 0; i < tsaModule.spriteCount; i++)
{ {
var sprite = tsaModule.GetSprite(i); var sprite = tsaModule.GetSprite(i);
if (!sprite) continue; if (sprite == null) continue;
return sprite.GetActualTexture(); return sprite.GetActualTexture();
} }
@@ -250,59 +161,40 @@ namespace Coffee.UIParticleExtensions
public static void Exec(this List<ParticleSystem> self, Action<ParticleSystem> action) public static void Exec(this List<ParticleSystem> self, Action<ParticleSystem> action)
{ {
self.RemoveAll(p => !p); foreach (var p in self)
self.ForEach(action);
}
}
internal static class Misc
{
public static void Destroy(Object obj)
{
if (!obj) return;
#if UNITY_EDITOR
if (!Application.isPlaying)
{ {
Object.DestroyImmediate(obj); if (p == null) continue;
} action.Invoke(p);
else
#endif
{
Object.Destroy(obj);
} }
} }
public static void DestroyImmediate(Object obj) public static ParticleSystem GetMainEmitter(this ParticleSystem self, List<ParticleSystem> list)
{ {
if (!obj) return; if (self == null || list == null || list.Count == 0) return null;
#if UNITY_EDITOR
if (Application.isEditor)
{
Object.DestroyImmediate(obj);
}
else
#endif
{
Object.Destroy(obj);
}
}
#if !UNITY_2021_2_OR_NEWER && !UNITY_2020_3_45 && !UNITY_2020_3_46 && !UNITY_2020_3_47 && !UNITY_2020_3_48 for (var i = 0; i < list.Count; i++)
public static T GetComponentInParent<T>(this Component self, bool includeInactive) where T : Component
{
if (!self) return null;
if (!includeInactive) return self.GetComponentInParent<T>();
var current = self.transform;
while (current)
{ {
var component = current.GetComponent<T>(); var parent = list[i];
if (component) return component; if (parent != self && IsSubEmitterOf(self, parent)) return parent;
current = current.parent;
} }
return null; return null;
} }
#endif
public static bool IsSubEmitterOf(this ParticleSystem self, ParticleSystem parent)
{
if (self == null || parent == null) return false;
var subEmitters = parent.subEmitters;
if (!subEmitters.enabled) return false; // No sub emitters.
var count = subEmitters.subEmittersCount;
for (var i = 0; i < count; i++)
{
if (subEmitters.GetSubEmitterSystem(i) == self) return true;
}
return false;
}
} }
} }

View File

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

View File

@@ -0,0 +1,32 @@
using System.Linq;
using UnityEngine;
[RequireComponent(typeof(ParticleSystemRenderer))]
public class ColorArrayInjection : MonoBehaviour
{
[SerializeField] private string m_PropertyName = "_Color2";
[SerializeField] private Color[] m_Colors = new Color[4]
{
Color.red,
Color.green,
Color.blue,
Color.yellow
};
private void OnEnable()
{
if (TryGetComponent<ParticleSystemRenderer>(out var psr))
{
var mpb = new MaterialPropertyBlock();
psr.GetPropertyBlock(mpb);
mpb.SetVectorArray(m_PropertyName, m_Colors.Select(x => new Vector4(x.r, x.g, x.b, 1.0f)).ToArray());
psr.SetPropertyBlock(mpb);
}
}
private void OnValidate()
{
OnEnable();
}
}

View File

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

View File

@@ -0,0 +1,160 @@
Shader "UI/Additive And Color"
{
Properties
{
_MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Fog { Mode Off }
Blend One One
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile __ UNITY_UI_CLIP_RECT
#pragma multi_compile __ UNITY_UI_ALPHACLIP
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
int _UIVertexColorAlwaysGammaSpace;
#define LENGTH 4
float4 _Color2[4];
half3 _UIGammaToLinear(half3 value)
{
half3 low = 0.0849710 * value - 0.000163029;
half3 high = value * (value * (value * 0.265885 + 0.736584) - 0.00980184) + 0.00319697;
// We should be 0.5 away from any actual gamma value stored in an 8 bit channel
const half3 split = (half3)0.0725490; // Equals 18.5 / 255
return (value < split) ? low : high;
}
v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;
float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
if (_UIVertexColorAlwaysGammaSpace)
{
#ifndef UNITY_COLORSPACE_GAMMA
v.color.rgb = _UIGammaToLinear(v.color.rgb);
#endif
}
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0 / alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip(color.a - 0.001);
#endif
fixed3 fc = _Color2[floor(color.r*LENGTH-0.01)].rgb;
if (0.1 < fc.r || 0.1 < fc.g || 0.1 < fc.b)
{
color.rgb = fc;
}
color.rgb *= color.a;
return color;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9cc9ed37ff19d40e684526abbc1d44a6
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,90 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UIParticle_Demo_Animatable+Color
m_Shader: {fileID: 4800000, guid: 9cc9ed37ff19d40e684526abbc1d44a6, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 10300, guid: 0000000000000000f000000000000000, type: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 81f29a831022a4756b17daa366d67e10
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -8,8 +8,8 @@ Material:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_Name: UIParticle_Demo_Animatable m_Name: UIParticle_Demo_Animatable
m_Shader: {fileID: 4800000, guid: ecfa8f5732b504ef98fba10aa18d0326, type: 3} m_Shader: {fileID: 4800000, guid: 9cc9ed37ff19d40e684526abbc1d44a6, type: 3}
m_ShaderKeywords: m_Parent: {fileID: 0}
m_LightmapFlags: 4 m_LightmapFlags: 4
m_EnableInstancingVariants: 0 m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0 m_DoubleSidedGI: 0

View File

@@ -69,6 +69,8 @@ MonoBehaviour:
m_AnimatableProperties: m_AnimatableProperties:
- m_Name: _MainTex_ST - m_Name: _MainTex_ST
m_Type: 1 m_Type: 1
- m_Name: _Color2
m_Type: 103
m_Particles: m_Particles:
- {fileID: 198637387440798640} - {fileID: 198637387440798640}
m_MeshSharing: 0 m_MeshSharing: 0
@@ -88,6 +90,7 @@ GameObject:
- component: {fileID: 199176884810573912} - component: {fileID: 199176884810573912}
- component: {fileID: 222047182059782320} - component: {fileID: 222047182059782320}
- component: {fileID: 95475093951880840} - component: {fileID: 95475093951880840}
- component: {fileID: 3543952012814938654}
m_Layer: 0 m_Layer: 0
m_Name: UvAnimParticle m_Name: UvAnimParticle
m_TagString: Untagged m_TagString: Untagged
@@ -4864,3 +4867,21 @@ Animator:
m_HasTransformHierarchy: 1 m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1 m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorControllerStateOnDisable: 0 m_KeepAnimatorControllerStateOnDisable: 0
--- !u!114 &3543952012814938654
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1349193913114882}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: da4c2e90ecd3d4dd4a520099c08cf493, type: 3}
m_Name:
m_EditorClassIdentifier:
m_PropertyName: _Color2
m_Colors:
- {r: 1, g: 0, b: 0, a: 1}
- {r: 0, g: 1, b: 0, a: 1}
- {r: 0, g: 0, b: 1, a: 1}
- {r: 1, g: 0.92156863, b: 0.015686275, a: 1}

View File

@@ -15,7 +15,7 @@ namespace Coffee.UIExtensions.Demo
private void Start() private void Start()
{ {
if (!m_Origin) return; if (m_Origin == null) return;
m_Origin.SetActive(false); m_Origin.SetActive(false);
var parent = m_Origin.transform.parent; var parent = m_Origin.transform.parent;

View File

@@ -42,7 +42,7 @@ public class UIElementDragger : MonoBehaviour, IBeginDragHandler, IDragHandler,
break; break;
case Target.Custom: case Target.Custom:
_rectTransform.localPosition += delta; _rectTransform.localPosition += delta;
if (m_CustomTarget) if (m_CustomTarget != null)
{ {
if (m_UseCanvasScale) if (m_UseCanvasScale)
{ {

View File

@@ -1,3 +1,4 @@
using Coffee.UIParticleInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization; using UnityEngine.Serialization;
using UnityEngine.UI; using UnityEngine.UI;
@@ -51,11 +52,7 @@ namespace Coffee.UIExtensions.Demo
public void EnableAnimations(bool flag) public void EnableAnimations(bool flag)
{ {
#if UNITY_2023_1_OR_NEWER foreach (var animator in Misc.FindObjectsOfType<Animator>())
foreach (var animator in FindObjectsByType<Animator>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var animator in FindObjectsOfType<Animator>())
#endif
{ {
animator.enabled = flag; animator.enabled = flag;
} }
@@ -83,11 +80,7 @@ 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 Misc.FindObjectsOfType<UIParticle>())
foreach (var uip in FindObjectsByType<UIParticle>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var uip in FindObjectsOfType<UIParticle>())
#endif
{ {
uip.scale = scale; uip.scale = scale;
} }
@@ -95,11 +88,7 @@ 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 Misc.FindObjectsOfType<ParticleSystem>())
foreach (var p in FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var p in FindObjectsOfType<ParticleSystem>())
#endif
{ {
var main = p.main; var main = p.main;
main.simulationSpace = flag main.simulationSpace = flag
@@ -135,11 +124,7 @@ 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 Misc.FindObjectsOfType<ParticleSystem>())
foreach (var ps in FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var ps in FindObjectsOfType<ParticleSystem>())
#endif
{ {
ps.transform.localScale = new Vector3(scale, scale, scale); ps.transform.localScale = new Vector3(scale, scale, scale);
} }

View File

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

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": "4.9.1", "version": "4.13.0",
"unity": "2018.2", "unity": "2018.2",
"license": "MIT", "license": "MIT",
"repository": { "repository": {