Créez des composants visuels dans l'interface utilisateur Unity. Système de particules

Salut Cet article concerne la création de vos propres composants visuels dans l'interface utilisateur à l'aide de l'exemple d'un composant pour visualiser un système de particules dans Canvas 'e.

Ces informations seront utiles pour implémenter divers effets dans l'interface utilisateur, et peuvent également être utilisées pour générer un maillage ou l'optimiser.

image


Un peu de théorie ou par où commencer la création de composants


La base de l'interface utilisateur d'Unity est Canvas . C'est lui qui est utilisé par le système de rendu pour afficher la géométrie «multicouche», conformément à la hiérarchie interne des éléments de l'interface utilisateur.
Tout composant d'interface utilisateur visuelle doit hériter de la classe Graphic (ou de sa classe MaskableGraphic dérivée), qui transmet toutes les données nécessaires au composant CanvasRenderer pour le rendre. Les données sont créées dans la méthode OnPopulateMesh , qui est appelée chaque fois qu'un composant doit mettre à jour sa géométrie (par exemple, lors du redimensionnement d'un élément). VertexHelper est passé en tant que paramètre, ce qui aide à générer un maillage pour l'interface utilisateur.

Création de composants


Base


Nous commençons l'implémentation en créant un script UIParticleSystem qui hérite de la classe MaskableGraphic . MaskableGraphic est un dérivé de la classe Graphic et permet en outre de travailler avec des masques. Substituez la méthode OnPopulateMesh . La base pour travailler avec VertexHelper pour générer les sommets d'un maillage de système de particules de maillage ressemblera à ceci:

public class UIParticleSystem : MaskableGraphic { protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); ... int particlesCount = ... ; for (int i = 0; i < particlesCount; i++) { Color vertexColor = ... ; Vector2[] vertexUV = ... ; UIVertex[] quadVerts = new UIVertex[4]; for (int j = 0; j < 4; j++) { Vector3 vertixPosition = ... ; quadVerts[j] = new UIVertex() { position = vertixPosition, color = vertexColor, uv0 = vertexUV }; } vh.AddUIVertexQuad(quadVerts); } } } 

Tout d'abord, vous devez effacer VertextHelper des données existantes en appelant la méthode Clear . Après cela, vous pouvez commencer à le remplir avec de nouvelles données sur les pics. Pour cela, la méthode AddUIVertexQuad sera utilisée, ce qui vous permet d'ajouter des informations sur 4 sommets à la fois. Cette méthode a été sélectionnée pour sa facilité d'utilisation, car chaque particule est un rectangle. Chaque sommet est décrit par un objet UIVertex . De tous les paramètres, nous devons remplir uniquement la position, la couleur et certaines coordonnées du scan UV .

VertexHelper
VertexHelper dispose d'un ensemble complet de méthodes pour ajouter des informations de sommet, ainsi qu'une paire pour recevoir les données actuelles. Pour une géométrie plus complexe, la meilleure solution serait de sélectionner la méthode AddUIVertexStream , qui accepte une liste de sommets et une liste d'indices.

Étant donné que chaque image, la position des particules, leur couleur et d'autres paramètres changeront, le maillage de leur rendu doit également être mis à jour.
Pour ce faire, chaque trame appellera la méthode SetVerticesDirty , qui définira l'indicateur sur la nécessité de recompter de nouvelles données, ce qui conduira à l'appel à la méthode OnPopulateMesh . De même pour un matériau, si ses propriétés changent, vous devez appeler la méthode SetMaterialDirty .

 protected void Update() { SetVerticesDirty(); } 

Substituez la propriété mainTexture . Il indique quelle texture sera transmise au CanvasRenderer et utilisée dans le matériau, la propriété de shader _MainTex . Pour ce faire, créez un champ ParticleImage , qui sera renvoyé par la propriété mainTexture .

 public Texture ParticleImage; public override Texture mainTexture { get { return ParticleImage; } } 

Système de particules


Les données pour générer des sommets de maillage seront prises à partir du composant ParticleSystem , qui est engagé dans tous les calculs sur l'emplacement des particules, leur taille, leur couleur, etc.
Le composant ParticleSystemRenderer sera impliqué dans le rendu des particules, qui devra être désactivé, donc d'autres composants seront également responsables de la création du maillage et de son rendu dans l'interface utilisateur - UIParticleSystem et CanvasRenderer .

