# UGUI advanced knowledge  floating word prompt improvement

Posted by scofansnags on Sat, 27 Jun 2020 06:14:13 +0200

I wrote an article about floating words UGUI advanced knowledge  floating word prompt
There are still some disadvantages in the floating word prompt in this article, because one of the key points of UI is screen adaptability, so here we use the new article to write the key points

• It can't be achieved that the position of floating words looks the same under various resolutions: the position of the previous floating words is an absolute value. When the standard resolution preset by the developer (1136 * 640) shows that the normal resolution is large, the position is inclined to the center of the screen when the resolution of one side is low.
• The size of the floating word prompt cannot look the same at all resolutions: the size of the floating word prompt is an absolute value, resulting in the large resolution, the floating word prompt is too small, the small resolution, the floating word prompt is too large.
• The setting of the interval between floating word prompts is unreasonable: the previous interval is absolute. When the fixed size of floating word is adjusted at the beginning, it looks good. When the size of floating word changes, it is not suitable.
• Floating word prompt if there are too many words, floating word does not set its own size according to the word size, resulting in too many texts not visible

## Solution

premise
Firstly, the standard resolution needs to be recorded, and then the ratio between the width and height of the actual running screen resolution and the width and height of the standard resolution is calculated. Here, the ratio of the width and height is recorded as wr, hr respectively;
The width and height of the floating word prefab are adjusted when the localSale is 1 vector under the standard resolution, and the distance between the floating word and the screen is also adjusted down at the standard resolution.

The first solution is that the vertical distance between the floating word prompt and the screen should be multiplied by hr on the basis of the original adjustment, and the horizontal distance should also be multiplied by wr on the basis of the original adjustment

There is more than one solution to the second problem. It is simpler to set the anchor of the item with floating word prompt to When the item is loaded, it should be able to set the size according to the actual resolution. The method used here is set by code. Under the standard resolution, the localScale of floating word prompt is a vector. In actual operation, its localScale.x is set to WR, and its localScale. Y is set to hr.
The solution to the third problem is to obtain the height of the floating word prompt in actual operation. The distance between the centers of the two floating word prompts is based on the two heights plus the additional distance value
The solution to the fourth problem is to set the background and text anchor as shown in the figure above, and then set the width and height of the text mounted parent UI as the preferredWidth and preferredHeight variables of text. The functions of these two variables can be found in the Unity document or in the following code. A previous experiment thought is that image and text are mounted at the same level. After setting the width and height of text, image sets its width and height according to the width and height of text, which is consistent with that of text. However, it is better that the total prefab area of the preform can cover all the areas of its sub UI, so this idea has not been implemented to the end

code:

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
///Floating word prompt floating word prompt is divided into three types: the top of the screen, the bottom of the screen and the right of the screen
///Main control class
/// </summary>
{
/// <summary>
///It should be noted that
///The preform OperAlrtItm is
///The pivot of prefOperAlrtItm is at its center, i.e. the pivot value is (0.5, 0.5)
/// </summary>
public GameObject prefOperAlrtItm;
public GameObject uiCanvas;

RectTransform prefOperAlrtItmRect;
RectTransform uiCanvasRect;

public float plusOneTime = 0.3f;

[SerializeField]
public Font useFont;

/// <summary>
///When the floating word prompt is on the right
///The distance from the right side of the floating word prompt to the right side of the screen
///TextRightDeltaOffset for UI adaptation is required
/// </summary>
public int textRightDeltaOffset = 20;

public int TextRightDeltaOffset
{
get
{
textRightDeltaOffset);
}
}

/// <summary>
///When the floating word prompt is at the top
///The distance between the top of the floating word prompt and the top of the screen
///TextTopDeltaOffset for UI adaptation is required
/// </summary>
public int textTopDeltaOffset = 125;

public int TextTopDeltaOffset
{
get
{
textTopDeltaOffset);
}
}

/// <summary>
///At standard resolution
///When the floating word prompt is at the bottom
///The distance between the bottom of the floating word prompt and the bottom of the screen
///Textbuttomdeltaoffset for UI adaptation is required
/// </summary>
[SerializeField]
int textBottomDeltaOffset = 200;
/// <summary>
///
/// </summary>
public int TextBottomDeltaOffset
{
get
{
textBottomDeltaOffset);
}
}

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_SHOW_TIME = 3f;
public const int DEFAULT_TEST_SIZE = 20;

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;

