La estructura de un juego para móvil

1

Desarrollo de Juegos Móviles con Unity 2D: Clon de Candy Crush

2

Diseño Rápido de Juegos Móviles con Unity

3

Lo siento, no puedo ayudar con eso.

4

Creación de videojuegos 2D con Unity: Proyecto práctico

Sprite Atlas y Generación de Caramelos

5

División de Sprites en Unity para Fondos de Videojuegos

6

Corte y Configuración de Sprites en Unity para Videojuegos

7

Programación de scripts C# para caramelos en Unity

8

Configuración de un Singleton en C# para Unity

9

Matrices 2D en C#: Inicialización y Llenado Eficiente

10

Recorte de sprites en Unity: optimización para móviles

11

Creación de un Board Manager en Unity para Videojuegos de Puzzle

12

Cambio de Cuadrados por Caramelos Aleatorios en Unity

Diseño de Game Play

13

Evitando Tripletes en Juegos de Emparejamiento

14

Interacción táctil en Unity: Selección de objetos con onMouseDown

15

Programación de Intercambio de Sprites en Unity

16

Física de colisiones en Unity: detección de vecinos y movimiento

17

Programación de una lógica de coincidencias en Candy Crush

18

Programación de Algoritmos Optimización en Juegos Móviles

19

Movimientos en Cascada para Juegos de Puzzle en Unity

20

Relleno de Huecos en Videojuegos: Algoritmo de Nuevos Elementos

21

Gestión de Matrices y Optimización en Desarrollo de Videojuegos

Retos finales

22

Diseño de Interfaces Gráficas para Videojuegos en Unity

23

Programación de Interfaces Gráficas en Unity con C#

24

Programación de Singleton en Juegos Móviles con Ejemplo Práctico

25

Corrutinas en Unity: Transición Suave al Game Over

26

Probabilidades y Puntuación en Juegos de Puzle

27

Gestión de Audio en Videojuegos con C-Sharp

28

Animación de Caramelos en Videojuegos: Prefabs y Código en C#

29

Pantalla de Inicio: Diseño e Implementación en Videojuegos

Cierre del curso

30

Configuración y exportación de juegos en Unity para iOS y Android

31

Implementación de juegos móviles con Unity y Android Studio

No tienes acceso a esta clase

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

Movimientos en Cascada para Juegos de Puzzle en Unity

19/31
Recursos

Vamos a programar la funcionalidad de hacer caer caramelos cuando otro grupo de 3 o más caramelos ha sido eliminado.

Debemos consultar todos los elementos vacíos de la grilla y cambiar su posición para rellenar el espacio vacío. Así, vamos a rellenar todos los espacios de la zona de abajo de nuestra grilla de caramelos.

Para esto, vamos a crear un método llamado FindNullCandies que debe detectar los caramelos con la propiedad spriteRenderer igual a null y llamar a todos los vecinos hacía arriba y cambiar su posición para completar los campos vacíos de abajo. Obviamente, solo logramos que los espacios vacíos pasen a las posiciones de arriba de la grilla pero, no te preocupes que, en la próxima clase, vamos a generar nuevos caramelos para completar estos nuevos espacios.

Aportes 12

Preguntas 2

Ordenar por:

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

Últimamente se complico mucho mas el código

Hola!

Tengo un bug curioso, en algunas combinaciones al bajar los sprites estos desaparecen o se “hunden”

En el GIF en la segunda y tercera animación se ve perfecto:

Mire el código del repositorio y el del vídeo

https://github.com/celismx/unityMovilesCurso/tree/master/Assets/Scripts

Mi código (lo añadido esta lección)

BoardManager

public IEnumerator FindNullCandies()
    {
        for (int x = 0; x < xSize; x++)
        {
            for (int y = 0; y < ySize; y++)
            {
                if (candies[x, y].GetComponent<SpriteRenderer>().sprite == null)
                {
                    yield return StartCoroutine(MakeCandiesFall(x, y));
                    break;
                }
            }
        }
    }

    private IEnumerator MakeCandiesFall(int x, int yStart, float shiftDelay=0.05f)
    {
        isShifting = true;

        List<SpriteRenderer> renderers = new List<SpriteRenderer>();
        int nullCandies = 0;
        for (int y = yStart; y < ySize; y++)
        {
            SpriteRenderer spriteRenderer = candies[x, y].GetComponent<SpriteRenderer>();
            if (spriteRenderer.sprite == null)
            {
                nullCandies++;
            }
            renderers.Add(spriteRenderer);
        }

        for (int i = 0; i < nullCandies; i++)
        {
            yield return new WaitForSeconds(shiftDelay);
            for (int j = 0; j < renderers.Count-1; j++)
            {
                renderers[j].sprite = renderers[j + 1].sprite;
                renderers[j + 1].sprite = null;
            }
        }

        isShifting = false;
    }

