Métodos de extensión en C# – Aplicación en Unity

Con algo de retraso con respecto a la planificación inicial os presento de nuevo un pequeño artículo sobre otra de las características que hacen de C# un lenguaje de programación muy interesante: Los métodos de extensión (Extension Methods)

En ocasiones nos encontramos con la necesidad de desarrollar una serie de métodos que utilizan objetos de clases ajenas, propias del framework, librería o motor que estemos utilizando en ese momento. A veces incluso sentimos el deseo de tener acceso al código fuente de estas clases para incluir métodos que utilizamos en múltiples ocasiones y que serían más útiles como métodos de la clase que como código auxiliar. Veamos un ejemplo:

public static class MathUtils {

    // Dado un ángulo en grados, devuelve el vector de radio 'radius' 
    // que le corresponde.
    public static Vector2 AngleToVector(float angle, float radius)
    {
        Vector2 ret = Vector2.zero;
        ret.x = Mathf.Cos(angle * Mathf.Deg2Rad) * radius;
        ret.y = Mathf.Sin(angle * Mathf.Deg2Rad) * radius;
        return ret;
    }

    // Dado un vector, devuelve el ángulo respecto al vector (1, 0)
    public static float VectorToAngle(Vector2 v)
    {
        float angle = Vector2.Angle(new Vector2(1f, 0f), v);
        if (v.y < 0f)
        {
            angle = 360f - angle;
        }
        return angle;
    }
}

Un código bastante sencillo y común para convertir entre ángulos y vectores. Un ejemplo de uso sería:

...
Vector2 direction = enemy.transform.position - player.transform.position;
float angle = MathUtils.VectorToAngle(direction);
...
Vector2 velocity = MathUtils.AngleToVector(angle, 2.0f);
...

Pero… ¿a que sería más cómodo y legible poder hacer lo siguiente?

Vector2 direction = enemy.transform.position - player.transform.position;
float angle = direction.ToAngle();
...
Vector2 velocity = angle.ToVector(2.0f);

Pues precisamente eso es lo que nos permiten los métodos de extensiones: ampliar la funcionalidad de clases sin necesidad de tener acceso al código fuente o recompilar.

Para cambiar el ejemplo anterior para que use métodos de extensiones tendríamos que cambiar la clase estática MathUtils del ejemplo anterior de la siguiente forma:

public static class MyExtensions {

    // Dado un ángulo en grados, devuelve el vector de radio 'radius' 
    // que le corresponde.
    public static Vector2 ToVector(this float angle, float radius=1.0f)
    {
        Vector2 ret = Vector2.zero;
        ret.x = Mathf.Cos(angle * Mathf.Deg2Rad) * radius;
        ret.y = Mathf.Sin(angle * Mathf.Deg2Rad) * radius;
        return ret;
    }

    // Dado un vector, devuelve el ángulo respecto al vector (1, 0)
    public static float ToAngle(this Vector2 v)
    {
        float angle = Vector2.Angle(new Vector2(1f, 0f), v);
        if (v.y < 0f)
        {
            angle = 360f - angle;
        }
        return angle;
    }
}

Como se puede ver las diferencias son mínimas:

  1. La clase contenedora sigue siendo estática, pero el nombre de la misma carece de importancia ya que luego no se utilizará en el código, por lo que no es necesario (pero si conveniente) que sea coherente con los métodos que contiene.
  2. Los métodos siguen siendo estáticos, pero el primer parámetro cuenta con el modificador this que indica la clase que estamos extendiendo. En el ejemplo extendemos Vector2 y float.
  3. El parámetro this corresponde a la instancia de la clase con la que se llame al método de extensión.

Hay que tener en cuenta que no es posible sobreescribir métodos ya existentes en las clases objetivo. Por ejemplo, no sería posible escribir el siguiente método de extensión:

public static class MyExtensions {

    // Dado un ángulo en grados, devuelve el vector de radio 'radius' 
    // que le corresponde.
    public static Vector2 Normalize(this Vector2 v){
        // Normalizamos el vector...
    }
}

Ante el anterior código, el compilador no nos avisaría con ningún warning ni mensaje de error, pero a la hora de utilizar el método se usaría el original de la clase.

Por último, algunos ejemplos de uso para esta técnica:

  • Extensión a Transform que aplique un efecto de agitado (shake). Dado que todos los GameObject de Unity tienen este componente, esto significa que podríamos usar el mismo método en todos los objetos de la escena (¡incluida la cámara!)
  • Extensiones matemáticas a Vectores similares a las que hemos visto en el artículo.
  • Extender la clase Camara con métodos que devuelvan el tamaño del mundo, la pantalla, puntos fuera de la pantalla, su centro…

Y hasta aquí llega el articulo de hoy. Por supuesto esta es una explicación muy superficial de los métodos de extensiones, así que si queréis profundizar os recomiendo que consultéis la documentación enlazada al principio del artículo.

2 thoughts on “Métodos de extensión en C# – Aplicación en Unity

  1. David

    hola, estoy haciendo algo en lo que necesito unos dados y los lanzo por dinamica y ya me muestra los valores cada dado (2) pero tengo un problemita, que ahora nesecito, con otra clase que se encuentra en el GameObject Tablero, aceder al valor de cada dado para tener una variable que seria la suma de los dados, y aplicar esta al movimiento de las fichas, pero no me deja crear un objeto de esta clase “Dado” que es donde se calcula el valor, aqui pongo un ejemplo de como lo estoy intentando hacer :

    sc_dado dado = new sc_dado();

    aqui creo una variable de tipo sc_dado que es la clase dado en la que se calcula el numero del dado para luego desde la clase tablero poder acceder de esta forma

    dado.getNumDado(); //(que es un metodo que se encuentra en la clase “sc_dado”)

    y el problema es que unity me dice que no puedo crear esta instancia de la clase usando “new”
    le agradecira mucho que puediera ayudarme
    gracias de antemano.

    Reply
    1. David Erosa Post author

      Hola David.

      Sin ver más código es difícil de decir, pero si Unity te dice que no puedes crear una instancia usando “new” seguramente sea porque tu clase hereda de MonoBehaviour y estas no se pueden instanciar de esa forma. Tendrías que o bien crear un GameObject y añadirle la clase sc_dado para poder usarlo o hacer que tu clase sc_dado no herede de MonoBehavior.

      Ya me cuentas, ¡suerte!

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *