日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

Unity如何制作道具tips隨著屏幕邊界自適應(yīng)變換位置,確保不超出屏幕的功能

作者:xiaofengxuan892 更新時(shí)間: 2022-01-08 編程語言

在開發(fā)中有時(shí)候會(huì)碰到需要展示tips說明信息的功能,比如打開道具列表,點(diǎn)擊相應(yīng)的道具并展示該道具的屬性等詳細(xì)信息,這時(shí)就有可能會(huì)出現(xiàn)tips的UI超出屏幕邊界的問題。

?

因此這里做一個(gè)自適應(yīng)的道具tips

首先需要設(shè)計(jì)tips的UI。

因?yàn)樾枰猆I隨著文本的長度自適應(yīng),確保始終能夠顯示全部的文本信息,因此Text的RectTransform也需要隨著文本高度自動(dòng)增加height,這里就會(huì)用到preferredHeight。

同時(shí)在Text自適應(yīng)后,其外部的背景bg也需要根據(jù)Text來自動(dòng)改變。

為了滿足以上的兩個(gè)需求,這里設(shè)計(jì)Tips的prefab為如下結(jié)構(gòu):

?

?

從以上結(jié)構(gòu)可知,Tips相對(duì)于TipsBg是自適應(yīng)的,并且保持上下左右完全對(duì)稱。注意這里設(shè)置TipsBg的pivot為“0,1”,默認(rèn)以鼠標(biāo)點(diǎn)擊位置為起點(diǎn)來顯示tips

??

這樣的結(jié)構(gòu)就可以實(shí)現(xiàn)一個(gè)效果:當(dāng)改變TipsBg的RectTransform的寬高時(shí),由于Tips的sizeDelta會(huì)始終保持如上的數(shù)值不變,因此Tips的寬高也會(huì)自動(dòng)的改變,。

所以只需要在運(yùn)行時(shí)改變TipsBg的RectTransform的寬高即可。

那么如何設(shè)置TipsBg的寬高呢?

當(dāng)為Text設(shè)置文本內(nèi)容后,text.preferredHeight指的是針對(duì)該內(nèi)容,組件Text希望RectTransform能夠提供的height。如果rect.height == text.preferredHeight,則該文本可以完全顯示出來

這樣就可以計(jì)算出TipsBg期望的高度:

float yHeight = tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y);   //獲取tipsTxt的理想height,并計(jì)算tipsBg的目標(biāo)height
tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, yHeight);

默認(rèn)情況下不需要改變TipsBg的寬度,所以寬度暫時(shí)不需要重新設(shè)置

注意:在設(shè)置RectTransform的寬高時(shí)使用UGUI自帶的方法:SetSizeWithCurrentAnchors。該方法會(huì)以UI當(dāng)前的pivot為起點(diǎn)來設(shè)置寬高,并且在設(shè)置完成后會(huì)自動(dòng)根據(jù)當(dāng)前的anchors分布來調(diào)整RectTransform面板中的顯示數(shù)據(jù)。

而且在獲取UI對(duì)象的position時(shí)也是該UI的pivot在世界空間下的坐標(biāo),而不是該UI四個(gè)anchor交叉點(diǎn)的坐標(biāo)。

基于pivot這樣的特性,在設(shè)置TipsBg的position時(shí),就可以在改變pivot的基礎(chǔ)上,直接將鼠標(biāo)點(diǎn)擊位置賦值給TipsBg,而不需要另外計(jì)算TipsBg超出邊界的數(shù)值,從而計(jì)算TipsBg的目標(biāo)position。

在設(shè)置完TipsBg的寬高后就有可能會(huì)出現(xiàn)tips邊界超出屏幕的問題,

那么如何檢測該tips是否超出了屏幕邊界并調(diào)整位置呢?

基于pivot的特性,直接把鼠標(biāo)位置賦值給TipsBg.position,因此就可以很容易計(jì)算出UI是否超出了屏幕邊界:

//通過設(shè)置TipsBg的pivot,而不是position,來避免超出屏幕邊界
if (Input.mousePosition.x + tipsBgRect.rect.width <= Screen.width)  //默認(rèn)向右顯示
	tipsBgPivot.x = 0;           //當(dāng)該UI在X軸上的最大值依然在屏幕以內(nèi)時(shí)
else
	tipsBgPivot.x = 1;

if (Input.mousePosition.y - tipsBgRect.rect.height >= 0) //默認(rèn)向下顯示
	tipsBgPivot.y = 1;
else
	tipsBgPivot.y = 0;

運(yùn)行時(shí)效果如下:紅點(diǎn)處為鼠標(biāo)點(diǎn)擊位置

通過改變TipsBg的pivot來調(diào)整UI位置,使其不超出屏幕邊界

雖然以上方法可以避免一部分超出邊界的情況,但如果文本過長,屏幕過小,調(diào)整之后依然會(huì)超出邊界,該如何呢?

首先需要獲取UI對(duì)象四個(gè)角的坐標(biāo)值:rectTransform.GetWorldCorners。該方法獲取到的坐標(biāo)點(diǎn)是以左下角為起點(diǎn),順時(shí)針方向依次輸出。通過檢測各個(gè)點(diǎn)的坐標(biāo)即可知道是否超出了邊界。

當(dāng)依然超出邊界時(shí),有兩種方式:一是改變TipsBg的寬度值,使得Tips的text.preferredHeight降低,從而避免;如果寬度已無法再調(diào)整,此時(shí)就只能改變Text的字體大小來改變text.preferredHeight

//雖然設(shè)置pivot后可以避免超出邊界的情況,但如果文本過長,依然有可能會(huì)超出邊界
//但這里只考慮垂直方向上依然超出邊界的情況,水平方向則不考慮——因?yàn)樗椒较虻膶挾瓤梢宰灾髟O(shè)定
//解決辦法:當(dāng)Y軸方向超出邊界時(shí),改變TipsBg的寬度,從而降低tipsTxt.preferredHeight
//獲取tipsBgRect的四角的坐標(biāo)
tipsBgRect.GetWorldCorners(worldCornersPos);
//從屏幕左下角開始順時(shí)針查看各個(gè)corner的Y軸坐標(biāo)值
if (worldCornersPos[0].y < 0 || worldCornersPos[1].y > Screen.height)   //說明超出邊界
{
	//Debug.Log("<color=yellow>   " + width + "  " + fontSize + "   " + allTips[index] + "   </color>");

	if(width < Screen.width * 0.5f)    //當(dāng)寬度尚且可以繼續(xù)調(diào)節(jié)時(shí)
	{
		tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Min(width + 100, Screen.width * 0.5f));  //寬度最大不超過屏幕的一半
		//當(dāng)tipsTxtRect的寬度改變后,其tipsTxt.preferredHeight也會(huì)即時(shí)改變——這一點(diǎn)很方便,很重要
		tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y));

		//繼續(xù)下一次檢測
		AutoAdjustTipsBounds(Mathf.Min(width + 100, Screen.width * 0.5f), fontSize);
	}
	else                            //寬度已無法再調(diào)節(jié),此時(shí)需要改變字體大小
	{
		tipsTxt.fontSize = fontSize - 1;
		tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Screen.width * 0.5f);
		tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y));
		
		//繼續(xù)下一次檢測
		AutoAdjustTipsBounds(Screen.width * 0.5f, fontSize - 1);
	}
}

通過以上遞歸方法的檢測,就可以一直調(diào)整到完全顯示在屏幕內(nèi)部的效果

完整代碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using System.Text;

public class TipsAdjust : MonoBehaviour
{
    Text tipsTxt;
    RectTransform tipsTxtRect, tipsBgRect;
    string[] allTips;    //所有的tips信息

    int index = 0;  //對(duì)應(yīng)的tip索引
    GameObject go;  //為方便顯示點(diǎn)擊的位置,這里用紅點(diǎn)代替

    float oldWidth = 0; //TipsBg的RectTransform原本的寬度
    int oldFontSize = 0; //text組件的字體大小——可能用于自適應(yīng)調(diào)整
    Vector2 tipsBgPivot = Vector2.zero;
    Vector3[] worldCornersPos = new Vector3[4];

    void Start()
    {
        //獲取組件對(duì)象
        GameObject tipsObj = GameObject.Find("Tips");
        tipsTxtRect = tipsObj.GetComponent<RectTransform>();
        tipsTxt = tipsObj.GetComponent<Text>();
        tipsBgRect = this.GetComponent<RectTransform>();

        //初始化文本
        string content = File.ReadAllText(Application.dataPath + "/Resources/Poems.txt");
        allTips = content.Split(new string[] { "##" }, System.StringSplitOptions.None);
        //注意:使用string.Split分割string時(shí)如果使用string類型的參數(shù),需要用上述的模式

        //為方便顯示鼠標(biāo)點(diǎn)擊位置,這里使用紅點(diǎn)代替
        go = GameObject.Instantiate(Resources.Load("Prefabs/RedPoint")) as GameObject;
        go.transform.parent = this.transform.parent;   //如果不設(shè)置parent,則新實(shí)例化的obj會(huì)脫離canvas層級(jí)而存在,因此不會(huì)顯示在畫面上
        go.SetActive(false);  //暫時(shí)不顯示

        oldWidth = tipsBgRect.rect.width;  //存儲(chǔ)原始寬度值
        oldFontSize = tipsTxt.fontSize;  //字體原始大小
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
            OnItemBtnClick();
    }

    void OnItemBtnClick()
    {
        //為tips賦值
        ++index;
        if (index >= allTips.Length) index = 0;
        tipsTxt.text = allTips[index];

        //設(shè)置Tips的寬高
        //由于Tips相對(duì)于TipsBg是自適應(yīng)的,因此這里只需要計(jì)算TipsBg的寬高,Tips就會(huì)自適應(yīng)達(dá)到效果
        tipsTxt.fontSize = oldFontSize;    //針對(duì)自適應(yīng)有可能會(huì)改變字體大小,所以這里重新設(shè)置
        tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, oldWidth);  //自適應(yīng)有可能會(huì)改變原有寬度,所以這里重新設(shè)置
        float yHeight = tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y);   //獲取tipsTxt的理想height,并計(jì)算tipsBg的目標(biāo)height
        tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, yHeight);
        //注意:
        //由于TipsBg和Tips之間UI的設(shè)計(jì),因此tips的rect是肯定小于TipsBg的,
        //所以可以明確知道tipsTxtRect.sizeDelta.y < 0。這里為了計(jì)算方便,直接使用Maths.Abs

        //為了讓效果更明顯,這里直接展示鼠標(biāo)點(diǎn)擊位置
        go.transform.position = Input.mousePosition;   
        if (!go.activeInHierarchy) go.SetActive(true);
        AutoAdjustTipsBounds(oldWidth, oldFontSize);
    }

    //檢測是否超出屏幕邊界,并自適應(yīng)調(diào)整位置
    void AutoAdjustTipsBounds(float width, int fontSize)
    {
        //通過設(shè)置TipsBg的pivot,而不是position,來避免超出屏幕邊界
        if (Input.mousePosition.x + tipsBgRect.rect.width <= Screen.width)  //默認(rèn)向右顯示
            tipsBgPivot.x = 0;           //當(dāng)該UI在X軸上的最大值依然在屏幕以內(nèi)時(shí)
        else
            tipsBgPivot.x = 1;

        if (Input.mousePosition.y - tipsBgRect.rect.height >= 0) //默認(rèn)向下顯示
            tipsBgPivot.y = 1;
        else
            tipsBgPivot.y = 0;

        //設(shè)置TipsBg的pivot以及position,由于TipsBg和Tips之間自適應(yīng)的關(guān)系,Tips也會(huì)自動(dòng)實(shí)現(xiàn)效果
        tipsBgRect.position = Input.mousePosition;
        tipsBgRect.pivot = tipsBgPivot;

        //雖然設(shè)置pivot后可以避免超出邊界的情況,但如果文本過長,依然有可能會(huì)超出邊界
        //但這里只考慮垂直方向上依然超出邊界的情況,水平方向則不考慮——因?yàn)樗椒较虻膶挾瓤梢宰灾髟O(shè)定
        //解決辦法:當(dāng)Y軸方向超出邊界時(shí),改變TipsBg的寬度,從而降低tipsTxt.preferredHeight
        //獲取tipsBgRect的四角的坐標(biāo)
        tipsBgRect.GetWorldCorners(worldCornersPos);
        //從屏幕左下角開始順時(shí)針查看各個(gè)corner的Y軸坐標(biāo)值
        if (worldCornersPos[0].y < 0 || worldCornersPos[1].y > Screen.height)   //說明超出邊界
        {
            //Debug.Log("<color=yellow>   " + width + "  " + fontSize + "   " + allTips[index] + "   </color>");

            if(width < Screen.width * 0.5f)    //當(dāng)寬度尚且可以繼續(xù)調(diào)節(jié)時(shí)
            {
                tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Min(width + 100, Screen.width * 0.5f));  //寬度最大不超過屏幕的一半
                //當(dāng)tipsTxtRect的寬度改變后,其tipsTxt.preferredHeight也會(huì)即時(shí)改變——這一點(diǎn)很方便,很重要
                tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y));

                //繼續(xù)下一次檢測
                AutoAdjustTipsBounds(Mathf.Min(width + 100, Screen.width * 0.5f), fontSize);
            }
            else                            //寬度已無法再調(diào)節(jié),此時(shí)需要改變字體大小
            {
                tipsTxt.fontSize = fontSize - 1;
                tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Screen.width * 0.5f);
                tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y));
                
                //繼續(xù)下一次檢測
                AutoAdjustTipsBounds(Screen.width * 0.5f, fontSize - 1);
            }
        }
    }

}