Créez les champs nécessaires au fonctionnement et initialisez-les dans la méthode Awake .

UIBehaviour
Awake , comme la plupart des méthodes, doit être redéfini ici, car ils sont répertoriés comme virtuels dans UIBehaviour . La classe UIBehaviour elle-même est abstraite et ne contient pratiquement aucune logique de travail, mais elle est fondamentale pour la classe Graphic .

 private ParticleSystem _particleSystem; private ParticleSystemRenderer _particleSystemRenderer; private ParticleSystem.MainModule _main; private ParticleSystem.Particle[] _particles; protected override void Awake() { base.Awake(); _particleSystem = GetComponent<ParticleSystem>(); _main = _particleSystem.main; _particleSystemRenderer = GetComponent<ParticleSystemRenderer>(); _particleSystemRenderer.enabled = false; int maxCount = _main.maxParticles; _particles = new ParticleSystem.Particle[maxCount]; } 

Le champ _particles sera utilisé pour stocker les particules ParticleSystem , et
_main est utilisé pour plus de commodité avec le module MainModule .

Ajoutons la méthode OnPopulateMesh, en prenant toutes les données nécessaires directement du système de particules. Créez les variables d'assistance Vector3 [] _quadCorners et Vector2 [] _simpleUV .

_quadCorners contient les coordonnées des 4 coins du rectangle, par rapport au centre de la particule. La taille initiale de chaque particule est considérée comme un carré avec des côtés 1x1.
_simpleUV - coordonnées du scan uv , dans ce cas toutes les particules utilisent la même texture sans aucun déplacement.

 private Vector3[] _quadCorners = new Vector3[] { new Vector3(-.5f, -.5f, 0), new Vector3(-.5f, .5f, 0), new Vector3(.5f, .5f, 0), new Vector3(.5f, -.5f, 0) }; private Vector2[] _simpleUV = new Vector2[] { new Vector2(0,0), new Vector2(0,1), new Vector2(1,1), new Vector2(1,0), }; 

 protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); int particlesCount = _particleSystem.GetParticles(_particles); for (int i = 0; i < particlesCount; i++) { var particle = _particles[i]; Vector3 particlePosition = particle.position; Color vertexColor = particle.GetCurrentColor(_particleSystem) * color; Vector3 particleSize = particle.GetCurrentSize3D(_particleSystem); Vector2[] vertexUV = _simpleUV; Quaternion rotation = Quaternion.AngleAxis(particle.rotation, Vector3.forward); UIVertex[]quadVerts = new UIVertex[4]; for (int j = 0; j < 4; j++) { Vector3 cornerPosition = rotation * Vector3.Scale(particleSize, _quadCorners[j]); Vector3 vertexPosition = cornerPosition + particlePosition; vertexPosition.z = 0; quadVerts[j] = new UIVertex(); quadVerts[j].color = vertexColor; quadVerts[j].uv0 = vertexUV[j]; quadVerts[j].position = vertexPosition; } vh.AddUIVertexQuad(quadVerts); } } 

vertexPosition
Tout d'abord, la position locale du sommet par rapport au centre de la particule est calculée en tenant compte de sa taille (opération Vector3.Scale (particuleSize, _quadCorners [j]) ) et de la rotation (multipliant la rotation du quaternion par un vecteur). Après la position de la particule elle-même est ajoutée au résultat

Créons maintenant une interface utilisateur simple pour le test à l'aide de composants standard.

image

Ajouter un UIParticleSystem au composant ParticleSystem

image

Exécutez la scène et vérifiez le résultat du composant.

image

Les particules sont affichées en fonction de leur position dans la hiérarchie et prennent en compte les masques utilisés. Lorsque vous modifiez la résolution de l'écran et ses proportions, ainsi que lorsque vous modifiez la propriété Rendere Mode de Canvas , les particules se comportent de la même manière que tout autre composant visuel dans Canvas et ne s'affichent que dans celui-ci.

SimulationSpace


