|
@@ -1,259 +1,144 @@
|
|
|
using UnityEngine;
|
|
|
using TMPro;
|
|
|
-using System.Collections.Generic;
|
|
|
+using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
|
|
|
public class MathQuizManager : MonoBehaviour
|
|
|
{
|
|
|
- [Header("UI References")]
|
|
|
- [SerializeField] private TextMeshProUGUI questionText; // 显示题目的文本
|
|
|
- [SerializeField] private TextMeshProUGUI coinText; // 显示金币的文本
|
|
|
- [SerializeField] private GameObject answerPrefab; // 答案选项预制体
|
|
|
-
|
|
|
- [Header("Game Settings")]
|
|
|
- [SerializeField] private float wrongAnswerPenalty = 0.1f; // 错误答案的攻击力减弱比例
|
|
|
- [SerializeField] private int correctAnswerReward = 10; // 正确答案的金币奖励
|
|
|
- [SerializeField] private float answerLifeTime = 15f; // 答案选项存在时间
|
|
|
- [SerializeField] private float floatSpeed = 0.3f; // 答案选项上浮速度
|
|
|
- [SerializeField] private float correctAnswerBoost = 0.2f; // 正确答案的攻击力加强比例
|
|
|
- [SerializeField] private float boostDuration = 5f; // 攻击力加强持续时间
|
|
|
- [SerializeField] private float answerSpawnRadius = 3f; // 答案生成半径
|
|
|
- [SerializeField] private float minAnswerDistance = 1.5f; // 答案最小生成距离
|
|
|
- [SerializeField] private int maxAnswersOnScreen = 6; // 屏幕上最多显示的答案数量
|
|
|
- [SerializeField] private float correctAnswerChance = 0.3f;// 正确答案生成概率
|
|
|
-
|
|
|
- // 公共方法用于获取配置值
|
|
|
- public float GetAnswerLifeTime() => answerLifeTime;
|
|
|
- public float GetFloatSpeed() => floatSpeed;
|
|
|
- public float GetCorrectAnswerBoost() => correctAnswerBoost;
|
|
|
- public float GetBoostDuration() => boostDuration;
|
|
|
-
|
|
|
- // 私有变量
|
|
|
- private int currentAnswer; // 当前题目的正确答案
|
|
|
- private int coins; // 玩家的金币数量
|
|
|
- private readonly List<Warrior> penalizedWarriors = new List<Warrior>(); // 受到惩罚的战士
|
|
|
- private readonly List<AnswerOption> activeAnswers = new List<AnswerOption>(); // 当前活动的答案选项
|
|
|
- private Vector3 lastEnemyDeathPosition; // 存储最后一个敌人死亡的位置
|
|
|
- private int enemyDeathCount = 0; // 敌人死亡计数
|
|
|
-
|
|
|
- private void Start()
|
|
|
- {
|
|
|
- InitializeGame();
|
|
|
- }
|
|
|
-
|
|
|
- private void InitializeGame()
|
|
|
- {
|
|
|
- GenerateNewProblem();
|
|
|
- UpdateCoinDisplay();
|
|
|
- }
|
|
|
-
|
|
|
- // 生成新的数学题目
|
|
|
- public void GenerateNewProblem()
|
|
|
- {
|
|
|
- int num1, num2;
|
|
|
- int operationType = Random.Range(0, 2); // 0:加法, 1:减法
|
|
|
-
|
|
|
- switch (operationType)
|
|
|
- {
|
|
|
- case 0: // 加法
|
|
|
- num1 = Random.Range(1, 11); // 1-10
|
|
|
- num2 = Random.Range(1, 11); // 1-10
|
|
|
- currentAnswer = num1 + num2;
|
|
|
- questionText.text = $"{num1} + {num2} = ?";
|
|
|
- break;
|
|
|
-
|
|
|
- case 1: // 减法
|
|
|
- num1 = Random.Range(1, 11); // 1-10
|
|
|
- num2 = Random.Range(1, num1 + 1); // 确保结果为正数
|
|
|
- currentAnswer = num1 - num2;
|
|
|
- questionText.text = $"{num1} - {num2} = ?";
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 检查答案
|
|
|
- public bool CheckAnswer(AnswerOption answerOption)
|
|
|
- {
|
|
|
- bool isCorrect = answerOption.GetAnswerValue() == currentAnswer;
|
|
|
-
|
|
|
- if (isCorrect)
|
|
|
+ [Header("UI")]
|
|
|
+ [SerializeField] private TextMeshProUGUI questionText;
|
|
|
+ [SerializeField] private TextMeshProUGUI coinText;
|
|
|
+ [SerializeField] private GameObject answerPrefab;
|
|
|
+
|
|
|
+ [Header("参数")]
|
|
|
+ [SerializeField] private float wrongPenalty = 0.1f;
|
|
|
+ [SerializeField] private int correctReward = 10;
|
|
|
+ [SerializeField] private float answerLife = 15f;
|
|
|
+ [SerializeField] private float floatSpeed = 0.3f;
|
|
|
+ [SerializeField] private float correctBoost = 0.2f;
|
|
|
+ [SerializeField] private float boostTime = 5f;
|
|
|
+ [SerializeField] private float spawnRadius = 3f;
|
|
|
+ [SerializeField] private float minSpawnDist = 1.5f;
|
|
|
+ [SerializeField] private int maxAnswers = 6;
|
|
|
+ [SerializeField] private float correctChance = 0.3f;
|
|
|
+
|
|
|
+ private int currentAnswer;
|
|
|
+ private int coins;
|
|
|
+ private List<Warrior> penalized = new();
|
|
|
+ private List<AnswerOption> answers = new();
|
|
|
+ private Vector3 lastEnemyPos;
|
|
|
+ private int deathCount = 0;
|
|
|
+
|
|
|
+ void Start() { NewProblem(); UpdateCoin(); }
|
|
|
+
|
|
|
+ void NewProblem()
|
|
|
+ {
|
|
|
+ int a = Random.Range(1, 11), b = Random.Range(1, 11);
|
|
|
+ if (Random.value < 0.5f) // 加法
|
|
|
{
|
|
|
- HandleCorrectAnswer();
|
|
|
+ currentAnswer = a + b;
|
|
|
+ questionText.text = $"{a} + {b} = ?";
|
|
|
}
|
|
|
- else
|
|
|
+ else // 减法
|
|
|
{
|
|
|
- HandleWrongAnswer(answerOption);
|
|
|
+ if (b > a) (a, b) = (b, a);
|
|
|
+ currentAnswer = a - b;
|
|
|
+ questionText.text = $"{a} - {b} = ?";
|
|
|
}
|
|
|
-
|
|
|
- return isCorrect;
|
|
|
}
|
|
|
|
|
|
- private void HandleCorrectAnswer()
|
|
|
+ public bool CheckAnswer(AnswerOption opt)
|
|
|
{
|
|
|
- AddCoins(correctAnswerReward);
|
|
|
- ResetAllWarriorsPenalties();
|
|
|
- ClearAllAnswers();
|
|
|
- GenerateNewProblem();
|
|
|
+ bool correct = opt.GetAnswerValue() == currentAnswer;
|
|
|
+ if (correct) OnCorrect();
|
|
|
+ else OnWrong(opt);
|
|
|
+ return correct;
|
|
|
}
|
|
|
|
|
|
- private void HandleWrongAnswer(AnswerOption answerOption)
|
|
|
+ void OnCorrect()
|
|
|
{
|
|
|
- ApplyPenaltyToAllWarriors();
|
|
|
- RemoveAnswer(answerOption);
|
|
|
- Destroy(answerOption.gameObject);
|
|
|
+ coins += correctReward;
|
|
|
+ UpdateCoin();
|
|
|
+ ResetPenalties();
|
|
|
+ ClearAnswers();
|
|
|
+ NewProblem();
|
|
|
}
|
|
|
|
|
|
- // 清除所有答案选项
|
|
|
- private void ClearAllAnswers()
|
|
|
+ void OnWrong(AnswerOption opt)
|
|
|
{
|
|
|
- foreach (AnswerOption answer in activeAnswers.ToList())
|
|
|
- {
|
|
|
- if (answer != null)
|
|
|
- {
|
|
|
- Destroy(answer.gameObject);
|
|
|
- }
|
|
|
- }
|
|
|
- activeAnswers.Clear();
|
|
|
+ ApplyPenalty();
|
|
|
+ RemoveAnswer(opt);
|
|
|
+ Destroy(opt.gameObject);
|
|
|
}
|
|
|
|
|
|
- // 从活动答案列表中移除答案
|
|
|
- public void RemoveAnswer(AnswerOption answer)
|
|
|
+ void ClearAnswers()
|
|
|
{
|
|
|
- activeAnswers.Remove(answer);
|
|
|
+ foreach (var a in answers) if (a) Destroy(a.gameObject);
|
|
|
+ answers.Clear();
|
|
|
}
|
|
|
|
|
|
- // 添加金币
|
|
|
- private void AddCoins(int amount)
|
|
|
- {
|
|
|
- coins += amount;
|
|
|
- UpdateCoinDisplay();
|
|
|
- }
|
|
|
+ public void RemoveAnswer(AnswerOption a) => answers.Remove(a);
|
|
|
|
|
|
- // 更新金币显示
|
|
|
- private void UpdateCoinDisplay()
|
|
|
- {
|
|
|
- if (coinText != null)
|
|
|
- {
|
|
|
- coinText.text = $" {coins}";
|
|
|
- }
|
|
|
- }
|
|
|
+ void UpdateCoin() => coinText.text = coins.ToString();
|
|
|
|
|
|
- // 对所有我方战士施加攻击力减弱效果
|
|
|
- private void ApplyPenaltyToAllWarriors()
|
|
|
+ void ApplyPenalty()
|
|
|
{
|
|
|
- var warriors = FindObjectsOfType<Warrior>();
|
|
|
- foreach (Warrior warrior in warriors)
|
|
|
- {
|
|
|
- if (!warrior.IsEnemy && !penalizedWarriors.Contains(warrior))
|
|
|
+ foreach (var w in FindObjectsOfType<Warrior>())
|
|
|
+ if (!w.IsEnemy && !penalized.Contains(w))
|
|
|
{
|
|
|
- warrior.ApplyDamagePenalty(wrongAnswerPenalty);
|
|
|
- penalizedWarriors.Add(warrior);
|
|
|
+ w.ApplyDamagePenalty(wrongPenalty);
|
|
|
+ penalized.Add(w);
|
|
|
}
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- // 重置所有战士的攻击力
|
|
|
- private void ResetAllWarriorsPenalties()
|
|
|
+ void ResetPenalties()
|
|
|
{
|
|
|
- foreach (Warrior warrior in penalizedWarriors.ToList())
|
|
|
- {
|
|
|
- if (warrior != null)
|
|
|
- {
|
|
|
- warrior.ResetDamagePenalty();
|
|
|
- }
|
|
|
- }
|
|
|
- penalizedWarriors.Clear();
|
|
|
+ foreach (var w in penalized) if (w) w.ResetDamagePenalty();
|
|
|
+ penalized.Clear();
|
|
|
}
|
|
|
|
|
|
- // 当敌人死亡时调用
|
|
|
- public void OnEnemyDeath(Vector3 position, int enemyValue)
|
|
|
+ public void OnEnemyDeath(Vector3 pos, int _)
|
|
|
{
|
|
|
- lastEnemyDeathPosition = position;
|
|
|
- enemyDeathCount++;
|
|
|
-
|
|
|
- // 每死两个敌人生成一个答案
|
|
|
- if (enemyDeathCount >= 2)
|
|
|
+ lastEnemyPos = pos;
|
|
|
+ if (++deathCount >= 2)
|
|
|
{
|
|
|
- GenerateAnswerOption();
|
|
|
- enemyDeathCount = 0; // 重置计数
|
|
|
+ SpawnAnswer();
|
|
|
+ deathCount = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 生成单个答案选项
|
|
|
- private void GenerateAnswerOption()
|
|
|
+ void SpawnAnswer()
|
|
|
{
|
|
|
- // 如果场上答案数量超过限制,移除最旧的答案
|
|
|
- if (activeAnswers.Count >= maxAnswersOnScreen)
|
|
|
+ if (answers.Count >= maxAnswers) RemoveOldest();
|
|
|
+ int val = Random.value < correctChance ? currentAnswer : Mathf.Clamp(currentAnswer + Random.Range(-3, 4), 1, 20);
|
|
|
+ Vector3 p = lastEnemyPos + Quaternion.Euler(0, Random.Range(0, 360f), 0) * (Vector3.forward * Random.Range(minSpawnDist, spawnRadius));
|
|
|
+ var obj = Instantiate(answerPrefab, p, Quaternion.identity);
|
|
|
+ var opt = obj.GetComponent<AnswerOption>();
|
|
|
+ if (opt)
|
|
|
{
|
|
|
- RemoveOldestAnswer();
|
|
|
+ opt.Initialize(this, val, val == currentAnswer);
|
|
|
+ answers.Add(opt);
|
|
|
}
|
|
|
-
|
|
|
- // 生成答案值
|
|
|
- int answerValue = GenerateAnswerValue();
|
|
|
-
|
|
|
- // 计算生成位置
|
|
|
- Vector3 spawnPosition = CalculateSpawnPosition();
|
|
|
-
|
|
|
- // 生成答案选项
|
|
|
- SpawnAnswerOption(answerValue, spawnPosition);
|
|
|
- if (!PlayerPrefs.HasKey("FirstQuizTutorial"))
|
|
|
- {
|
|
|
- PlayerPrefs.SetInt("FirstQuizTutorial", 1);
|
|
|
- var Tutorial =FindObjectOfType<Tutorial>();
|
|
|
- Tutorial.ShowQuizTutorial(answerPrefab.transform);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RemoveOldestAnswer()
|
|
|
- {
|
|
|
- if (activeAnswers.Count > 0)
|
|
|
+ else
|
|
|
{
|
|
|
- AnswerOption oldestAnswer = activeAnswers[0];
|
|
|
- activeAnswers.RemoveAt(0);
|
|
|
- if (oldestAnswer != null)
|
|
|
- {
|
|
|
- Destroy(oldestAnswer.gameObject);
|
|
|
- }
|
|
|
+ Destroy(obj);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private int GenerateAnswerValue()
|
|
|
- {
|
|
|
- bool isCorrect = Random.value < correctAnswerChance;
|
|
|
- if (isCorrect)
|
|
|
+ // 新手引导
|
|
|
+ if (!PlayerPrefs.HasKey("FirstQuizTutorial"))
|
|
|
{
|
|
|
- return currentAnswer;
|
|
|
+ PlayerPrefs.SetInt("FirstQuizTutorial", 1);
|
|
|
+ var tut = FindObjectOfType<Tutorial>();
|
|
|
+ if (tut) tut.ShowQuizTutorial(answerPrefab.transform);
|
|
|
}
|
|
|
-
|
|
|
- int difference = Random.Range(-3, 4);
|
|
|
- int wrongAnswer = currentAnswer + difference;
|
|
|
- return Mathf.Clamp(wrongAnswer, 1, 20);
|
|
|
- }
|
|
|
-
|
|
|
- private Vector3 CalculateSpawnPosition()
|
|
|
- {
|
|
|
- float randomAngle = Random.Range(0f, 360f);
|
|
|
- float randomRadius = Random.Range(minAnswerDistance, answerSpawnRadius);
|
|
|
- float x = Mathf.Cos(randomAngle * Mathf.Deg2Rad) * randomRadius;
|
|
|
- float z = Mathf.Sin(randomAngle * Mathf.Deg2Rad) * randomRadius;
|
|
|
- return lastEnemyDeathPosition + new Vector3(x, 0f, z);
|
|
|
}
|
|
|
|
|
|
- private void SpawnAnswerOption(int answerValue, Vector3 position)
|
|
|
+ void RemoveOldest()
|
|
|
{
|
|
|
- GameObject answerObj = Instantiate(answerPrefab, position, Quaternion.identity);
|
|
|
- AnswerOption answerOption = answerObj.GetComponent<AnswerOption>();
|
|
|
-
|
|
|
- if (answerOption != null)
|
|
|
- {
|
|
|
- bool isCorrect = answerValue == currentAnswer;
|
|
|
- answerOption.Initialize(this, answerValue, isCorrect);
|
|
|
- activeAnswers.Add(answerOption);
|
|
|
- }
|
|
|
- else
|
|
|
+ if (answers.Count > 0)
|
|
|
{
|
|
|
- Debug.LogError("MathQuizManager: AnswerOption component not found on prefab!");
|
|
|
- Destroy(answerObj);
|
|
|
+ var oldest = answers[0];
|
|
|
+ answers.RemoveAt(0);
|
|
|
+ if (oldest) Destroy(oldest.gameObject);
|
|
|
}
|
|
|
}
|
|
|
}
|