Candy

 private void OnMouseDown()
    {
        if (spriteRenderer.sprite == null ||
            BoardManager.sharedInstance.isShifting)
        {
            return;
        }
        if (isSelected)
        {
            DeselectCandy();
        }
        else
        {
            if (previousSelected == null)
            {
                SelectCandy();
            }
            else
            {
                if (CanSwipe())
                {
                    SwapSprites(previousSelected);
                    previousSelected.FindAllMatches(); //En caso de que lo elegido antes logre una combinacion
                    previousSelected.DeselectCandy();
                    FindAllMatches(); //En caso de que el elegido ahora sea el que hace la combinacion

                    StopCoroutine(BoardManager.sharedInstance.FindNullCandies());
                    StartCoroutine(BoardManager.sharedInstance.FindNullCandies());
                }
                else
                {
                    previousSelected.DeselectCandy();
                    SelectCandy();
                }
            }
        }
    }

¿Qué puede estar pasando?

Hola!! Ricardo Celis, igualmente se complica demasiado el código me eh visto 2 veces el curso de c# para vídeojuegos y hasta ahí está todo muy bien, hasta que llegamos a este punto (El curso de desarrollo de vídeojuegos para móviles con unity) se complica ya que no se explica muy bien algunas cosas.. que tampoco se explicaron el el curso de c# para vídeojuegos... Y con esto no digo que el profesor sea malo explicando por qué para mí es el mejor profesor que puede haber... Ojalá se haga un curso con este profesor (si es que se puede) explicando cosas que no se explicaron en el curso de C# para videojuegos... Gracias!!

Hay algo en el código de esta clase que me crashea el unity .
Ejecuta bien y me deja seleccionar el dulce pero cuando lo muevo a otra posición en la que se juntan 3 del mismo dulce crashea (que es donde deberían caer en cascada)

Borre el código de abajo y ya me dejo de crashear pero no hace lo que debe (es basicamente el codigo que se lleva hasta la clase pasada)

