Unity UI
学:ui的控件使用,ui控件的事件响应,ui的分辨率自适应
发展史:GUI->NGUI->UGUI->UIElements(编辑器开发)
Inspector面板扩展(EditorGUI、EditorGUILayout、GUI、GUILayout)-CSDN博客
GUI
GUI是什么
全称:即时模式游戏用户交互界面(IMGUI),被简称为GUI,代码驱动的UI系统
GUI的主要作用:
1.作为程序员的调试工具,创建游戏那调试工具
2.为脚本组件创建自定义检视面板
3.创建新的编辑器窗口和工具以拓展Unity本身(一般作为内置游戏工具)
//不要用它为玩家制作UI功能,不是所见即所得,要运行才有
//可以做程序用的工具和地图编辑器
GUI的工作原理
1.在MonoBehaviour的脚本中的特殊函数里
2.调用GUI提供的方法,类似生命周期函数
private void OnGUI(){
//在其中书写 GUI相关代码 即可显示GUI内容
//控制,事件监听
}
注意:
1.它每帧执行,相当于是用于专门绘制GUI界面的函数(update)
2.一般只在其中执行GUI相关界面绘制和操作逻辑
3.该函数在OnDisable之前 LateUpdate之后执行
4.只要是继承MonoBehaviour的脚本 都可以在OnGUI中绘制GUI
重要参数以及文本和按钮
参数详情见:Unity 用户手册 (2019.4 LTS) - Unity 手册
GUI控件绘制的共同点
1.他们都是GUI公共类中提供的静态函数,直接调用即可
2.他们的参数大同小异
位置参数:Rect(矩形)x,y位置 w,h 尺寸
显示文本: string参数
图片信息:Texture参数(材质)
综合信息: CUIContent参数
自定义样式:GUIStyle参数
3.每一种控件都有多种重制,都是各个参数的排列组合,必备的参数是位置信息和显示信息
文本和按钮 Label Button
public class lesson1: MonoBehaviour
{
public Texture a1; //材质/图形的参数
public Rect pic_a1;//位置参数
public GUIContent sss;//打包的参数,可以有图片和string
public GUIStyle aaa; //各参数见下面
private void OnGUI()
{
//文本控件
GUI.Label(new Rect(0,0,100,20),"夜雨");
GUI.Label(pic_a1,sss,aaa);
//GUI.toolip可以获取当前鼠标或者键盘选中的GUI控件对应的tooltip信息
//按键控件
GUI.Button(pic_a1,a1,buttonstyle);
//按钮有一个bool类型的返回值可以判断点击 按下和抬起才算一次点击
//长按的按钮只要在按钮范围内按下就一直返回ture
GUI,RepeatButton(位置,东西,style);
}
}
多选框和单选框 toggle
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1: MonoBehaviour
{
private bool flag= true;
private bool flag2 = true;
public GUIStyle sss;
private int a = 1;
private void OnGUI()
{
//多选框,普通样式
// flag = GUI.Toggle(new Rect(0, 0, 100, 30), flag, "效果开关");
/*
Toggle 开/关状态由 true/false 布尔值表示。必须提供布尔值作为参数来使 Toggle 表示实际状态。
如果点击,则 Toggle 函数将返回一个新的布尔值。为了捕获此交互性,必须指定布尔值来接受 Toggle 函数的返回值。
*/
// 自定义样式 显示问题
//修改固定高宽 fixedWidth和fixedHeight
//修改从GUIStyle边缘到内容起始处的空间 padding,把字体和图标移动一点
flag2 = GUI.Toggle(new Rect(0, 0, 100, 50), flag2, "app开关",sss);
//单选框基于多选框(多选一)
//可以把中间的判断改成 int t==1
if (GUI.Toggle(new Rect(0, 100, 100, 30), a == 1, "app开关1")) a = 1;
if (GUI.Toggle(new Rect(0, 200, 100, 30), a == 2, "app开关2 ")) a = 2;
}
}
输入框和拖动条
public string inputstr;
public string sss;
public float nowval;
private void OnGUI()
{
//可以在屏幕上输入字符,读取输入的字符再打印到屏幕上,不赋值会在下一帧刷新掉
//参数 位置,显示的string,最大string的长度
inputstr= GUI.TextField(new Rect(0, 0, 100, 30), inputstr,5);
//密码输入,把输入的字符串替换成另外的字符
sss = GUI.PasswordField(new Rect(0, 50, 100, 30), sss,'*');
//拖动条都类似的,表示从最左托到最右边的数值变化
nowval=GUI.HorizontalSlider(new Rect(0, 100, 100, 50), nowval, 0, 1); //水平
nowval=GUI.VerticalSlider(new Rect(0, 150, 20, 50), nowval, 0, 1); //竖直
}
图片绘制和框 DrawTexture Box
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1: MonoBehaviour
{
public Rect weizhi;
public Texture tex;
private void OnGUI( )
{
//传一张图设置宽高,位置和图片,参数有
//ScaleMode
//ScaleAndCrop
//会通过宽高比来计算图片但是会进行裁剪
//ScakeToFit
//会自动根据狂高比进行计算 不会拉变形会一直保持图片的完全显示的状态
//StretchToFill 始终填充满你传入的 Rect范围
// alpha 用来控制图片是否开启透明通道的
//imageAspect
//自定义宽高比 如果不填 默认为0 就会使用图片原始的宽高
GUI.DrawTexture(weizhi, tex,mode,alpha,wh);
GUI.Box(weizhi, "框"); //没啥用
}
}
工具栏和选择网格 Toolbar SelectionGrid
public Rect weizhi;
private int toolbaridx=0;
private int seidx = 0;
private string[] toolbarinfors = new string[] { "选项一", "选项二", "选择三" };
private void OnGUI( )
{
//类似单选栏
//toolbaridx=GUI.Toolbar(weizhi, toolbaridx,toolbarinfors);
//可以用这个索引来做操作
switch(toolbaridx)
{
case 0:
break;
}
//选择网格 相对于toolbar多了一个参数xCount代表水平方向最多显示的按钮数量
seidx=GUI.SelectionGrid(weizhi, seidx, toolbarinfors, 3);
}
分组和滑动列表 BeginGroup EndGroup
public Rect grouppos;
public Rect showpos;
public Rect scPos;
private Vector2 nowpos;
private void OnGUI( )
{
#region 分组
//用于批量控制控件位置
//可以理解为 包裹着的控件加了一个父对象
//可以通过控制分组来控制包裹控件的位置
GUI.BeginGroup(new Rect(0,0,0,0) ); //以这个位置为原点
GUI.Button(new Rect(0, 0, 100, 50), "测试按钮");
GUI.Label(new Rect(0, 60, 100, 30), "文本");
GUI.EndGroup();
#endregion ;
//滚动列表
//scpos可见范围 nowpos整体位置 showpos内容大小
nowpos = GUI.BeginScrollView(scPos, nowpos, showpos);
GUI.EndScrollView();
}
窗口 window
public Rect weizhi = new Rect(100, 100, 200, 150);
private void OnGUI( )
{
// 窗口
//第一个参数 id是窗口的唯一ID 不要和别的窗口重复
//委托参数 是用于绘制窗口用的函数 传入即可
//id除了区分不同的窗口还可以在一个函数中去处理多个窗口的逻辑
GUI.Window(1, weizhi, hanshu, "测试窗口");
//模态窗口 可以让该其它控件不在有用
//你可以理解该窗口在最上层 其它按钮都点击不到了
//只能点击该窗口上的控件
GUI.ModalWindow(3, new Rect(300, 100, 200, 100), hanshu, "模态窗口");
//警告和提示的作用,必须先处理完
// 拖动窗口,因为函数有返回值所有还是可以改变
weizhi = GUI.Window(4, weizhi, hanshu2, "窗口4");
}
private void hanshu2(int id)
{
GUI.DragWindow();
//该API写在窗口函数中调用,可以让窗口被拖动
//传如Rect参数的重载 作用
//是决定窗口中那一部分位置 可以被拖动
//无参重载,默认所有位置都可以拖动
}
private void hanshu(int id)
{
GUI.Button(new Rect(0, 30, 30, 20), "1");
}
颜色
用TextMeshPro 显示时间
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class ShowTime : MonoBehaviour
{
// Start is called before the first frame update
public TextMeshProUGUI Text;
// Start is called before the first frame update
void Start()
{
Text = transform.GetComponent<TextMeshProUGUI>();
// TxtCurrentTime = GetComponent<TextMeshPro> ();
}
// Update is called once per frame
void Update()
{
//获取系统当前时间
System.DateTime NowTime = System.DateTime.Now.ToLocalTime();
Text.text = NowTime.ToString("HH:mm");
}
}
所有UI都必须显示在canvous上
如何实现Scroll上的text能够无限滑动 C#脚本
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;
using TMPro;
//读取位置和宽度的特性
[RequireComponent(typeof(GridLayoutGroup))]
[RequireComponent(typeof(ContentSizeFitter))]
public class InfinityGridLayoutGroup : MonoBehaviour
{
[SerializeField]
int minAmount = 0;
/*
实现无限滚动,需要的最少的child数量,上面一个下面一个 2个就够
*/
RectTransform rectTransform; //位置
GridLayoutGroup gridLayoutGroup;
ContentSizeFitter contentSizeFitter;
ScrollRect scrollRect;
List<RectTransform> children = new List<RectTransform>();
Vector2 startPosition;
int amount = 0;
public delegate void UpdateChildrenCallbackDelegate(int index, Transform trans);
public UpdateChildrenCallbackDelegate updateChildrenCallback = null;
//回调每次要更新每个btn的Text
int realIndex = -1;
int realIndexUp = -1; //两个指针从下往上;
bool hasInit = false;
Vector2 gridLayoutSize;
Vector2 gridLayoutPos;
Dictionary<Transform, Vector2> childsAnchoredPosition = new Dictionary<Transform, Vector2>();
Dictionary<Transform, int> childsSiblingIndex = new Dictionary<Transform, int>();
IEnumerator InitChildren()
{
yield return 0;
if (!hasInit)
{
//获取Grid的宽度;
rectTransform = GetComponent<RectTransform>();
gridLayoutGroup = GetComponent<GridLayoutGroup>();
gridLayoutGroup.enabled = false;
contentSizeFitter = GetComponent<ContentSizeFitter>();
contentSizeFitter.enabled = false;
/*关于Anchors布局 https://blog.lujun.co/2017/09/07/unity_rect_transform/ */
gridLayoutPos = rectTransform.anchoredPosition;
gridLayoutSize = rectTransform.sizeDelta;
//注册ScrollRect滚动回调;
scrollRect = transform.parent.GetComponent<ScrollRect>();
scrollRect.onValueChanged.AddListener((data) => { ScrollCallback(data); });
/*
* onValueChanged 用于监视 ScrollRect 对象中的更改。 onValueChanged 调用将使用 UnityEvent.AddListener API 来监视 更改。
* 发生更改时,将调用用户提供的脚本代码。 ScrollRect.onValueChanged 的 UnityEvent.AddListener API 要求一个 Vector2 参数。
*/
//获取所有child anchoredPosition 以及 SiblingIndex;
/* SiblingIndex即为同级索引,意为「同一父节点下的兄弟节点间的位置」。
siblingIndex 越小的节点排越前,索引最小值为 0,也就是第一个节点的索引值。
使用 GetSiblingIndex 查找 GameObject 在该层级视图中的位置。当 GameObject 的同级索引发生更改时,其在“Hierarchy”窗口中的顺序也会改变。
如果需要更改 GameObject 子项的排序(例如使用布局组组件时很有用。布局组还将按索引以可视化方式对组进行重新排序
*/
for (int index = 0; index < transform.childCount; index++)
{
Transform child = transform.GetChild(index);
RectTransform childRectTrans = child.GetComponent<RectTransform>();
childsAnchoredPosition.Add(child, childRectTrans.anchoredPosition);
childsSiblingIndex.Add(child, child.GetSiblingIndex());
}
hasInit = true;
}
else
{
rectTransform.anchoredPosition = gridLayoutPos;
rectTransform.sizeDelta = gridLayoutSize;
children.Clear();
realIndex = -1;
realIndexUp = -1;
//children重新设置上下顺序;
foreach (var info in childsSiblingIndex)
{
info.Key.SetSiblingIndex(info.Value);
}
//children重新设置anchoredPosition;
for (int i= 0; index < transform.childCount; i++)
{
Transform child = transform.GetChild(i);
RectTransform childRectTrans = child.GetComponent<RectTransform>();
if (childsAnchoredPosition.ContainsKey(child))
childRectTrans.anchoredPosition = childsAnchoredPosition[child];
else Debug.LogError("childsAnchoredPosition no contain " + child.name);
}
}
//获取所有child;
for (int i = 0; index < transform.childCount; i++)
{
Transform trans = transform.GetChild(i);
trans.gameObject.SetActive(true);
children.Add(transform.GetChild(index).GetComponent<RectTransform>());
//初始化前面几个更新一次;
UpdateChildrenCallback(children.Count - 1, transform.GetChild(i));
}
startPosition = rectTransform.anchoredPosition;
realIndex = children.Count - 1;
/*
* Debug.Log( scrollRect.transform.TransformPoint(Vector3.zero));
Debug.Log(transform.TransformPoint(children[0].localPosition));
*/
//如果需要显示的个数小于设定的个数;
for (int i= 0; index < minAmount; i++) children[i].gameObject.SetActive(i< amount);
if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
{
//如果小了一行,则需要把GridLayout的高度减去一行的高度;
int row = (minAmount - amount) / gridLayoutGroup.constraintCount;
if (row > 0)
{
rectTransform.sizeDelta -= new Vector2(0, (gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y) * row);
}
}
else
{
//如果小了一列,则需要把GridLayout的宽度减去一列的宽度;
int column = (minAmount - amount) / gridLayoutGroup.constraintCount;
if (column > 0)
{
rectTransform.sizeDelta -= new Vector2((gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x) * column, 0);
}
}
}
void ScrollCallback(Vector2 data)
{
UpdateChildren();
}
void UpdateChildren()
{
if (transform.childCount < minAmount)
{
return;
}
Vector2 currentPos = rectTransform.anchoredPosition;
if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount) //看cell够不够将列数约束为指定数量。
{
float offsetY = currentPos.y - startPosition.y;
if (offsetY > 0)
{
//向上拉,向下扩展;
{
if (realIndex >= amount - 1)
{
startPosition = currentPos;
return; //剪枝(
}
float scrollRectUp=scrollRect.transform.TransformPoint(Vector3.zero).y;
Vector3 childBottomLeft = new Vector3(children[0].anchoredPosition.x, children[0].anchoredPosition.y - gridLayoutGroup.cellSize.y, 0f);
//Debug.Log(children[0].anchoredPosition.x+".............."+children[0].anchoredPosition.y)
float childBottom = transform.TransformPoint(childBottomLeft).y;
if (childBottom >= scrollRectUp)
{
//Debug.Log("childBottom >= scrollRectUp");
//移动到底部;
for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
{
children[index].SetAsLastSibling();
children[index].anchoredPosition = new Vector2(children[index].anchoredPosition.x, children[children.Count - 1].anchoredPosition.y - gridLayoutGroup.cellSize.y - gridLayoutGroup.spacing.y);
realIndex++;
if (realIndex > amount - 1)
{
children[index].gameObject.SetActive(false);
}
else
{
UpdateChildrenCallback(realIndex, children[index]);
}
}
//GridLayoutGroup 底部加长;
rectTransform.sizeDelta += new Vector2(0, gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y);
//更新child;
for (int index = 0; index < children.Count; index++)
{
children[index] = transform.GetChild(index).GetComponent<RectTransform>();
}
}
}
}
else
{
//Debug.Log("");
//向下拉,下面收缩;
if (realIndex + 1 <= children.Count)
{
startPosition = currentPos;
return;
}
RectTransform scrollRectTransform = scrollRect.GetComponent<RectTransform>();
Vector3 scrollRectAnchorBottom = new Vector3(0, -scrollRectTransform.rect.height - gridLayoutGroup.spacing.y, 0f);
float scrollRectBottom = scrollRect.transform.TransformPoint(scrollRectAnchorBottom).y;
Vector3 childUpLeft = new Vector3(children[children.Count - 1].anchoredPosition.x, children[children.Count - 1].anchoredPosition.y, 0f);
float childUp = transform.TransformPoint(childUpLeft).y;
if (childUp < scrollRectBottom)
{
//Debug.Log("childUp < scrollRectBottom");
//把底部的一行 移动到顶部
for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
{
children[children.Count - 1 - index].SetAsFirstSibling();
children[children.Count - 1 - index].anchoredPosition = new Vector2(children[children.Count - 1 - index].anchoredPosition.x, children[0].anchoredPosition.y + gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y);
//Debug.log()
children[children.Count - 1 - index].gameObject.SetActive(true);
UpdateChildrenCallback(realIndex - children.Count - index, children[children.Count - 1 - index]);
//更新
}
realIndex -= gridLayoutGroup.constraintCount;
//GridLayoutGroup 底部缩短;
rectTransform.sizeDelta -= new Vector2(0, gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y);
//更新child;
for (int index = 0; index < children.Count; index++)
{
children[index] = transform.GetChild(index).GetComponent<RectTransform>();
}
}
}
}
else
{
float offsetX = currentPos.x - startPosition.x;
if (offsetX < 0)
{
//向左拉,向右扩展;
{
if (realIndex >= amount - 1)
{
startPosition = currentPos;
return;
}
float scrollRectLeft = scrollRect.transform.TransformPoint(Vector3.zero).x;
Vector3 childBottomRight = new Vector3(children[0].anchoredPosition.x + gridLayoutGroup.cellSize.x, children[0].anchoredPosition.y, 0f);
float childRight = transform.TransformPoint(childBottomRight).x;
// Debug.LogError("childRight=" + childRight);
if (childRight <= scrollRectLeft)
{
//Debug.Log("childRight <= scrollRectLeft");
//移动到右边;
for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
{
children[index].SetAsLastSibling();
children[index].anchoredPosition = new Vector2(children[children.Count - 1].anchoredPosition.x + gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x, children[index].anchoredPosition.y);
realIndex++;
if (realIndex > amount - 1)
{
children[index].gameObject.SetActive(false);
}
else
{
UpdateChildrenCallback(realIndex, children[index]);
}
}
//GridLayoutGroup 右侧加长;
rectTransform.sizeDelta += new Vector2(gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x, 0);
//更新child;
for (int index = 0; index < children.Count; index++)
{
children[index] = transform.GetChild(index).GetComponent<RectTransform>();
}
}
}
}
else
{
//Debug.Log("Drag Down");
//向右拉,右边收缩;
if (realIndex + 1 <= children.Count)
{
startPosition = currentPos;
return;
}
RectTransform scrollRectTransform = scrollRect.GetComponent<RectTransform>();
Vector3 scrollRectAnchorRight = new Vector3(scrollRectTransform.rect.width + gridLayoutGroup.spacing.x, 0, 0f);
float scrollRectRight = scrollRect.transform.TransformPoint(scrollRectAnchorRight).x;
Vector3 childUpLeft = new Vector3(children[children.Count - 1].anchoredPosition.x, children[children.Count - 1].anchoredPosition.y, 0f);
float childLeft = transform.TransformPoint(childUpLeft).x;
if (childLeft >= scrollRectRight)
{
//Debug.LogError("childLeft > scrollRectRight");
//把右边的一行 移动到左边;
for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
{
children[children.Count - 1 - index].SetAsFirstSibling();
children[children.Count - 1 - index].anchoredPosition = new Vector2(children[0].anchoredPosition.x - gridLayoutGroup.cellSize.x - gridLayoutGroup.spacing.x, children[children.Count - 1 - index].anchoredPosition.y);
children[children.Count - 1 - index].gameObject.SetActive(true);
UpdateChildrenCallback(realIndex - children.Count - index, children[children.Count - 1 - index]);
}
//GridLayoutGroup 右侧缩短;
rectTransform.sizeDelta -= new Vector2(gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x, 0);
//更新child;
for (int index = 0; index < children.Count; index++)
{
children[index] = transform.GetChild(index).GetComponent<RectTransform>();
}
realIndex -= gridLayoutGroup.constraintCount;
}
}
}
startPosition = currentPos;
}
void UpdateChildrenCallback(int index, Transform trans)
{
if (updateChildrenCallback != null)
{
updateChildrenCallback(index, trans);
}
}
/// <summary>
/// 设置总的个数;
/// </summary>
/// <param name="count"></param>
public void SetAmount(int count)
{
amount = count;
StartCoroutine(InitChildren());
}
}