IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity如何制作道具tips随着屏幕边界自适应变换位置,确保不超出屏幕的功能 -> 正文阅读

[游戏开发]Unity如何制作道具tips随着屏幕边界自适应变换位置,确保不超出屏幕的功能

在开发中有时候会碰到需要展示tips说明信息的功能,比如打开道具列表,点击相应的道具并展示该道具的属性等详细信息,这时就有可能会出现tips的UI超出屏幕边界的问题。

?

因此这里做一个自适应的道具tips

首先需要设计tips的UI。

因为需要UI随着文本的长度自适应,确保始终能够显示全部的文本信息,因此Text的RectTransform也需要随着文本高度自动增加height,这里就会用到preferredHeight。

同时在Text自适应后,其外部的背景bg也需要根据Text来自动改变。

为了满足以上的两个需求,这里设计Tips的prefab为如下结构:

?

?

从以上结构可知,Tips相对于TipsBg是自适应的,并且保持上下左右完全对称。注意这里设置TipsBg的pivot为“0,1”,默认以鼠标点击位置为起点来显示tips

??

这样的结构就可以实现一个效果:当改变TipsBg的RectTransform的宽高时,由于Tips的sizeDelta会始终保持如上的数值不变,因此Tips的宽高也会自动的改变,。

所以只需要在运行时改变TipsBg的RectTransform的宽高即可。

那么如何设置TipsBg的宽高呢?

当为Text设置文本内容后,text.preferredHeight指的是针对该内容,组件Text希望RectTransform能够提供的height。如果rect.height == text.preferredHeight,则该文本可以完全显示出来

这样就可以计算出TipsBg期望的高度:

float yHeight = tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y);   //获取tipsTxt的理想height,并计算tipsBg的目标height
tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, yHeight);

默认情况下不需要改变TipsBg的宽度,所以宽度暂时不需要重新设置

注意:在设置RectTransform的宽高时使用UGUI自带的方法:SetSizeWithCurrentAnchors。该方法会以UI当前的pivot为起点来设置宽高,并且在设置完成后会自动根据当前的anchors分布来调整RectTransform面板中的显示数据。

而且在获取UI对象的position时也是该UI的pivot在世界空间下的坐标,而不是该UI四个anchor交叉点的坐标。

基于pivot这样的特性,在设置TipsBg的position时,就可以在改变pivot的基础上,直接将鼠标点击位置赋值给TipsBg,而不需要另外计算TipsBg超出边界的数值,从而计算TipsBg的目标position。

在设置完TipsBg的宽高后就有可能会出现tips边界超出屏幕的问题,

那么如何检测该tips是否超出了屏幕边界并调整位置呢?

基于pivot的特性,直接把鼠标位置赋值给TipsBg.position,因此就可以很容易计算出UI是否超出了屏幕边界:

//通过设置TipsBg的pivot,而不是position,来避免超出屏幕边界
if (Input.mousePosition.x + tipsBgRect.rect.width <= Screen.width)  //默认向右显示
	tipsBgPivot.x = 0;           //当该UI在X轴上的最大值依然在屏幕以内时
else
	tipsBgPivot.x = 1;

if (Input.mousePosition.y - tipsBgRect.rect.height >= 0) //默认向下显示
	tipsBgPivot.y = 1;
else
	tipsBgPivot.y = 0;

运行时效果如下:红点处为鼠标点击位置

通过改变TipsBg的pivot来调整UI位置,使其不超出屏幕边界

虽然以上方法可以避免一部分超出边界的情况,但如果文本过长,屏幕过小,调整之后依然会超出边界,该如何呢?

首先需要获取UI对象四个角的坐标值:rectTransform.GetWorldCorners。该方法获取到的坐标点是以左下角为起点,顺时针方向依次输出。通过检测各个点的坐标即可知道是否超出了边界。

当依然超出边界时,有两种方式:一是改变TipsBg的宽度值,使得Tips的text.preferredHeight降低,从而避免;如果宽度已无法再调整,此时就只能改变Text的字体大小来改变text.preferredHeight

//虽然设置pivot后可以避免超出边界的情况,但如果文本过长,依然有可能会超出边界
//但这里只考虑垂直方向上依然超出边界的情况,水平方向则不考虑——因为水平方向的宽度可以自主设定
//解决办法:当Y轴方向超出边界时,改变TipsBg的宽度,从而降低tipsTxt.preferredHeight
//获取tipsBgRect的四角的坐标
tipsBgRect.GetWorldCorners(worldCornersPos);
//从屏幕左下角开始顺时针查看各个corner的Y轴坐标值
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)    //当宽度尚且可以继续调节时
	{
		tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Min(width + 100, Screen.width * 0.5f));  //宽度最大不超过屏幕的一半
		//当tipsTxtRect的宽度改变后,其tipsTxt.preferredHeight也会即时改变——这一点很方便,很重要
		tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y));

		//继续下一次检测
		AutoAdjustTipsBounds(Mathf.Min(width + 100, Screen.width * 0.5f), fontSize);
	}
	else                            //宽度已无法再调节,此时需要改变字体大小
	{
		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));
		
		//继续下一次检测
		AutoAdjustTipsBounds(Screen.width * 0.5f, fontSize - 1);
	}
}

