Yone Moreno Jiménez
Saul Maldonado Ortiz
No sé si te diste cuenta pero a el le ocurre lo mismo
Digital Exp
pudiste solucionarlo?
Implementa una caída en cascada fluida y controlada tras destruir coincidencias de 3 o más caramelos. Con una combinación de corrutinas, control de interacción con isShifting y manejo de SpriteRenderer, lograrás un efecto visual claro y estable, listo para completar con la fase de relleno aleatorio más adelante.
¿Cómo implementar la caída en cascada con corrutinas?
La idea central es detectar huecos donde el SpriteRenderer está en null y, para cada hueco, iniciar una coroutine que desplace hacia abajo los sprites superiores en la misma columna, con un pequeño shift delay para el efecto de cascada.
Código orientativo del flujo descrito:
// En el BoardManager public IEnumerator FindNullCandies() { for (int x = 0; x < xSize; x++) { for (int y = 0; y < ySize; y++) { var sr = candies[x, y].GetComponent<SpriteRenderer>(); if (sr.sprite == null) { yield return StartCoroutine(MakeCandiesFall(x, y)); break; // salir para evitar colisiones de corrutinas en el mismo barrido } } } } private IEnumerator MakeCandiesFall(int x, int yStart, float shiftDelay = 0.05f) { isShifting = true; List<SpriteRenderer> renderers = new List<SpriteRenderer>(); int nullCandies = 0; for (int i = yStart; i < ySize; i++) { var sr = candies[x, i].GetComponent<SpriteRenderer>(); if (sr.sprite == null) nullCandies++; renderers.Add(sr); } 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; }
¿Qué hace cada método clave y por qué es importante?
Este enfoque divide responsabilidades para mantener el código legible y fácil de depurar. Además, las coroutines se “autogobiernan”, pausando y reanudando su ejecución cuando lo necesitan.
¿Cómo detecta FindNullCandies los huecos sin imagen?
sprite == null.yield return StartCoroutine(...) para esperar la caída de esa columna.break tras iniciar la coroutine para evitar múltiples disparos simultáneos.¿Cómo desplaza MakeCandiesFall los sprites hacia abajo?
yStart hasta el tope de la columna.nullCandies para conocer cuántos descensos ejecutar.yield return new WaitForSeconds(shiftDelay) en cada iteración.j+1 a j y deja j+1 en null para simular la caída.¿Cómo mejora la experiencia el shift delay y el control de interacción?
0.05f aporta un efecto de cascada claro y agradable.¿Cómo activar la caída desde el script del caramelo?
Tras el swap y la evaluación de matches, es buen momento para disparar la detección de huecos. Antes, conviene frenar cualquier coroutine anterior para evitar solapamientos.
// En el script Candy, tras el swap y los matches StopCoroutine(BoardManager.sharedInstance.FindNullCandies()); StartCoroutine(BoardManager.sharedInstance.FindNullCandies());
Buenas prácticas en esta fase:
¿Te gustaría comentar cómo ajustarías el delay o qué estrategia seguirías para el relleno aleatorio posterior?
Yone Moreno Jiménez
Saul Maldonado Ortiz
No sé si te diste cuenta pero a el le ocurre lo mismo
Digital Exp
pudiste solucionarlo?
Iván Del Campo
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!!
Leonardo Gutierrez
Te recomiendo tener el código del profesor abierto al lado del tuyo e ir comentando cada línea con todo lo que dice el profe, avanzaras más lento, pero entenderás cada cosa.
Bruno Ivan Paz Martinez
Bruno Ivan Paz Martinez
Ya vi porque, en FindNullCandies los primeros for los tenia asi:
for(int x = 0; x < xSize; xSize++){ for(int y = 0; y < ySize; ySize++)``` y es asi:
for(int x = 0; x < xSize; x++){ for(int y = 0; y < ySize; y++)```
yo supongo que crasheaba porque el for se volvía infinito en la primera manera
Ikal Esteban Fernández Galván
EXELENTE
Leonardo Gutierrez
Genial
IVÁN ROMERO RODRÍGUEZ
Leonardo Ernesto Solórzano Carrasquero
Para renombrar una variable globalmente pueden hacer click sobre el nombre de la variable y Ctrl + R (2 veces)
Alejandro Arroyave Bedoya
Bruno Ivan Paz Martinez
Si afecta la funcionalidad, cuando caen coinciden en vertical no cae, yo entiendo que se debe a que al faltarle un for , no recorre la matriz en ese sentido
Samar Jaffal
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; } }
Edward Fernandez
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 :eyes:. Tengan cuidado para que no les pase lo mismo.
Webmaster El Universo
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.
Juan Carlos Padierna Rueda
No se muy bien a que te refieres, pero creo que deberías asegurarte de que el método MakeCandiesFall() termine con la línea de código isShifting = false; , ya que si no la pones en falso no hay ninguna interacción con el juego.
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?
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)]; }*/```
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); } }
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.