Compare commits

..

31 Commits

Author SHA1 Message Date
semantic-release-bot 3c11209f17 chore(release): 4.2.1 [skip ci]
## [4.2.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/4.2.0...4.2.1) (2023-08-18)

### Bug Fixes

* autoScaling and PositionMode may be locked ([3f2f12d](3f2f12d2cf))
2023-08-18 13:50:36 +00:00
mob-sakai 3f2f12d2cf fix: autoScaling and PositionMode may be locked 2023-08-18 21:22:35 +09:00
semantic-release-bot b9643b98ff chore(release): 4.2.0 [skip ci]
# [4.2.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/4.1.7...4.2.0) (2023-08-18)

### Bug Fixes

* assertion 'ps->array_size()' in UpdateMesh() when using trails of type ribbon ([f75fcce](f75fcce0da)), closes [#241](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/241)
* built-in shaders are no longer supported ([c2119c1](c2119c171a)), closes [#233](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/233) [#257](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/257)
* crash occurs when too many vertices are rendered ([723a04d](723a04d0cf))
* error: SerializedObject target has been destroyed ([e930516](e93051603e)), closes [#267](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/267)
* excessive particle emitted on move ParticleSystem for local space simulation and emission over distance ([2fe0bde](2fe0bde422)), closes [#265](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/265)
* fix typos ([52f2ef1](52f2ef1f24))
* generated GameObject will be named '[generated] *' ([9b2e5c1](9b2e5c1d10))
* inactive ParticleSystems are removed from the list on refresh ([4851a18](4851a1880e))
* mesh sharing not working ([8b4ca1a](8b4ca1add5)), closes [#236](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/236)
* nullptr exceptions when using nested UIParticle components in hierarchy ([e67e948](e67e9482e2)), closes [#246](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/246)
* nullReferenceException after copy-n-paste ([425aad0](425aad0cba)), closes [#258](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/258)
* remove unnecessary per-frame allocation. ([e92b514](e92b514624))
* scaling ParticleSystem puts prewarmed particles in wrong location ([fb31db4](fb31db47f2)), closes [#235](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/235)
* sub-emitters option is not work in editor playing ([b308b26](b308b26833)), closes [#231](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/231)
* the camera under UIParticle will be assigned as _orthoCamera ([c42f8c8](c42f8c8ab0))
* UIParticleAttractor attracts the particles at wrong position when in RelativeMode ([68d9925](68d9925a16)), closes [#262](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/262)

### Features

* 'AbsoluteMode' option is renamed to 'PositionMode' ([67eff61](67eff61073))
* 'AutoScaling' option will be imported from 'IgnoreCanvasScale' (for v3.x) ([4103041](410304125f))
* add 'AutoScaling' option for UIParticle ([35325c8](35325c8899))
* add 'UpdateMode' option for UIParticleAttractor ([903f702](903f702d7b)), closes [#250](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/250)
* add particle system getter and setter for attractor ([a4bcf93](a4bcf93022)), closes [#253](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/253)
* add public properties for UIParticleAttractor ([392ab6d](392ab6dd76)), closes [#253](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/253)
* add Start/StopEmission API for UIParticle ([e499836](e4998365c9)), closes [#240](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/240)
2023-08-18 08:54:23 +00:00
mob-sakai b36a1571b3 chore: fix workflows 2023-08-18 17:52:48 +09:00
mob-sakai c42f8c8ab0 fix: the camera under UIParticle will be assigned as _orthoCamera
Prevents unintended camera control
2023-08-18 17:45:04 +09:00
mob-sakai 9b2e5c1d10 fix: generated GameObject will be named '[generated] *'
These objects are automatically generated and are not stored in the scene or prefab (HideFlags.DontSave)
2023-08-18 17:45:04 +09:00
mob-sakai 821f08ff5c docs: add FAQ 2023-08-18 17:45:04 +09:00
mob-sakai 28d8698597 fix compile error on Unity 2021.1 2023-08-18 17:45:04 +09:00
mob-sakai db28f171f7 docs: update docs 2023-08-18 15:08:24 +09:00
mob-sakai 410304125f feat: 'AutoScaling' option will be imported from 'IgnoreCanvasScale' (for v3.x) 2023-08-18 13:17:09 +09:00
mob-sakai 67eff61073 feat: 'AbsoluteMode' option is renamed to 'PositionMode'
AbsoluteMode property is obsolete
2023-08-18 13:17:09 +09:00
mob-sakai 4851a1880e fix: inactive ParticleSystems are removed from the list on refresh 2023-08-18 13:17:09 +09:00
mob-sakai 2fe0bde422 fix: excessive particle emitted on move ParticleSystem for local space simulation and emission over distance
close #265
2023-08-18 13:17:09 +09:00
mob-sakai c2119c171a fix: built-in shaders are no longer supported
close #233, close #257

Use UI shaders instead.
If built-in non-UI shaders are used, an error is displayed in the inspector.
2023-08-18 13:17:09 +09:00
mob-sakai 3df190382a resharp 2023-08-18 13:17:09 +09:00
mob-sakai 35325c8899 feat: add 'AutoScaling' option for UIParticle
Transform.lossyScale (=world scale) is automatically set to (1, 1, 1).
It prevents the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.
This option works in reverse of ’IgnoreCanvasScaler’ option in v3.x.
2023-08-18 11:48:05 +09:00
mob-sakai 903f702d7b feat: add 'UpdateMode' option for UIParticleAttractor
close #250
2023-08-18 11:48:05 +09:00
mob-sakai e93051603e fix: error: SerializedObject target has been destroyed
close #267
2023-08-18 11:48:05 +09:00
mob-sakai e4998365c9 feat: add Start/StopEmission API for UIParticle
close #240
2023-08-18 11:48:05 +09:00
mob-sakai 68d9925a16 fix: UIParticleAttractor attracts the particles at wrong position when in RelativeMode
close #262
2023-08-18 11:48:05 +09:00
mob-sakai 392ab6dd76 feat: add public properties for UIParticleAttractor
close #253
2023-08-18 11:48:05 +09:00
mob-sakai f75fcce0da fix: assertion 'ps->array_size()' in UpdateMesh() when using trails of type ribbon
close #241
2023-08-18 11:48:05 +09:00
mob-sakai fb31db47f2 fix: scaling ParticleSystem puts prewarmed particles in wrong location
close #235
2023-08-18 11:47:40 +09:00
mob-sakai 425aad0cba fix: nullReferenceException after copy-n-paste
close #258
2023-08-18 11:47:40 +09:00
mob-sakai 8b4ca1add5 fix: mesh sharing not working
close #236
2023-08-18 11:47:40 +09:00
mob-sakai b308b26833 fix: sub-emitters option is not work in editor playing
close #231
2023-08-18 11:47:40 +09:00
mob-sakai e67e9482e2 fix: nullptr exceptions when using nested UIParticle components in hierarchy
close #246
2023-08-18 11:47:11 +09:00
mob-sakai 723a04d0cf fix: crash occurs when too many vertices are rendered 2023-08-18 11:47:11 +09:00
mob-sakai 52f2ef1f24 fix: fix typos 2023-08-15 09:40:11 +09:00
Jake O'Connor e92b514624 fix: remove unnecessary per-frame allocation. 2023-08-15 09:40:11 +09:00
AndreevWezom a4bcf93022 feat: add particle system getter and setter for attractor
close #253
2023-08-15 09:40:11 +09:00
46 changed files with 2151 additions and 1817 deletions

View File

@ -10,26 +10,39 @@ assignees: mob-sakai
NOTE: Your issue may already be reported! Please search on the [issue tracker](../) before creating one.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- Version [e.g. 1.0.0]
- Platform: [e.g. Editor(Windows/Mac), Standalone(Windows/Mac), iOS, Android, WebGL]
- Unity version: [e.g. 2018.2.8f1]
- Build options: [e.g. IL2CPP, .Net 4.x, LWRP]
**Environment (please complete the following)**
- Package version [e.g. 4.2.0, 3.3.14]
- Platform: [e.g. Editor(Windows/Mac), Standalone(Windows/Mac), iOS, Android, WebGL]
- Unity version: [e.g. 2021.3.7f1]
- Project options: [e.g. Mono/IL2CPP, .Net Standard 2.1/.Net 4.x, URP/HDRP, GraphicAPIs]
**Additional context**
Add any other context about the problem here.
Please add any other context about the issue here.
It will help us resolve the issue.
- Error messages and crash dump
- Screenshots, gif animations and movie files
- Reproducible minimal project
- The entire project (zipped Assets, Packages, ProjectSettings) is preferred over `.unitypackage`

View File

@ -10,13 +10,17 @@ assignees: mob-sakai
NOTE: Your issue may already be reported! Please search on the [issue tracker](../) before creating one.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the issue is. (e.g. I'm always frustrated when [...])
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -13,9 +13,9 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: cycjimmy/semantic-release-action@v2
- uses: cycjimmy/semantic-release-action@v3
with:
extra_plugins: |
@semantic-release/changelog

View File

@ -36,7 +36,7 @@ jobs:
steps:
# Checkout sandbox project
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: sandbox
submodules: true
@ -48,7 +48,7 @@ jobs:
run: git checkout ${{ github.sha }}
# Cache
- uses: actions/cache@v2
- uses: actions/cache@v3
with:
path: Library
key: Library-${{ matrix.version }}-${{ github.sha }}

View File

@ -1,3 +1,43 @@
## [4.2.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/4.2.0...4.2.1) (2023-08-18)
### Bug Fixes
* autoScaling and PositionMode may be locked ([3f2f12d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/3f2f12d2cf7541118c02830ec9fdea8183357487))
# [4.2.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/4.1.7...4.2.0) (2023-08-18)
### Bug Fixes
* assertion 'ps->array_size()' in UpdateMesh() when using trails of type ribbon ([f75fcce](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f75fcce0dae0bc166bd01d36a150aded1fd721f3)), closes [#241](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/241)
* built-in shaders are no longer supported ([c2119c1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/c2119c171a1262431eac7fea6bf3125db2bcaaca)), closes [#233](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/233) [#257](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/257)
* crash occurs when too many vertices are rendered ([723a04d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/723a04d0cfd156715a3c92b6d6bd75fdc1862c28))
* error: SerializedObject target has been destroyed ([e930516](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e93051603e121732c92bcd89ded48087c2b0d7fb)), closes [#267](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/267)
* excessive particle emitted on move ParticleSystem for local space simulation and emission over distance ([2fe0bde](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/2fe0bde422f9769dfedaf6b053ea07f773646679)), closes [#265](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/265)
* fix typos ([52f2ef1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/52f2ef1f2471a2e1c29fca96255c04b222d9c848))
* generated GameObject will be named '[generated] *' ([9b2e5c1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/9b2e5c1d1024e091de6d18a4578cd18b43563e48))
* inactive ParticleSystems are removed from the list on refresh ([4851a18](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/4851a1880eef9f385dd9db644ea7e544f95da4fc))
* mesh sharing not working ([8b4ca1a](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/8b4ca1add5c409601e840253e1c0dbcdbf536da8)), closes [#236](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/236)
* nullptr exceptions when using nested UIParticle components in hierarchy ([e67e948](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e67e9482e2cb840b16e2cfe76e04f7423fcbd3a3)), closes [#246](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/246)
* nullReferenceException after copy-n-paste ([425aad0](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/425aad0cbab475635c72bee84ecbf3f2acedccc2)), closes [#258](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/258)
* remove unnecessary per-frame allocation. ([e92b514](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e92b514624cc362e53ddeae5ade20fa732f94c7c))
* scaling ParticleSystem puts prewarmed particles in wrong location ([fb31db4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/fb31db47f2debb3aadbdc4d1b88d0efd9c4ad7bd)), closes [#235](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/235)
* sub-emitters option is not work in editor playing ([b308b26](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b308b2683372662bb834b6f6d23ea3435a68d1b3)), closes [#231](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/231)
* the camera under UIParticle will be assigned as _orthoCamera ([c42f8c8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/c42f8c8ab0ff033689349a81e02a4808e071a8a2))
* UIParticleAttractor attracts the particles at wrong position when in RelativeMode ([68d9925](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/68d9925a16237df3c7b07b4781172cbd03425421)), closes [#262](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/262)
### Features
* 'AbsoluteMode' option is renamed to 'PositionMode' ([67eff61](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/67eff610736344ba0122163ff5ee63b25c43f555))
* 'AutoScaling' option will be imported from 'IgnoreCanvasScale' (for v3.x) ([4103041](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/410304125f3f25f3f543c7bc01dcc661eab00609))
* add 'AutoScaling' option for UIParticle ([35325c8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/35325c88996fa6aea19a6dd1395c05884e1f84ae))
* add 'UpdateMode' option for UIParticleAttractor ([903f702](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/903f702d7be38228841a5a693e3afdceb4a59d9f)), closes [#250](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/250)
* add particle system getter and setter for attractor ([a4bcf93](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a4bcf93022d2729f3d2a74a2cac4f52e68641b18)), closes [#253](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/253)
* add public properties for UIParticleAttractor ([392ab6d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/392ab6dd76c36e815320d3a50744d19faa631260)), closes [#253](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/253)
* add Start/StopEmission API for UIParticle ([e499836](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e4998365c9825fa385e0a317768ce073a1f15b48)), closes [#240](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/240)
## [4.1.7](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/4.1.6...4.1.7) (2022-08-30)

View File

@ -1,4 +1,4 @@
Copyright 2018-2022 mob-sakai
Copyright 2018-2023 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:

112
README.md
View File

@ -4,14 +4,12 @@ Particle Effect For UGUI (UI Particle)
This plugin provide a component to render particle effect for uGUI in Unity 2018.2 or later.
The particle rendering is maskable and sortable, without Camera, RenderTexture or Canvas.
![](https://img.shields.io/badge/Editor-2018.2+-4796F2?logo=unity)
[![](https://img.shields.io/npm/v/com.coffee.ui-particle?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.coffee.ui-particle/)
[![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI?include_prereleases)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases)
[![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI?include_prereleases&sort=semver)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases)
[![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI?include_prereleases&sort=semver&filter=3.%2A&label=release(3.x))](https://github.com/mob-sakai/ParticleEffectForUGUI/releases)
[![](https://img.shields.io/github/release-date/mob-sakai/ParticleEffectForUGUI.svg)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases) [![](https://img.shields.io/github/license/mob-sakai/ParticleEffectForUGUI.svg)](https://github.com/mob-sakai/ParticleEffectForUGUI/blob/master/LICENSE.txt)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](http://makeapullrequest.com)
![](https://img.shields.io/badge/Unity%202018.2+-supported-blue.svg)
![](https://img.shields.io/badge/Unity%202019.x-supported-blue.svg)
![](https://img.shields.io/badge/Unity%202020.x-supported-blue.svg)
![](https://img.shields.io/badge/Universal%20Rendering%20Pipeline-supported-blue.svg)
<< [Description](#Description) | [Demo](#demo) | [Installation](#installation) | [Usage](#usage) | [Development Note](#development-note) | [Change log](https://github.com/mob-sakai/ParticleEffectForUGUI/blob/upm/CHANGELOG.md) >>
@ -58,6 +56,8 @@ Compares this "Baking mesh" approach with the conventional approach:
* Support CanvasGroup alpha
* No allocations
* Support overlay, camera space and world space
* Support Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP)
* Support disabling `Enter Play Mode Options > Reload Domain`
* Support changing material property with AnimationClip (AnimatableProperty)
![](https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif)
* [4.0.0+] Support 8+ materials
@ -92,39 +92,41 @@ Compares this "Baking mesh" approach with the conventional approach:
### Requirement
![](https://img.shields.io/badge/Unity%202018.2+-supported-blue.svg)
![](https://img.shields.io/badge/Unity%202019.x-supported-blue.svg)
![](https://img.shields.io/badge/Unity%202020.x-supported-blue.svg)
![](https://img.shields.io/badge/Universal%20Rendering%20Pipeline-supported-blue.svg)
Unity 2018.2 or later
### Using OpenUPM
### Install via OpenUPM
This package is available on [OpenUPM](https://openupm.com).
You can install it via [openupm-cli](https://github.com/openupm/openupm-cli).
It's recommended to install it via [openupm-cli](https://github.com/openupm/openupm-cli).
```
openupm add com.coffee.ui-particle
```
### Using Git
### Install via Package Manager Window (using Git URL)
Find the manifest.json file in the Packages folder of your project and add a line to `dependencies` field.
1. Select `Window/Package Manager` menu to open `Package Manager` window.
2. Click `+` and `Install package from git URL...`
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/57317aa4-e55c-4568-b2aa-2ee2a78ddc02)
3. Input `https://github.com/mob-sakai/ParticleEffectForUGUI.git` and click `Install`
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/72cc38b5-cb32-4c85-8209-c85f7bb931ea)
* `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git"`
To update the package, change suffix `#{version}` to the target version.
* `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.0.0",`
Or, use [UpmGitExtension](https://github.com/mob-sakai/UpmGitExtension) to install and update the package.
### For Unity 2018.2
### Install via manifest.json (using Git URL)
Unity 2018.2 supports embedded packages.
1. Open `Packages/manifest.json` in your project.
2. Add this line below `"dependencies": {`
* `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git",`
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/fe1cad9c-8d09-4e17-b6f5-9eac0d267921)
3. To update the package, change suffix `#{version}`.
* `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.2.0",`
### Install as an embed package
1. Download a source code zip file from [Releases](https://github.com/mob-sakai/ParticleEffectForUGUI/releases) page
2. Extract it
3. Import it under `Packages` directory in your Unity project
2. Extract it and place it under `Packages` directory in your project.
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/4c3d3439-5bb0-4e87-a917-ebe328ef89a8)
<br><br><br><br>
@ -161,17 +163,17 @@ Unity 2018.2 supports embedded packages.
`UIParticle` controls the ParticleSystems that is attached to its own game objects and child game objects.
![](https://user-images.githubusercontent.com/12690315/174413976-691eb38e-7f92-4fbe-9790-8771b9dc70b2.png)
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/3559df45-63e7-4c4c-9233-f455779efa29)
| Properties | Description |
| -- | -- |
| Maskable | Does this graphic allow masking. |
| Ignore Canvas Scale | Ignore the scale of the root canvas.<br>This prevents it from displaying small even in hierarchy scaling mode of `ParticleSystem`. |
| Scale | Scale the rendering.<br>When the `3D` toggle is enabled, 3D scale (x,y,z) is supported. |
| Animatable Properties | If you want update material properties (e.g. `_MainTex_ST`, `_Color`) in AnimationClip, use this to mark the changes. |
| Mesh Sharing | Particle simulation results are shared within the same group.<br>A large number of the same effects can be displayed with a small load.<br>When the `Random` toggle is enabled, it will be grouped randomaly. |
| Absolute Mode | The particles will be emitted at the ParticleSystem position.<br>Move the UIParticle/ParticleSystem to move the particle. |
| Rendering Order | The ParticleSystems to be rendered.<br>You can change the rendering order and the materials. |
| Position Mode | **Absolute:** Emit from the world position of the `ParticleSystem`.<br>**Relative:** Emit from the scaled position of the `ParticleSystem`. |
| Auto Scaling | Transform.lossyScale (=world scale) will be set to `(1, 1, 1)` on update.<br>It prevents the root-Canvas scale from affecting the hierarchy-scaled `ParticleSystem`. |
| Rendering Order | The ParticleSystem list to be rendered.<br>You can change the order and the materials. |
NOTE: Press `Refresh` button to reconstruct rendering order based on children ParticleSystem's sorting order and z position.
@ -227,7 +229,7 @@ uiParticle.Stop();
`UIParticleAttractor` attracts particles generated by the specified ParticleSystem.
![](https://user-images.githubusercontent.com/12690315/174413982-b31c358a-8e1d-4b3e-a6d8-18b050b25d6f.png)
![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/ea6ae0ed-f9a8-437c-8baa-47526303391e)
![](https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif)
| Properties | Description |
@ -237,22 +239,66 @@ uiParticle.Stop();
| Delay Rate | Delay to start attracting.<br>It is a percentage of the particle's start lifetime. |
| Max Speed | Maximum speed of attracting.<br> If this value is too small, attracting may not be completed by the end of the lifetime and `OnAttracted` may not be called. |
| Movement | Attracting movement type. (Linear, Smooth, Sphere) |
| Update Mode | **Normal:** Update with scaled delta time.<br>**Unscaled Time:** Update with unscaled delta time. |
| OnAttracted | An event called when attracting is complete (per particle). |
<br><br><br><br>
## Development Note
### FAQ: Why are my particle effects not displayed correctly?
If `ParticleSystem` alone displays particles correctly but `UIParticle` does not, please check the following points:
* [Shader Limitation](https://github.com/mob-sakai/ParticleEffectForUGUI#shader-limitation)
* Most cases can be solved by using `UI/Additive` or `UI/Default`.
* Particles are masked
* `UIParticle` is maskable.
* Set `Mask` or `RectMask2D` component properly.
* Particles are too small
* If particles are small enough, they will not appear on the screen.
* Increase the `Scale` value.
* If you don't want to change the apparent size depending on the resolution, try `Auto Scaling` option.
* Particles are too many
* No more than 65535 vertices can be displayed (for mesh combination limitations).
* Please set `Emission` module and `Max Particles` of ParticleSystem properly.
* Particles are emitted off-screen.
* When `Position Mode = Relative`, particles are emitted from scaled position of the ParticleSystem, not from the screen point of the ParticleSystem.
* Place the ParticleSystem in the proper position or try `Position Mode = Absolute`.
* Attaching `UIParticle` to the same object as `ParticleSystem`
* `Transform.localScale` will be overridden by `Auto Scaling` option.
* It is recommended to place `ParticleSystem` under `UIParticle`.
<br>
### Shader Limitation
UIParticles are based on UIVertex.
Therefore, only xy components is available for each UV in the shader. (zw components will be ignored).
So unfortunately UIParticles will not work well with some shaders.
When using custom vertex streams, you can fill zw components with "unnecessary" data.
https://github.com/mob-sakai/ParticleEffectForUGUI/issues/191
The use of UI shaders is recommended.
- If you need a simple Additive shader, use the `UI/Additive` shader instead.
- If you need a simple alpha-blend shader, use the `UI/Default` shader instead.
- If your custom shader does not work properly with UIParticle, consider creating a custom UI shader.
#### Built-in shaders are not supported
UIParticle does not support all built-in shaders except for `UI/Default`.
If their use is detected, an error is displayed in the inspector.
Use UI shaders instead.
#### UV.zw components will be discarded
UIParticleRenderer renders the particles based on UIVertex.
Therefore, only xy components is available for each UV in the shader. (zw components will be discarded).
So unfortunately UIParticles will not work well with some shaders.
#### Custom vertex streams
When using custom vertex streams, you can fill zw components with "unnecessary" data.
https://github.com/mob-sakai/ParticleEffectForUGUI/issues/191
<br>
### Overheads

View File

@ -1,33 +1,51 @@
using System;
using System.Linq;
using System.Reflection;
using Coffee.UIExtensions;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Object = UnityEngine.Object;
public class CFX_Demo_With_UIParticle : MonoBehaviour
namespace Coffee.UIExtensions.Demo
{
private UIParticle UiParticle;
private Toggle spawnOnUI;
private MonoBehaviour demo;
public class CFX_Demo_With_UIParticle : MonoBehaviour
{
private MonoBehaviour _demo;
private Toggle _spawnOnUI;
private UIParticle _uiParticle;
// Start is called before the first frame update
private void Start()
{
UiParticle = GetComponentInChildren<UIParticle>();
spawnOnUI = GetComponentInChildren<Toggle>();
demo = FindObjectOfType("CFX_Demo_New") as MonoBehaviour
_uiParticle = GetComponentInChildren<UIParticle>();
_spawnOnUI = GetComponentInChildren<Toggle>();
_demo = FindObjectOfType("CFX_Demo_New") as MonoBehaviour
?? FindObjectOfType("WFX_Demo_New") as MonoBehaviour;
SetCanvasWidth(800);
SetCanvasRenderOverlay(true);
}
private Object FindObjectOfType(string typeName)
// Update is called once per frame
private void Update()
{
if (!_spawnOnUI.isOn || !_demo || !Input.GetMouseButtonDown(0)) return;
foreach (Transform child in _uiParticle.transform)
{
Destroy(child.gameObject);
}
var particle = _demo.GetType()
.GetMethod("spawnParticle", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
?.Invoke(_demo, Array.Empty<object>()) as GameObject;
if (!particle) return;
particle.transform.localScale = Vector3.one;
_uiParticle.SetParticleSystemInstance(particle);
}
private static Object FindObjectOfType(string typeName)
{
var type = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
@ -36,23 +54,6 @@ public class CFX_Demo_With_UIParticle : MonoBehaviour
return type == null ? null : FindObjectOfType(type);
}
// Update is called once per frame
private void Update()
{
if (!spawnOnUI.isOn || !demo || !Input.GetMouseButtonDown(0)) return;
foreach (Transform child in UiParticle.transform)
{
Destroy(child.gameObject);
}
var particle = demo.GetType()
.GetMethod("spawnParticle", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Invoke(demo, new object[0]) as GameObject;
particle.transform.localScale = Vector3.one;
UiParticle.SetParticleSystemInstance(particle);
}
public void SetCanvasWidth(int width)
{
var scaler = GetComponentInParent<CanvasScaler>();
@ -82,4 +83,5 @@ public class CFX_Demo_With_UIParticle : MonoBehaviour
{
SceneManager.LoadScene(scene);
}
}
}

View File

@ -1,22 +1,28 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions.Demo
{
public class CopyItemOnStart : MonoBehaviour
{
public GameObject origin;
public int count;
[FormerlySerializedAs("origin")]
[SerializeField]
private GameObject m_Origin;
[FormerlySerializedAs("count")]
[SerializeField]
private int m_Count;
private void Start()
{
if (!origin) return;
origin.SetActive(false);
if (!m_Origin) return;
m_Origin.SetActive(false);
var parent = origin.transform.parent;
for (var i = 0; i < count; i++)
var parent = m_Origin.transform.parent;
for (var i = 0; i < m_Count; i++)
{
var go = Instantiate(origin, parent, false);
go.name = string.Format("{0} {1}", origin.name, i + 1);
var go = Instantiate(m_Origin, parent, false);
go.name = $"{m_Origin.name} {i + 1}";
go.hideFlags = HideFlags.DontSave;
go.SetActive(true);
}

View File

@ -1,25 +1,31 @@
using UnityEngine;
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
public class UIElementDragger : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public enum Target
{
Self,
Parent,
Custom,
}
[SerializeField]
private Target m_Target;
private RectTransform rectTransform;
private Canvas canvas;
public Target m_Target;
public Transform m_CustomTarget;
public bool ex2;
[SerializeField]
private Transform m_CustomTarget;
[FormerlySerializedAs("ex2")]
[SerializeField]
private bool m_UseCanvasScale;
private Canvas _canvas;
private RectTransform _rectTransform;
private void OnEnable()
{
rectTransform = GetComponent<RectTransform>();
canvas = GetComponentInParent<Canvas>();
_rectTransform = GetComponent<RectTransform>();
_canvas = GetComponentInParent<Canvas>();
}
public void OnBeginDrag(PointerEventData eventData)
{
}
public void OnDrag(PointerEventData eventData)
@ -29,25 +35,25 @@ public class UIElementDragger : MonoBehaviour, IBeginDragHandler, IDragHandler,
switch (m_Target)
{
case Target.Self:
rectTransform.localPosition += delta;
_rectTransform.localPosition += delta;
break;
case Target.Parent:
rectTransform.parent.localPosition += delta;
_rectTransform.parent.localPosition += delta;
break;
case Target.Custom:
rectTransform.localPosition += delta;
_rectTransform.localPosition += delta;
if (m_CustomTarget)
{
if (ex2)
delta.Scale(canvas.rootCanvas.transform.localScale);
m_CustomTarget.localPosition += delta;
}
break;
}
if (m_UseCanvasScale)
{
delta.Scale(_canvas.rootCanvas.transform.localScale);
}
public void OnBeginDrag(PointerEventData eventData)
{
m_CustomTarget.localPosition += delta;
}
break;
}
}
public void OnEndDrag(PointerEventData eventData)
@ -56,7 +62,7 @@ public class UIElementDragger : MonoBehaviour, IBeginDragHandler, IDragHandler,
private Vector2 GetLocalDelta(Vector2 evDelta)
{
switch (canvas.renderMode)
switch (_canvas.renderMode)
{
case RenderMode.ScreenSpaceOverlay:
{
@ -65,21 +71,23 @@ public class UIElementDragger : MonoBehaviour, IBeginDragHandler, IDragHandler,
return delta - zero;
}
case RenderMode.ScreenSpaceCamera:
{
Vector2 zero, delta;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, Vector2.zero, canvas.worldCamera, out zero);
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, evDelta, canvas.worldCamera, out delta);
return delta - zero;
}
case RenderMode.WorldSpace:
{
Vector3 zero, delta;
RectTransformUtility.ScreenPointToWorldPointInRectangle(rectTransform, Vector2.zero, canvas.worldCamera, out zero);
RectTransformUtility.ScreenPointToWorldPointInRectangle(rectTransform, evDelta, canvas.worldCamera, out delta);
RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTransform, Vector2.zero,
_canvas.worldCamera, out var zero);
RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTransform, evDelta,
_canvas.worldCamera, out var delta);
return delta - zero;
}
default:
throw new System.NotSupportedException();
throw new NotSupportedException();
}
}
private enum Target
{
Self,
Parent,
Custom
}
}

View File

@ -1,14 +1,18 @@
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Coffee.UIExtensions.Demo
{
public class UIParticle_Demo : MonoBehaviour
{
public Canvas root;
[FormerlySerializedAs("root")]
[SerializeField]
private Canvas m_RootCanvas;
private int _width;
private int _height;
private int _score;
private int _width;
private void Start()
{
@ -24,11 +28,18 @@ namespace Coffee.UIExtensions.Demo
case RuntimePlatform.WindowsPlayer:
case RuntimePlatform.LinuxPlayer:
if (Screen.width == _width && Screen.height == _height)
{
Screen.SetResolution(_height, _width, Screen.fullScreen);
}
else if (Screen.width == _height && Screen.height == _width)
{
Screen.SetResolution(Mathf.Min(_width, _height), Mathf.Min(_width, _height), Screen.fullScreen);
}
else
{
Screen.SetResolution(_width, _height, Screen.fullScreen);
}
break;
}
}
@ -38,27 +49,31 @@ namespace Coffee.UIExtensions.Demo
Screen.fullScreen = !Screen.fullScreen;
}
public void EnableAnimations(bool enabled)
public void EnableAnimations(bool flag)
{
foreach (var animator in FindObjectsOfType<Animator>())
{
animator.enabled = enabled;
animator.enabled = flag;
}
}
public void UIParticle_MeshSharing(bool enabled)
public void UIParticle_MeshSharing(bool flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootCanvas.GetComponentsInChildren<UIParticle>(true))
{
uip.meshSharing = enabled ? UIParticle.MeshSharing.Auto : UIParticle.MeshSharing.None;
uip.meshSharing = flag
? UIParticle.MeshSharing.Auto
: UIParticle.MeshSharing.None;
}
}
public void UIParticle_RandomGroup(bool enabled)
public void UIParticle_RandomGroup(bool flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootCanvas.GetComponentsInChildren<UIParticle>(true))
{
uip.groupMaxId = enabled ? 4 : 0;
uip.groupMaxId = flag
? 4
: 0;
}
}
@ -70,38 +85,40 @@ namespace Coffee.UIExtensions.Demo
}
}
public void ParticleSystem_WorldSpaseSimulation(bool enabled)
public void ParticleSystem_WorldSpaseSimulation(bool flag)
{
foreach (var ps in FindObjectsOfType<ParticleSystem>())
foreach (var p in FindObjectsOfType<ParticleSystem>())
{
var main = ps.main;
main.simulationSpace = enabled ? ParticleSystemSimulationSpace.World : ParticleSystemSimulationSpace.Local;
var main = p.main;
main.simulationSpace = flag
? ParticleSystemSimulationSpace.World
: ParticleSystemSimulationSpace.Local;
}
}
public void ParticleSystem_WorldSpaseSimulation(ParticleSystem particleSystem)
public void ParticleSystem_WorldSpaseSimulation(ParticleSystem ps)
{
foreach (var ps in particleSystem.GetComponentsInChildren<ParticleSystem>())
foreach (var p in ps.GetComponentsInChildren<ParticleSystem>())
{
var main = ps.main;
var main = p.main;
main.simulationSpace = ParticleSystemSimulationSpace.World;
ps.Clear();
p.Clear();
}
}
public void ParticleSystem_LocalSpaseSimulation(ParticleSystem particleSystem)
public void ParticleSystem_LocalSpaseSimulation(ParticleSystem ps)
{
foreach (var ps in particleSystem.GetComponentsInChildren<ParticleSystem>())
foreach (var p in ps.GetComponentsInChildren<ParticleSystem>())
{
var main = ps.main;
var main = p.main;
main.simulationSpace = ParticleSystemSimulationSpace.Local;
ps.Clear();
p.Clear();
}
}
public void ParticleSystem_Emit(ParticleSystem particleSystem)
public void ParticleSystem_Emit(ParticleSystem ps)
{
particleSystem.Emit(5);
ps.Emit(5);
}
public void ParticleSystem_SetScale(float scale)
@ -127,41 +144,37 @@ namespace Coffee.UIExtensions.Demo
attractor.movement = UIParticleAttractor.Movement.Sphere;
}
int score = 0;
public void UIParticleAttractor_OnAttract(Text scoreText)
{
score++;
scoreText.text = score.ToString();
_score++;
scoreText.text = _score.ToString();
scoreText.GetComponent<Animator>().Play(0);
}
public void Canvas_WorldSpace(bool flag)
{
if (flag)
{
if (!flag) return;
var canvas = FindObjectOfType<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.renderMode = RenderMode.WorldSpace;
canvas.transform.rotation = Quaternion.Euler(new Vector3(0, 10, 0));
}
}
public void Canvas_CameraSpace(bool flag)
{
if (flag)
{
if (!flag) return;
var canvas = FindObjectOfType<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceCamera;
}
}
public void Canvas_Overlay(bool flag)
{
if (flag)
{
if (!flag) return;
var canvas = FindObjectOfType<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
}
}
}
}

View File

@ -1,30 +1,37 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions.Demo
{
public class UIParticle_Demo_UIParticleController : MonoBehaviour
{
public Transform root;
[FormerlySerializedAs("root")]
[SerializeField]
private Transform m_RootTransform;
public void UIParticle_MeshSharing(bool enabled)
public void UIParticle_MeshSharing(bool flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootTransform.GetComponentsInChildren<UIParticle>(true))
{
uip.meshSharing = enabled ? UIParticle.MeshSharing.Auto : UIParticle.MeshSharing.None;
uip.meshSharing = flag
? UIParticle.MeshSharing.Auto
: UIParticle.MeshSharing.None;
}
}
public void UIParticle_RandomGroup(bool enabled)
public void UIParticle_RandomGroup(bool flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootTransform.GetComponentsInChildren<UIParticle>(true))
{
uip.groupMaxId = enabled ? 4 : 0;
uip.groupMaxId = flag
? 4
: 0;
}
}
public void UIParticle_Scale(float scale)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootTransform.GetComponentsInChildren<UIParticle>(true))
{
uip.scale = scale;
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 70069a8db1df148ae8b4c8108c079acf
guid: d07e6c2670f164cf7939ab011061a9bf
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
@ -8,9 +8,9 @@ TrueTypeFontImporter:
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontName: Falstin
fontName: Share Tech Mono
fontNames:
- Falstin
- Share Tech Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 1

View File

@ -127,7 +127,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -206,7 +206,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -285,7 +285,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -366,7 +366,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -516,7 +516,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -595,7 +595,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -736,7 +736,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 16
m_FontStyle: 0
m_BestFit: 0
@ -889,7 +889,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 16
m_FontStyle: 0
m_BestFit: 0
@ -1042,7 +1042,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
@ -1206,7 +1206,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 16
m_FontStyle: 0
m_BestFit: 0
@ -1418,7 +1418,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Interval: 0.5
m_Precision: 2
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_Opened: 1
m_FoldoutObject: {fileID: 8720459857733317289}
m_OpenButton: {fileID: 2149219299373497733}
@ -1502,7 +1502,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 16
m_FontStyle: 0
m_BestFit: 0
@ -1695,7 +1695,7 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0

View File

@ -1,10 +1,11 @@
using System;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Serialization;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
#endif
namespace Coffee.NanoMonitor
@ -13,24 +14,29 @@ namespace Coffee.NanoMonitor
[CustomEditor(typeof(NanoMonitor))]
internal class NanoMonitorEditor : Editor
{
private UnityEditorInternal.ReorderableList m_MonitorItemList;
private ReorderableList _monitorItemList;
private void OnEnable()
{
var items = serializedObject.FindProperty("m_CustomMonitorItems");
m_MonitorItemList = new UnityEditorInternal.ReorderableList(serializedObject, items)
_monitorItemList = new ReorderableList(serializedObject, items)
{
draggable = false,
drawHeaderCallback = r => { EditorGUI.LabelField(r, new GUIContent("Custom Monitor Items")); },
drawHeaderCallback = r =>
{
EditorGUI.LabelField(r, new GUIContent("Custom Monitor Items"));
},
drawElementCallback = (r, i, _, __) =>
{
EditorGUI.LabelField(new Rect(r.x, r.y, r.width, r.height - 2), GUIContent.none, EditorStyles.textArea);
EditorGUI.LabelField(new Rect(r.x, r.y, r.width, r.height - 2), GUIContent.none,
EditorStyles.textArea);
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 80;
EditorGUI.PropertyField(new Rect(r.x + 2, r.y + 3, r.width - 4, r.height - 4), items.GetArrayElementAtIndex(i), true);
EditorGUI.PropertyField(new Rect(r.x + 2, r.y + 3, r.width - 4, r.height - 4),
items.GetArrayElementAtIndex(i), true);
EditorGUIUtility.labelWidth = labelWidth;
},
elementHeightCallback = i => EditorGUI.GetPropertyHeight(items.GetArrayElementAtIndex(i)) + 6,
elementHeightCallback = i => EditorGUI.GetPropertyHeight(items.GetArrayElementAtIndex(i)) + 6
};
}
@ -38,7 +44,7 @@ namespace Coffee.NanoMonitor
{
base.OnInspectorGUI();
m_MonitorItemList.DoLayoutList();
_monitorItemList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
@ -52,38 +58,50 @@ namespace Coffee.NanoMonitor
// Serialize Members.
//################################
// Settings
[Header("Settings")] [SerializeField] [Range(0.01f, 2f)]
[Header("Settings")]
[SerializeField]
[Range(0.01f, 2f)]
private float m_Interval = 1f;
[SerializeField] [Range(0, 3)] private int m_Precision = 2;
[SerializeField] private Font m_Font;
[SerializeField]
[Range(0, 3)]
private int m_Precision = 2;
[SerializeField]
private Font m_Font;
// Foldout
[Header("Foldout")] [SerializeField] private bool m_Opened = false;
[Header("Foldout")]
[SerializeField]
private bool m_Opened;
[FormerlySerializedAs("m_Collapse")] [SerializeField]
private GameObject m_FoldoutObject = null;
[FormerlySerializedAs("m_Collapse")]
[SerializeField]
private GameObject m_FoldoutObject;
[SerializeField] private Button m_OpenButton = null;
[SerializeField] private Button m_CloseButton = null;
[SerializeField]
private Button m_OpenButton;
[SerializeField]
private Button m_CloseButton;
// View
[Header("View")] [SerializeField] private MonitorUI m_Fps = null;
[SerializeField] private MonitorUI m_Gc = null;
[SerializeField] private MonitorUI m_MonoUsage = null;
[SerializeField] private MonitorUI m_UnityUsage = null;
[Header("View")]
[SerializeField]
private MonitorUI m_Fps;
[HideInInspector] [SerializeField] private CustomMonitorItem[] m_CustomMonitorItems = new CustomMonitorItem[0];
[SerializeField]
private MonitorUI m_Gc;
[SerializeField]
private MonitorUI m_MonoUsage;
//################################
// Public Members.
//################################
public void Clean()
{
Resources.UnloadUnusedAssets();
GC.Collect();
}
[SerializeField]
private MonitorUI m_UnityUsage;
[HideInInspector]
[SerializeField]
private CustomMonitorItem[] m_CustomMonitorItems = new CustomMonitorItem[0];
//################################
@ -93,44 +111,75 @@ namespace Coffee.NanoMonitor
private double _fpsElapsed;
private int _frames;
private void Open()
private void Update()
{
_frames++;
_elapsed += Time.unscaledDeltaTime;
_fpsElapsed += Time.unscaledDeltaTime;
if (_elapsed < m_Interval) return;
if (m_Fps)
{
m_Fps.SetText("FPS: {0}", (int)(_frames / _fpsElapsed));
}
if (m_Gc)
{
m_Gc.SetText("GC: {0}", GC.CollectionCount(0));
}
if (m_MonoUsage)
{
var monoUsed = (Profiler.GetMonoUsedSizeLong() >> 10) / 1024f;
var monoTotal = (Profiler.GetMonoHeapSizeLong() >> 10) / 1024f;
if (m_Precision == 3)
{
m_MonoUsage.SetText("Mono: {0:N3}/{1:N3}MB", monoUsed, monoTotal);
}
else if (m_Precision == 2)
{
m_MonoUsage.SetText("Mono: {0:N2}/{1:N2}MB", monoUsed, monoTotal);
}
else if (m_Precision == 1)
{
m_MonoUsage.SetText("Mono: {0:N1}/{1:N1}MB", monoUsed, monoTotal);
}
else
{
m_MonoUsage.SetText("Mono: {0:N0}/{1:N0}MB", monoUsed, monoTotal);
}
}
if (m_UnityUsage)
{
var unityUsed = (Profiler.GetTotalAllocatedMemoryLong() >> 10) / 1024f;
var unityTotal = (Profiler.GetTotalReservedMemoryLong() >> 10) / 1024f;
if (m_Precision == 3)
{
m_UnityUsage.SetText("Unity: {0:N3}/{1:N3}MB", unityUsed, unityTotal);
}
else if (m_Precision == 2)
{
m_UnityUsage.SetText("Unity: {0:N2}/{1:N2}MB", unityUsed, unityTotal);
}
else if (m_Precision == 1)
{
m_UnityUsage.SetText("Unity: {0:N1}/{1:N1}MB", unityUsed, unityTotal);
}
else
{
m_UnityUsage.SetText("Unity: {0:N0}/{1:N0}MB", unityUsed, unityTotal);
}
}
foreach (var item in m_CustomMonitorItems)
{
item.UpdateText();
}
_frames = 0;
_elapsed = m_Interval;
_elapsed %= m_Interval;
_fpsElapsed = 0;
if (m_FoldoutObject)
{
m_FoldoutObject.SetActive(true);
}
if (m_CloseButton)
{
m_CloseButton.gameObject.SetActive(true);
}
if (m_OpenButton)
{
m_OpenButton.gameObject.SetActive(false);
}
}
private void Close()
{
if (m_FoldoutObject)
{
m_FoldoutObject.SetActive(false);
}
if (m_CloseButton)
{
m_CloseButton.gameObject.SetActive(false);
}
if (m_OpenButton)
{
m_OpenButton.gameObject.SetActive(true);
}
}
@ -172,77 +221,6 @@ namespace Coffee.NanoMonitor
}
}
private void Update()
{
_frames++;
_elapsed += Time.unscaledDeltaTime;
_fpsElapsed += Time.unscaledDeltaTime;
if (_elapsed < m_Interval) return;
if (m_Fps)
{
m_Fps.SetText("FPS: {0}", (int) (_frames / _fpsElapsed));
}
if (m_Gc)
{
m_Gc.SetText("GC: {0}", GC.CollectionCount(0));
}
if (m_MonoUsage)
{
var monoUsed = (UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong() >> 10) / 1024f;
var monoTotal = (UnityEngine.Profiling.Profiler.GetMonoHeapSizeLong() >> 10) / 1024f;
if (m_Precision == 3)
{
m_MonoUsage.SetText("Mono: {0:N3}/{1:N3}MB", monoUsed, monoTotal);
}
else if (m_Precision == 2)
{
m_MonoUsage.SetText("Mono: {0:N2}/{1:N2}MB", monoUsed, monoTotal);
}
else if (m_Precision == 1)
{
m_MonoUsage.SetText("Mono: {0:N1}/{1:N1}MB", monoUsed, monoTotal);
}
else
{
m_MonoUsage.SetText("Mono: {0:N0}/{1:N0}MB", monoUsed, monoTotal);
}
}
if (m_UnityUsage)
{
var unityUsed = (UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() >> 10) / 1024f;
var unityTotal = (UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong() >> 10) / 1024f;
if (m_Precision == 3)
{
m_UnityUsage.SetText("Unity: {0:N3}/{1:N3}MB", unityUsed, unityTotal);
}
else if (m_Precision == 2)
{
m_UnityUsage.SetText("Unity: {0:N2}/{1:N2}MB", unityUsed, unityTotal);
}
else if (m_Precision == 1)
{
m_UnityUsage.SetText("Unity: {0:N1}/{1:N1}MB", unityUsed, unityTotal);
}
else
{
m_UnityUsage.SetText("Unity: {0:N0}/{1:N0}MB", unityUsed, unityTotal);
}
}
foreach (var item in m_CustomMonitorItems)
{
item.UpdateText();
}
_frames = 0;
_elapsed %= m_Interval;
_fpsElapsed = 0;
}
#if UNITY_EDITOR
private void OnValidate()
{
@ -264,5 +242,55 @@ namespace Coffee.NanoMonitor
}
}
#endif
//################################
// Public Members.
//################################
public void Clean()
{
Resources.UnloadUnusedAssets();
GC.Collect();
}
private void Open()
{
_frames = 0;
_elapsed = m_Interval;
_fpsElapsed = 0;
if (m_FoldoutObject)
{
m_FoldoutObject.SetActive(true);
}
if (m_CloseButton)
{
m_CloseButton.gameObject.SetActive(true);
}
if (m_OpenButton)
{
m_OpenButton.gameObject.SetActive(false);
}
}
private void Close()
{
if (m_FoldoutObject)
{
m_FoldoutObject.SetActive(false);
}
if (m_CloseButton)
{
m_CloseButton.gameObject.SetActive(false);
}
if (m_OpenButton)
{
m_OpenButton.gameObject.SetActive(true);
}
}
}
}

View File

@ -2,14 +2,17 @@
using UnityEngine;
using UnityEngine.UI;
namespace Coffee.NanoMonitor{
namespace Coffee.NanoMonitor
{
public class FixedFont
{
private readonly UIVertex[] _tmpVerts = new UIVertex[4];
private static readonly Dictionary<Font, FixedFont> _fonts = new Dictionary<Font, FixedFont>();
private static readonly TextGenerator _textGenerator = new TextGenerator(100);
private const string k_Characters =
"_!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
private static TextGenerationSettings _settings = new TextGenerationSettings
private static readonly Dictionary<Font, FixedFont> s_Fonts = new Dictionary<Font, FixedFont>();
private static readonly TextGenerator s_TextGenerator = new TextGenerator(100);
private static TextGenerationSettings s_Settings = new TextGenerationSettings
{
scaleFactor = 1,
horizontalOverflow = HorizontalWrapMode.Overflow,
@ -19,25 +22,33 @@ namespace Coffee.NanoMonitor{
color = Color.white
};
private int _resolution = 0;
private readonly Font _font;
private UIVertex[] _verts;
private readonly UIVertex[] _tmpVerts = new UIVertex[4];
private UICharInfo[] _charInfos;
private int _resolution;
private UIVertex[] _verts;
private FixedFont(Font font)
{
_font = font;
}
public int fontSize => _font.dynamic ? 32 : _font.fontSize;
public int fontSize
{
get
{
return _font.dynamic ? 32 : _font.fontSize;
}
}
public static FixedFont GetOrCreate(Font font)
{
if (font == null) return null;
if (_fonts.TryGetValue(font, out var data)) return data;
if (s_Fonts.TryGetValue(font, out var data)) return data;
data = new FixedFont(font);
_fonts.Add(font, data);
s_Fonts.Add(font, data);
return data;
}
@ -61,18 +72,20 @@ namespace Coffee.NanoMonitor{
if (_resolution == currentResolution) return;
_resolution = currentResolution;
_settings.font = _font;
_textGenerator.Invalidate();
_textGenerator.Populate("_!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", _settings);
s_Settings.font = _font;
s_TextGenerator.Invalidate();
s_TextGenerator.Populate(k_Characters, s_Settings);
_verts = _textGenerator.GetVerticesArray();
_charInfos = _textGenerator.GetCharactersArray();
_verts = s_TextGenerator.GetVerticesArray();
_charInfos = s_TextGenerator.GetCharactersArray();
float offsetX = 0;
for (var i = 0; i < _verts.Length; i++)
{
if ((i & 3) == 0)
{
offsetX = _verts[i].position.x;
}
var v = _verts[i];
v.position -= new Vector3(offsetX, 0);
@ -118,18 +131,27 @@ namespace Coffee.NanoMonitor{
for (var i = 0; i < 4; i++)
{
_tmpVerts[i] = new UIVertex();
_tmpVerts[i].uv0 = uv;
_tmpVerts[i].color = color;
_tmpVerts[i] = new UIVertex
{
uv0 = uv,
color = color
};
if (i == 0)
switch (i)
{
case 0:
_tmpVerts[i].position = new Vector2(-size.x, -size.y) + offset;
else if (i == 1)
break;
case 1:
_tmpVerts[i].position = new Vector2(-size.x, size.y) + offset;
else if (i == 2)
break;
case 2:
_tmpVerts[i].position = new Vector2(size.x, size.y) + offset;
else
break;
case 3:
_tmpVerts[i].position = new Vector2(size.x, -size.y) + offset;
break;
}
}
toFill.AddUIVertexQuad(_tmpVerts);

View File

@ -1,50 +1,46 @@
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
namespace Coffee.NanoMonitor{
namespace Coffee.NanoMonitor
{
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(MonitorUI))]
public class MonitorTextEditor : Editor
{
private SerializedProperty m_Mode;
private SerializedProperty m_TextAnchor;
private SerializedProperty m_FontSize;
private SerializedProperty m_Font;
private SerializedProperty m_Text;
private SerializedProperty m_Color;
private SerializedProperty _color;
private SerializedProperty _font;
private SerializedProperty _fontSize;
private SerializedProperty _mode;
private SerializedProperty _text;
private SerializedProperty _textAnchor;
private void OnEnable()
{
m_Mode = serializedObject.FindProperty("m_Mode");
m_Text = serializedObject.FindProperty("m_Text");
m_Color = serializedObject.FindProperty("m_Color");
_mode = serializedObject.FindProperty("m_Mode");
_text = serializedObject.FindProperty("m_Text");
_color = serializedObject.FindProperty("m_Color");
var fontData = serializedObject.FindProperty("m_FontData");
m_FontSize = fontData.FindPropertyRelative("m_FontSize");
m_Font = fontData.FindPropertyRelative("m_Font");
m_TextAnchor = serializedObject.FindProperty("m_TextAnchor");
_fontSize = fontData.FindPropertyRelative("m_FontSize");
_font = fontData.FindPropertyRelative("m_Font");
_textAnchor = serializedObject.FindProperty("m_TextAnchor");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_Mode);
if ((MonitorUI.Mode) m_Mode.intValue == MonitorUI.Mode.Text)
EditorGUILayout.PropertyField(_mode);
if ((MonitorUI.Mode)_mode.intValue == MonitorUI.Mode.Text)
{
EditorGUILayout.PropertyField(m_Text);
EditorGUILayout.PropertyField(m_FontSize);
EditorGUILayout.PropertyField(m_TextAnchor);
//EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(m_Font);
//EditorGUI.EndDisabledGroup();
EditorGUILayout.PropertyField(_text);
EditorGUILayout.PropertyField(_fontSize);
EditorGUILayout.PropertyField(_textAnchor);
EditorGUILayout.PropertyField(_font);
}
EditorGUILayout.PropertyField(m_Color);
EditorGUILayout.PropertyField(_color);
serializedObject.ApplyModifiedProperties();
}
@ -56,7 +52,7 @@ namespace Coffee.NanoMonitor{
public enum Mode
{
Text,
Fill,
Fill
}
public enum TextAnchor
@ -74,26 +70,38 @@ namespace Coffee.NanoMonitor{
[SerializeField] private TextAnchor m_TextAnchor;
//################################
// Private Members.
//################################
private readonly StringBuilder _sb = new StringBuilder(64);
//################################
// Public Members.
//################################
public override string text
{
get => m_StringBuilder.ToString();
get
{
return _sb.ToString();
}
set
{
m_Text = value;
if (m_StringBuilder.IsEqual(m_Text)) return;
if (_sb.IsEqual(m_Text)) return;
m_StringBuilder.Length = 0;
m_StringBuilder.Append(m_Text);
_sb.Length = 0;
_sb.Append(m_Text);
SetVerticesDirty();
}
}
public TextAnchor textAnchor
{
get => m_TextAnchor;
get
{
return m_TextAnchor;
}
set
{
if (m_TextAnchor == value) return;
@ -105,65 +113,30 @@ namespace Coffee.NanoMonitor{
public override bool raycastTarget
{
get => m_Mode == Mode.Fill;
get
{
return m_Mode == Mode.Fill;
}
set { }
}
public void SetText(string format, double arg0 = 0, double arg1 = 0, double arg2 = 0, double arg3 = 0)
{
m_StringBuilder.Length = 0;
m_StringBuilder.AppendFormatNoAlloc(format, arg0, arg1, arg2, arg3);
SetVerticesDirty();
}
public void SetText(StringBuilder builder)
{
m_StringBuilder.Length = 0;
m_StringBuilder.Append(builder);
SetVerticesDirty();
}
//################################
// Private Members.
//################################
private readonly StringBuilder m_StringBuilder = new StringBuilder(64);
private void UpdateFont()
{
//var globalFont = NNanoMonitorttings.Instance.font;
//if (globalFont)
//{
// font = globalFont;
//}
var fontData = FixedFont.GetOrCreate(font);
if (fontData != null)
{
fontData.Invalidate();
fontData.UpdateFont();
}
}
//################################
// Unity Callbacks.
//################################
protected override void OnEnable()
{
//NaNanoMonitortings.Instance.onFontChanged += UpdateFont;
RegisterDirtyMaterialCallback(UpdateFont);
base.OnEnable();
raycastTarget = false;
maskable = false;
m_StringBuilder.Length = 0;
m_StringBuilder.Append(m_Text);
_sb.Length = 0;
_sb.Append(m_Text);
}
protected override void OnDisable()
{
//NanNanoMonitorings.Instance.onFontChanged -= UpdateFont;
UnregisterDirtyMaterialCallback(UpdateFont);
base.OnDisable();
@ -174,14 +147,38 @@ namespace Coffee.NanoMonitor{
{
base.OnValidate();
if (!m_StringBuilder.IsEqual(m_Text))
if (!_sb.IsEqual(m_Text))
{
m_StringBuilder.Length = 0;
m_StringBuilder.Append(m_Text);
_sb.Length = 0;
_sb.Append(m_Text);
}
}
#endif
public void SetText(string format, double arg0 = 0, double arg1 = 0, double arg2 = 0, double arg3 = 0)
{
_sb.Length = 0;
_sb.AppendFormatNoAlloc(format, arg0, arg1, arg2, arg3);
SetVerticesDirty();
}
public void SetText(StringBuilder builder)
{
_sb.Length = 0;
_sb.Append(builder);
SetVerticesDirty();
}
private void UpdateFont()
{
var fontData = FixedFont.GetOrCreate(font);
if (fontData != null)
{
fontData.Invalidate();
fontData.UpdateFont();
}
}
protected override void OnPopulateMesh(VertexHelper toFill)
{
toFill.Clear();
@ -197,7 +194,7 @@ namespace Coffee.NanoMonitor{
return;
}
var scale = (float) fontSize / fontData.fontSize;
var scale = (float)fontSize / fontData.fontSize;
float offset = 0;
switch (textAnchor)
{
@ -205,20 +202,26 @@ namespace Coffee.NanoMonitor{
offset = rectTransform.rect.xMin;
break;
case TextAnchor.Center:
for (var i = 0; i < m_StringBuilder.Length; i++)
offset = fontData.Layout(m_StringBuilder[i], offset, scale);
for (var i = 0; i < _sb.Length; i++)
{
offset = fontData.Layout(_sb[i], offset, scale);
}
offset = -offset / 2;
break;
case TextAnchor.Right:
for (var i = 0; i < m_StringBuilder.Length; i++)
offset = fontData.Layout(m_StringBuilder[i], offset, scale);
for (var i = 0; i < _sb.Length; i++)
{
offset = fontData.Layout(_sb[i], offset, scale);
}
offset = rectTransform.rect.xMax - offset;
break;
}
for (var i = 0; i < m_StringBuilder.Length; i++)
for (var i = 0; i < _sb.Length; i++)
{
offset = fontData.Append(toFill, m_StringBuilder[i], offset, scale, color);
offset = fontData.Append(toFill, _sb[i], offset, scale, color);
}
}

View File

@ -1,55 +0,0 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Coffee.MiniProfiler
{
internal class MiniProfilerSettings : ScriptableSettings<MiniProfilerSettings>
{
[Header("UI")]
[SerializeField] private Font m_Font = null;
[Header("Instantiate On Boot")]
[SerializeField] private GameObject miniProfilerPrefab = null;
[SerializeField] private string[] m_SceneNames = new string[0];
public Font font => m_Font;
public event Action onFontChanged;
protected override void OnBoot()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
}
public void OnSceneLoaded(Scene scene, LoadSceneMode __)
{
SceneManager.sceneLoaded -= OnSceneLoaded;
if (!miniProfilerPrefab || !m_SceneNames.Contains(scene.name)) return;
var go = Instantiate(miniProfilerPrefab);
if (Application.isPlaying)
DontDestroyOnLoad(go);
}
protected override bool bootOnPlayMode => true;
protected override bool bootOnEditorMode => false;
#if UNITY_EDITOR
protected override void OnValidate()
{
onFontChanged?.Invoke();
base.OnValidate();
}
private void Reset()
{
m_Font = UnityEngine.Resources.GetBuiltinResource<Font>("Arial.ttf");
}
[UnityEditor.SettingsProvider]
private static UnityEditor.SettingsProvider CreateSettingsProvider() => new ScriptableSettingsProvider<MiniProfilerSettings>();
#endif
}
}

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 647dad4f8226a49b9874b68b79413d64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- miniProfilerPrefab: {fileID: 7211429669315726685, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,280 +0,0 @@
using UnityEngine;
using System;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Coffee.MiniProfiler
{
internal abstract class ScriptableSettings : ScriptableObject
{
#if UNITY_EDITOR
public virtual string GetSettingPath()
{
return GetSettingPath(GetType());
}
internal static string GetSettingPath(Type type)
{
return $"Project/{ObjectNames.NicifyVariableName(type.Name.Replace("ProjectSettings", ""))}";
}
#endif
public virtual string GetDefaultAssetPath()
{
return $"Assets/ProjectSettings/{GetType().Name}.asset";
}
}
internal abstract class ScriptableSettings<T> : ScriptableSettings
where T : ScriptableSettings<T>
{
//################################
// Public Members.
//################################
public static T Instance => _instance ? _instance : _instance = GetOrCreate();
private static T _instance;
protected virtual bool bootOnEditorMode => true;
protected virtual bool bootOnPlayMode => true;
protected abstract void OnBoot();
#if UNITY_EDITOR
//################################
// Private Members.
//################################
private bool enabled;
private static T GetOrCreate()
{
return PlayerSettings.GetPreloadedAssets()
.OfType<T>()
.FirstOrDefault() ?? CreateInstance<T>();
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
switch (state)
{
case PlayModeStateChange.EnteredEditMode:
enabled = true;
Boot();
break;
case PlayModeStateChange.ExitingEditMode:
case PlayModeStateChange.ExitingPlayMode:
enabled = false;
break;
}
}
private void Boot()
{
var preloadedAssets = PlayerSettings.GetPreloadedAssets();
var assets = preloadedAssets.OfType<T>().ToArray();
var first = assets.FirstOrDefault() ?? this as T;
// If there are no preloaded assets, registry the first asset.
// If there are multiple preloaded assets, unregisters all but the first one.
if (assets.Length != 1)
{
// The first asset is not saved.
if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(first)))
{
if (!AssetDatabase.IsValidFolder("Assets/ProjectSettings"))
AssetDatabase.CreateFolder("Assets", "ProjectSettings");
var assetName = ObjectNames.NicifyVariableName(first.GetType().Name.Replace("ProjectSettings", ""));
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/ProjectSettings/{assetName}.asset");
AssetDatabase.CreateAsset(first, assetPath);
}
PlayerSettings.SetPreloadedAssets(preloadedAssets
.Where(x => x)
.Except(assets)
.Concat(new[] {first})
.ToArray());
}
// Another asset is registered as a preload asset.
if (first != this)
{
UnityEngine.Debug.LogError($"Another asset '{first}' is registered as a preload '{typeof(T).Name}' asset." +
$"\nThis instance ('{this}') will be ignored. Check 'Project Settings > Player > Preload Assets'", this);
return;
}
_instance = first;
// Do nothing on editor mode.
if (!bootOnEditorMode && !EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
// Do nothing on play mode.
if (!bootOnPlayMode && EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
OnBoot();
}
//################################
// Unity Callbacks.
//################################
private void OnEnable()
{
enabled = true;
Boot();
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
private void OnDisable()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
protected virtual void OnValidate()
{
if (enabled)
Boot();
}
#else
private static T GetOrCreate()
{
return CreateInstance<T>();
}
private void OnEnable()
{
if (!bootOnPlayMode) return;
_instance = this as T;
OnBoot();
}
#endif
}
[CustomEditor(typeof(ScriptablePreferenceSettings<>), true)]
internal class ScriptablePreferenceSettingsEditor : Editor
{
private static readonly GUIContent _button = new GUIContent("Open Project Settings");
public override void OnInspectorGUI()
{
if (GUILayout.Button(_button))
{
SettingsService.OpenProjectSettings(((ScriptableSettings) target).GetSettingPath());
}
}
}
internal abstract class ScriptablePreferenceSettings<T> : ScriptableSingleton<T>
where T : ScriptablePreferenceSettings<T>
{
}
#if UNITY_EDITOR
[CustomEditor(typeof(ScriptableSettings), true)]
internal class ScriptableSettingsEditor : Editor
{
private static readonly GUIContent _button = new GUIContent("Open Project Settings");
public override void OnInspectorGUI()
{
if (GUILayout.Button(_button))
{
SettingsService.OpenProjectSettings(((ScriptableSettings) target).GetSettingPath());
}
}
}
internal class ScriptableSettingsProvider<T> : SettingsProvider where T : ScriptableSettings<T>
{
public ScriptableSettingsProvider(string path) : base(path)
{
}
public ScriptableSettingsProvider() : base(ScriptableSettings.GetSettingPath(typeof(T)))
{
}
protected SerializedObject serializedObject { get; private set; }
protected ScriptableSettings<T> target { get; private set; }
public sealed override void OnGUI(string searchContext)
{
if (!target)
{
target = ScriptableSettings<T>.Instance;
serializedObject = new SerializedObject(target);
}
OnGUI();
}
protected virtual void OnGUI()
{
serializedObject.UpdateIfRequiredOrScript();
var iterator = serializedObject.GetIterator();
var enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
if (iterator.propertyPath != "m_Script")
EditorGUILayout.PropertyField(iterator, true);
enterChildren = false;
}
serializedObject.ApplyModifiedProperties();
}
}
internal class ScriptablePreferenceSettingsProvider<T> : SettingsProvider where T : ScriptableSettings<T>
{
public ScriptablePreferenceSettingsProvider(string path) : base(path, SettingsScope.User)
{
}
public ScriptablePreferenceSettingsProvider() : base(ScriptableSettings.GetSettingPath(typeof(T)), SettingsScope.User)
{
}
protected SerializedObject serializedObject { get; private set; }
protected ScriptableSettings<T> target { get; private set; }
public sealed override void OnGUI(string searchContext)
{
if (!target)
{
target = ScriptableSettings<T>.Instance;
serializedObject = new SerializedObject(target);
}
OnGUI();
}
protected virtual void OnGUI()
{
serializedObject.UpdateIfRequiredOrScript();
var iterator = serializedObject.GetIterator();
var enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
if (iterator.propertyPath != "m_Script")
EditorGUILayout.PropertyField(iterator, true);
enterChildren = false;
}
serializedObject.ApplyModifiedProperties();
}
}
#endif
}

View File

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

View File

@ -1,4 +1,5 @@
using UnityEngine;
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
@ -11,12 +12,17 @@ namespace Coffee.NanoMonitor
{
public override void OnGUI(Rect p, SerializedProperty property, GUIContent label)
{
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 0, p.width, 16), property.FindPropertyRelative("m_Text"));
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 1, p.width, 16), property.FindPropertyRelative("m_Format"));
var pos = new Rect(p.x, p.y + 18 * 0, p.width, 16);
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Text"));
pos.y += 18;
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Format"));
pos.y += 18;
EditorGUI.indentLevel++;
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 2, p.width, 16), property.FindPropertyRelative("m_Arg0"));
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 3, p.width, 16), property.FindPropertyRelative("m_Arg1"));
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 4, p.width, 16), property.FindPropertyRelative("m_Arg2"));
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Arg0"));
pos.y += 18;
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Arg1"));
pos.y += 18;
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Arg2"));
EditorGUI.indentLevel--;
property.serializedObject.ApplyModifiedProperties();
@ -29,19 +35,21 @@ namespace Coffee.NanoMonitor
}
#endif
[System.Serializable]
[Serializable]
public class CustomMonitorItem
{
[SerializeField] private MonitorUI m_Text = null;
[SerializeField] private MonitorUI m_Text;
[SerializeField] private string m_Format = "";
[SerializeField] private NumericProperty m_Arg0 = null;
[SerializeField] private NumericProperty m_Arg1 = null;
[SerializeField] private NumericProperty m_Arg2 = null;
[SerializeField] private NumericProperty m_Arg0;
[SerializeField] private NumericProperty m_Arg1;
[SerializeField] private NumericProperty m_Arg2;
public void UpdateText()
{
if (m_Text)
{
m_Text.SetText(m_Format, m_Arg0.Get(), m_Arg1.Get(), m_Arg2.Get());
}
}
}
}

View File

@ -2,12 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
namespace Coffee.NanoMonitor
{
@ -20,39 +16,45 @@ namespace Coffee.NanoMonitor
private static readonly Dictionary<Type, string> s_SupportedTypes = new Dictionary<Type, string>
{
{typeof(bool), "bool"},
{typeof(sbyte), "sbyte"},
{typeof(short), "short"},
{typeof(int), "int"},
{typeof(long), "long"},
{typeof(byte), "byte"},
{typeof(ushort), "ushort"},
{typeof(uint), "uint"},
{typeof(ulong), "ulong"},
{typeof(float), "float"},
{typeof(double), "double"},
{typeof(decimal), "decimal"},
{ typeof(bool), "bool" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(byte), "byte" },
{ typeof(ushort), "ushort" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(float), "float" },
{ typeof(double), "double" },
{ typeof(decimal), "decimal" }
};
private static void Init()
{
if (s_PropertyMenu != null) return;
const BindingFlags bindingFlags = BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Static
| BindingFlags.GetProperty;
var properties = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.SelectMany(type => type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty))
.SelectMany(type => type.GetProperties(bindingFlags))
.Where(pi => pi.GetMethod != null && s_SupportedTypes.ContainsKey(pi.PropertyType))
.OrderBy(pi => ConvertToMenuItem(pi, false))
.ToArray();
s_PropertyMenu = new GenericMenu();
s_PropertyMenu.AddItem(new GUIContent("No Property"), false, arg => s_OnMenuSelected?.Invoke(arg as PropertyInfo), null);
s_PropertyMenu.AddItem(new GUIContent("(Non Public Properties)/"), false, ()=>{});
s_PropertyMenu.AddItem(new GUIContent("No Property"), false,
arg => s_OnMenuSelected?.Invoke(arg as PropertyInfo), null);
s_PropertyMenu.AddItem(new GUIContent("(Non Public Properties)/"), false, () => { });
s_PropertyMenu.AddSeparator("");
foreach (var pi in properties)
{
s_PropertyMenu.AddItem(new GUIContent(ConvertToMenuItem(pi, true)), false, arg => s_OnMenuSelected?.Invoke(arg as PropertyInfo), pi);
s_PropertyMenu.AddItem(new GUIContent(ConvertToMenuItem(pi, true)), false,
arg => s_OnMenuSelected?.Invoke(arg as PropertyInfo), pi);
}
}
@ -60,11 +62,17 @@ namespace Coffee.NanoMonitor
{
var type = p.DeclaringType;
if (type == null) return "";
var category = p.GetMethod.IsPublic && type.IsPublic ? "" : "(Non Public Properties)/";
var category = p.GetMethod.IsPublic && type.IsPublic
? ""
: "(Non Public Properties)/";
var typeName = type.FullName;
var asmName = type.Assembly.GetName().Name;
if (asmName == "UnityEngine.CoreModule")
{
asmName = "UnityEngine";
}
return propertyType
? $"{category}{asmName}/{typeName}/{s_SupportedTypes[p.PropertyType]} {p.Name}"
: $"{category}{asmName}/{typeName}/{p.Name}";
@ -84,7 +92,9 @@ namespace Coffee.NanoMonitor
{
s_OnMenuSelected = p =>
{
path.stringValue = p == null ? "" : $"{p.DeclaringType?.FullName}, {p.DeclaringType?.Assembly.GetName().Name};{p.Name}";
path.stringValue = p == null
? ""
: $"{p.DeclaringType?.FullName}, {p.DeclaringType?.Assembly.GetName().Name};{p.Name}";
property.serializedObject.ApplyModifiedProperties();
};
s_PropertyMenu.DropDown(position);
@ -104,6 +114,22 @@ namespace Coffee.NanoMonitor
[SerializeField] private string m_Path = "";
//################################
// Private Members.
//################################
private Func<double> _get;
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
var pInfo = GetPropertyInfo(m_Path);
_get = CreateFunc(pInfo?.GetMethod);
}
//################################
// Public Members.
//################################
@ -112,12 +138,6 @@ namespace Coffee.NanoMonitor
return _get?.Invoke() ?? -1;
}
//################################
// Private Members.
//################################
private Func<double> _get = null;
private static PropertyInfo GetPropertyInfo(string path)
{
var p = path.Split(';');
@ -126,14 +146,14 @@ namespace Coffee.NanoMonitor
var type = Type.GetType(p[0]);
if (type == null)
{
UnityEngine.Debug.LogException(new Exception($"Type '{p[0]}' is not found"));
Debug.LogException(new Exception($"Type '{p[0]}' is not found"));
return null;
}
var pInfo = type.GetProperty(p[1], BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Static);
if (pInfo == null)
{
UnityEngine.Debug.LogException(new Exception($"Member '{p[1]}' is not found in type '{type}'"));
Debug.LogException(new Exception($"Member '{p[1]}' is not found in type '{type}'"));
}
return pInfo;
@ -145,55 +165,72 @@ namespace Coffee.NanoMonitor
switch (Type.GetTypeCode(mInfo.ReturnType))
{
case TypeCode.Boolean:
var f_bool = (Func<bool>) mInfo.CreateDelegate(typeof(Func<bool>));
return () => f_bool() ? 1 : 0;
{
var func = (Func<bool>)mInfo.CreateDelegate(typeof(Func<bool>));
return () => func() ? 1 : 0;
}
case TypeCode.Byte:
var f_byte = (Func<byte>) mInfo.CreateDelegate(typeof(Func<byte>));
return () => f_byte();
{
var func = (Func<byte>)mInfo.CreateDelegate(typeof(Func<byte>));
return () => func();
}
case TypeCode.SByte:
var f_sbyte = (Func<sbyte>) mInfo.CreateDelegate(typeof(Func<sbyte>));
return () => f_sbyte();
{
var func = (Func<sbyte>)mInfo.CreateDelegate(typeof(Func<sbyte>));
return () => func();
}
case TypeCode.UInt16:
var f_ushort = (Func<ushort>) mInfo.CreateDelegate(typeof(Func<ushort>));
return () => f_ushort();
{
var func = (Func<ushort>)mInfo.CreateDelegate(typeof(Func<ushort>));
return () => func();
}
case TypeCode.UInt32:
var f_uint = (Func<uint>) mInfo.CreateDelegate(typeof(Func<uint>));
return () => f_uint();
{
var func = (Func<uint>)mInfo.CreateDelegate(typeof(Func<uint>));
return () => func();
}
case TypeCode.UInt64:
var f_ulong = (Func<ulong>) mInfo.CreateDelegate(typeof(Func<ulong>));
return () => f_ulong();
{
var func = (Func<ulong>)mInfo.CreateDelegate(typeof(Func<ulong>));
return () => func();
}
case TypeCode.Int16:
var f_short = (Func<short>) mInfo.CreateDelegate(typeof(Func<short>));
return () => f_short();
{
var func = (Func<short>)mInfo.CreateDelegate(typeof(Func<short>));
return () => func();
}
case TypeCode.Int32:
var f_int = (Func<int>) mInfo.CreateDelegate(typeof(Func<int>));
return () => f_int();
{
var f = (Func<int>)mInfo.CreateDelegate(typeof(Func<int>));
return () => f();
}
case TypeCode.Int64:
var f_long = (Func<long>) mInfo.CreateDelegate(typeof(Func<long>));
return () => f_long();
{
var f = (Func<long>)mInfo.CreateDelegate(typeof(Func<long>));
return () => f();
}
case TypeCode.Decimal:
var f_decimal = (Func<decimal>) mInfo.CreateDelegate(typeof(Func<decimal>));
return () => (double) f_decimal();
{
var f = (Func<decimal>)mInfo.CreateDelegate(typeof(Func<decimal>));
return () => (double)f();
}
case TypeCode.Double:
var f_double = (Func<double>) mInfo.CreateDelegate(typeof(Func<double>));
return f_double;
{
var f = (Func<double>)mInfo.CreateDelegate(typeof(Func<double>));
return f;
}
case TypeCode.Single:
var f_float = (Func<float>) mInfo.CreateDelegate(typeof(Func<float>));
return () => f_float();
{
var f = (Func<float>)mInfo.CreateDelegate(typeof(Func<float>));
return () => f();
}
default:
UnityEngine.Debug.LogException(new Exception(string.Format("Method '{1}.{0}' is not supported.", mInfo.Name, mInfo.DeclaringType)));
{
var message = $"Method '{mInfo.DeclaringType}.{mInfo.Name} ({mInfo.ReturnType})' is not supported.";
Debug.LogException(new Exception(message));
return null;
}
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
var pInfo = GetPropertyInfo(m_Path);
_get = CreateFunc(pInfo?.GetMethod);
}
}
}

View File

@ -21,7 +21,8 @@ namespace Coffee.NanoMonitor
return true;
}
public static void AppendFormatNoAlloc(this StringBuilder sb, string format, double arg0 = 0, double arg1 = 0, double arg2 = 0, double arg3 = 0)
public static void AppendFormatNoAlloc(this StringBuilder sb, string format, double arg0 = 0, double arg1 = 0,
double arg2 = 0, double arg3 = 0)
{
for (var i = 0; i < format.Length; i++)
{
@ -56,7 +57,7 @@ namespace Coffee.NanoMonitor
}
}
internal static void AppendInteger(this StringBuilder sb, double number, int padding, int precision, int alignment)
private static void AppendInteger(this StringBuilder sb, double number, int padding, int alignment)
{
number = Math.Truncate(number);
var sign = number < 0;
@ -68,8 +69,8 @@ namespace Coffee.NanoMonitor
var n = Math.Truncate(number % 10);
number /= 10;
sb.Append((char) (n + 48));
} while (1 <= number || (sb.Length - startIndex) < padding);
sb.Append((char)(n + 48));
} while (1 <= number || sb.Length - startIndex < padding);
if (sign)
{
@ -82,11 +83,12 @@ namespace Coffee.NanoMonitor
sb.Alignment(alignment, startIndex, endIndex);
}
internal static void AppendDouble(this StringBuilder sb, double number, int padding, int precision, int alignment)
private static void AppendDouble(this StringBuilder sb, double number, int padding, int precision,
int alignment)
{
var integer = Math.Truncate(number);
var startIndex = sb.Length;
sb.AppendInteger(integer, padding, precision, 0);
sb.AppendInteger(integer, padding, 0);
if (0 < precision)
{
@ -96,8 +98,8 @@ namespace Coffee.NanoMonitor
for (var p = 0; p < precision; p++)
{
number *= 10;
integer = (long) number;
sb.Append((char) (integer + 48));
integer = (long)number;
sb.Append((char)(integer + 48));
number -= integer;
number = Math.Round(number, precision);
}
@ -106,20 +108,18 @@ namespace Coffee.NanoMonitor
sb.Alignment(alignment, startIndex, sb.Length - 1);
}
internal static void Reverse(this StringBuilder sb, int start, int end)
private static void Reverse(this StringBuilder sb, int start, int end)
{
while (start < end)
{
var c = sb[start];
sb[start] = sb[end];
sb[end] = c;
// swap
(sb[start], sb[end]) = (sb[end], sb[start]);
start++;
end--;
}
}
internal static void Alignment(this StringBuilder sb, int alignment, int start, int end)
private static void Alignment(this StringBuilder sb, int alignment, int start, int end)
{
if (alignment == 0) return;
@ -129,9 +129,8 @@ namespace Coffee.NanoMonitor
sb.Append(' ', alignment - len);
for (var i = 0; i < len; i++)
{
var c = sb[end - i];
sb[end - i] = sb[start + alignment - i - 1];
sb[start + alignment - i - 1] = c;
// swap
(sb[end - i], sb[start + alignment - i - 1]) = (sb[start + alignment - i - 1], sb[end - i]);
}
}
else if (alignment < 0 && len < -alignment)
@ -140,7 +139,8 @@ namespace Coffee.NanoMonitor
}
}
internal static int GetFormat(string format, int i, out int argIndex, out int padding, out int precision, out int alignment)
private static int GetFormat(string format, int i, out int argIndex, out int padding, out int precision,
out int alignment)
{
argIndex = -1;
padding = 0;

View File

@ -1,26 +1,35 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions.Demo
{
public class UIParticle_PerformanceDemo : MonoBehaviour
{
public GameObject copyOrigin;
public int copyCount;
public Canvas root;
[FormerlySerializedAs("copyOrigin")]
[SerializeField]
private GameObject m_CopyOrigin;
[FormerlySerializedAs("copyCount")]
[SerializeField]
public int m_CopyCount;
[FormerlySerializedAs("root")]
[SerializeField]
public Canvas m_RootCanvas;
private void Start()
{
Application.targetFrameRate = 60;
if (copyOrigin)
if (m_CopyOrigin)
{
copyOrigin.SetActive(false);
m_CopyOrigin.SetActive(false);
var parent = copyOrigin.transform.parent;
for (var i = 0; i < copyCount; i++)
var parent = m_CopyOrigin.transform.parent;
for (var i = 0; i < m_CopyCount; i++)
{
var go = GameObject.Instantiate(copyOrigin, parent, false);
go.name = string.Format("{0} {1}", copyOrigin.name, i + 1);
var go = Instantiate(m_CopyOrigin, parent, false);
go.name = string.Format("{0} {1}", m_CopyOrigin.name, i + 1);
go.hideFlags = HideFlags.DontSave;
go.SetActive(true);
@ -28,32 +37,14 @@ namespace Coffee.UIExtensions.Demo
}
}
public void ChangeScreenSize()
public void UIParticle_Enable(bool flag)
{
if (Screen.width == 400 && Screen.height == 720)
Screen.SetResolution(720, 720, false);
else if (Screen.width == 720 && Screen.height == 720)
Screen.SetResolution(720, 400, false);
else
Screen.SetResolution(400, 720, false);
foreach (var uip in m_RootCanvas.GetComponentsInChildren<UIParticle>(true))
{
uip.enabled = flag;
}
public void EnableAnimations(bool enabled)
{
foreach (var animator in FindObjectsOfType<Animator>())
{
animator.enabled = enabled;
}
}
public void UIParticle_Enable(bool enabled)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
{
uip.enabled = enabled;
}
if (!enabled)
if (!flag)
{
foreach (var ps in FindObjectsOfType<ParticleSystem>())
{
@ -62,36 +53,23 @@ namespace Coffee.UIExtensions.Demo
}
}
public void UIParticle_MeshSharing(bool enabled)
public void UIParticle_MeshSharing(bool flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootCanvas.GetComponentsInChildren<UIParticle>(true))
{
uip.meshSharing = enabled ? UIParticle.MeshSharing.Auto : UIParticle.MeshSharing.None;
uip.meshSharing = flag
? UIParticle.MeshSharing.Auto
: UIParticle.MeshSharing.None;
}
}
public void UIParticle_RandomGroup(bool enabled)
public void UIParticle_RandomGroup(bool flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
foreach (var uip in m_RootCanvas.GetComponentsInChildren<UIParticle>(true))
{
uip.groupMaxId = enabled ? 4 : 0;
}
}
public void UIParticle_Scale(float scale)
{
foreach (var uip in FindObjectsOfType<UIParticle>())
{
uip.scale = scale;
}
}
public void ParticleSystem_WorldSpaseSimulation(bool enabled)
{
foreach (var ps in FindObjectsOfType<ParticleSystem>())
{
var main = ps.main;
main.simulationSpace = enabled ? ParticleSystemSimulationSpace.World : ParticleSystemSimulationSpace.Local;
uip.groupMaxId = flag
? 4
: 0;
}
}
@ -102,34 +80,5 @@ namespace Coffee.UIExtensions.Demo
ps.transform.localScale = new Vector3(scale, scale, scale);
}
}
public void Canvas_WorldSpace(bool flag)
{
if (flag)
{
var canvas = FindObjectOfType<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.renderMode = RenderMode.WorldSpace;
canvas.transform.rotation = Quaternion.Euler(new Vector3(0, 10, 0));
}
}
public void Canvas_CameraSpace(bool flag)
{
if (flag)
{
var canvas = FindObjectOfType<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceCamera;
}
}
public void Canvas_Overlay(bool flag)
{
if (flag)
{
var canvas = FindObjectOfType<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
}
}
}
}

View File

@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.12731704, g: 0.13414726, b: 0.1210787, a: 1}
m_IndirectSpecularColor: {r: 0.12731689, g: 0.13414702, b: 0.1210784, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@ -757,85 +757,6 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &455490117
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 455490120}
- component: {fileID: 455490119}
- component: {fileID: 455490118}
m_Layer: 5
m_Name: Custom 1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &455490118
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 455490117}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b0e08c6080a2e4d8186f97c60518830f, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 70069a8db1df148ae8b4c8108c079acf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 'UIParticle: 888'
m_Mode: 0
m_TextAnchor: 0
--- !u!222 &455490119
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 455490117}
m_CullTransparentMesh: 0
--- !u!224 &455490120
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 455490117}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1002673088}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 251.8711, y: -10}
m_SizeDelta: {x: 247.8711, y: 20}
m_Pivot: {x: 0, y: 0.5}
--- !u!1 &496349153
GameObject:
m_ObjectHideFlags: 0
@ -972,12 +893,12 @@ PrefabInstance:
- target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_AnchoredPosition.x
value: 240.00002
value: 240
objectReference: {fileID: 0}
- target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_AnchoredPosition.y
value: -179.99998
value: -180
objectReference: {fileID: 0}
- target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
@ -1160,6 +1081,85 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 608849065}
m_CullTransparentMesh: 0
--- !u!1 &733597319
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 733597322}
- component: {fileID: 733597321}
- component: {fileID: 733597320}
m_Layer: 5
m_Name: Custom 1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &733597320
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 733597319}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b0e08c6080a2e4d8186f97c60518830f, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: d07e6c2670f164cf7939ab011061a9bf, type: 3}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 'Screen: 8888x8888 Time: 8888.88'
m_Mode: 0
m_TextAnchor: 0
--- !u!222 &733597321
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 733597319}
m_CullTransparentMesh: 0
--- !u!224 &733597322
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 733597319}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1002673088}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 251.8711, y: -10}
m_SizeDelta: {x: 247.8711, y: 20}
m_Pivot: {x: 0, y: 0.5}
--- !u!1 &879285143
GameObject:
m_ObjectHideFlags: 0
@ -1287,9 +1287,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 3a595cf9cf93f4e0686f247a5e7e19ce, type: 3}
m_Name:
m_EditorClassIdentifier:
copyOrigin: {fileID: 1581241706}
copyCount: 266
root: {fileID: 919637305}
m_CopyOrigin: {fileID: 1581241706}
m_CopyCount: 266
m_RootCanvas: {fileID: 919637305}
--- !u!4 &903906985
Transform:
m_ObjectHideFlags: 0
@ -1505,11 +1505,6 @@ PrefabInstance:
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 151878254403844079, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 1002673091}
- target: {fileID: 323826319202443003, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_AnchorMin.y
@ -1530,11 +1525,6 @@ PrefabInstance:
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1087632561348207336, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 1002673092}
- target: {fileID: 1458366075964155596, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_AnchorMin.y
@ -1564,7 +1554,7 @@ PrefabInstance:
type: 3}
propertyPath: m_CustomMonitorItems.Array.data[1].m_Text
value:
objectReference: {fileID: 455490118}
objectReference: {fileID: 733597320}
- target: {fileID: 4567906826058368312, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_CustomMonitorItems.Array.data[1].m_Format
@ -1630,16 +1620,6 @@ PrefabInstance:
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5526675317024915317, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 1002673089}
- target: {fileID: 5526675317129615258, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 1002673090}
- target: {fileID: 5758968653464739090, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_SizeDelta.y
@ -1803,54 +1783,6 @@ RectTransform:
type: 3}
m_PrefabInstance: {fileID: 1002673087}
m_PrefabAsset: {fileID: 0}
--- !u!114 &1002673089 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 5526675317024915314, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
m_PrefabInstance: {fileID: 1002673087}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b0e08c6080a2e4d8186f97c60518830f, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1002673090 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 5526675317129615259, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
m_PrefabInstance: {fileID: 1002673087}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b0e08c6080a2e4d8186f97c60518830f, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1002673091 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 6638879077379268485, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
m_PrefabInstance: {fileID: 1002673087}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b0e08c6080a2e4d8186f97c60518830f, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &1002673092 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 6720252555115459274, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
m_PrefabInstance: {fileID: 1002673087}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b0e08c6080a2e4d8186f97c60518830f, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1023851109
GameObject:
m_ObjectHideFlags: 0
@ -3632,6 +3564,11 @@ PrefabInstance:
m_Modification:
m_TransformParent: {fileID: 1924051224}
m_Modifications:
- target: {fileID: 864895764861262036, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_IgnoreCanvasScaler
value: 1
objectReference: {fileID: 0}
- target: {fileID: 864895765507486433, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_AnchorMax.x
@ -3790,17 +3727,17 @@ PrefabInstance:
- target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_LocalScale.x
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_LocalScale.y
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_LocalScale.z
value: 0
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: a4669dfbba654429bbcddf0c9b79fb8b, type: 3}

View File

@ -1,8 +1,9 @@
using UnityEngine;
using System;
using UnityEngine;
namespace Coffee.UIExtensions
{
[System.Serializable]
[Serializable]
public class AnimatableProperty : ISerializationCallbackReceiver
{
public enum ShaderPropertyType
@ -11,11 +12,11 @@ namespace Coffee.UIExtensions
Vector,
Float,
Range,
Texture,
Texture
}
[SerializeField] string m_Name = "";
[SerializeField] ShaderPropertyType m_Type = ShaderPropertyType.Vector;
[SerializeField] private string m_Name = "";
[SerializeField] private ShaderPropertyType m_Type = ShaderPropertyType.Vector;
public int id { get; private set; }
public ShaderPropertyType type
@ -23,6 +24,15 @@ namespace Coffee.UIExtensions
get { return m_Type; }
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
id = Shader.PropertyToID(m_Name);
}
public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb)
{
if (!material.HasProperty(id)) return;
@ -31,35 +41,38 @@ namespace Coffee.UIExtensions
{
case ShaderPropertyType.Color:
var color = mpb.GetColor(id);
if (color != default(Color))
if (color != default)
{
material.SetColor(id, color);
}
break;
case ShaderPropertyType.Vector:
var vector = mpb.GetVector(id);
if (vector != default(Vector4))
if (vector != default)
{
material.SetVector(id, vector);
}
break;
case ShaderPropertyType.Float:
case ShaderPropertyType.Range:
var value = mpb.GetFloat(id);
if (value != default(float))
if (!Mathf.Approximately(value, 0))
{
material.SetFloat(id, value);
}
break;
case ShaderPropertyType.Texture:
var tex = mpb.GetTexture(id);
if (tex != default(Texture))
{
material.SetTexture(id, tex);
}
break;
}
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
id = Shader.PropertyToID(m_Name);
}
}
}

View File

@ -0,0 +1,143 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace Coffee.UIExtensions
{
internal static class AnimatablePropertyEditor
{
private static readonly GUIContent s_ContentNothing = new GUIContent("Nothing");
private static readonly List<string> s_ActiveNames = new List<string>();
private static readonly StringBuilder s_Sb = new StringBuilder();
private static readonly HashSet<string> s_Names = new HashSet<string>();
private static string CollectActiveNames(SerializedProperty sp, List<string> result)
{
result.Clear();
for (var i = 0; i < sp.arraySize; i++)
{
var spName = sp.GetArrayElementAtIndex(i).FindPropertyRelative("m_Name");
if (spName == null) continue;
result.Add(spName.stringValue);
}
s_Sb.Length = 0;
if (result.Count == 0)
{
s_Sb.Append("Nothing");
}
else
{
result.Aggregate(s_Sb, (a, b) => s_Sb.AppendFormat("{0}, ", b));
s_Sb.Length -= 2;
}
return s_Sb.ToString();
}
public static void Draw(SerializedProperty sp, Material[] mats)
{
bool isClicked;
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false)))
{
var pos = EditorGUILayout.GetControlRect(true);
var label = new GUIContent(sp.displayName, sp.tooltip);
var rect = EditorGUI.PrefixLabel(pos, label);
var text = sp.hasMultipleDifferentValues
? "-"
: CollectActiveNames(sp, s_ActiveNames);
isClicked = GUI.Button(rect, text, EditorStyles.popup);
}
if (!isClicked) return;
var gm = new GenericMenu();
gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, () =>
{
sp.ClearArray();
sp.serializedObject.ApplyModifiedProperties();
});
if (!sp.hasMultipleDifferentValues)
{
for (var i = 0; i < sp.arraySize; i++)
{
var p = sp.GetArrayElementAtIndex(i);
var name = p.FindPropertyRelative("m_Name").stringValue;
var type = (AnimatableProperty.ShaderPropertyType)p.FindPropertyRelative("m_Type").intValue;
AddMenu(gm, sp, new ShaderProperty(name, type), false);
}
}
s_Names.Clear();
for (var j = 0; j < mats.Length; j++)
{
var mat = mats[j];
if (!mat || !mat.shader) continue;
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
{
var name = ShaderUtil.GetPropertyName(mat.shader, i);
var type = (AnimatableProperty.ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i);
if (s_Names.Contains(name)) continue;
s_Names.Add(name);
AddMenu(gm, sp, new ShaderProperty(name, type), true);
if (type != AnimatableProperty.ShaderPropertyType.Texture) continue;
AddMenu(gm, sp, new ShaderProperty($"{name}_ST"), true);
AddMenu(gm, sp, new ShaderProperty($"{name}_HDR"), true);
AddMenu(gm, sp, new ShaderProperty($"{name}_TexelSize"), true);
}
}
gm.ShowAsContext();
}
private static void AddMenu(GenericMenu menu, SerializedProperty sp, ShaderProperty prop, bool add)
{
if (add && s_ActiveNames.Contains(prop.name)) return;
var label = new GUIContent($"{prop.name} ({prop.type})");
menu.AddItem(label, s_ActiveNames.Contains(prop.name), () =>
{
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
{
public readonly string name;
public readonly AnimatableProperty.ShaderPropertyType type;
public ShaderProperty(string name)
{
this.name = name;
type = AnimatableProperty.ShaderPropertyType.Vector;
}
public ShaderProperty(string name, AnimatableProperty.ShaderPropertyType type)
{
this.name = name;
this.type = type;
}
}
}
}

View File

@ -1,123 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using ShaderPropertyType = Coffee.UIExtensions.AnimatableProperty.ShaderPropertyType;
namespace Coffee.UIExtensions
{
internal class AnimatedPropertiesEditor
{
private static readonly List<string> s_ActiveNames = new List<string>();
private static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder();
private static readonly HashSet<string> s_Names = new HashSet<string>();
private string _name;
private ShaderPropertyType _type;
static string CollectActiveNames(SerializedProperty sp, List<string> result)
{
result.Clear();
for (var i = 0; i < sp.arraySize; i++)
{
var spName = sp.GetArrayElementAtIndex(i).FindPropertyRelative("m_Name");
if (spName == null) continue;
result.Add(spName.stringValue);
}
s_Sb.Length = 0;
if (result.Count == 0)
{
s_Sb.Append("Nothing");
}
else
{
result.Aggregate(s_Sb, (a, b) => s_Sb.AppendFormat("{0}, ", b));
s_Sb.Length -= 2;
}
return s_Sb.ToString();
}
public static void DrawAnimatableProperties(SerializedProperty sp, Material[] mats)
{
bool isClicked;
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false)))
{
var r = EditorGUI.PrefixLabel(EditorGUILayout.GetControlRect(true), new GUIContent(sp.displayName, sp.tooltip));
var text = sp.hasMultipleDifferentValues ? "-" : CollectActiveNames(sp, s_ActiveNames);
isClicked = GUI.Button(r, text, EditorStyles.popup);
}
if (!isClicked) return;
var gm = new GenericMenu();
gm.AddItem(new GUIContent("Nothing"), s_ActiveNames.Count == 0, () =>
{
sp.ClearArray();
sp.serializedObject.ApplyModifiedProperties();
});
if (!sp.hasMultipleDifferentValues)
{
for (var i = 0; i < sp.arraySize; i++)
{
var p = sp.GetArrayElementAtIndex(i);
var name = p.FindPropertyRelative("m_Name").stringValue;
var type = (ShaderPropertyType) p.FindPropertyRelative("m_Type").intValue;
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = name, _type = type}, false);
}
}
s_Names.Clear();
foreach (var mat in mats)
{
if (!mat || !mat.shader) continue;
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
{
var pName = ShaderUtil.GetPropertyName(mat.shader, i);
var type = (ShaderPropertyType) ShaderUtil.GetPropertyType(mat.shader, i);
var name = string.Format("{0} ({1})", pName, type);
if (s_Names.Contains(name)) continue;
s_Names.Add(name);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName, _type = type}, true);
if (type != ShaderPropertyType.Texture) continue;
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_ST", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_HDR", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_TexelSize", _type = ShaderPropertyType.Vector}, true);
}
}
gm.ShowAsContext();
}
private static void AddMenu(GenericMenu menu, SerializedProperty sp, AnimatedPropertiesEditor property, bool add)
{
if (add && s_ActiveNames.Contains(property._name)) return;
menu.AddItem(new GUIContent(string.Format("{0} ({1})", property._name, property._type)), s_ActiveNames.Contains(property._name), () =>
{
var index = s_ActiveNames.IndexOf(property._name);
if (0 <= index)
{
sp.DeleteArrayElementAtIndex(index);
}
else
{
sp.InsertArrayElementAtIndex(sp.arraySize);
var p = sp.GetArrayElementAtIndex(sp.arraySize - 1);
p.FindPropertyRelative("m_Name").stringValue = property._name;
p.FindPropertyRelative("m_Type").intValue = (int) property._type;
}
sp.serializedObject.ApplyModifiedProperties();
});
}
}
}

View File

@ -32,11 +32,16 @@ namespace Coffee.UIExtensions
private static void ImportSample(string jsonGuid, string sampleName)
{
var jsonPath = AssetDatabase.GUIDToAssetPath(jsonGuid);
var packageRoot = Path.GetDirectoryName(jsonPath).Replace('\\', '/');
if (string.IsNullOrEmpty(jsonPath)) return;
var jsonDir = Path.GetDirectoryName(jsonPath);
if (string.IsNullOrEmpty(jsonDir)) return;
var packageRoot = jsonDir.Replace('\\', '/');
var json = File.ReadAllText(jsonPath);
var version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
var src = string.Format("{0}/Samples~/{1}", packageRoot, sampleName);
var dst = string.Format("Assets/Samples/{0}/{1}/{2}", k_DisplayName, version, sampleName);
var src = $"{packageRoot}/Samples~/{sampleName}";
var dst = $"Assets/Samples/{k_DisplayName}/{version}/{sampleName}";
var previousPath = GetPreviousSamplePath(k_DisplayName, sampleName);
// Remove the previous sample directory.
@ -46,33 +51,45 @@ namespace Coffee.UIExtensions
+ previousPath
+ "\n\nIt will be deleted when you update. Are you sure you want to continue?";
if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel"))
{
return;
}
FileUtil.DeleteFileOrDirectory(previousPath);
var metaFile = previousPath + ".meta";
if (File.Exists(metaFile))
{
FileUtil.DeleteFileOrDirectory(metaFile);
}
}
if (!Directory.Exists(dst))
{
FileUtil.DeleteFileOrDirectory(dst);
}
var dstDir = Path.GetDirectoryName(dst);
if (!Directory.Exists(dstDir))
if (!string.IsNullOrEmpty(dstDir) && !Directory.Exists(dstDir))
{
Directory.CreateDirectory(dstDir);
}
if (Directory.Exists(src))
{
FileUtil.CopyFileOrDirectory(src, dst);
}
else
{
throw new DirectoryNotFoundException(src);
}
AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
}
private static string GetPreviousSamplePath(string displayName, string sampleName)
{
var sampleRoot = string.Format("Assets/Samples/{0}", displayName);
var sampleRoot = $"Assets/Samples/{displayName}";
var sampleRootInfo = new DirectoryInfo(sampleRoot);
if (!sampleRootInfo.Exists) return null;

View File

@ -1,19 +1,24 @@
#if UNITY_2019_3_11 || UNITY_2019_3_12 || UNITY_2019_3_13 || UNITY_2019_3_14 || UNITY_2019_3_15 || UNITY_2019_4_OR_NEWER
#define SERIALIZE_FIELD_MASKABLE
#endif
using UnityEditor;
using UnityEditor.UI;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEditorInternal;
using UnityEngine.UI;
using System;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.Overlays;
#else
using System.Reflection;
#endif
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#elif UNITY_2018_3_OR_NEWER
using UnityEditor.Experimental.SceneManagement;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Coffee.UIParticleExtensions;
using UnityEditor;
using UnityEditor.UI;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace Coffee.UIExtensions
{
@ -23,7 +28,10 @@ namespace Coffee.UIExtensions
{
#if UNITY_2021_2_OR_NEWER
#if UNITY_2022_1_OR_NEWER
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true, defaultDockPosition = DockPosition.Bottom, defaultDockZone = DockZone.Floating, defaultLayout = Layout.Panel)]
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true,
defaultDockPosition = DockPosition.Bottom,
defaultDockZone = DockZone.Floating,
defaultLayout = Layout.Panel)]
#else
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true)]
#endif
@ -53,24 +61,22 @@ namespace Coffee.UIExtensions
private static readonly GUIContent s_ContentRandom = new GUIContent("Random");
private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
private static SerializedObject s_SerializedObject;
private static bool s_XYZMode;
#if !SERIALIZE_FIELD_MASKABLE
private SerializedProperty m_Maskable;
#endif
private SerializedProperty m_Scale3D;
private SerializedProperty m_AnimatableProperties;
private SerializedProperty m_MeshSharing;
private SerializedProperty m_GroupId;
private SerializedProperty m_GroupMaxId;
private SerializedProperty m_AbsoluteMode;
private SerializedProperty _maskable;
private SerializedProperty _scale3D;
private SerializedProperty _animatableProperties;
private SerializedProperty _meshSharing;
private SerializedProperty _groupId;
private SerializedProperty _groupMaxId;
private SerializedProperty _positionMode;
private SerializedProperty _autoScaling;
private ReorderableList _ro;
static private bool _xyzMode;
private bool _showMax;
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
private static readonly List<ParticleSystemVertexStream> s_Streams = new List<ParticleSystemVertexStream>();
private static readonly List<string> s_MaskablePropertyNames = new List<string>
{
"_Stencil",
@ -78,21 +84,20 @@ namespace Coffee.UIExtensions
"_StencilOp",
"_StencilWriteMask",
"_StencilReadMask",
"_ColorMask",
"_ColorMask"
};
[InitializeOnLoadMethod]
static void Init()
private static void Init()
{
#if !UNITY_2021_2_OR_NEWER
var miSceneViewOverlayWindow = Type.GetType("UnityEditor.SceneViewOverlay, UnityEditor")
.GetMethods(BindingFlags.Public | BindingFlags.Static)
?.GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(x => x.Name == "Window" && 5 <= x.GetParameters().Length);
var windowFunction = (Action<UnityEngine.Object, SceneView>)WindowFunction;
var windowFunction = (Action<Object, SceneView>)WindowFunction;
var windowFunctionType = Type.GetType("UnityEditor.SceneViewOverlay+WindowFunction, UnityEditor");
var windowFunctionDelegate = Delegate.CreateDelegate(windowFunctionType, windowFunction.Method);
var windowTitle = new GUIContent(ObjectNames.NicifyVariableName(typeof(UIParticle).Name));
var windowTitle = new GUIContent(ObjectNames.NicifyVariableName(nameof(UIParticle)));
#if UNITY_2019_2_OR_NEWER
//public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, Object target, WindowDisplayOption option, EditorWindow window = null)
var sceneViewArgs = new object[] { windowTitle, windowFunctionDelegate, 599, null, 2, null };
@ -102,7 +107,7 @@ namespace Coffee.UIExtensions
#endif
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui += _ => { if (s_SerializedObject != null) miSceneViewOverlayWindow.Invoke(null, sceneViewArgs); };
SceneView.duringSceneGui += _ =>
#else
SceneView.onSceneGUIDelegate += _ =>
#endif
@ -114,25 +119,21 @@ namespace Coffee.UIExtensions
};
#endif
Func<SerializedObject> createSerializeObject = () =>
SerializedObject CreateSerializeObject()
{
var uiParticles = Selection.gameObjects
.Select(x => x.GetComponent<ParticleSystem>())
var uiParticles = Selection.gameObjects.Select(x => x.GetComponent<ParticleSystem>())
.Where(x => x)
.Select(x => x.GetComponentInParent<UIParticle>())
.Select(x => x.GetComponentInParent<UIParticle>(true))
.Where(x => x && x.canvas)
.Concat(
Selection.gameObjects
.Select(x => x.GetComponent<UIParticle>())
.Where(x => x && x.canvas)
)
.Concat(Selection.gameObjects.Select(x => x.GetComponent<UIParticle>())
.Where(x => x && x.canvas))
.Distinct()
.ToArray();
return 0 < uiParticles.Length ? new SerializedObject(uiParticles) : null;
};
}
s_SerializedObject = createSerializeObject();
Selection.selectionChanged += () => s_SerializedObject = createSerializeObject();
s_SerializedObject = CreateSerializeObject();
Selection.selectionChanged += () => s_SerializedObject = CreateSerializeObject();
}
//################################
@ -145,19 +146,21 @@ namespace Coffee.UIExtensions
{
base.OnEnable();
m_Maskable = serializedObject.FindProperty("m_Maskable");
m_Scale3D = serializedObject.FindProperty("m_Scale3D");
m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
m_MeshSharing = serializedObject.FindProperty("m_MeshSharing");
m_GroupId = serializedObject.FindProperty("m_GroupId");
m_GroupMaxId = serializedObject.FindProperty("m_GroupMaxId");
m_AbsoluteMode = serializedObject.FindProperty("m_AbsoluteMode");
_maskable = serializedObject.FindProperty("m_Maskable");
_scale3D = serializedObject.FindProperty("m_Scale3D");
_animatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
_meshSharing = serializedObject.FindProperty("m_MeshSharing");
_groupId = serializedObject.FindProperty("m_GroupId");
_groupMaxId = serializedObject.FindProperty("m_GroupMaxId");
_positionMode = serializedObject.FindProperty("m_PositionMode");
_autoScaling = serializedObject.FindProperty("m_AutoScaling");
var sp = serializedObject.FindProperty("m_Particles");
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true);
_ro.elementHeight = (EditorGUIUtility.singleLineHeight * 3) + 4;
_ro.elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2);
_ro.drawElementCallback = (rect, index, _, __) =>
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true)
{
elementHeight = EditorGUIUtility.singleLineHeight * 3 + 4,
elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2),
drawElementCallback = (rect, index, _, __) =>
{
EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues);
rect.y += 1;
@ -179,29 +182,35 @@ namespace Coffee.UIExtensions
{
materials.serializedObject.ApplyModifiedProperties();
}
};
_ro.drawHeaderCallback = rect =>
},
drawHeaderCallback = rect =>
{
#if !UNITY_2019_3_OR_NEWER
rect.y -= 1;
#endif
EditorGUI.LabelField(new Rect(rect.x, rect.y, 150, rect.height), s_ContentRenderingOrder);
var pos = new Rect(rect.x, rect.y, 150, rect.height);
EditorGUI.LabelField(pos, s_ContentRenderingOrder);
if (GUI.Button(new Rect(rect.width - 35, rect.y, 60, rect.height), s_ContentRefresh, EditorStyles.miniButton))
pos = new Rect(rect.width - 35, rect.y, 60, rect.height);
if (GUI.Button(pos, s_ContentRefresh, EditorStyles.miniButton))
{
foreach (UIParticle t in targets)
foreach (var uip in targets.OfType<UIParticle>())
{
t.RefreshParticles();
EditorUtility.SetDirty(t);
uip.RefreshParticles();
EditorUtility.SetDirty(uip);
}
}
}
};
// On select UIParticle, refresh particles.
foreach (UIParticle t in targets)
if (!Application.isPlaying)
{
if (Application.isPlaying || PrefabUtility.GetPrefabAssetType(t) != PrefabAssetType.NotAPrefab) continue;
t.RefreshParticles(t.particles);
foreach (var uip in targets.OfType<UIParticle>())
{
if (PrefabUtility.GetPrefabAssetType(uip) != PrefabAssetType.NotAPrefab) continue;
uip.RefreshParticles(uip.particles);
}
}
}
@ -230,11 +239,11 @@ namespace Coffee.UIExtensions
serializedObject.Update();
// Maskable
EditorGUILayout.PropertyField(m_Maskable);
EditorGUILayout.PropertyField(_maskable);
// Scale
EditorGUI.BeginDisabledGroup(!m_MeshSharing.hasMultipleDifferentValues && m_MeshSharing.intValue == 4);
_xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode);
EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4);
s_XYZMode = DrawFloatOrVector3Field(_scale3D, s_XYZMode);
EditorGUI.EndDisabledGroup();
// AnimatableProperties
@ -244,12 +253,11 @@ namespace Coffee.UIExtensions
.Where(x => x)
.ToArray();
// Animated properties
AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats);
AnimatablePropertyEditor.Draw(_animatableProperties, mats);
// Mesh sharing
EditorGUI.BeginChangeCheck();
_showMax = DrawMeshSharing(m_MeshSharing, m_GroupId, m_GroupMaxId, _showMax);
_showMax = DrawMeshSharing(_meshSharing, _groupId, _groupMaxId, _showMax);
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
@ -259,8 +267,11 @@ namespace Coffee.UIExtensions
}
}
// Absolute Mode
EditorGUILayout.PropertyField(m_AbsoluteMode);
// Position Mode
EditorGUILayout.PropertyField(_positionMode);
// Auto Scaling
DrawAutoScaling(_autoScaling, targets.OfType<UIParticle>());
// Target ParticleSystems.
EditorGUI.BeginChangeCheck();
@ -276,8 +287,22 @@ namespace Coffee.UIExtensions
}
}
// Non-UI built-in shader is not supported.
foreach (var mat in current.materials)
{
if (!mat || !mat.shader) continue;
var shader = mat.shader;
if (IsBuiltInObject(shader) && !shader.name.StartsWith("UI/"))
{
EditorGUILayout.HelpBox(
$"Built-in shader '{shader.name}' in '{mat.name}' is not supported.\n" +
"Use UI shaders instead.",
MessageType.Error);
}
}
// Does the shader support UI masks?
if (current.maskable && current.GetComponentInParent<Mask>())
if (current.maskable && current.GetComponentInParent<Mask>(false))
{
foreach (var mat in current.materials)
{
@ -289,15 +314,20 @@ namespace Coffee.UIExtensions
{
if (mat.HasProperty(propName)) continue;
EditorGUILayout.HelpBox(string.Format("Shader '{0}' doesn't have '{1}' property. This graphic cannot be masked.", shader.name, propName), MessageType.Warning);
EditorGUILayout.HelpBox(
$"Shader '{shader.name}' doesn't have '{propName}' property." +
"\nThis graphic cannot be masked.",
MessageType.Warning);
break;
}
}
}
s_Shaders.Clear();
// UIParticle for trail should be removed.
if (FixButton(current.m_IsTrail, "This UIParticle component should be removed. The UIParticle for trails is no longer needed."))
var label = "This UIParticle component should be removed. The UIParticle for trails is no longer needed.";
if (FixButton(current.m_IsTrail, label))
{
DestroyUIParticle(current);
}
@ -313,7 +343,9 @@ namespace Coffee.UIExtensions
{
var so = new SerializedObject(allPsRenderers);
var sp = so.FindProperty("m_ApplyActiveColorSpace");
if (FixButton(sp.boolValue || sp.hasMultipleDifferentValues, "When using linear color space, the particle colors are not output correctly.\nTo fix, set 'Apply Active Color Space' in renderer module to false."))
label = "When using linear color space, the particle colors are not output correctly.\n" +
"To fix, set 'Apply Active Color Space' in renderer module to false.";
if (FixButton(sp.boolValue || sp.hasMultipleDifferentValues, label))
{
sp.boolValue = false;
so.ApplyModifiedProperties();
@ -328,10 +360,21 @@ namespace Coffee.UIExtensions
if (2 < s_Streams.Select(GetUsedComponentCount).Sum())
{
EditorGUILayout.HelpBox(string.Format("ParticleSystem '{0}' uses 'TEXCOORD*.zw' components as custom vertex stream.\nUIParticle does not support it (See README.md).", psr.name), MessageType.Warning);
EditorGUILayout.HelpBox(
$"ParticleSystem '{psr.name}' uses 'TEXCOORD*.zw' components as custom vertex stream.\n" +
"UIParticle does not support it (See README.md).",
MessageType.Warning);
}
s_Streams.Clear();
}
}
}
private bool IsBuiltInObject(Object obj)
{
return AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _)
&& Regex.IsMatch(guid, "^0{16}.0{15}$", RegexOptions.Compiled);
}
private static int GetUsedComponentCount(ParticleSystemVertexStream s)
@ -389,10 +432,12 @@ namespace Coffee.UIExtensions
case ParticleSystemVertexStream.Custom2XYZW:
return 4;
}
return 3;
}
private static bool DrawMeshSharing(SerializedProperty spMeshSharing, SerializedProperty spGroupId, SerializedProperty spGroupMaxId, bool showMax)
private static bool DrawMeshSharing(SerializedProperty spMeshSharing, SerializedProperty spGroupId,
SerializedProperty spGroupMaxId, bool showMax)
{
showMax |= spGroupId.intValue != spGroupMaxId.intValue ||
spGroupId.hasMultipleDifferentValues ||
@ -404,7 +449,10 @@ namespace Coffee.UIExtensions
EditorGUI.BeginChangeCheck();
showMax = GUILayout.Toggle(showMax, s_ContentRandom, EditorStyles.miniButton, GUILayout.Width(60));
if (EditorGUI.EndChangeCheck() && !showMax)
{
spGroupMaxId.intValue = spGroupId.intValue;
}
EditorGUILayout.EndHorizontal();
EditorGUI.BeginDisabledGroup(spMeshSharing.intValue == 0);
@ -417,20 +465,41 @@ namespace Coffee.UIExtensions
else if (spMeshSharing.intValue == 1 || spMeshSharing.intValue == 4)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Primary", UIParticleUpdater.GetPrimary(spGroupId.intValue), typeof(UIParticle), false);
var obj = UIParticleUpdater.GetPrimary(spGroupId.intValue);
EditorGUILayout.ObjectField("Primary", obj, typeof(UIParticle), false);
EditorGUI.EndDisabledGroup();
}
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
return showMax;
}
private static void WindowFunction(UnityEngine.Object target, SceneView sceneView)
private static void DrawAutoScaling(SerializedProperty prop, IEnumerable<UIParticle> uiParticles)
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(prop);
if (!EditorGUI.EndChangeCheck()) return;
// on changed true->false, reset scale.
EditorApplication.delayCall += () =>
{
foreach (var uip in uiParticles)
{
if (!uip || uip.autoScaling) continue;
uip.transform.localScale = Vector3.one;
}
};
}
private static void WindowFunction(Object target, SceneView sceneView)
{
try
{
if (s_SerializedObject.targetObjects.OfType<UIParticle>().Any(x => !x || !x.canvas)) return;
if (s_SerializedObject == null || !s_SerializedObject.targetObject) return;
var uiParticles = s_SerializedObject.targetObjects.OfType<UIParticle>();
if (uiParticles.Any(x => !x || !x.canvas)) return;
s_SerializedObject.Update();
using (new EditorGUILayout.VerticalScope(GUILayout.Width(220f)))
@ -438,37 +507,36 @@ namespace Coffee.UIExtensions
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 100;
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_Enabled"));
_xyzMode = DrawFloatOrVector3Field(s_SerializedObject.FindProperty("m_Scale3D"), _xyzMode);
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_AbsoluteMode"));
s_XYZMode = DrawFloatOrVector3Field(s_SerializedObject.FindProperty("m_Scale3D"), s_XYZMode);
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_PositionMode"));
DrawAutoScaling(s_SerializedObject.FindProperty("m_AutoScaling"), uiParticles);
EditorGUIUtility.labelWidth = labelWidth;
}
s_SerializedObject.ApplyModifiedProperties();
}
catch
{
// ignored
}
}
private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false)
{
if (!p || ignoreCurrent && target == p) return;
if (!p || (ignoreCurrent && target == p)) return;
var cr = p.canvasRenderer;
DestroyImmediate(p);
DestroyImmediate(cr);
#if UNITY_2021_2_OR_NEWER
var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
#elif UNITY_2018_3_OR_NEWER
var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
#endif
#if UNITY_2018_3_OR_NEWER
var stage = PrefabStageUtility.GetCurrentPrefabStage();
if (stage != null && stage.scene.isLoaded)
{
#if UNITY_2020_1_OR_NEWER
string prefabAssetPath = stage.assetPath;
#else
string prefabAssetPath = stage.prefabAssetPath;
var prefabAssetPath = stage.prefabAssetPath;
#endif
PrefabUtility.SaveAsPrefabAsset(stage.prefabContentsRoot, prefabAssetPath);
}
@ -517,7 +585,10 @@ namespace Coffee.UIExtensions
EditorGUI.BeginChangeCheck();
showXyz = GUILayout.Toggle(showXyz, s_Content3D, EditorStyles.miniButton, GUILayout.Width(30));
if (EditorGUI.EndChangeCheck() && !showXyz)
{
z.floatValue = y.floatValue = x.floatValue;
}
EditorGUILayout.EndHorizontal();
return showXyz;

View File

@ -34,10 +34,10 @@ namespace Coffee.UIExtensions
ps.transform.SetParent(uiParticle.transform, false);
ps.transform.localPosition = Vector3.zero;
// Assign default material.
// Assign default material (UIAdditive).
var renderer = ps.GetComponent<ParticleSystemRenderer>();
var defaultMat = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Particle.mat");
renderer.material = defaultMat ? defaultMat : renderer.material;
var path = AssetDatabase.GUIDToAssetPath("9944483a3e009401ba5dcc42f14d5c63");
renderer.material = AssetDatabase.LoadAssetAtPath<Material>(path);
// Refresh particles.
uiParticle.RefreshParticles();

View File

@ -10,7 +10,7 @@ namespace Coffee.UIParticleExtensions
public static Material Add(Material baseMat, Texture texture, int id)
{
MatEntry e;
for (var i = 0; i < s_Entries.Count; ++i)
for (var i = 0; i < s_Entries.Count; i++)
{
e = s_Entries[i];
if (e.baseMat != baseMat || e.texture != texture || e.id != id) continue;
@ -18,15 +18,19 @@ namespace Coffee.UIParticleExtensions
return e.customMat;
}
e = new MatEntry();
e.count = 1;
e.baseMat = baseMat;
e.texture = texture;
e.id = id;
e.customMat = new Material(baseMat);
e.customMat.hideFlags = HideFlags.HideAndDontSave;
if (texture)
e.customMat.mainTexture = texture;
e = new MatEntry
{
count = 1,
baseMat = baseMat,
texture = texture,
id = id,
customMat = new Material(baseMat)
{
name = $"{baseMat.name}_{id}",
hideFlags = HideFlags.HideAndDontSave,
mainTexture = texture ? texture : null
}
};
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;
@ -43,7 +47,8 @@ namespace Coffee.UIParticleExtensions
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);
DestroyImmediate(e.customMat);
Misc.DestroyImmediate(e.customMat);
e.customMat = null;
e.baseMat = null;
e.texture = null;
s_Entries.RemoveAt(i);
@ -53,22 +58,13 @@ namespace Coffee.UIParticleExtensions
}
}
private static void DestroyImmediate(Object obj)
{
if (!obj) return;
if (Application.isEditor)
Object.DestroyImmediate(obj);
else
Object.Destroy(obj);
}
private class MatEntry
{
public Material baseMat;
public Material customMat;
public int count;
public Texture texture;
public Material customMat;
public int id;
public Texture texture;
}
}
}

View File

@ -2,11 +2,11 @@
#define SERIALIZE_FIELD_MASKABLE
#endif
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Coffee.UIParticleExtensions;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using UnityEngine.UI;
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
@ -19,7 +19,7 @@ namespace Coffee.UIExtensions
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))]
public class UIParticle : MaskableGraphic
public class UIParticle : MaskableGraphic, ISerializationCallbackReceiver
{
public enum MeshSharing
{
@ -27,16 +27,34 @@ namespace Coffee.UIExtensions
Auto,
Primary,
PrimarySimulator,
Reprica,
Replica
}
[HideInInspector][SerializeField] internal bool m_IsTrail = false;
public enum PositionMode
{
Relative,
Absolute
}
[HideInInspector]
[SerializeField]
internal bool m_IsTrail;
[HideInInspector]
[FormerlySerializedAs("m_IgnoreParent")]
[SerializeField]
private bool m_IgnoreCanvasScaler;
[HideInInspector]
[SerializeField]
private bool m_AbsoluteMode;
[Tooltip("Particle effect scale")]
[SerializeField]
private Vector3 m_Scale3D = new Vector3(10, 10, 10);
[Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")]
[Tooltip("Animatable material properties.\n" +
"If you want to change the material properties of the ParticleSystem in Animation, enable it.")]
[SerializeField]
internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
@ -44,33 +62,44 @@ namespace Coffee.UIExtensions
[SerializeField]
private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
[Tooltip("Mesh sharing.None: disable mesh sharing.\nAuto: automatically select Primary/Reprica.\nPrimary: provides particle simulation results to the same group.\nPrimary Simulator: Primary, but do not render the particle (simulation only).\nReprica: render simulation results provided by the primary.")]
[Tooltip("Mesh sharing.\n" +
"None: disable mesh sharing.\n" +
"Auto: automatically select Primary/Replica.\n" +
"Primary: provides particle simulation results to the same group.\n" +
"Primary Simulator: Primary, but do not render the particle (simulation only).\n" +
"Replica: render simulation results provided by the primary.")]
[SerializeField]
private MeshSharing m_MeshSharing = MeshSharing.None;
[Tooltip("Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group.")]
[Tooltip("Mesh sharing group ID.\n" +
"If non-zero is specified, particle simulation results are shared within the group.")]
[SerializeField]
private int m_GroupId = 0;
private int m_GroupId;
[SerializeField]
private int m_GroupMaxId = 0;
private int m_GroupMaxId;
[Tooltip("Relative: The particles will be emitted from the scaled position of ParticleSystem.\n" +
"Absolute: The particles will be emitted from the world position of ParticleSystem.")]
[SerializeField]
private PositionMode m_PositionMode = PositionMode.Relative;
[SerializeField]
[Tooltip("The particles will be emitted at the ParticleSystem position.\nMove the UIParticle/ParticleSystem to move the particle.")]
private bool m_AbsoluteMode = false;
private List<UIParticleRenderer> m_Renderers = new List<UIParticleRenderer>();
[Tooltip("Transform.lossyScale (=world scale) is automatically set to (1, 1, 1), " +
"to prevent the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.")]
private bool m_AutoScaling = true;
#if !SERIALIZE_FIELD_MASKABLE
[SerializeField] private bool m_Maskable = true;
[SerializeField]
private bool m_Maskable = true;
#endif
private DrivenRectTransformTracker _tracker;
private Camera _orthoCamera;
private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>();
private int _groupId;
private Camera _orthoCamera;
private DrivenRectTransformTracker _tracker;
/// <summary>
/// Should this graphic be considered a target for raycasting?
/// Should this graphic be considered a target for ray-casting?
/// </summary>
public override bool raycastTarget
{
@ -79,11 +108,12 @@ namespace Coffee.UIExtensions
}
/// <summary>
/// Mesh sharing.None: disable mesh sharing.
/// Auto: automatically select Primary/Reprica.
/// Mesh sharing.
/// None: disable mesh sharing.
/// Auto: automatically select Primary/Replica.
/// Primary: provides particle simulation results to the same group.
/// Primary Simulator: Primary, but do not render the particle (simulation only).
/// Reprica: render simulation results provided by the primary.
/// Replica: render simulation results provided by the primary.
/// </summary>
public MeshSharing meshSharing
{
@ -92,7 +122,8 @@ namespace Coffee.UIExtensions
}
/// <summary>
/// Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group.
/// Mesh sharing group ID.
/// If non-zero is specified, particle simulation results are shared within the group.
/// </summary>
public int groupId
{
@ -102,9 +133,11 @@ namespace Coffee.UIExtensions
if (m_GroupId == value) return;
m_GroupId = value;
if (m_GroupId != m_GroupMaxId)
{
ResetGroupId();
}
}
}
public int groupMaxId
{
@ -118,14 +151,40 @@ namespace Coffee.UIExtensions
}
/// <summary>
/// Absolute particle position mode.
/// The particles will be emitted at the ParticleSystem position.
/// Move the UIParticle/ParticleSystem to move the particle.
/// Particle position mode.
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem.
/// </summary>
public PositionMode positionMode
{
get { return m_PositionMode; }
set { m_PositionMode = value; }
}
/// <summary>
/// Particle position mode.
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem.
/// </summary>
public bool absoluteMode
{
get { return m_AbsoluteMode; }
set { m_AbsoluteMode = value; }
get { return m_PositionMode == PositionMode.Absolute; }
set { positionMode = value ? PositionMode.Absolute : PositionMode.Relative; }
}
/// <summary>
/// Transform.lossyScale (=world scale) will be set to (1, 1, 1) on update.
/// It prevents the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.
/// </summary>
public bool autoScaling
{
get { return m_AutoScaling; }
set
{
if (m_AutoScaling == value) return;
m_AutoScaling = value;
UpdateTracker();
}
}
internal bool useMeshSharing
@ -135,17 +194,33 @@ namespace Coffee.UIExtensions
internal bool isPrimary
{
get { return m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.PrimarySimulator; }
get
{
return m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.PrimarySimulator;
}
}
internal bool canSimulate
{
get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.PrimarySimulator; }
get
{
return m_MeshSharing == MeshSharing.None
|| m_MeshSharing == MeshSharing.Auto
|| m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.PrimarySimulator;
}
}
internal bool canRender
{
get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.Reprica; }
get
{
return m_MeshSharing == MeshSharing.None
|| m_MeshSharing == MeshSharing.Auto
|| m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.Replica;
}
}
/// <summary>
@ -178,12 +253,12 @@ namespace Coffee.UIExtensions
{
get
{
for (var i = 0; i < m_Renderers.Count; i++)
for (var i = 0; i < _renderers.Count; i++)
{
if (!m_Renderers[i] || !m_Renderers[i].material) continue;
yield return m_Renderers[i].material;
var r = _renderers[i];
if (!r || !r.material) continue;
yield return r.material;
}
yield break;
}
}
@ -195,7 +270,78 @@ namespace Coffee.UIExtensions
/// <summary>
/// Paused.
/// </summary>
public bool isPaused { get; internal set; }
public bool isPaused { get; private set; }
public Vector3 parentScale { get; private set; }
protected override void OnEnable()
{
#if !SERIALIZE_FIELD_MASKABLE
maskable = m_Maskable;
#endif
ResetGroupId();
UpdateTracker();
UIParticleUpdater.Register(this);
RegisterDirtyMaterialCallback(UpdateRendererMaterial);
if (0 < particles.Count)
{
RefreshParticles(particles);
}
else
{
RefreshParticles();
}
base.OnEnable();
}
/// <summary>
/// This function is called when the behaviour becomes disabled.
/// </summary>
protected override void OnDisable()
{
UpdateTracker();
UIParticleUpdater.Unregister(this);
_renderers.ForEach(r => r.Reset());
UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
base.OnDisable();
}
/// <summary>
/// Callback for when properties have been changed by animation.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
UpdateTracker();
}
#endif
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (m_IgnoreCanvasScaler)
{
m_IgnoreCanvasScaler = false;
m_AutoScaling = false;
}
if (m_AbsoluteMode)
{
m_AbsoluteMode = false;
m_PositionMode = PositionMode.Absolute;
}
}
public void Play()
{
@ -220,6 +366,24 @@ namespace Coffee.UIExtensions
isPaused = true;
}
public void StartEmission()
{
particles.Exec(p =>
{
var emission = p.emission;
emission.enabled = true;
});
}
public void StopEmission()
{
particles.Exec(p =>
{
var emission = p.emission;
emission.enabled = false;
});
}
public void Clear()
{
particles.Exec(p => p.Clear());
@ -239,14 +403,10 @@ namespace Coffee.UIExtensions
{
var go = child.gameObject;
go.SetActive(false);
if (!destroyOldParticles) continue;
#if UNITY_EDITOR
if (!Application.isPlaying)
DestroyImmediate(go);
else
#endif
Destroy(go);
if (destroyOldParticles)
{
Misc.Destroy(go);
}
}
var tr = instance.transform;
@ -271,48 +431,60 @@ namespace Coffee.UIExtensions
private void RefreshParticles(GameObject root)
{
if (!root) return;
root.GetComponentsInChildren(particles);
particles.RemoveAll(x => x.GetComponentInParent<UIParticle>() != this);
root.GetComponentsInChildren(true, particles);
particles.RemoveAll(x => x.GetComponentInParent<UIParticle>(true) != this);
foreach (var ps in particles)
for (var i = 0; i < particles.Count; i++)
{
var ps = particles[i];
var tsa = ps.textureSheetAnimation;
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
{
tsa.uvChannelMask = UVChannelFlags.UV0;
}
}
RefreshParticles(particles);
}
public void RefreshParticles(List<ParticleSystem> particles)
{
GetComponentsInChildren(m_Renderers);
// #246: Nullptr exceptions when using nested UIParticle components in hierarchy
_renderers.Clear();
foreach (Transform child in transform)
{
var uiParticleRenderer = child.GetComponent<UIParticleRenderer>();
if (uiParticleRenderer != null)
{
_renderers.Add(uiParticleRenderer);
}
}
for (var i = 0; i < _renderers.Count; i++)
{
_renderers[i].Reset(i);
}
var j = 0;
for (var i = 0; i < particles.Count; i++)
{
if (!particles[i]) continue;
GetRenderer(j++).Set(this, particles[i], false);
if (particles[i].trails.enabled)
var ps = particles[i];
if (!ps) continue;
GetRenderer(j++).Set(this, ps, false);
if (ps.trails.enabled)
{
GetRenderer(j++).Set(this, particles[i], true);
GetRenderer(j++).Set(this, ps, true);
}
}
for (; j < m_Renderers.Count; j++)
{
GetRenderer(j).Clear(j);
}
}
internal void UpdateTransformScale()
{
//var newScale = Vector3.one;
//if (uiScaling)
//{
// newScale = transform.parent.lossyScale.Inverse();
//}
var newScale = transform.parent.lossyScale.Inverse();
parentScale = transform.parent.lossyScale;
if (!autoScaling) return;
var newScale = parentScale.Inverse();
if (transform.localScale != newScale)
{
transform.localScale = newScale;
@ -323,73 +495,40 @@ namespace Coffee.UIExtensions
{
if (!isActiveAndEnabled) return;
if (m_Renderers.Any(x => !x))
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r)
{
RefreshParticles(particles);
break;
}
}
var bakeCamera = GetBakeCamera();
for (var i = 0; i < m_Renderers.Count; i++)
for (var i = 0; i < _renderers.Count; i++)
{
if (!m_Renderers[i]) continue;
m_Renderers[i].UpdateMesh(bakeCamera);
var r = _renderers[i];
if (!r) continue;
r.UpdateMesh(bakeCamera);
}
}
internal void UpdateParticleCount()
{
for (var i = 0; i < m_Renderers.Count; i++)
for (var i = 0; i < _renderers.Count; i++)
{
if (!m_Renderers[i]) continue;
m_Renderers[i].UpdateParticleCount();
var r = _renderers[i];
if (!r) continue;
r.UpdateParticleCount();
}
}
protected override void OnEnable()
{
#if !SERIALIZE_FIELD_MASKABLE
maskable = m_Maskable;
#endif
ResetGroupId();
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
UIParticleUpdater.Register(this);
RegisterDirtyMaterialCallback(UpdateRendererMaterial);
if (0 < particles.Count)
{
RefreshParticles(particles);
}
else
{
RefreshParticles();
}
base.OnEnable();
}
internal void ResetGroupId()
{
if (m_GroupId == m_GroupMaxId)
{
_groupId = m_GroupId;
}
else
{
_groupId = Random.Range(m_GroupId, m_GroupMaxId + 1);
}
}
/// <summary>
/// This function is called when the behaviour becomes disabled.
/// </summary>
protected override void OnDisable()
{
_tracker.Clear();
UIParticleUpdater.Unregister(this);
m_Renderers.ForEach(r => r.Clear());
UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
base.OnDisable();
_groupId = m_GroupId == m_GroupMaxId
? m_GroupId
: Random.Range(m_GroupId, m_GroupMaxId + 1);
}
protected override void UpdateMaterial()
@ -403,54 +542,61 @@ namespace Coffee.UIExtensions
{
}
/// <summary>
/// Callback for when properties have been changed by animation.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
}
private void UpdateRendererMaterial()
{
for (var i = 0; i < m_Renderers.Count; i++)
for (var i = 0; i < _renderers.Count; i++)
{
if (!m_Renderers[i]) continue;
m_Renderers[i].maskable = maskable;
m_Renderers[i].SetMaterialDirty();
var r = _renderers[i];
if (!r) continue;
r.maskable = maskable;
r.SetMaterialDirty();
}
}
internal UIParticleRenderer GetRenderer(int index)
{
if (m_Renderers.Count <= index)
if (_renderers.Count <= index)
{
m_Renderers.Add(UIParticleRenderer.AddRenderer(this, index));
_renderers.Add(UIParticleRenderer.AddRenderer(this, index));
}
if (!m_Renderers[index])
if (!_renderers[index])
{
m_Renderers[index] = UIParticleRenderer.AddRenderer(this, index);
_renderers[index] = UIParticleRenderer.AddRenderer(this, index);
}
return m_Renderers[index];
return _renderers[index];
}
private Camera GetBakeCamera()
{
if (!canvas) return Camera.main;
// World camera.
// When render mode is ScreenSpaceCamera or WorldSpace, use world camera.
var root = canvas.rootCanvas;
if (root.renderMode != RenderMode.ScreenSpaceOverlay) return root.worldCamera ? root.worldCamera : Camera.main;
if (root.renderMode != RenderMode.ScreenSpaceOverlay)
{
return root.worldCamera ? root.worldCamera : Camera.main;
}
// When render mode is ScreenSpaceOverlay, use ortho-camera.
if (!_orthoCamera)
{
// Find existing ortho-camera.
foreach (Transform child in transform)
{
var cam = child.GetComponent<Camera>();
if (cam && cam.name == "[generated] UIParticleOverlayCamera")
{
_orthoCamera = cam;
break;
}
}
// Create ortho-camera.
if (!_orthoCamera)
{
_orthoCamera = GetComponentInChildren<Camera>();
if (!_orthoCamera)
{
var go = new GameObject("UIParticleOverlayCamera")
{
hideFlags = HideFlags.DontSave,
};
var go = new GameObject("[generated] UIParticleOverlayCamera") { hideFlags = HideFlags.DontSave };
go.SetActive(false);
go.transform.SetParent(transform, false);
_orthoCamera = go.AddComponent<Camera>();
@ -467,5 +613,17 @@ namespace Coffee.UIExtensions
return _orthoCamera;
}
private void UpdateTracker()
{
if (!enabled || !autoScaling)
{
_tracker.Clear();
}
else
{
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
}
}
}
}

View File

@ -1,7 +1,7 @@
using UnityEngine;
using System;
using Coffee.UIParticleExtensions;
using UnityEngine;
using UnityEngine.Events;
using System;
namespace Coffee.UIExtensions
{
@ -12,7 +12,13 @@ namespace Coffee.UIExtensions
{
Linear,
Smooth,
Sphere,
Sphere
}
public enum UpdateMode
{
Normal,
UnscaledTime
}
[SerializeField]
@ -24,7 +30,7 @@ namespace Coffee.UIExtensions
[Range(0f, 0.95f)]
[SerializeField]
private float m_DelayRate = 0;
private float m_DelayRate;
[Range(0.001f, 100f)]
[SerializeField]
@ -33,70 +39,81 @@ namespace Coffee.UIExtensions
[SerializeField]
private Movement m_Movement;
[SerializeField]
private UpdateMode m_UpdateMode;
[SerializeField]
private UnityEvent m_OnAttracted;
private UIParticle _uiParticle;
public float destinationRadius
{
get { return m_DestinationRadius; }
set { m_DestinationRadius = Mathf.Clamp(value, 0.1f, 10f); }
}
public float delay
{
get
{
return m_DelayRate;
}
set
{
m_DelayRate = value;
}
get { return m_DelayRate; }
set { m_DelayRate = value; }
}
public float maxSpeed
{
get
{
return m_MaxSpeed;
}
set
{
m_MaxSpeed = value;
}
get { return m_MaxSpeed; }
set { m_MaxSpeed = value; }
}
public Movement movement
{
get
{
return m_Movement;
}
set
{
m_Movement = value;
}
get { return m_Movement; }
set { m_Movement = value; }
}
private UIParticle _uiParticle;
public UpdateMode updateMode
{
get { return m_UpdateMode; }
set { m_UpdateMode = value; }
}
public UnityEvent onAttracted
{
get { return m_OnAttracted; }
set { m_OnAttracted = value; }
}
#if UNITY_EDITOR
public new ParticleSystem particleSystem
#else
public ParticleSystem particleSystem
#endif
{
get { return m_ParticleSystem; }
set
{
m_ParticleSystem = value;
ApplyParticleSystem();
}
}
private void OnEnable()
{
if (m_ParticleSystem == null)
{
Debug.LogError("No particle system attached to particle attractor script", this);
enabled = false;
return;
}
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>();
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
{
_uiParticle = null;
}
ApplyParticleSystem();
UIParticleUpdater.Register(this);
}
private void OnDisable()
{
_uiParticle = null;
UIParticleUpdater.Unregister(this);
}
private void OnDestroy()
{
_uiParticle = null;
m_ParticleSystem = null;
}
internal void Attract()
{
if (m_ParticleSystem == null) return;
@ -128,6 +145,7 @@ namespace Coffee.UIExtensions
Debug.LogException(e);
}
}
continue;
}
@ -154,36 +172,51 @@ namespace Coffee.UIExtensions
var psPos = m_ParticleSystem.transform.position;
var attractorPos = transform.position;
var dstPos = attractorPos;
if (m_ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local)
var isLocalSpace = m_ParticleSystem.IsLocalSpace();
if (isLocalSpace)
{
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos);
}
if (isUI)
{
dstPos = dstPos.GetScaled(_uiParticle.transform.localScale, _uiParticle.scale3D.Inverse());
}
}
else
var inverseScale = _uiParticle.parentScale.Inverse();
dstPos = dstPos.GetScaled(inverseScale, _uiParticle.scale3D.Inverse());
// Relative mode
if (_uiParticle.positionMode == UIParticle.PositionMode.Relative)
{
var diff = _uiParticle.transform.position - psPos;
diff.Scale(_uiParticle.scale3D - inverseScale);
diff.Scale(_uiParticle.scale3D.Inverse());
dstPos += diff;
}
#if UNITY_EDITOR
if (!Application.isPlaying && isUI)
if (!Application.isPlaying && !isLocalSpace)
{
var diff = dstPos - psPos;
diff = diff.GetScaled(_uiParticle.transform.localScale, _uiParticle.scale3D.Inverse());
return psPos + diff;
dstPos += psPos - psPos.GetScaled(inverseScale, _uiParticle.scale3D.Inverse());
}
#endif
if (isUI)
{
dstPos.Scale(_uiParticle.transform.localScale);
dstPos.Scale(_uiParticle.scale3D.Inverse());
}
}
return dstPos;
}
private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float duration, float time)
{
var speed = m_MaxSpeed;
switch (m_UpdateMode)
{
case UpdateMode.Normal:
speed *= 60 * Time.deltaTime;
break;
case UpdateMode.UnscaledTime:
speed *= 60 * Time.unscaledDeltaTime;
break;
}
switch (m_Movement)
{
case Movement.Linear:
@ -200,5 +233,26 @@ namespace Coffee.UIExtensions
return Vector3.MoveTowards(current, target, speed);
}
private void ApplyParticleSystem()
{
_uiParticle = null;
if (m_ParticleSystem == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
Debug.LogError("No particle system attached to particle attractor script", this);
}
return;
}
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(true);
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
{
_uiParticle = null;
}
}
}
}

View File

@ -1,9 +1,10 @@
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections.Generic;
using Coffee.UIParticleExtensions;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using System.Collections.Generic;
using UnityEngine.UI;
namespace Coffee.UIExtensions
{
@ -13,41 +14,34 @@ namespace Coffee.UIExtensions
[AddComponentMenu("")]
internal class UIParticleRenderer : MaskableGraphic
{
private static readonly CombineInstance[] s_CombineInstances = new CombineInstance[] { new CombineInstance() };
private static readonly CombineInstance[] s_CombineInstances = { new CombineInstance() };
private static readonly List<Material> s_Materials = new List<Material>(2);
private static MaterialPropertyBlock s_Mpb;
private static readonly List<UIParticleRenderer> s_Renderers = new List<UIParticleRenderer>();
private static readonly Vector3[] s_Corners = new Vector3[4];
private ParticleSystemRenderer _renderer;
private ParticleSystem _particleSystem;
private int _prevParticleCount = 0;
private UIParticle _parent;
private Material _currentMaterialForRendering;
private bool _delay;
private int _index;
private bool _isTrail;
private Material _modifiedMaterial;
private Vector3 _prevScale;
private Vector3 _prevPsPos;
private Vector2Int _prevScreenSize;
private bool _delay = false;
private bool _prewarm = false;
private Material _currentMaterialForRendering;
private Bounds _lastBounds;
private Material _modifiedMaterial;
private UIParticle _parent;
private ParticleSystem _particleSystem;
private int _prevParticleCount;
private Vector3 _prevPsPos;
private Vector3 _prevScale;
private Vector2Int _prevScreenSize;
private bool _prewarm;
private ParticleSystemRenderer _renderer;
public override Texture mainTexture
{
get
{
return _isTrail ? null : _particleSystem.GetTextureForSprite();
}
get { return _isTrail ? null : _particleSystem.GetTextureForSprite(); }
}
public override bool raycastTarget
{
get
{
return false;
}
get { return false; }
}
private Rect rootCanvasRect
@ -62,32 +56,103 @@ namespace Coffee.UIExtensions
{
var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix;
for (var i = 0; i < 4; ++i)
{
s_Corners[i] = worldToLocalMatrix.MultiplyPoint(s_Corners[i]);
}
var corner1 = (Vector2) s_Corners[0];
var corner2 = (Vector2) s_Corners[0];
}
var corner1 = (Vector2)s_Corners[0];
var corner2 = (Vector2)s_Corners[0];
for (var i = 1; i < 4; ++i)
{
if (s_Corners[i].x < corner1.x)
{
corner1.x = s_Corners[i].x;
}
else if (s_Corners[i].x > corner2.x)
{
corner2.x = s_Corners[i].x;
}
if (s_Corners[i].y < corner1.y)
{
corner1.y = s_Corners[i].y;
}
else if (s_Corners[i].y > corner2.y)
{
corner2.y = s_Corners[i].y;
}
}
return new Rect(corner1, corner2 - corner1);
}
}
public void Reset(int index = -1)
{
if (_renderer)
{
_renderer.enabled = true;
}
_parent = null;
_particleSystem = null;
_renderer = null;
_prevParticleCount = 0;
if (0 <= index)
{
_index = index;
}
//_emitter = null;
if (this && isActiveAndEnabled)
{
material = null;
workerMesh.Clear();
canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds();
enabled = false;
}
else
{
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
_currentMaterialForRendering = null;
}
}
protected override void OnEnable()
{
base.OnEnable();
if (!s_CombineInstances[0].mesh)
{
s_CombineInstances[0].mesh = new Mesh
{
name = "[UIParticleRenderer] Combine Instance Mesh",
hideFlags = HideFlags.HideAndDontSave
};
}
_currentMaterialForRendering = null;
}
protected override void OnDisable()
{
base.OnDisable();
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
_currentMaterialForRendering = null;
}
public static UIParticleRenderer AddRenderer(UIParticle parent, int index)
{
// Create renderer object.
var go = new GameObject("UIParticleRenderer", typeof(UIParticleRenderer))
var go = new GameObject("[generated] UIParticleRenderer", typeof(UIParticleRenderer))
{
hideFlags = HideFlags.DontSave,
layer = parent.gameObject.layer,
layer = parent.gameObject.layer
};
// Set parent.
@ -112,7 +177,12 @@ namespace Coffee.UIExtensions
{
_currentMaterialForRendering = null;
if (!IsActive()) return baseMaterial;
if (!IsActive() || !_parent)
{
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
return baseMaterial;
}
var modifiedMaterial = base.GetModifiedMaterial(baseMaterial);
@ -134,50 +204,28 @@ namespace Coffee.UIExtensions
return modifiedMaterial;
}
public void Clear(int index = -1)
{
if (_renderer)
{
_renderer.enabled = true;
}
_parent = null;
_particleSystem = null;
_renderer = null;
_prevParticleCount = 0;
if (0 <= index)
{
_index = index;
}
//_emitter = null;
if (this && isActiveAndEnabled)
{
material = null;
workerMesh.Clear();
canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds();
enabled = false;
}
}
public void Set(UIParticle parent, ParticleSystem particleSystem, bool isTrail)
public void Set(UIParticle parent, ParticleSystem ps, bool isTrail)
{
_parent = parent;
maskable = parent.maskable;
gameObject.layer = parent.gameObject.layer;
_particleSystem = particleSystem;
_particleSystem = ps;
_prewarm = _particleSystem.main.prewarm;
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
if (_particleSystem.isPlaying)
{
if (_particleSystem.isPlaying || _prewarm)
{
_particleSystem.Clear();
_particleSystem.Pause();
}
_prewarm = _particleSystem.main.prewarm;
}
_renderer = particleSystem.GetComponent<ParticleSystemRenderer>();
_renderer = ps.GetComponent<ParticleSystemRenderer>();
_renderer.enabled = false;
//_emitter = emitter;
@ -188,9 +236,11 @@ namespace Coffee.UIExtensions
s_Materials.Clear();
// Support sprite.
var tsa = particleSystem.textureSheetAnimation;
var tsa = ps.textureSheetAnimation;
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
{
tsa.uvChannelMask = UVChannelFlags.UV0;
}
_prevScale = GetWorldScale();
_prevPsPos = _particleSystem.transform.position;
@ -207,13 +257,15 @@ namespace Coffee.UIExtensions
{
// No particle to render: Clear mesh.
if (
!isActiveAndEnabled || !_particleSystem || !_parent || !canvasRenderer || !canvas || !bakeCamera
|| _parent.meshSharing == UIParticle.MeshSharing.Reprica
!isActiveAndEnabled || !_particleSystem || !_parent
|| !canvasRenderer || !canvas || !bakeCamera
|| _parent.meshSharing == UIParticle.MeshSharing.Replica
|| !transform.lossyScale.GetScaled(_parent.scale3D).IsVisible() // Scale is not visible.
|| (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle.
|| (_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.
|| canvasRenderer.GetInheritedAlpha() <
0.01f // #102: Do not bake particle system to mesh when the alpha is zero.
#endif
)
{
@ -251,20 +303,25 @@ namespace Coffee.UIExtensions
}
// When the ParticleSystem simulation is complete, stop it.
if (!main.loop && main.duration <= _particleSystem.time && (_particleSystem.IsAlive() || _particleSystem.particleCount == 0))
if (!main.loop
&& main.duration <= _particleSystem.time
&& (_particleSystem.IsAlive() || _particleSystem.particleCount == 0)
)
{
_particleSystem.Stop(false);
}
}
_prevScale = scale;
_prevPsPos = psPos;
_delay = false;
}
Profiler.EndSample();
// Bake mesh.
Profiler.BeginSample("[UIParticleRenderer] Bake Mesh");
if (_isTrail && _parent.canSimulate)
if (_isTrail && _parent.canSimulate && 0 < s_CombineInstances[0].mesh.vertexCount)
{
_renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true);
}
@ -276,21 +333,41 @@ namespace Coffee.UIExtensions
{
s_CombineInstances[0].mesh.Clear(false);
}
// Too many vertices to render.
if (65535 <= s_CombineInstances[0].mesh.vertexCount)
{
s_CombineInstances[0].mesh.Clear(false);
Debug.LogErrorFormat(this,
"Too many vertices to render. index={0}, isTrail={1}, vertexCount={2}(>=65535)",
_index,
_isTrail,
s_CombineInstances[0].mesh.vertexCount
);
s_CombineInstances[0].mesh.Clear(false);
}
Profiler.EndSample();
// Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local)
Profiler.BeginSample("[UIParticleRenderer] Combine Mesh");
if (_parent.canSimulate)
{
if (_parent.absoluteMode)
if (_parent.positionMode == UIParticle.PositionMode.Absolute)
{
s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * GetWorldMatrix(psPos, scale);
s_CombineInstances[0].transform =
canvasRenderer.transform.worldToLocalMatrix
* GetWorldMatrix(psPos, scale);
}
else
{
var diff = _particleSystem.transform.position - _parent.transform.position;
s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * Matrix4x4.Translate(diff.GetScaled(scale - Vector3.one)) * GetWorldMatrix(psPos, scale);
s_CombineInstances[0].transform =
canvasRenderer.transform.worldToLocalMatrix
* Matrix4x4.Translate(diff.GetScaled(scale - Vector3.one))
* GetWorldMatrix(psPos, scale);
}
workerMesh.CombineMeshes(s_CombineInstances, true, true);
workerMesh.RecalculateBounds();
@ -304,6 +381,7 @@ namespace Coffee.UIExtensions
workerMesh.bounds = bounds;
_lastBounds = bounds;
}
Profiler.EndSample();
@ -316,7 +394,7 @@ namespace Coffee.UIExtensions
// Set mesh to the CanvasRenderer.
Profiler.BeginSample("[UIParticleRenderer] Set Mesh");
for (int i = 0; i < s_Renderers.Count; i++)
for (var i = 0; i < s_Renderers.Count; i++)
{
if (s_Renderers[i] == this) continue;
s_Renderers[i].canvasRenderer.SetMesh(workerMesh);
@ -327,6 +405,7 @@ namespace Coffee.UIExtensions
{
workerMesh.Clear();
}
canvasRenderer.SetMesh(workerMesh);
Profiler.EndSample();
@ -339,7 +418,8 @@ namespace Coffee.UIExtensions
{
_currentMaterialForRendering = materialForRendering;
}
for (int i = 0; i < s_Renderers.Count; i++)
for (var i = 0; i < s_Renderers.Count; i++)
{
if (s_Renderers[i] == this) continue;
@ -347,6 +427,7 @@ namespace Coffee.UIExtensions
s_Renderers[i].canvasRenderer.SetMaterial(_currentMaterialForRendering, 0);
}
}
Profiler.EndSample();
s_Renderers.Clear();
@ -358,30 +439,6 @@ namespace Coffee.UIExtensions
_prevParticleCount = _particleSystem.particleCount;
}
protected override void OnEnable()
{
base.OnEnable();
if (!s_CombineInstances[0].mesh)
{
s_CombineInstances[0].mesh = new Mesh()
{
name = "[UIParticleRenderer] Combine Instance Mesh",
hideFlags = HideFlags.HideAndDontSave,
};
}
_currentMaterialForRendering = null;
}
protected override void OnDisable()
{
base.OnDisable();
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
_currentMaterialForRendering = null;
}
/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
@ -391,7 +448,9 @@ namespace Coffee.UIExtensions
public override void Cull(Rect clipRect, bool validRect)
{
var cull = _lastBounds.extents == Vector3.zero || !validRect || !clipRect.Overlaps(rootCanvasRect, true);
var cull = _lastBounds.extents == Vector3.zero
|| !validRect
|| !clipRect.Overlaps(rootCanvasRect, true);
if (canvasRenderer.cull == cull) return;
canvasRenderer.cull = cull;
@ -403,16 +462,7 @@ namespace Coffee.UIExtensions
private Vector3 GetWorldScale()
{
Profiler.BeginSample("[UIParticleRenderer] GetWorldScale");
var scale = _parent.scale3D;
//if (_parent.uiScaling)
{
scale.Scale(_parent.transform.localScale.Inverse());
}
//else if (_parent.scalingMode == UIParticle.ScalingMode.UI && _particleSystem.main.scalingMode != ParticleSystemScalingMode.Hierarchy)
//{
// var gscale = _parent.transform.lossyScale.GetScaled(canvas.transform.lossyScale.Inverse());
// scale.Scale(gscale * canvas.scaleFactor);
//}
var scale = _parent.scale3D.GetScaled(_parent.parentScale);
Profiler.EndSample();
return scale;
}
@ -452,7 +502,7 @@ namespace Coffee.UIExtensions
//* Matrix4x4.Translate(-wpos)
;
default:
throw new System.NotSupportedException();
throw new NotSupportedException();
}
}
@ -464,8 +514,9 @@ namespace Coffee.UIExtensions
private void ResolveResolutionChange(Vector3 psPos, Vector3 scale)
{
var screenSize = new Vector2Int(Screen.width, Screen.height);
//if ((_prevScreenSize != screenSize || _prevScale != scale) && _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World && _parent.uiScaling)
if ((_prevScreenSize != screenSize || _prevScale != scale) && _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World)
var isWorldSpace = _particleSystem.IsWorldSpace();
var resolutionChanged = _prevScreenSize != screenSize || _prevScale != scale;
if (resolutionChanged && isWorldSpace)
{
// Update particle array size and get particles.
var size = _particleSystem.particleCount;
@ -474,13 +525,17 @@ namespace Coffee.UIExtensions
// Resolusion resolver:
// (psPos / scale) / (prevPsPos / prevScale) -> psPos * scale.inv * prevPsPos.inv * prevScale
var modifier = psPos.GetScaled(scale.Inverse(), _prevPsPos.Inverse(), _prevScale);
var modifier = psPos.GetScaled(
scale.Inverse(),
_prevPsPos.Inverse(),
_prevScale);
for (var i = 0; i < size; i++)
{
var particle = particles[i];
particle.position = particle.position.GetScaled(modifier);
particles[i] = particle;
}
_particleSystem.SetParticles(particles, size);
// Delay: Do not progress in the frame where the resolution has been changed.
@ -488,6 +543,7 @@ namespace Coffee.UIExtensions
_prevScale = scale;
_prevPsPos = psPos;
}
_prevScreenSize = screenSize;
}
@ -507,6 +563,8 @@ namespace Coffee.UIExtensions
_prewarm = false;
}
// (COMMENT OUT) #231: Sub Emitters option is not work in editor playing
/*
// Emitted particles found.
if (_prevParticleCount != _particleSystem.particleCount)
{
@ -522,24 +580,31 @@ namespace Coffee.UIExtensions
_particleSystem.SetParticles(particles, size);
}
*/
// get world position.
var isLocalSpace = _particleSystem.IsLocalSpace();
var psTransform = _particleSystem.transform;
var originWorldPosition = psTransform.position;
var originWorldRotation = psTransform.rotation;
var emission = _particleSystem.emission;
var rateOverDistance = emission.enabled && 0 < emission.rateOverDistance.constant && 0 < emission.rateOverDistanceMultiplier;
if (rateOverDistance)
var rateOverDistance = emission.enabled
&& 0 < emission.rateOverDistance.constant
&& 0 < emission.rateOverDistanceMultiplier;
if (rateOverDistance && !paused)
{
// (For rate-over-distance emission,) Move to previous scaled position, simulate (delta = 0).
Vector3 prevScaledPos = _prevPsPos.GetScaled(_prevScale.Inverse());
var prevScaledPos = isLocalSpace
? _prevPsPos
: _prevPsPos.GetScaled(_prevScale.Inverse());
psTransform.SetPositionAndRotation(prevScaledPos, originWorldRotation);
_particleSystem.Simulate(0, false, false, false);
}
// Move to scaled position, simulate, revert to origin position.
var scaledPos = originWorldPosition.GetScaled(scale.Inverse());
var scaledPos = isLocalSpace
? originWorldPosition
: originWorldPosition.GetScaled(scale.Inverse());
psTransform.SetPositionAndRotation(scaledPos, originWorldRotation);
_particleSystem.Simulate(deltaTime, false, false, false);
psTransform.SetPositionAndRotation(originWorldPosition, originWorldRotation);
@ -549,7 +614,8 @@ namespace Coffee.UIExtensions
private void SimulateForEditor(Vector3 diffPos, Vector3 scale)
{
// Extra world simulation.
if (_particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World && 0 < Vector3.SqrMagnitude(diffPos))
var isWorldSpace = _particleSystem.IsWorldSpace();
if (isWorldSpace && 0 < Vector3.SqrMagnitude(diffPos))
{
Profiler.BeginSample("[UIParticle] Bake Mesh > Extra world simulation");
diffPos.x *= 1f - 1f / Mathf.Max(0.001f, scale.x);
@ -577,7 +643,9 @@ namespace Coffee.UIExtensions
if (_parent.m_AnimatableProperties.Length == 0) return;
if (s_Mpb == null)
{
s_Mpb = new MaterialPropertyBlock();
}
_renderer.GetPropertyBlock(s_Mpb);
if (s_Mpb.isEmpty) return;
@ -585,8 +653,9 @@ namespace Coffee.UIExtensions
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
if (!_modifiedMaterial) return;
foreach (var ap in _parent.m_AnimatableProperties)
for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++)
{
var ap = _parent.m_AnimatableProperties[i];
ap.UpdateMaterialProperties(_modifiedMaterial, s_Mpb);
}

View File

@ -1,21 +1,19 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Coffee.UIExtensions
{
internal static class UIParticleUpdater
{
static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
static readonly List<UIParticleAttractor> s_ActiveAttractors = new List<UIParticleAttractor>();
static readonly HashSet<int> s_UpdatedGroupIds = new HashSet<int>();
private static int frameCount = 0;
private static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
private static readonly List<UIParticleAttractor> s_ActiveAttractors = new List<UIParticleAttractor>();
private static readonly HashSet<int> s_UpdatedGroupIds = new HashSet<int>();
private static int s_FrameCount;
public static int uiParticleCount
{
get
{
return s_ActiveParticles.Count;
}
get { return s_ActiveParticles.Count; }
}
public static void Register(UIParticle particle)
@ -43,7 +41,7 @@ namespace Coffee.UIExtensions
}
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
[InitializeOnLoadMethod]
#endif
[RuntimeInitializeOnLoadMethod]
private static void InitializeOnLoad()
@ -55,8 +53,8 @@ namespace Coffee.UIExtensions
private static void Refresh()
{
// Do not allow it to be called in the same frame.
if (frameCount == Time.frameCount) return;
frameCount = Time.frameCount;
if (s_FrameCount == Time.frameCount) return;
s_FrameCount = Time.frameCount;
// Simulate -> Primary
for (var i = 0; i < s_ActiveParticles.Count; i++)
@ -129,6 +127,7 @@ namespace Coffee.UIExtensions
if (uip.isPrimary) return uip;
if (!primary && uip.canSimulate) primary = uip;
}
return primary;
}
}

View File

@ -46,11 +46,11 @@ namespace Coffee.UIParticleExtensions
internal static class SpriteExtensions
{
#if UNITY_EDITOR
private static Type tSpriteEditorExtension =
private static readonly Type s_SpriteEditorExtensionType =
Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor")
?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor");
private static MethodInfo miGetActiveAtlasTexture = tSpriteEditorExtension
private static readonly MethodInfo s_GetActiveAtlasTextureMethodInfo = s_SpriteEditorExtensionType
.GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic);
public static Texture2D GetActualTexture(this Sprite self)
@ -58,8 +58,10 @@ namespace Coffee.UIParticleExtensions
if (!self) return null;
if (Application.isPlaying) return self.texture;
var ret = miGetActiveAtlasTexture.Invoke(null, new[] { self }) as Texture2D;
return ret ? ret : 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)
@ -77,12 +79,14 @@ namespace Coffee.UIParticleExtensions
{
if (s_TmpParticles.Length < size)
{
while(s_TmpParticles.Length < size)
while (s_TmpParticles.Length < size)
{
size = Mathf.NextPowerOfTwo(size);
}
s_TmpParticles = new ParticleSystem.Particle[size];
}
return s_TmpParticles;
}
@ -102,47 +106,69 @@ namespace Coffee.UIParticleExtensions
var main = self.main;
var space = main.simulationSpace;
if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace)
{
space = ParticleSystemSimulationSpace.Local;
}
return space;
}
public static bool IsLocalSpace(this ParticleSystem self)
{
return GetActualSimulationSpace(self) == ParticleSystemSimulationSpace.Local;
}
public static bool IsWorldSpace(this ParticleSystem self)
{
return GetActualSimulationSpace(self) == ParticleSystemSimulationSpace.World;
}
public static void SortForRendering(this List<ParticleSystem> self, Transform transform, bool sortByMaterial)
{
self.Sort((a, b) =>
{
var tr = transform;
var aRenderer = a.GetComponent<ParticleSystemRenderer>();
var bRenderer = b.GetComponent<ParticleSystemRenderer>();
// Render queue: ascending
var aMat = aRenderer.sharedMaterial ?? aRenderer.trailMaterial;
var bMat = bRenderer.sharedMaterial ?? bRenderer.trailMaterial;
var aMat = aRenderer.sharedMaterial ? aRenderer.sharedMaterial : aRenderer.trailMaterial;
var bMat = bRenderer.sharedMaterial ? bRenderer.sharedMaterial : bRenderer.trailMaterial;
if (!aMat && !bMat) return 0;
if (!aMat) return -1;
if (!bMat) return 1;
if (sortByMaterial)
{
return aMat.GetInstanceID() - bMat.GetInstanceID();
}
if (aMat.renderQueue != bMat.renderQueue)
{
return aMat.renderQueue - bMat.renderQueue;
}
// Sorting layer: ascending
if (aRenderer.sortingLayerID != bRenderer.sortingLayerID)
return SortingLayer.GetLayerValueFromID(aRenderer.sortingLayerID) - SortingLayer.GetLayerValueFromID(bRenderer.sortingLayerID);
{
return SortingLayer.GetLayerValueFromID(aRenderer.sortingLayerID) -
SortingLayer.GetLayerValueFromID(bRenderer.sortingLayerID);
}
// Sorting order: ascending
if (aRenderer.sortingOrder != bRenderer.sortingOrder)
{
return aRenderer.sortingOrder - bRenderer.sortingOrder;
}
// Z position & sortingFudge: descending
var aTransform = a.transform;
var bTransform = b.transform;
var aPos = tr.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge;
var bPos = tr.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge;
var aPos = transform.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge;
var bPos = transform.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge;
if (!Mathf.Approximately(aPos, bPos))
{
return (int)Mathf.Sign(bPos - aPos);
}
return (int)Mathf.Sign(GetIndex(self, a) - GetIndex(self, b));
});
@ -152,7 +178,10 @@ namespace Coffee.UIParticleExtensions
{
for (var i = 0; i < list.Count; i++)
{
if (list[i].GetInstanceID() == ps.GetInstanceID()) return i;
if (list[i].GetInstanceID() == ps.GetInstanceID())
{
return i;
}
}
return 0;
@ -183,4 +212,55 @@ namespace Coffee.UIParticleExtensions
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);
}
else
#endif
{
Object.Destroy(obj);
}
}
public static void DestroyImmediate(Object obj)
{
if (!obj) return;
#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
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>();
if (component) return component;
current = current.parent;
}
return null;
}
#endif
}
}

85
Shaders/UIAdditive.mat Normal file
View File

@ -0,0 +1,85 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UIAdditive
m_Shader: {fileID: 4800000, guid: ecfa8f5732b504ef98fba10aa18d0326, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
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_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: 9944483a3e009401ba5dcc42f14d5c63
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2,7 +2,7 @@
"name": "com.coffee.ui-particle",
"displayName": "UI Particle",
"description": "This plugin provide a component to render particle effect for uGUI.\nThe particle rendering is maskable and sortable, without Camera, RenderTexture or Canvas.",
"version": "4.1.7",
"version": "4.2.1",
"unity": "2018.2",
"license": "MIT",
"repository": {