I would like to animate two of my UI images in a similar manner to the red and blue images below:
I'd like to accomplish this through script, but I'm not certain if it's possible.
Does anyone have insight on how I can recreate this effect? Thanks!
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It only takes a minute to sign up.
Sign up to join this communityFor color, this is basically an additive blending:
Unity has made an additive shader for the particle system, for convenience you can use it directly:
For shapes, this basically draws two quads on the UI and updates their vertex positions via script. Now let's see how to draw polygons on the UI. Here is a simple code. For convenience, I specify that all children of the script transform are the vertices of the polygon, and agree that they are in order.
using UnityEngine;
using UnityEngine.UI;
public class UIPolygon : Graphic
{
private void OnGUI()
{
SetAllDirty();
}
protected override void OnPopulateMesh(VertexHelper vh)
{
if (transform.childCount < 2)
{
return;
}
vh.Clear();
foreach (Transform child in transform)
{
vh.AddVert(child.localPosition, color, Vector2.zero);
}
for (int i = 0; i < transform.childCount - 2; i++)
{
vh.AddTriangle(i + 1, i + 2, 0);
}
}
}
Now, mount this script on a UI object and create 4 children for it:
Then, create a new material, set its shader to additive, and attach it to the polygon.
Then set the color of this polygon to red, duplicate a polygon and set its color to cyan, and adjust the positions of their vertices:
Finally, we attach another script to the polygon to make all its children move randomly within a certain range.
**Due to the frame rate of the gif, I don't know whether the movement of the vertices is continuous or discrete. Here I simply let it change the position randomly. You can modify it however you want.
using System.Collections;
using UnityEngine;
public class UIPolygonChildrenMove : MonoBehaviour
{
void Start()
{
foreach (RectTransform child in transform)
{
StartCoroutine(UpdateChildPos(child, child.anchoredPosition));
}
}
IEnumerator UpdateChildPos(RectTransform child, Vector2 prePos)
{
while (true)
{
child.anchoredPosition = prePos + RandomOffset(30);
yield return new WaitForSeconds(0.1f);
}
}
private Vector2 RandomOffset(float max)
{
return new Vector2(Random.Range(-max, max), Random.Range(-max, max));
}
}
Result:
And you can add some lerp:
using System.Collections;
using UnityEngine;
public class UIPolygonChildrenMove : MonoBehaviour
{
void Start()
{
foreach (RectTransform child in transform)
{
StartCoroutine(UpdateChildPos(child));
}
}
IEnumerator UpdateChildPos(RectTransform child)
{
Vector2 prePos = child.anchoredPosition;
Vector2 fromPos = prePos;
Vector2 targetPos = prePos + RandomOffset(30);
float duration = 0.2f;
float pastTime = 0;
while (true)
{
pastTime += Time.deltaTime;
if (pastTime >= duration)
{
fromPos = targetPos;
targetPos = prePos + RandomOffset(30);
pastTime = 0;
}
var t = pastTime / duration;
child.anchoredPosition = Vector2.Lerp(fromPos, targetPos, t);
yield return null;
}
}
private Vector2 RandomOffset(float max)
{
return new Vector2(Random.Range(-max, max), Random.Range(-max, max));
}
}
Result:
Edit:
Sorry I haven't played Persona 5, I thought the effect would only render on a black background. Now let's try to make it work in any background.
First find the source code of Mobile/Particles/Additive
. We can see that it simply does an additive blend by Blend SrcAlpha One
:
Shader "Mobile/Particles/Additive" {
Properties {
_MainTex ("Particle Texture", 2D) = "white" {}
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend SrcAlpha One
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}
SubShader {
Pass {
SetTexture [_MainTex] {
combine texture * primary
}
}
}
}
}
We can use Stencil to mark the stencil buffer, indicating that this pixel is rendered by a specific shader, and then the rendered pixel will read this mark and process it. In general, we need to output the normal color and mark it during the first rendering, and then perform additive blend if we read this mark during the second rendering.
Shader "Custom/myAdditive"
{
Properties {
_MainTex ("Particle Texture", 2D) = "white" {}
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}
SubShader {
Pass {
Stencil
{
Ref 1
Comp NotEqual
Pass Replace
}
SetTexture [_MainTex] {
combine texture * primary
}
}
Pass {
Stencil {
Ref 1
Comp Equal
}
Blend SrcAlpha One
SetTexture [_MainTex] {
combine texture * primary
}
}
}
}
}
If ref
Not Equal 1
, Execute the first pass, output the color directly, and set ref
to 1
. If ref
Equal 1
, Execute the second pass, output the color after additive blend.
Result:
the UI shapes
are images, We cannot change their vertex positions separately. Maybe they can be used as textures on quad shapes, but just pictures won't accomplish the effect in the answer. : )
\$\endgroup\$
OnPopulateMesh
There is no need to call it manually, it just overloads the parent class method and is called by the engine itself, just like Start
and Update
. Color is also a member of the parent class, it will show in inspector. And the VertexHelper can aid in the generation of meshes for the UI.
\$\endgroup\$