Hello guys,
another day, another dev journal entry. I’m Emiliano, lead dev of H&R, and today I am gonna disclose and explain our new tooltip system. As they say, sharing is caring, and tooltips are something that I’ve seen many time being requested in groups and forums.
So here we go, with our contribution for the community. This tooltip system has the following features :
- Multi string/multi line
- Changes to fit text (Configurable padding options)
- Behavior and offsets proportional independently from the screen resolution/size
- Never goes out from the screen
This tooltip is designed to be used above or below the mouse pointer by default. With not much work it can be modified to work on the sides of the pointer according to specific needs.
A tooltip example with pivot-driven offset of (0.5,-0.5)
In order to maintain the tooltip efficient independently from the screen resolution and size, its offset is regulated by the pivot point. For example a pivot of (0.5,-0.5) will position the tooltip above the mouse pointer, a pivot of (0.5,1.5) will position the tooltip below the pointer (in both cases the offset will be equal to half the width on screen of the tooltip itself).
If the tooltip tries to go out of screen horizontally, it will remain on screen despite the movement of the pointer. Should it try to go our of screen vertically, it will first reduce the offset distance (until 25% of the default one), then if the pointer moves further towards the edge of the screen, it will swap position and appear below the pointer (if the pointer approaches the upper edge) or above the pointer (if the pointer approaches the lower edge).
Tooltip position swapped due to upper edge
Horizontal and upper bounded tooltip
The structure of the tooltip file is the following :
BackgroundImage
*EmptyElement (ContentSizeFitter+HorizontalLayoutFitter) *Text
The text size determines the size of the EmptyElement container and of the Horizontal Layout Fitter, and the latter RectTransform is used to determine the size (plus some padding) of the background image.
Part of the code is written in order to bypass a specific design choice (imo more a flaw than a feature) of the Unity3d HorizontalLayoutGroup. The component indeed, only applies ad the end of the frame, and in order to have it properly resized, it’s therefore necessary to wait for one Update call to have been accomplished. In order to avoid the flickering caused by resized tooltips (e.g. the previous tooltip shown was smaller than the one we want to currently show and therefore for one frame we would see the flicker of the older size), I initially hide (color.alpha = 0) both the text and the background image, in order to show them beginning from the next frame already configured in the correct size.
By using the Pivot as a offset source, and the manually calculated scale factor on x and y, the whole system keeps on working in the same correct way in any scale of screen.
Call SetTooltip(“Text”); to activate the tooltip and HideTooltip(); to deactivate it.
By calling the method this way the tooltip will display the elements of the array each as a new line:
SetTooltip(new string[]{“Line1″,”Line2″,”Line3”});
Multi-line tooltip example (3 lines)
And here we go with a little video example of how the tooltip works, followed by the code of the script. Hopefully you should have now the full information on how to re-implement it, if any problem arises, please write me at info [at] hammerandravens [dot] com and I will do my best to help you!
In the while of course, you can subscribe to our newsletter (a huge tutorial on pointer management system is coming soon, both text and video) and follow our youtube channel and our twitter and facebook pages.
Cheers and enjoy!
Emiliano, @DokHgn
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class MainMapTooltip : MonoBehaviour {
//manually selectable padding for the background image
public int horizontalPadding;
public int verticalPadding;
//tooltip text
public Text thisText;
//horizontal layout of the tooltip
public HorizontalLayoutGroup hlG;
//tooltip background image
public RectTransform bgImage;
Image bgImageSource;
//needed as the layout refreshes only on the first Update() call
bool firstUpdate;
//if the tooltip is inside a UI element
bool inside;
//size of the tooltip, needed to track if out of screen
// public float width;
// public float height;
//detect canvas mode so to apply different behaviors to different canvas modes, currently only RenderMode.ScreenSpaceCamera implemented
int canvasMode;
RenderMode GUIMode;
//the scene GUI camera
Camera GUICamera;
//the default tooltip object has the following pivots, so that the offset from the mouse is always proportional to the screen resolution (the y pivot)
//Pivot(0.5,-0.5)
//screen viewport corners for out of screen detection
Vector3 lowerLeft;
Vector3 upperRight;
//scale factor of proportionality to the reference resolution (1280x720)
float currentYScaleFactor;
float currentXScaleFactor;
//standard X and Y offsets of the new tooltip
float defaultYOffset;
float defaultXOffset;
//real on screen sizes of the tooltip object
float tooltipRealHeight;
float tooltipRealWidth;
// Use this for initialization
void Start () {
//in this line you need to change the string in order to get your Camera //TODO MAYBE DO IT FROM THE INSPECTOR
GUICamera = GameObject.Find("GUICamera").GetComponent<Camera>();
GUIMode = this.transform.parent.parent.GetComponent<Canvas>().renderMode;
bgImageSource=bgImage.GetComponent<Image>();
//at start the pointer is never to be considered over and UI element
inside=false;
//assign the tooltip to the singleton GUI class manager for fast access
TacticalGUIManager.tgm.mmttp = this;
//hide the tooltip
HideTooltipVisibility();
this.transform.parent.gameObject.SetActive(false);
}
//single string input tooltip
public void SetTooltip(string text){
NewTooltip();
//init tooltip string
thisText.text=text;
//call the position function
OnScreenSpaceCamera();
}
//multi string/line input tooltip (each string of the input array is a new line)
public void SetTooltip(string[] texts){
NewTooltip();
//build up the tooltip line after line with the input
string tooltipText = "";
int index = 0;
foreach(string newLine in texts){
if(index==0){
tooltipText+=newLine;
}else{
tooltipText+=("\n"+newLine);
}
index++;
}
//init tooltip string
thisText.text=tooltipText;
//call the position function
OnScreenSpaceCamera();
}
//temporary call to don't fuck up old code, will be removed
public void SetTooltip(string text, bool test){
NewTooltip();
//init tooltip string
thisText.text=text;
//call the position function
OnScreenSpaceCamera();
}
//position function, currently not working correctly due to the use of pivots and not manual offsets, soon to be fixed
public void OnScreenSpaceCamera(){
//get the dynamic position of the pous in viewport coordinates
Vector3 newPos = GUICamera.ScreenToViewportPoint(Input.mousePosition);
// store in val the updated position (x or y) of the tooltip edge of interest
float val;
//store the new offset to impose in case of out of screen
float yOffSet = 0f;
float xOffSet = 0f;
//check for right edge of screen
//obtain the x coordinate of the right edge of the tooltip
val = ((GUICamera.ViewportToScreenPoint(newPos).x)+(tooltipRealWidth*bgImage.pivot.x));
//evaluate if the right edge of the tooltip goes out of screen
if(val>(upperRight.x)){
float distFromRight = upperRight.x-val;
xOffSet=distFromRight;
//assign the new modified coordinates to the tooltip and convert to screen coordinates
Vector3 newTooltipPos = new Vector3(GUICamera.ViewportToScreenPoint(newPos).x+xOffSet,0f,0f);
newPos.x = GUICamera.ScreenToViewportPoint(newTooltipPos).x;
}
//check for left edge of screen
//obtain the x coordinate of the left edge of the tooltip
val = ((GUICamera.ViewportToScreenPoint(newPos).x)-(tooltipRealWidth*bgImage.pivot.x));
//evaluate if the left edge of the tooltip goes out of screen
if(val<(lowerLeft.x)){
float distFromLeft = lowerLeft.x-val;
xOffSet=-distFromLeft;
//assign the new modified coordinates to the tooltip and convert to screen coordinates
Vector3 newTooltipPos = new Vector3(GUICamera.ViewportToScreenPoint(newPos).x-xOffSet,0f,0f);
newPos.x = GUICamera.ScreenToViewportPoint(newTooltipPos).x;
}
//check for upper edge of the screen
//obtain the y coordinate of the upper edge of the tooltip
val = ((GUICamera.ViewportToScreenPoint(newPos).y)-((bgImage.sizeDelta.y*currentYScaleFactor*(bgImage.pivot.y))-(tooltipRealHeight)));
//evaluate if the upper edge of the tooltip goes out of screen
if(val>(upperRight.y)){
float distFromUpper = upperRight.y-val;
yOffSet = (bgImage.sizeDelta.y*currentYScaleFactor*(bgImage.pivot.y));
if(distFromUpper>(defaultYOffset*0.75)){
//shorten the temporary offset up to a certain distance from the tooltip
yOffSet=distFromUpper;
}else{
//if the distance becomes too short flip the tooltip to below the pointer (by offset+twice the height of the tooltip)
yOffSet= ((defaultYOffset)-(tooltipRealHeight)*2f);
}
//assign the new modified coordinates to the tooltip and convert to screen coordinates
Vector3 newTooltipPos = new Vector3(newPos.x,GUICamera.ViewportToScreenPoint(newPos).y+yOffSet,0f);
newPos.y = GUICamera.ScreenToViewportPoint(newTooltipPos).y;
}
//check for lower edge of the screen
//obtain the y coordinate of the lower edge of the tooltip
val = ((GUICamera.ViewportToScreenPoint(newPos).y)-((bgImage.sizeDelta.y*currentYScaleFactor*(bgImage.pivot.y))));
//evaluate if the upper edge of the tooltip goes out of screen
if(val<(lowerLeft.y)){
float distFromLower = lowerLeft.y-val;
yOffSet = (bgImage.sizeDelta.y*currentYScaleFactor*(bgImage.pivot.y));
if(distFromLower<(defaultYOffset*0.75-tooltipRealHeight)){
//shorten the temporary offset up to a certain distance from the tooltip
yOffSet=distFromLower;
}else{
//if the distance becomes too short flip the tooltip to above the pointer (by twice the height of the tooltip)
yOffSet=((tooltipRealHeight)*2f);
}
//assign the new modified coordinates to the tooltip and convert to screen coordinates
Vector3 newTooltipPos = new Vector3(newPos.x,GUICamera.ViewportToScreenPoint(newPos).y+yOffSet,0f);
newPos.y = GUICamera.ScreenToViewportPoint(newTooltipPos).y;
}
this.transform.parent.transform.position= new Vector3(GUICamera.ViewportToWorldPoint(newPos).x,GUICamera.ViewportToWorldPoint(newPos).y,0f);
this.transform.parent.gameObject.SetActive(true);
inside=true;
}
//call to hide tooltip when hovering out from the object
public void HideTooltip(){
if(GUIMode==RenderMode.ScreenSpaceCamera){
if(this!=null){
this.transform.parent.gameObject.SetActive(false);
inside=false;
HideTooltipVisibility();
}
}
}
// Update is called once per frame
void Update () {
LayoutInit();
if(inside){
if(GUIMode==RenderMode.ScreenSpaceCamera){
OnScreenSpaceCamera();
}
}
}
//this function is used in order to setup the size of the tooltip by cheating on the HorizontalLayoutBehavior. The resize is done in the first update.
void LayoutInit(){
if(firstUpdate){
firstUpdate=false;
bgImage.sizeDelta=new Vector2(hlG.preferredWidth+horizontalPadding,hlG.preferredHeight+verticalPadding);
defaultYOffset = (bgImage.sizeDelta.y*currentYScaleFactor*(bgImage.pivot.y));
defaultXOffset = (bgImage.sizeDelta.x*currentXScaleFactor*(bgImage.pivot.x));
tooltipRealHeight=bgImage.sizeDelta.y*currentYScaleFactor;
tooltipRealWidth=bgImage.sizeDelta.x*currentXScaleFactor;
ActivateTooltipVisibility();
}
}
//init basic variables on a new tooltip set
void NewTooltip(){
firstUpdate=true;
lowerLeft = GUICamera.ViewportToScreenPoint(new Vector3(0.0f,0.0f,0.0f));
upperRight = GUICamera.ViewportToScreenPoint(new Vector3(1.0f,1.0f,0.0f));
currentYScaleFactor=Screen.height/this.transform.root.GetComponent<CanvasScaler>().referenceResolution.y;
currentXScaleFactor=Screen.width/this.transform.root.GetComponent<CanvasScaler>().referenceResolution.x;
}
//used to visualize the tooltip one update call after it has been built (to avoid flickers)
public void ActivateTooltipVisibility(){
Color textColor = thisText.color;
thisText.color = new Color(textColor.r,textColor.g,textColor.b,1f);
bgImageSource.color = new Color(bgImageSource.color.r,bgImageSource.color.g,bgImageSource.color.b,0.8f);
}
//used to hide the tooltip so that it can be made visible one update call after it has been built (to avoid flickers)
public void HideTooltipVisibility(){
Color textColor = thisText.color;
thisText.color = new Color(textColor.r,textColor.g,textColor.b,0f);
bgImageSource.color = new Color(bgImageSource.color.r,bgImageSource.color.g,bgImageSource.color.b,0f);
}
}