Snapshot of the existing Unity 6 language-learning word game before any refactoring. Adds Unity .gitignore and .gitattributes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1118 lines
No EOL
30 KiB
C#
1118 lines
No EOL
30 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.UI;
|
||
|
||
public class TetrisTranslationDropMod : MonoBehaviour
|
||
{
|
||
[Header("━━━ FALLING BLOCK ━━━━━━━━━━━━━━━━━━━━━")]
|
||
public RectTransform fallingBlock;
|
||
public TMP_Text fallingText;
|
||
|
||
[Header("━━━ CONTROLS ━━━━━━━━━━━━━━━━━━━━━━━━━")]
|
||
public Button btnLeft;
|
||
public Button btnRight;
|
||
public Button btnDown;
|
||
public Button backButton;
|
||
|
||
[Header("━━━ UI ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")]
|
||
public TMP_Text scoreText;
|
||
public TMP_Text comboText;
|
||
public TMP_Text timerText;
|
||
public TMP_Text feedbackText;
|
||
|
||
[Header("━━━ TRANSLATION SLOTS (4) ━━━━━━━━━━━━━")]
|
||
public RectTransform[] translationSlots;
|
||
public TMP_Text[] translationSlotTexts;
|
||
|
||
[Header("━━━ WORD TXT ━━━━━━━━━━━━━━━━━━━━━━━━━")]
|
||
[Tooltip("Each line: tr,en,es,de,fr,pt")]
|
||
public TextAsset wordsTxt;
|
||
|
||
[Header("━━━ SCENE SETTINGS ━━━━━━━━━━━━━━━━━━━")]
|
||
public string backSceneName = "ModSec 7";
|
||
public string resultSceneName = "Mod7Resume";
|
||
|
||
[Header("━━━ GAME SETTINGS ━━━━━━━━━━━━━━━━━━━")]
|
||
public float baseFallSpeed = 220f;
|
||
public float maxFallSpeed = 620f;
|
||
public float fallSpeedIncreaseRate = 12f;
|
||
|
||
public float moveStep = 140f;
|
||
public float fastFallStep = 350f;
|
||
|
||
public float gameTime = 90f;
|
||
public float nextWordDelay = 0.75f;
|
||
|
||
public int correctBaseScore = 50;
|
||
public int comboBonus = 10;
|
||
public int wrongPenalty = 20;
|
||
|
||
[Header("━━━ SCREEN LIMITS ━━━━━━━━━━━━━━━━━━━━")]
|
||
public float minX = -420f;
|
||
public float maxX = 420f;
|
||
public float spawnY = 650f;
|
||
public float bottomLimitY = -650f;
|
||
|
||
[Header("━━━ COLORS ━━━━━━━━━━━━━━━━━━━━━━━━━━")]
|
||
public Color normalSlotColor = new Color32(255, 255, 255, 255);
|
||
public Color correctSlotColor = new Color32(70, 220, 120, 255);
|
||
public Color wrongSlotColor = new Color32(240, 80, 80, 255);
|
||
public Color feedbackCorrectColor = new Color32(70, 220, 120, 255);
|
||
public Color feedbackWrongColor = new Color32(240, 80, 80, 255);
|
||
public Color timerNormalColor = new Color32(255, 255, 255, 255);
|
||
public Color timerWarningColor = new Color32(255, 80, 80, 255);
|
||
|
||
[Header("━━━ ANIMATION SETTINGS ━━━━━━━━━━━━━━━")]
|
||
public float blockSpawnScale = 0.82f;
|
||
public float blockSpawnAnimTime = 0.18f;
|
||
public float slotPulseScale = 1.08f;
|
||
public float slotPulseTime = 0.12f;
|
||
public float shakeAmount = 12f;
|
||
public float shakeTime = 0.28f;
|
||
|
||
[Header("━━━ SOUND ━━━━━━━━━━━━━━━━━━━━━━━━━━━")]
|
||
public AudioSource sfxSource;
|
||
public AudioClip clickClip;
|
||
public AudioClip correctClip;
|
||
public AudioClip wrongClip;
|
||
|
||
// INTERNAL
|
||
private List<WordEntry> fullDeck = new List<WordEntry>();
|
||
private int deckIndex = 0;
|
||
|
||
private WordEntry currentWord;
|
||
private string correctAnswer = "";
|
||
private string lastWordKey = "";
|
||
|
||
private float timer;
|
||
private float currentFallSpeed;
|
||
private float smoothVelocity = 0f;
|
||
|
||
private bool processing = false;
|
||
private bool gameOver = false;
|
||
|
||
private int score = 0;
|
||
private int combo = 0;
|
||
private int bestComboThisRun = 0;
|
||
|
||
private int correctCount = 0;
|
||
private int wrongCount = 0;
|
||
|
||
private string questionLangCode;
|
||
private string answerLangCode = "en";
|
||
private string uiLangCode;
|
||
|
||
private Vector3[] originalSlotScales;
|
||
private Vector2[] originalSlotPositions;
|
||
private Vector3 originalBlockScale = Vector3.one;
|
||
|
||
private Coroutine blockSpawnCo;
|
||
private Coroutine blockReactCo;
|
||
private Coroutine feedbackCo;
|
||
private Coroutine[] slotCoroutines;
|
||
|
||
private string modKey = "Mod7";
|
||
|
||
private class WordEntry
|
||
{
|
||
public string tr;
|
||
public string en;
|
||
public string es;
|
||
public string de;
|
||
public string fr;
|
||
public string pt;
|
||
|
||
public string Key => tr + "_" + en;
|
||
}
|
||
|
||
// ======================================================
|
||
void Start()
|
||
{
|
||
gameOver = false;
|
||
processing = false;
|
||
|
||
questionLangCode = GetQuestionLanguage();
|
||
answerLangCode = "en";
|
||
uiLangCode = GetUILanguage();
|
||
|
||
PlayerPrefs.SetString("QuestionLangCode", questionLangCode);
|
||
PlayerPrefs.SetString("AnswerLangCode", answerLangCode);
|
||
PlayerPrefs.Save();
|
||
|
||
timer = PlayerPrefs.GetFloat("SelectedGameTime", gameTime);
|
||
|
||
if (timer <= 0)
|
||
timer = gameTime;
|
||
|
||
currentFallSpeed = baseFallSpeed;
|
||
|
||
CacheSlotData();
|
||
LoadWords();
|
||
|
||
if (fullDeck.Count < 4)
|
||
{
|
||
Debug.LogError("Mod7 kelime dosyası en az 4 satır olmalı! Format: tr,en,es,de,fr,pt");
|
||
enabled = false;
|
||
return;
|
||
}
|
||
|
||
if (!ValidateUI())
|
||
{
|
||
enabled = false;
|
||
return;
|
||
}
|
||
|
||
BindButtons();
|
||
|
||
Shuffle(fullDeck);
|
||
|
||
UpdateUI();
|
||
UpdateTimerUI();
|
||
SaveProgress();
|
||
|
||
SpawnNewWord();
|
||
}
|
||
|
||
// ======================================================
|
||
void Update()
|
||
{
|
||
if (gameOver || processing) return;
|
||
|
||
timer -= Time.deltaTime;
|
||
|
||
if (timer <= 0)
|
||
{
|
||
timer = 0;
|
||
UpdateTimerUI();
|
||
GameOver();
|
||
return;
|
||
}
|
||
|
||
UpdateTimerUI();
|
||
|
||
float targetSpeed = baseFallSpeed + (score / 250f) * fallSpeedIncreaseRate;
|
||
targetSpeed = Mathf.Clamp(targetSpeed, baseFallSpeed, maxFallSpeed);
|
||
|
||
currentFallSpeed = Mathf.SmoothDamp(
|
||
currentFallSpeed,
|
||
targetSpeed,
|
||
ref smoothVelocity,
|
||
0.18f
|
||
);
|
||
|
||
if (fallingBlock != null)
|
||
fallingBlock.anchoredPosition += Vector2.down * currentFallSpeed * Time.deltaTime;
|
||
|
||
CheckCollision();
|
||
}
|
||
|
||
// ======================================================
|
||
bool ValidateUI()
|
||
{
|
||
if (fallingBlock == null)
|
||
{
|
||
Debug.LogError("fallingBlock atanmadı!");
|
||
return false;
|
||
}
|
||
|
||
if (fallingText == null)
|
||
{
|
||
Debug.LogError("fallingText atanmadı!");
|
||
return false;
|
||
}
|
||
|
||
if (translationSlots == null || translationSlots.Length < 4)
|
||
{
|
||
Debug.LogError("translationSlots en az 4 olmalı!");
|
||
return false;
|
||
}
|
||
|
||
if (translationSlotTexts == null || translationSlotTexts.Length < 4)
|
||
{
|
||
Debug.LogError("translationSlotTexts en az 4 olmalı!");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void BindButtons()
|
||
{
|
||
if (btnLeft != null)
|
||
{
|
||
btnLeft.onClick.RemoveAllListeners();
|
||
btnLeft.onClick.AddListener(() =>
|
||
{
|
||
PlayClick();
|
||
Move(-moveStep);
|
||
});
|
||
}
|
||
|
||
if (btnRight != null)
|
||
{
|
||
btnRight.onClick.RemoveAllListeners();
|
||
btnRight.onClick.AddListener(() =>
|
||
{
|
||
PlayClick();
|
||
Move(+moveStep);
|
||
});
|
||
}
|
||
|
||
if (btnDown != null)
|
||
{
|
||
btnDown.onClick.RemoveAllListeners();
|
||
btnDown.onClick.AddListener(() =>
|
||
{
|
||
PlayClick();
|
||
FastFall();
|
||
});
|
||
}
|
||
|
||
if (backButton != null)
|
||
{
|
||
backButton.onClick.RemoveAllListeners();
|
||
backButton.onClick.AddListener(() =>
|
||
{
|
||
PlayClick();
|
||
SaveProgress();
|
||
GoBack();
|
||
});
|
||
}
|
||
}
|
||
|
||
void GoBack()
|
||
{
|
||
if (!string.IsNullOrEmpty(backSceneName))
|
||
SceneManager.LoadScene(backSceneName);
|
||
else
|
||
Debug.LogWarning("backSceneName boş!");
|
||
}
|
||
|
||
// ======================================================
|
||
void CacheSlotData()
|
||
{
|
||
if (fallingBlock != null)
|
||
originalBlockScale = fallingBlock.localScale;
|
||
|
||
if (translationSlots == null) return;
|
||
|
||
originalSlotScales = new Vector3[translationSlots.Length];
|
||
originalSlotPositions = new Vector2[translationSlots.Length];
|
||
slotCoroutines = new Coroutine[translationSlots.Length];
|
||
|
||
for (int i = 0; i < translationSlots.Length; i++)
|
||
{
|
||
if (translationSlots[i] == null) continue;
|
||
|
||
originalSlotScales[i] = translationSlots[i].localScale;
|
||
originalSlotPositions[i] = translationSlots[i].anchoredPosition;
|
||
}
|
||
}
|
||
|
||
void ResetSlotVisuals()
|
||
{
|
||
if (translationSlots == null) return;
|
||
|
||
for (int i = 0; i < translationSlots.Length; i++)
|
||
{
|
||
if (translationSlots[i] == null) continue;
|
||
|
||
if (slotCoroutines != null && i < slotCoroutines.Length && slotCoroutines[i] != null)
|
||
{
|
||
StopCoroutine(slotCoroutines[i]);
|
||
slotCoroutines[i] = null;
|
||
}
|
||
|
||
Image img = translationSlots[i].GetComponent<Image>();
|
||
|
||
if (img != null)
|
||
img.color = FullAlpha(normalSlotColor);
|
||
|
||
if (originalSlotScales != null && i < originalSlotScales.Length)
|
||
translationSlots[i].localScale = originalSlotScales[i];
|
||
|
||
if (originalSlotPositions != null && i < originalSlotPositions.Length)
|
||
translationSlots[i].anchoredPosition = originalSlotPositions[i];
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
void LoadWords()
|
||
{
|
||
fullDeck.Clear();
|
||
|
||
if (wordsTxt == null)
|
||
{
|
||
Debug.LogError("wordsTxt atanmadı!");
|
||
return;
|
||
}
|
||
|
||
string[] lines = wordsTxt.text.Split('\n');
|
||
|
||
foreach (string rawLine in lines)
|
||
{
|
||
string line = rawLine.Trim();
|
||
|
||
if (string.IsNullOrWhiteSpace(line))
|
||
continue;
|
||
|
||
string[] p = line.Split(',');
|
||
|
||
if (p.Length < 6)
|
||
{
|
||
Debug.LogWarning("Eksik satır: " + line);
|
||
continue;
|
||
}
|
||
|
||
fullDeck.Add(new WordEntry
|
||
{
|
||
tr = p[0].Trim(),
|
||
en = p[1].Trim(),
|
||
es = p[2].Trim(),
|
||
de = p[3].Trim(),
|
||
fr = p[4].Trim(),
|
||
pt = p[5].Trim()
|
||
});
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
void SpawnNewWord()
|
||
{
|
||
if (gameOver) return;
|
||
|
||
processing = false;
|
||
ResetSlotVisuals();
|
||
|
||
if (feedbackText != null)
|
||
feedbackText.text = "";
|
||
|
||
if (deckIndex >= fullDeck.Count)
|
||
{
|
||
Shuffle(fullDeck);
|
||
deckIndex = 0;
|
||
lastWordKey = "";
|
||
}
|
||
|
||
currentWord = fullDeck[deckIndex];
|
||
|
||
if (currentWord.Key == lastWordKey && fullDeck.Count > 1)
|
||
{
|
||
int rnd = Random.Range(0, fullDeck.Count);
|
||
currentWord = fullDeck[rnd];
|
||
deckIndex = rnd + 1;
|
||
|
||
if (deckIndex >= fullDeck.Count)
|
||
deckIndex = 0;
|
||
}
|
||
else
|
||
{
|
||
deckIndex++;
|
||
}
|
||
|
||
lastWordKey = currentWord.Key;
|
||
|
||
string questionWord = GetWordByLang(currentWord, questionLangCode);
|
||
correctAnswer = GetWordByLang(currentWord, answerLangCode);
|
||
|
||
// ✅ Yukarıdan soru dili gelir
|
||
fallingText.text = questionWord;
|
||
|
||
if (fallingBlock != null)
|
||
{
|
||
fallingBlock.anchoredPosition = new Vector2(0, spawnY);
|
||
fallingBlock.localScale = originalBlockScale;
|
||
}
|
||
|
||
// ✅ Alttaki seçenekler İngilizce gelir
|
||
SetupTranslationSlots(correctAnswer);
|
||
|
||
if (blockSpawnCo != null)
|
||
StopCoroutine(blockSpawnCo);
|
||
|
||
if (blockReactCo != null)
|
||
StopCoroutine(blockReactCo);
|
||
|
||
blockSpawnCo = StartCoroutine(BlockSpawnAnim());
|
||
}
|
||
|
||
void SetupTranslationSlots(string correct)
|
||
{
|
||
List<string> choices = new List<string>();
|
||
choices.Add(correct);
|
||
|
||
int guard = 0;
|
||
|
||
while (choices.Count < 4 && guard < 500)
|
||
{
|
||
guard++;
|
||
|
||
WordEntry randomWord = fullDeck[Random.Range(0, fullDeck.Count)];
|
||
string candidate = GetWordByLang(randomWord, answerLangCode);
|
||
|
||
if (!string.IsNullOrWhiteSpace(candidate) && !choices.Contains(candidate))
|
||
choices.Add(candidate);
|
||
}
|
||
|
||
while (choices.Count < 4)
|
||
choices.Add("-");
|
||
|
||
Shuffle(choices);
|
||
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
if (translationSlotTexts[i] != null)
|
||
translationSlotTexts[i].text = choices[i];
|
||
|
||
if (translationSlots[i] != null)
|
||
{
|
||
translationSlots[i].name = choices[i];
|
||
|
||
Image img = translationSlots[i].GetComponent<Image>();
|
||
|
||
if (img != null)
|
||
img.color = FullAlpha(normalSlotColor);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
void Move(float x)
|
||
{
|
||
if (processing || gameOver || fallingBlock == null) return;
|
||
|
||
Vector2 pos = fallingBlock.anchoredPosition;
|
||
pos.x = Mathf.Clamp(pos.x + x, minX, maxX);
|
||
fallingBlock.anchoredPosition = pos;
|
||
}
|
||
|
||
void FastFall()
|
||
{
|
||
if (processing || gameOver || fallingBlock == null) return;
|
||
|
||
fallingBlock.anchoredPosition += Vector2.down * fastFallStep;
|
||
CheckCollision();
|
||
}
|
||
|
||
// ======================================================
|
||
void CheckCollision()
|
||
{
|
||
if (processing || gameOver) return;
|
||
if (fallingBlock == null || translationSlots == null || translationSlots.Length < 4) return;
|
||
|
||
float slotY = translationSlots[0].anchoredPosition.y;
|
||
float blockY = fallingBlock.anchoredPosition.y;
|
||
float halfH = fallingBlock.rect.height * 0.5f;
|
||
|
||
if (blockY - halfH <= slotY)
|
||
{
|
||
int idx = DetectSlot(fallingBlock.anchoredPosition.x);
|
||
|
||
if (idx == -1)
|
||
{
|
||
Wrong("miss");
|
||
return;
|
||
}
|
||
|
||
Evaluate(idx);
|
||
return;
|
||
}
|
||
|
||
if (blockY + halfH < bottomLimitY)
|
||
Wrong("out");
|
||
}
|
||
|
||
int DetectSlot(float x)
|
||
{
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
if (translationSlots[i] == null) continue;
|
||
|
||
float sx = translationSlots[i].anchoredPosition.x;
|
||
float half = translationSlots[i].rect.width * 0.5f;
|
||
|
||
if (x >= sx - half && x <= sx + half)
|
||
return i;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
// ======================================================
|
||
void Evaluate(int slotIndex)
|
||
{
|
||
if (processing || gameOver) return;
|
||
|
||
processing = true;
|
||
|
||
string selectedAnswer = "";
|
||
|
||
if (translationSlotTexts[slotIndex] != null)
|
||
selectedAnswer = translationSlotTexts[slotIndex].text;
|
||
|
||
bool correct = selectedAnswer == correctAnswer;
|
||
|
||
if (correct)
|
||
HandleCorrect(slotIndex);
|
||
else
|
||
HandleWrong(slotIndex);
|
||
|
||
UpdateUI();
|
||
SaveProgress();
|
||
|
||
Invoke(nameof(SpawnNewWord), nextWordDelay);
|
||
}
|
||
|
||
void HandleCorrect(int slotIndex)
|
||
{
|
||
score += correctBaseScore + combo * comboBonus;
|
||
|
||
combo++;
|
||
correctCount++;
|
||
|
||
if (combo > bestComboThisRun)
|
||
bestComboThisRun = combo;
|
||
|
||
Image img = translationSlots[slotIndex].GetComponent<Image>();
|
||
|
||
if (img != null)
|
||
img.color = FullAlpha(correctSlotColor);
|
||
|
||
PlayCorrect();
|
||
|
||
SetFeedback(LocalizeFeedback("correct") + " +" + correctBaseScore, feedbackCorrectColor);
|
||
|
||
StartSlotAnim(slotIndex, SlotPulse(slotIndex));
|
||
|
||
if (blockReactCo != null)
|
||
StopCoroutine(blockReactCo);
|
||
|
||
blockReactCo = StartCoroutine(BlockCorrectAnim());
|
||
}
|
||
|
||
void HandleWrong(int slotIndex)
|
||
{
|
||
score = Mathf.Max(0, score - wrongPenalty);
|
||
|
||
combo = 0;
|
||
wrongCount++;
|
||
|
||
Image selectedImg = translationSlots[slotIndex].GetComponent<Image>();
|
||
|
||
if (selectedImg != null)
|
||
selectedImg.color = FullAlpha(wrongSlotColor);
|
||
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
if (translationSlotTexts[i] == null || translationSlots[i] == null)
|
||
continue;
|
||
|
||
if (translationSlotTexts[i].text == correctAnswer)
|
||
{
|
||
Image correctImg = translationSlots[i].GetComponent<Image>();
|
||
|
||
if (correctImg != null)
|
||
correctImg.color = FullAlpha(correctSlotColor);
|
||
|
||
StartSlotAnim(i, SlotPulse(i));
|
||
}
|
||
}
|
||
|
||
PlayWrong();
|
||
|
||
SetFeedback(LocalizeFeedback("wrong") + " | " + correctAnswer, feedbackWrongColor);
|
||
|
||
StartSlotAnim(slotIndex, ShakeSlot(slotIndex));
|
||
|
||
if (blockReactCo != null)
|
||
StopCoroutine(blockReactCo);
|
||
|
||
blockReactCo = StartCoroutine(BlockWrongAnim());
|
||
}
|
||
|
||
void Wrong(string reason)
|
||
{
|
||
if (processing || gameOver) return;
|
||
|
||
processing = true;
|
||
|
||
score = Mathf.Max(0, score - wrongPenalty);
|
||
|
||
combo = 0;
|
||
wrongCount++;
|
||
|
||
PlayWrong();
|
||
|
||
SetFeedback(LocalizeFeedback(reason), feedbackWrongColor);
|
||
|
||
if (blockReactCo != null)
|
||
StopCoroutine(blockReactCo);
|
||
|
||
blockReactCo = StartCoroutine(BlockWrongAnim());
|
||
|
||
UpdateUI();
|
||
SaveProgress();
|
||
|
||
Invoke(nameof(SpawnNewWord), nextWordDelay);
|
||
}
|
||
|
||
// ======================================================
|
||
void UpdateUI()
|
||
{
|
||
if (scoreText != null)
|
||
scoreText.text = score.ToString();
|
||
|
||
if (comboText != null)
|
||
comboText.text = "x" + combo;
|
||
}
|
||
|
||
void UpdateTimerUI()
|
||
{
|
||
if (timerText == null) return;
|
||
|
||
timerText.text = Mathf.CeilToInt(timer).ToString();
|
||
|
||
if (timer <= 10f)
|
||
{
|
||
timerText.color = FullAlpha(Color.Lerp(timerWarningColor, Color.white, Mathf.PingPong(Time.time * 3f, 1f)));
|
||
timerText.transform.localScale = Vector3.one * (1f + Mathf.Sin(Time.time * 10f) * 0.05f);
|
||
}
|
||
else
|
||
{
|
||
timerText.color = FullAlpha(timerNormalColor);
|
||
timerText.transform.localScale = Vector3.one;
|
||
}
|
||
}
|
||
|
||
void SetFeedback(string msg, Color c)
|
||
{
|
||
if (feedbackText == null) return;
|
||
|
||
feedbackText.text = msg;
|
||
feedbackText.color = FullAlpha(c);
|
||
|
||
if (feedbackCo != null)
|
||
StopCoroutine(feedbackCo);
|
||
|
||
feedbackCo = StartCoroutine(FeedbackPop());
|
||
}
|
||
|
||
IEnumerator FeedbackPop()
|
||
{
|
||
if (feedbackText == null) yield break;
|
||
|
||
Vector3 normal = Vector3.one;
|
||
Vector3 big = Vector3.one * 1.14f;
|
||
|
||
float t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / 0.12f;
|
||
feedbackText.transform.localScale = Vector3.Lerp(normal, big, EaseOutBack(t));
|
||
yield return null;
|
||
}
|
||
|
||
t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / 0.12f;
|
||
feedbackText.transform.localScale = Vector3.Lerp(big, normal, EaseOutQuad(t));
|
||
yield return null;
|
||
}
|
||
|
||
feedbackText.transform.localScale = normal;
|
||
feedbackCo = null;
|
||
}
|
||
|
||
// ======================================================
|
||
void SaveProgress()
|
||
{
|
||
PlayerPrefs.SetInt("Mod7Skor", score);
|
||
PlayerPrefs.SetInt("Mod7Score", score);
|
||
PlayerPrefs.SetInt("Mod7_LastScore", score);
|
||
PlayerPrefs.SetInt("Mod7_Correct", correctCount);
|
||
PlayerPrefs.SetInt("Mod7_Wrong", wrongCount);
|
||
PlayerPrefs.SetInt("Mod7_LastCombo", combo);
|
||
PlayerPrefs.SetInt("Mod7_BestComboRun", bestComboThisRun);
|
||
PlayerPrefs.SetInt("Mod7_LastTimeLeft", Mathf.CeilToInt(timer));
|
||
|
||
int totalAnswers = correctCount + wrongCount;
|
||
int accuracy = totalAnswers > 0 ? Mathf.RoundToInt((correctCount / (float)totalAnswers) * 100f) : 0;
|
||
|
||
PlayerPrefs.SetInt("Mod7_LastAccuracy", accuracy);
|
||
|
||
int oldMax = PlayerPrefs.GetInt("Mod7_MaxScore", 0);
|
||
|
||
if (score > oldMax)
|
||
PlayerPrefs.SetInt("Mod7_MaxScore", score);
|
||
|
||
int oldBestCombo = PlayerPrefs.GetInt("Mod7_BestCombo", 0);
|
||
|
||
if (bestComboThisRun > oldBestCombo)
|
||
PlayerPrefs.SetInt("Mod7_BestCombo", bestComboThisRun);
|
||
|
||
PlayerPrefs.Save();
|
||
}
|
||
|
||
void GameOver()
|
||
{
|
||
if (gameOver) return;
|
||
|
||
gameOver = true;
|
||
processing = true;
|
||
|
||
SaveProgress();
|
||
|
||
PlayerPrefs.SetString("LastPlayedMod", modKey);
|
||
|
||
PlayerPrefs.SetInt(modKey + "_TotalCorrect", PlayerPrefs.GetInt(modKey + "_TotalCorrect", 0) + correctCount);
|
||
PlayerPrefs.SetInt(modKey + "_TotalWrong", PlayerPrefs.GetInt(modKey + "_TotalWrong", 0) + wrongCount);
|
||
PlayerPrefs.SetInt(modKey + "_PlayCount", PlayerPrefs.GetInt(modKey + "_PlayCount", 0) + 1);
|
||
|
||
PlayerPrefs.SetInt("TotalScore", PlayerPrefs.GetInt("TotalScore", 0) + score);
|
||
|
||
PlayerPrefs.Save();
|
||
|
||
if (!string.IsNullOrEmpty(resultSceneName))
|
||
SceneManager.LoadScene(resultSceneName);
|
||
else
|
||
Debug.LogWarning("resultSceneName boş!");
|
||
}
|
||
|
||
// ======================================================
|
||
void StartSlotAnim(int slotIndex, IEnumerator routine)
|
||
{
|
||
if (slotCoroutines == null) return;
|
||
if (slotIndex < 0 || slotIndex >= slotCoroutines.Length) return;
|
||
|
||
if (slotCoroutines[slotIndex] != null)
|
||
StopCoroutine(slotCoroutines[slotIndex]);
|
||
|
||
slotCoroutines[slotIndex] = StartCoroutine(routine);
|
||
}
|
||
|
||
IEnumerator SlotPulse(int slotIndex)
|
||
{
|
||
if (translationSlots == null) yield break;
|
||
if (slotIndex < 0 || slotIndex >= translationSlots.Length) yield break;
|
||
if (translationSlots[slotIndex] == null) yield break;
|
||
|
||
Vector3 normal = originalSlotScales[slotIndex];
|
||
Vector3 big = normal * slotPulseScale;
|
||
|
||
float t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / Mathf.Max(0.01f, slotPulseTime);
|
||
translationSlots[slotIndex].localScale = Vector3.Lerp(normal, big, EaseOutQuad(t));
|
||
yield return null;
|
||
}
|
||
|
||
t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / Mathf.Max(0.01f, slotPulseTime);
|
||
translationSlots[slotIndex].localScale = Vector3.Lerp(big, normal, EaseOutQuad(t));
|
||
yield return null;
|
||
}
|
||
|
||
translationSlots[slotIndex].localScale = normal;
|
||
slotCoroutines[slotIndex] = null;
|
||
}
|
||
|
||
IEnumerator ShakeSlot(int slotIndex)
|
||
{
|
||
if (translationSlots == null) yield break;
|
||
if (slotIndex < 0 || slotIndex >= translationSlots.Length) yield break;
|
||
if (translationSlots[slotIndex] == null) yield break;
|
||
|
||
Vector2 origin = originalSlotPositions[slotIndex];
|
||
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < shakeTime)
|
||
{
|
||
float power = 1f - elapsed / shakeTime;
|
||
float x = Random.Range(-1f, 1f) * shakeAmount * power;
|
||
|
||
translationSlots[slotIndex].anchoredPosition = origin + new Vector2(x, 0f);
|
||
|
||
elapsed += Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
translationSlots[slotIndex].anchoredPosition = origin;
|
||
slotCoroutines[slotIndex] = null;
|
||
}
|
||
|
||
IEnumerator BlockSpawnAnim()
|
||
{
|
||
if (fallingBlock == null) yield break;
|
||
|
||
Vector3 target = originalBlockScale;
|
||
Vector3 small = originalBlockScale * blockSpawnScale;
|
||
|
||
fallingBlock.localScale = small;
|
||
|
||
float t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / Mathf.Max(0.01f, blockSpawnAnimTime);
|
||
fallingBlock.localScale = Vector3.Lerp(small, target, EaseOutBack(t));
|
||
yield return null;
|
||
}
|
||
|
||
fallingBlock.localScale = target;
|
||
}
|
||
|
||
IEnumerator BlockCorrectAnim()
|
||
{
|
||
if (fallingBlock == null) yield break;
|
||
|
||
Vector3 normal = originalBlockScale;
|
||
Vector3 big = originalBlockScale * 1.08f;
|
||
|
||
float t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / 0.10f;
|
||
fallingBlock.localScale = Vector3.Lerp(normal, big, EaseOutQuad(t));
|
||
yield return null;
|
||
}
|
||
|
||
t = 0f;
|
||
|
||
while (t < 1f)
|
||
{
|
||
t += Time.deltaTime / 0.10f;
|
||
fallingBlock.localScale = Vector3.Lerp(big, normal, EaseOutQuad(t));
|
||
yield return null;
|
||
}
|
||
|
||
fallingBlock.localScale = normal;
|
||
}
|
||
|
||
IEnumerator BlockWrongAnim()
|
||
{
|
||
if (fallingBlock == null) yield break;
|
||
|
||
Vector2 origin = fallingBlock.anchoredPosition;
|
||
|
||
float elapsed = 0f;
|
||
float duration = 0.28f;
|
||
|
||
while (elapsed < duration)
|
||
{
|
||
float power = 1f - elapsed / duration;
|
||
float x = Random.Range(-1f, 1f) * 14f * power;
|
||
|
||
fallingBlock.anchoredPosition = origin + new Vector2(x, 0f);
|
||
|
||
elapsed += Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
fallingBlock.anchoredPosition = origin;
|
||
}
|
||
|
||
// ======================================================
|
||
void PlayClick()
|
||
{
|
||
if (sfxSource != null && clickClip != null)
|
||
sfxSource.PlayOneShot(clickClip);
|
||
}
|
||
|
||
void PlayCorrect()
|
||
{
|
||
if (sfxSource != null && correctClip != null)
|
||
sfxSource.PlayOneShot(correctClip);
|
||
}
|
||
|
||
void PlayWrong()
|
||
{
|
||
if (sfxSource != null && wrongClip != null)
|
||
sfxSource.PlayOneShot(wrongClip);
|
||
}
|
||
|
||
// ======================================================
|
||
string GetQuestionLanguage()
|
||
{
|
||
string q = PlayerPrefs.GetString("QuestionLangCode", "");
|
||
q = NormalizeLang(q);
|
||
|
||
if (!string.IsNullOrWhiteSpace(q) && q != "en")
|
||
return q;
|
||
|
||
int appId = PlayerPrefs.GetInt("AppLanguage", -1);
|
||
|
||
if (appId >= 0)
|
||
{
|
||
string appLang = LangIdToCode(appId);
|
||
|
||
if (appLang != "en")
|
||
return appLang;
|
||
}
|
||
|
||
string appString = NormalizeLang(PlayerPrefs.GetString("AppLanguage", "tr"));
|
||
|
||
if (appString != "en")
|
||
return appString;
|
||
|
||
return "tr";
|
||
}
|
||
|
||
string GetUILanguage()
|
||
{
|
||
int appId = PlayerPrefs.GetInt("AppLanguage", -1);
|
||
|
||
if (appId >= 0)
|
||
return LangIdToCode(appId);
|
||
|
||
return NormalizeLang(PlayerPrefs.GetString("AppLanguage", "en"));
|
||
}
|
||
|
||
string LangIdToCode(int id)
|
||
{
|
||
return id switch
|
||
{
|
||
0 => "tr",
|
||
2 => "es",
|
||
3 => "de",
|
||
4 => "fr",
|
||
5 => "pt",
|
||
_ => "en"
|
||
};
|
||
}
|
||
|
||
string NormalizeLang(string raw)
|
||
{
|
||
if (string.IsNullOrEmpty(raw))
|
||
return "en";
|
||
|
||
raw = raw.Trim().ToLower();
|
||
|
||
if (raw == "tr" || raw == "turkish" || raw == "tr-tr")
|
||
return "tr";
|
||
|
||
if (raw == "en" || raw == "english" || raw.StartsWith("en-"))
|
||
return "en";
|
||
|
||
if (raw == "es" || raw == "spanish" || raw == "es-es")
|
||
return "es";
|
||
|
||
if (raw == "de" || raw == "german" || raw == "de-de")
|
||
return "de";
|
||
|
||
if (raw == "fr" || raw == "french" || raw == "fr-fr")
|
||
return "fr";
|
||
|
||
if (raw == "pt" || raw == "portuguese" || raw == "pt-pt" || raw == "pt-br")
|
||
return "pt";
|
||
|
||
return "en";
|
||
}
|
||
|
||
string GetWordByLang(WordEntry w, string lang)
|
||
{
|
||
return lang switch
|
||
{
|
||
"tr" => w.tr,
|
||
"es" => w.es,
|
||
"de" => w.de,
|
||
"fr" => w.fr,
|
||
"pt" => w.pt,
|
||
_ => w.en
|
||
};
|
||
}
|
||
|
||
string LocalizeFeedback(string key)
|
||
{
|
||
return key switch
|
||
{
|
||
"correct" => uiLangCode switch
|
||
{
|
||
"tr" => "Doğru!",
|
||
"es" => "¡Correcto!",
|
||
"de" => "Richtig!",
|
||
"fr" => "Correct!",
|
||
"pt" => "Correto!",
|
||
_ => "Correct!"
|
||
},
|
||
|
||
"wrong" => uiLangCode switch
|
||
{
|
||
"tr" => "Yanlış!",
|
||
"es" => "¡Incorrecto!",
|
||
"de" => "Falsch!",
|
||
"fr" => "Faux!",
|
||
"pt" => "Errado!",
|
||
_ => "Wrong!"
|
||
},
|
||
|
||
"miss" => uiLangCode switch
|
||
{
|
||
"tr" => "Kaçırdın!",
|
||
"es" => "Fallaste!",
|
||
"de" => "Verpasst!",
|
||
"fr" => "Raté!",
|
||
"pt" => "Perdeu!",
|
||
_ => "Miss!"
|
||
},
|
||
|
||
"out" => uiLangCode switch
|
||
{
|
||
"tr" => "Ekran dışı!",
|
||
"es" => "Fuera!",
|
||
"de" => "Außerhalb!",
|
||
"fr" => "Hors écran!",
|
||
"pt" => "Fora!",
|
||
_ => "Out!"
|
||
},
|
||
|
||
_ => key
|
||
};
|
||
}
|
||
|
||
// ======================================================
|
||
Color FullAlpha(Color c)
|
||
{
|
||
c.a = 1f;
|
||
return c;
|
||
}
|
||
|
||
float EaseOutQuad(float t)
|
||
{
|
||
t = Mathf.Clamp01(t);
|
||
return 1f - (1f - t) * (1f - t);
|
||
}
|
||
|
||
float EaseOutBack(float t)
|
||
{
|
||
t = Mathf.Clamp01(t);
|
||
|
||
float c1 = 1.70158f;
|
||
float c3 = c1 + 1f;
|
||
|
||
return 1f + c3 * Mathf.Pow(t - 1f, 3f) + c1 * Mathf.Pow(t - 1f, 2f);
|
||
}
|
||
|
||
void Shuffle<T>(List<T> list)
|
||
{
|
||
for (int i = list.Count - 1; i > 0; i--)
|
||
{
|
||
int r = Random.Range(0, i + 1);
|
||
T temp = list[i];
|
||
list[i] = list[r];
|
||
list[r] = temp;
|
||
}
|
||
}
|
||
} |