using System; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; /// /// Contains all game logic for the JustSign game /// public class JustSignController : MonoBehaviour { /// /// All of the words that can be used in this session /// private List words = new List(); /// /// The canvas containing all components /// public Canvas canvas; /// /// The input field where the user can type his or her answer /// public TMP_InputField answerField; /// /// The feedback on the timing /// public TMP_Text feedBack; /// /// The current score /// public TMP_Text scoreDisplay; /// /// Reference to the minigame ScriptableObject /// public Minigame minigame; /// /// Reference to the list of available songs /// public SongList songList; /// /// Reference to the currently used song /// private Song currentSong; /// /// The zone that the player should be hitting with his or her inputs /// public GameObject hitZone; /// /// Symbol prefab /// public GameObject symbolPrefab; /// /// Reference to symbol prefab /// public Transform symbolContainer; /// /// All of the available themes /// private ThemeList themeList; /// /// The theme we are currently using /// private Theme currentTheme; /// /// List of strings representing all words on the track /// private List activeWords = new List(); /// /// List of objects representing all symbols on the track /// private List activeSymbols = new List(); /// /// The current score /// private int score; /// /// Have the symbols started spawning or not /// private bool gameIsActive = false; /// /// Width and height of the symbols /// private int symbolSize = 280; /// /// Controls movement speed of symbols (higher -> faster) /// private int moveSpeed = 200; /// /// Starting X-coordinate of a symbol = (-1920 - symbolsize) / 2 /// private int trackX = -1100; /// /// Starting Y-coordinate of a symbol /// private int trackY = -200; /// /// Max distance from hit zone to get perfect score /// private int perfectBoundary = 10; /// /// Score obtained when getting a perfect hit /// private int perfectScore = 5; /// /// Max distance from hit zone to get good score /// private int goodBoundary = 120; /// /// Score obtained when getting a good hit /// private int goodScore = 3; /// /// Max distance from hit zone to get meh score /// private int mehBoundary = 200; /// /// Score obtained when getting a meh hit /// private int mehScore = 1; /// /// Score obtained when getting a terrible hit /// private int terribleScore = -3; /// /// Score obtained when symbol goes offscreen /// private int offscreenScore = -5; /// /// Time at which the last symbol was spawned /// private float lastSpawn; /// /// Time at which the game started, needed to know when to stop /// private float beginTime; /// /// Time at which the last symbol should spawn /// private float lastSymbolTime; /// /// Start is called before the first frame update /// void Start() { scoreDisplay.text = "Score: " + score.ToString(); currentTheme = minigame.themeList.themes[minigame.themeList.currentThemeIndex]; words.AddRange(currentTheme.learnables); currentSong = songList.songs[songList.currentSongIndex]; AudioSource.PlayClipAtPoint(currentSong.song, Vector3.zero, 1.0f); beginTime = Time.time; lastSymbolTime = beginTime + currentSong.duration - 1920.0f / moveSpeed; StartCoroutine(WaitThenStart(currentSong.firstSymbolTime)); } /// /// Wait for a given amount of time (specified in song) before spawning symbols /// IEnumerator WaitThenStart(float nrOfSeconds) { //yield on a new YieldInstruction that waits for nrOfSeconds seconds yield return new WaitForSeconds(nrOfSeconds); gameIsActive = true; } /// /// Update is called once per frame /// void Update() { if (gameIsActive) { int matchedSymbolIndex = -1; for (int i = 0; i < activeWords.Count; i++) { if (activeWords[i].ToLower() == answerField.text.ToLower()) { matchedSymbolIndex = i; } } // Destroy the oldest symbol if the current input matches it if (matchedSymbolIndex >= 0) { int difference = Math.Abs((int) (activeSymbols[matchedSymbolIndex].transform.position.x - hitZone.transform.position.x)); if (difference < perfectBoundary) { feedBack.text = "Perfect!"; score += perfectScore; } else if (difference < goodBoundary) { feedBack.text = "Good!"; score += goodScore; } else if (difference < mehBoundary) { feedBack.text = "Meh..."; score += mehScore; } else { feedBack.text = "Terrible!"; score += terribleScore; } DestroySymbolAt(matchedSymbolIndex); answerField.text = ""; } // Destroy the oldest symbol if it leaves the screen if (activeSymbols.Count > 0) { if (activeSymbols[0].GetComponent().localPosition.x > -trackX) { DestroySymbolAt(0); feedBack.text = "Terrible!"; score += offscreenScore; } } // Spawn new symbol every spawn period float currentTime = Time.time; if (currentTime - lastSpawn > currentSong.spawnPeriod && lastSymbolTime > currentTime) { lastSpawn = currentTime; SpawnNewSymbol(); } // Check if the song has ended and activate scorescreen if it has if (currentTime - beginTime > currentSong.duration) { gameIsActive = false; while (activeSymbols.Count > 0) { DestroySymbolAt(0); } // TODO: Scoreboard } // Move all active symbols to the right foreach (GameObject symbol in activeSymbols) { RectTransform rectTransform = symbol.GetComponent(); rectTransform.localPosition = new Vector3(rectTransform.localPosition.x + Time.deltaTime * moveSpeed, trackY, 0); } scoreDisplay.text = "Score: " + score.ToString(); } } /// /// Destroy the symbol at the given index /// /// The index of the symbol to destroy void DestroySymbolAt(int index) { activeWords.RemoveAt(index); GameObject symbol = activeSymbols[index]; activeSymbols.RemoveAt(index); Destroy(symbol); } /// /// Create a new symbol at the start of the track /// void SpawnNewSymbol() { // Pick a word that isn't in use yet List unusedWordIndices = new List(); for (int i = 0; i < words.Count; i++) { if (!activeWords.Contains(words[i].name)) { unusedWordIndices.Add(i); } } Learnable newLearnable = words[unusedWordIndices[UnityEngine.Random.Range(0, unusedWordIndices.Count)]]; string nextSymbol = newLearnable.name; GameObject newSymbolObject = GameObject.Instantiate(symbolPrefab, symbolContainer); // Dynamically load appearance Image image = newSymbolObject.GetComponent(); image.sprite = newLearnable.image; image.rectTransform.sizeDelta = new Vector2(symbolSize, symbolSize); // Place the word that the symbol represents under the image TMP_Text text = newSymbolObject.GetComponentInChildren(); text.text = nextSymbol; text.color = Color.black; text.rectTransform.localPosition = new Vector3(0, -160, 0); activeWords.Add(nextSymbol); activeSymbols.Add(newSymbolObject); } }