3D世界人物头上的数值显示-如伤害

一般来讲,在回合制和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();
        }
    }
}


GIF.gif


四、扩展

可以加上字体或则是字体颜色等,继承PopEffect创建更多的动画效果,达到暴击加血等等的不同效果。


首页 我的博客
粤ICP备17103704号