Parce que nous plaçons le système de particules à l'intérieur de l'interface utilisateur, il y a un problème avec le paramètre SimulationSpace . Lorsqu'elles sont simulées dans l'espace mondial, les particules ne sont pas affichées où elles devraient. Par conséquent, nous ajoutons le calcul de la position des particules en fonction de la valeur du paramètre.

 protected override void OnPopulateMesh(VertexHelper vh) { ... Vector3 particlePosition; switch (_main.simulationSpace) { case ParticleSystemSimulationSpace.World: particlePosition = _rectTransform.InverseTransformPoint(particle.position); break; case ParticleSystemSimulationSpace.Local: particlePosition = particle.position; break; case ParticleSystemSimulationSpace.Custom: if (_main.customSimulationSpace != null) particlePosition = _rectTransform.InverseTransformPoint( _main.customSimulationSpace.TransformPoint(particle.position) ); else particlePosition = particle.position; break; default: particlePosition = particle.position; break; } ... } 

Simuler les propriétés de ParticleSystemRenderer


Maintenant, nous implémentons une partie de la fonctionnalité ParticleSystemRenderer . À savoir, les propriétés de RenderMode , SortMode , Pivot .

Renderder


Nous nous limitons au fait que les particules ne seront toujours localisées que dans le plan de la toile. Par conséquent, nous n'implémentons que deux valeurs: Billboard et StretchedBillboard .
Créons notre énumération CanvasParticleSystemRenderMode pour cela.

 public enum CanvasParticleSystemRenderMode { Billboard = 0, StretchedBillboard = 1 } 

 public CanvasParticleSystemRenderMode RenderMode; public float SpeedScale = 0f; public float LengthScale = 1f; protected override void OnPopulateMesh(VertexHelper vh) { ... Quaternion rotation; switch (RenderMode) { case CanvasParticleSystemRenderMode.Billboard: rotation = Quaternion.AngleAxis(particle.rotation, Vector3.forward); break; case CanvasParticleSystemRenderMode.StretchedBillboard: rotation = Quaternion.LookRotation(Vector3.forward, particle.totalVelocity); float speed = particle.totalVelocity.magnitude; particleSize = Vector3.Scale(particleSize, new Vector3(LengthScale + speed * SpeedScale, 1f, 1f)); rotation *= Quaternion.AngleAxis(90, Vector3.forward); break; default: rotation = Quaternion.AngleAxis(particle.rotation, Vector3.forward); break; } ... } 

Si vous sélectionnez le paramètre StretchedBillboard , la taille des particules dépendra des paramètres LengthScale et SpeedScale et sa rotation sera dirigée uniquement dans la direction du mouvement.

image

Mode de tri


De même, créez l'énumération CanvasParticlesSortMode . et nous implémentons uniquement le tri par durée de vie des particules.

 public enum CanvasParticlesSortMode { None = 0, OldestInFront = 1, YoungestInFront = 2 } 

 public CanvasParticlesSortMode SortMode; 

Pour le tri, nous devons stocker des données sur la durée de vie des particules, qui seront stockées dans la variable _particleElapsedLifetime . Le tri est implémenté à l'aide de la méthode Array.Sort .

 private float[] _particleElapsedLifetime; protected override void Awake() { ... _particles = new ParticleSystem.Particle[maxCount]; _particleElapsedLifetime = new float[maxCount]; } protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); int particlesCount = _particleSystem.GetParticles(_particles); for (int i = 0; i < particlesCount; i++) _particleElapsedLifetime[i] = _particles[i].startLifetime - _particles[i].remainingLifetime; switch (SortMode) { case CanvasParticlesSortMode.None: break; case CanvasParticlesSortMode.OldestInFront: Array.Sort(_particleElapsedLifetime, _particles, 0, particlesCount,Comparer<float>.Default); Array.Reverse(_particles, 0, particlesCount); break; case CanvasParticlesSortMode.YoungestInFront: Array.Sort(_particleElapsedLifetime, _particles, 0, particlesCount, Comparer<float>.Default); break; } ... } 

Pivot


Créez un champ de pivot pour décaler le point central de la particule.

 public Vector3 Pivot = Vector3.zero; 

Et lors du calcul de la position du sommet, nous ajoutons cette valeur.

 Vector3 cornerPosition = Vector3.Scale(particleSize, _quadCorners[j] + Pivot); Vector3 vertexPosition = rotation * cornerPosition + particlePosition; vertexPosition.z = 0; 

Taille ajustable


Si l'élément auquel le système de particules est attaché n'a pas de tailles fixes ou qu'il peut changer au moment de l'exécution, il serait bon d'adapter la taille du système de particules. Rendons la forme source proportionnelle à la taille de l'élément.

La méthode OnRectTransformDimensionsChange est appelée lorsque le composant RectTransform est redimensionné . Nous redéfinissons cette méthode en implémentant un changement d'échelle de la forme pour l'adapter aux dimensions de RectTransform .

Créez d'abord les variables du composant RectTransform et du module ShapeModule . Pour désactiver la mise à l'échelle des formes, créez la variable ScaleShapeByRectTransform .

En outre, la mise à l'échelle doit être effectuée lorsque le composant est activé pour définir son échelle initiale.

 private RectTransform _rectTransform; private ParticleSystem.ShapeModule _shape; public bool ScaleShapeByRectTransform; protected override void Awake() { ... _rectTransform = GetComponent<RectTransform>(); _shape = _particleSystem.shape; ... } protected override void OnEnable() { base.OnEnable(); ScaleShape(); } protected override void OnRectTransformDimensionsChange() { base.OnRectTransformDimensionsChange(); ScaleShape(); } protected void ScaleShape() { if (!ScaleShapeByRectTransform) return; Rect rect = _rectTransform.rect; var scale = Quaternion.Euler(_shape.rotation) * new Vector3(rect.width, rect.height, 0); scale = new Vector3(Mathf.Abs(scale.x), Mathf.Abs(scale.y), Mathf.Abs(scale.z)); _shape.scale = scale; } 

Lors du calcul, il convient de considérer la rotation de la forme . Les valeurs du résultat final doivent être prises modulo, car elles peuvent se révéler négatives, ce qui affectera la direction du mouvement des particules.

Pour tester l'opération, exécutez l'animation de redimensionnement RectTransform avec un système de particules attaché.

image

Initialisation


Afin que le script s'exécute correctement dans l'éditeur et évite les erreurs lors de l'appel de la méthode OnRectTransformDimensionsChange , nous mettons l'initialisation des variables dans une méthode distincte. Et ajoutez son appel aux méthodes OnPopulateMesh et OnRectTransformDimensionsChange .

ExecuteInEditMode
Vous n'avez pas besoin de spécifier l'attribut ExecuteInEditMode , car Le graphique implémente déjà ce comportement et le script est exécuté dans l'éditeur.

 private bool _initialized; protected void Initialize() { if (_initialized) return; _initialized = true; _rectTransform = GetComponent<RectTransform>(); _particleSystem = GetComponent<ParticleSystem>(); _main = _particleSystem.main; _textureSheetAnimation = _particleSystem.textureSheetAnimation; _shape = _particleSystem.shape; _particleSystemRenderer = GetComponent<ParticleSystemRenderer>(); _particleSystemRenderer.enabled = false; _particleSystemRenderer.material = null; var maxCount = _main.maxParticles; _particles = new ParticleSystem.Particle[maxCount]; _particlesLifeProgress = new float[maxCount]; _particleRemainingLifetime = new float[maxCount]; } protected override void Awake() { base.Awake(); Initialize(); } protected override void OnPopulateMesh(VertexHelper vh) { Initialize(); ... } protected override void OnRectTransformDimensionsChange() { #if UNITY_EDITOR Initialize(); #endif ... } 

La méthode OnRectTransformDimensionsChange peut être appelée plus tôt que Awake . Par conséquent, à chaque appel, il est nécessaire d'initialiser les variables.

Performance et optimisation


Ce rendu de particules est un peu plus cher que l'utilisation de ParticleSystemRenderer , qui nécessite une utilisation plus prudente, notamment sur les appareils mobiles.
Il convient également de noter que si au moins un des éléments Canvas est marqué comme sale , cela entraînera un recalcul de la géométrie entière du Canvas et la génération de nouvelles commandes de rendu. Si l'interface utilisateur contient beaucoup de géométrie complexe et ses calculs, il vaut la peine de la diviser en plusieurs toiles intégrées.

image

PS: Tout le code source et les démos sont des liens git .
L'article a été lancé il y a près d'un an après avoir été obligé d'utiliser ParticleSystem dans l'interface utilisateur. A cette époque, je n'ai pas trouvé de solution similaire, et les solutions disponibles n'étaient pas optimales pour la tâche en cours. Mais quelques jours avant la publication de cet article, lors de la collecte de matériel, j'ai accidentellement trouvé une solution similaire en utilisant la méthode Graphic.OnPopulateMesh. Par conséquent, je considère qu'il est nécessaire de spécifier un lien vers le référentiel .

Source: https://habr.com/ru/post/fr464961/


All Articles