这是结果图和对应的网格图,网格图把上面多于的字体和路径删了,只留下背景地图和小图标,只使用了一张image就可以实现地图和多个小图标
在写之前查了好多小地图的写法,这里有三种
下面上实现方法:
自定义Image
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MinimapImage : Image
{
public List Icons = new List();
private UIVertex GetVertex(float x, float y, Vector2? iconTag = null, Color? color = null)
{
UIVertex uiVertex = new UIVertex();
uiVertex.color = color ?? this.color;
uiVertex.position.x = rectTransform.rect.width * (x - 0.5f);
uiVertex.position.y = rectTransform.rect.height * (y - 0.5f);
uiVertex.uv0 = new Vector2(x, y);
uiVertex.uv1 = iconTag != null ? iconTag.Value / 2 + 0.5f * Vector2.one : Vector2.zero;
return uiVertex;
}
protected override void OnPopulateMesh(VertexHelper toFill)
{
toFill.Clear();
toFill.AddUIVertexQuad(new[] {GetVertex(0, 0), GetVertex(0, 1), GetVertex(1, 1), GetVertex(1, 0)});
lock (Icons)
{
for (int i = 0; i < Icons.Count; i++)
{
AddIconVertexArr(toFill, Icons[i]);
}
}
}
private void AddIconVertexArr(VertexHelper vh, Icon icon)
{
UIVertex[] vertices = new UIVertex[4];
Vector2[] poss =
{
icon.LBPos,
icon.LTPos,
icon.RTPos,
icon.RBPos,
};
vertices[0] = GetVertex(poss[0].x,poss[0].y, icon.UVLeftBottom);
vertices[1] = GetVertex(poss[1].x,poss[1].y,
new Vector2(icon.UVLeftBottom.x, icon.UVLeftBottom.y + icon.UVWidthLength.y));
vertices[2] = GetVertex(poss[2].x,poss[2].y, new Vector2(icon.UVLeftBottom.x + icon.UVWidthLength.x, icon.UVLeftBottom.y + icon.UVWidthLength.y));
vertices[3] = GetVertex(poss[3].x,poss[3].y,
new Vector2(icon.UVLeftBottom.x + icon.UVWidthLength.x, icon.UVLeftBottom.y));
vh.AddUIVertexQuad(vertices);
}
public class Icon
{
public Vector2 PosCenter;
public Vector2 LBPos;
public Vector2 LTPos;
public Vector2 RTPos;
public Vector2 RBPos;
public float Angle;
public Vector2 UVLeftBottom;
public Vector2 UVWidthLength;
public Icon(Vector2 posCenter, Vector2 lbPos, Vector2 ltPos, Vector2 rtPos, Vector2 rbPos, float angle, Vector2 uvLeftBottom, Vector2 uvWidthLength)
{
PosCenter = posCenter;
LBPos = lbPos;
LTPos = ltPos;
RTPos = rtPos;
RBPos = rbPos;
Angle = angle;
UVLeftBottom = uvLeftBottom;
UVWidthLength = uvWidthLength;
}
}
}
shader:从unity自带的sprite shader稍加修改过来的
Shader "Custom/minimap"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_IconTex("Icon Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
fixed2 texcoord1 : TEXCOORD1;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO
fixed2 texcoord1 : TEXCOORD2;
};
fixed4 _Color;
sampler2D _MainTex;
fixed4 _MainTex_ST;
sampler2D _IconTex;
fixed4 _IconTex_ST;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
OUT.texcoord1 = v.texcoord1;
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
fixed4 c;
if(IN.texcoord1.x >= 0.5)
{
c = tex2D(_IconTex, (IN.texcoord1 - 0.5)*2);
}else{
c = tex2D(_MainTex, IN.texcoord);
}
c * IN.color;
#ifdef UNITY_UI_CLIP_RECT
c.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (c.a - 0.001);
#endif
return c;
}
ENDCG
}
}
}
cs代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Framework;
using UnityEngine;
public class MiniMap : MonoBehaviour
{
public enum MapType
{
Radar,
Map,
}
public MapType Type;
private MinimapImage MinimapImage;
private Vector2 imgMapLB
{
get
{
Vector2 lb = new Vector2(playerPos.x - uiMapToRealWidth / 2,
playerPos.z - uiMapToRealHeight / 2);
if (playerPos.x + uiMapToRealWidth / 2 > mapWidth)
{
lb.x = mapWidth - uiMapToRealWidth;
}
else if (playerPos.x - uiMapToRealWidth / 2 < 0)
{
lb.x = uiMapToRealWidth / 2;
}
if (playerPos.z + uiMapToRealHeight / 2 > mapHeight)
{
lb.y = mapHeight - uiMapToRealHeight;
}
else if (playerPos.z - uiMapToRealHeight / 2 < 0)
{
lb.y = uiMapToRealHeight / 2;
}
return lb;
}
}
private Vector3 originMapCenter;
private Vector3 playerPos;
//UI map的宽高
private float uiMapHeight => MinimapImage.rectTransform.rect.height;
private float uiMapWidth => MinimapImage.rectTransform.rect.width;
//对应真是地图的宽 高
private float uiMapToRealHeight => uiMapToRealWidth * uiMapHeight / uiMapWidth;
private float uiMapToRealWidth;
private float mapWidth;
private float mapHeight;
private Vector2 uvScale;
private UiSpriteMultiAsset spriteAsset;
private bool isSpriteLoaded;
private List Icons = new List();
private void Awake()
{
MinimapImage = GetComponent();
}
public void SetMapWidthHeight(float width, float height)
{
mapWidth = width;
mapHeight = height;
}
public void SetUI2RealScale(float widthScale)
{
uiMapToRealWidth = widthScale;
uvScale = new Vector2(uiMapToRealWidth / mapWidth, uiMapToRealHeight / mapHeight);
}
public void SetOriginMapCenter(Vector3 mapCenter)
{
originMapCenter = mapCenter;
}
public void SetPlayerPos(Vector3 pos)
{
playerPos = pos;
}
public void UpdateIcons(List icons)
{
Icons.Clear();
Icons.AddRange(icons);
MinimapImage.Icons.Clear();
foreach (var icon in icons)
{
AddIcon(icon.Pos, icon.Radius, icon.Dir, icon.SpritePath, false);
}
MinimapImage.SetVerticesDirty();
}
public void UpdateMapUV()
{
Vector2 lb = imgMapLB;
Vector2 playerOffset = new Vector2(lb.x + uiMapToRealWidth / 2, lb.y + uiMapToRealHeight / 2);
float widthRate = playerOffset.x / mapWidth;
float heightRate = playerOffset.y / mapHeight;
SetMapUV(new Vector4(uvScale.x, uvScale.y, widthRate - uvScale.x / 2, heightRate - uvScale.y / 2));
}
private Vector2 GetIconPos(Vector3 pos)
{
float widthRate = 1;
float heightRate = 1;
switch (Type)
{
case MapType.Radar:
var offsetPos = new Vector2(pos.x, pos.z) - imgMapLB;
widthRate = offsetPos.x / uiMapToRealWidth;
heightRate = 1 - offsetPos.y / uiMapToRealHeight;
break;
case MapType.Map:
var offset = pos - originMapCenter;
widthRate = offset.x / mapWidth;
heightRate = offset.z / mapHeight;
break;
}
return new Vector2(widthRate, heightRate);
}
private void AddIcon(Vector3 pos, float radius, Vector3 dir, string spritePath, bool isUpdateVertices = true)
{
if (spriteAsset == null)
{
InitSpriteAsset(spritePath);
}
Icons.Add(new MinimapIcon(pos, radius, dir, spritePath));
if(!isSpriteLoaded) return;
var (uvLb, wl) = CalcUV(spriteAsset.GetSprite(GetSpriteName(spritePath)));
var iconPos = GetIconPos(pos);
if(Type == MapType.Radar && (iconPos.x > 1 || iconPos.y > 1)) return;
float angle = Vector3.Angle(Vector3.forward, dir);
Vector3 normal = Vector3.Cross (dir, Vector3.forward);
angle *= Mathf.Sign (Vector3.Dot(normal,Vector3.up));
MinimapImage.Icons.Add(AdjustIconPos(iconPos, radius, angle, uvLb, wl));
if (isUpdateVertices)
MinimapImage.SetVerticesDirty();
}
///
/// 调整icon的旋转,还有因为地图宽高不一致造成的icon拉伸
///
private MinimapImage.Icon AdjustIconPos(Vector2 center, float radius, float angle, Vector2 uvLb, Vector2 wl)
{
Matrix4x4 rotateMat = Matrix4x4.Translate(center) * Matrix4x4.Rotate(Quaternion.Euler(0, 0, angle)) *
Matrix4x4.Translate(-center);
Vector2 lb = rotateMat * new Vector4(center.x - radius / 2, center.y - radius / 2,1,1);
Vector2 lt = rotateMat * new Vector4(center.x - radius / 2, center.y + radius / 2,1,1);
Vector2 rt = rotateMat * new Vector4(center.x + radius / 2, center.y + radius / 2,1,1);
Vector2 rb = rotateMat * new Vector4(center.x + radius / 2, center.y - radius / 2,1,1);
float rate = uiMapWidth / uiMapHeight;
lb.y = center.y + (lb.y - center.y) * rate;
lt.y = center.y + (lt.y - center.y) * rate;
rt.y = center.y + (rt.y - center.y) * rate;
rb.y = center.y + (rb.y - center.y) * rate;
var icon = new MinimapImage.Icon(center, lb, lt, rt, rb, angle, uvLb, wl);
return icon;
}
private void InitSpriteAsset(string path)
{
path = GetAtlasPath(path);
spriteAsset = new UiSpriteMultiAsset();
spriteAsset.Load(path, () =>
{
List tempIcons = new List(Icons);
Icons.Clear();
MinimapImage.material.SetTexture("_IconTex", spriteAsset.assets.First().Value.texture);
isSpriteLoaded = true;
UpdateIcons(tempIcons);
});
}
private string GetAtlasPath(string path)
{
int index = path.LastIndexOf("/");
path = path.Substring(0, index);
return $"{path}.png";
}
private string GetSpriteName(string path)
{
return Path.GetFileNameWithoutExtension(path);
}
private (Vector2 uvLb, Vector2 wl) CalcUV(Sprite sprite)
{
Rect UVs = sprite.rect;
UVs.x /= sprite.texture.width;
UVs.width /= sprite.texture.width;
UVs.y /= sprite.texture.height;
UVs.height /= sprite.texture.height;
return (UVs.position, UVs.size);
}
private void SetMapUV(Vector4 st)
{
MinimapImage.material.SetVector("_MainTex_ST", st);
}
private void OnDestroy()
{
spriteAsset?.Release();
}
public struct MinimapIcon
{
public Vector3 Pos;
public float Radius;
public Vector3 Dir;
public string SpritePath;
public MinimapIcon(Vector3 pos, float radius, Vector3 dir, string spritePath)
{
Pos = pos;
Radius = radius;
Dir = dir;
SpritePath = spritePath;
}
}
}
整体思想就是背景地图图片更改uv来显示,其他icon画到背景的image上,通过指定不同的uv来显示icon的图标,在游戏过程中不断更改mesh和uv来达到小地图的效果。