?以上就實(shí)現(xiàn)了道具tips不超出邊界的需求。運(yùn)行效果如下:?

? ? ? ? ? ? ? ? ? ? ? ??

完整項(xiàng)目鏈接如下:AutoAdjustTipsProject.zip-Unity3D文檔類資源-CSDN下載

PS:

1.UGUI中rectTransform.sizeDelta 和 rectTransform.rect.size的區(qū)別:

“rect.size”代表的是該UI對(duì)象的真實(shí)寬高,不論瞄點(diǎn)如何設(shè)置;

“sizeDelta”表示UI對(duì)象的寬高比對(duì)應(yīng)的anchor矩形大或者小多少

驗(yàn)證:

如圖,Test01的四個(gè)錨點(diǎn)都在中心處,Test02的四個(gè)錨點(diǎn)則分散在四個(gè)角落:

?此時(shí)使用如下代碼分別輸出兩個(gè)對(duì)象的rect.size 和 sizeDelta:

GameObject test01 = GameObject.Find("Test01");
GameObject test02 = GameObject.Find("Test02");
RectTransform rect01 = test01.GetComponent<RectTransform>();
RectTransform rect02 = test02.GetComponent<RectTransform>();
Debug.Log("<color=green>  rect01:  " + rect01.sizeDelta + "  " + rect01.rect.size + "  </color>");
Debug.Log("<color=green>  rect02:   " + rect02.sizeDelta + "  " + rect02.rect.size + "  </color>");

?運(yùn)行結(jié)果如下:

?從以上結(jié)果可知:

1.當(dāng)UI對(duì)象的四個(gè)anchor匯聚在一處時(shí)(不論是否匯聚在中心,只要四個(gè)錨點(diǎn)不分散即可),此時(shí)sizeDelta與rect.size相同

2.當(dāng)在代碼中手動(dòng)設(shè)定UI對(duì)象的sizeDelta時(shí):

rect02.sizeDelta = new Vector2(-50, -50);
Debug.Log("<color=green>  rect02:  @@@@@@@@@@  " + rect02.sizeDelta + "  " + rect02.rect.size + "  </color>");

運(yùn)行結(jié)果如下:

而此時(shí)Test02對(duì)象的RectTransform:

Game視圖中的結(jié)果為:

總結(jié):

1.當(dāng)需要獲取UI對(duì)象的真實(shí)寬高時(shí)使用“rect.size”,而不要使用“sizeDelta”

2.如果要設(shè)置UI對(duì)象的寬高,則使用rect.SetSizeWithCurrentAnchors,不要直接的設(shè)置rect.size,該變量不具備set屬性

rect02.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);   //設(shè)置寬度
rect02.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 300);     //設(shè)置高度

Game視圖效果以及Test02的RectTransform變化如下:

??

注意:

1.當(dāng)使用SetSizeWithCurrentAnchors設(shè)置UI對(duì)象的寬高時(shí),是以當(dāng)前UI對(duì)象的pivot為中心點(diǎn)來設(shè)置的,例如以上Test02的pivot為“0.5,0.5”,由于屏幕空間以左下角為原點(diǎn),X向右遞增,Y向上遞增,所以“0.5,0.5”代表的是矩形屏幕的正中心點(diǎn)。在此基礎(chǔ)上拉伸UI對(duì)象的寬和高

倘若設(shè)置Test02的pivot為“0,0.5”,則代表以屏幕左側(cè)邊緣的正中為基準(zhǔn)點(diǎn)來拉伸UI對(duì)象的寬高

?

pivot 和 anchor是兩個(gè)不同的屬性,這里并沒有用到anchor來影響UI對(duì)象的寬高顯示