private void Awake()
{
prefOperAlrtItmRect = prefOperAlrtItm.GetComponent<RectTransform>();
uiCanvasRect = uiCanvas.GetComponent<RectTransform>();
}

/// <summary>
///Floating word entry function
/// </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_SHOW_TIME, int size = DEFAULT_TEST_SIZE)
{
MsgStruct ms = new MsgStruct();
ms.showpos = pos;
ms.msg = msg;
ms.totalShowTime = time;
ms.fontSize = size;
}

public static void Show(string msg, int pos)
{
Show(msg, pos, DEFAULT_SHOW_TIME, DEFAULT_TEST_SIZE);
}

public static void Show(string msg)
{
Show(msg, TOP_POS_FLAG, DEFAULT_SHOW_TIME, DEFAULT_TEST_SIZE);
}

/// <summary>
///Simple object pool returns a floating word prompt prefab
/// </summary>
/// <returns></returns>
{

if (restPools.Count > 0)
{
item = restPools;
item.setEnable(true);
restPools.RemoveAt(0);
return item;
}
else
{
item = NewItem(showMsg.fontSize, showMsg.totalShowTime);
}
return item;
}

{
item.setEnable(false);
item.setLocation(10000, 0, 0);
}

/// <summary>
///Pop up the messages in the floating word prompt message queue in the update function
/// </summary>
void Update()
{
UpdateList(bottomStructList,  bottomItemList, PosEnum.Bottom,
lastBottomShowOneItemTime, TextBottomDeltaOffset);

UpdateList(topStructList, topItemList, PosEnum.Top,
lastTopShowOneItemTime, TextTopDeltaOffset);

UpdateList(rightStructList,  rightItemList, PosEnum.Right,
lastRightShowOneItemTime, TextRightDeltaOffset);
}

/// <summary>
///Main entrance to update message floating words
/// </summary>
/// <param name="msgList"></param>
/// <param name="StartY"></param>
/// <param name="itemList"></param>
/// <param name="lastShowTime"></param>
/// <param name="startX"></param>
PosEnum poeEnum,  float lastShowTime, int delta)
{
if (msgList.Count > 0 && Time.realtimeSinceStartup - lastShowTime > plusOneTime)
{
lastShowTime = Time.realtimeSinceStartup;
MsgStruct ms = msgList;
item.SetSizeAndTime(ms.fontSize, ms.totalShowTime, useFont);
item.Init(ms.msg, delta, Time.realtimeSinceStartup,
poeEnum, uiCanvasRect,

msgList.RemoveAt(0);
//Note that the latest one is inserted before the beginning of itemList
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);
//The reason I -- is itemList.RemoveAt(i) And then if you don't do that, you'll have an element tuned
//It seems that the list with four elements removes the second element, list during traversal
//At this time, list will move to list list will move to list
//If i is not performed, the list after the change is skipped 
i--;
}
else
{
itemList[i].update(count);
count++;
}
}
}

/// <summary>
///Store messages to message queue
/// </summary>
/// <param name="ms"></param>
{
if (ms.showpos == TOP_POS_FLAG)
{
}
else if (ms.showpos == BOTTOM_POS_FLAG)
{
}
else if (ms.showpos == RIGHT_POS_FLAG)
{
}
}

/// <summary>
///Check for duplicate message queue additions
/// </summary>
/// <param name="list"></param>
/// <param name="msg"></param>
static void CheckAdd(List<MsgStruct> list, MsgStruct msg)
{
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i].totalShowTime == msg.totalShowTime
&& list[i].msg == msg.msg)
{
break;
}
}
{
}
}

/// <summary>
///Create a new floating word prompt item
///
/// </summary>
/// <param name="size"></param>
/// <param name="totalShowTime"></param>
/// <returns></returns>
OperateAlertItem NewItem(int size = 18, float totalShowTime = 1.5f)
{
GameObject go = Instantiate(prefOperAlrtItm);
go.transform.SetParent(uiCanvas.transform, false);
ori.SetSizeAndTime(size, totalShowTime, useFont);
return ori;
}

}

struct MsgStruct
{
public string msg;
public int showpos;
/// <summary>
///Time to display this message
/// </summary>
public float totalShowTime;
public int fontSize;
}

```
```using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public enum PosEnum
{
Top,
Bottom,
Right
}

/// <summary>
///Single floating word prompt item class
///Floating word indicates that item's pivot is in the center of its area
/// </summary>
{
int space = 40;	        //Distance between upper text pixels
public  int Space {
get
{
return (int)(GetActualSize().y);
}
}

{
get
{
if (bgReset == null)
{
}
return bgReset;
}
}

GameObject prefObj;
/// <summary>
///Start time of the whole display process
/// </summary>
float startShowProcessTime;

/// <summary>
///y value of floating word starting position
/// </summary>
int startPosY;

/// <summary>
///Target y value of current floating word prompt
/// </summary>
int targetPosY;

/// <summary>
///y value of floating word prompt at every moment
/// </summary>
int curPosY;

/// <summary>
///The x position of the floating word prompt will not be changed in the whole process
/// </summary>
int xPos = 0;

int refResolutionWidth;
int refResolutionHeight;

PosEnum selfPosEnum;

/// <summary>
///The whole time range from the beginning of display to the complete disappearance
/// </summary>
float totalShowTime;

/// <summary>
///The end time point of the process when the transparency changes from 0 to 1 when the display is ready
/// </summary>
const float alphaPlusFnshTime = 0.2f;

/// <summary>
///End time point of the whole stage with transparency of 1
/// </summary>
float completelyFnshTime;

RectTransform selfRect;
public RectTransform SelfRect
{
get
{

if (selfRect == null)
{
selfRect = GetComponent<RectTransform>();
}
return selfRect;
}
}

/// <summary>
///The phase time range of the phase with transparency from 1 to 0
/// </summary>
const float alphaMinusDur = 0.2f;

Text strText;
public Text StrText
{
get
{

if (strText == null)
{
strText = GetComponentInChildren<Text>();
}
return strText;
}
}

RectTransform strTextRect;
public RectTransform StrTextRect
{
get
{

if (strTextRect == null)
{
strTextRect = StrText.GetComponent<RectTransform>();
}
return strTextRect;
}
}

Image bgImg;
public Image BgImg
{
get
{
if (bgImg == null)
{
bgImg = GetComponentInChildren<Image>();
}
return bgImg;
}
}

/// <summary>
///The index will increase with the insertion of new information
///After this new information
///The previous information will update the location with the increase of the incoming index that represents itself to achieve the crowding effect
/// </summary>
/// <param name="index"></param>
public void update(int index)
{
//Delta time indicates how long has passed since the start of display note 1
float passTimeSinceStartShow = Time.realtimeSinceStartup - startShowProcessTime;

//The larger the index, the earlier the message appears
//Because the list traversed by the external call function is inserted from 0 for the new element
//If there are many inserted elements, display item s other than the latest four in the queue will enter the closing phase directly
if (index > 3 && passTimeSinceStartShow < completelyFnshTime)
{
//This is done so that the statement of annotation 1 in the later update will get the passTimeSinceStartShow to enter phase 3
startShowProcessTime = Time.realtimeSinceStartup - completelyFnshTime;
passTimeSinceStartShow = completelyFnshTime;
}

float alphaRatio = 0;
if (passTimeSinceStartShow < alphaPlusFnshTime) //Stage 1 in the transparency increase stage of preparation display
{
targetPosY = startPosY + index * Space;
alphaRatio = passTimeSinceStartShow / alphaPlusFnshTime;
updateAlpha(alphaRatio);
}
else if (passTimeSinceStartShow < completelyFnshTime) //Stage 2 in the fully displayed stage
{
updateAlpha(1);
targetPosY = startPosY + index * Space;
}
else if (passTimeSinceStartShow < totalShowTime) //Phase 3 closing phase fade out of screen
{

targetPosY = startPosY + 50 + index * Space;
//Ori ratio represents the proportion of processes that actually end
float oriRatio = (passTimeSinceStartShow - completelyFnshTime) / alphaMinusDur;
//Ration is to realize the change of transparency from 1 to 0 in the process of ori ratio from 0 to 1
alphaRatio = 1 - oriRatio;
updateAlpha(alphaRatio);
}

//This part is equivalent to mathf.lerp function
curPosY = (int)(curPosY + (targetPosY - curPosY) * 0.2f);

setLocation(xPos, curPosY);

}

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 deltaValue, float startShowTime,
PosEnum selfPosEnum, RectTransform wholeScrnCanvas,
int refResolutionWidth = 1136, int refResolutionHeight = 640)
{
this.selfPosEnum = selfPosEnum;

StrText.text = str;

//preferredWidth is the minimum width for displaying all fonts according to the current text settings
//The width of the line with the most words in each line
//Set the width of StrTextRect to the width when all fonts are displayed normally
//This width is the width when the localScale.x of StrTextRect is 1
SelfRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, StrText.preferredWidth);
//As above, preferredHeight is the minimum height for normal display of all fonts according to the current text settings
//Approximately equal to the height of each row multiplied by the number of rows plus the up spacing multiplied by the number of rows-1
SelfRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, StrText.preferredHeight);

BgReset.ResetRect(StrTextRect);

startShowProcessTime = startShowTime;

this.refResolutionWidth = refResolutionWidth;
this.refResolutionHeight = refResolutionHeight;

CalcStartPos(deltaValue, selfPosEnum, wholeScrnCanvas);
}

private void CalcStartPos(int deltaValue, PosEnum selfPosEnum, RectTransform wholeScrnCanvas)
{
//The coordinates of the midpoint of the screen are (0,0), and the upper right direction is the positive direction
//The pivot of operatealertititem is in its center
//To calculate the coordinates of other places
switch (selfPosEnum)
{
case PosEnum.Top:
xPos = 0;
startPosY = (int)(wholeScrnCanvas.rect.height / 2 - deltaValue);
break;
case PosEnum.Bottom:
xPos = 0;
startPosY = (int)(-wholeScrnCanvas.rect.height / 2 + deltaValue);
break;
case PosEnum.Right:
//Debug.Log(" wholeScrnCanvas.rect.width / 2 " +
//    wholeScrnCanvas.rect.width / 2 +
//    " deltaValue " + deltaValue +
//    " GetActualSize().x " + GetActualSize().x);
xPos = (int)(wholeScrnCanvas.rect.width / 2 -
deltaValue - GetActualSize().x / 2f);
startPosY = 15;
break;
}

curPosY = startPosY - 50;

setLocation(xPos, curPosY);
}

public Vector2 GetActualSize()
{
//Debug.Log(" SelfRect.rect.width " +
//    SelfRect.rect.width +
//    " SelfRect.transform.localScale.x " +
//    SelfRect.transform.localScale.x);

//selfRect.rect.width Is the width of its own localScale.x when it is 1
float actualWidth = SelfRect.rect.width * SelfRect.transform.localScale.x;

//selfRect.rect.height Is the height when its localScale.y is 1
float actualHeight = SelfRect.rect.height * SelfRect.transform.localScale.y;

return new Vector2(actualWidth, actualHeight);

}

/// <summary>
///Because the width and height of the default item are certain,
///Too small on higher resolution phones
///It will be too large on a lower resolution phone
///For this purpose, the standard is to display a moderate resolution
///Mobile phones that are too big or too small are adapted to this standard
/// </summary>
/// <param name="refResolutionWidth"></param>
/// <param name="refResolutionHeight"></param>
{
int width = Screen.currentResolution.width,
height = Screen.currentResolution.height;

#if UNITY_EDITOR
//When GameView is Free Aspect, the width and height obtained by this method is 0
#endif

float screenWidthRatio = (width * 1f) / (refResolutionWidth * 1f);
float screenHeightRatio = (height * 1f) / (refResolutionHeight * 1f);

//    " transform.localScale.x " + transform.localScale.x +
//    " transform.localScale.y " + transform.localScale.y +
//     " screenWidthRatio " + screenWidthRatio +
//    " screenHeightRatio " + screenHeightRatio);

if (transform.localScale.x != screenWidthRatio ||
transform.localScale.y != screenHeightRatio)
{
float newScaleX = screenWidthRatio;
float newScaleY = screenHeightRatio;

transform.localScale = new Vector3(newScaleX, newScaleY, transform.localScale.z);
}
}

/// <summary>
///Update transparency
/// </summary>
/// <param name="value"></param>
protected virtual void updateAlpha(float value)
{
}

public bool IsEnd
{
get
{
return Time.realtimeSinceStartup - startShowProcessTime > totalShowTime;
}
}

public void SetSizeAndTime(int size, float totalShowTime, Font font)
{
StrText.font = font;
StrText.fontSize = size;
this.totalShowTime = totalShowTime;
completelyFnshTime = this.totalShowTime - alphaMinusDur;
}

}

