Dibujar sobre una textura


En el juego quiero que las líneas del suelo del campo de futbol (circulo central, áreas, etc...) puedan irse borrando al paso de los jugadores.

Como soy tan listo se me ha ocurrido que seguro que no hay otra forma de hacerlo que "pintar" por código sobre la textura del terreno de juego.

En este video de otro autor se explica bien como pintar sobre una textura: 

Unity - Draw the Texture for a Material at Runtime

Basándome en ello he realizado mi propia implementación para pintar el círculo central y un semicírculo de una área:

using UnityEngine;
 
 public class TerrainDrawer : MonoBehaviour
 {
     private Texture2D paintTexture;
 
     public void Start()
     {
         CloneTextureTerrain();
         DrawCircleCenter();
         DrawCircleArcArea();
     }
 
     private void CloneTextureTerrain()
     {
         Texture2D srcTexture = (Texture2D)GetComponent<renderer>().material.GetTexture("_MainTex");
         paintTexture = new Texture2D(srcTexture.width, srcTexture.height);
         Graphics.CopyTexture(srcTexture, paintTexture);
 
         GetComponent<renderer>().material.SetTexture("_MainTex", paintTexture);
     }
 
     private void DrawCircleCenter()
     {
         for (float angle = 0; angle < 2 * Mathf.PI; angle += 0.01f) // Ángulo en radianes de 0 a 2*PI para obtener una circunferencia entera
         {
             Vector2 point = UtilsLines.GetCirclePoint(256, 256, 50, angle);
             DrawPointLine(paintTexture, point);
         }
         paintTexture.Apply();
     }
 
     private void DrawCircleArcArea()
     {
         for (float angle = 35; angle < 145; angle += 0.01f) // Aquí usamos ángulos en grados
         {
             Vector2 point = UtilsLines.GetCirclePoint(256, 80, 50, Mathf.Deg2Rad * angle); // El ángulo hay que convertirlo a radianes
             DrawPointLine(paintTexture, point);
         }
         paintTexture.Apply();
     }
 
     private void DrawPointLine(Texture2D texture, Vector2 point)
     {
         texture.SetPixels32((int)point.x, (int)point.y, 1, 1, new Color32[] { Color.white});
         texture.SetPixels32((int)point.x + 1, (int)point.y, 1, 1, new Color32[] { Color.white });
         texture.SetPixels32((int)point.x + 2, (int)point.y, 1, 1, new Color32[] { Color.white });
         texture.SetPixels32((int)point.x, (int)point.y + 1, 1, 1, new Color32[] { Color.white });
         texture.SetPixels32((int)point.x, (int)point.y + 2, 1, 1, new Color32[] { Color.white });
         texture.SetPixels32((int)point.x+1, (int)point.y+1, 1, 1, new Color32[] { Color.white });
         texture.SetPixels32((int)point.x + 2, (int)point.y + 2, 1, 1, new Color32[] { Color.white });
     }
 }

En el ejemplo la textura de origen debe ser marcada como readable (Read/Write) desde los assets del proyecto.

Esto permite que la textura se cargue en la memoria principal de la CPU y no en la memoria de la tarjeta gráfica o GPU, por lo que se habilita la textura para poder ser leída y modificada por el programa.

También ha sido necesario marcarla como formato RGBA32.

Notar en el código como es necesario empezar copiando la textura, pues si se modifica la original esta queda ya modificada aunque se reinicie el juego (aunque no afecta al fichero de la imagen en disco, y por lo tanto si se da el caso se puede volver a demarcar el check de Read/Write y aplicar para que Unity vuelva a reconstruir la textura a partir de la imagen original. Todo esto se evita haciendo al principio una copia de la textura original, y trabajar solo pintando sobre dicha copia.

En el código se ve como la función Apply() de la textura es la que se encarga de enviar los cambios de la textura hacia la GPU. Es necesario minimizar estas acciones, por lo que aquí se hacen solo en el inicio. En tiempo de juego habrá que hacerlo solo si realmente un jugador o algo modifica las líneas del suelo.


Por otra parte, la función UtilsLines.GetCirclePoint(...) se define como una simple función matemática que devuelve un punto de una circunferencia, de la siguiente forma:

    public static Vector2 GetCirclePoint(float xCenter, float yCenter, float radius, float angleProgression)     
    {
         Vector2 point = new Vector2();
         point.x = Mathf.Cos(angleProgression) * radius + xCenter;
         point.y = Mathf.Sin(angleProgression) * radius + yCenter;
         return point;
    }

Dado un centro (x e y) de la circunferencia, un radio, y un determinado ángulo (en radianes), la función devuelve el punto correspondiente a esa circunferencia en dicho ángulo. Basta con progresar el ángulo desde 0 hasta 2*PI radianes, para que devuelva todos los puntos y se pueda pintar una circunferencia entera.

 

El resultado de esto son líneas pintadas en el suelo, por lo que pueden ser alteradas cuando pasen los jugadores…

La distorsión visible es porque la textura se deforma para ajustarse al plano del terreno de juego, pero esto se arreglará cuando se utilicen los terrenos y texturas creados a partir de las medidas oficiales.

Leave a comment

Log in with itch.io to leave a comment.