通过以上递归方法的检测,就可以一直调整到完全显示在屏幕内部的效果

完整代码如下:

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;  //对应的tip索引
    GameObject go;  //为方便显示点击的位置,这里用红点代替

    float oldWidth = 0; //TipsBg的RectTransform原本的宽度
    int oldFontSize = 0; //text组件的字体大小——可能用于自适应调整
    Vector2 tipsBgPivot = Vector2.zero;
    Vector3[] worldCornersPos = new Vector3[4];

    void Start()
    {
        //获取组件对象
        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时如果使用string类型的参数,需要用上述的模式

        //为方便显示鼠标点击位置,这里使用红点代替
        go = GameObject.Instantiate(Resources.Load("Prefabs/RedPoint")) as GameObject;
        go.transform.parent = this.transform.parent;   //如果不设置parent,则新实例化的obj会脱离canvas层级而存在,因此不会显示在画面上
        go.SetActive(false);  //暂时不显示

        oldWidth = tipsBgRect.rect.width;  //存储原始宽度值
        oldFontSize = tipsTxt.fontSize;  //字体原始大小
    }

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

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

        //设置Tips的宽高
        //由于Tips相对于TipsBg是自适应的,因此这里只需要计算TipsBg的宽高,Tips就会自适应达到效果
        tipsTxt.fontSize = oldFontSize;    //针对自适应有可能会改变字体大小,所以这里重新设置
        tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, oldWidth);  //自适应有可能会改变原有宽度,所以这里重新设置
        float yHeight = tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y);   //获取tipsTxt的理想height,并计算tipsBg的目标height
        tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, yHeight);
        //注意:
        //由于TipsBg和Tips之间UI的设计,因此tips的rect是肯定小于TipsBg的,
        //所以可以明确知道tipsTxtRect.sizeDelta.y < 0。这里为了计算方便,直接使用Maths.Abs

        //为了让效果更明显,这里直接展示鼠标点击位置
        go.transform.position = Input.mousePosition;   
        if (!go.activeInHierarchy) go.SetActive(true);
        AutoAdjustTipsBounds(oldWidth, oldFontSize);
    }

    //检测是否超出屏幕边界,并自适应调整位置
    void AutoAdjustTipsBounds(float width, int fontSize)
    {
        //通过设置TipsBg的pivot,而不是position,来避免超出屏幕边界
        if (Input.mousePosition.x + tipsBgRect.rect.width <= Screen.width)  //默认向右显示
            tipsBgPivot.x = 0;           //当该UI在X轴上的最大值依然在屏幕以内时
        else
            tipsBgPivot.x = 1;

        if (Input.mousePosition.y - tipsBgRect.rect.height >= 0) //默认向下显示
            tipsBgPivot.y = 1;
        else
            tipsBgPivot.y = 0;

        //设置TipsBg的pivot以及position,由于TipsBg和Tips之间自适应的关系,Tips也会自动实现效果
        tipsBgRect.position = Input.mousePosition;
        tipsBgRect.pivot = tipsBgPivot;

        //虽然设置pivot后可以避免超出边界的情况,但如果文本过长,依然有可能会超出边界
        //但这里只考虑垂直方向上依然超出边界的情况,水平方向则不考虑——因为水平方向的宽度可以自主设定
        //解决办法:当Y轴方向超出边界时,改变TipsBg的宽度,从而降低tipsTxt.preferredHeight
        //获取tipsBgRect的四角的坐标
        tipsBgRect.GetWorldCorners(worldCornersPos);
        //从屏幕左下角开始顺时针查看各个corner的Y轴坐标值
        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)    //当宽度尚且可以继续调节时
            {
                tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Min(width + 100, Screen.width * 0.5f));  //宽度最大不超过屏幕的一半
                //当tipsTxtRect的宽度改变后,其tipsTxt.preferredHeight也会即时改变——这一点很方便,很重要
                tipsBgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, tipsTxt.preferredHeight + Mathf.Abs(tipsTxtRect.sizeDelta.y));

                //继续下一次检测
                AutoAdjustTipsBounds(Mathf.Min(width + 100, Screen.width * 0.5f), fontSize);
            }
            else                            //宽度已无法再调节,此时需要改变字体大小
            {
                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));
                
                //继续下一次检测
                AutoAdjustTipsBounds(Screen.width * 0.5f, fontSize - 1);
            }
        }
    }

}

