NGUI丰富的文字特效拓展

本文介绍了一种通过监听UILabel的onPostFill事件来实现实时文本动态效果的方法。具体包括字符抖动、波浪形文字、旋转及快乐波浪等效果,并提供了完整的Unity脚本示例。

原理就是监听UILabel.onPostFill事件,然后修改里面的verts等属性

mLabel = GetComponent<UILabel>();
mLabel.onPostFill += OnPostFillHandle;
void OnPostFillHandle(UIWidget widget, int bufferOffset, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols){
		int max = mEffects.Count;
		for (int i = 0; i < max; i++) {
			var effectEntry = mEffects [i];
			int start = effectEntry.index * 4;
			int end = start + effectEntry.text.Length * 4;

			if (start > verts.size)
				continue;
			if (end > verts.size)
				continue;
			ApplyEffects (verts, uvs, cols, start, end, effectEntry);
		}
	}

	void ResetEffectValue(){
		wordTempValue.Clear ();
	}

	void ApplyEffects(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols, int start, int end,EffectEntry effectEntry){
		for (int i = start; i < end; ++i) {
			Vector3 vert = verts [i];
			Color32 color = cols [i];
			byte alpha = color.a;

			string key = effectEntry.key;
			if (key.StartsWith ("{!")) {
				vert.x += angryStrength * (Random.insideUnitCircle * angryJitterStrength).x * (i % 2 == 0 ? 1 : -1);
				vert.y += angryStrength * (Random.insideUnitCircle * angryJitterStrength).y * (i % 2 == 0 ? 1 : -1);
				color = angryColor;
			} else if (key.StartsWith ("{~")) {
				int wordIndex = (int)((float)i / 4f) - start;
				float wordInterval = wordIndex * waveGap;
				float angle = GetWordVar (i, wordInterval) + waveSpeed;
				vert.y += Mathf.Sin (angle) * waveDistance;
				color = waveColor;
				SetWordVar (i, angle);
			}
			else if(key.StartsWith ("{@")){
				float angle = GetWordVar (i) + waveSpeed;
				vert.x += Mathf.Cos(angle) * angryStrength - Mathf.Sin(angle) * angryStrength;
				vert.y += Mathf.Cos(angle) * angryStrength + Mathf.Sin(angle) * angryStrength;
				color = waveColor;
				SetWordVar (i, angle);
			}
			else if(key.StartsWith ("{#")){
				float angle = GetWordVar (i) + happySpeed;
				vert.y += Mathf.Sin (angle) * happyDistance;
				color = happyColor;
				SetWordVar (i, angle);
			}
			color.a = alpha;
			verts [i] = vert;
			cols [i] = color;
		}
	}
效果预览:

完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;

[RequireComponent(typeof(UILabel))]
public class SmartUILabel : MonoBehaviour {
	public struct EffectEntry
	{
		public int id;
		public int index;
		public string key;
		public string text;
	}
	public struct SplitEntry
	{
		public int id;
		public int index;
		public string left;
		public string right;
		public string total;
		public string value;
	}

	/// <summary>
	/// How many characters will be printed per second.
	/// </summary>
	public int charsPerSecond = 20;
	public bool keepFullDimensions = true;
	public float angryStrength = 2f;
	public float angryJitterStrength = 2f;
	public Color angryColor = Color.red;
	public float waveGap = 0.5f;
	public float waveSpeed = 0.2f;
	public float waveDistance = 5f;
	public Color waveColor=Color.yellow;
	public float happyDistance = 5f;
	public Color happyColor=Color.yellow;
	public float happySpeed = 0.25f;
	public UnityEngine.Events.UnityEvent onFinished;

	UILabel mLabel;
	string mFullText = "";
	int mCurrentOffset = 0;
	float mNextChar = 0f;
	bool mReset = true;
	bool mActive = false;
	int mTempId=0;
	List<EffectEntry> mEffects=new List<EffectEntry>();
	List<SplitEntry> mSplitEntries=new List<SplitEntry>();

	public bool IsActive { get { return mActive; } }
	void OnEnable () { mReset = true; mActive = true; }
	void OnDisable () { Finish(); }

	public void Replay(){
		mReset = true;
		mActive = true;
		mNextChar = 0f;
		mCurrentOffset = 0;
		Update();
	}

	public void Finish ()
	{
		if (mActive)
		{
			mActive = false;

			if (!mReset)
			{
				mCurrentOffset = mFullText.Length;
				mLabel.text = mFullText;
			}

			onFinished.Invoke ();
		}
	}

	void Update () {
		if (mActive) {
			if (mReset)
			{
				mReset = false;
				Reset ();
			}

			if (string.IsNullOrEmpty(mFullText)) return;

			while (mCurrentOffset < mFullText.Length && mNextChar <= RealTime.time) {
				int lastOffset = mCurrentOffset;

				char lastChar = mFullText [lastOffset];

				++mCurrentOffset;

				if (mCurrentOffset > mFullText.Length) break;

				char currentChar = (mCurrentOffset <= mFullText.Length - 1) ? mFullText [mCurrentOffset] : '\0';

				float delay = 1f / charsPerSecond;
				if (mNextChar == 0f)
				{
					mNextChar = RealTime.time + delay;
				}
				else mNextChar += delay;

				if (lastChar=='{' && (currentChar == '<' || currentChar=='>')) {
					int keyEndIndex = mFullText.IndexOf ('}', mCurrentOffset);
					if (keyEndIndex != -1) {
						string realKey = mFullText.Substring (lastOffset, keyEndIndex - lastOffset + 1);
						mFullText = mFullText.Remove (lastOffset, keyEndIndex - lastOffset + 1);
						mCurrentOffset -= 1;
						bool isSpeedUp = realKey.IndexOf (">>") != -1;

						Regex reg = new Regex (@"[{}><\s]");
						string k = reg.Replace (realKey,"");
						float realValue = 0f;
						float.TryParse (k, out realValue);
						mNextChar += realValue * (isSpeedUp ? 1f : -1f);
					}
				}

				if (mCurrentOffset >= mFullText.Length) {
					mLabel.text = mFullText;
					onFinished.Invoke ();
					mActive = false;
				} else {
					mLabel.text = keepFullDimensions ?
						mFullText.Substring (0, mCurrentOffset) + "[00]" + mFullText.Substring (mCurrentOffset) :
						mFullText.Substring (0, mCurrentOffset);
				}
			}
		}

		if (HasEffect) {
			mLabel.MarkAsChanged ();
		}
	}

	void SplitValues(){
		SplitValue ("!");
		SplitValue ("~");
		SplitValue ("@");
		SplitValue ("#");

		foreach (var oldEffect in mEffects) {
			Debug.Log ("Key=" + oldEffect.key + "  value:" + oldEffect.text + "  Index:" + oldEffect.index);
		}
	}

	void SplitValue(string matchKey){
		string content=mFullText;
		string splitRegex = "({" + matchKey + @"})(\S*?)({/" + matchKey + "})";

		string newContent = content;

		mSplitEntries.Clear ();
		foreach (Match match in Regex.Matches(content, splitRegex)) {
			string key = matchKey;
			string text = match.Groups [2].Value;
			int index = match.Index;

			string total = match.Value;
			string left = match.Groups [1].Value;
			string right = match.Groups [3].Value;

			SplitEntry splitEntry = new SplitEntry ();
			splitEntry.id = ++mTempId;
			splitEntry.index = index;
			splitEntry.left = left;
			splitEntry.right = right;
			splitEntry.value = text;
			splitEntry.total = total;
			mSplitEntries.Add (splitEntry);
		}

		for (int i = 0; i < mSplitEntries.Count; i++) {
			SplitEntry splitEntry = mSplitEntries [i];

			string total = splitEntry.total;
			string left = splitEntry.left;
			string right = splitEntry.right;
			int rightIndex = total.IndexOf (right);
			total = total.Remove (rightIndex, right.Length);
			int leftIndex = total.IndexOf (left);
			total = total.Remove (leftIndex, left.Length);

			int newRightIndex = splitEntry.index + rightIndex;
			newContent = newContent.Remove (newRightIndex, right.Length);
			if (newRightIndex < splitEntry.index) {
				splitEntry.index -= right.Length;
			}

			int newLeftIndex = splitEntry.index + leftIndex;
			newContent = newContent.Remove (newLeftIndex, left.Length);
			if (newLeftIndex < splitEntry.index ) {
				splitEntry.index  -= left.Length;
			}

			for (int j = 0; j < mSplitEntries.Count; j++) {
				var newSplitEntry = mSplitEntries [j];
				if (newSplitEntry.id.Equals(splitEntry.id))
					continue;
				if (newRightIndex < newSplitEntry.index) {
					newSplitEntry.index -= right.Length;
				}
				if (newLeftIndex < newSplitEntry.index ) {
					newSplitEntry.index  -= left.Length;
				}
				mSplitEntries [j] = newSplitEntry;
			}

			for (int j = 0; j < mEffects.Count; j++) {
				var newEffectEntry = mEffects [j];
				if (newEffectEntry.id.Equals(splitEntry.id))
					continue;
				if (newRightIndex < newEffectEntry.index) {
					newEffectEntry.index -= right.Length;
				}
				if (newLeftIndex < newEffectEntry.index ) {
					newEffectEntry.index  -= left.Length;
				}
				mEffects [j] = newEffectEntry;
			}

			EffectEntry effectEntry = new EffectEntry ();
			effectEntry.id = splitEntry.id;
			effectEntry.index = splitEntry.index;
			effectEntry.key = splitEntry.left;
			effectEntry.text = splitEntry.value;
			mEffects.Add (effectEntry);

			mSplitEntries [i] = splitEntry;
		}

		mFullText = newContent;
	}

	void Reset(){
		if (mLabel != null) {
			mLabel.onPostFill -= OnPostFillHandle;
		}
		ResetEffectValue ();
		mEffects.Clear ();
		mSplitEntries.Clear ();
		mCurrentOffset = 0;
		mTempId = 0;
		mLabel = GetComponent<UILabel>();
		mLabel.onPostFill += OnPostFillHandle;
		mFullText = mLabel.processedText;

		SplitValues ();
	}

