1. Grid Generation and Initialization

The first step in creating a match-3 game is generating a grid of tiles, where each tile is associated with a gem or other collectible item. The grid is usually a 2D array, and each tile is represented as a GameObject with a unique identifier (sprite, position, etc.).

using UnityEngine;

public class GridManager : MonoBehaviour
{
    public int width = 8;
    public int height = 8;
    public GameObject tilePrefab;
    public Sprite[] gemSprites;
    private GameObject[,] grid;

    void Start()
    {
        grid = new GameObject[width, height];
        GenerateGrid();
    }

    void GenerateGrid()
    {
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Vector2 position = new Vector2(x, y);
                GameObject tile = Instantiate(tilePrefab, position, Quaternion.identity);
                tile.name = "Tile_" + x + "_" + y;
                tile.GetComponent<SpriteRenderer>().sprite = gemSprites[Random.Range(0, gemSprites.Length)];
                grid[x, y] = tile;
            }
        }
    }
}
  • Explanation: Here, we create an 8×8 grid with randomized gem sprites. This GridManager class takes care of instantiating each tile, assigning a random sprite to it, and ensuring that the grid is properly initialized.

2. Tile Selection and Swapping

In a match-3 game, the player interacts with the tiles by selecting two adjacent tiles to swap their positions. This logic involves capturing user input and swapping the positions of the selected tiles.

using UnityEngine;

public class TileSelector : MonoBehaviour
{
    private GameObject selectedTile;
    private GridManager gridManager;

    void Start()
    {
        gridManager = FindObjectOfType<GridManager>();
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))  // Left click
        {
            Vector2 clickPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            RaycastHit2D hit = Physics2D.Raycast(clickPosition, Vector2.zero);

            if (hit.collider != null)
            {
                GameObject clickedTile = hit.collider.gameObject;
                if (selectedTile == null)
                {
                    selectedTile = clickedTile;
                }
                else
                {
                    SwapTiles(selectedTile, clickedTile);
                    selectedTile = null;
                }
            }
        }
    }

    void SwapTiles(GameObject tile1, GameObject tile2)
    {
        Vector2 tempPos = tile1.transform.position;
        tile1.transform.position = tile2.transform.position;
        tile2.transform.position = tempPos;

        // Swap references in the grid array
        int x1 = (int)tile1.transform.position.x;
        int y1 = (int)tile1.transform.position.y;
        int x2 = (int)tile2.transform.position.x;
        int y2 = (int)tile2.transform.position.y;

        gridManager.grid[x1, y1] = tile2;
        gridManager.grid[x2, y2] = tile1;
    }
}
  • Explanation: This script listens for mouse clicks to select tiles. Once two tiles are selected, the SwapTiles method swaps their positions on the grid and updates the grid references accordingly.

3. Matching Logic

Now comes the core of a match-3 game: finding and matching three or more consecutive tiles of the same type. This involves checking both horizontally and vertically for consecutive tiles with the same sprite.

using System.Collections.Generic;
using UnityEngine;

public class TileMatcher : MonoBehaviour
{
    private GridManager gridManager;

    void Start()
    {
        gridManager = FindObjectOfType<GridManager>();
    }

    public List<GameObject> FindMatches()
    {
        List<GameObject> matchedTiles = new List<GameObject>();

        for (int x = 0; x < gridManager.width; x++)
        {
            for (int y = 0; y < gridManager.height; y++)
            {
                GameObject tile = gridManager.grid[x, y];
                Sprite tileSprite = tile.GetComponent<SpriteRenderer>().sprite;

                List<GameObject> horizontalMatches = CheckDirection(x, y, 1, 0, tileSprite);
                List<GameObject> verticalMatches = CheckDirection(x, y, 0, 1, tileSprite);

                if (horizontalMatches.Count >= 3) matchedTiles.AddRange(horizontalMatches);
                if (verticalMatches.Count >= 3) matchedTiles.AddRange(verticalMatches);
            }
        }

        return matchedTiles;
    }

