Here’s the entire gameplay code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Gameplay : MonoBehaviour
{
// value corresponds to index in sprites array
private const int ALCHEMIST = 0;
private const int LIFESTEALER = 1;
private const int PANGOLIER = 2;
private const int TERRORBLADE = 3;
private const int DEATH_PROPHET = 4;
private const int CLOCKWERK = 5;
private const int EARTH_SPIRIT = 6;
private const int GRIMSTROKE = 7;
[SerializeField] private int maxTries = 10;
[SerializeField] private Image healthBar = null;
[SerializeField] private Sprite[] sprites = null;
[SerializeField] private Button[] tiles = null;
private int[] tileArrangement = {
ALCHEMIST, ALCHEMIST,
LIFESTEALER, LIFESTEALER,
PANGOLIER, PANGOLIER,
TERRORBLADE, TERRORBLADE,
DEATH_PROPHET, DEATH_PROPHET,
CLOCKWERK, CLOCKWERK,
EARTH_SPIRIT, EARTH_SPIRIT,
GRIMSTROKE, GRIMSTROKE
};
private int indexOfCurr1stClick = -1;
private int indexOfCurr2ndClick = -1;
// logically this should be false as when the game starts, there are no matching tiles but
// i have to make this true so in the OnTileClicked(), the code inside "if(!match)" won't be
// executed until at a later time
private bool match = true;
// Needed since indexOfCurr1stClick and indexOfCurr2ndClick will be reset to -1 after every 2 tiles
// are opened/clicked. If the 2 tiles are not a match, they need to be closed (hide the images) and
// their indexes have to be remembered and thus, the 2 variables below
private int indexOfPrev1stClick = -1;
private int indexOfPrev2ndClick = -1;
private float hpBarOrigHeight = 0;
private int triesLeft = 0;
private int correctTiles = 0;
// Start is called before the first frame update
void Start()
{
// get height of health bar Image
hpBarOrigHeight = healthBar.GetComponent<RectTransform>().rect.height;
triesLeft = maxTries;
ShuffleTileArrangement();
SetupTiles();
}
// Fisher–Yates modern shuffle
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
private void ShuffleTileArrangement()
{
for(int i = 0; i <= tileArrangement.Length - 2; ++i)
{
int randomIndex = Random.Range(i, tileArrangement.Length);
// swap
int temp = tileArrangement[i];
tileArrangement[i] = tileArrangement[randomIndex];
tileArrangement[randomIndex] = temp;
}
}
private void SetupTiles()
{
for(int i = 0; i < tiles.Length; ++i)
{
SetImagesOnTiles(i);
ShowHideImagesOnTiles(i, false);
AddOnClickListeners(i);
}
}
private void SetImagesOnTiles(int i)
{
Sprite sprite = null;
switch (tileArrangement[i])
{
case ALCHEMIST:
sprite = sprites[ALCHEMIST];
break;
case LIFESTEALER:
sprite = sprites[LIFESTEALER];
break;
case PANGOLIER:
sprite = sprites[PANGOLIER];
break;
case TERRORBLADE:
sprite = sprites[TERRORBLADE];
break;
case DEATH_PROPHET:
sprite = sprites[DEATH_PROPHET];
break;
case CLOCKWERK:
sprite = sprites[CLOCKWERK];
break;
case EARTH_SPIRIT:
sprite = sprites[EARTH_SPIRIT];
break;
case GRIMSTROKE:
sprite = sprites[GRIMSTROKE];
break;
}
// https://answers.unity.com/questions/464616/access-child-of-a-gameobject.html
// get the child Image of a Button
GameObject childImg = tiles[i].transform.GetChild(0).gameObject;
// https://forum.unity.com/threads/solved-changing-ui-image-with-script.440347/
// from the child Image, get the Image script component and assign sprite as the Source Image
childImg.GetComponent<Image>().sprite = sprite;
}
private void ShowHideImagesOnTiles(int i, bool show)
{
// https://answers.unity.com/questions/464616/access-child-of-a-gameobject.html
// get the child Image of a Button
GameObject childImg = tiles[i].transform.GetChild(0).gameObject;
// disable or enable the child Image object to make it disappear or show on screen
childImg.SetActive(show);
}
private void AddOnClickListeners(int i)
{
// https://forum.unity.com/threads/addlistener-and-delegates-i-think-im-doing-it-wrong.413093/
int iCopy = i;
// https://answers.unity.com/questions/1288510/buttononclickaddlistener-how-to-pass-parameter-or.html
// added onClick listeners so that each button can listen to click events and it is done in code
// so I can pass a parameter (index of the button clicked) to the click handler function:
// OnTileClicked()
tiles[i].onClick.AddListener(delegate { OnTileClicked(iCopy); });
}
private void OnTileClicked(int i)
{
// "open" a tile by showing its image
ShowHideImagesOnTiles(i, true);
// a) at the start of the game, no tile has been clicked yet so indexOfCurr1stClick is -1
// b) after every 2 tiles opened, indexOfCurr1stClick is reset to -1
if (indexOfCurr1stClick == -1)
{
// at the start of the game, match is true so skip, else errors (array index out of bounds) will
// occur since indexOfPrev1stClick and indexOfPrev2ndClick are -1
if (!match)
{
// make the buttons clickable again since the last 2 clicked tiles were not a
// match
tiles[indexOfPrev1stClick].GetComponent<Button>().interactable = true; // get Button script component of this Button object and set Interactable to false
tiles[indexOfPrev2ndClick].GetComponent<Button>().interactable = true;
// "close" the last 2 clicked tiles by hiding their images
ShowHideImagesOnTiles(indexOfPrev1stClick, false);
ShowHideImagesOnTiles(indexOfPrev2ndClick, false);
} // else if it is a match, leave them opened and don't do anything
// save the index of the 1st clicked tile
indexOfPrev1stClick = indexOfCurr1stClick = i;
// make this button "unclickable" so even if the player clicks an already opened tile (image is showing),
// it will not mess up the game
// get Button script component of this Button object and set Interactable to false
tiles[indexOfCurr1stClick].GetComponent<Button>().interactable = false;
}
else
{
// at this point, indexOfCurr1stClick is not -1 which means a 1st tile has been clicked
// and the current click is for the 2nd tile
if (indexOfCurr2ndClick == -1)
{
// save the index of the 2nd clicked tile
indexOfPrev2ndClick = indexOfCurr2ndClick = i;
// make this button "unclickable" so even if the player clicks an already opened tile (image is showing),
// it will not mess up the game
// get Button script component of this Button object and set Interactable to false
tiles[indexOfCurr2ndClick].GetComponent<Button>().interactable = false;
// compare the 2 tiles
if (tileArrangement[indexOfCurr1stClick] == tileArrangement[indexOfCurr2ndClick])
{
Debug.Log("match");
match = true;
correctTiles += 2;
// you won the game if you opened all tiles before consuming all tries
if(correctTiles >= tiles.Length)
{
SceneManager.LoadScene("VictoryScene");
}
}
else
{
Debug.Log("didn't match");
match = false;
// https://answers.unity.com/questions/988686/recttransform-how-to-change-height.html
// get Rect Transform component of health bar Image
RectTransform rt = healthBar.GetComponent<RectTransform>();
// don't forget to set pivot point (anchors option) to "bottom center" so height
// will decrease only from 1 direction (from the top)
// decrease height of health bar
rt.sizeDelta = new Vector2(rt.sizeDelta.x,
rt.sizeDelta.y - (hpBarOrigHeight / maxTries));
--triesLeft;
// you lost if all tries have been consumed before opening all tiles
if(triesLeft <= 0)
{
SceneManager.LoadScene("DefeatScene");
}
}
// reset to match a new pair of tiles
indexOfCurr1stClick = indexOfCurr2ndClick = -1;
}
}
}
}
Hierarchy setup:
Scene setup:
Gameplay script in Inspector: