HI all,
Short time reader, first time poster here. Great resources. Unity and the Unity community continue to impress. Now, on to the madness…
I’ve run into an issue with a proof of concept (which in fairness, is still just a learning process) for a classic memory card matching game. I’ve stripped away everything that’s not relevant into an isolated scene in the hopes that the problem would become more evident, but the only two conclusions I’ve come to I’m not sure how to resolve.
The isolated concept:
Four cards are in a grid, the top two match one another and the bottom two match one another.
Game play sequence of events:
Clicking the first card reveals it’s face and remains visible until a second card is revealed
Clicking the second card reveals its face then performs a check to see if they were a match.
If match, they remain visible, else they hide themselves.
The problem is with the animations. If you click subsequent cards at certain speeds they get “interrupted” and throw off the correct state. My first thought was that I’m using iTween for animation and that I’m possibly not handling those tweens properly, but the more I experiment with it, the less I feel that’s the case; though the way I’m recursively calling iTweens to create a “sequence” may also be my issue. Maybe @pixelplacement will have a thought. Also, if I’m instantiating new prefabs into my grid how can calling the animation method in one instance cause issues in another? So confused as to where I’ve lost track here. I’ll list the scripts in code here, but I’m also going to add a link to a webGL version and the assets of the game below in case nothing stands out in the code and someone feels like really digging in.
Any advice, comments, criticism, etc. are appreciated!
WebGL: http://thisguy.forgot.his.name/isolation/
Assets zipped: http://thisguy.forgot.his.name/isolation/Assets.zip
Scripts:
// Game.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Game: MonoBehaviour {
private Button restart;
public List<GameObject> Target { get; set; }
public List<int> Match { get; set; }
void Awake() {
restart = GameObject.Find ("Restart").GetComponent<Button> ();
restart.onClick.AddListener (Restart);
ResetMatches ();
}
private void Restart() {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
private void ResetMatches() {
Target = new List<GameObject> ();
Match = new List<int> ();
}
public void CheckMatch() {
if (!IsMatch ()) {
Target [0].GetComponent<Card> ().FlipCard ();
Target [1].GetComponent<Card> ().FlipCard ();
}
ResetMatches ();
}
private bool IsMatch() {
return Match [0] == Match [1] && Target[0] != Target[1];
}
}
// Grid.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Grid: MonoBehaviour {
public GameObject cardPrefab;
private Transform grid;
void Awake () {
grid = gameObject.transform;
}
void Start() {
Generate ();
}
private void CreateCard (int i, int matchId, string text) {
GameObject prefab;
prefab = (GameObject)Instantiate (cardPrefab);
prefab.transform.SetParent (grid);
prefab.GetComponent<RectTransform> ().localScale = Vector3.one;
prefab.name = "card-" + i.ToString ();
prefab.GetComponent<Card> ().MatchId = matchId;
prefab.GetComponentInChildren<Text> ().text = text;
}
private void Generate () {
CreateCard (0, 0, "a");
CreateCard (1, 0, "A");
CreateCard (2, 1, "b");
CreateCard (3, 1, "B");
}
}
// AnimationSequence.cs
using System;
using UnityEngine;
public struct AnimationSequence {
public AnimationSequence (string onUpdateHandler, Vector3 origin, Vector3 destination, float duration, string easeType, float delay) {
OnUpdateHandler = onUpdateHandler;
Origin = origin;
Destination = destination;
Duration = duration;
EaseType = easeType;
Delay = delay;
}
public string OnUpdateHandler { get; set; }
public Vector3 Origin { get; set; }
public Vector3 Destination { get; set; }
public float Duration { get; set; }
public string EaseType { get; set; }
public float Delay { get; set; }
}
// Card.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Card: MonoBehaviour {
private Game game;
private Button button;
private Transform front;
private Transform back;
private Dictionary<string, AnimationSequence[]> animationSequences;
private float sequenceDuration = 1f;
private float pairShownDuration = 0.75f;
private string currentSequence = "";
private string lastSequence = "";
private int currentSequenceIndex = -1;
private bool IsFlipped { get; set; }
public int MatchId { get; set; }
void Awake () {
game = GameObject.FindObjectOfType<Game> ();
front = gameObject.transform.FindChild ("Front");
back = gameObject.transform.FindChild ("Back");
button = GetComponent<Button> ();
button.onClick.AddListener (OnClick);
iTween.Init (gameObject);
CreateSequences ();
}
public void FlipCard (bool reveal = false) {
string sequence = (reveal) ? "reveal" : "hide";
HandleSequence (sequence);
}
private void OnClick () {
// disallow clicks on flipped cards
if (!IsFlipped) {
IsFlipped = true;
FlipCard (true);
game.Match.Add (MatchId);
game.Target.Add (gameObject);
}
}
private void CreateSequences () {
animationSequences = new Dictionary<string, AnimationSequence[]> ();
// reveal
AnimationSequence[] reveal = new AnimationSequence[2];
reveal [0] = new AnimationSequence ("HandleBack", Vector3.zero, new Vector3 (0, -90, 0), sequenceDuration / reveal.Length, "easeInBack", 0f);
reveal [1] = new AnimationSequence ("HandleFront", new Vector3 (0, 90, 0), Vector3.zero, sequenceDuration / reveal.Length, "easeOutBack", 0f);
// hide
AnimationSequence[] hide = new AnimationSequence[2];
hide [0] = new AnimationSequence ("HandleFront", Vector3.zero, new Vector3 (0, 90, 0), sequenceDuration / hide.Length, "easeInBack", pairShownDuration);
hide [1] = new AnimationSequence ("HandleBack", new Vector3 (0, -90, 0), Vector3.zero, sequenceDuration / hide.Length, "easeOutBack", 0f);
animationSequences.Add ("reveal", reveal);
animationSequences.Add ("hide", hide);
}
private void HandleSequence (string sequence) {
currentSequence = sequence;
RunSequence ();
}
private void RunSequence () {
// if the current sequence was cleared below (at end of sequence)
// check last sequence for "complete" events and return
if (currentSequence == "") {
if (lastSequence == "reveal" && game.Match.Count > 1) {
game.CheckMatch ();
}
if (lastSequence == "hide") {
IsFlipped = false;
}
lastSequence = "";
return;
}
currentSequenceIndex++;
string onUpdateHandler = animationSequences [currentSequence] [currentSequenceIndex].OnUpdateHandler;
Vector3 origin = animationSequences [currentSequence] [currentSequenceIndex].Origin;
Vector3 destination = animationSequences [currentSequence] [currentSequenceIndex].Destination;
float duration = animationSequences [currentSequence] [currentSequenceIndex].Duration;
string easeType = animationSequences [currentSequence] [currentSequenceIndex].EaseType;
float delay = animationSequences [currentSequence] [currentSequenceIndex].Delay;
Hashtable hashParams = iTween.Hash ("from", origin, "to", destination, "time", duration, "onupdate", onUpdateHandler, "onupdatetarget", gameObject, "easetype", easeType, "oncomplete", "RunSequence", "delay", delay);
iTween.ValueTo (gameObject, hashParams);
// the sequence is at end of its tweens, clear sequence
// for the next recursive call of this method
if (currentSequenceIndex == animationSequences [currentSequence].Length - 1) {
lastSequence = currentSequence;
currentSequence = "";
currentSequenceIndex = -1;
}
}
private void HandleBack(Vector3 rotation) {
back.localEulerAngles = rotation;
}
private void HandleFront(Vector3 rotation) {
front.localEulerAngles = rotation;
}
}