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);
浙公網安備 33010602011771號