UnityPackage下载连接
这次是在3D程序里面一个非常常见的飘字提示功能,说难也难 说不难也不难,主要实现的是下列一些功能。
- 消息队列比较长的时候 飘字提示需要将比较旧的飘字直接弹出消失
- 新消息出现时,旧消息的挤上效果
- 消息结束时的上浮效果
- 简单对象池
- 简单UI适配
接下来是主要代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 主要控制类
/// </summary>
public class OperateAlert : MonoBehaviour
{
public GameObject prefOperAlrtItm;
public GameObject uiCanvas;
public float plusOneTime = 0.3f;
public int topStartPosX = 0;
public int btmStartPosX = 0;
/// <summary>
/// 右边弹出的飘字提升距离屏幕右边的像素距离
/// </summary>
public int rightStartPosXRightInterval = 100;
int RightStartPosX
{
get {
return Screen.height - rightStartPosXRightInterval;
}
}
public const int TOP_POS_FLAG = 1;
public const int BOTTOM_POS_FLAG = 2;
public const int RIGHT_POS_FLAG = 3;
public const float DEFAULT_TIME = 1.5f;
public const int DEFAULT_TEST_SIZE = 20;
List<OperateAlertItem> restPools = new List<OperateAlertItem>();
List<OperateAlertItem> rightItemList = new List<OperateAlertItem>();
List<OperateAlertItem> bottomItemList = new List<OperateAlertItem>();
List<OperateAlertItem> topItemList = new List<OperateAlertItem>();
static List<MsgStruct> rightStructList = new List<MsgStruct>();
static List<MsgStruct> bottomStructList = new List<MsgStruct>();
static List<MsgStruct> topStructList = new List<MsgStruct>();
float lastTopShowOneItemTime = 0;
float lastBottomShowOneItemTime = 0;
float lastRightShowOneItemTime = 0;
MsgStruct showMsg;
Coroutine updateItemCor;
/// <summary>
/// 控制入口函数
/// </summary>
/// <param name="msg"></param>
/// <param name="pos"></param>
/// <param name="time"></param>
/// <param name="size"></param>
public static void Show(string msg, int pos = TOP_POS_FLAG, float time = DEFAULT_TIME, int size = DEFAULT_TEST_SIZE)
{
MsgStruct ms = new MsgStruct();
ms.showpos = pos;
ms.msg = msg;
ms.totalShowTime = time;
ms.fontSize = size;
AddToStructList(ms);
}
public static void Show(string msg, int pos)
{
Show(msg, pos, DEFAULT_TIME, DEFAULT_TEST_SIZE);
}
public static void Show(string msg)
{
Show(msg, TOP_POS_FLAG, DEFAULT_TIME, DEFAULT_TEST_SIZE);
}
OperateAlertItem GetOneItem()
{
OperateAlertItem item;
if (restPools.Count > 0)
{
item = restPools[0];
item.setEnable(true);
restPools.RemoveAt(0);
return item;
}
else
{
item = NewItem(showMsg.fontSize, showMsg.totalShowTime);
}
return item;
}
void ReturnOneItem(OperateAlertItem item)
{
item.setEnable(false);
item.setLocation(10000, 0, 0);
restPools.Add(item);
}
void Update()
{
UpdateList(bottomStructList, BottomStartY, bottomItemList, lastBottomShowOneItemTime, btmStartPosX);
UpdateList(topStructList, TopStartY, topItemList, lastTopShowOneItemTime, topStartPosX);
UpdateList(rightStructList, RightStartY, rightItemList, lastRightShowOneItemTime, RightStartPosX);
}
void UpdateList(List<MsgStruct> msgList, int StartY, List<OperateAlertItem> itemList, float lastShowTime, int startX)
{
if (msgList.Count > 0 && Time.realtimeSinceStartup - lastShowTime > plusOneTime)
{
lastShowTime = Time.realtimeSinceStartup;
MsgStruct ms = msgList[0];
OperateAlertItem item = GetOneItem();
item.SetSizeAndTime(ms.fontSize, ms.totalShowTime);
item.Init(ms.msg, StartY, Time.realtimeSinceStartup, startX);
msgList.RemoveAt(0);
//注意最新的是插到list的开头 之前的就在后面了
itemList.Insert(0, item);
}
int count = 0;
for (int i = 0; i < itemList.Count; i++)
{
if (itemList[i].IsEnd == true)
{
ReturnOneItem(itemList[i]);
itemList.RemoveAt(i);
//这里要进行i--的原因是itemList.RemoveAt(i)之后 如果不这样做 会调过一个元素
//好像有四个元素的list 在遍历的时候移除了第二个元素 即list[1]
//这时list[2] 会移动到list[1] list[3]会移动到list[2]
//如果不进行i--则跳过了变化之后的list[1]
i--;
}
else
{
itemList[i].update(count);
count++;
}
}
}
static void AddToStructList(MsgStruct ms)
{
if (ms.showpos == TOP_POS_FLAG)
{
CheckAdd(topStructList, ms);
}
else if (ms.showpos == BOTTOM_POS_FLAG)
{
CheckAdd(bottomStructList, ms);
}
else if (ms.showpos == RIGHT_POS_FLAG)
{
CheckAdd(rightStructList, ms);
}
}
static void CheckAdd(List<MsgStruct> list, MsgStruct msg)
{
bool canAdd = true;
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i].totalShowTime == msg.totalShowTime
&& list[i].msg == msg.msg)
{
canAdd = false;
break;
}
}
if (canAdd)
{
list.Add(msg);
}
}
OperateAlertItem NewItem(int size = 18, float totalShowTime = 1.5f)
{
GameObject go = Instantiate(prefOperAlrtItm);
go.transform.parent = uiCanvas.transform;
OperateAlertItem ori = go.GetComponent<OperateAlertItem>();
ori.SetSizeAndTime(size, totalShowTime);
return ori;
}
int BottomStartY
{
get
{
return -Screen.height / 2 + 125;
}
}
int TopStartY
{
get
{
return Screen.height / 2 - 200;
}
}
int RightStartY
{
get
{
return 15;
}
}
}
struct MsgStruct
{
public string msg;
public int showpos;
public float totalShowTime;
public int fontSize;
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 控制子类
/// </summary>
public class OperateAlertItem : MonoBehaviour
{
const int space = 40; //上方文本之间相隔距离像素
GameObject prefObj;
readonly int _size;
/// <summary>
/// 整个显示过程的开始时间点
/// </summary>
float startShowProcessTime;
int startPosY;
int targetPosY;
int curPos;
int x = 0;
/// <summary>
/// 整个从开始显示到完全消失的时间范围
/// </summary>
float totalShowTime;
/// <summary>
/// 准备显示的时候 透明度由0变到1的过程的结束时间点
/// </summary>
const float alphaPlusFnshTime = 0.2f;
/// <summary>
/// 整个透明度为1的阶段的结束时间点
/// </summary>
float completelyFnshTime;
/// <summary>
/// 准备消失的时候
/// 透明度由1到0的阶段的阶段时间范围
/// </summary>
const float alphaMinusDur = 0.2f;
Text strText;
public Text StrText
{
get
{
if (strText == null)
{
strText = GetComponentInChildren<Text>();
}
return strText;
}
}
Image bgImg;
public Image BgImg
{
get
{
if (bgImg == null)
{
bgImg = GetComponent<Image>();
}
return bgImg;
}
}
/// <summary>
/// 这里面的index其实会随着新来的信息的插入而增大
/// 这样新来信息之后
/// 前面的信息会随着传入的表示它自己的index的增大而进行位置的更新 实现了挤上的效果
/// </summary>
/// <param name="index"></param>
public void update(int index)
{
//deltaTime表示从开始显示的时间开始 过了多久 注释1
float passTimeSinceStartShow = Time.realtimeSinceStartup - startShowProcessTime;
//这里index越大表示越早出现的消息
//因为外部调用函数遍历的list对于新的元素是从0开始插入的
//如果插入的元 素有很多 则把队列里面最新的4个之外的 展示item直接进入收尾阶段
if (index > 3 && passTimeSinceStartShow < completelyFnshTime)
{
//这样做是为了让后面的update中注释1的语句得到的passTimeSinceStartShow是进入阶段3
startShowProcessTime = Time.realtimeSinceStartup - completelyFnshTime;
passTimeSinceStartShow = completelyFnshTime;
}
float alphaRatio = 0;
if (passTimeSinceStartShow < alphaPlusFnshTime) //阶段1 在准备显示的透明度增加阶段
{
targetPosY = startPosY + index * space;
alphaRatio = passTimeSinceStartShow / alphaPlusFnshTime;
updateAlpha(alphaRatio);
}
else if (passTimeSinceStartShow < completelyFnshTime) //阶段2 在完全显示的阶段
{
updateAlpha(1);
targetPosY = startPosY + index * space;
}
else if (passTimeSinceStartShow < totalShowTime) //阶段3 收尾阶段 渐隐滑出屏幕
{
targetPosY = startPosY + 50 + index * space;
//oriRatio表示的是真正的结束进程的比例
float oriRatio = (passTimeSinceStartShow - completelyFnshTime) / alphaMinusDur;
//ratio是为了实现oriRatio从0到1的过程中 实现透明度从1到0变化的转变
alphaRatio = 1 - oriRatio;
updateAlpha(alphaRatio);
}
//这部分相当于mathf.lerp函数
curPos = (int)(curPos + (targetPosY - curPos) * 0.2f);
setLocation(x, curPos);
}
internal void setEnable(bool v)
{
gameObject.SetActive(v);
}
public void setLocation(int x, int y)
{
transform.localPosition = new Vector3(x, y);
}
public void setLocation(int x, int y, int z)
{
transform.localPosition = new Vector3(x, y, z);
}
public virtual void Init(string str, int startPosY, float startShowTime, int startPosX = 0)
{
//txt.text = str;
startShowProcessTime = startShowTime;
this.startPosY = startPosY;
curPos = startPosY - 50;
x = startPosX;
setLocation(startPosX, curPos);
}
protected virtual void updateAlpha(float value)
{
StrText.CrossFadeAlpha(value, 0, true);
BgImg.CrossFadeAlpha(value, 0, true);
}
public bool IsEnd
{
get
{
return Time.realtimeSinceStartup - startShowProcessTime > totalShowTime;
}
}
public void SetSizeAndTime(int size, float totalShowTime)
{
StrText.fontSize = size;
this.totalShowTime = totalShowTime;
completelyFnshTime = this.totalShowTime - alphaMinusDur;
}
}