?以上就实现了道具tips不超出边界的需求。运行效果如下:?

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

完整项目链接如下:AutoAdjustTipsProject.zip-Unity3D文档类资源-CSDN下载

PS:

1.UGUI中rectTransform.sizeDelta 和 rectTransform.rect.size的区别:

“rect.size”代表的是该UI对象的真实宽高,不论瞄点如何设置;

“sizeDelta”表示UI对象的宽高比对应的anchor矩形大或者小多少

验证:

如图,Test01的四个锚点都在中心处,Test02的四个锚点则分散在四个角落:

?此时使用如下代码分别输出两个对象的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>");

?运行结果如下:

?从以上结果可知:

1.当UI对象的四个anchor汇聚在一处时(不论是否汇聚在中心,只要四个锚点不分散即可),此时sizeDelta与rect.size相同

2.当在代码中手动设定UI对象的sizeDelta时:

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

运行结果如下:

而此时Test02对象的RectTransform:

Game视图中的结果为:

总结:

1.当需要获取UI对象的真实宽高时使用“rect.size”,而不要使用“sizeDelta”

2.如果要设置UI对象的宽高,则使用rect.SetSizeWithCurrentAnchors,不要直接的设置rect.size,该变量不具备set属性

rect02.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);   //设置宽度
rect02.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 300);     //设置高度

Game视图效果以及Test02的RectTransform变化如下:

??

注意:

1.当使用SetSizeWithCurrentAnchors设置UI对象的宽高时,是以当前UI对象的pivot为中心点来设置的,例如以上Test02的pivot为“0.5,0.5”,由于屏幕空间以左下角为原点,X向右递增,Y向上递增,所以“0.5,0.5”代表的是矩形屏幕的正中心点。在此基础上拉伸UI对象的宽和高

倘若设置Test02的pivot为“0,0.5”,则代表以屏幕左侧边缘的正中为基准点来拉伸UI对象的宽高

?

pivot 和 anchor是两个不同的属性,这里并没有用到anchor来影响UI对象的宽高显示

2.设置了UI对象的宽高后,由于SetSizeWithCurrentAnchors还需要考虑该UI对象的anchor,在设置size后,UGUI会自动根据UI对象的size和anchor来设置其rectTransform相应的数值

3.UGUI中pivot 和 anchor是完全不同的两个属性,两者的作用也不相同,由上述测试知晓

4.sizeDelta实际代表的意义:

当四个锚点分散开时:

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

所以当sizeDelta.x <?0 时代表rect的宽度小于原有的矩形宽度,而在UGUI的RectTransform视图中,却是站在该UI对象的角度,超出anchor矩形的使用负数,在anchor以内的则用正数表示。

如此就会出现当sizeDelta.x < 0,而RectTransform中数值为正数的情况

? ?

PS: "Left","Right"的数值不一定是对半分,这里只是因为设置的tipsBg为上下左右对称的设计才如此;但sizeDelta.x代表的一定是UI实际宽度与anchor矩形宽度的总差值

5.RectTransform.rect.width 和 rect.size.x是等效的,都是获取rect的宽度,但RectTransform.rect.x是获取rect的X坐标,这个不能和rect.size.x弄混

2.如何获取UI对象四角的坐标值:

在获取UI对象四角的坐标值时有两种方式:一种是Unity自带的方法rectTransform.GetWorldCorners,另一种则需要根据该UI对象的pivot和rect.size来分别计算——这种比较麻烦,因此不同的pivot计算方式不同

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

//方式二:根据不同的pivot用不同的方式计算四角的坐标值
//情况一:当pivot为“0, 0.5”时
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>");
}
//这里为了方便比较两种方式的差异,所以先使用方式一输出结果
//从结果可以看出,如果使用方式二主动计算,需要根据pivot的设置变换不同的计算过程,这样其实没有必要。所以这里推荐使用Unity自带的方法来获取corner数值

以上测试中“Test03”的pivot为“0,0.5”,运行结果如下:

??

?

?总结:

1.从以上数值的输出来看,推荐使用UGUI自带的"GetWorldCorners"来获取UI对象四角的坐标值。并且从数值的输出顺序也可以看出,以屏幕空间左下角为起点,顺时针方向输出四角的值

?2.从以上输出结果可以看出,在获取UI对象坐标时得到的是以pivot为中心点显示的坐标(0, 249, 0),而并不是UI对象自身四角交叉线的重合点坐标,由此可见pivot的重要性,在设置UI对象坐标和宽高时都会用到

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-12-26 22:33:05  更:2021-12-26 22:35:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:46:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码