unity3d:小地图UV,UGUIshader毒圈挖孔,吃鸡跑毒缩圈

文章详细介绍了在Unity中实现小地图和大地图的技巧,包括使用Mask和RawImage处理UV来显示地图,以及确定玩家和目标在地图上的位置。同时,文章阐述了缩圈机制的两个阶段,内切运动和向小圆运动,以及如何通过调整半径和圆心位置实现这一效果。最后提到了UGUI上的挖孔技术,使用Shader处理大圆和小圆的显示与隐藏。
摘要由CSDN通过智能技术生成

运行效果

场景中缩圈

在这里插入图片描述

小地图中挖孔

在这里插入图片描述

大地图中挖孔

在这里插入图片描述

小地图

方案1使用Mask

给了一个方形的mask组件,然后根据玩家位置计算出地图左下角的位置进行移动。这种实现方式虽然简单,但是会有两个问题:
1.Overdraw特别大,几乎很多时候会有整个屏幕的overdraw;
2.玩家在移动过程中,因为一直在持续移动图片的位置(做了适当的降频处理),所以会一直有UI的Mesh重建过程。

方案2使用RawImage,UV

小地图使用RawImage,设置显示大小为300*300,其中Texture,放入场景的顶视图
在这里插入图片描述

如何确定小地图的UV范围

1.先确定w的值为0.1,代表会从整个顶视图中取宽度占比0.1
2.再确定h的值,因为地图长宽不相等,按照 w 总宽 = h*总高,这样可以得到一个正方形显示

        public float m_mapWidth = 1680; //世界中场景的宽度,米
        public float m_mapHeight = 960; //世界中场景的高度,米
        m_xScale = 0.1f;
        m_yScale = m_xScale * m_mapWidth / m_mapHeight;

这样得到w = 0.1,h = 0.175

如何确定小地图的UV中X,Y

   public void SetMeInMini()
   {
       float posPlayerX = m_player.position.x / m_mapWidth;
       float posPlayerY = m_player.position.z / m_mapHeight;

       m_imgMap.uvRect = new Rect(posPlayerX - m_xScale / 2, posPlayerY - m_yScale / 2, m_xScale, m_yScale);
       Vector3 oriArrow = m_playerArrow.transform.eulerAngles;
       oriArrow.z = -m_player.eulerAngles.y;
       m_playerArrow.eulerAngles = oriArrow;
   }

假设地图的起点是从0点开始(实际项目时可以加偏移值,x,y,代表3d场景是从偏移点开始,并且增加3d场景的实际宽高)。这里为了简单实现,未加偏移值,与实际宽高
在这里插入图片描述

因为地图从0点开始
x方向,我占地图的百分比 float posPlayerX = m_player.position.x / m_mapWidth;
y方向,我占地图的百分比float posPlayerY = m_player.position.z / m_mapHeight;
m_imgMap.uvRect = new Rect(posPlayerX - m_xScale / 2, posPlayerY - m_yScale / 2, m_xScale, m_yScale);
x方向百分比-m_xScale的一半,即为uv的x
y方向百分比-m_yScale的一半,即为uv的y
这样显示出玩家的位置,一定是在小地图的中间,并加上箭头表示我的方向
在这里插入图片描述

如何确定地图上目标在小地图位置

把目标的世界坐标,转换成小地图的localPosition

    public Vector3 GetTarget2MiniMapPoint(Vector3 targetWorldPos,Vector3 posPlayer)
    {
        //世界坐标上与me的差,
        float x = (targetWorldPos.x - posPlayer.x) * m_meter2Pixel;
        float y = ( targetWorldPos.z  - posPlayer.z) * m_meter2Pixel;
        return new Vector3(x, y,0);
    }

小地图大小为300*300,因为是正方形,所以300像素单位最多代表的米为

    public float m_mapWidth = 1680; //世界中场景的宽度,米
    public float m_xScale = 0; //表示 uv 的取值w, 0-1,例如x = 0.1
    public float m_totalMeter = 0; //小地图,总像素长代表的米  
    m_totalMeter = m_mapWidth * m_xScale; 

那么每个像素单位可代表的米

m_viewWidth = m_imgMap.rectTransform.rect.width;
 m_meter2Pixel = m_viewWidth / m_totalMeter;

用目标的世界坐标x-我的世界坐标x,结果*m_meter2Pixel,则为目标在小地图localPosition
在这里插入图片描述

这里需要注意:
1.小地图的Pivot,min,max为0.5,才能让localPosition等于anchoredPosition,否则只能用anchoredPosition设置目标在小地图位置
在这里插入图片描述

2.目标点localPosition超过小地图的长宽,可以设置该点显示隐藏。或者使用RestMask2d

大地图

