Déjame clarificar el resultado final aquí mismo. Éste es lo que vamos a hacer en este tutorial:
Recientemente en Grimwood Team me encargué de crear algún progreso de nivel. La idea principal es que el gamer debería mantener jugando mientras algunas cosas se llenan de líquido y luego explotan. La principal inspiración hacer algo así nos ha venido de Hollow Knight. ¿Recuerda?, hay una "barra" de progreso de salud tal como un cráneo se llena con el alma líquida(al menos es lo que veo).
Implementemos estas cosas.
PS. Se puede encontrar el código fuente en el repositorio de este tutorial.
Unity 2019.3.2f1 - Lo estoy usando en el momento de escribir este tutorial. Yo uso la versión inglesa así que habrán los nombres de menús en inglés y también estaré usando los nombres ingleses para los objetos.
La textura de orbe con poca transparencia(efecto de vidrio).
La textura de máscara de orbe - en mi caso es un negro círculo de la talla del orbe.
La textura del borde del orbe - como efecto adicional para la composición general.
La textura del líquido - esa textura se usará para llenar el orbe(He creado algunos en mi repositorio para fines de prueba).
Cree un proyecto 2D en Unity con una escena vacía y llamada "LiquidOrb".
Mueva todos los sprites de requisitos previos a "LiquidOrb/Assets/Sprites".
Creemos una jerarquía para nuestro orbe
Debería tener la siguente jerarquía:
Agreguemos Sprite Renderers a los objetos "Agua", "OrbBorder", "Orb" con los sprites correspondientes. Para hacerlo seleccione el objeto y en el Inspector seleccione "Add Component" y encuentre "Sprite Renderer".
Afine la coordinada z de los objectos así que el objeto "Water" estará atrás de los objetos "OrbBorder" y "Orb", el objeto "Orb" estará entre los "Water" y "OrbBorder". He configurado 1, 0, -1 valores para "Agua", "Orb", "OrbBorder" correspondientemente.
Configure la escala del objeto "Agua" para que cubra "Orbe".
Estamos listos para hacer magia.
Seleccione "OrbObject", en el Inspector seleccione "Add Component", encuentre y agregue el componente "Sprite Mask". Adjunte orb_mask sprite al componente "Sprite Mask". Ahora debería ver los bordes de la máscara en la escena(línea naranja-marrón).
No le preocupa, será un círculo perfecto cuando terminemos. Unity lo hace así.
Enmascaremos nuestro objeto "Agua". Seleccione "Water" y en el componente Sprite Renderer cambie la propiedad "Mask Interaction" a "Visible Inside Mask".
Y ahora podemos ver algo que se vea como el resultado final.
Ahora necesitamos crear un nuevo material con shader que proporcione el comportamiento líquido.
He encontrado un efecto súper genial en shadertoy:
Eso es casi que necesitamos.
He bifurcado este shader y hice un pequeño cambio para hacer lo "que necesitamos" más claramente:
Se ve bien, eh? Traigamos este efecto a nuestro proyecto de Unity.
Venga al proyecto "LiquidOrb". Agregue carpeta "Assets/Shaders". Haga clic derecho en la carpeta "Shaders", seleccione "Create", "Shader", "Image Effect Shader"(O cualquier shader - eliminaremos su contenido más tarde). Abra este shader, vamos a verificar lo que tenemos adentro.
Shader "Hidden/LiquidShader"
{
//Muchas cosas están aquí
}
Cambiemos "Hidden/LiquidShader" a "MyShaders/LiquidShader"(para hacerlo disponible más tarde) y eliminar todo el contenido del shader.
Shader "MyShaders/LiquidShader"
{
}
No necesitamos contenido generado automáticamente, no funcionará como esperamos nuestro efecto porque no tiene reglas para mascar las cosas, etc. La idea aquí es tomar el integrado shader de sprite y modificarlo con el shader de Shadertoy. He cargado los shaders integrado a mi repositorio de github. También puede encontrar estos shaders en el sitio de Unity: seleccione la versión que te gusta, haga clic en "Downloads" y seleccione "Builtin Shaders".
Vamos a actualizar nuestro LiquidShader con el contenido del shader integrado de Unity. Usaré la versión de mi repositorio.
Ahora vamos a verificar que todo funciona bien:
Hemos creado un material para nuestro sprite de liquido. Seleccione objecto "Water" en la jerarquía. En el Componente "Sprite Renderer" del inspector seleccione Material, encuentre y especifique "LiquidMaterial".
Como resultado, todo debería estar bien y funcionar cómo estaba funcionando antes. Eso significa que nuestras cosas personalizadas funcionan. Ahora vamos a integrar el shader de Shadertoy.
El shader liquido de Shadertoy es un shader de fragmentos. Creemos una función separada para el shader de fragmentos. En "LiquidShader" venga a la sección "Pass".
Pass
{
CGPROGRAM
#pragma vertex SpriteVert
#pragma fragment SpriteFrag // We need to change this part
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile_local _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
ENDCG
}
Ahora SpriteFrag es la función de shader de fragmentos que es implementado en #include "UnitySprites.cginc".
Entonces vamos a crear nuestra función personalizada llamada frag y la ponemos debajo de #include "UnitySprites.cginc"
:
Pass
{
CGPROGRAM
#pragma vertex SpriteVert
#pragma fragment frag // we've changed the name of the func to "frag". The implementation can be found below
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile_local _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = fixed4(0.0,0.0,0.0,0.0);
return col;
}
ENDCG
}
Ahora en la escena deberíamos ver que esa agua no se muestra porque hemos cambiado el shader de fragmentos para devolver el color fixed4(0.0, 0.0, 0.0, 0.0) para cada posición que viene a la función frag.
Vamos a copiar el código del shader de Shadertoy y pegarlo a LiquidShader debajo de la función frag:
Pass
{
CGPROGRAM
#pragma vertex SpriteVert
#pragma fragment frag // Hemos cambiado el nombre de la función a "frag". Se puede encontrar la implementación abajo
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile_local _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = fixed4(0.0,0.0,0.0,0.0);
return col;
}
vec4 drawWater(vec4 water_color, sampler2D color, float transparency, float height, float angle, float wave_strength, float wave_ratio, vec2 uv);
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
vec2 uv = fragCoord / iResolution.xy;
float WATER_HEIGHT = abs(sin(iTime * 0.5));
vec4 WATER_COLOR = vec4(0.5, 0.5, 0.53, 1.0);
float WAVE_STRENGTH = 3.0;
float WAVE_FREQUENCY = 8.0;
float WATER_TRANSPARENCY = 0.4;
float WATER_ANGLE = 2.0;
fragColor = drawWater(WATER_COLOR, iChannel0, WATER_TRANSPARENCY, WATER_HEIGHT, WATER_ANGLE, WAVE_STRENGTH, WAVE_FREQUENCY, uv);
}
vec4 drawWater(vec4 water_color, sampler2D color, float transparency, float height, float angle, float wave_strength, float wave_frequency, vec2 uv)
{
angle *= uv.y/height+angle/1.5; //3D effect
wave_strength /= 1000.0;
float wave = sin(10.0*uv.y+10.0*uv.x+wave_frequency*iTime)*wave_strength;
wave += sin(20.0*-uv.y+20.0*uv.x+wave_frequency*1.0*iTime)*wave_strength*0.5;
wave += sin(15.0*-uv.y+15.0*-uv.x+wave_frequency*0.6*iTime)*wave_strength*1.3;
wave += sin(3.0*-uv.y+3.0*-uv.x+wave_frequency*0.3*iTime)*wave_strength*10.0;
if(uv.y - wave <= height)
return mix(
mix(
texture(color, vec2(uv.x, ((1.0 + angle)*(height + wave) - angle*uv.y + wave))),
water_color,
0.6-(0.3-(0.3*uv.y/height))),
texture(color, vec2(uv.x + wave, uv.y - wave)),
transparency-(transparency*uv.y/height));
else
return vec4(0,0,0,0);
}
ENDCG
}
Como puede observar, hay algunas diferencias entre los idiomas. No le preocupe. Vamos a hacer los cambios siquientes:
Ahora puede ejecutar la escena y ver que el orbe se está llenando lentamente por el liquido. ¡Estamos casi allí!
Actualicemos el shader para hacer todos los variables WATER_HEIGHT, WATER_COLOR, WAVE_STRENGTH, WAVE_FREQUENCY, WATER_TRANSPARENCY, WATER_ANGLE ajustables del inspector.
Modifique la sección Propiedades de LiquidShader a lo siguiente:
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_Progress ("Progress", Range(0.0, 1.0)) = 0.5 // Nueva
_WaterColor ("WaterColor", Color) = (1.0, 1.0, 0.2, 1.0) // Nueva
_WaveStrength ("WaveStrength", Float) = 2.0 // Nueva
_WaveFrequency ("WaveFrequency", Float) = 180.0 // Nueva
_WaterTransparency ("WaterTransparency", Float) = 1.0 // Nueva
_WaterAngle ("WaterAngle", Float) = 4.0 // Nueva
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
}
Agregue lo siguiente a la sección Pass debajo de #include "UnitySrpites.cginc":
#include "UnitySprites.cginc"
float _Progress; // Var Nueva
fixed4 _WaterColor; // Var Nueva
float _WaveStrength; // Var Nueva
float _WaveFrequency; // Var Nueva
float _WaterTransparency; // Var Nueva
float _WaterAngle; // Var Nueva
fixed4 drawWater(fixed4 water_color, sampler2D color, float transparency, float height, float angle, float wave_strength, float wave_frequency, fixed2 uv);
Actualice la función frag a:
fixed4 frag (v2f i) : SV_Target
{
fixed2 uv = i.texcoord;
float WATER_HEIGHT = _Progress; // Progres Personalizado
float4 WATER_COLOR = _WaterColor; // Color Personalizado
float WAVE_STRENGTH = _WaveStrength; // Fuerza Personalizada
float WAVE_FREQUENCY = _WaveFrequency; // Frequencia Personalizada
float WATER_TRANSPARENCY = _WaterTransparency; // Transparencia de liquido Personalizada
float WATER_ANGLE = _WaterAngle; // Ángulo del liquido personalizado
fixed4 fragColor = drawWater(WATER_COLOR, _MainTex, WATER_TRANSPARENCY, WATER_HEIGHT, WATER_ANGLE, WAVE_STRENGTH, WAVE_FREQUENCY, uv);
return fragColor;
}
Si seleccione LiquidMaterial de la carpeta Materials en el inspector podrá ver los parámetros personalizados. Puede ejecutar la escena y cambiarlos durante la ejecución de la escena. Genial, eh?
Ahora puede tener el acceso a sus parámetros de sus scripts:
public class LiquidProgressController : MonoBehaviour
{
public SpriteRenderer LiquidRenderer;
public float AccumulatedTime;
public float DelayCoef = 0.5f;
// Update se llama cada frame
void Update()
{
AccumulatedTime += Time.deltaTime * DelayCoef;
LiquidRenderer.material.SetFloat("_Progress", Mathf.Abs(Mathf.Sin(AccumulatedTime)));
}
}
Eso fue el básico de la migración shaders de Shadertoy a Unity. Me gustaría mencionar algunas cosas.
Si quiere ejecutarlo en la plataforma móvil entonces se necesita desmarcar "Disable Depth and Stencil" en "Player Settings"(para iOS y Android). De lo contrario, el enmascaramiento no funcionará y toda la textura de agua será mostrada.
Si lo quiere que funciona en UI necesitará escribir este shader para UI. Eso defiere un poco. Las instrucciones son similares: Obtenga el shader integrado para UI y actualice la función frag, etc.
Hay muchas cosas visuales que podemos mejorar aquí, por ejemplo podemos agregar algunas partículas con enamascaramiento.
El código fuente se puede encontrar aquí.
Espero les guste leer eso y crear este efecto. Y también dejame saber como es la traducción del artículo. Estoy aprendiendo español y quiero mejorarlo. Si hay errores y algunas cosas que no entiende, por favor, póngase en contacto conmigo.
PS. Creo que probablemente los tutoriales así pueden ser más simples para entender si fueran de video tutoriales. ¿Qué piensa?
Yo agregue una rama nueva con la implementación del caso de UI Canvas aqui. La uníca cosa aqui es yo uso Unity 2019.4.12f1 versión. Pero no debería haber cambios significativos.
Usualmente un blogger pone aquí algunos artículos relativos. Estoy trabajando en crear más contenido interesante. Tan pronto como tengo algun contenido relativo implementaré algo de lógica aquí. A continuación puede encontrar algunos comentarios. Si no hay nada, es tu oportunidad de dejar el primer comentario:) PS.Me alegra que esté aquí y lea esto^^