No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Verificar y evitar caramelos repetidos

13/31
Recursos

Vamos a solucionar el problema de la clase anterior.

Primero, debemos asignar un identificador diferente para cada elemento que generamos en la grilla. Además, debemos asegurarnos de que no hay un mismo tipo de dulce repetido en cualquier dirección.

Para esto ultimo, en realidad, solo debemos verificar el tipo de dulce que generamos con las posiciones de abajo y a la izquierda, de esta forma nos aseguramos de que no habrá dulces repetidos en ninguna dirección.

Recuerda que la primera columna de la grilla no tiene vecinos a la izquierda y, de igual forma, los primeros elementos de cada columna no tienen vecinos hacia abajo. De no tener esto en cuenta podemos generar algunos errores en nuestro algoritmo.

Aportes 12

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Para quien haya seguido mis pasos en los ultimos videos, les advierto aqui que ahora tuve que hacer mas cambios por preferencias personales, y que si siguieron lo que les dije antes, muchas cosas van a estar fuera de lugar, si aun asi quieren seguir con mis consejos:

Le quite el BoxCollider2D al background y se lo a;adi al objeto que sera el caramelo per se, cuando hice esto ya no se podia calcular el offset, asi que en vez de acceder al componente BoxCollider, accedo al GetComponent<SpriteRenderer>() que tienen una propiedad que es bounds y esa tiene sus propiedades vectoriales.

Como mis caramelos son prefabs que contienen la animacion y el box collider, y lo que existe en el juego son instancias de los caramelos, no puedo volverlos hijos de un objeto porque corromperia el estado del prefab, asi que decidi que no me molesta tanto que esten los candies en la jerarquia lateral, sin embargo los fondos si los hice hijos del board manager.

Mi codigo:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoardManager : MonoBehaviour
{
    public static BoardManager sharedInstance;
    public List<GameObject> prefabs = new List<GameObject>();
    public GameObject backgroundGO;
    public int xSize, ySize;

    private GameObject[,] candies;

    int idx = -1;

    [SerializeField]
    private float paddingX = 0.4f, paddingY = 0.4f;

    public bool isShifting { get; set; }

    private void Start()
    {
        if(sharedInstance == null)
        {
            sharedInstance = this;
        }
        else
        {
            Destroy(this.gameObject);
        }

        Vector2 offset = new Vector2(backgroundGO.GetComponent<SpriteRenderer>().bounds.size.x + paddingX, backgroundGO.GetComponent<SpriteRenderer>().bounds.size.y + paddingY);
        CreateInitialBoard(offset);
    }

    private void CreateInitialBoard(Vector2 offSet)
    {
        candies = new GameObject[xSize, ySize];

        float startX = this.transform.position.x;
        float startY = this.transform.position.y;

        for(int x = 0; x < xSize; x++)
        {
            for(int y = 0; y < ySize; y++)
            {
                GameObject candyBackground = Instantiate(backgroundGO, new Vector3(startX + (offSet.x * x), startY + (offSet.y * y), 0), backgroundGO.transform.rotation);
                candyBackground.name = string.Format($"Candy[{x}][{y}]");

                candyBackground.transform.parent = this.transform;

                do
                {
                    idx = Random.Range(0, prefabs.Count);
                }while((x > 0 && idx == candies[x-1,y].GetComponent<Candy>().Id) || (y > 0 && idx == candies[x,y-1].GetComponent<Candy>().Id));

                GameObject candy = prefabs[idx];
                candy.GetComponent<Candy>().Id = idx;
                Instantiate(candy, candyBackground.transform.position, candyBackground.transform.rotation);

                candies[x, y] = candy;
            }
        }
    }

    private void Update()
    {
        
    }
}

Condiciones del ciclo do-white para que los dulces no se repitan a la izquierda o hacía abajo:

do {
  // ... Random.Range ...
} while (
  (x > 0 && idx == candies[x-1, y].GetComponent<Candy>().id) ||
  (y > 0 && idx == candies[x, y-1].GetComponent<Candy().id>
)

Podemos configurar nuestros dulces para que todos sean hijos del Game Object invisible y podamos tener una vista más cómoda en Unity cuando colapsamos el elemento con el script del Board Manager:

newCandy.transform.parent = this.transform;

Analizando la lógica que se utiliza para evitar un tres en raya, note que también evita el caso en el que sean solo dos caramelos en raya, cosa que si puede ocurrir en juegos de este tipo, cambie el while por el siguiente y este si permite ese caso.

< while((x>1 && idx == candies[x-2,y].GetComponent<Candy>().id) || (y>1 && idx == candies[x,y-2].GetComponent<Candy>().id)); >

Yo agregué otra función para que los Candys tengan un espacio de color oscuro de fondo cada uno y haya un espacio entre ellos que llamé padding y se puede definir en el inspector.

    private void CreateBoard()
    {
        spaceCandies = new GameObject[xSize, ySize];

        PlayerPrefs.SetInt("xSize", xSize);
        PlayerPrefs.SetInt("ySize", ySize);

        float startX = this.transform.position.x;
        float startY = this.transform.position.y;

        for(int x = 0; x < xSize; x++)
        {
            for(int y = 0; y < ySize; y++)
            {
                // Instanciar y posicionar SPACE CANDY
                Vector3 posCandy = VerificatePos(startX, startY, x, y);

                GameObject newCandy = Instantiate(spaceCandy, posCandy, spaceCandy.transform.rotation);

                // Aplicar nombre "Candy x=0 y=1"
                newCandy.name = string.Format("Space[{0}][{1}]", x, y);

                // Agregar al array de SPACES
                spaceCandies[x, y] = newCandy;
            }
        }

        CreatorCandies();
    }
    private void CreatorCandies()
    {
        candies = new GameObject[xSize, ySize];

        float startX = this.transform.position.x;
        float startY = this.transform.position.y;

        for (int x = 0; x < xSize; x++)
        {
            for (int y = 0; y < ySize; y++)
            {
                // Instanciar y posicionar SPACE CANDY
                Vector3 posCandy = VerificatePos(startX, startY, x, y);

                GameObject newCandy = Instantiate(currentCandy, posCandy, currentCandy.transform.rotation);

                // Reajusta el Sprite del objeto instanciado
                Sprite sprite = prefabs[Random.Range(0, prefabs.Count)];
                newCandy.GetComponent<SpriteRenderer>().sprite = sprite;

                // Aplica ID y NOMBRE en base al Candy
                Candy[] totalCandies = FindObjectsOfType<Candy>(); // TEST 
                newCandy.GetComponent<Candy>().id = totalCandies.Length + 1;
                newCandy.name = string.Format("Candy[{0}]", newCandy.GetComponent<Candy>().id);

                candies[x, y] = newCandy;
            }
        }
    }
    private Vector3 VerificatePos(float startX, float startY, int x, int y)
    {
        Vector3 posCandy;

        if (x == 0 && y == 0)
            posCandy = new Vector3(startX + (offset.x * x), startY + (offset.y * y), 0);
        else
            posCandy = new Vector3(startX + (offset.x + padding) * x, startY + (offset.y + padding) * y, 0);

        return posCandy;
    }

No sé como adjuntar imágenes y por eso no lo muestro xd. La función VerificatePos se encarga de que se aplique el padding entre cada espacio excepto que el espacio actual sea el (0, 0)

Modifiqué un poco el bucle con la condición, ya que prefiero que el código sea un poco más claro:
 
Sugerencias, bienvenidas 😃

bool iterateAgain;

do
{
  int leftCandyId = -1;
  int downCandyId = -1;
  prefabIndex = Random.Range(0, prefabs.Count);

  if (x > 0)
  {
    leftCandyId = candies[x - 1, y].GetComponent<Candy>().id;
  }

  if (y > 0)
  {
    downCandyId = candies[x, y - 1].GetComponent<Candy>().id;
  }

  iterateAgain = prefabIndex == leftCandyId || prefabIndex == downCandyId;
} while (iterateAgain);

Tengo un problema no aparecen los dulces en pantalla y desaparece el boardmanager cuando corro el juego…

Lo que entendi es que Si el dulce de la izquierda o abajo coincide se va a volver a llamar al “Random.Range(0, prefabs.Count);” para que ponga otro dulce aleatorio?

Genial

OK. entonces generamos un bucle que esta garantizado a ejecutarse al menos una vez que lo que hace es generar un numero aleatorio siempre y cuando el identificador del caramelo que esta a nuestra izquierda y abajo no coincida con el identificador del caramelo que estamos generando.
.
Ahora este numero aleatorio generado lo usaremos para elegir el caramelo correspondiente a la hora de crear la matriz con los caramelos asegurándonos que el juego no tenga caramelos repetidos uno cerca del otro.

Saludos, tengo

Sprite sprite = prefabs[Random.Range(0, prefabs.Count)];
newCandy.GetComponent<SpriteRenderer>().sprite = sprite;
newCandy.GetComponent<Candy>().id = -1;

El problema es que solo me aparece un solo candy Random en vez de llenarse la pantalla

Una sugerencia, para mejorar la lectura del código he movido la condición del do - while a una función separada:

bool NeighborIsSameCandy(int x, int y) {
  return (x > 0 && idx == candies[x-1, y].GetComponent<Candy>().id) 
    || (y > 0 && idx == candies[x, y - 1].GetComponent<Candy>().id);
}

Y así mi do - while se lee de este modo:

do 
{
   idx = Random.Range(0, prefabs.Count);
} while (NeighborIsSameCandy(x, y));