点击小地图,可展开大地图

如何确定我大地图的localPosition

在这里插入图片描述

世界坐标单位米与大地图上像素对应

float m_widthPixel = 1680; //大地图顶视图这张图的尺寸,单位像素
    float m_heightPixel = 960;
        public float m_widthScene = 1680; //场景中真实的最大宽,单位米
    public float m_heightScene = 960;
     public float m_scale = 0; //1米可对应多少像素。单位为  像素/米
     m_scale = m_widthPixel / m_widthScene;

在这里插入图片描述
在这里插入图片描述

大地图采用真实像素大小,1680,960。虽然在1280*720的视图中有些边界显示不到,那是项目设计如此,边界不可达到

世界坐标转大地图localPosition

   public Vector2 PosWorld2Local(Vector2 pos)
   {
       Vector2 ret = Vector2.zero;
       float x = pos.x * m_scale;
       x -= m_widthPixel / 2;

       float y = pos.y * m_scale;
       y -= m_heightPixel / 2;

       ret = new Vector2(x, y);
       return ret;
   }

目标位置x * m_scale,可得到像素坐标x,-m_widthPixel / 2,地图bg的像素的一半,可得到在大地图中的localPosition

缩圈机制

1.小圆一定是全部包含在大圆内部。运动的是大圆,直到大圆与小圆圆心,半径重合
2.缩圈运动分两个阶段,第一阶段为向内切运动:大圆圆心不变,按照速度缩小大圆半径,直到大圆半径 = 圆心距离+小圆半径
3.第二阶段为先小圆运动:大圆圆心向着小圆圆心移动,同时大圆半径缩小,直到大圆半径= 小圆半径

第一阶段内切运动

在这里插入图片描述

小圆一开始在大圆内部,如果大圆半径R1> 小圆半径R2+圆心距离,说明还处在第一阶段向内切运动,否则转向第二阶段,向小圆运动

第二阶段向小圆运动

在这里插入图片描述

大圆的圆心P1向小圆圆心P2移动,每帧半径减少
float diffBigR = m_circleData.speed * Time.deltaTime
那么大圆圆心在x,y方向上变化量为
m_circleData.bigPos.x += diffBigR * m_circleData.cos;
m_circleData.bigPos.y += diffBigR * m_circleData.sin;

缩圈数据

public class CircleData
{
    public float bigR; //大圆半径
    public Vector2 bigPos; //世界坐标,大圆圆心

    public float smallR; //小圆半径
    public Vector2 smallPos;//世界坐标,小圆圆心

    public float time = 10; //总共运动时间
    public float speed; //速度
    public float dis; //两圆心初始距离
    public float cos;  //大圆移动x上投影分量
    public float sin; //大圆移动y上投影分量

速度speed即为每帧大圆半径需要减少数值,用半径差/总时间

speed = (bigR - smallR) / time;

dis两圆心初始距离

dis = Vector2.Distance(bigPos, smallPos);

cos,sin的运动分量
内切运动运动结束后,每次大圆圆心,x方向+半径变化值*cos,即为新的大圆圆心x位置
有以下几种状态

if (bigPos.x == smallPos.x && bigPos.y != smallPos.y)
        {
            cos = 0;
            sin = 1;
        }
        else if (bigPos.x != smallPos.x && bigPos.y == smallPos.y)
        {
            cos = 1;
            sin = 0;
        }
        else if (bigPos.x == smallPos.x && bigPos.y == smallPos.y)
        {
            cos = 0;
            sin = 0;
        }
        else 
        {
            float a = smallPos.x - bigPos.x;
            float b = smallPos.y - bigPos.y;
            
            cos = a / dis;
            sin = b /dis;
        }

大圆运动

在update中执行
1.每帧大圆半径的变化量为m_circleData.speed * Time.deltaTime
2.大圆半径的减少为m_circleData.bigR -= diffBigR;
3.当是向内切阶段运动,则只会减少大圆半径
4.当是向小圆阶段运动,减少大圆半径的同时,大圆圆心运动
m_circleData.bigPos.x += diffBigR * m_circleData.cos;
m_circleData.bigPos.y += diffBigR * m_circleData.sin;
5.直到大圆半径《=小圆半径,说明运动结束

float diffBigR = m_circleData.speed * Time.deltaTime;
            m_circleData.bigR -= diffBigR;
            if (m_circleData.bigR > m_circleData.smallR + m_circleData.dis)
            {
                Debug.Log("内切");
                UpdateTransCircle();
            }
            else if (m_circleData.bigR > m_circleData.smallR && m_circleData.bigR <= m_circleData.smallR + m_circleData.dis)
            {
                Debug.Log("小圆");
                m_circleData.bigPos.x += diffBigR * m_circleData.cos;
                m_circleData.bigPos.y += diffBigR * m_circleData.sin;
                UpdateTransCircle();
            }
            else if (m_circleData.bigR <= m_circleData.smallR)
            {
                m_isMove = false;
            }

毒圈在场景中表现
为一个去掉上顶,下底的圆柱体,每次移动改变它的坐标与缩放

    void UpdateTransCircle()
    {
        m_transCircle.position = new Vector3(m_circleData.bigPos.x, 0, m_circleData.bigPos.y);
        m_transCircle.localScale = new Vector3(m_circleData.bigR * 2, 1, m_circleData.bigR * 2);
    }

在这里插入图片描述

UGUI上毒圈挖孔

效果

小地图上显示
在这里插入图片描述

大地图上显示
在这里插入图片描述

其中白圈为小圆,即最终安全区
外围大圈会大圆不断缩小移动

小地图Mask

使用跟小地图同样像素大小的RawImage。
在这里插入图片描述

大地图Mask

在这里插入图片描述

可以使用不同像素大小,例如屏幕分辨率为1280720,大地图bg实际为1680960。但是他们的中心点是一致的,这样传递的localPosition在大地图上,和传递到Mask上是对等的
在这里插入图片描述

shader处理

v2f vert(appdata_t v)
				{
					v2f o;
					o.vertex = UnityObjectToClipPos(v.vertex);
					o.worldPosition = mul(unity_ObjectToWorld, v.vertex);
					o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
					o.color = v.color;
					return o;
				}

			
				fixed4 frag(v2f i) : SV_Target
				{
					//显示白圈
					float2 diffCurWithSmallllCenter = float2(i.worldPosition.x, i.worldPosition.y) - float2(_CenterSmall.x, _CenterSmall.y);
					float disCurWithSmallCenter = sqrt(diffCurWithSmallllCenter.x * diffCurWithSmallllCenter.x + diffCurWithSmallllCenter.y * diffCurWithSmallllCenter.y);
					if (abs(disCurWithSmallCenter - _SmallR) < _SmallWidth )
					{
						return float4(1, 1, 1, 1);
					}


				   float2 center = float2(i.worldPosition.x, i.worldPosition.y) - float2(_Center.x, _Center.y);
				   float disSquare = (center.x * center.x + center.y * center.y);

				   //比较距离大小,可以不用开平方
				   clip(disSquare - _BigRSquare);

				   fixed4 col = tex2D(_MainTex, i.texcoord) * i.color;
				   return col;
				}

显示小圆白圈

当前世界坐标与小圆圆心的坐标相差

float2 diffCurWithSmallllCenter = float2(i.worldPosition.x, i.worldPosition.y) - float2(_CenterSmall.x, _CenterSmall.y);

当前世界坐标与小圆圆心距离disCurWithSmallCenter,坐标开平方

float disCurWithSmallCenter = sqrt(diffCurWithSmallllCenter.x * diffCurWithSmallllCenter.x + diffCurWithSmallllCenter.y * diffCurWithSmallllCenter.y);

disCurWithSmallCenter - 小圆半径的绝对值,< _SmallWidth,直接返回白色

					if (abs(disCurWithSmallCenter - _SmallR) < _SmallWidth )
					{
						return float4(1, 1, 1, 1);
					}

因为小圆是要距离差的绝对值与圈宽_SmallWidth比较,所以不能使用平方比较

大圆挖孔

当前世界坐标与大圆圆心坐标差

float2 center = float2(i.worldPosition.x, i.worldPosition.y) - float2(_Center.x, _Center.y);

当前世界坐标与大圆圆心的距离的平方

 float disSquare = (center.x * center.x + center.y * center.y);

距离的平方<大圆半径的平方,则舍弃

//比较距离大小,可以不用开平方
clip(disSquare - _BigRSquare);

因为只要比较距离的平方的大小,这样省去一步开平方计算,提高点性能

C#中传递参数给Shader

