unity3d:小地图UV,UGUIshader毒圈挖孔,吃鸡跑毒缩圈
运行效果
场景中缩圈
小地图中挖孔
大地图中挖孔
小地图
方案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);
posted on 2023-06-18 17:36 luoyikun 阅读(85) 评论(0) 编辑 收藏 举报 来源
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!