    List<GameObject> CheckDirection(int startX, int startY, int dirX, int dirY, Sprite sprite)
    {
        List<GameObject> matched = new List<GameObject>();

        int x = startX;
        int y = startY;

        while (x >= 0 && x < gridManager.width && y >= 0 && y < gridManager.height &&
               gridManager.grid[x, y].GetComponent<SpriteRenderer>().sprite == sprite)
        {
            matched.Add(gridManager.grid[x, y]);
            x += dirX;
            y += dirY;
        }

        return matched;
    }
}
  • Explanation: This code looks for horizontal and vertical matches. The CheckDirection function checks in a specific direction (either horizontal or vertical) and adds matching tiles to a list if three or more consecutive tiles are found.

4. Removing Matched Tiles

Once matches are found, we need to remove the matched tiles and replace them with new ones. Removing tiles typically involves destroying them, followed by spawning new tiles in their place.

using System.Collections;
using UnityEngine;

public class TileRemover : MonoBehaviour
{
    private GridManager gridManager;

    void Start()
    {
        gridManager = FindObjectOfType<GridManager>();
    }

    public void RemoveMatchedTiles(List<GameObject> matchedTiles)
    {
        foreach (var tile in matchedTiles)
        {
            Destroy(tile);
        }

        // Generate new tiles in the gaps
        StartCoroutine(GenerateNewTiles());
    }

    IEnumerator GenerateNewTiles()
    {
        yield return new WaitForSeconds(0.5f);  // Wait before filling the gaps

        for (int x = 0; x < gridManager.width; x++)
        {
            for (int y = 0; y < gridManager.height; y++)
            {
                if (gridManager.grid[x, y] == null)
                {
                    Vector2 position = new Vector2(x, y);
                    GameObject tile = Instantiate(gridManager.tilePrefab, position, Quaternion.identity);
                    tile.GetComponent<SpriteRenderer>().sprite = gridManager.gemSprites[Random.Range(0, gridManager.gemSprites.Length)];
                    gridManager.grid[x, y] = tile;
                }
            }
        }
    }
}
  • Explanation: The TileRemover class removes matched tiles and replaces them by instantiating new tiles in the empty spaces. It also includes a slight delay before generating new tiles for better visual effects.

5. Cascading Effects

A key feature of match-3 games is cascading effects, where tiles above the removed tiles fall down to fill the empty spaces, potentially creating new matches. This can be handled in the tile generation logic.

using System.Collections;
using UnityEngine;

public class CascadingEffect : MonoBehaviour
{
    private GridManager gridManager;

    void Start()
    {
        gridManager = FindObjectOfType<GridManager>();
    }

    public void CascadeTiles()
    {
        for (int x = 0; x < gridManager.width; x++)
        {
            for (int y = 0; y < gridManager.height; y++)
            {
                if (gridManager.grid[x, y] == null)
                {
                    MoveTilesDown(x, y);
                }
            }
        }
    }

    void MoveTilesDown(int x, int emptyY)
    {
        for (int y = emptyY + 1; y < gridManager.height; y++)
        {
            if (gridManager.grid[x, y] != null)
            {
                Vector2 newPosition = new Vector2(x, emptyY);
                gridManager.grid[x, emptyY] = gridManager.grid[x, y];
                gridManager.grid[x, y].transform.position = newPosition;
                gridManager.grid[x, y] = null;
                emptyY++;
            }
        }
    }
}
  • Explanation: The CascadingEffect class ensures that tiles fall down to fill the empty spaces when a match is made and tiles are removed. This cascading logic simulates gravity, allowing tiles to shift downward and potentially create new matches.

6. Scoring System

To add a scoring system, you can assign points based on the number of tiles removed, the size of the match, or special tiles that might be cleared.

using UnityEngine;

public class ScoreManager : MonoBehaviour
{
    public int score = 0;

    public void IncreaseScore(int points)
    {
        score += points;
        Debug.Log("Score: " + score);
    }
}
  • Explanation: The ScoreManager class keeps track of the player’s score. Each time a match is made, points are awarded based on the number of tiles in the match.

Conclusion

In this comprehensive guide, we have covered the essential logic required to implement a full match-3 game in Unity. From grid generation and tile swapping to matching, removing, cascading, and scoring, all of the fundamental features have been explained with practical code examples. You can now take these concepts and integrate them into your own Unity projects to build a fully functional and engaging match-3 game. The provided system is modular, so you can easily extend it by adding new mechanics like special tiles, power-ups, and animations for a more dynamic and rewarding gameplay experience.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다