    public void SetClip(Vector2 vec, float radius, Vector2 smallPos,float smallR)
    {
        Vector3 centerWrold = transform.TransformPoint(new Vector3(vec.x,vec.y,0));
        m_mat.SetVector("_Center", centerWrold);
        Vector3 pointInCircle = new Vector3(vec.x + radius, vec.y,0);
        Vector3 pointInCircleWorld = transform.TransformPoint(pointInCircle);
        Vector2 diffVecWorld = centerWrold - pointInCircleWorld;
        float bigRSquare = diffVecWorld.x * diffVecWorld.x + diffVecWorld.y * diffVecWorld.y;
        m_mat.SetFloat("_BigRSquare", bigRSquare);

        Vector3 smallPosWorld = transform.TransformPoint(new Vector3(smallPos.x, smallPos.y, 0));
        m_mat.SetVector("_CenterSmall", smallPosWorld);
        Vector3 pointInCircleSmall = new Vector3(smallPos.x + smallR, smallPos.y, 0);
        Vector3 pointInCircleWorldSamll = transform.TransformPoint(pointInCircleSmall);
        Vector2 diffVecWorldSmall = smallPosWorld - pointInCircleWorldSamll;
        float smallRSquare = diffVecWorldSmall.x * diffVecWorldSmall.x + diffVecWorldSmall.y * diffVecWorldSmall.y;
        float smallRWorld = Vector2.Distance(smallPosWorld, pointInCircleWorldSamll);
        m_mat.SetFloat("_SmallR", smallRWorld);
    }

传递圆心坐标值

不管大地图,小地图,传递的坐标为基于地图的localPosition,所以都要用Mask的transform转为世界坐标。mask即是挖孔

Vector3 centerWrold = transform.TransformPoint(new Vector3(vec.x,vec.y,0));

传递半径值

大圆

传入参数radius是指UI上的像素值,先用圆心x+半径,得到半径上一点,在UI上localPosition

Vector3 pointInCircle = new Vector3(vec.x + radius, vec.y,0);

再转为mask世界坐标上一点

Vector3 pointInCircleWorld = transform.TransformPoint(pointInCircle);

大圆只需要传递世界坐标下半径的平方,在shader做为世界坐标差平方,与大圆半径平方比较,进行clip。shader中减少一次开根运算

Vector2 diffVecWorld = centerWrold - pointInCircleWorld;
        float bigRSquare = diffVecWorld.x * diffVecWorld.x + diffVecWorld.y * diffVecWorld.y;
        m_mat.SetFloat("_BigRSquare", bigRSquare);
小圆

小圆因为要画一个白圈,具有一定的宽度,所以只能传递小圆半径
传入半径smallR,是UI上像素长
先用圆心x+半径,得到半径上一点,在UI上localPosition

Vector3 pointInCircleSmall = new Vector3(smallPos.x + smallR, smallPos.y, 0);

再转基于mask的世界坐标

Vector3 pointInCircleWorldSamll = transform.TransformPoint(pointInCircleSmall);

算出半径上一点,与圆心的世界坐标上差距,即为小圆世界坐标下半径长,传入shader

Vector3 pointInCircleWorldSamll = transform.TransformPoint(pointInCircleSmall);
        float smallRWorld = Vector2.Distance(smallPosWorld, pointInCircleWorldSamll);
        m_mat.SetFloat("_SmallR", smallRWorld);

小地图传递值

大圆的世界坐标圆心,转小地图的localPosition

Vector3 bigV3 = new Vector3(CircleMgr.instance.m_circleData.bigPos.x, 0, CircleMgr.instance.m_circleData.bigPos.y);
            Vector3 bigPos = GetTarget2MiniMapPoint(bigV3, m_player.position);

大圆的半径转成小地图的像素值

float bigR = CircleMgr.instance.m_circleData.bigR * m_meter2Pixel;

小圆同理,传递进入Mask

            Vector3 bigV3 = new Vector3(CircleMgr.instance.m_circleData.bigPos.x, 0, CircleMgr.instance.m_circleData.bigPos.y);
            Vector3 bigPos = GetTarget2MiniMapPoint(bigV3, m_player.position);
            float bigR = CircleMgr.instance.m_circleData.bigR * m_meter2Pixel;

            Vector3 smallV3 = new Vector3(CircleMgr.instance.m_circleData.smallPos.x, 0, CircleMgr.instance.m_circleData.smallPos.y);
            Vector3 smallPos = GetTarget2MiniMapPoint(smallV3, m_player.position);
            float smallR = CircleMgr.instance.m_circleData.smallR * m_meter2Pixel;

            m_circleClip.SetClip(bigPos, bigR, smallPos, smallR);

大地图传递值

大圆圆心世界坐标转地图上localPosition

Vector2 bigPos = PosWorld2Local(CircleMgr.instance.m_circleData.bigPos);

大圆半径世界坐标(米为单位)转地图上像素单位

float bigR = CircleMgr.instance.m_circleData.bigR* m_scale;

小圆同理,传递进入Mask

            Vector2 bigPos = PosWorld2Local(CircleMgr.instance.m_circleData.bigPos);
            Vector2 smallPos = PosWorld2Local(CircleMgr.instance.m_circleData.smallPos);
            float bigR = CircleMgr.instance.m_circleData.bigR* m_scale;
            float smallR = CircleMgr.instance.m_circleData.smallR* m_scale;

            m_circleClip.SetClip(bigPos,bigR, smallPos, smallR);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四夕立羽

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值