Planteamiento de la UI en Unity
Buenas tardes gente. Lo que voy a mostrar ahora es la metodología que he seguido para generar y utilizar los recursos gráficos que van a conformar mi juego. No digo que sea la panacea pero, bajo mi criterio, es la manera más óptima que conozco de hacer algo... ¿"decente"?
Intentaré que sea una lectura interesante
Ligero vistazo al pasado
Mi planteamiento antes era 1 textura = 1 sprite, es decir, que si quería tener 3 botones rectangulares, me hacía mis 3 .PNGs correspondientes. Horrible, lo sé, casi todos tenemos algo oscuro en nuestro pasado .
Cambio de tercio
Cierto tiempo después, cuano abro los proyectos a medias y veo que tenía la carpeta Sprites petada de imágenes de 40 - 200KB, es cuando comprendo lo fácil que era en su día cambiar en Photoshop, guardar con otro nombre, importar y seguir tirando. Ahora, algo más veterano, sigo las siguientes pautas:
- Buscar la figura más primitiva y reutilizable posible (cuadrados y círculos sobre todo)
- No generar ninguna textura de más de 128 x 128 si todo va a ser para la UI (peso innecesario)
- A la hora de elegir el color pensar siempre que blanco = lo que desees y que, salvo que necesite un contraste concreto, utilizar grises de relleno ofrece suficiente calidad
- Mantener un fondo transparente y procurar que el color nunca toque los bordes (suele notarse mucho el corte o los límites de una textura si le aplicamos cualquier efecto posterior)
Así es como genero algo similar a estas texturas:
Resultado en proyecto:
Si ahora le meto estas texturas a uno de mis botones pues tengo lo siguiente:
(Poco sexy y se nota que el color llega hasta el borde)
Modificación de sprites en el editor de Unity
Estos son mis pasos desglosados al importar una imagen en Unity y meterla en la carpeta Assets/Resources/Sprites para que quede como quiero:
1 - Convertir el Sprite a tipo 2D UI
2 - Editar el Sprite para hacerlo extensible
3 - Otros cambios
*(No olvidar poner el Mesh Type como Full Rect para objetos que no sean de la UI, en otro caso se puede dejar como Tight)
Uso de los sprites en UI y en escena
Antes de conseguir el efecto deseado hay que hacer un último cambio en el componente de destino (SpriteRenderer, Image, loquesea):
Y ahora sí, tenemos un resultado como este:
*(Nótese que solamente uso el color del editor, pero el botón muestra dos tonalidades por el borde y el relleno)
Conclusiones
- Este sistema me permite utilizar imágenes de menor resolución (32 x 32 en el ejemplo) pero buen resultado en pantallas que ocupan menos de 10KB
- Las imágenes son mucho más reutilizables al aprovechar el color de Unity como multiplicador
- Puedo aprovechar materiales para hacer cambios y transformaciones puntuales, que es más respetuoso con la memoria y no produce una sobrecarga de GPU sensible:
El gif se ve como la mierda pero no logro hacerlo mejor así que dejo una captura también:
Muchísimas gracias por leerme, aquí os dejo el shader utilizado para el efecto de neón (aún en progreso para optimizar):
spoilerShader "Venat/Glow"
{
// Original here: https://gist.github.com/jzayed/d96f002b7eb149bf600c55ceb835da78
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_AlphaIntensity_Fade_1("_AlphaIntensity_Fade_1", Range(0.1, 2)) = 1
_MainTint("_MainTint", COLOR) = (1,1,1,1)
_AlphaIntensity_Fade_2("_AlphaIntensity_Fade_2", Range(0.1, 2)) = 1
_SecondaryTint("_SecondaryTint", COLOR) = (1,1,1,1)
_OperationBlend_Fade_1("_OperationBlend_Fade_1", Range(0, 1)) = 1
_SpriteFade("SpriteFade", Range(0, 1)) = 1.0
// required for UI.Mask
[HideInInspector]_StencilComp("Stencil Comparison", Float) = 8
[HideInInspector]_Stencil("Stencil ID", Float) = 0
[HideInInspector]_StencilOp("Stencil Operation", Float) = 0
[HideInInspector]_StencilWriteMask("Stencil Write Mask", Float) = 255
[HideInInspector]_StencilReadMask("Stencil Read Mask", Float) = 255
[HideInInspector]_ColorMask("Color Mask", Float) = 15
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "true"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
// required for UI.Mask
Stencil
{
Ref[_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask[_StencilReadMask]
WriteMask[_StencilWriteMask]
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 color : COLOR;
};
sampler2D _MainTex;
float _SpriteFade;
float _AlphaIntensity_Fade_1;
float4 _MainTint;
float _AlphaIntensity_Fade_2;
float4 _SecondaryTint;
float _OperationBlend_Fade_1;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color;
return OUT;
}
float4 TintRGBA(float4 txt, float4 color)
{
float3 tint = dot(txt.rgb, float3(.222, .707, .071));
tint.rgb *= color.rgb;
txt.rgb = lerp(txt.rgb,tint.rgb,color.a);
return txt;
}
float4 OperationBlend(float4 origin, float4 overlay, float blend)
{
float4 o = origin;
o.a = overlay.a + origin.a * (1 - overlay.a);
o.rgb = (overlay.rgb * overlay.a + origin.rgb * origin.a * (1 - overlay.a)) / (o.a + 0.0000001);
o.a = saturate(o.a);
o = lerp(origin, o, blend);
return o;
}
float4 AlphaIntensity(float4 txt,float fade)
{
if (txt.a < 1) txt.a = lerp(0, txt.a, fade);
return txt;
}
float4 frag(v2f i) : COLOR
{
float4 _MainTex_1 = tex2D(_MainTex, i.texcoord);
float4 AlphaIntensity_1 = AlphaIntensity(_MainTex_1,_AlphaIntensity_Fade_1);
float4 TintRGBA_1 = TintRGBA(AlphaIntensity_1, _MainTint);
float4 _MainTex_2 = tex2D(_MainTex, i.texcoord);
float4 AlphaIntensity_2 = AlphaIntensity(_MainTex_2,_AlphaIntensity_Fade_2);
float4 TintRGBA_2 = TintRGBA(AlphaIntensity_2, _SecondaryTint);
float4 OperationBlend_1 = OperationBlend(TintRGBA_2, TintRGBA_1, _OperationBlend_Fade_1);
float4 FinalResult = OperationBlend_1;
FinalResult.rgb *= i.color.rgb;
FinalResult.a = FinalResult.a * _SpriteFade * i.color.a;
return FinalResult;
}
ENDCG
}
}
Fallback "Sprites/Default"
}