🎮 UnityDocs

Arma con munición y recarga

Un arma completa: cadencia de disparo, munición en el cargador y reserva, recarga con tiempo, y disparo por raycast o proyectil. Con eventos para la UI y buenas prácticas de input.

🍳 RecetaIntermedio✅ RevisadoUnity 6.0 LTSActualizado 2026-07-15
DificultadConceptual Código Matemáticas
Tiempo📖 Lectura 7 min · ⌨️ Práctica 25 min

El objetivo#

Un arma con cadencia (no dispara infinitamente rápido), cargador limitado, recarga que tarda un tiempo, y munición de reserva. Con eventos para que la UI muestre "24 / 90".

La receta#

C#
using System;
using System.Collections;
using UnityEngine;

public class Arma : MonoBehaviour
{
    [Header("Munición")]
    [SerializeField] private int capacidadCargador = 30;
    [SerializeField] private int municionReserva = 90;

    [Header("Disparo")]
    [SerializeField] private float cadencia = 0.1f;   // segundos entre disparos
    [SerializeField] private int daño = 20;
    [SerializeField] private float alcance = 100f;
    [SerializeField] private Camera camara;

    [Header("Recarga")]
    [SerializeField] private float tiempoRecarga = 1.5f;

    private int enCargador;
    private float proximoDisparo;
    private bool recargando;

    // La UI escucha esto: (cargador, reserva)
    public event Action<int, int> AlCambiarMunicion;

    void Awake()
    {
        enCargador = capacidadCargador;
        AvisarUI();
    }

    void Update()
    {
        // Disparo automático mientras se mantiene el botón
        if (Input.GetButton("Fire1") && Time.time >= proximoDisparo)
            Disparar();

        if (Input.GetKeyDown(KeyCode.R))
            StartCoroutine(Recargar());
    }

    void Disparar()
    {
        if (recargando) return;
        if (enCargador <= 0) { StartCoroutine(Recargar()); return; }   // recarga sola al vaciarse

        proximoDisparo = Time.time + cadencia;
        enCargador--;
        AvisarUI();

        // Disparo hitscan por raycast (ver artículo de raycasting)
        Ray rayo = new Ray(camara.transform.position, camara.transform.forward);
        if (Physics.Raycast(rayo, out RaycastHit hit, alcance))
        {
            if (hit.collider.TryGetComponent<IDañable>(out var objetivo))
                objetivo.RecibirDaño(daño);
        }
    }

    IEnumerator Recargar()
    {
        if (recargando || enCargador == capacidadCargador || municionReserva <= 0) yield break;

        recargando = true;
        yield return new WaitForSeconds(tiempoRecarga);

        int necesita = capacidadCargador - enCargador;
        int aRecargar = Mathf.Min(necesita, municionReserva);   // solo lo que hay en reserva
        enCargador += aRecargar;
        municionReserva -= aRecargar;

        recargando = false;
        AvisarUI();
    }

    void AvisarUI() => AlCambiarMunicion?.Invoke(enCargador, municionReserva);
}

Por qué así#

  • Time.time >= proximoDisparo: la cadencia sin corrutinas. Cada disparo fija el siguiente instante permitido. Simple y preciso.
  • La recarga es una corrutina: tarda tiempoRecarga sin congelar el juego, y bloquea disparar mientras dura.
  • Mathf.Min(necesita, reserva): solo recargas lo que cabe y lo que te queda en reserva; nada de munición mágica.
  • IDañable: reutiliza el sistema de vida y daño — el arma no sabe a qué dispara.
Munición realista: no rellenes de más

El error clásico es sumar capacidadCargador a la reserva sin mirar. Lo correcto: recargas solo lo que falta en el cargador y solo si lo tienes en reserva. Por eso el Mathf.Min. Así 5 balas en reserva no llenan un cargador de 30.

Variantes#

  • Proyectil en vez de raycast: instancia una bala (mejor con object pooling si disparas mucho).
  • Disparo semiautomático: usa Input.GetButtonDown en vez de GetButton (un disparo por clic).
  • Dispersión (spread): añade un pequeño ángulo aleatorio a la dirección del rayo.
  • Retroceso y feedback: screen shake, fogonazo, sonido, casquillo.

Errores frecuentes#

  • Recargar sumando el cargador completo sin descontar de la reserva.
  • Permitir disparar durante la recarga (bloquéalo con la bandera recargando).
  • Cadencia con corrutinas cuando Time.time es más simple y preciso.
  • No avisar a la UI por evento y acabar consultando la munición cada frame.
  • Leer el disparo en FixedUpdate (va en Update).

Resumen#

Controla la cadencia con Time.time >= proximoDisparo, gestiona cargador y reserva por separado (recarga solo lo que falta y tienes, con Mathf.Min), haz la recarga con una corrutina que bloquea el disparo, y avisa a la UI por evento. El daño reutiliza IDañable del sistema de vida. Amplíalo con proyectiles, dispersión y feedback.

Fuentes y para profundizar