Part of these game systems:
Damage Popup
Floating damage numbers that animate upward and fade out. Supports critical hits, healing, and custom colors.
How to Use
1
Call DamagePopup.Create(position, damageAmount) from your combat script
2
For critical hits: DamagePopup.Create(pos, dmg, isCritical: true)
3
For healing: DamagePopup.Create(pos, amount, isHeal: true)
4
Numbers float up and fade automatically
5
Requires TextMeshPro package (included with Unity by default)
Source Code
C#
using UnityEngine;
using TMPro;
/// <summary>
/// Floating damage number popup. Spawns at a world position,
/// floats upward, and fades out over its lifetime.
/// </summary>
public class DamagePopup : MonoBehaviour
{
[Header("Animation")]
[SerializeField] private float floatSpeed = 1.5f;
[SerializeField] private float lifetime = 0.8f;
[SerializeField] private float fadeStartPercent = 0.5f;
[Header("Scaling")]
[SerializeField] private float normalScale = 1f;
[SerializeField] private float critScale = 1.4f;
[Header("Colors")]
[SerializeField] private Color damageColor = Color.white;
[SerializeField] private Color critColor = new Color(1f, 0.85f, 0f);
[SerializeField] private Color healColor = new Color(0.3f, 1f, 0.5f);
private TextMeshPro textMesh;
private float timer;
private Color startColor;
private Vector3 randomOffset;
private bool isCrit;
private void Awake()
{
textMesh = GetComponent<TextMeshPro>();
if (textMesh == null)
textMesh = gameObject.AddComponent<TextMeshPro>();
textMesh.alignment = TextAlignmentOptions.Center;
textMesh.fontSize = 6f;
textMesh.sortingOrder = 100;
}
/// <summary>
/// Create a damage popup at the given world position.
/// </summary>
public static DamagePopup Create(Vector3 position, float amount, bool isCritical = false, bool isHeal = false)
{
GameObject go = new GameObject("DamagePopup");
go.transform.position = position + new Vector3(
Random.Range(-0.3f, 0.3f),
Random.Range(0f, 0.2f),
0f
);
DamagePopup popup = go.AddComponent<DamagePopup>();
popup.Setup(amount, isCritical, isHeal);
return popup;
}
private void Setup(float amount, bool isCritical, bool isHeal)
{
if (textMesh == null)
{
textMesh = GetComponent<TextMeshPro>();
if (textMesh == null)
textMesh = gameObject.AddComponent<TextMeshPro>();
}
string prefix = isHeal ? "+" : "";
textMesh.text = prefix + Mathf.RoundToInt(Mathf.Abs(amount)).ToString();
if (isHeal)
startColor = healColor;
else if (isCritical)
startColor = critColor;
else
startColor = damageColor;
textMesh.color = startColor;
isCrit = isCritical;
float scale = isCritical ? critScale : normalScale;
transform.localScale = Vector3.one * scale;
randomOffset = new Vector3(Random.Range(-0.3f, 0.3f), 0f, 0f);
}
private void Update()
{
timer += Time.deltaTime;
// Float upward
transform.position += (Vector3.up * floatSpeed + randomOffset * 0.5f) * Time.deltaTime;
// Fade out
float percent = timer / lifetime;
if (percent > fadeStartPercent)
{
float fadePercent = (percent - fadeStartPercent) / (1f - fadeStartPercent);
Color c = startColor;
c.a = Mathf.Lerp(1f, 0f, fadePercent);
textMesh.color = c;
}
// Scale down slightly
float scalePercent = 1f - (percent * 0.2f);
transform.localScale = Vector3.one * scalePercent * (isCrit ? critScale : normalScale);
if (timer >= lifetime)
Destroy(gameObject);
}
// Face camera (billboard)
private void LateUpdate()
{
if (Camera.main != null)
transform.forward = Camera.main.transform.forward;
}
}