	void OnPostFillHandle(UIWidget widget, int bufferOffset, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols){
		int max = mEffects.Count;
		for (int i = 0; i < max; i++) {
			var effectEntry = mEffects [i];
			int start = effectEntry.index * 4;
			int end = start + effectEntry.text.Length * 4;

			if (start > verts.size)
				continue;
			if (end > verts.size)
				continue;
			ApplyEffects (verts, uvs, cols, start, end, effectEntry);
		}
	}

	void ResetEffectValue(){
		wordTempValue.Clear ();
	}

	void ApplyEffects(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols, int start, int end,EffectEntry effectEntry){
		for (int i = start; i < end; ++i) {
			Vector3 vert = verts [i];
			Color32 color = cols [i];
			byte alpha = color.a;

			string key = effectEntry.key;
			if (key.StartsWith ("{!")) {
				vert.x += angryStrength * (Random.insideUnitCircle * angryJitterStrength).x * (i % 2 == 0 ? 1 : -1);
				vert.y += angryStrength * (Random.insideUnitCircle * angryJitterStrength).y * (i % 2 == 0 ? 1 : -1);
				color = angryColor;
			} else if (key.StartsWith ("{~")) {
				int wordIndex = (int)((float)i / 4f) - start;
				float wordInterval = wordIndex * waveGap;
				float angle = GetWordVar (i, wordInterval) + waveSpeed;
				vert.y += Mathf.Sin (angle) * waveDistance;
				color = waveColor;
				SetWordVar (i, angle);
			}
			else if(key.StartsWith ("{@")){
				float angle = GetWordVar (i) + waveSpeed;
				vert.x += Mathf.Cos(angle) * angryStrength - Mathf.Sin(angle) * angryStrength;
				vert.y += Mathf.Cos(angle) * angryStrength + Mathf.Sin(angle) * angryStrength;
				color = waveColor;
				SetWordVar (i, angle);
			}
			else if(key.StartsWith ("{#")){
				float angle = GetWordVar (i) + happySpeed;
				vert.y += Mathf.Sin (angle) * happyDistance;
				color = happyColor;
				SetWordVar (i, angle);
			}
			color.a = alpha;
			verts [i] = vert;
			cols [i] = color;
		}
	}

	Dictionary<int,float> wordTempValue=new Dictionary<int, float>();

	float GetWordVar(int index){
		return GetWordVar (index, 0f);
	}
	float GetWordVar(int index,float defaultValue){
		if (!wordTempValue.ContainsKey (index)) {
			wordTempValue.Add (index, defaultValue);
		}
		return wordTempValue [index];
	}

	void SetWordVar(int index,float value){
		if (!wordTempValue.ContainsKey (index)) {
			wordTempValue.Add (index, 0f);
		}
		wordTempValue [index] = value;
	}

	bool HasEffect{
		get{ 
			return mEffects.Count > 0;
		}
	}
}

Unity3D中下载并使用NGUI插件来创建背包系统,可以按照以下步骤操作: 1. **创建新项目**: 创建一个新的Unity项目,选择合适的游戏平台。 2. **安装NGUI**: **引用[1]**提到,UGUI是Unity官方的新UI系统,但如果你仍然想使用NGUI,可以在Unity Asset Store搜索"NGUI"。点击下载并导入最版本的NGUI包(如3.9.8),记得选择正版授权以支持开发者社区。 ```markdown - 搜索 "NGUI" - 选中合适的版本并点击 "Import Package" 导入到项目中 ``` 3. **准备资源**: 准备好用于展示背包物品的图片素材,这些图片可能需要打包成图集以便于管理。你可以使用NGUI的工具来进行这一步骤。 ```markdown - 在Project视图中右键新建 "Image Atlas" 或者 "Sprite Sheet" - 将图片拖放到相应的图像集合中 ``` 4. **创建背包组件**: 使用NGUI提供的控件,如Panel、Button等,创建背包的基本布局。可以通过脚本来控制显示哪些物品以及其数量。 ```csharp // 示例代码片段 public class Backpack : MonoBehaviour { private List<GameObject> items; // ... 其他初始化和更新逻辑 } ``` 5. **添加背包逻辑**: 编写背包类,处理添加/移除物品、显示数量等功能。使用Unity的事件系统或C#脚本控制这些行为。 ```csharp void AddItem(GameObject item) { items.Add(item); // 更新UI显示 } void RemoveItem(GameObject item) { items.Remove(item); // 更新UI显示 } ``` 完成以上步骤后,你就有了一个基本的使用NGUI构建的背包系统。随着对Unity UI系统的熟悉,未来可能会逐渐转向UGUI或其他官方推荐的解决方案。不过,由于题目的重点在于NGUI的使用,所以这里主要展示了与NGUI相关的配置和代码示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼蛋-Felix

如果对你有用,可以请我喝杯可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值