Third part of tutorials on how to create Memory game.
Add new text and name it “TimeCounter”, after set Canvas and “TimeCounter” properties to the following:
Canvas propeties:
“TimeCounter” properties:
After adjusting that GameObjects add new script “TimeCounter” to “TimeCounter” GameObject.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; // ? 2017 TheFlyingKeyboard and released under MIT License // theflyingkeyboard.net public class TimeCounter : MonoBehaviour { private float _timeCounter = 0.0f; [SerializeField] private Text _timeText; private int _counter = 1; private bool _countTime = true; public bool countTime { get { return _countTime; } set { _countTime = value; } } public int timeCounted { get { return _counter; } } // Update is called once per frame void Update() { if (_countTime) { if (_timeCounter >= _counter) //Updating Text each frame would be very CPU expensive { _timeText.text = "Time: " + _counter; _counter++; } _timeCounter += Time.deltaTime; } } }
Fill “TimeText” field with “TimeCounter” GameObject from Canvas and create new empty GameObject in canvas (Right Click on Canvas and then choose Create Empty), set its properties to match the following ones:
After that add new Button, Image and Text to this object with properties as shown below:
The color is: #B2B2B259
Now add new script “Restart” to “RestartButton”:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; // ? 2017 TheFlyingKeyboard and released under MIT License // theflyingkeyboard.net public class Restart : MonoBehaviour { public void RestartGame() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } }
After that click little “+” in On Click () in button and drag “RestartButton” into a little field with small circle sign and choose “RestartGame” function from little menu. Finally it should be like this:
It is important to have GameObject in the following order, because Image that was created will hover “TimeCounter” and not “RestartButton” and “ScoreText”. Next step is to create new GameObject called ScoreManager with new script “ScoreManager”:
using System.Collections; using System.Collections.Generic; using UnityEngine; // ? 2017 TheFlyingKeyboard and released under MIT License // theflyingkeyboard.net public class ScoreManager : MonoBehaviour { private int _score = 0; [SerializeField] private int _initialTimeScoreBonus = 100000; [SerializeField] private int _bonusScorePerSecondLost = 80; [SerializeField] private int _scorePerCard = 50; private TimeCounter _timeCounter; public int score { get { return _score; } set { _score = value; } } void Start() { _timeCounter = FindObjectOfType<TimeCounter>(); } public void AddScore() { _score += _scorePerCard; } public void CalculateEndScore { _score += Mathf.Clamp(_initialTimeScoreBonus - _bonusScorePerSecondLost * _timeCounter.timeCounted, 0, _initialTimeScoreBonus); } }
Come back to “GameManager” and modify it, so it should look like this:
using System.Collections; using System.Collections.Generic; using UnityEngine; // ? 2017 TheFlyingKeyboard and released under MIT License // theflyingkeyboard.net public class GameManager : MonoBehaviour { private GameObject _firstCard = null; private GameObject _secondCard = null; private int _cardsLeft; private bool _canFlip = true; [SerializeField] private float _timeBetweenFlips = 0.75f; //I think that particullar value works best, but you can adjust it as you want private ScoreManager _scoreManager; //<--- [SerializeField] private GameObject _winMenu; //<--- [SerializeField] private TimeCounter _timeCounter; public bool canFlip { get { return _canFlip; } set { _canFlip = value; } } public int cardsLeft { get { return _cardsLeft; } set { _cardsLeft = value; } } void Start() { _scoreManager = FindObjectOfType<ScoreManager>(); //<--- _timeCounter = FindObjectOfType<TimeCounter>(); //<--- } public void AddCard(GameObject card) //This function will be called from CardController class { if (_firstCard == null) //Adds first card { _firstCard = card; } else //Adds second card and checks if both cards match { _secondCard = card; _canFlip = false; if (CheckIfMatch()) { DecreaseCardCount(); _scoreManager.AddScore(); //<--- _scoreManager.AddScore(); //<--- StartCoroutine(DeactivateCards()); } else { StartCoroutine(FlipCards()); } } } IEnumerator DeactivateCards() { yield return new WaitForSeconds(_timeBetweenFlips); //Wait so player can see flipped cards and not click to fast _firstCard.SetActive(false); _secondCard.SetActive(false); Reset(); } IEnumerator FlipCards() { yield return new WaitForSeconds(_timeBetweenFlips); //Wait so player can see flipped cards and not click too fast _firstCard.GetComponent<CardController>().ChangeSide(); _secondCard.GetComponent<CardController>().ChangeSide(); Reset(); } public void Reset() { _firstCard = null; _secondCard = null; _canFlip = true; } public void DecreaseCardCount() { _cardsLeft -= 2; if (_cardsLeft <= 0) { _winMenu.SetActive(true); //<--- _timeCounter.countTime = false; //<--- _scoreManager.CalculateEndScore(); //<--- } } bool CheckIfMatch() { if (_firstCard.GetComponent<CardController>().cardName == _secondCard.GetComponent<CardController>().cardName) { return true; } return false; } }
After that fill “WinMenu” field with “WinMenu” GameObject from Canvas and add new script “SetScoreText” to “ScoreText” in Canvas.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; // ? 2017 TheFlyingKeyboard and released under MIT License // theflyingkeyboard.net public class SetScoreText : MonoBehaviour { [SerializeField] private Text _scoreText; private ScoreManager _scoreManager; // Use this for initialization void Start () { _scoreManager= FindObjectOfType<ScoreManager>(); _scoreText.text = "Score: " + _scoreManager.score; } }
When the script was created fill “ScoreText” field with “ScoreText” GameObject.
And finally Change Camera Background Color to: #BDC2F900.
The game should be working right now. Have fun with it and if you want you can implement some features like:
- Generate amount of cards, depending on user input in begining of the game;
- Add Pause Menu
- Add More Cards
- Add Sound Effect, when card is being flipped
- Add Music to the game
- Add Flip Animation
Thanks for reading :).
Had some errors that prevented it from working
I’ll check the tutorial 🙂
I’ve checked it and it seems to be working.
i have errors at ScoreManager script at lines 42-44
It says a get or set accessor expected
Strange, it works for me :/
got errors too in this part:
“_secondCard.CalculateEndScore();” for Game Manager.
Fixed it, it should be _scoreManager.CalculateEndScore(); in GameManager 😉
I am not getting any cards appearing on the game screen. When I look at the scene they are spawning really small near the corner.
They are not scaling with the resolution or screen size.
I know what it is now. I was wrong its because the cards are behind the image.
please how to make it for each sprite has only one doubling I mean if I have 8 sprites 8 other will spawn and match the other 8
I have problem. I have five cards with different motive, and even though I choose to spawn 5 and set it to each individual card I have some cards showing up more than as a double. So Memory1 which should have itself and a double, might show up as 4 cards in the game. How do I make it only show 2 of each motive?
Another problem is if I press three different cards quickly in a row I get an error saying: “NullReferenceException: Object reference not set to an instance of an object
GameManager+c__Iterator1.MoveNext () (at Assets/GameManager.cs:65)
UnityEngine.SetupCoroutine.InvokeMoveNext (IEnumerator enumerator, IntPtr returnValueAddress) (at C:/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)”
How can I make sure the player can only click two cards and then have to wait?
Very important! Change void Start in the SetScoreText script to void Update otherwise you will have errors.