Compare commits

...

21 Commits

Author SHA1 Message Date
mob-sakai f1d12c3b04 fix compile error on Unity 2021.1 2023-08-18 15:08:49 +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
42 changed files with 2062 additions and 1812 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. NOTE: Your issue may already be reported! Please search on the [issue tracker](../) before creating one.
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):** **Environment (please complete the following)**
- Version [e.g. 1.0.0]
- Platform: [e.g. Editor(Windows/Mac), Standalone(Windows/Mac), iOS, Android, WebGL] - Package version [e.g. 4.2.0, 3.3.14]
- Unity version: [e.g. 2018.2.8f1] - Platform: [e.g. Editor(Windows/Mac), Standalone(Windows/Mac), iOS, Android, WebGL]
- Build options: [e.g. IL2CPP, .Net 4.x, LWRP] - 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** **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. 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.** **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** **Describe the solution you'd like**
A clear and concise description of what you want to happen. A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Additional context** **Additional context**
Add any other context or screenshots about the feature request here. Add any other context or screenshots about the feature request here.

View File

@ -81,4 +81,4 @@ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcem
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

View File

@ -1,7 +1,7 @@
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: 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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -2,16 +2,14 @@ Particle Effect For UGUI (UI Particle)
=== ===
This plugin provide a component to render particle effect for uGUI in Unity 2018.2 or later. 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. 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/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) [![](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) [![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) >> << [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 * Support CanvasGroup alpha
* No allocations * No allocations
* Support overlay, camera space and world space * 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) * Support changing material property with AnimationClip (AnimatableProperty)
![](https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif) ![](https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif)
* [4.0.0+] Support 8+ materials * [4.0.0+] Support 8+ materials
@ -92,39 +92,41 @@ Compares this "Baking mesh" approach with the conventional approach:
### Requirement ### Requirement
![](https://img.shields.io/badge/Unity%202018.2+-supported-blue.svg) Unity 2018.2 or later
![](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)
### Using OpenUPM ### Install via OpenUPM
This package is available on [OpenUPM](https://openupm.com). 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 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. 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 1. Download a source code zip file from [Releases](https://github.com/mob-sakai/ParticleEffectForUGUI/releases) page
2. Extract it 2. Extract it and place it under `Packages` directory in your project.
3. Import it under `Packages` directory in your Unity project ![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/4c3d3439-5bb0-4e87-a917-ebe328ef89a8)
<br><br><br><br> <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. `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 | | Properties | Description |
| -- | -- | | -- | -- |
| Maskable | Does this graphic allow masking. | | 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. | | 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. | | 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. | | 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. | | Position Mode | **Absolute:** Emit from the world position of the `ParticleSystem`.<br>**Relative:** Emit from the scaled position of the `ParticleSystem`. |
| Rendering Order | The ParticleSystems to be rendered.<br>You can change the rendering order and the materials. | | 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. 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. `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) ![](https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif)
| Properties | Description | | Properties | Description |
@ -237,6 +239,7 @@ uiParticle.Stop();
| Delay Rate | Delay to start attracting.<br>It is a percentage of the particle's start lifetime. | | 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. | | 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) | | 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). | | OnAttracted | An event called when attracting is complete (per particle). |
<br><br><br><br> <br><br><br><br>
@ -245,14 +248,31 @@ uiParticle.Stop();
### Shader Limitation ### Shader Limitation
UIParticles are based on UIVertex. The use of UI shaders is recommended.
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
- If you need a simple Additive shader, use the `UI/Additive` shader instead. - 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 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 ### Overheads

View File

@ -1,85 +1,87 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Coffee.UIExtensions;
using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using UnityEngine.UI; using UnityEngine.UI;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
public class CFX_Demo_With_UIParticle : MonoBehaviour namespace Coffee.UIExtensions.Demo
{ {
private UIParticle UiParticle; public class CFX_Demo_With_UIParticle : MonoBehaviour
private Toggle spawnOnUI;
private MonoBehaviour demo;
// Start is called before the first frame update
private void Start()
{ {
UiParticle = GetComponentInChildren<UIParticle>(); private MonoBehaviour _demo;
spawnOnUI = GetComponentInChildren<Toggle>(); private Toggle _spawnOnUI;
private UIParticle _uiParticle;
demo = FindObjectOfType("CFX_Demo_New") as MonoBehaviour // Start is called before the first frame update
?? FindObjectOfType("WFX_Demo_New") as MonoBehaviour; private void Start()
SetCanvasWidth(800);
SetCanvasRenderOverlay(true);
}
private Object FindObjectOfType(string typeName)
{
var type = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.FirstOrDefault(x => x.Name == typeName);
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); _uiParticle = GetComponentInChildren<UIParticle>();
_spawnOnUI = GetComponentInChildren<Toggle>();
_demo = FindObjectOfType("CFX_Demo_New") as MonoBehaviour
?? FindObjectOfType("WFX_Demo_New") as MonoBehaviour;
SetCanvasWidth(800);
SetCanvasRenderOverlay(true);
} }
var particle = demo.GetType() // Update is called once per frame
.GetMethod("spawnParticle", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) private void Update()
.Invoke(demo, new object[0]) as GameObject;
particle.transform.localScale = Vector3.one;
UiParticle.SetParticleSystemInstance(particle);
}
public void SetCanvasWidth(int width)
{
var scaler = GetComponentInParent<CanvasScaler>();
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 0;
var resolution = scaler.referenceResolution;
resolution.x = width;
scaler.referenceResolution = resolution;
}
public void SetCanvasRenderOverlay(bool enable)
{
var canvas = GetComponentInParent<Canvas>();
if (enable)
{ {
canvas.renderMode = RenderMode.ScreenSpaceOverlay; if (!_spawnOnUI.isOn || !_demo || !Input.GetMouseButtonDown(0)) return;
}
else
{
canvas.worldCamera = Camera.main;
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.planeDistance = 5;
}
}
public void LoadScene(string scene) foreach (Transform child in _uiParticle.transform)
{ {
SceneManager.LoadScene(scene); 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())
.FirstOrDefault(x => x.Name == typeName);
return type == null ? null : FindObjectOfType(type);
}
public void SetCanvasWidth(int width)
{
var scaler = GetComponentInParent<CanvasScaler>();
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 0;
var resolution = scaler.referenceResolution;
resolution.x = width;
scaler.referenceResolution = resolution;
}
public void SetCanvasRenderOverlay(bool enable)
{
var canvas = GetComponentInParent<Canvas>();
if (enable)
{
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
}
else
{
canvas.worldCamera = Camera.main;
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.planeDistance = 5;
}
}
public void LoadScene(string scene)
{
SceneManager.LoadScene(scene);
}
} }
} }

View File

@ -1,4 +1,4 @@
Cartoon FX & War FX Demo Cartoon FX & War FX Demo
=== ===
Please import assets "Cartoon FX Free" and/or "War FX Free" from Unity asset store. Please import assets "Cartoon FX Free" and/or "War FX Free" from Unity asset store.

View File

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

View File

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

View File

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

View File

@ -1,30 +1,37 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions.Demo namespace Coffee.UIExtensions.Demo
{ {
public class UIParticle_Demo_UIParticleController : MonoBehaviour 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) 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; uip.scale = scale;
} }

View File

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

View File

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

View File

@ -1,10 +1,11 @@
using System; using System;
using UnityEngine; using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Serialization; using UnityEngine.Serialization;
using UnityEngine.UI; using UnityEngine.UI;
#if UNITY_EDITOR #if UNITY_EDITOR
using UnityEditor; using UnityEditor;
using UnityEditorInternal;
#endif #endif
namespace Coffee.NanoMonitor namespace Coffee.NanoMonitor
@ -13,24 +14,29 @@ namespace Coffee.NanoMonitor
[CustomEditor(typeof(NanoMonitor))] [CustomEditor(typeof(NanoMonitor))]
internal class NanoMonitorEditor : Editor internal class NanoMonitorEditor : Editor
{ {
private UnityEditorInternal.ReorderableList m_MonitorItemList; private ReorderableList _monitorItemList;
private void OnEnable() private void OnEnable()
{ {
var items = serializedObject.FindProperty("m_CustomMonitorItems"); var items = serializedObject.FindProperty("m_CustomMonitorItems");
m_MonitorItemList = new UnityEditorInternal.ReorderableList(serializedObject, items) _monitorItemList = new ReorderableList(serializedObject, items)
{ {
draggable = false, 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, _, __) => 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; var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 80; 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; 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(); base.OnInspectorGUI();
m_MonitorItemList.DoLayoutList(); _monitorItemList.DoLayoutList();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
} }
@ -52,38 +58,50 @@ namespace Coffee.NanoMonitor
// Serialize Members. // Serialize Members.
//################################ //################################
// Settings // Settings
[Header("Settings")] [SerializeField] [Range(0.01f, 2f)] [Header("Settings")]
[SerializeField]
[Range(0.01f, 2f)]
private float m_Interval = 1f; private float m_Interval = 1f;
[SerializeField] [Range(0, 3)] private int m_Precision = 2; [SerializeField]
[SerializeField] private Font m_Font; [Range(0, 3)]
private int m_Precision = 2;
[SerializeField]
private Font m_Font;
// Foldout // Foldout
[Header("Foldout")] [SerializeField] private bool m_Opened = false; [Header("Foldout")]
[SerializeField]
private bool m_Opened;
[FormerlySerializedAs("m_Collapse")] [SerializeField] [FormerlySerializedAs("m_Collapse")]
private GameObject m_FoldoutObject = null; [SerializeField]
private GameObject m_FoldoutObject;
[SerializeField] private Button m_OpenButton = null; [SerializeField]
[SerializeField] private Button m_CloseButton = null; private Button m_OpenButton;
[SerializeField]
private Button m_CloseButton;
// View // View
[Header("View")] [SerializeField] private MonitorUI m_Fps = null; [Header("View")]
[SerializeField] private MonitorUI m_Gc = null; [SerializeField]
[SerializeField] private MonitorUI m_MonoUsage = null; private MonitorUI m_Fps;
[SerializeField] private MonitorUI m_UnityUsage = null;
[HideInInspector] [SerializeField] private CustomMonitorItem[] m_CustomMonitorItems = new CustomMonitorItem[0]; [SerializeField]
private MonitorUI m_Gc;
[SerializeField]
private MonitorUI m_MonoUsage;
//################################ [SerializeField]
// Public Members. private MonitorUI m_UnityUsage;
//################################
public void Clean() [HideInInspector]
{ [SerializeField]
Resources.UnloadUnusedAssets(); private CustomMonitorItem[] m_CustomMonitorItems = new CustomMonitorItem[0];
GC.Collect();
}
//################################ //################################
@ -93,44 +111,75 @@ namespace Coffee.NanoMonitor
private double _fpsElapsed; private double _fpsElapsed;
private int _frames; 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; _frames = 0;
_elapsed = m_Interval; _elapsed %= m_Interval;
_fpsElapsed = 0; _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 #if UNITY_EDITOR
private void OnValidate() private void OnValidate()
{ {
@ -264,5 +242,55 @@ namespace Coffee.NanoMonitor
} }
} }
#endif #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;
using UnityEngine.UI; using UnityEngine.UI;
namespace Coffee.NanoMonitor{ namespace Coffee.NanoMonitor
{
public class FixedFont public class FixedFont
{ {
private readonly UIVertex[] _tmpVerts = new UIVertex[4]; private const string k_Characters =
private static readonly Dictionary<Font, FixedFont> _fonts = new Dictionary<Font, FixedFont>(); "_!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
private static readonly TextGenerator _textGenerator = new TextGenerator(100);
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, scaleFactor = 1,
horizontalOverflow = HorizontalWrapMode.Overflow, horizontalOverflow = HorizontalWrapMode.Overflow,
@ -19,25 +22,33 @@ namespace Coffee.NanoMonitor{
color = Color.white color = Color.white
}; };
private int _resolution = 0;
private readonly Font _font; private readonly Font _font;
private UIVertex[] _verts; private readonly UIVertex[] _tmpVerts = new UIVertex[4];
private UICharInfo[] _charInfos; private UICharInfo[] _charInfos;
private int _resolution;
private UIVertex[] _verts;
private FixedFont(Font font) private FixedFont(Font font)
{ {
_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) public static FixedFont GetOrCreate(Font font)
{ {
if (font == null) return null; 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); data = new FixedFont(font);
_fonts.Add(font, data); s_Fonts.Add(font, data);
return data; return data;
} }
@ -61,18 +72,20 @@ namespace Coffee.NanoMonitor{
if (_resolution == currentResolution) return; if (_resolution == currentResolution) return;
_resolution = currentResolution; _resolution = currentResolution;
_settings.font = _font; s_Settings.font = _font;
_textGenerator.Invalidate(); s_TextGenerator.Invalidate();
_textGenerator.Populate("_!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", _settings); s_TextGenerator.Populate(k_Characters, s_Settings);
_verts = _textGenerator.GetVerticesArray(); _verts = s_TextGenerator.GetVerticesArray();
_charInfos = _textGenerator.GetCharactersArray(); _charInfos = s_TextGenerator.GetCharactersArray();
float offsetX = 0; float offsetX = 0;
for (var i = 0; i < _verts.Length; i++) for (var i = 0; i < _verts.Length; i++)
{ {
if ((i & 3) == 0) if ((i & 3) == 0)
{
offsetX = _verts[i].position.x; offsetX = _verts[i].position.x;
}
var v = _verts[i]; var v = _verts[i];
v.position -= new Vector3(offsetX, 0); v.position -= new Vector3(offsetX, 0);
@ -118,18 +131,27 @@ namespace Coffee.NanoMonitor{
for (var i = 0; i < 4; i++) for (var i = 0; i < 4; i++)
{ {
_tmpVerts[i] = new UIVertex(); _tmpVerts[i] = new UIVertex
_tmpVerts[i].uv0 = uv; {
_tmpVerts[i].color = color; uv0 = uv,
color = color
};
if (i == 0) switch (i)
_tmpVerts[i].position = new Vector2(-size.x, -size.y) + offset; {
else if (i == 1) case 0:
_tmpVerts[i].position = new Vector2(-size.x, size.y) + offset; _tmpVerts[i].position = new Vector2(-size.x, -size.y) + offset;
else if (i == 2) break;
_tmpVerts[i].position = new Vector2(size.x, size.y) + offset; case 1:
else _tmpVerts[i].position = new Vector2(-size.x, size.y) + offset;
_tmpVerts[i].position = new Vector2(size.x, -size.y) + offset; break;
case 2:
_tmpVerts[i].position = new Vector2(size.x, size.y) + offset;
break;
case 3:
_tmpVerts[i].position = new Vector2(size.x, -size.y) + offset;
break;
}
} }
toFill.AddUIVertexQuad(_tmpVerts); toFill.AddUIVertexQuad(_tmpVerts);

View File

@ -1,50 +1,46 @@
using System.Text; using System.Text;
using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
namespace Coffee.NanoMonitor{ namespace Coffee.NanoMonitor
{
#if UNITY_EDITOR #if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(MonitorUI))] [CustomEditor(typeof(MonitorUI))]
public class MonitorTextEditor : Editor public class MonitorTextEditor : Editor
{ {
private SerializedProperty m_Mode; private SerializedProperty _color;
private SerializedProperty m_TextAnchor; private SerializedProperty _font;
private SerializedProperty m_FontSize; private SerializedProperty _fontSize;
private SerializedProperty m_Font; private SerializedProperty _mode;
private SerializedProperty m_Text; private SerializedProperty _text;
private SerializedProperty m_Color; private SerializedProperty _textAnchor;
private void OnEnable() private void OnEnable()
{ {
m_Mode = serializedObject.FindProperty("m_Mode"); _mode = serializedObject.FindProperty("m_Mode");
_text = serializedObject.FindProperty("m_Text");
m_Text = serializedObject.FindProperty("m_Text"); _color = serializedObject.FindProperty("m_Color");
m_Color = serializedObject.FindProperty("m_Color");
var fontData = serializedObject.FindProperty("m_FontData"); var fontData = serializedObject.FindProperty("m_FontData");
m_FontSize = fontData.FindPropertyRelative("m_FontSize"); _fontSize = fontData.FindPropertyRelative("m_FontSize");
m_Font = fontData.FindPropertyRelative("m_Font"); _font = fontData.FindPropertyRelative("m_Font");
m_TextAnchor = serializedObject.FindProperty("m_TextAnchor"); _textAnchor = serializedObject.FindProperty("m_TextAnchor");
} }
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.PropertyField(m_Mode); EditorGUILayout.PropertyField(_mode);
if ((MonitorUI.Mode) m_Mode.intValue == MonitorUI.Mode.Text) if ((MonitorUI.Mode)_mode.intValue == MonitorUI.Mode.Text)
{ {
EditorGUILayout.PropertyField(m_Text); EditorGUILayout.PropertyField(_text);
EditorGUILayout.PropertyField(m_FontSize); EditorGUILayout.PropertyField(_fontSize);
EditorGUILayout.PropertyField(m_TextAnchor); EditorGUILayout.PropertyField(_textAnchor);
EditorGUILayout.PropertyField(_font);
//EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(m_Font);
//EditorGUI.EndDisabledGroup();
} }
EditorGUILayout.PropertyField(m_Color); EditorGUILayout.PropertyField(_color);
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
@ -56,7 +52,7 @@ namespace Coffee.NanoMonitor{
public enum Mode public enum Mode
{ {
Text, Text,
Fill, Fill
} }
public enum TextAnchor public enum TextAnchor
@ -74,26 +70,38 @@ namespace Coffee.NanoMonitor{
[SerializeField] private TextAnchor m_TextAnchor; [SerializeField] private TextAnchor m_TextAnchor;
//################################
// Private Members.
//################################
private readonly StringBuilder _sb = new StringBuilder(64);
//################################ //################################
// Public Members. // Public Members.
//################################ //################################
public override string text public override string text
{ {
get => m_StringBuilder.ToString(); get
{
return _sb.ToString();
}
set set
{ {
m_Text = value; m_Text = value;
if (m_StringBuilder.IsEqual(m_Text)) return; if (_sb.IsEqual(m_Text)) return;
m_StringBuilder.Length = 0; _sb.Length = 0;
m_StringBuilder.Append(m_Text); _sb.Append(m_Text);
SetVerticesDirty(); SetVerticesDirty();
} }
} }
public TextAnchor textAnchor public TextAnchor textAnchor
{ {
get => m_TextAnchor; get
{
return m_TextAnchor;
}
set set
{ {
if (m_TextAnchor == value) return; if (m_TextAnchor == value) return;
@ -105,44 +113,11 @@ namespace Coffee.NanoMonitor{
public override bool raycastTarget public override bool raycastTarget
{ {
get => m_Mode == Mode.Fill; get
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(); return m_Mode == Mode.Fill;
fontData.UpdateFont();
} }
set { }
} }
@ -151,19 +126,17 @@ namespace Coffee.NanoMonitor{
//################################ //################################
protected override void OnEnable() protected override void OnEnable()
{ {
//NaNanoMonitortings.Instance.onFontChanged += UpdateFont;
RegisterDirtyMaterialCallback(UpdateFont); RegisterDirtyMaterialCallback(UpdateFont);
base.OnEnable(); base.OnEnable();
raycastTarget = false; raycastTarget = false;
maskable = false; maskable = false;
m_StringBuilder.Length = 0; _sb.Length = 0;
m_StringBuilder.Append(m_Text); _sb.Append(m_Text);
} }
protected override void OnDisable() protected override void OnDisable()
{ {
//NanNanoMonitorings.Instance.onFontChanged -= UpdateFont;
UnregisterDirtyMaterialCallback(UpdateFont); UnregisterDirtyMaterialCallback(UpdateFont);
base.OnDisable(); base.OnDisable();
@ -174,14 +147,38 @@ namespace Coffee.NanoMonitor{
{ {
base.OnValidate(); base.OnValidate();
if (!m_StringBuilder.IsEqual(m_Text)) if (!_sb.IsEqual(m_Text))
{ {
m_StringBuilder.Length = 0; _sb.Length = 0;
m_StringBuilder.Append(m_Text); _sb.Append(m_Text);
} }
} }
#endif #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) protected override void OnPopulateMesh(VertexHelper toFill)
{ {
toFill.Clear(); toFill.Clear();
@ -197,7 +194,7 @@ namespace Coffee.NanoMonitor{
return; return;
} }
var scale = (float) fontSize / fontData.fontSize; var scale = (float)fontSize / fontData.fontSize;
float offset = 0; float offset = 0;
switch (textAnchor) switch (textAnchor)
{ {
@ -205,20 +202,26 @@ namespace Coffee.NanoMonitor{
offset = rectTransform.rect.xMin; offset = rectTransform.rect.xMin;
break; break;
case TextAnchor.Center: case TextAnchor.Center:
for (var i = 0; i < m_StringBuilder.Length; i++) for (var i = 0; i < _sb.Length; i++)
offset = fontData.Layout(m_StringBuilder[i], offset, scale); {
offset = fontData.Layout(_sb[i], offset, scale);
}
offset = -offset / 2; offset = -offset / 2;
break; break;
case TextAnchor.Right: case TextAnchor.Right:
for (var i = 0; i < m_StringBuilder.Length; i++) for (var i = 0; i < _sb.Length; i++)
offset = fontData.Layout(m_StringBuilder[i], offset, scale); {
offset = fontData.Layout(_sb[i], offset, scale);
}
offset = rectTransform.rect.xMax - offset; offset = rectTransform.rect.xMax - offset;
break; 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 #if UNITY_EDITOR
using UnityEditor; using UnityEditor;
#endif #endif
@ -11,12 +12,17 @@ namespace Coffee.NanoMonitor
{ {
public override void OnGUI(Rect p, SerializedProperty property, GUIContent label) 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")); var pos = new Rect(p.x, p.y + 18 * 0, p.width, 16);
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 1, p.width, 16), property.FindPropertyRelative("m_Format")); EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Text"));
pos.y += 18;
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Format"));
pos.y += 18;
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 2, p.width, 16), property.FindPropertyRelative("m_Arg0")); EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Arg0"));
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 3, p.width, 16), property.FindPropertyRelative("m_Arg1")); pos.y += 18;
EditorGUI.PropertyField(new Rect(p.x, p.y + 18 * 4, p.width, 16), property.FindPropertyRelative("m_Arg2")); EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Arg1"));
pos.y += 18;
EditorGUI.PropertyField(pos, property.FindPropertyRelative("m_Arg2"));
EditorGUI.indentLevel--; EditorGUI.indentLevel--;
property.serializedObject.ApplyModifiedProperties(); property.serializedObject.ApplyModifiedProperties();
@ -29,19 +35,21 @@ namespace Coffee.NanoMonitor
} }
#endif #endif
[System.Serializable] [Serializable]
public class CustomMonitorItem public class CustomMonitorItem
{ {
[SerializeField] private MonitorUI m_Text = null; [SerializeField] private MonitorUI m_Text;
[SerializeField] private string m_Format = ""; [SerializeField] private string m_Format = "";
[SerializeField] private NumericProperty m_Arg0 = null; [SerializeField] private NumericProperty m_Arg0;
[SerializeField] private NumericProperty m_Arg1 = null; [SerializeField] private NumericProperty m_Arg1;
[SerializeField] private NumericProperty m_Arg2 = null; [SerializeField] private NumericProperty m_Arg2;
public void UpdateText() public void UpdateText()
{ {
if (m_Text) if (m_Text)
{
m_Text.SetText(m_Format, m_Arg0.Get(), m_Arg1.Get(), m_Arg2.Get()); 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEngine;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor; using UnityEditor;
using UnityEngine;
#endif
namespace Coffee.NanoMonitor namespace Coffee.NanoMonitor
{ {
@ -20,39 +16,45 @@ namespace Coffee.NanoMonitor
private static readonly Dictionary<Type, string> s_SupportedTypes = new Dictionary<Type, string> private static readonly Dictionary<Type, string> s_SupportedTypes = new Dictionary<Type, string>
{ {
{typeof(bool), "bool"}, { typeof(bool), "bool" },
{typeof(sbyte), "sbyte"}, { typeof(sbyte), "sbyte" },
{typeof(short), "short"}, { typeof(short), "short" },
{typeof(int), "int"}, { typeof(int), "int" },
{typeof(long), "long"}, { typeof(long), "long" },
{typeof(byte), "byte"}, { typeof(byte), "byte" },
{typeof(ushort), "ushort"}, { typeof(ushort), "ushort" },
{typeof(uint), "uint"}, { typeof(uint), "uint" },
{typeof(ulong), "ulong"}, { typeof(ulong), "ulong" },
{typeof(float), "float"}, { typeof(float), "float" },
{typeof(double), "double"}, { typeof(double), "double" },
{typeof(decimal), "decimal"}, { typeof(decimal), "decimal" }
}; };
private static void Init() private static void Init()
{ {
if (s_PropertyMenu != null) return; if (s_PropertyMenu != null) return;
const BindingFlags bindingFlags = BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Static
| BindingFlags.GetProperty;
var properties = AppDomain.CurrentDomain.GetAssemblies() var properties = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes()) .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)) .Where(pi => pi.GetMethod != null && s_SupportedTypes.ContainsKey(pi.PropertyType))
.OrderBy(pi => ConvertToMenuItem(pi, false)) .OrderBy(pi => ConvertToMenuItem(pi, false))
.ToArray(); .ToArray();
s_PropertyMenu = new GenericMenu(); 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("No Property"), false,
s_PropertyMenu.AddItem(new GUIContent("(Non Public Properties)/"), false, ()=>{}); arg => s_OnMenuSelected?.Invoke(arg as PropertyInfo), null);
s_PropertyMenu.AddItem(new GUIContent("(Non Public Properties)/"), false, () => { });
s_PropertyMenu.AddSeparator(""); s_PropertyMenu.AddSeparator("");
foreach (var pi in properties) 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; var type = p.DeclaringType;
if (type == null) return ""; 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 typeName = type.FullName;
var asmName = type.Assembly.GetName().Name; var asmName = type.Assembly.GetName().Name;
if (asmName == "UnityEngine.CoreModule") if (asmName == "UnityEngine.CoreModule")
{
asmName = "UnityEngine"; asmName = "UnityEngine";
}
return propertyType return propertyType
? $"{category}{asmName}/{typeName}/{s_SupportedTypes[p.PropertyType]} {p.Name}" ? $"{category}{asmName}/{typeName}/{s_SupportedTypes[p.PropertyType]} {p.Name}"
: $"{category}{asmName}/{typeName}/{p.Name}"; : $"{category}{asmName}/{typeName}/{p.Name}";
@ -84,7 +92,9 @@ namespace Coffee.NanoMonitor
{ {
s_OnMenuSelected = p => 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(); property.serializedObject.ApplyModifiedProperties();
}; };
s_PropertyMenu.DropDown(position); s_PropertyMenu.DropDown(position);
@ -104,6 +114,22 @@ namespace Coffee.NanoMonitor
[SerializeField] private string m_Path = ""; [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. // Public Members.
//################################ //################################
@ -112,12 +138,6 @@ namespace Coffee.NanoMonitor
return _get?.Invoke() ?? -1; return _get?.Invoke() ?? -1;
} }
//################################
// Private Members.
//################################
private Func<double> _get = null;
private static PropertyInfo GetPropertyInfo(string path) private static PropertyInfo GetPropertyInfo(string path)
{ {
var p = path.Split(';'); var p = path.Split(';');
@ -126,14 +146,14 @@ namespace Coffee.NanoMonitor
var type = Type.GetType(p[0]); var type = Type.GetType(p[0]);
if (type == null) 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; return null;
} }
var pInfo = type.GetProperty(p[1], BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Static); var pInfo = type.GetProperty(p[1], BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Static);
if (pInfo == null) 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; return pInfo;
@ -145,55 +165,72 @@ namespace Coffee.NanoMonitor
switch (Type.GetTypeCode(mInfo.ReturnType)) switch (Type.GetTypeCode(mInfo.ReturnType))
{ {
case TypeCode.Boolean: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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; 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; 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++) 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); number = Math.Truncate(number);
var sign = number < 0; var sign = number < 0;
@ -68,8 +69,8 @@ namespace Coffee.NanoMonitor
var n = Math.Truncate(number % 10); var n = Math.Truncate(number % 10);
number /= 10; number /= 10;
sb.Append((char) (n + 48)); sb.Append((char)(n + 48));
} while (1 <= number || (sb.Length - startIndex) < padding); } while (1 <= number || sb.Length - startIndex < padding);
if (sign) if (sign)
{ {
@ -82,11 +83,12 @@ namespace Coffee.NanoMonitor
sb.Alignment(alignment, startIndex, endIndex); 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 integer = Math.Truncate(number);
var startIndex = sb.Length; var startIndex = sb.Length;
sb.AppendInteger(integer, padding, precision, 0); sb.AppendInteger(integer, padding, 0);
if (0 < precision) if (0 < precision)
{ {
@ -96,8 +98,8 @@ namespace Coffee.NanoMonitor
for (var p = 0; p < precision; p++) for (var p = 0; p < precision; p++)
{ {
number *= 10; number *= 10;
integer = (long) number; integer = (long)number;
sb.Append((char) (integer + 48)); sb.Append((char)(integer + 48));
number -= integer; number -= integer;
number = Math.Round(number, precision); number = Math.Round(number, precision);
} }
@ -106,20 +108,18 @@ namespace Coffee.NanoMonitor
sb.Alignment(alignment, startIndex, sb.Length - 1); 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) while (start < end)
{ {
var c = sb[start]; // swap
sb[start] = sb[end]; (sb[start], sb[end]) = (sb[end], sb[start]);
sb[end] = c;
start++; start++;
end--; 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; if (alignment == 0) return;
@ -129,9 +129,8 @@ namespace Coffee.NanoMonitor
sb.Append(' ', alignment - len); sb.Append(' ', alignment - len);
for (var i = 0; i < len; i++) for (var i = 0; i < len; i++)
{ {
var c = sb[end - i]; // swap
sb[end - i] = sb[start + alignment - i - 1]; (sb[end - i], sb[start + alignment - i - 1]) = (sb[start + alignment - i - 1], sb[end - i]);
sb[start + alignment - i - 1] = c;
} }
} }
else if (alignment < 0 && len < -alignment) 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; argIndex = -1;
padding = 0; padding = 0;

View File

@ -1,26 +1,35 @@
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions.Demo namespace Coffee.UIExtensions.Demo
{ {
public class UIParticle_PerformanceDemo : MonoBehaviour public class UIParticle_PerformanceDemo : MonoBehaviour
{ {
public GameObject copyOrigin; [FormerlySerializedAs("copyOrigin")]
public int copyCount; [SerializeField]
public Canvas root; private GameObject m_CopyOrigin;
[FormerlySerializedAs("copyCount")]
[SerializeField]
public int m_CopyCount;
[FormerlySerializedAs("root")]
[SerializeField]
public Canvas m_RootCanvas;
private void Start() private void Start()
{ {
Application.targetFrameRate = 60; Application.targetFrameRate = 60;
if (copyOrigin) if (m_CopyOrigin)
{ {
copyOrigin.SetActive(false); m_CopyOrigin.SetActive(false);
var parent = copyOrigin.transform.parent; var parent = m_CopyOrigin.transform.parent;
for (var i = 0; i < copyCount; i++) for (var i = 0; i < m_CopyCount; i++)
{ {
var go = GameObject.Instantiate(copyOrigin, parent, false); var go = Instantiate(m_CopyOrigin, parent, false);
go.name = string.Format("{0} {1}", copyOrigin.name, i + 1); go.name = string.Format("{0} {1}", m_CopyOrigin.name, i + 1);
go.hideFlags = HideFlags.DontSave; go.hideFlags = HideFlags.DontSave;
go.SetActive(true); 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) foreach (var uip in m_RootCanvas.GetComponentsInChildren<UIParticle>(true))
Screen.SetResolution(720, 720, false);
else if (Screen.width == 720 && Screen.height == 720)
Screen.SetResolution(720, 400, false);
else
Screen.SetResolution(400, 720, false);
}
public void EnableAnimations(bool enabled)
{
foreach (var animator in FindObjectsOfType<Animator>())
{ {
animator.enabled = enabled; uip.enabled = flag;
} }
}
public void UIParticle_Enable(bool enabled) if (!flag)
{
foreach (var uip in root.GetComponentsInChildren<UIParticle>(true))
{
uip.enabled = enabled;
}
if (!enabled)
{ {
foreach (var ps in FindObjectsOfType<ParticleSystem>()) 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; uip.groupMaxId = flag
} ? 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;
} }
} }
@ -102,34 +80,5 @@ namespace Coffee.UIExtensions.Demo
ps.transform.localScale = new Vector3(scale, scale, scale); 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_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0} m_CustomReflection: {fileID: 0}
m_Sun: {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 m_UseRadianceAmbientProbe: 0
--- !u!157 &3 --- !u!157 &3
LightmapSettings: LightmapSettings:
@ -757,85 +757,6 @@ Transform:
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 2 m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 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 --- !u!1 &496349153
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -972,12 +893,12 @@ PrefabInstance:
- target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
propertyPath: m_AnchoredPosition.x propertyPath: m_AnchoredPosition.x
value: 240.00002 value: 240
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
propertyPath: m_AnchoredPosition.y propertyPath: m_AnchoredPosition.y
value: -179.99998 value: -180
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 864895766138689324, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
@ -1160,6 +1081,85 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 608849065} m_GameObject: {fileID: 608849065}
m_CullTransparentMesh: 0 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 --- !u!1 &879285143
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1287,9 +1287,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 3a595cf9cf93f4e0686f247a5e7e19ce, type: 3} m_Script: {fileID: 11500000, guid: 3a595cf9cf93f4e0686f247a5e7e19ce, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
copyOrigin: {fileID: 1581241706} m_CopyOrigin: {fileID: 1581241706}
copyCount: 266 m_CopyCount: 266
root: {fileID: 919637305} m_RootCanvas: {fileID: 919637305}
--- !u!4 &903906985 --- !u!4 &903906985
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1505,11 +1505,6 @@ PrefabInstance:
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 151878254403844079, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 1002673091}
- target: {fileID: 323826319202443003, guid: 784696794bc6345bc80bf49623581c2e, - target: {fileID: 323826319202443003, guid: 784696794bc6345bc80bf49623581c2e,
type: 3} type: 3}
propertyPath: m_AnchorMin.y propertyPath: m_AnchorMin.y
@ -1530,11 +1525,6 @@ PrefabInstance:
propertyPath: m_AnchoredPosition.y propertyPath: m_AnchoredPosition.y
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1087632561348207336, guid: 784696794bc6345bc80bf49623581c2e,
type: 3}
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 1002673092}
- target: {fileID: 1458366075964155596, guid: 784696794bc6345bc80bf49623581c2e, - target: {fileID: 1458366075964155596, guid: 784696794bc6345bc80bf49623581c2e,
type: 3} type: 3}
propertyPath: m_AnchorMin.y propertyPath: m_AnchorMin.y
@ -1564,7 +1554,7 @@ PrefabInstance:
type: 3} type: 3}
propertyPath: m_CustomMonitorItems.Array.data[1].m_Text propertyPath: m_CustomMonitorItems.Array.data[1].m_Text
value: value:
objectReference: {fileID: 455490118} objectReference: {fileID: 733597320}
- target: {fileID: 4567906826058368312, guid: 784696794bc6345bc80bf49623581c2e, - target: {fileID: 4567906826058368312, guid: 784696794bc6345bc80bf49623581c2e,
type: 3} type: 3}
propertyPath: m_CustomMonitorItems.Array.data[1].m_Format propertyPath: m_CustomMonitorItems.Array.data[1].m_Format
@ -1630,16 +1620,6 @@ PrefabInstance:
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
objectReference: {fileID: 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, - target: {fileID: 5758968653464739090, guid: 784696794bc6345bc80bf49623581c2e,
type: 3} type: 3}
propertyPath: m_SizeDelta.y propertyPath: m_SizeDelta.y
@ -1803,54 +1783,6 @@ RectTransform:
type: 3} type: 3}
m_PrefabInstance: {fileID: 1002673087} m_PrefabInstance: {fileID: 1002673087}
m_PrefabAsset: {fileID: 0} 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 --- !u!1 &1023851109
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -3632,6 +3564,11 @@ PrefabInstance:
m_Modification: m_Modification:
m_TransformParent: {fileID: 1924051224} m_TransformParent: {fileID: 1924051224}
m_Modifications: m_Modifications:
- target: {fileID: 864895764861262036, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3}
propertyPath: m_IgnoreCanvasScaler
value: 1
objectReference: {fileID: 0}
- target: {fileID: 864895765507486433, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 864895765507486433, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
propertyPath: m_AnchorMax.x propertyPath: m_AnchorMax.x
@ -3790,17 +3727,17 @@ PrefabInstance:
- target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
propertyPath: m_LocalScale.x propertyPath: m_LocalScale.x
value: 0 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
propertyPath: m_LocalScale.y propertyPath: m_LocalScale.y
value: 0 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b, - target: {fileID: 1088568576793901561, guid: a4669dfbba654429bbcddf0c9b79fb8b,
type: 3} type: 3}
propertyPath: m_LocalScale.z propertyPath: m_LocalScale.z
value: 0 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
m_RemovedComponents: [] m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: a4669dfbba654429bbcddf0c9b79fb8b, type: 3} m_SourcePrefab: {fileID: 100100000, guid: a4669dfbba654429bbcddf0c9b79fb8b, type: 3}

View File

@ -1,8 +1,9 @@
using UnityEngine; using System;
using UnityEngine;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
[System.Serializable] [Serializable]
public class AnimatableProperty : ISerializationCallbackReceiver public class AnimatableProperty : ISerializationCallbackReceiver
{ {
public enum ShaderPropertyType public enum ShaderPropertyType
@ -11,11 +12,11 @@ namespace Coffee.UIExtensions
Vector, Vector,
Float, Float,
Range, Range,
Texture, Texture
} }
[SerializeField] string m_Name = ""; [SerializeField] private string m_Name = "";
[SerializeField] ShaderPropertyType m_Type = ShaderPropertyType.Vector; [SerializeField] private ShaderPropertyType m_Type = ShaderPropertyType.Vector;
public int id { get; private set; } public int id { get; private set; }
public ShaderPropertyType type public ShaderPropertyType type
@ -23,6 +24,15 @@ namespace Coffee.UIExtensions
get { return m_Type; } get { return m_Type; }
} }
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
id = Shader.PropertyToID(m_Name);
}
public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb) public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb)
{ {
if (!material.HasProperty(id)) return; if (!material.HasProperty(id)) return;
@ -31,35 +41,38 @@ namespace Coffee.UIExtensions
{ {
case ShaderPropertyType.Color: case ShaderPropertyType.Color:
var color = mpb.GetColor(id); var color = mpb.GetColor(id);
if (color != default(Color)) if (color != default)
{
material.SetColor(id, color); material.SetColor(id, color);
}
break; break;
case ShaderPropertyType.Vector: case ShaderPropertyType.Vector:
var vector = mpb.GetVector(id); var vector = mpb.GetVector(id);
if (vector != default(Vector4)) if (vector != default)
{
material.SetVector(id, vector); material.SetVector(id, vector);
}
break; break;
case ShaderPropertyType.Float: case ShaderPropertyType.Float:
case ShaderPropertyType.Range: case ShaderPropertyType.Range:
var value = mpb.GetFloat(id); var value = mpb.GetFloat(id);
if (value != default(float)) if (!Mathf.Approximately(value, 0))
{
material.SetFloat(id, value); material.SetFloat(id, value);
}
break; break;
case ShaderPropertyType.Texture: case ShaderPropertyType.Texture:
var tex = mpb.GetTexture(id); var tex = mpb.GetTexture(id);
if (tex != default(Texture)) if (tex != default(Texture))
{
material.SetTexture(id, tex); material.SetTexture(id, tex);
}
break; 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) private static void ImportSample(string jsonGuid, string sampleName)
{ {
var jsonPath = AssetDatabase.GUIDToAssetPath(jsonGuid); 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 json = File.ReadAllText(jsonPath);
var version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value; var version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
var src = string.Format("{0}/Samples~/{1}", packageRoot, sampleName); var src = $"{packageRoot}/Samples~/{sampleName}";
var dst = string.Format("Assets/Samples/{0}/{1}/{2}", k_DisplayName, version, sampleName); var dst = $"Assets/Samples/{k_DisplayName}/{version}/{sampleName}";
var previousPath = GetPreviousSamplePath(k_DisplayName, sampleName); var previousPath = GetPreviousSamplePath(k_DisplayName, sampleName);
// Remove the previous sample directory. // Remove the previous sample directory.
@ -46,33 +51,45 @@ namespace Coffee.UIExtensions
+ previousPath + previousPath
+ "\n\nIt will be deleted when you update. Are you sure you want to continue?"; + "\n\nIt will be deleted when you update. Are you sure you want to continue?";
if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel")) if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel"))
{
return; return;
}
FileUtil.DeleteFileOrDirectory(previousPath); FileUtil.DeleteFileOrDirectory(previousPath);
var metaFile = previousPath + ".meta"; var metaFile = previousPath + ".meta";
if (File.Exists(metaFile)) if (File.Exists(metaFile))
{
FileUtil.DeleteFileOrDirectory(metaFile); FileUtil.DeleteFileOrDirectory(metaFile);
}
} }
if (!Directory.Exists(dst)) if (!Directory.Exists(dst))
{
FileUtil.DeleteFileOrDirectory(dst); FileUtil.DeleteFileOrDirectory(dst);
}
var dstDir = Path.GetDirectoryName(dst); var dstDir = Path.GetDirectoryName(dst);
if (!Directory.Exists(dstDir)) if (!string.IsNullOrEmpty(dstDir) && !Directory.Exists(dstDir))
{
Directory.CreateDirectory(dstDir); Directory.CreateDirectory(dstDir);
}
if (Directory.Exists(src)) if (Directory.Exists(src))
{
FileUtil.CopyFileOrDirectory(src, dst); FileUtil.CopyFileOrDirectory(src, dst);
}
else else
{
throw new DirectoryNotFoundException(src); throw new DirectoryNotFoundException(src);
}
AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive); AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
} }
private static string GetPreviousSamplePath(string displayName, string sampleName) 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); var sampleRootInfo = new DirectoryInfo(sampleRoot);
if (!sampleRootInfo.Exists) return null; 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 #if UNITY_2021_2_OR_NEWER
using UnityEditor.Overlays; using UnityEditor.Overlays;
#else #else
using System.Reflection; using System.Reflection;
#endif #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 namespace Coffee.UIExtensions
{ {
@ -23,7 +28,10 @@ namespace Coffee.UIExtensions
{ {
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
#if UNITY_2022_1_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 #else
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true)] [Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true)]
#endif #endif
@ -53,24 +61,22 @@ namespace Coffee.UIExtensions
private static readonly GUIContent s_ContentRandom = new GUIContent("Random"); private static readonly GUIContent s_ContentRandom = new GUIContent("Random");
private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
private static SerializedObject s_SerializedObject; private static SerializedObject s_SerializedObject;
private static bool s_XYZMode;
#if !SERIALIZE_FIELD_MASKABLE private SerializedProperty _maskable;
private SerializedProperty m_Maskable; private SerializedProperty _scale3D;
#endif private SerializedProperty _animatableProperties;
private SerializedProperty m_Scale3D; private SerializedProperty _meshSharing;
private SerializedProperty m_AnimatableProperties; private SerializedProperty _groupId;
private SerializedProperty m_MeshSharing; private SerializedProperty _groupMaxId;
private SerializedProperty m_GroupId; private SerializedProperty _positionMode;
private SerializedProperty m_GroupMaxId; private SerializedProperty _autoScaling;
private SerializedProperty m_AbsoluteMode;
private ReorderableList _ro; private ReorderableList _ro;
static private bool _xyzMode;
private bool _showMax; private bool _showMax;
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>(); private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
private static readonly List<ParticleSystemVertexStream> s_Streams = new List<ParticleSystemVertexStream>(); private static readonly List<ParticleSystemVertexStream> s_Streams = new List<ParticleSystemVertexStream>();
private static readonly List<string> s_MaskablePropertyNames = new List<string> private static readonly List<string> s_MaskablePropertyNames = new List<string>
{ {
"_Stencil", "_Stencil",
@ -78,21 +84,20 @@ namespace Coffee.UIExtensions
"_StencilOp", "_StencilOp",
"_StencilWriteMask", "_StencilWriteMask",
"_StencilReadMask", "_StencilReadMask",
"_ColorMask", "_ColorMask"
}; };
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
static void Init() private static void Init()
{ {
#if !UNITY_2021_2_OR_NEWER #if !UNITY_2021_2_OR_NEWER
var miSceneViewOverlayWindow = Type.GetType("UnityEditor.SceneViewOverlay, UnityEditor") 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); .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 windowFunctionType = Type.GetType("UnityEditor.SceneViewOverlay+WindowFunction, UnityEditor");
var windowFunctionDelegate = Delegate.CreateDelegate(windowFunctionType, windowFunction.Method); 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 #if UNITY_2019_2_OR_NEWER
//public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, Object target, WindowDisplayOption option, EditorWindow window = null) //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 }; var sceneViewArgs = new object[] { windowTitle, windowFunctionDelegate, 599, null, 2, null };
@ -102,7 +107,7 @@ namespace Coffee.UIExtensions
#endif #endif
#if UNITY_2019_1_OR_NEWER #if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui += _ => { if (s_SerializedObject != null) miSceneViewOverlayWindow.Invoke(null, sceneViewArgs); }; SceneView.duringSceneGui += _ =>
#else #else
SceneView.onSceneGUIDelegate += _ => SceneView.onSceneGUIDelegate += _ =>
#endif #endif
@ -114,25 +119,21 @@ namespace Coffee.UIExtensions
}; };
#endif #endif
Func<SerializedObject> createSerializeObject = () => SerializedObject CreateSerializeObject()
{ {
var uiParticles = Selection.gameObjects var uiParticles = Selection.gameObjects.Select(x => x.GetComponent<ParticleSystem>())
.Select(x => x.GetComponent<ParticleSystem>()) .Where(x => x)
.Where(x => x) .Select(x => x.GetComponentInParent<UIParticle>(true))
.Select(x => x.GetComponentInParent<UIParticle>()) .Where(x => x && x.canvas)
.Where(x => x && x.canvas) .Concat(Selection.gameObjects.Select(x => x.GetComponent<UIParticle>())
.Concat( .Where(x => x && x.canvas))
Selection.gameObjects .Distinct()
.Select(x => x.GetComponent<UIParticle>()) .ToArray();
.Where(x => x && x.canvas)
)
.Distinct()
.ToArray();
return 0 < uiParticles.Length ? new SerializedObject(uiParticles) : null; return 0 < uiParticles.Length ? new SerializedObject(uiParticles) : null;
}; }
s_SerializedObject = createSerializeObject(); s_SerializedObject = CreateSerializeObject();
Selection.selectionChanged += () => s_SerializedObject = createSerializeObject(); Selection.selectionChanged += () => s_SerializedObject = CreateSerializeObject();
} }
//################################ //################################
@ -145,63 +146,71 @@ namespace Coffee.UIExtensions
{ {
base.OnEnable(); base.OnEnable();
m_Maskable = serializedObject.FindProperty("m_Maskable"); _maskable = serializedObject.FindProperty("m_Maskable");
m_Scale3D = serializedObject.FindProperty("m_Scale3D"); _scale3D = serializedObject.FindProperty("m_Scale3D");
m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); _animatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
m_MeshSharing = serializedObject.FindProperty("m_MeshSharing"); _meshSharing = serializedObject.FindProperty("m_MeshSharing");
m_GroupId = serializedObject.FindProperty("m_GroupId"); _groupId = serializedObject.FindProperty("m_GroupId");
m_GroupMaxId = serializedObject.FindProperty("m_GroupMaxId"); _groupMaxId = serializedObject.FindProperty("m_GroupMaxId");
m_AbsoluteMode = serializedObject.FindProperty("m_AbsoluteMode"); _positionMode = serializedObject.FindProperty("m_PositionMode");
_autoScaling = serializedObject.FindProperty("m_AutoScaling");
var sp = serializedObject.FindProperty("m_Particles"); var sp = serializedObject.FindProperty("m_Particles");
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true); _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true)
_ro.elementHeight = (EditorGUIUtility.singleLineHeight * 3) + 4;
_ro.elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2);
_ro.drawElementCallback = (rect, index, _, __) =>
{ {
EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues); elementHeight = EditorGUIUtility.singleLineHeight * 3 + 4,
rect.y += 1; elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2),
rect.height = EditorGUIUtility.singleLineHeight; drawElementCallback = (rect, index, _, __) =>
var p = sp.GetArrayElementAtIndex(index);
EditorGUI.ObjectField(rect, p, GUIContent.none);
rect.x += 15;
rect.width -= 15;
var ps = p.objectReferenceValue as ParticleSystem;
var materials = ps
? new SerializedObject(ps.GetComponent<ParticleSystemRenderer>()).FindProperty("m_Materials")
: null;
rect.y += rect.height + 1;
MaterialField(rect, s_ContentMaterial, materials, 0);
rect.y += rect.height + 1;
MaterialField(rect, s_ContentTrailMaterial, materials, 1);
EditorGUI.EndDisabledGroup();
if (materials != null && materials.serializedObject.hasModifiedProperties)
{ {
materials.serializedObject.ApplyModifiedProperties(); EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues);
} rect.y += 1;
}; rect.height = EditorGUIUtility.singleLineHeight;
_ro.drawHeaderCallback = rect => var p = sp.GetArrayElementAtIndex(index);
{ EditorGUI.ObjectField(rect, p, GUIContent.none);
#if !UNITY_2019_3_OR_NEWER rect.x += 15;
rect.y -= 1; rect.width -= 15;
#endif var ps = p.objectReferenceValue as ParticleSystem;
EditorGUI.LabelField(new Rect(rect.x, rect.y, 150, rect.height), s_ContentRenderingOrder); var materials = ps
? new SerializedObject(ps.GetComponent<ParticleSystemRenderer>()).FindProperty("m_Materials")
if (GUI.Button(new Rect(rect.width - 35, rect.y, 60, rect.height), s_ContentRefresh, EditorStyles.miniButton)) : null;
{ rect.y += rect.height + 1;
foreach (UIParticle t in targets) MaterialField(rect, s_ContentMaterial, materials, 0);
rect.y += rect.height + 1;
MaterialField(rect, s_ContentTrailMaterial, materials, 1);
EditorGUI.EndDisabledGroup();
if (materials != null && materials.serializedObject.hasModifiedProperties)
{ {
t.RefreshParticles(); materials.serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(t); }
},
drawHeaderCallback = rect =>
{
#if !UNITY_2019_3_OR_NEWER
rect.y -= 1;
#endif
var pos = new Rect(rect.x, rect.y, 150, rect.height);
EditorGUI.LabelField(pos, s_ContentRenderingOrder);
pos = new Rect(rect.width - 35, rect.y, 60, rect.height);
if (GUI.Button(pos, s_ContentRefresh, EditorStyles.miniButton))
{
foreach (var uip in targets.OfType<UIParticle>())
{
uip.RefreshParticles();
EditorUtility.SetDirty(uip);
}
} }
} }
}; };
// On select UIParticle, refresh particles. // On select UIParticle, refresh particles.
foreach (UIParticle t in targets) if (!Application.isPlaying)
{ {
if (Application.isPlaying || PrefabUtility.GetPrefabAssetType(t) != PrefabAssetType.NotAPrefab) continue; foreach (var uip in targets.OfType<UIParticle>())
t.RefreshParticles(t.particles); {
if (PrefabUtility.GetPrefabAssetType(uip) != PrefabAssetType.NotAPrefab) continue;
uip.RefreshParticles(uip.particles);
}
} }
} }
@ -230,11 +239,11 @@ namespace Coffee.UIExtensions
serializedObject.Update(); serializedObject.Update();
// Maskable // Maskable
EditorGUILayout.PropertyField(m_Maskable); EditorGUILayout.PropertyField(_maskable);
// Scale // Scale
EditorGUI.BeginDisabledGroup(!m_MeshSharing.hasMultipleDifferentValues && m_MeshSharing.intValue == 4); EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4);
_xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode); s_XYZMode = DrawFloatOrVector3Field(_scale3D, s_XYZMode);
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
// AnimatableProperties // AnimatableProperties
@ -244,12 +253,11 @@ namespace Coffee.UIExtensions
.Where(x => x) .Where(x => x)
.ToArray(); .ToArray();
// Animated properties AnimatablePropertyEditor.Draw(_animatableProperties, mats);
AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats);
// Mesh sharing // Mesh sharing
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
_showMax = DrawMeshSharing(m_MeshSharing, m_GroupId, m_GroupMaxId, _showMax); _showMax = DrawMeshSharing(_meshSharing, _groupId, _groupMaxId, _showMax);
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
{ {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
@ -259,8 +267,11 @@ namespace Coffee.UIExtensions
} }
} }
// Absolute Mode // Position Mode
EditorGUILayout.PropertyField(m_AbsoluteMode); EditorGUILayout.PropertyField(_positionMode);
// Auto Scaling
DrawAutoScaling(_autoScaling, targets.OfType<UIParticle>());
// Target ParticleSystems. // Target ParticleSystems.
EditorGUI.BeginChangeCheck(); 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? // 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) foreach (var mat in current.materials)
{ {
@ -289,15 +314,20 @@ namespace Coffee.UIExtensions
{ {
if (mat.HasProperty(propName)) continue; 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; break;
} }
} }
} }
s_Shaders.Clear(); s_Shaders.Clear();
// UIParticle for trail should be removed. // 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); DestroyUIParticle(current);
} }
@ -313,7 +343,9 @@ namespace Coffee.UIExtensions
{ {
var so = new SerializedObject(allPsRenderers); var so = new SerializedObject(allPsRenderers);
var sp = so.FindProperty("m_ApplyActiveColorSpace"); 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; sp.boolValue = false;
so.ApplyModifiedProperties(); so.ApplyModifiedProperties();
@ -328,12 +360,23 @@ namespace Coffee.UIExtensions
if (2 < s_Streams.Select(GetUsedComponentCount).Sum()) 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) private static int GetUsedComponentCount(ParticleSystemVertexStream s)
{ {
switch (s) switch (s)
@ -389,10 +432,12 @@ namespace Coffee.UIExtensions
case ParticleSystemVertexStream.Custom2XYZW: case ParticleSystemVertexStream.Custom2XYZW:
return 4; return 4;
} }
return 3; 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 || showMax |= spGroupId.intValue != spGroupMaxId.intValue ||
spGroupId.hasMultipleDifferentValues || spGroupId.hasMultipleDifferentValues ||
@ -404,7 +449,10 @@ namespace Coffee.UIExtensions
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
showMax = GUILayout.Toggle(showMax, s_ContentRandom, EditorStyles.miniButton, GUILayout.Width(60)); showMax = GUILayout.Toggle(showMax, s_ContentRandom, EditorStyles.miniButton, GUILayout.Width(60));
if (EditorGUI.EndChangeCheck() && !showMax) if (EditorGUI.EndChangeCheck() && !showMax)
{
spGroupMaxId.intValue = spGroupId.intValue; spGroupMaxId.intValue = spGroupId.intValue;
}
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
EditorGUI.BeginDisabledGroup(spMeshSharing.intValue == 0); EditorGUI.BeginDisabledGroup(spMeshSharing.intValue == 0);
@ -417,20 +465,41 @@ namespace Coffee.UIExtensions
else if (spMeshSharing.intValue == 1 || spMeshSharing.intValue == 4) else if (spMeshSharing.intValue == 1 || spMeshSharing.intValue == 4)
{ {
EditorGUI.BeginDisabledGroup(true); 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.EndDisabledGroup();
} }
EditorGUI.indentLevel--; EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
return showMax; 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 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(); s_SerializedObject.Update();
using (new EditorGUILayout.VerticalScope(GUILayout.Width(220f))) using (new EditorGUILayout.VerticalScope(GUILayout.Width(220f)))
@ -438,37 +507,36 @@ namespace Coffee.UIExtensions
var labelWidth = EditorGUIUtility.labelWidth; var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 100; EditorGUIUtility.labelWidth = 100;
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_Enabled")); EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_Enabled"));
_xyzMode = DrawFloatOrVector3Field(s_SerializedObject.FindProperty("m_Scale3D"), _xyzMode); s_XYZMode = DrawFloatOrVector3Field(s_SerializedObject.FindProperty("m_Scale3D"), s_XYZMode);
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_AbsoluteMode")); EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_PositionMode"));
DrawAutoScaling(s_SerializedObject.FindProperty("m_AutoScaling"), uiParticles);
EditorGUIUtility.labelWidth = labelWidth; EditorGUIUtility.labelWidth = labelWidth;
} }
s_SerializedObject.ApplyModifiedProperties(); s_SerializedObject.ApplyModifiedProperties();
} }
catch catch
{ {
// ignored
} }
} }
private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false) private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false)
{ {
if (!p || ignoreCurrent && target == p) return; if (!p || (ignoreCurrent && target == p)) return;
var cr = p.canvasRenderer; var cr = p.canvasRenderer;
DestroyImmediate(p); DestroyImmediate(p);
DestroyImmediate(cr); 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 #if UNITY_2018_3_OR_NEWER
var stage = PrefabStageUtility.GetCurrentPrefabStage();
if (stage != null && stage.scene.isLoaded) if (stage != null && stage.scene.isLoaded)
{ {
#if UNITY_2020_1_OR_NEWER #if UNITY_2020_1_OR_NEWER
string prefabAssetPath = stage.assetPath; string prefabAssetPath = stage.assetPath;
#else #else
string prefabAssetPath = stage.prefabAssetPath; var prefabAssetPath = stage.prefabAssetPath;
#endif #endif
PrefabUtility.SaveAsPrefabAsset(stage.prefabContentsRoot, prefabAssetPath); PrefabUtility.SaveAsPrefabAsset(stage.prefabContentsRoot, prefabAssetPath);
} }
@ -517,7 +585,10 @@ namespace Coffee.UIExtensions
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
showXyz = GUILayout.Toggle(showXyz, s_Content3D, EditorStyles.miniButton, GUILayout.Width(30)); showXyz = GUILayout.Toggle(showXyz, s_Content3D, EditorStyles.miniButton, GUILayout.Width(30));
if (EditorGUI.EndChangeCheck() && !showXyz) if (EditorGUI.EndChangeCheck() && !showXyz)
{
z.floatValue = y.floatValue = x.floatValue; z.floatValue = y.floatValue = x.floatValue;
}
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
return showXyz; return showXyz;

View File

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

View File

@ -10,7 +10,7 @@ namespace Coffee.UIParticleExtensions
public static Material Add(Material baseMat, Texture texture, int id) public static Material Add(Material baseMat, Texture texture, int id)
{ {
MatEntry e; MatEntry e;
for (var i = 0; i < s_Entries.Count; ++i) for (var i = 0; i < s_Entries.Count; i++)
{ {
e = s_Entries[i]; e = s_Entries[i];
if (e.baseMat != baseMat || e.texture != texture || e.id != id) continue; if (e.baseMat != baseMat || e.texture != texture || e.id != id) continue;
@ -18,15 +18,19 @@ namespace Coffee.UIParticleExtensions
return e.customMat; return e.customMat;
} }
e = new MatEntry(); e = new MatEntry
e.count = 1; {
e.baseMat = baseMat; count = 1,
e.texture = texture; baseMat = baseMat,
e.id = id; texture = texture,
e.customMat = new Material(baseMat); id = id,
e.customMat.hideFlags = HideFlags.HideAndDontSave; customMat = new Material(baseMat)
if (texture) {
e.customMat.mainTexture = texture; name = $"{baseMat.name}_{id}",
hideFlags = HideFlags.HideAndDontSave,
mainTexture = texture ? texture : null
}
};
s_Entries.Add(e); s_Entries.Add(e);
//Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count, baseMat, texture, id); //Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count, baseMat, texture, id);
return e.customMat; return e.customMat;
@ -43,7 +47,8 @@ namespace Coffee.UIParticleExtensions
if (--e.count == 0) 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); //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.baseMat = null;
e.texture = null; e.texture = null;
s_Entries.RemoveAt(i); 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 private class MatEntry
{ {
public Material baseMat; public Material baseMat;
public Material customMat;
public int count; public int count;
public Texture texture; public Material customMat;
public int id; public int id;
public Texture texture;
} }
} }
} }

