一般来讲,在回合制和RPG游戏中,不可避免的需要在人物头上显示被攻击的伤害值,或则是被治疗时候的治疗值。一般就是一些跟随人物Label就可以了,加上一些出现的小动画就可以了。
一、效果元素组
//动画的一个状态元素 using UnityEngine; public class PopElement { public float posx = 0.0f;//起始位置 public float posy = 0.0f; public float detailTime = 0.0f;//这个状态的总共时间 public float transferX = 0.0f;//移动的偏移位置 public float transferY = 0.0f; public float scale = 1.0f;//缩放 public float startAlpha = 1.0f;//开始的透明度 public float alpha = 1.0f;//结束的透明度 float totalTime = 0f; public PopElement(Vector2 pos, float time, Vector2 transfer, float scale, float startalpha, float alpha) { this.posx = pos.x; this.posy = pos.y; this.detailTime = time; this.transferX = transfer.x; this.transferY = transfer.y; this.scale = scale; this.startAlpha = startalpha; this.alpha = alpha; Init(); } public void Init() { totalTime = detailTime; } public void Update(float time) { totalTime -= time; } public bool isEnd() { return totalTime <= 0; } }
//一个效果将由很多个动画状态组成 using System.Collections.Generic; public class PopEffect { public List<PopElement> elementList = new List<PopElement>(); }
//具体的一个效果 using UnityEngine; public class PopEffectHurt : PopEffect { public PopEffectHurt() { elementList.Add(new PopElement(new Vector2(0f, 0f), 0.15f, new Vector2(0f, 1.99f), 1.05f, 0.3f, 1f));//0.15秒 从透明到显示 放大一点 向上移动1.99 elementList.Add(new PopElement(new Vector2(0f, 0f), 0.15f, new Vector2(0f, 1.99f), 0.95f, 1f, 1f));//0.15秒 往上走 缩小一点 elementList.Add(new PopElement(new Vector2(0f, 0f), 0.8f, new Vector2(0f, 0f), 1.0f, 1f, 1f));//停留不动0.8秒 elementList.Add(new PopElement(new Vector2(0f, 0f), 0.1f, new Vector2(0, -0.5f), 0.95f, 1f, 0.3f));//0.1秒 往下走并隐藏 } }
二、那个Damage物体,一般是一个Label或Text
//一个PDamage代表一个伤害Label的实例 using UnityEngine; using UnityEngine.UI; public class PDamage { public GameObject go = null;//实例化出来的物体 public Text lable = null;//显示数值的Label,在go下的 public PopEffect effect = null;//使用的动画效果 public void Destory() { GameObject.Destroy(go); go = null; } public void Clear() { effect = null; go.SetActive(false); } }
//将会绑定到上面的go上的,这个是基类,一些移动缩放偏移等的基本实现 using UnityEngine; using UnityEngine.UI; public class UIHeadElement : MonoBehaviour { protected GameObject attachedObject;//联系上的物体,3D人物 protected Vector3 worldPos = Vector3.zero; protected Vector3 attachedOffset = Vector3.zero;//偏移 private bool isTransfer = false;//是否计算移动 protected float transX = 0.0f;//总的偏移量 protected float transY = 0.0f; private float shiftPosX = 0.0f;//当前的偏移移动量 private float shiftPosY = 0.0f; private float startPosX = 0.0f;//起始位置 private float startPosY = 0.0f; protected float transScale = 1.0f;//缩放因子 private float ClipFar = 100; //远剪裁面的距离,用来计算大小,实现越远数值大小也越小 Vector3 curPos = Vector3.zero; Transform trans; public Text chatLable; protected void Awake() { trans = gameObject.transform; GameObject uiRt = GameObject.Find("Canvas"); trans.parent = uiRt.transform; InitHead(); } void InitHead() { worldPos = Vector3.zero; shiftPosX = 0f; shiftPosY = 0f; trans.localPosition = Vector3.zero; trans.localRotation = Quaternion.identity; if (attachedObject != null) { trans.localScale = Vector3.one * (1 - CameraManager.Instance.CalObjDepthFromCamera( attachedObject.transform.position.z ) / ClipFar); } trans.position = new Vector3(10000.0f,10000.0f,0.0f); //先移到看不到的地方 } protected void Update () { if(attachedObject == null) return; CalcBoxPos(); ShowBoxPos(); } protected void Tick (float time) { if(attachedObject == null) return; CalcBoxPos(time); ShowBoxPos(); } void CalcBoxPos(float time) { worldPos = attachedObject.transform.position + attachedOffset; if (isTransfer) { //计算动画的移动 和 缩放 shiftPosX = shiftPosX + transX * time; shiftPosY = shiftPosY + transY * time; worldPos.x += shiftPosX; worldPos.y += shiftPosY; worldPos.x += startPosX; worldPos.y += startPosY; trans.localScale *= transScale;//直接乘于缩放因子 } worldPos = CameraManager.Instance.PosFromMainCamToUICam(worldPos); worldPos.z = trans.position.z; } void CalcBoxPos() { worldPos = attachedObject.transform.position + attachedOffset; if(isTransfer) { shiftPosX = shiftPosX + transX * Time.deltaTime; shiftPosY = shiftPosY + transY * Time.deltaTime; worldPos.x += shiftPosX; worldPos.y += shiftPosY; worldPos.x += startPosX; worldPos.y += startPosY; trans.localScale *= transScale; } worldPos = CameraManager.Instance.PosFromMainCamToUICam(worldPos); worldPos.z = trans.position.z; } void ShowBoxPos() { if(curPos == worldPos) return; trans.position = worldPos; curPos = worldPos; } public void AttachObject(GameObject obj, Vector3 offset) { attachedObject = obj; attachedOffset = offset; InitHead(); } public void SetTransfer(bool isTrans) { isTransfer = isTrans; } public void setTransPos(float x,float y) { transX = x; transY = y; } public void setStartPos(float x,float y) { startPosX = x; startPosY = y; } public void setScale(float scale) { transScale = scale; } public void SetClipFar(float far) { ClipFar = far; } }
//继承UIHeadElement,使用PopEffect来设置动画状态,并控制每个Element的使用 using UnityEngine; public delegate void DestoryCB(PDamage pDamaga); public class UIDamagaElement :UIHeadElement { enum PopState { PopState_None, PopState_Ready, PopState_Loop, PopState_Finish, } DestoryCB destoryCB = null; PopState state = PopState.PopState_None; int curIndex= 0; float alpha = 0f; PDamage pDamaga; void Awake() { base.Awake(); } public void Init(PDamage damaga,DestoryCB destoryCB) { pDamaga = damaga; if(!this.gameObject.activeSelf) { this.gameObject.SetActive(true); } this.destoryCB = destoryCB; } void Update() { Tick(Time.deltaTime); } public void Tick(float time) { switch(state) { case PopState.PopState_None: curIndex = 0; state = PopState.PopState_Ready; this.SetTransfer(true); break; case PopState.PopState_Ready: { //初始化时间计数,设置好所有属性 pDamaga.effect.elementList[curIndex].Init(); if(!pDamaga.go.activeSelf) { pDamaga.go.SetActive(true); } //获取初始alpha值 alpha = pDamaga.effect.elementList[curIndex].startAlpha; //设置位移量和缩放比例 this.setStartPos(pDamaga.effect.elementList[curIndex].posx,pDamaga.effect.elementList[curIndex].posy); this.setTransPos(pDamaga.effect.elementList[curIndex].transferX,pDamaga.effect.elementList[curIndex].transferY); this.setScale(pDamaga.effect.elementList[curIndex].scale); //设置字当前的alpha pDamaga.lable.color = new Color(pDamaga.lable.color.r, pDamaga.lable.color.g, pDamaga.lable.color.b, alpha); state = PopState.PopState_Loop; break; } case PopState.PopState_Loop: { //记录重置的 pDamaga.effect.elementList[curIndex].Update(time); base.Tick(time); //设置字的alpha值 alpha += (pDamaga.effect.elementList[curIndex].alpha / (pDamaga.effect.elementList[curIndex].detailTime / time)); pDamaga.lable.color = new Color(pDamaga.lable.color.r, pDamaga.lable.color.g, pDamaga.lable.color.b, alpha); //判断是否结束 if (pDamaga.effect.elementList[curIndex].isEnd()) { curIndex ++; if(curIndex < pDamaga.effect.elementList.Count) state = PopState.PopState_Ready; //循环播放 else state = PopState.PopState_Finish; //停止播放 } break; } case PopState.PopState_Finish: { this.AttachObject(null, Vector3.zero); this.SetTransfer(false); Clear(); state = PopState.PopState_None; break; } default: break; } } void Clear() { if(destoryCB != null) { destoryCB(pDamaga); } curIndex= 0; alpha = 0f; } }
//提供两个相机之间 物体位置的转换,和距离的计算等 using UnityEngine; public class CameraManager : MonoBehaviour { private Camera MainCamera; private Camera UICamera; public static CameraManager Instance; void Awake () { MainCamera = GameObject.FindWithTag("MainCamera").GetComponent<Camera>(); UICamera = GameObject.FindWithTag("UICamera").GetComponent<Camera>(); Instance = this; } public Vector3 PosFromMainCamToUICam(Vector3 position) { if (MainCamera == null || UICamera == null) return Vector3.zero; Vector3 pos = MainCamera.WorldToViewportPoint(position); return UICamera.ViewportToWorldPoint(pos); } public float CalObjDepthFromCamera(float z) { if (MainCamera == null) return 0; return z - MainCamera.transform.position.z; } }
//伤害弹弹弹的管理器,负责初始化PDamage实例,缓存并使用PDamage //为某一个3D人物,添加伤害显示 using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PopDamageManager : MonoBehaviour { public static PopDamageManager Instance; private Queue<PDamage> damagePool = new Queue<PDamage>(); private List<PDamage> activeDamage = new List<PDamage>(); public GameObject DamagePrefab; public Vector3 DefaultOffset = new Vector3(0, 0.6f, 0); void Awake () { Instance = this; } //初始化数量到对象池中,一般知道人物的多少,这些数量是可以预估出来的 public void Init(int damageCount) { if (DamagePrefab == null) return; for (int i = 0; i < damageCount; i++) { PDamage pDamage = new PDamage(); pDamage.go = Instantiate(DamagePrefab) as GameObject; pDamage.lable = pDamage.go.GetComponentInChildren<Text>(); pDamage.go.AddComponent<UIDamagaElement>(); damagePool.Enqueue(pDamage); pDamage.Clear(); } } //为go人物,添加一个伤害显示,指定好动画效果 public void AttachDamageToGameObject(GameObject go, PopEffect effect, string text) { if (damagePool.Count == 0) return; PDamage pDamage = damagePool.Dequeue(); pDamage.effect = effect; pDamage.lable.text = text; UIDamagaElement element = pDamage.go.GetComponent<UIDamagaElement>(); if (element != null) { element.AttachObject(go, DefaultOffset); element.Init(pDamage, RecoverDamage); } activeDamage.Add(pDamage); } //动画播放完成后就 隐藏回收到对象池 void RecoverDamage(PDamage node) { activeDamage.Remove(node); node.Clear(); damagePool.Enqueue(node); } public void Destory() { for (int i = 0; i < activeDamage.Count; i++) { damagePool.Enqueue(activeDamage[i]); } activeDamage.Clear(); PDamage node = null; while (damagePool.Count != 0 && (node = damagePool.Dequeue()) != null) { node.Destory(); } } }
三、测试
using UnityEngine; public class TestPop : MonoBehaviour { public GameObject Target; void Start() { PopDamageManager.Instance.Init(10); } private void Update() { if (Input.GetMouseButtonDown(0)) { int val = Random.Range(100, 1000); PopDamageManager.Instance.AttachDamageToGameObject(Target, new PopEffectHurt(), "-" + val); } if (Input.GetMouseButtonDown(1)) { PopDamageManager.Instance.Destory(); } } }
四、扩展
可以加上字体或则是字体颜色等,继承PopEffect创建更多的动画效果,达到暴击加血等等的不同效果。