Search Unity

Match-3 Style Game. Tiled Grid. Problem when swapping tile positions

Discussion in 'Scripting' started by megabrobro, Jul 1, 2019.

  1. megabrobro

    megabrobro

    Joined:
    Jul 8, 2017
    Posts:
    109
    NOTE: Since making this post, things have moved on where I now think my code is OK but doesn't work with newer versions of Unity. The code works in a Unity 2017 version

    Hi all, I'm trying to make a Match-3 game with c# and Unity. I've been learning this engine for a few years on and off, but still sometimes make very silly mistakes so this really could be anything that causes my issue here (sorry lol!)

    The problem started when I tried to make a method in the GameBoard class(theres only that class and a Tile class really). The method took two parameters both of Tile type and tried to swap them. I have deleted/lost this version of the code the other day as it just kept 'hanging' in Unity as soon as i clicked the second tile.

    Then I did the method taking just VectorInt's for the index of both tiles. I tried swapping using just the two indexes but this resulted in the two tiles being on top of eachother.

    So I finally have this method which takes the Indexes, creates a temp Tile of one of them and then swaps them. I've been confused for a few days trying many things, so what I did was hard code Input Key Q to first set tile [0,0] to be the "CurrentSelectedTileIndex" and then if that index is not null, then another Q press will act as clicking tile [1,0].

    That's where it gets wierd! if I click Q in game (SLOWLY) it works great, the two tiles swap places. If I click at more than a rate of about 1 press per second, the game hangs again. Furthermore, if I mouse-click the actual tiles (as per the real input for the game i originally coded) this again hangs Unity as soon as the second tile is clicked.

    I tried posting questions online with very limited scope as people don't like me posting whole game and yelling "Fix this please!!!" , but to be honest... I'm a at a loss and almost ready to give up :[

    So i guess I could quietly whisper "...Fix this please.... kind fellows " :D Thanks for any help. Here are both classes, which is the full game code:

    Code (CSharp):
    1. //required in 2017 as Vector2Int was not found .. wasn't used in the 2019 attempts
    2. public class Vector2Int
    3. {
    4.  
    5.     public int x;
    6.     public int y;
    7.  
    8.     public Vector2Int(int x, int y)
    9.     {
    10.         this.x = x;
    11.         this.y = y;
    12.     }
    13. }
    14.  
    Code (CSharp):
    1. public class GlobalAssets : MonoBehaviour
    2. {
    3.     public static GlobalAssets instance;
    4.  
    5.     public Sprite[] sprites;
    6.     public Tile tilePrefab;
    7.  
    8.     private void Awake()
    9.     {
    10.         if (instance == null)
    11.             instance = this;
    12.         else
    13.             Destroy(this);
    14.  
    15.  
    16.     }
    17.  
    18. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Tile : MonoBehaviour
    6. {
    7.     [HideInInspector] public SpriteRenderer sr;
    8.     [HideInInspector] public enum TileType { A, B, C, D, E, F, G };
    9.     [HideInInspector] public TileType tileType;
    10.     [HideInInspector] public int TYPE_COUNT = 7;
    11.     [HideInInspector] public Vector2Int coords;
    12.  
    13.     public void InitTile(TileType type, int x, int y)
    14.     {
    15.         tileType = type;
    16.         GiveNewGridCoords(new Vector2Int(x, y));
    17.         sr = GetComponent<SpriteRenderer>();
    18.         sr.sprite = GlobalAssets.instance.sprites[(int)tileType];
    19.  
    20.     }
    21.  
    22.     public void MakeEmpty()
    23.     {
    24.         Debug.Log("Clearing tile...");
    25.         sr.sprite = null;
    26.     }
    27.  
    28.     public List<Tile> FindMatches(Vector2 castDirection)
    29.     {
    30.         List<Tile> matches = new List<Tile>();
    31.  
    32.         RaycastHit2D hit = Physics2D.Raycast(transform.position + new Vector3(castDirection.x, castDirection.y, 0), castDirection);
    33.         while (hit.collider != null && hit.collider.GetComponent<SpriteRenderer>().sprite == sr.sprite)
    34.         {
    35.             matches.Add(hit.collider.GetComponent<Tile>());
    36.             hit = Physics2D.Raycast(hit.transform.position + new Vector3(castDirection.x, castDirection.y, 0), castDirection);
    37.         }
    38.         hit = Physics2D.Raycast(transform.position - new Vector3(castDirection.x, castDirection.y, 0), -castDirection);
    39.         while (hit.collider != null && hit.collider.GetComponent<SpriteRenderer>().sprite == sr.sprite)
    40.         {
    41.             matches.Add(hit.collider.GetComponent<Tile>());
    42.             hit = Physics2D.Raycast(hit.transform.position - new Vector3(castDirection.x, castDirection.y, 0), -castDirection);
    43.         }
    44.  
    45.         matches.Add(this);
    46.  
    47.         return matches;
    48.     }
    49.  
    50.     public void GiveNewGridCoords(Vector2Int coords)
    51.     {
    52.         this.coords = coords;
    53.         RepositionUsingGridCoords(coords);
    54.     }
    55.  
    56.     void RepositionUsingGridCoords(Vector2Int coords)
    57.     {
    58.         transform.position = new Vector2(coords.x * GameBoard.tileSize, coords.y * GameBoard.tileSize);
    59.     }
    60.  
    61.     private void OnMouseDown()
    62.     {
    63.         Debug.Log("MOUSE CLICKED ON TILE" + transform.position.x + "," + transform.position.y);
    64.         GameBoard.instance.TileClicked(this);
    65.     }
    66. }
    67.  
    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using UnityEngine;
    6.  
    7. public class GameBoard : MonoBehaviour
    8. {
    9.     public static GameBoard instance;
    10.  
    11.     const int GRID_WIDTH = 9;
    12.     const int GRID_HEIGHT = 9;
    13.     public static int tileSize = 1;
    14.     Tile[,] tiles = new Tile[GRID_WIDTH, GRID_HEIGHT];
    15.     System.Random rand;
    16.  
    17.     Vector2Int currentlySelectedTileCoords = new Vector2Int(-1,-1);
    18.     Vector2Int UNSELECTION_COORDS = new Vector2Int(-1, -1);
    19.  
    20.     private void Awake()
    21.     {
    22.         if (instance == null)
    23.             instance = this;
    24.         else
    25.             Destroy(this);
    26.     }
    27.  
    28.     private void Start()
    29.     {
    30.         CreateNewBoard();
    31.    
    32.     }
    33.  
    34.     void CreateNewBoard()
    35.     {
    36.         rand = new System.Random();
    37.         for (int y = 0; y < tiles.GetLength(1); y++)
    38.         {
    39.             for (int x = 0; x < tiles.GetLength(0); x++)
    40.             {
    41.                 Tile tile = Instantiate(GlobalAssets.instance.tilePrefab);
    42.                 tile.InitTile((Tile.TileType)rand.Next(tile.TYPE_COUNT), x, y);
    43.                 tiles[x, y] = tile;
    44.             }
    45.         }
    46.     }
    47.  
    48.     private void Update()
    49.     {
    50.         if (Input.GetKeyDown(KeyCode.Q))
    51.         {
    52.             if (currentlySelectedTileCoords == UNSELECTION_COORDS)
    53.                 TileClicked(tiles[0, 0]);
    54.             else
    55.                 TileClicked(tiles[1, 0]);
    56.         }
    57.  
    58.         if (currentlySelectedTileCoords != UNSELECTION_COORDS)
    59.         {
    60.             return;
    61.         }
    62.  
    63.         CheckForMatches();
    64.         MakeBlocksFall();
    65.  
    66.    
    67.     }
    68.  
    69.     public void CheckForMatches()
    70.     {
    71.         for (int y = 0; y < tiles.GetLength(1); y++)
    72.         {
    73.             for (int x = 0; x < tiles.GetLength(0); x++)
    74.             {
    75.                 List<Tile> matchedTilesX = tiles[x,y].FindMatches(Vector2.right);
    76.                 if (matchedTilesX.Count >= 3)
    77.                 {
    78.                     foreach (Tile t in matchedTilesX)
    79.                     {
    80.                         t.MakeEmpty();
    81.                     }
    82.                 }
    83.                 List<Tile> matchedTilesY = tiles[x,y].FindMatches(Vector2.up);
    84.                 if (matchedTilesY.Count >= 3)
    85.                 {
    86.                     foreach (Tile t in matchedTilesY)
    87.                     {
    88.                         t.MakeEmpty();
    89.                     }
    90.                 }
    91.             }
    92.         }
    93.     }
    94.  
    95.     void MakeBlocksFall()
    96.     {
    97.  
    98.         for (int y = 1; y < tiles.GetLength(1); y++)
    99.         {
    100.             for (int x = 0; x < tiles.GetLength(0); x++)
    101.             {
    102.                 if (y == tiles.GetLength(1) - 1 && tiles[x, y].sr.sprite == null)
    103.                 {
    104.                     Tile tile = Instantiate(GlobalAssets.instance.tilePrefab);
    105.                     tile.InitTile((Tile.TileType)rand.Next(tile.TYPE_COUNT), x, y);
    106.                     tiles[x, y] = tile;
    107.                 }
    108.  
    109.                 if (tiles[x,y-1].sr.sprite == null)
    110.                 {
    111.                     tiles[x, y - 1].sr.sprite = tiles[x, y].sr.sprite;
    112.                     tiles[x, y].MakeEmpty();
    113.                 }
    114.             }
    115.         }
    116.     }
    117.  
    118.     public void TileClicked(Tile tile)
    119.     {
    120.         Debug.Log("Selected TILE INDEX: " + currentlySelectedTileCoords);
    121.         Debug.Log("TileClicked!");
    122.         if (currentlySelectedTileCoords == UNSELECTION_COORDS)
    123.         {
    124.             currentlySelectedTileCoords = tile.coords;
    125.             Debug.Log("selceted tile was null. it is now: " + currentlySelectedTileCoords);
    126.         }
    127.         else
    128.         {
    129.             if (tile.coords != currentlySelectedTileCoords)
    130.             {
    131.                 SwapTilePositions(currentlySelectedTileCoords, tile.coords);
    132.                 currentlySelectedTileCoords = UNSELECTION_COORDS;
    133.            
    134.             }
    135.             else
    136.             {
    137.                 Debug.Log("Re-clicked the same tile. Now selectedTile is made null");
    138.                 currentlySelectedTileCoords = UNSELECTION_COORDS;
    139.             }
    140.         }
    141.     }
    142.  
    143.     public void SwapTilePositions(Vector2Int selectedTileIndex, Vector2Int otherTileIndex)
    144.     {
    145.         if (selectedTileIndex.x < 0 || selectedTileIndex.y < 0)
    146.             return;
    147.  
    148.         if (selectedTileIndex.x == otherTileIndex.x + 1 || selectedTileIndex.x == otherTileIndex.x - 1
    149.             || selectedTileIndex.y == otherTileIndex.y + 1 || selectedTileIndex.y == otherTileIndex.y - 1)
    150.         {
    151.  
    152.             Vector2Int a = selectedTileIndex;
    153.             Vector2Int b = otherTileIndex;
    154.             Debug.Log("ax and ay = " + a.x + a.y);
    155.             Debug.Log("tile = " + tiles[a.x, a.y]);
    156.             Tile tempTile = tiles[a.x, a.y];
    157.  
    158.             tiles[a.x, a.y] = tiles[b.x, b.y];
    159.             tiles[b.x, b.y] = tempTile;
    160.  
    161.             tiles[a.x, a.y].GiveNewGridCoords(a);
    162.             tiles[b.x, b.y].GiveNewGridCoords(b);
    163.         }
    164.         else
    165.         {
    166.             Debug.Log("Tile not adjecent, cannot swap tiles");
    167.         }
    168.     }
    169.  
    170.  
    171. }
    172.  
     
    Last edited: Jul 1, 2019
  2. megabrobro

    megabrobro

    Joined:
    Jul 8, 2017
    Posts:
    109
    Ok, i found something quite concerning. I downloaded a much older Unity as a test. I downloaded 2017.1.5f1. I put my code in (then had to make my own version of Vector2Int) then I ran my code and it gave me error "index outside array". I then found i had been passing in (-1,-1) on my mouse-click.

    So I added a check to return if selected index is less than 0.
    In Unity 2017 THE GAME WORKS FINE!! , I tried taking this knowledge and changing the Unity 2019 (version 2019.1.8f1) code I had, but the game still just hangs.

    (....Could it be the Vector2Int unity class causing this, i highly doubt it, and also seem to remember I tried many versions of this method to get it working and pretty sure many didnt use any Vector2Int's anyway)

    Just tested it using latest Alpha. I took copy of the working project, opened it using latest Alpha and the game hangs like in the 2018.1.8 version. The exact same code works in 2017. What the heck can i do :[
     
    Last edited: Jul 1, 2019
  3. megabrobro

    megabrobro

    Joined:
    Jul 8, 2017
    Posts:
    109
    spent a couple of nights going round in circles wondering what I done wrong ....god i love learn to code on my own haha :p

    We'll if a mod or admin comes and reads this, is there any way this can be moved into a 'problems/bugs' section maybe ?

    I am going to edit the question so the exact full working code is there that works in 2017.
    Thanks for any help
     
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    Can't really tell what the code is doing, but general advice from the text:

    o Is there any delayed effect in the swap? If something breaks only when you tap it quickly, it's often because it's still "in motion". A trick is to set something on a tile to make it unclickable for a while, then reset it when it's done switching places.

    o Hanging means a loop. Probably one of those while raycasting loops. A trick is to set count=0 beforehand, put count++ in the loop, and add "if(count>999) { print("count too big"); break; }" inside. Whichever one triggers, if any, that's where you were hanging. Make sure to reset count to 0 before the next loop.

    o While running, go into Scene mode and recheck everything. After a swap, loop at each tile in Hierachy and double-check the positions. Recheck the debug's with positions if need be. Eventually some debug will give results that make no sense, and there's the problem.
     
    megabrobro likes this.
  5. megabrobro

    megabrobro

    Joined:
    Jul 8, 2017
    Posts:
    109
    cheers for the help. I will try to do that count in the while loops. But isn't it strange that it works perfectly in 2017 no matter how quickly i press it.

    (Note: With this latest version of the code (i.e. The one posted above), it works fully fine in 2017 but in 2019+ it hangs immediately when clicking second tile (no matter of speed of click etc)

    Also I think i have been quite careful to make sure it doesnt update or move any tiles whilst 'currentlySelectedTile' is not set to Vector(-1,-1).

    Not to worry too much as I will just continue this particular project in 2017 for now and also have started another completely different project in latest Unity.

    Thanks again for the helpful advice