Here are a few code samples from my most recent 3D game which you can read about on the Untitled Movement Shooter page.

A simple damage number script which pops the number out when firing:

    void Start()
    {
        // References
        rb = GetComponent<Rigidbody>();
        target = GameObject.FindWithTag("Player");
        hitNumber = GetComponentInChildren<TMP_Text>();

        // Dynamic sizing
        initScale = transform.localScale;

        // Crit coloring
        damageColor = critHit ? critColor : normalColor;

        // Shoots off damage number in a random direction.
        rb.AddForce(new Vector3(randDir(), 1.5f, randDir()), ForceMode.Impulse);

        // Destroy timer
        Destroy(this.gameObject, timeToDestroy);
    }

    void Update()
    {
        // If there's a player in the scene, look at them
        if(target != null)
            transform.LookAt(target.transform);

        // Fade text with countdown of destroy timer.
        if(timeToDestroy > destroyCurTime)
        {
            destroyCurTime += Time.deltaTime;
            hitNumber.color = new Color (damageColor.r, damageColor.g, damageColor.b, (timeToDestroy / destroyCurTime) - 1f);
        }

        // Dynamic sizing
        float distance = (target.transform.position - transform.position).magnitude;
        float size = distance * fixedSize / 12.5f;
        transform.localScale = Vector3.one * size;
    }

    // The scrunkly (color fade)
    public void updateDamageText(float damage, bool crit, float critHitMult)
    {
        // Pass the crit outside of the referenced script (for color).
        critHit = crit;

        // Make sure that the other functions fire first
        Start();
        Update();

        // Update the text.
        hitNumber.text = "-" + System.Math.Round(damage,2).ToString();
    }

I reference that inside of a interface for damageable items!

public interface IDamagable
{
    public void Damage(float damage, bool crit, float critHitMult, string gunName);

    public static void spawnHitNumber(float damage, bool crit, float critHitMult, Vector3 spawnLoc)
    {
        GameObject curHitNumber = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/HitCanvas"), spawnLoc, Quaternion.identity);
        curHitNumber.GetComponent<HitNumberScript>().updateDamageText(damage, crit, critHitMult);
    }
}

Utilizing Unity’s prefab system, I can very easily prototype this out and reference it inside of gun scripts.

Speaking of gun scripts…

[CreateAssetMenu(fileName="Gun", menuName="Weapon/Gun")]

public class GunData : ScriptableObject
{
    public enum GunType {
        Automatic,
        SingleShot,
        Projectile
    }

    [Header("Stats")]
    public string gunName;
    public float damage;
    public float critHitMult;
    public float maxDistance;
    [Space(5)]

    [Header("Weapon Type Data")]
    public GunType weaponType;

    // Automatic
    public int automaticFireRate;

    // Projectile
    public float projectileSpeed;
    public GameObject projectilePrefab;

    // Single shot
    public float singleShotDelay;
    [Space(5)]

    [Header("Weapon Sounds")]
    public List<AudioClip> fireSounds = new List<AudioClip>();
}

I can create scriptable objects in Unity to rapidly prototype new weapon ideas, though any special gimics would have to be coded into the weapons themselves.