FairyGUI 中,文本控件自带了投影的功能,我们可以在设置中设置投影的偏移以及颜色。但是我们的UI同学想给图片等也添加投影的效果,就无从实现了。然后就丢给我们程序帮忙解决=。=
需求
可以给组件,图片等添加投影,文字投影需要可以设置透明度(目前不行)。
思路
首先自己莫得水平去改FairyGUI Editor编辑器,那就得从unity那下手。如何让unity知道哪些组件我们是想设置投影的,并且获得投影的相关参数。观察发现每个控件的设置中,都有一个自定义数据的设置,我们可以通过在自定义数据中输入一些关键字,然后在Unity解析的时候,进行处理,从而得知该控件是否需要投影以及相关参数。
接着,知道参数后,如何实现投影的效果,一开始给每个需要投影的GObject的GameObject添加一个UnityEngine.UI.Shadow的Component,发现并没什么软用。所以通过原本自带的文本投影的功能,去查看了下他的实现方法,找到相关代码如下:
//TextField.cs
public void OnPopulateMesh(VertexBuffer vb)
{
....
bool hasShadow = _shadowOffset.x != 0 || _shadowOffset.y != 0;
......
if (allocCount != count)
{
VertexBuffer vb2 = VertexBuffer.Begin();
List<Vector3> vertList2 = vb2.vertices;
Y_5_2 || UNITY_5_3_OR_NEWER
List<Vector4> uvList2 = vb2.uv0;
List<Vector2> uvList2 = vb2.uv0;
List<Color32> colList2 = vb2.colors;
Color32 strokeColor = _strokeColor;
......
if (hasShadow)
{
for (int i = 0; i < count; i++)
{
Vector3 vert = vertList[i];
Vector4 u = uvList[i];
uvList2.Add(u);
vertList2.Add(new Vector3(vert.x + _shadowOffset.x, vert.y - _shadowOffset.y, 0));
colList2.Add(strokeColor);
}
}
vb.Insert(vb2);
vb2.End();
}
vb.AddTriangles();
......
}
大致就是依样画葫芦,将原始文本的原始顶点都进行偏移(投影偏移),然后用投影的颜色绘制出文本,然后再绘制原始文本叠加在上面,从而实现投影的功能。其中,我们只要修改strokeColor的布尔值,就可以达到投影透明的效果。
实现
思路就是TextField的投影实现方式,对于TextField,我们只需要修改strokeColor.a的值即可,对于GImage的Image,我们需自己添加类似的代码实现投影的绘制。
需要修改的文件有五个,GComponent,GImage,GObject,GTextField,NGraphics。由于这些都是FairyGUI的文件,直接改动的话之后要是更新了,容易导致覆盖,所以我们可以把这些类都改为partial class然后把大部分的逻辑代码都写在外面。
首先,我要解析我们设定的自定义数据,我定的规则如下ShadowOffset:5,5|ShadowAlpha:0.5|ShadowColor:00FF00,| 分割的键值对。新建一个GObject.cs文件,进行自定义数据的解析
using UnityEngine;
using System;
namespace FairyGUI
{
public partial class GObject : EventDispatcher
{
bool mIsCustomShadowOffset = false;
Vector2 mCustomShadowOffset = Vector2.zero;
public bool IsCustomShadowOffset
{
get { return mIsCustomShadowOffset; }
}
public Vector2 CustomShadowOffset
{
get { return mCustomShadowOffset; }
}
bool mIsCustomShadowColor = false;
Color mCustomShadowColor = Color.black;
public bool IsCustomShadowColor
{
get { return mIsCustomShadowColor; }
}
public Color CustomShadowColor
{
get { return mCustomShadowColor; }
}
bool mIsCustomShadowAlpha = false;
float mCustomShadowAlpha = 1f;
public bool IsCustomShadowAlpha
{
get { return mIsCustomShadowAlpha; }
}
public float CustomShadowAlpha
{
get { return mCustomShadowAlpha; }
}
//call in AnalyUserDefineData
public virtual void CustomAction()
{
}
//call in Setup_AfterAdd
public void AnalyUserDefineData(object data)
{
if (data != null && !"".Equals(data.ToString()))
{
string[] args = data.ToString().Split('|');
foreach(string arg in args)
{
string[] keyvalue = arg.Split(':');
if (keyvalue.Length != 2)
{
continue;
}
if (keyvalue[0].Equals("ShadowOffset"))
{
mIsCustomShadowOffset = true;
string[] array = keyvalue[1].Split(',');
mCustomShadowOffset = new Vector2(float.Parse(array[0]), float.Parse(array[1]));
}
if (keyvalue[0].Equals("ShadowColor"))
{
mIsCustomShadowColor = true;
mCustomShadowColor = GetColorByHex(keyvalue[1]);
}
if (keyvalue[0].Equals("ShadowAlpha"))
{
mIsCustomShadowAlpha = true;
mCustomShadowAlpha = float.Parse(keyvalue[1]);
}
}
if (mIsCustomShadowAlpha && mIsCustomShadowColor)
{
mCustomShadowColor.a = mCustomShadowAlpha;
}
CustomAction();
}
}
//format:FF000
Color GetColorByHex(string v)
{
if(v.Length == 6)
{
try
{
float r = Convert.ToInt32($"{v[0]}{v[1]}", 16) / 255.0f;
float g = Convert.ToInt32($"{v[2]}{v[3]}", 16) / 255.0f;
float b = Convert.ToInt32($"{v[4]}{v[5]}", 16) / 255.0f;
return new Color(r, g, b);
}
catch
{
return Color.black;
}
}
return Color.black;
}
}
}
其中CustomAction在解析数据后操作,具体实现方式会在后面的GImage和GTextField中实现,主要是将解析出来的信息,再次赋值给子类的其他数据。
然后在FairyGUI的GObject的Setup_AfterAdd方法的最后进行调用
AnalyUserDefineData(this.data);
这样就可以获取到每个GObject的投影信息了。拿到投影信息后,我们就要去修改组件的绘制过程了,首先从最简单的文本信息添加投影透明度开始。
新建一个GTextField.cs文件,然后修改GTextField.strokeColor即可。如下:
using UnityEngine;
namespace FairyGUI
{
public partial class GTextField : GObject, ITextColorGear
{
//call in CustomAction
void InitCustomSetting()
{
if (IsCustomShadowOffset)
{
this.shadowOffset = CustomShadowOffset;
}
if (IsCustomShadowColor)
{
this.strokeColor = CustomShadowColor;
}
else
{
if (IsCustomShadowAlpha)
{
Color c = this.strokeColor;
c.a = CustomShadowAlpha;
this.strokeColor = c;
}
}
}
public override void CustomAction()
{
InitCustomSetting();
}
}
}
效果如下
我们在FairyGUI中设置两个带投影的文本,第一个绿色投影,第二个红色投影,其中第二个的自定义数据为ShadowAlpha:0.5|ShadowColor:00FF00
发布后在Unity的效果如下(color和alpha都生效了)
接着是比较复杂的图片投影的处理,现在只处理了九宫格图和普通图片的投影效果,具体代码大家自己看下,这边就贴下改动代码。
首先创建NGraphics.cs文件,用于纪录投影信息(文本的有这些字段如GTextField.shadowOffset和GTextField.strokeColor),图片的需要我们自己创建
using System.Collections.Generic;
using UnityEngine;
namespace FairyGUI
{
public partial class NGraphics : IMeshFactory
{
bool mIsCustomShadowOffset = false;
Vector2 mCustomShadowOffset = Vector2.zero;
Color mCustomShadowColor = Color.black;
//call in GImage CustomAction
public void InitCustomShadow(Vector2 offset, Color c)
{
mIsCustomShadowOffset = true;
mCustomShadowOffset = offset;
mCustomShadowColor = c;
}
//call in NGraphics OnPopulateMesh
//call in Image SliceFill
public bool AddCustomShadow(VertexBuffer vb)
{
if (mIsCustomShadowOffset)
{
VertexBuffer vb2 = VertexBuffer.Begin();
List<Vector3> vertList2 = vb2.vertices;
#if UNITY_5_2 || UNITY_5_3_OR_NEWER
List<Vector4> uvList2 = vb2.uv0;
#else
List<Vector2> uvList2 = vb2.uv0;
#endif
List<Color32> colList2 = vb2.colors;
for (int i = 0; i < vb.vertices.Count; i++)
{
Vector3 vert = vb.vertices[i];
Vector4 u = vb.uv0[i];
uvList2.Add(u);
vertList2.Add(new Vector3(vert.x + mCustomShadowOffset.x, vert.y - mCustomShadowOffset.y, 0));
colList2.Add(mCustomShadowColor);
}
vb.Insert(vb2);
vb2.End();
return true;
}
return false;
}
}
}
然后创建GImage.cs,将GObject的信息传递到NGraphics中,即调用上面的InitCustomShadow方法。
namespace FairyGUI
{
public partial class GImage : GObject, IColorGear
{
public override void CustomAction()
{
if (IsCustomShadowOffset)
{
_content.graphics.InitCustomShadow(CustomShadowOffset, CustomShadowColor);
}
}
}
}
然后我们需要在两个地方添加投影的逻辑,即调用NGraphics的AddCustomShadow方法。普通图片在NGraphics的OnPopulateMesh方法中调用
public partial class NGraphics : IMeshFactory
{
...
public void OnPopulateMesh(VertexBuffer vb)
{
Rect rect = texture.GetDrawRect(vb.contentRect);
vb.AddQuad(rect, vb.vertexColor, vb.uvRect);
AddCustomShadow(vb);
vb.AddTriangles();
vb._isArbitraryQuad = vertexMatrix != null;
}
}
九宫格图片在Image的SliceFill方法中调用
public class Image : DisplayObject, IMeshFactory
{
public void SliceFill(VertexBuffer vb)
{
for (int pi = 0; pi < 9; pi++)
{
...
}
if (graphics.AddCustomShadow(vb))
{
vb.AddTriangles(TRIANGLES_9_GRID);
vb.AddTriangles(TRIANGLES_9_GRID, 16);
}
else
{
vb.AddTriangles(TRIANGLES_9_GRID);
}
vb.AddTriangles();
}
}
效果图如下(紫色的添加了投影的设置 ShadowAlpha:0.5|ShadowColor:00FF00|ShadowOffset:5,5)
最后除了图片和文本外,可能一些自定义的组件我们也需要投影,但是组件内部有很多的元素,一个个添加可能会太麻烦,所以我们可以在GComponent中,若父组件有设置投影信息,而子组件没有,则自动继承下去。
新建一个GComponent.cs文件,里面添加一个方法,用于递归继承自定义数据(GComponent的子组件也可能是GComponent)
using System;
#if FAIRYGUI_TOLUA
using LuaInterface;
#endif
namespace FairyGUI
{
/// <summary>
/// Component
/// </summary>
public partial class GComponent : GObject
{
//call in ConstructFromResource, after child.Setup_BeforeAdd(buffer, curPos);
void AnalyUserDefineDataToChild(GObject obj)
{
if (obj.data != null)
{
GComponent com = obj as GComponent;
if (com != null)
{
GObject[] array = com.GetChildren();
for (int j = 0; j < array.Length; j++)
{
if (array[j].data == null)
{
GComponent childCom = array[j] as GComponent;
if (childCom != null)
{
array[j].data = obj.data;
//if child is GComponent,for example GList, recursion
AnalyUserDefineDataToChild(array[j]);
}
else
{
array[j].AnalyUserDefineData(obj.data);
}
}
}
}
}
}
}
}
最后我们在GComponent的ConstructFromResource方法中调用即可。
public partial class GComponent : GObject
{
.....
internal void ConstructFromResource(List<GObject> objectPool, int poolIndex)
{
......
child.underConstruct = true;
child.Setup_BeforeAdd(buffer, curPos);
AnalyUserDefineDataToChild(child);
child.InternalSetParent(this);
_children.Add(child);
buffer.position = curPos + dataLen;
......
}
......
}