Ngui Text Vertical Scrollbar?

I want to create a scroll bar text field in NGUI where I can put as much text in a certain area but be able to scroll down with a vertical bar to see all of it. I haven’t been able to find a NGUI tutorial that seems to do this. Also I just got Ngui pro can someone recommend some good starting tutorials? I checked out the 2 video tutorials they don’t seem to cover things like I’m asking right now.

Yeah, it turned out to be a bit of a pain in the ass to do this. I’m sure there are better ways to do it, but here’s my solution (it scrolls a parent object with anything in it, it can be text or other stuff). Should be pretty easy to use and self-explanatory:

using UnityEngine;
using System.Collections;

//=============================================================================
// Manages the scrolling of the contents inside a clipped panel
// Just put all objects to be scrolled inside a common parent, and then setup
// the panel, the scrollbar, and the common parent object in the inspector.
//
// Make sure this script does not sit on the scrollbar gameobject itself, since
// we will be disabling it when the scrollbar is not needed.
//=============================================================================
public class UIPanelScroller : MonoBehaviour {
	
	public UIScrollBar scrollBar;
	public UIPanel     clippingPanel;
	public Transform   panelContentsParent;
	
	public float checkContentsInterval = 0.1f;
	public bool  alwaysShowScroller = false;
	
	private float contentsZeroY;
	
	//========================================================================
	void Start () {
		this.scrollBar.onChange += this.OnScrollbarChange;
		
		this.scrollBar.scrollValue = 0;
		this.contentsZeroY = this.panelContentsParent.localPosition.y;
		
		this.UpdateStuff();
		this.StartCoroutine(this.CheckContentsChanges());
	}

	//========================================================================
	IEnumerator CheckContentsChanges()
	{
		while (this.enabled) {
			yield return new WaitForSeconds(this.checkContentsInterval);
			this.UpdateStuff();
		}
	}	

	//========================================================================
	private float _getContentsHeight() {
		float relative = NGUIMath.CalculateRelativeWidgetBounds(this.panelContentsParent.transform).size.y;
		return relative * this.panelContentsParent.transform.localScale.y;
	}
	
	//========================================================================
	void UpdateStuff()
	{
		float panelH   = this.clippingPanel.clipRange.w;
		float contentH = this._getContentsHeight();
		this.scrollBar.barSize = panelH / contentH;
		
		this.scrollBar.gameObject.SetActive(true);
		if (contentH < panelH && !this.alwaysShowScroller) {
			this.scrollBar.gameObject.SetActive(false);
		}
	}
	
	//========================================================================
	void OnScrollbarChange(UIScrollBar ignored)
	{
		float nonVisibleHeight = this._getContentsHeight() - this.clippingPanel.clipRange.w + this.clippingPanel.clipSoftness.y;
		float contentPos = this.contentsZeroY + (nonVisibleHeight * this.scrollBar.scrollValue);
		this.panelContentsParent.transform.SetLocalPosY(contentPos);
	}
	
}

SetLocalPosY is a simple extension method I use to avoid having to make a copy of stuff everytime I want to change a single value:

public static void SetLocalPosZ(this Transform trans, float z) {
	Vector3 vector = trans.localPosition;
	vector.z = z;
	trans.localPosition = vector;
}

I had some issues with the clipping panel refusing to show it’s contents at startup, because everything was cached up in NGUI, so I had to make this hack to shake things up:

public class UIPanelClipRefreshHack : MonoBehaviour {

	public void GO() {
		foreach(UIWidget widget in this.GetComponentsInChildren<UIWidget>()) {
			widget.enabled = false;
			widget.enabled = true;
		}
	}
}

Drop it on the panel, and call Go when displaying your window.

Phew!

Yeah, it turned out to be a bit of a pain in the ass to do this. I’m sure there are better ways to do it, but here’s my solution (it scrolls a parent object with anything in it, it can be text or other stuff). Should be pretty easy to use and self-explanatory:

using UnityEngine;
using System.Collections;

//=============================================================================
// Manages the scrolling of the contents inside a clipped panel
// Just put all objects to be scrolled inside a common parent, and then setup
// the panel, the scrollbar, and the common parent object in the inspector.
//
// Make sure this script does not sit on the scrollbar gameobject itself, since
// we will be disabling it when the scrollbar is not needed.
//=============================================================================
public class UIPanelScroller : MonoBehaviour {
	
	public UIScrollBar scrollBar;
	public UIPanel     clippingPanel;
	public Transform   panelContentsParent;
	
	public float checkContentsInterval = 0.1f;
	public bool  alwaysShowScroller = false;
	
	private float contentsZeroY;
	
	//========================================================================
	void Start () {
		this.scrollBar.onChange += this.OnScrollbarChange;
		
		this.scrollBar.scrollValue = 0;
		this.contentsZeroY = this.panelContentsParent.localPosition.y;
		
		this.UpdateStuff();
		this.StartCoroutine(this.CheckContentsChanges());
	}

	//========================================================================
	IEnumerator CheckContentsChanges()
	{
		while (this.enabled) {
			yield return new WaitForSeconds(this.checkContentsInterval);
			this.UpdateStuff();
		}
	}	

	//========================================================================
	private float _getContentsHeight() {
		float relative = NGUIMath.CalculateRelativeWidgetBounds(this.panelContentsParent.transform).size.y;
		return relative * this.panelContentsParent.transform.localScale.y;
	}
	
	//========================================================================
	void UpdateStuff()
	{
		float panelH   = this.clippingPanel.clipRange.w;
		float contentH = this._getContentsHeight();
		this.scrollBar.barSize = panelH / contentH;
		
		this.scrollBar.gameObject.SetActive(true);
		if (contentH < panelH && !this.alwaysShowScroller) {
			this.scrollBar.gameObject.SetActive(false);
		}
	}
	
	//========================================================================
	void OnScrollbarChange(UIScrollBar ignored)
	{
		float nonVisibleHeight = this._getContentsHeight() - this.clippingPanel.clipRange.w + this.clippingPanel.clipSoftness.y;
		float contentPos = this.contentsZeroY + (nonVisibleHeight * this.scrollBar.scrollValue);
		this.panelContentsParent.transform.SetLocalPosY(contentPos);
	}
	
}

SetLocalPosY is a simple extension method I use to avoid having to make a copy of stuff everytime I want to change a single value:

public static void SetLocalPosZ(this Transform trans, float z) {
	Vector3 vector = trans.localPosition;
	vector.z = z;
	trans.localPosition = vector;
}

I had some issues with the clipping panel refusing to show it’s contents at startup, because everything was cached up in NGUI, so I had to make this hack to shake things up:

public class UIPanelClipRefreshHack : MonoBehaviour {

	public void GO() {
		foreach(UIWidget widget in this.GetComponentsInChildren<UIWidget>()) {
			widget.enabled = false;
			widget.enabled = true;
		}
	}
}

Drop it on the panel, and call Go when displaying your window.

Phew!