View File

@ -2,11 +2,11 @@
#define SERIALIZE_FIELD_MASKABLE #define SERIALIZE_FIELD_MASKABLE
#endif #endif
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Coffee.UIParticleExtensions; using Coffee.UIParticleExtensions;
using UnityEngine; using UnityEngine;
using UnityEngine.Rendering; using UnityEngine.Rendering;
using UnityEngine.Serialization;
using UnityEngine.UI; using UnityEngine.UI;
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] [assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
@ -19,7 +19,7 @@ namespace Coffee.UIExtensions
[ExecuteAlways] [ExecuteAlways]
[RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(CanvasRenderer))]
public class UIParticle : MaskableGraphic public class UIParticle : MaskableGraphic, ISerializationCallbackReceiver
{ {
public enum MeshSharing public enum MeshSharing
{ {
@ -27,16 +27,34 @@ namespace Coffee.UIExtensions
Auto, Auto,
Primary, Primary,
PrimarySimulator, PrimarySimulator,
Replica, 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")] [Tooltip("Particle effect scale")]
[SerializeField] [SerializeField]
private Vector3 m_Scale3D = new Vector3(10, 10, 10); 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] [SerializeField]
internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
@ -44,33 +62,44 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private List<ParticleSystem> m_Particles = new List<ParticleSystem>(); private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
[Tooltip("Mesh sharing.None: disable mesh sharing.\nAuto: automatically select Primary/Replica.\nPrimary: provides particle simulation results to the same group.\nPrimary Simulator: Primary, but do not render the particle (simulation only).\nReplica: 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] [SerializeField]
private MeshSharing m_MeshSharing = MeshSharing.None; 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] [SerializeField]
private int m_GroupId = 0; private int m_GroupId;
[SerializeField] [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] [SerializeField]
[Tooltip("The particles will be emitted at the ParticleSystem position.\nMove the UIParticle/ParticleSystem to move the particle.")] [Tooltip("Transform.lossyScale (=world scale) is automatically set to (1, 1, 1), " +
private bool m_AbsoluteMode = false; "to prevent the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.")]
private bool m_AutoScaling = true;
private List<UIParticleRenderer> m_Renderers = new List<UIParticleRenderer>();
#if !SERIALIZE_FIELD_MASKABLE #if !SERIALIZE_FIELD_MASKABLE
[SerializeField] private bool m_Maskable = true; [SerializeField]
private bool m_Maskable = true;
#endif #endif
private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>();
private DrivenRectTransformTracker _tracker;
private Camera _orthoCamera;
private int _groupId; private int _groupId;
private Camera _orthoCamera;
private DrivenRectTransformTracker _tracker;
/// <summary> /// <summary>
/// Should this graphic be considered a target for raycasting? /// Should this graphic be considered a target for ray-casting?
/// </summary> /// </summary>
public override bool raycastTarget public override bool raycastTarget
{ {
@ -79,7 +108,8 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// Mesh sharing.None: disable mesh sharing. /// Mesh sharing.
/// None: disable mesh sharing.
/// Auto: automatically select Primary/Replica. /// Auto: automatically select Primary/Replica.
/// Primary: provides particle simulation results to the same group. /// Primary: provides particle simulation results to the same group.
/// Primary Simulator: Primary, but do not render the particle (simulation only). /// Primary Simulator: Primary, but do not render the particle (simulation only).
@ -92,7 +122,8 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <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> /// </summary>
public int groupId public int groupId
{ {
@ -102,7 +133,9 @@ namespace Coffee.UIExtensions
if (m_GroupId == value) return; if (m_GroupId == value) return;
m_GroupId = value; m_GroupId = value;
if (m_GroupId != m_GroupMaxId) if (m_GroupId != m_GroupMaxId)
{
ResetGroupId(); ResetGroupId();
}
} }
} }
@ -118,14 +151,40 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// Absolute particle position mode. /// Particle position mode.
/// The particles will be emitted at the ParticleSystem position. /// Relative: The particles will be emitted from the scaled position of the ParticleSystem.
/// Move the UIParticle/ParticleSystem to move the particle. /// 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> /// </summary>
public bool absoluteMode public bool absoluteMode
{ {
get { return m_AbsoluteMode; } get { return m_PositionMode == PositionMode.Absolute; }
set { m_AbsoluteMode = value; } 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 internal bool useMeshSharing
@ -135,17 +194,33 @@ namespace Coffee.UIExtensions
internal bool isPrimary 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 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 internal bool canRender
{ {
get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.Replica; } get
{
return m_MeshSharing == MeshSharing.None
|| m_MeshSharing == MeshSharing.Auto
|| m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.Replica;
}
} }
/// <summary> /// <summary>
@ -178,12 +253,12 @@ namespace Coffee.UIExtensions
{ {
get 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; var r = _renderers[i];
yield return m_Renderers[i].material; if (!r || !r.material) continue;
yield return r.material;
} }
yield break;
} }
} }
@ -195,7 +270,78 @@ namespace Coffee.UIExtensions
/// <summary> /// <summary>
/// Paused. /// Paused.
/// </summary> /// </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()
{
m_IgnoreCanvasScaler = !m_AutoScaling;
m_AbsoluteMode = m_PositionMode == PositionMode.Absolute;
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (m_IgnoreCanvasScaler)
{
m_AutoScaling = false;
}
if (m_AbsoluteMode)
{
m_PositionMode = PositionMode.Absolute;
}
}
public void Play() public void Play()
{ {
@ -220,6 +366,24 @@ namespace Coffee.UIExtensions
isPaused = true; 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() public void Clear()
{ {
particles.Exec(p => p.Clear()); particles.Exec(p => p.Clear());
@ -239,14 +403,10 @@ namespace Coffee.UIExtensions
{ {
var go = child.gameObject; var go = child.gameObject;
go.SetActive(false); go.SetActive(false);
if (!destroyOldParticles) continue; if (destroyOldParticles)
{
#if UNITY_EDITOR Misc.Destroy(go);
if (!Application.isPlaying) }
DestroyImmediate(go);
else
#endif
Destroy(go);
} }
var tr = instance.transform; var tr = instance.transform;
@ -271,14 +431,17 @@ namespace Coffee.UIExtensions
private void RefreshParticles(GameObject root) private void RefreshParticles(GameObject root)
{ {
if (!root) return; if (!root) return;
root.GetComponentsInChildren(particles); root.GetComponentsInChildren(true, particles);
particles.RemoveAll(x => x.GetComponentInParent<UIParticle>() != this); 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; var tsa = ps.textureSheetAnimation;
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0) if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
{
tsa.uvChannelMask = UVChannelFlags.UV0; tsa.uvChannelMask = UVChannelFlags.UV0;
}
} }
RefreshParticles(particles); RefreshParticles(particles);
@ -286,33 +449,42 @@ namespace Coffee.UIExtensions
public void RefreshParticles(List<ParticleSystem> 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; var j = 0;
for (var i = 0; i < particles.Count; i++) for (var i = 0; i < particles.Count; i++)
{ {
if (!particles[i]) continue; var ps = particles[i];
GetRenderer(j++).Set(this, particles[i], false); if (!ps) continue;
if (particles[i].trails.enabled) 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() internal void UpdateTransformScale()
{ {
//var newScale = Vector3.one; parentScale = transform.parent.lossyScale;
//if (uiScaling) if (!autoScaling) return;
//{
// newScale = transform.parent.lossyScale.Inverse(); var newScale = parentScale.Inverse();
//}
var newScale = transform.parent.lossyScale.Inverse();
if (transform.localScale != newScale) if (transform.localScale != newScale)
{ {
transform.localScale = newScale; transform.localScale = newScale;
@ -323,9 +495,10 @@ namespace Coffee.UIExtensions
{ {
if (!isActiveAndEnabled) return; if (!isActiveAndEnabled) return;
foreach (var rend in m_Renderers) for (var i = 0; i < _renderers.Count; i++)
{ {
if (!rend) var r = _renderers[i];
if (!r)
{ {
RefreshParticles(particles); RefreshParticles(particles);
break; break;
@ -333,67 +506,29 @@ namespace Coffee.UIExtensions
} }
var bakeCamera = GetBakeCamera(); 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; var r = _renderers[i];
m_Renderers[i].UpdateMesh(bakeCamera); if (!r) continue;
r.UpdateMesh(bakeCamera);
} }
} }
internal void UpdateParticleCount() 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; var r = _renderers[i];
m_Renderers[i].UpdateParticleCount(); 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() internal void ResetGroupId()
{ {
if (m_GroupId == m_GroupMaxId) _groupId = m_GroupId == m_GroupMaxId
{ ? m_GroupId
_groupId = m_GroupId; : Random.Range(m_GroupId, m_GroupMaxId + 1);
}
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();
} }
protected override void UpdateMaterial() protected override void UpdateMaterial()
@ -407,43 +542,42 @@ namespace Coffee.UIExtensions
{ {
} }
/// <summary>
/// Callback for when properties have been changed by animation.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
}
private void UpdateRendererMaterial() 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; var r = _renderers[i];
m_Renderers[i].maskable = maskable; if (!r) continue;
m_Renderers[i].SetMaterialDirty(); r.maskable = maskable;
r.SetMaterialDirty();
} }
} }
internal UIParticleRenderer GetRenderer(int index) 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() private Camera GetBakeCamera()
{ {
if (!canvas) return Camera.main; if (!canvas) return Camera.main;
// World camera. // Render mode is not ScreenSpaceOverlay, use world camera.
var root = canvas.rootCanvas; 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;
}
// Create ortho-camera. // Create ortho-camera.
if (!_orthoCamera) if (!_orthoCamera)
@ -451,10 +585,7 @@ namespace Coffee.UIExtensions
_orthoCamera = GetComponentInChildren<Camera>(); _orthoCamera = GetComponentInChildren<Camera>();
if (!_orthoCamera) if (!_orthoCamera)
{ {
var go = new GameObject("UIParticleOverlayCamera") var go = new GameObject("UIParticleOverlayCamera") { hideFlags = HideFlags.DontSave };
{
hideFlags = HideFlags.DontSave,
};
go.SetActive(false); go.SetActive(false);
go.transform.SetParent(transform, false); go.transform.SetParent(transform, false);
_orthoCamera = go.AddComponent<Camera>(); _orthoCamera = go.AddComponent<Camera>();
@ -462,7 +593,7 @@ namespace Coffee.UIExtensions
} }
} }
// //
var size = ((RectTransform)root.transform).rect.size; var size = ((RectTransform)root.transform).rect.size;
_orthoCamera.orthographicSize = Mathf.Max(size.x, size.y) * root.scaleFactor; _orthoCamera.orthographicSize = Mathf.Max(size.x, size.y) * root.scaleFactor;
_orthoCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity); _orthoCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity);
@ -471,5 +602,17 @@ namespace Coffee.UIExtensions
return _orthoCamera; 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 Coffee.UIParticleExtensions;
using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using System;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
@ -12,7 +12,13 @@ namespace Coffee.UIExtensions
{ {
Linear, Linear,
Smooth, Smooth,
Sphere, Sphere
}
public enum UpdateMode
{
Normal,
UnscaledTime
} }
[SerializeField] [SerializeField]
@ -24,7 +30,7 @@ namespace Coffee.UIExtensions
[Range(0f, 0.95f)] [Range(0f, 0.95f)]
[SerializeField] [SerializeField]
private float m_DelayRate = 0; private float m_DelayRate;
[Range(0.001f, 100f)] [Range(0.001f, 100f)]
[SerializeField] [SerializeField]
@ -33,73 +39,81 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private Movement m_Movement; private Movement m_Movement;
[SerializeField]
private UpdateMode m_UpdateMode;
[SerializeField] [SerializeField]
private UnityEvent m_OnAttracted; 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 public float delay
{ {
get get { return m_DelayRate; }
{ set { m_DelayRate = value; }
return m_DelayRate;
}
set
{
m_DelayRate = value;
}
} }
public float maxSpeed public float maxSpeed
{ {
get get { return m_MaxSpeed; }
{ set { m_MaxSpeed = value; }
return m_MaxSpeed;
}
set
{
m_MaxSpeed = value;
}
} }
public Movement movement public Movement movement
{ {
get get { return m_Movement; }
{ set { m_Movement = value; }
return m_Movement;
}
set
{
m_Movement = value;
}
} }
public ParticleSystem particleSystem public UpdateMode updateMode
{ {
get get { return m_UpdateMode; }
{ set { m_UpdateMode = value; }
return m_ParticleSystem; }
}
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 set
{ {
m_ParticleSystem = value; m_ParticleSystem = value;
if (!ApplyParticleSystem()) return; ApplyParticleSystem();
enabled = true;
} }
} }
private UIParticle _uiParticle;
private void OnEnable() private void OnEnable()
{ {
if (!ApplyParticleSystem()) return; ApplyParticleSystem();
UIParticleUpdater.Register(this); UIParticleUpdater.Register(this);
} }
private void OnDisable() private void OnDisable()
{ {
_uiParticle = null;
UIParticleUpdater.Unregister(this); UIParticleUpdater.Unregister(this);
} }
private void OnDestroy()
{
_uiParticle = null;
m_ParticleSystem = null;
}
internal void Attract() internal void Attract()
{ {
if (m_ParticleSystem == null) return; if (m_ParticleSystem == null) return;
@ -131,6 +145,7 @@ namespace Coffee.UIExtensions
Debug.LogException(e); Debug.LogException(e);
} }
} }
continue; continue;
} }
@ -157,36 +172,51 @@ namespace Coffee.UIExtensions
var psPos = m_ParticleSystem.transform.position; var psPos = m_ParticleSystem.transform.position;
var attractorPos = transform.position; var attractorPos = transform.position;
var dstPos = attractorPos; var dstPos = attractorPos;
if (m_ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local) var isLocalSpace = m_ParticleSystem.IsLocalSpace();
if (isLocalSpace)
{ {
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos); dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos);
if (isUI)
{
dstPos = dstPos.GetScaled(_uiParticle.transform.localScale, _uiParticle.scale3D.Inverse());
}
} }
else
if (isUI)
{ {
#if UNITY_EDITOR var inverseScale = _uiParticle.parentScale.Inverse();
if (!Application.isPlaying && isUI) dstPos = dstPos.GetScaled(inverseScale, _uiParticle.scale3D.Inverse());
// Relative mode
if (_uiParticle.positionMode == UIParticle.PositionMode.Relative)
{ {
var diff = dstPos - psPos; var diff = _uiParticle.transform.position - psPos;
diff = diff.GetScaled(_uiParticle.transform.localScale, _uiParticle.scale3D.Inverse()); diff.Scale(_uiParticle.scale3D - inverseScale);
return psPos + diff; diff.Scale(_uiParticle.scale3D.Inverse());
dstPos += diff;
}
#if UNITY_EDITOR
if (!Application.isPlaying && !isLocalSpace)
{
dstPos += psPos - psPos.GetScaled(inverseScale, _uiParticle.scale3D.Inverse());
} }
#endif #endif
if (isUI)
{
dstPos.Scale(_uiParticle.transform.localScale);
dstPos.Scale(_uiParticle.scale3D.Inverse());
}
} }
return dstPos; return dstPos;
} }
private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float duration, float time) private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float duration, float time)
{ {
var speed = m_MaxSpeed; 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) switch (m_Movement)
{ {
case Movement.Linear: case Movement.Linear:
@ -203,22 +233,26 @@ namespace Coffee.UIExtensions
return Vector3.MoveTowards(current, target, speed); return Vector3.MoveTowards(current, target, speed);
} }
private bool ApplyParticleSystem() private void ApplyParticleSystem()
{ {
_uiParticle = null;
if (m_ParticleSystem == null) if (m_ParticleSystem == null)
{ {
Debug.LogError("No particle system attached to particle attractor script", this); #if UNITY_EDITOR
enabled = false; if (Application.isPlaying)
return false; #endif
{
Debug.LogError("No particle system attached to particle attractor script", this);
}
return;
} }
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(); _uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(true);
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem)) if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
{ {
_uiParticle = null; _uiParticle = null;
} }
return true;
} }
} }
} }