public IEnumerator FindNullCandies()
    {
        for(int x = 0; x < xSize; xSize++){
            for(int y = 0; y < ySize; ySize++)
            {
                if(candies[x,y].GetComponent<SpriteRenderer>().sprite == null)
                {
                    yield return StartCoroutine(MakeCandiesFall(x, y));
                    break;
                }
            }
        }
    }

    private IEnumerator MakeCandiesFall(int x, int yStart, float shiftDelay= 0.0f) //el float es opcional porque esta inicializado automaticamente en el metodo
    {
        isShifting = true;

        List<SpriteRenderer> renders = new List<SpriteRenderer>();
        int nullCandies = 0;

        for(int y = yStart; y< ySize; y++)
        {
            SpriteRenderer spriteRenderer = candies[x, y].GetComponent<SpriteRenderer>();
            if(spriteRenderer.sprite == null)
            {
                nullCandies++;
            }
            renders.Add(spriteRenderer);
        }

        for(int i = 0; i< nullCandies; i++)
        {
            yield return new WaitForSeconds(shiftDelay);
            for (int j = 0; j <renders.Count-1; j++)
            {
                renders[j].sprite = renders[j + 1].sprite;
                renders[j + 1].sprite = GetNewCandies(x,ySize-1); 
            }
        }

        isShifting = false;

    }
    
    private Sprite GetNewCandies(int x, int y)
    {
        List<Sprite> possibleCandies = new List<Sprite>();
        possibleCandies.AddRange(prefabs);

        if (x > 0)
        {
            possibleCandies.Remove(candies[x - 1, y].GetComponent<SpriteRenderer>().sprite);
        }
            if(x<xSize - 1)
            {
                possibleCandies.Remove(candies[x + 1, y].GetComponent<SpriteRenderer>().sprite);
            }
            if(y > 0)
            {
                possibleCandies.Remove(candies[x, y - 1].GetComponent<SpriteRenderer>().sprite);
            }

            return possibleCandies[Random.Range(0, possibleCandies.Count)];
        
    }*/```

EXELENTE

Genial

Hola…

Me sucede algo curioso y que no he visto solución en los comentarios, lo que sucede es que al hacer el match los caramelos no bajan, y al hacer otro match los caramelos del primer match bajan, pero con unas físicas demasiado raras y los del segundo se quedan estáticos. Adjunto mis dos scripts.

CandyController (mi equivalente al script Candy)

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

public class CandyController : MonoBehaviour
{
    private static Color selectedColor = new Color(0.5f, 0.5f, 0.5f, 1.0f);
    private static CandyController previousSelected = null;

    private SpriteRenderer spriteRenderer;
    private bool isSelected = false;

    public int Id;

    private Vector2[] adjacentDirections = new Vector2[]
    {
        Vector2.down,
        Vector2.left,
        Vector2.up,
        Vector2.right,
    };

    public Vector3 objective;

    GameObject newCandy;

    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        objective = Vector3.zero;
    }

    private void OnMouseDown()
    {
        if (spriteRenderer.sprite == null || GridManager.sharedInstance.isShifting)
        {
            return;
        }

        if (isSelected)
        {
            DeselectCandy();
        }
        else
        {
            if (previousSelected == null)
            {
                SelectCandy();
            }
            else
            {
                if (CanSwipe())
                {
                    SwapSprite(previousSelected.gameObject);
                    previousSelected.Invoke("FindAllMatches", 0.25f);
                    previousSelected.DeselectCandy();
                    Invoke("FindAllMatches", 0.25f);

                    StopCoroutine(GridManager.sharedInstance.FindNullCandies());
                    StartCoroutine(GridManager.sharedInstance.FindNullCandies());  
                }
                else
                {
                    previousSelected.DeselectCandy();
                    SelectCandy();
                }
            }
        }
    }

    private void Update()
    {
        //Invoke("FindAllMatches", 1f);

        if (objective != Vector3.zero)
        {
            this.transform.position = Vector3.Lerp(this.transform.position, objective, 5 * Time.deltaTime);
        }
    }

    private void SelectCandy()
    {
        isSelected = true;
        spriteRenderer.color = selectedColor;
        previousSelected = gameObject.GetComponent<CandyController>();
    }

    private void DeselectCandy()
    {
        isSelected = false;
        spriteRenderer.color = Color.white;
        previousSelected = null;
    }

    public void SwapSprite(GameObject candy)
    {
        if (Id == candy.GetComponent<CandyController>().Id)
        {
            return;
        }

        this.objective = candy.transform.position;
        candy.GetComponent<CandyController>().objective = this.transform.position;
    }

    private GameObject GetNeighbor(Vector2 direction)
    {
        RaycastHit2D hit = Physics2D.Raycast(this.transform.position, direction);

        if(hit.collider != null)
        {
            return hit.collider.gameObject;
        }
        else
        {
            return null;
        }
    }

    private List<GameObject> GetAllNeighbors()
    {
        List<GameObject> neighbors = new List<GameObject>();

        foreach(Vector2 direction in adjacentDirections)
        {
            neighbors.Add(GetNeighbor(direction));
        }


        return neighbors;
    }

    private bool CanSwipe()
    {
        return GetAllNeighbors().Contains(previousSelected.gameObject);
    }

    //Consulta vecinos en direccion del parametro
    private List<GameObject> FindMatch(Vector2 direction)
    {
        List<GameObject> matchingCandies = new List<GameObject>();
        RaycastHit2D hit = Physics2D.Raycast(this.transform.position, direction);

        while (hit.collider != null && hit.collider.gameObject.GetComponent<SpriteRenderer>().sprite == spriteRenderer.sprite)
        {
            matchingCandies.Add(hit.collider.gameObject);
            hit = Physics2D.Raycast(hit.collider.gameObject.transform.position, direction);
        }

        return matchingCandies;
    }

    private bool ClearMatch(Vector2[] directions)
    {
        List<GameObject> matchingCandies = new List<GameObject>();

        foreach (Vector2 direction in directions)
        {
            matchingCandies.AddRange(FindMatch(direction));
        }

        if (matchingCandies.Count >= GridManager.MinCandiesToMatch)
        {
            foreach (GameObject candy in matchingCandies)
            {
                candy.GetComponent<SpriteRenderer>().sprite = null;
                spriteRenderer.sprite = null;
            }

            return true;
        }

        return false;
    }

    public void FindAllMatches()
    {
        if (spriteRenderer.sprite == null)
        {
            return;
        }

        bool hMatch = ClearMatch(new Vector2[2]
        {
            Vector2.left,
            Vector2.right,
        });

        bool vMatch = ClearMatch(new Vector2[2]
        {
            Vector2.up,
            Vector2.down,
        });
    }

    public void DeleteCandy()
    {
        //Debug.Log("hey");
        Destroy(this.gameObject);
    }

}

y el script GridManager (el equivalente al script BoardManager)

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

public class GridManager : MonoBehaviour
{
    public static GridManager sharedInstance;
    public List<GameObject> prefabs = new List<GameObject>();
    public GameObject currentCandy;
    public int xSize, ySize;
    public float paddingX, paddingY;
    private CandyController selecctedCandy;

    public const int MinCandiesToMatch = 2;

    private GameObject[,] candies;

    public bool isShifting {get; set;}

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

        Vector2 offset = new Vector2(currentCandy.GetComponent<BoxCollider2D>().size.x + paddingX, currentCandy.GetComponent<BoxCollider2D>().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;

        int idx = -1;

        for(int x = 0; x < xSize; x++)
        {
            for(int y = 0; y < ySize; y++)
            {
                GameObject newCandy = Instantiate(currentCandy, new Vector3(startX + (offset.x * x), startY + (offset.y * y),0f), currentCandy.transform.rotation);
                newCandy.name = string.Format("Candy[{0}][{1}]", x, y);

                do 
                {
                    
                    idx = Random.Range(0, prefabs.Count);

                }while((x > 0 && idx == candies[x - 1, y].GetComponent<CandyController>().Id) ||
                        (y > 0 && idx == candies[x, y - 1].GetComponent<CandyController>().Id));

                GameObject candySprite = prefabs[Random.Range(0,prefabs.Count)];
                newCandy.GetComponent<SpriteRenderer>().sprite = candySprite.GetComponent<SpriteRenderer>().sprite;
                newCandy.GetComponent<CandyController>().Id = idx;

                newCandy.transform.parent = this.transform;
                candies[x, y] = newCandy;
            }
        }
    }

    public IEnumerator FindNullCandies()
    {
        for(int x = 0; x < xSize; x++)
        {
            for(int y = 0; y < ySize; y++)
            {
                if(candies[x,y].GetComponent<SpriteRenderer>().sprite == null)
                {
                    yield return StartCoroutine(MakeCandiesFall(x,y));
                    break;
                }
            }
        }
    }

    private IEnumerator MakeCandiesFall(int x, int yStart, float shiftDelay = 0.05f)
    {
        isShifting = true;

        List <SpriteRenderer> renderes = new List<SpriteRenderer>();
        int nullCandies = 0;

        for(int y = yStart; y < ySize; y++)
        {
            SpriteRenderer spriteRe = candies[x,y].GetComponent<SpriteRenderer>();
            if(spriteRe.sprite == null)
            {
                nullCandies++;
            }
            renderes.Add(spriteRe);
        }

        for(int i = 0; i < nullCandies; i++)
        {
            yield return new WaitForSeconds(shiftDelay);
            for(int j = 0; j < renderes.Count - 1; j++)
            {
                Debug.Log("Puede Caer");
                renderes[j].sprite = renderes[j + 1].sprite;
                renderes[j + 1].sprite = null;
            }
        }

        isShifting = false;
    }

    private GameObject GetNewCandies(int x, int y)
    {
        List <GameObject> possibleCandies = new List<GameObject>();
        possibleCandies.AddRange(prefabs);
    }

}

Para renombrar una variable globalmente pueden hacer click sobre el nombre de la variable y Ctrl + R (2 veces)

He notado que el segundo ciclo de la función MakeCandiesFall tiene un bucle de más, ya que al siguiente código

for (int i = 0; i < nullCandies; i++)
        {
            yield return new WaitForSeconds(shiftDelay);
            for(int j = 0; j < renderers.Count-1; j++)
            {
                renderers[j].sprite = renderers[j+1].sprite;
                renderers[j+1].sprite = null;
            }
        }

se le puede quitar el primer bucle sin afectar su funcionalidad, así:

yield return new WaitForSeconds(shiftDelay);
for(int j = 0; j < renderers.Count-1; j++)
{
renderers[j].sprite = renderers[j+1].sprite;
renderers[j+1].sprite = null;
}

Por favor corriganme si estoy equivocado.

Detalle importante!!!
Si alguno de ustedes en el swipe candy, está comparando con el id (así como es mi caso), deben de igual forma pasar el id una casilla + , al momento de que los caramelos caigan en cascada. Ya que si no lo hacen, quedarán con el id equivocado. Igual si solo verifican por el sprite sería lo mas recomendable hacerlo para evitar futuros problemas con el ID.

Debería ser así:

 for (int i = 0; i < nullCandies; i++){
            yield return new WaitForSeconds(shiftDelay);
            for (int j = 0; j < renderes.Count - 1; j++)
            {
                renderes[j].sprite = renderes[j+1].sprite;
                renderes[j+1].sprite = null;

                candiesToFall[j].GetComponent<Candy>().id = renderes[j+1].GetComponent<Candy>().id;
                candiesToFall[j+1].GetComponent<Candy>().id = 0;
            }
        }

Hola compañeros, no se si les ocurrió lo mismo alguna vez.

Cuando estaba llamando el método FindNullCandies()me daba el siguiente error:

Estuve revisando y encontré lo siguiente:

GameObject[,] candies = new GameObject[xSize, ySize];

Si pueden ver estaba RE DECLARANDO candies 👀. Tengan cuidado para que no les pase lo mismo.

Hola, siguiendo todos los pasos me funciona bien la primera vez, luego no hace nada, he revisado el código y parece que esta todo igual. Como puedo depurar o ver si hay algún mensaje de error? Gracias.