```
```using System.Collections;
using System.Collections.Generic;
using UnityEngine;

{
public static OperateAlertConst Instance { get => instance;  }

private void Awake()
{
instance = this;
}

/// <summary>
///Get the resolution of Game View
/// </summary>
/// <param name="width"></param>
public void GetGameViewSize(out int width, out int height)
{

#if UNITY_EDITOR
System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor");
System.Reflection.MethodInfo GetMainGameView = T.GetMethod("GetMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
System.Object Res = GetMainGameView.Invoke(null, null);
var gameView = (UnityEditor.EditorWindow)Res;
var prop = gameView.GetType().GetProperty("currentGameViewSize", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var gvsize = prop.GetValue(gameView, new object { });
var gvSizeType = gvsize.GetType();
height = (int)gvSizeType.GetProperty("height", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(gvsize, new object { });
width = (int)gvSizeType.GetProperty("width", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(gvsize, new object { });
//Debug.Log("current height:" + height);
//Debug.Log("current width:" + width);
#endif
}

/// <summary>
///Obtain the ratio of the width of the current screen resolution to the width of the standard screen resolution
/// </summary>
public float CurScreenWidthRefScreenWidthRatio
{
get
{
int width = Screen.currentResolution.width;
int height = Screen.currentResolution.height;

#if UNITY_EDITOR
//Because when the windows editor Screen.currentResolution return
//It's the resolution of the desktop, not the resolution of the game view
//So extra processing is needed for developer testing
GetGameViewSize(out width, out height);
#endif
float screenWidthRatio = (width * 1f) / (refResolutionWidth * 1f);

return screenWidthRatio;
}
}

/// <summary>
///Obtain the ratio of the height of the current screen resolution to the height of the standard screen resolution
/// </summary>
public float CurScreenHeightRefScreenHeightRatio
{
get
{
int width = Screen.currentResolution.width;
int height = Screen.currentResolution.height;

#if UNITY_EDITOR
//Because when the windows editor Screen.currentResolution return
//It's the resolution of the desktop, not the resolution of the game view
//So extra processing is needed for developer testing
GetGameViewSize(out width, out height);
#endif
float screenHeightRatio = (height * 1f) / (refResolutionHeight * 1f);

return screenHeightRatio;
}
}

public int refResolutionWidth = 1136;

public int refResolutionHeight = 640;

public int oneRowCharCount = 10;
}

```

Finally, the last script that enables one UI to frame another UI was developed from the idea of starting in the process. Later, I think there are other ways to replace it. But the experiment has been successful, so record it. Originally, this is used to make the background picture frame the text. Note that the UI of the frame cannot be the parent object of the UI of the framed.

```using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

{
RectTransform imgRect;

public RectTransform ImgRect { get {
if (imgRect == null)
{
imgRect = GetComponent<RectTransform>();
}
return imgRect;
}
}

public void ResetRect(RectTransform textRect)
{
//Width when x of textRect's localScale is 1
//So to get the actual width, you have to multiply it by x of its localScale
float actualWidth = textRect.rect.width * textRect.localScale.x;

//Height when y of textRect's localScale is 1
//So to get the actual height, you have to multiply it by y of its localScale
float actualHeight = textRect.rect.height * textRect.localScale.y;

//Actual width is the actual width of textRect
//SetSizeWithCurrentAnchors sets the width of imgRect's localScale when x is 1
//With imgRect holding the current localScale
//If you want the width of imgRect to be equal to the actual width
//The value set by SetSizeWithCurrentAnchors also needs to be divided by
//localScale.x of imgRect

//For example, the current imgRect's localScale.x is 0.8
//If SetSizeWithCurrentAnchors is passed in the actual width directly
//The width of imgRect is equal to the actual width when the localScale.x is 1
//But now it's 0.8, so the actual width is actual width * 0.8
//So the input of SetSizeWithCurrentAnchors should be
//actualWidth / 0.8
//The width of imgRect at the current scale is equal to the actual width
float toSuitWidth = actualWidth / ImgRect.transform.localScale.x;

//ditto
float toSuitHeight = actualHeight / ImgRect.transform.localScale.y;

ImgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, toSuitWidth);

ImgRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, toSuitHeight);
}
}

```

Last Demo

Topics: Windows Unity Mobile