View File

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

View File

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

View File

@ -46,11 +46,11 @@ namespace Coffee.UIParticleExtensions
internal static class SpriteExtensions internal static class SpriteExtensions
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
private static Type tSpriteEditorExtension = private static readonly Type s_SpriteEditorExtensionType =
Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor") Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor")
?? Type.GetType("UnityEditor.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); .GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic);
public static Texture2D GetActualTexture(this Sprite self) public static Texture2D GetActualTexture(this Sprite self)
@ -58,8 +58,10 @@ namespace Coffee.UIParticleExtensions
if (!self) return null; if (!self) return null;
if (Application.isPlaying) return self.texture; if (Application.isPlaying) return self.texture;
var ret = miGetActiveAtlasTexture.Invoke(null, new[] { self }) as Texture2D; var ret = s_GetActiveAtlasTextureMethodInfo.Invoke(null, new object[] { self }) as Texture2D;
return ret ? ret : self.texture; return ret
? ret
: self.texture;
} }
#else #else
internal static Texture2D GetActualTexture(this Sprite self) internal static Texture2D GetActualTexture(this Sprite self)
@ -77,12 +79,14 @@ namespace Coffee.UIParticleExtensions
{ {
if (s_TmpParticles.Length < size) if (s_TmpParticles.Length < size)
{ {
while(s_TmpParticles.Length < size) while (s_TmpParticles.Length < size)
{ {
size = Mathf.NextPowerOfTwo(size); size = Mathf.NextPowerOfTwo(size);
} }
s_TmpParticles = new ParticleSystem.Particle[size]; s_TmpParticles = new ParticleSystem.Particle[size];
} }
return s_TmpParticles; return s_TmpParticles;
} }
@ -102,47 +106,69 @@ namespace Coffee.UIParticleExtensions
var main = self.main; var main = self.main;
var space = main.simulationSpace; var space = main.simulationSpace;
if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace) if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace)
{
space = ParticleSystemSimulationSpace.Local; space = ParticleSystemSimulationSpace.Local;
}
return space; 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) public static void SortForRendering(this List<ParticleSystem> self, Transform transform, bool sortByMaterial)
{ {
self.Sort((a, b) => self.Sort((a, b) =>
{ {
var tr = transform;
var aRenderer = a.GetComponent<ParticleSystemRenderer>(); var aRenderer = a.GetComponent<ParticleSystemRenderer>();
var bRenderer = b.GetComponent<ParticleSystemRenderer>(); var bRenderer = b.GetComponent<ParticleSystemRenderer>();
// Render queue: ascending // Render queue: ascending
var aMat = aRenderer.sharedMaterial ?? aRenderer.trailMaterial; var aMat = aRenderer.sharedMaterial ? aRenderer.sharedMaterial : aRenderer.trailMaterial;
var bMat = bRenderer.sharedMaterial ?? bRenderer.trailMaterial; var bMat = bRenderer.sharedMaterial ? bRenderer.sharedMaterial : bRenderer.trailMaterial;
if (!aMat && !bMat) return 0; if (!aMat && !bMat) return 0;
if (!aMat) return -1; if (!aMat) return -1;
if (!bMat) return 1; if (!bMat) return 1;
if (sortByMaterial) if (sortByMaterial)
{
return aMat.GetInstanceID() - bMat.GetInstanceID(); return aMat.GetInstanceID() - bMat.GetInstanceID();
}
if (aMat.renderQueue != bMat.renderQueue) if (aMat.renderQueue != bMat.renderQueue)
{
return aMat.renderQueue - bMat.renderQueue; return aMat.renderQueue - bMat.renderQueue;
}
// Sorting layer: ascending // Sorting layer: ascending
if (aRenderer.sortingLayerID != bRenderer.sortingLayerID) 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 // Sorting order: ascending
if (aRenderer.sortingOrder != bRenderer.sortingOrder) if (aRenderer.sortingOrder != bRenderer.sortingOrder)
{
return aRenderer.sortingOrder - bRenderer.sortingOrder; return aRenderer.sortingOrder - bRenderer.sortingOrder;
}
// Z position & sortingFudge: descending // Z position & sortingFudge: descending
var aTransform = a.transform; var aTransform = a.transform;
var bTransform = b.transform; var bTransform = b.transform;
var aPos = tr.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge; var aPos = transform.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge;
var bPos = tr.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge; var bPos = transform.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge;
if (!Mathf.Approximately(aPos, bPos)) if (!Mathf.Approximately(aPos, bPos))
{
return (int)Mathf.Sign(bPos - aPos); return (int)Mathf.Sign(bPos - aPos);
}
return (int)Mathf.Sign(GetIndex(self, a) - GetIndex(self, b)); 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++) 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; return 0;
@ -183,4 +212,72 @@ namespace Coffee.UIParticleExtensions
self.ForEach(action); 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_2020_3_OR_NEWER || UNITY_2021_1
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;
}
public static T GetComponentInChildren<T>(this Component self, bool includeInactive) where T : Component
{
if (!self) return null;
if (!includeInactive) return self.GetComponentInChildren<T>();
var component = self.GetComponent<T>();
if (component) return component;
foreach (Transform child in self.transform)
{
component = child.GetComponentInChildren<T>(true);
if (component) return component;
}
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: