BaseMeshEffect介绍:
要做镜像,就要修改Image的网格,所以要继承BaseMeshEffect,实现修改网格的抽象方法。
graphic是当前的图形,可以转成Image,表示当前脚本上的Image。
ModifyMesh(VertexHelper vh)就是我们要实现的抽象方法了,用来执行修改网格的操作。
具体实现过程如下:
先写个翻转顶点和剔除无效顶点的工具类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MirrorUtil
{
///
/// 根据MirrorType来翻转顶点
///
public static void MirrorVertex(Rect rect, List vertices,MirrorType mirrorType)
{
int count = vertices.Count;
switch (mirrorType)
{
case MirrorType.HORIZONTAL:
ExtendLength(vertices, count);
MirrorVertex(rect, vertices, count, MirrorHorizontal);
break;
case MirrorType.VERTICAL:
ExtendLength(vertices, count);
MirrorVertex(rect, vertices, count, MirrorVertical);
break;
case MirrorType.ALL:
ExtendLength(vertices, count * 3);
MirrorVertex(rect, vertices, count, MirrorHorizontal);
MirrorVertex(rect, vertices, count * 2, MirrorVertical);
break;
default:
throw new ArgumentOutOfRangeException(nameof(mirrorType), mirrorType, null);
}
}
///
/// 翻转顶点
///
private static void MirrorVertex(Rect rect, List vertices,int count,Func getPos)
{
UIVertex vertex;
for (int i = 0; i < count; i++)
{
vertex = vertices[i];
vertex.position = getPos(rect, vertex.position);
vertices.Add(vertex);
}
}
///
/// 横向翻转顶点
///
private static Vector3 MirrorHorizontal(Rect rect, Vector3 pos)
{
return new Vector3(rect.center.x * 2 - pos.x,pos.y,pos.z);
}
///
/// 纵向翻转顶点
///
private static Vector3 MirrorVertical(Rect rect, Vector3 pos)
{
return new Vector3(pos.x,rect.center.y * 2 - pos.y,pos.z);
}
///
/// 扩展指定List的长度,因为当List容量不足时,默认的List的长度是翻倍增加的
///
///
///
private static void ExtendLength(List vertices,int addLength)
{
int length = vertices.Count + addLength;
if (vertices.Capacity < length)
{
vertices.Capacity = length;
}
}
///
/// 剔除无效顶点
///
///
public static void RemoveInvalidVertex(List vertices)
{
int count = vertices.Count;
int i = 0;
while (i < count)
{
UIVertex v1 = vertices[i];
UIVertex v2 = vertices[i+1];
UIVertex v3 = vertices[i+2];
if (v1.position == v2.position
|| v2.position == v3.position
|| v3.position == v1.position)
{
vertices[i] = vertices[count - 3];
vertices[i + 1] = vertices[count - 2];
vertices[i+2] = vertices[count - 1];
count -= 3;
}
else
{
i += 3;
}
}
if (count < vertices.Count)
{
//移除指定范围内的顶点,即剔除掉无效顶点
vertices.RemoveRange(count,vertices.Count - count);
}
}
}
写个编辑器扩展类,用于扩展图片设置大小的功能
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(MirrorImage))]//在MirrorImage脚本上添加编辑器扩展功能
[CanEditMultipleObjects]//选择多个物体的时候也可以操作
public class MirrorImageEditor : Editor
{
private MirrorImage _mirrorImage;
private void OnEnable()
{
//取得MirrorImage脚本
_mirrorImage = serializedObject.targetObject as MirrorImage;
}
///
/// 对该方法的重写,可以自定义对Inspector面板的绘制
///
public override void OnInspectorGUI()
{
//更新序列化物体
serializedObject.Update();
//枚举弹出选择菜单
_mirrorImage._mirrorType = (MirrorType)EditorGUILayout.EnumPopup("镜像类型",_mirrorImage._mirrorType);
if(GUILayout.Button("设置默认图片大小"))
{
//记录RectTransform的操作,不记录的话,无法执行ctrl+z的撤回操作
Undo.RecordObject(_mirrorImage.transform.GetComponent(),"");
_mirrorImage.SetNativeSize();
}
if (GUI.changed)
{
EditorUtility.SetDirty(target);
_mirrorImage.SetVerticesDirty();
}
//应用修改的属性
serializedObject.ApplyModifiedProperties();
}
}
新建一个MirrorImage.cs脚本,挂载在Image上,代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
///
/// 镜像接口
///
public interface IMirror
{
bool CanDraw { get; }
void Init(MirrorType mirrorType, Image image);
void Draw(List vertices);
}
[RequireComponent(typeof(Image))]
public class MirrorImage : BaseMeshEffect
{
//镜像的类型
public MirrorType _mirrorType = MirrorType.HORIZONTAL;
//存放UI的顶点信息
private List _vertices = new List();
//key是图片平铺类型
private Dictionary _mirrors;
private Dictionary Mirrors
{
get
{
if (_mirrors == null)
{
InitMirror();
}
return _mirrors;
}
}
///
/// 初始化镜像Image的类型字典
///
private void InitMirror()
{
_mirrors = new Dictionary();
_mirrors.Add(Image.Type.Simple,new SimpleMirror());
_mirrors.Add(Image.Type.Sliced,new SimpleMirror());
_mirrors.Add(Image.Type.Tiled,new TiledMirror());
}
///
/// 修改网格
///
///
public override void ModifyMesh(VertexHelper vh)
{
//如果当前脚本隐藏了,就执行默认的,变回原来的样子,不加这两句是不会变化的
if (!IsActive())
return;
IMirror mirror = GetMirrorObject();
//取得UI网格的顶点
vh.GetUIVertexStream(_vertices);
//
mirror.Draw(_vertices);
vh.Clear();
vh.AddUIVertexTriangleStream(_vertices);
}
///
/// 获取Mirror的实现类
///
///
private IMirror GetMirrorObject()
{
Image image = graphic as Image;
IMirror mirror = Mirrors[Image.Type.Simple];
if (Mirrors.ContainsKey(image.type))
{
mirror = Mirrors[image.type];
mirror.Init(_mirrorType,image);
if (mirror.CanDraw)
{
return mirror;
}
}
mirror.Init(_mirrorType,image);
return mirror;
}
///
/// 扩大显示区域大小
///
public void SetNativeSize()
{
Image image = graphic as Image;
if (image.overrideSprite != null)
{
//pixelsPerUnit表示Unity中一个单位对应这个图片的多少像素
float width = image.overrideSprite.rect.width / image.pixelsPerUnit;
float height = image.overrideSprite.rect.height / image.pixelsPerUnit;
//Unity中点击SetNativeSize后,anchorMax的数值会变得跟anchorMin一样
image.rectTransform.anchorMax = image.rectTransform.anchorMin;
Vector2 temp = Vector2.zero;
switch (_mirrorType)
{
case MirrorType.HORIZONTAL:
temp.x = width * 2;
temp.y = height;
break;
case MirrorType.VERTICAL:
temp.x = width;
temp.y = height * 2;
break;
case MirrorType.ALL:
temp.x = width * 2;
temp.y = height * 2;
break;
default:
throw new ArgumentOutOfRangeException();
}
image.rectTransform.sizeDelta = temp;
image.SetVerticesDirty();
}
}
///
/// 设置脏标志,才能更新UI
///
public void SetVerticesDirty()
{
Image image = graphic as Image;
image.SetVerticesDirty();
}
}
///
/// 镜像类型,包括横向,纵向,横纵向
///
public enum MirrorType
{
HORIZONTAL,
VERTICAL,
ALL
}
新建一个镜像接口的实现类SimpleMirror.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SimpleMirror : IMirror
{
private MirrorType _mirrorType;
private Image _image;
public bool CanDraw
{
get { return true; }
}
public void Init(MirrorType mirrorType, Image image)
{
_mirrorType = mirrorType;
_image = image;
}
///
/// 绘制图形
///
///
public void Draw(List vertices)
{
//目标绘制区域的坐标及大小(x,y为该UI相对于轴心的坐标,width,height为UI宽高)
Rect rect = _image.GetPixelAdjustedRect();
//将顶点位置变换到左上角
ChangeVertexPos(rect, vertices);
//移除无效顶点
MirrorUtil.RemoveInvalidVertex(vertices);
//根据MirrorType来翻转顶点
MirrorUtil.MirrorVertex(rect,vertices,_mirrorType);
}
///
/// 将图像分成四份,然后图像变换到左上角的位置
///
///
///
private void ChangeVertexPos(Rect rect, List vertices)
{
Vector3 pos = Vector3.zero;
UIVertex uiVertex;
for (int i = 0; i < vertices.Count; i++)
{
uiVertex = vertices[i];
pos = uiVertex.position;
if (_mirrorType == MirrorType.HORIZONTAL || _mirrorType == MirrorType.ALL)
{
pos.x = (pos.x + rect.x) * 0.5f;
}
if (_mirrorType == MirrorType.VERTICAL || _mirrorType == MirrorType.ALL)
{
//不加上+ rect.height的话,y方向的值变换后就在左下角了
pos.y = (pos.y + rect.y + rect.height) * 0.5f;
}
uiVertex.position = pos;
vertices[i] = uiVertex;
}
}
}
再新建一个镜像接口的实现类TiledMirror.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;
public class TiledMirror : IMirror
{
private MirrorType _mirrorType;
private Image _image;
///
/// 判断是否可以绘制
///
public bool CanDraw
{
get { return _image.overrideSprite != null; }
}
public void Init(MirrorType mirrorType, Image image)
{
_mirrorType = mirrorType;
_image = image;
}
///
/// 绘制翻转后的UV
///
///
public void Draw(List vertices)
{
Sprite sprite = _image.overrideSprite;
//获得sprite的uv坐标
Vector4 uv = DataUtility.GetOuterUV(sprite);
float width = sprite.rect.width / _image.pixelsPerUnit;
float height = sprite.rect.height / _image.pixelsPerUnit;
//目标绘制区域的坐标及大小(x,y为该UI相对于轴心的坐标,width,height为UI宽高)
Rect rect = _image.GetPixelAdjustedRect();
for (int i = 0; i < vertices.Count /3; i++)
{
UIVertex v1 = vertices[i * 3];
UIVertex v2 = vertices[i* 3+1];
UIVertex v3 = vertices[i* 3+2];
Vector2 center = GetCenter(v1.position, v2.position, v3.position);
if (_mirrorType == MirrorType.HORIZONTAL || _mirrorType == MirrorType.ALL)
{
//用来判断属于哪个区域块
int id = Mathf.CeilToInt((center.x - rect.xMin) / width);
//偶数区域才需要翻转UV
if (id % 2 == 0)
{
v1.uv0 = GetFlipUV(v1.uv0, uv, true);
v2.uv0 = GetFlipUV(v2.uv0, uv, true);
v3.uv0 = GetFlipUV(v3.uv0, uv, true);
}
}
if (_mirrorType == MirrorType.VERTICAL || _mirrorType == MirrorType.ALL)
{
//用来判断属于哪个区域块
int id = Mathf.CeilToInt((center.y - rect.yMin) / height);
//偶数区域才需要翻转UV
if (id % 2 == 0)
{
v1.uv0 = GetFlipUV(v1.uv0, uv, false);
v2.uv0 = GetFlipUV(v2.uv0, uv, false);
v3.uv0 = GetFlipUV(v3.uv0, uv, false);
}
}
vertices[i* 3] = v1;
vertices[i* 3+1] = v2;
vertices[i* 3+2] = v3;
}
}
///
/// 获取三个坐标点的中心
///
///
private Vector2 GetCenter(Vector3 p1,Vector3 p2,Vector3 p3)
{
float x = GetCenterValue(p1.x, p2.x, p3.x);
float y = GetCenterValue(p1.y, p2.y, p3.y);
return new Vector2(x,y);
}
private float GetCenterValue(float f1,float f2,float f3)
{
float min = Mathf.Min(Mathf.Min(f1, f2), f3);
float max = Mathf.Max(Mathf.Max(f1, f2), f3);
return (min + max) * 0.5f;
}
///
/// UV翻转
///
///
private Vector2 GetFlipUV(Vector2 uv,Vector4 outerUv,bool isHorizontal)
{
//outerUv的左下角坐标为(x,y),右上角坐标为(z,w)
if (isHorizontal)
{
uv.x = outerUv.x + outerUv.z - uv.x;
}
else
{
uv.y = outerUv.y + outerUv.w - uv.y;
}
return uv;
}
}