2.設(shè)置了UI對(duì)象的寬高后,由于SetSizeWithCurrentAnchors還需要考慮該UI對(duì)象的anchor,在設(shè)置size后,UGUI會(huì)自動(dòng)根據(jù)UI對(duì)象的size和anchor來設(shè)置其rectTransform相應(yīng)的數(shù)值

3.UGUI中pivot 和 anchor是完全不同的兩個(gè)屬性,兩者的作用也不相同,由上述測試知曉

4.sizeDelta實(shí)際代表的意義:

當(dāng)四個(gè)錨點(diǎn)分散開時(shí):

sizeDelta.x = rect.x - anchorRectangle.x;
sizeDelta.y = rect.y - anchorRectangle.y;

所以當(dāng)sizeDelta.x <?0 時(shí)代表rect的寬度小于原有的矩形寬度,而在UGUI的RectTransform視圖中,卻是站在該UI對(duì)象的角度,超出anchor矩形的使用負(fù)數(shù),在anchor以內(nèi)的則用正數(shù)表示。

如此就會(huì)出現(xiàn)當(dāng)sizeDelta.x < 0,而RectTransform中數(shù)值為正數(shù)的情況

? ?

PS: "Left","Right"的數(shù)值不一定是對(duì)半分,這里只是因?yàn)樵O(shè)置的tipsBg為上下左右對(duì)稱的設(shè)計(jì)才如此;但sizeDelta.x代表的一定是UI實(shí)際寬度與anchor矩形寬度的總差值

5.RectTransform.rect.width 和 rect.size.x是等效的,都是獲取rect的寬度,但RectTransform.rect.x是獲取rect的X坐標(biāo),這個(gè)不能和rect.size.x弄混

2.如何獲取UI對(duì)象四角的坐標(biāo)值:

在獲取UI對(duì)象四角的坐標(biāo)值時(shí)有兩種方式:一種是Unity自帶的方法rectTransform.GetWorldCorners,另一種則需要根據(jù)該UI對(duì)象的pivot和rect.size來分別計(jì)算——這種比較麻煩,因此不同的pivot計(jì)算方式不同

//方式一:
Vector3[] worldCornersPos = new Vector3[4];
tipsImageRect.GetWorldCorners(worldCornersPos);
foreach(var temp in worldCornersPos)
{
	Debug.Log("<color=blue> " + temp + "   </color>");
}

//方式二:根據(jù)不同的pivot用不同的方式計(jì)算四角的坐標(biāo)值
//情況一:當(dāng)pivot為“0, 0.5”時(shí)
GameObject test03 = GameObject.Find("Test03");
RectTransform test03Rect = test03.GetComponent<RectTransform>();
test03Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);
test03Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 300);
Vector3[] test03WorldCornersPos = new Vector3[4];
test03Rect.GetWorldCorners(test03WorldCornersPos);
foreach (var temp2 in test03WorldCornersPos)
{
	Debug.Log("<color=yellow>  " + temp2 + "  " + test03.transform.position + "   </color>");
}
//這里為了方便比較兩種方式的差異,所以先使用方式一輸出結(jié)果
//從結(jié)果可以看出,如果使用方式二主動(dòng)計(jì)算,需要根據(jù)pivot的設(shè)置變換不同的計(jì)算過程,這樣其實(shí)沒有必要。所以這里推薦使用Unity自帶的方法來獲取corner數(shù)值

以上測試中“Test03”的pivot為“0,0.5”,運(yùn)行結(jié)果如下:

??

?

?總結(jié):

1.從以上數(shù)值的輸出來看,推薦使用UGUI自帶的"GetWorldCorners"來獲取UI對(duì)象四角的坐標(biāo)值。并且從數(shù)值的輸出順序也可以看出,以屏幕空間左下角為起點(diǎn),順時(shí)針方向輸出四角的值

?2.從以上輸出結(jié)果可以看出,在獲取UI對(duì)象坐標(biāo)時(shí)得到的是以pivot為中心點(diǎn)顯示的坐標(biāo)(0, 249, 0),而并不是UI對(duì)象自身四角交叉線的重合點(diǎn)坐標(biāo),由此可見pivot的重要性,在設(shè)置UI對(duì)象坐標(biāo)和寬高時(shí)都會(huì)用到

原文鏈接:https://blog.csdn.net/m0_47975736/article/details/122089583

欄目分類
最近更新