网站建设方案平台选择,中信建设有限责任公司资质,WordPress不会php,医生咨询在线24小时免费1、为什么要使用镜像
在游戏开发过程中#xff0c;我们经常会为了节省 美术图片资源大小#xff0c;美术会将两边相同的图片进行切一半来处理。如下所示一个按钮 需要 400 * 236#xff0c;然而美术只需要切一张 74*236的大小就可以了。这样一来图集就可以容纳更多的图片。…
1、为什么要使用镜像
在游戏开发过程中我们经常会为了节省 美术图片资源大小美术会将两边相同的图片进行切一半来处理。如下所示一个按钮 需要 400 * 236然而美术只需要切一张 74*236的大小就可以了。这样一来图集就可以容纳更多的图片。内存占用也更少。 2.实现方案
拷贝一张图片然后把 scale改成-1这种方法比较笨需要多加一张图片操作起来也很不方便。没啥好讲的。拷贝原有的图片顶点进行对称处理。如下图所示。 3.在哪里拿到mesh顶点修改顶点
BaseMeshEffect 是用于实现网格效果的抽象类实现IMeshModifier接口是Shadow、Outline等效果的基类。 从Unity uGUI原理解析-Graphic可以知道Graphic 在执行 DoMeshGeneration 时会获取到当前GameObject上的所有实现 IMeshModifier 的组件。 并且会调用 ModifyMesh 方法来修改当前Graphic 的Mesh数据。BaseMeshEffect 的类结构图如下 我们编写一个MirrorImage来继承BaseMeshEffect,拿到 VertexHelper获取网格的顶点流数据然后进行下面的镜像映射操作
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UI;public enum MirrorType
{Horizontal,Vertical,HorizontalAndVertical
}[RequireComponent(typeof(Image))]
public class MirrorImage : BaseMeshEffect
{public MirrorType mirrorType MirrorType.Horizontal;public DictionaryImage.Type, IMirror MirrorDic new DictionaryImage.Type, IMirror(){{Image.Type.Simple, new SimpleMirror()},{Image.Type.Sliced, new SlicedMirror()},{Image.Type.Tiled, new TiledMirror()},{Image.Type.Filled, new FilledMirror()},};public Image image graphic as Image;public override void ModifyMesh(VertexHelper vh){if (!IsActive()) return;Image.Type imageType (graphic as Image).type;ListUIVertex vertices new ListUIVertex();vh.GetUIVertexStream(vertices);vh.Clear();MirrorDic[imageType].Draw(image, vertices, mirrorType);vh.AddUIVertexTriangleStream(vertices);}[Button(Set Native Size)]public void SetNativeSize(){if (image.sprite ! null){float w image.sprite.rect.width / image.pixelsPerUnit;float h image.sprite.rect.height / image.pixelsPerUnit;image.rectTransform.anchorMax image.rectTransform.anchorMin;float x mirrorType MirrorType.Horizontal || mirrorType MirrorType.HorizontalAndVertical ? 2 : 1;float y mirrorType MirrorType.Vertical || mirrorType MirrorType.HorizontalAndVertical ? 2 : 1;image.rectTransform.sizeDelta new Vector2(w * x, h * y);image.SetAllDirty();}}
}当然除了继承BaseMeshEffect当然还可以直接继承Image重写OnPopulateMesh。
4.如何实现顶点的镜像
很简单 假设中心的是 center( 00需要水平镜像的点 A(-1,0) 镜像后的点就是 B-1,0需要满足 A 到Center的距离 B到center的距离
所以我们先求出镜像的中心点 center的位置 因为矩形有自己的中心点位置我们需要求出镜像的中心点位置在改矩形下的坐标这么说可能有点绕看下图
矩形的宽w:100,h:100,矩形自身的中心点蓝色的圈 这边为称为O点在Unity中是以 Rect中的 x,y代表的是坐下角的点 既 (-75,-50),
对于Rect(x,y) 不理解的话可以看下GPT的回答可能比我讲的清楚 那么镜像真正的中心点坐标
center.x rect.x rect.width;center.y rect.y rect.height; 那么要镜像的点 A 镜像后为B ,需要求出A到center的长度大小然后取反 矩形中心点与镜像中心点的偏移
B.x -(A.x - center.x) (rect.xrect.width/2)
B.y -(A.y - center.y) (rect.xrect.width/2) 逻辑分析完了直接看代码吧 using System.Collections.Generic;
using UnityEngine;public static class MirrorUtlis
{public static void Mirror(Rect rect,ListUIVertex uiVertices,MirrorType type){int count uiVertices.Count;switch (type){case MirrorType.Horizontal:Mirror(rect, uiVertices, type, count);break;case MirrorType.Vertical:Mirror(rect, uiVertices, type, count);break;case MirrorType.HorizontalAndVertical:Mirror(rect, uiVertices, MirrorType.Horizontal, count);Mirror(rect, uiVertices, MirrorType.Vertical, 2 * count);break;}RemoveVertices(uiVertices);}private static void Mirror(Rect rect, ListUIVertex uiVertices, MirrorType type, int count){for (int i 0; i count; i){UIVertex vertex uiVertices[i];switch (type){case MirrorType.Horizontal:vertex HorizontalMirror(rect, vertex);break;case MirrorType.Vertical:vertex VerticalMirror(rect, vertex);break;case MirrorType.HorizontalAndVertical:vertex HorizontalMirror(rect, vertex);vertex VerticalMirror(rect, vertex);break;}uiVertices.Add(vertex);}}private static UIVertex HorizontalMirror(Rect rect, UIVertex vertex){float center rect.width / 2 rect.x;vertex.position.x -(vertex.position.x - center) rect.x rect.width/2;return vertex;}private static UIVertex VerticalMirror(Rect rect, UIVertex vertex){float center rect.height / 2 rect.y;vertex.position.y -(vertex.position.y - center) rect.y rect.height/2;return vertex;}// 移除构不成三角形的顶点private static void RemoveVertices(ListUIVertex uiVertices){int end uiVertices.Count;for (int i 2; i end; i 3){UIVertex v1 uiVertices[i];UIVertex v2 uiVertices[i - 1];UIVertex v3 uiVertices[i - 2];if (v1.position v2.position ||v1.position v3.position ||v2.position v3.position){// 移动到尾部ChaneVertices(uiVertices, i - 1, end - 3);ChaneVertices(uiVertices, i - 2, end - 2);ChaneVertices(uiVertices, i, end - 1);end - 3;}}if(end uiVertices.Count)uiVertices.RemoveRange(end,uiVertices.Count - end);}private static void ChaneVertices(ListUIVertex uiVertices,int a,int b){(uiVertices[a], uiVertices[b]) (uiVertices[b], uiVertices[a]);}
}
在顶点镜像前我们需要对顶点进行顶点映射什么时时映射 如上图所示原来图片是白色区域大小顶点为白色图片的四个顶点因为要做对称所以需要留出一半的位置来增加映射后的顶点。
在不同模式下的顶点映射需要做不同处理在Unity中有一下几种模式
Simple: 如上面的图所示对顶点的位置 除以 2 即可比较简单Sliced:九宫格模式下因为要保留九宫格的顶点位置不能让九宫格保留的位置发生形变就不能直接除以2来处理需要做平移处理。具体实现下面在讲Filed:平铺模式下是不需要在镜像网格顶点的因为平铺下的图片顶点是已经增加好的了我们只需要对UV做镜像就可以了Filled:暂时未实现该模式下的以后有时间在研究。
由于有多种模式的处理我们将实现接口化方便我们的管理定义一个IMirror接口:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public interface IMirror
{void Draw(Image image,ListUIVertex uiVertices,MirrorType type);
}Simpe下的顶点映射
public class SimpleMirror : IMirror
{public void Draw(Image image,ListUIVertex uiVertices,MirrorType type){ChangeVertices(image.rectTransform.rect,uiVertices,type);MirrorUtlis.Mirror(image.rectTransform.rect,uiVertices,type);}// 改变原有的顶点位置 做一半映射 如果是 Horizontal:左半部分 Vertical:上半部分 HorizontalAndVertical:左上四分之一private void ChangeVertices(Rect rect,ListUIVertex uiVertices,MirrorType type){for (int i 0; i uiVertices.Count; i){UIVertex vertex uiVertices[i];switch (type){case MirrorType.Horizontal:vertex HorizontalVertex(rect, vertex);break;case MirrorType.Vertical:vertex VerticalVertex(rect, vertex);break;case MirrorType.HorizontalAndVertical:vertex HorizontalVertex(rect, vertex);vertex VerticalVertex(rect, vertex);break;}uiVertices[i] vertex;}}// 水平映射private UIVertex HorizontalVertex(Rect rect,UIVertex vertex){vertex.position.x (vertex.position.x rect.x) / 2;// - rect.width / 2;return vertex;}// 垂直映射private UIVertex VerticalVertex(Rect rect,UIVertex vertex){vertex.position.y (rect.y vertex.position.y) / 2 rect.height / 2;return vertex;}
}
Sliced下的顶点映射
我们可以看到如下映射的主要方法
// 水平映射
private UIVertex HorizontalVertex(Rect rect,UIVertex vertex)
{if (vertex.position.x s_VertScratch[0].x || vertex.position.x s_VertScratch[1].x) return vertex;vertex.position.x - rect.width / 2;return vertex;
} 时机上非常简单就是直接对 x 做矩形宽度/2 的平移比较难的是我们需要知道什么顶点需要做平移
这个需要看一下Image的源码是如何实现顶点的处理 /// summary/// Generate vertices for a 9-sliced Image./// /summaryprivate void GenerateSlicedSprite(VertexHelper toFill){if (!hasBorder){GenerateSimpleSprite(toFill, false);return;}Vector4 outer, inner, padding, border;if (activeSprite ! null){outer Sprites.DataUtility.GetOuterUV(activeSprite);inner Sprites.DataUtility.GetInnerUV(activeSprite);padding Sprites.DataUtility.GetPadding(activeSprite);border activeSprite.border;}else{outer Vector4.zero;inner Vector4.zero;padding Vector4.zero;border Vector4.zero;}Rect rect GetPixelAdjustedRect();Vector4 adjustedBorders GetAdjustedBorders(border / multipliedPixelsPerUnit, rect);padding padding / multipliedPixelsPerUnit;s_VertScratch[0] new Vector2(padding.x, padding.y);s_VertScratch[3] new Vector2(rect.width - padding.z, rect.height - padding.w);s_VertScratch[1].x adjustedBorders.x;s_VertScratch[1].y adjustedBorders.y;s_VertScratch[2].x rect.width - adjustedBorders.z;s_VertScratch[2].y rect.height - adjustedBorders.w;for (int i 0; i 4; i){s_VertScratch[i].x rect.x;s_VertScratch[i].y rect.y;}............}
这段源码不全我截取了计算图片的位置信息然后把4个顶点位置信息按顺序写进s_VertScratch数组中。这四个位置分别对应一下位置 即九宫格裁剪后映射到Image顶点上的四个位置所以当我们向做水平映射的时候只需要平移和 3和4 x轴相等的顶点,与1和2 x轴相等的顶点保留原来的位置。如下图可以很直观的看出来 至于怎么算出这四个九宫格映射的顶点就直接拷贝Image源码的实现就好了。
贴一下完整代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class SlicedMirror : IMirror
{private Image image;// 九宫格的四个分界点private Vector2[] s_VertScratch new Vector2[4];public void Draw(Image image,ListUIVertex uiVertices,MirrorType type){this.image image;SetVertScratch();ChangeVertices(image.rectTransform.rect,uiVertices,type);MirrorUtlis.Mirror(image.rectTransform.rect,uiVertices,type);}private void ChangeVertices(Rect rect,ListUIVertex uiVertices,MirrorType type){for (int i 0; i uiVertices.Count; i){UIVertex vertex uiVertices[i];switch (type){case MirrorType.Horizontal:vertex HorizontalVertex(rect, vertex);break;case MirrorType.Vertical:vertex VerticalVertex(rect, vertex);break;case MirrorType.HorizontalAndVertical:vertex HorizontalVertex(rect, vertex);vertex VerticalVertex(rect, vertex);break;}uiVertices[i] vertex;}}// 水平映射private UIVertex HorizontalVertex(Rect rect,UIVertex vertex){if (vertex.position.x s_VertScratch[0].x || vertex.position.x s_VertScratch[1].x) return vertex;vertex.position.x - rect.width / 2;return vertex;}// 垂直映射private UIVertex VerticalVertex(Rect rect,UIVertex vertex){if (vertex.position.y s_VertScratch[2].y || vertex.position.y s_VertScratch[3].y) return vertex;vertex.position.y rect.height / 2;return vertex;}private void SetVertScratch(){Sprite activeSprite image.sprite;Vector4 padding, border;if (activeSprite ! null){padding UnityEngine.Sprites.DataUtility.GetPadding(activeSprite);border activeSprite.border;}else{padding Vector4.zero;border Vector4.zero;}Rect rect image.GetPixelAdjustedRect();var multipliedPixelsPerUnit image.pixelsPerUnit * image.pixelsPerUnitMultiplier;Vector4 adjustedBorders GetAdjustedBorders(border / multipliedPixelsPerUnit, rect);padding / multipliedPixelsPerUnit;s_VertScratch[0] new Vector2(padding.x, padding.y);s_VertScratch[3] new Vector2(rect.width - padding.z, rect.height - padding.w);s_VertScratch[1].x adjustedBorders.x;s_VertScratch[1].y adjustedBorders.y;s_VertScratch[2].x rect.width - adjustedBorders.z;s_VertScratch[2].y rect.height - adjustedBorders.w;for (int i 0; i 4; i){s_VertScratch[i].x rect.x;s_VertScratch[i].y rect.y;}}private Vector4 GetAdjustedBorders(Vector4 border, Rect adjustedRect){Rect originalRect image.rectTransform.rect;for (int axis 0; axis 1; axis){float borderScaleRatio;if (originalRect.size[axis] ! 0){borderScaleRatio adjustedRect.size[axis] / originalRect.size[axis];border[axis] * borderScaleRatio;border[axis 2] * borderScaleRatio;}float combinedBorders border[axis] border[axis 2];if (adjustedRect.size[axis] combinedBorders combinedBorders ! 0){borderScaleRatio adjustedRect.size[axis] / combinedBorders;border[axis] * borderScaleRatio;border[axis 2] * borderScaleRatio;}}return border;}
}Tiled:模式下的映射
在平铺模式下顶点都是完整的不需要做镜像处理只需要修改没一块对应的UV对称即可我们固定开始位置为1,不做翻转那么
2位置所构成的顶点UV.y:就需要做对称处理
4位置所构成的顶点UV.x:就需要做对称处理
3位置所构成的顶点UV.x和y :都需要做对称处理 如何判断某几个顶点的UV需要做对称 我们知道一个三角面由三个顶点构成那么我么只需要找出三个顶点的中心位置假设sprite的宽高都为w 100,h 100构成的三角形区域的中心点分别为
1位置 5050
2位置 (50,150)
3位置 (150,150)
4位置 (150,50)
我们对中心的的
1 x / W 0.5
2 x / W 1.5
。。
我们对结果 %2 那么 结果为 1 的就需要翻转了
uv怎么翻转
outerUv UnityEngine.Sprites.DataUtility.GetOuterUV(image.sprite);
outerUV中的 x,y就代表右下角的点z就代表宽w:代表高假设A镜像UV后的点为B那么就满足
A 到 00的距离 B到0z的距离所以
镜像后的 A.x outer.z -( A.x - outerUv.x ) 贴一下完整代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class TiledMirror : IMirror
{private Vector4 outerUv;public void Draw(Image image,ListUIVertex uiVertices,MirrorType type){outerUv UnityEngine.Sprites.DataUtility.GetOuterUV(image.sprite);ChangeUv(uiVertices,type);}// uv翻转private void ChangeUv(ListUIVertex uiVertices,MirrorType type){Vector3 cellMinP uiVertices[0].position;Vector3 cellMaxP uiVertices[2].position;float w cellMaxP.x - cellMinP.x;float h cellMaxP.y - cellMinP.y;for (int i 0; i uiVertices.Count; i 3){UIVertex v1 uiVertices[i];UIVertex v2 uiVertices[i1];UIVertex v3 uiVertices[i2];float centerX GetCenter(v1, v2, v3, true) - cellMaxP.x;float centerY GetCenter(v1, v2, v3, false) - cellMaxP.y;bool changeX Mathf.Ceil(centerX / w) % 2 ! 0;bool changeY Mathf.Ceil(centerY / h) % 2 ! 0;if (changeX (type MirrorType.Horizontal || type MirrorType.HorizontalAndVertical)){v1 HorizontalUv(v1);v2 HorizontalUv(v2);v3 HorizontalUv(v3);}if (changeY (type MirrorType.Vertical || type MirrorType.HorizontalAndVertical)){v1 VerticalUv(v1);v2 VerticalUv(v2);v3 VerticalUv(v3);}uiVertices[i] v1;uiVertices[i 1] v2;uiVertices[i 2] v3;}}// 获取三个顶点的中心位置private float GetCenter(UIVertex v1,UIVertex v2,UIVertex v3,bool isX){float min Mathf.Min(Mathf.Min(isX ? v1.position.x : v1.position.y,isX ? v2.position.x : v2.position.y),Mathf.Min(isX ? v1.position.x : v1.position.y,isX ? v3.position.x : v3.position.y));float max Mathf.Max(Mathf.Max(isX ? v1.position.x : v1.position.y,isX ? v2.position.x : v2.position.y),Mathf.Max(isX ? v1.position.x : v1.position.y,isX ? v3.position.x : v3.position.y));return (min max) / 2;}private UIVertex HorizontalUv(UIVertex vertex){vertex.uv0.x outerUv.x outerUv.z - vertex.uv0.x;return vertex;}private UIVertex VerticalUv(UIVertex vertex){vertex.uv0.y outerUv.y outerUv.w - vertex.uv0.y;return vertex;}}总结
实现过程中思路理清之后实现基本上是不难但是需要去理解Unity Image的实现针对绘制不同模式的生成网格顶点的实现知道图篇是怎么绘制上去的三个顶点构成一个面Rect中的x,y,z,w分别代表什么。然后就是计算计算。