一、原版修改
1、导入资源
Unity Learn | 3D Beginner: Complete Project | URP
2、设置Scene
删除SampleScene,打开UnityTechnologies-3DBeginnerComplete下的MainScene
3、降低音量
(1) 打开Hierarchy面板上的Audio降低音量
(2) 打开Prefabs文件夹,找到Ghost,降低音量
4、给FaderCanvas添加组件
(1) Canvas Scaler
(2) Graphic Raycaster
二、编辑答题面板
(1) UI-Image,命名为Questions。
(2) 选中导入的图片,然后在Inspector面板中将Texture Type设置为Sprite (2D and UI)
(3) 更改Questions的Image,Transform
(4) 添加按钮,QuitBtn(以隐藏答题面板)
(5) 添OptionBtnA(ABCD)
(6) 新增文本:TipAnswerText、TitleText
三、建立和调用题库
1、创建Question.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Question
{public string titleIndex, questiont, correctOptionIndex; // 题号,问题文本, 正确答案的索引(1表示A,2表示B,依此类推)public string optionA, optionB, optionC, optionD;// 选项// 构造函数(可选),用于初始化问题public Questions(string titleInx, string q, string optsA, string optsB, string optsC, string optsD, string correctIndex){titleIndex = titleInx;questiont = q;optionA = optsA;optionB = optsB;optionC = optsC;optionD = optsD;correctOptionIndex = correctIndex;}// 检查答案是否正确public bool CheckAnswer(string playerChoice){return playerChoice == correctOptionIndex;}
}
或——建议这个
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[System.Serializable]
public class Question
{public string questionTitle, questionTitleIndex; // 问题文本、题号public string[] options = new string[4]; // 选项数组,假设每个问题都有4个选项public int correctOptionIndex; // 正确答案的索引(1表示A,2表示B,依此类推)// 构造函数(可选),用于初始化问题public Question(string questionID, string questionText, string[] options, int correctOptionIndex){this.questionTitleIndex = questionID;this.questionTitle = questionText;this.options = options;this.correctOptionIndex = correctOptionIndex;}
}
2、加载题库并随机显示
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using TMPro;
using UnityEngine;public class LoadQuestions : MonoBehaviour
{//存放问题的容器public TextMeshProUGUI titleText, optionTextA, optionTextB, optionTextC, optionTextD;private void Start(){LoadQuestionsFromFile("YW");}public void LoadQuestionsFromFile(string filePath){//加载位于Resources文件夹中的YW文件TextAsset textFile = Resources.Load<TextAsset>("YW");//若文件加载成功if (textFile != null){Debug.Log("YW文件加载成功");//将textFile中储存的文本按行拆分成一个字符串数组,每个数组元素都表示一行文本("\n":换行符)//string[] lines = textFile.text.Split("new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None");//不同系统或编辑器可能使用的不同换行符格式string[] lines = textFile.text.Split("\n");// 获取一个随机的索引int randomIndex = UnityEngine.Random.Range(0, lines.Length);//从lines中获取一个随机元素,将之赋值给linestring line = lines[randomIndex];//解析line中的字段string[] fields = line.Split(":");// 创建问题对象Questions question = new(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6]);titleText.text = question.questiont;optionTextA.text = question.optionA;optionTextB.text = question.optionB;optionTextC.text = question.optionC;optionTextD.text = question.optionD;}else { Debug.Log("YW文件加载失败"); }}
}
或 ——建议下面这个(需要在Unity编辑器中设置选项按钮的On Click
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class LoadQuestions : MonoBehaviour
{public Text titleText;public Button[] optionButtons;private string[] lines; // 保存所有问题的数组private List<Question> questions = new List<Question>(); // 问题列表private Question currentQuestion;private void Start(){LoadQuestionsFromFile();UpdateQuestionUI();}//加载题库public void LoadQuestionsFromFile(){TextAsset textFile = Resources.Load<TextAsset>("test_01");if (textFile != null){lines = textFile.text.Split("\n");for (int i = 0; i < lines.Length; i++){string[] fields = lines[i].Split(":");if (fields.Length >= 7){string questionID = fields[0];string questionTitle = fields[1];string[] options = new string[] { fields[2], fields[3], fields[4], fields[5] };//int correctOptionIndex = int.Parse(fields[6]); // 将正确答案的索引作为整数类型存储int correctOptionIndex;if (int.TryParse(fields[6], out correctOptionIndex)) // 使用 TryParse 避免异常{questions.Add(new Question(questionID, questionTitle, options, correctOptionIndex));}else{Debug.LogError("文件格式错误:第 " + (i + 1) + " 行中的正确答案索引不是有效的整数");}}else{Debug.LogError("文件格式错误:第 " + (Array.IndexOf(lines, lines) + 1) + " 行没有足够的字段");}}}}//随机显示public void UpdateQuestionUI(){int randomIndex = UnityEngine.Random.Range(0, questions.Count - 1);currentQuestion = questions[randomIndex];string question = currentQuestion.questionTitle;string[] options = currentQuestion.options; // 获取选项数组int correctIndex = currentQuestion.correctOptionIndex; // 获取正确选项的索引titleText.text = question;for (int i = 0; i < optionButtons.Length; i++){optionButtons[i].onClick.RemoveAllListeners();int index = i; // 创建局部变量保存索引值optionButtons[i].GetComponentInChildren<Text>().text = options[i];}}
}
3、判断正误
public void CheckAnswer(int optionIndex){if (optionIndex == currentQuestion.correctOptionIndex){Debug.Log("回答正确!" + optionIndex + currentQuestion.correctOptionIndex);}else{Debug.Log("回答错误!" + optionIndex + currentQuestion.correctOptionIndex);}}
4、禁用按钮
public void UpdateQuestionUI()
{int randomIndex = UnityEngine.Random.Range(0, questions.Count - 1);currentQuestion = questions[randomIndex];string question = currentQuestion.questionTitle;string[] options = currentQuestion.options; // 获取选项数组int correctIndex = currentQuestion.correctOptionIndex; // 获取正确选项的索引titleText.text = question;for (int i = 0; i < optionButtons.Length; i++){optionButtons[i].onClick.RemoveAllListeners();int index = i; // 创建局部变量保存索引值optionButtons[i].GetComponentInChildren<Text>().text = options[i];optionButtons[i].interactable = true; // 启用按钮}
}
public void CheckAnswer(int optionIndex)
{for (int i = 0; i < optionButtons.Length; i++){optionButtons[i].interactable = false; // 禁用按钮}if (optionIndex == currentQuestion.correctOptionIndex){Debug.Log("回答正确!" + optionIndex + currentQuestion.correctOptionIndex);}else{Debug.Log("回答错误!" + optionIndex + currentQuestion.correctOptionIndex);}
}
5、记录答过的题目,并从题库中清除
public void LoadQuestionsFromFile()
{TextAsset textFile = Resources.Load<TextAsset>("test_01");if (textFile != null){//确保跨平台兼容性拆分文本string[] lines = textFile.text.Split(new string[] { System.Environment.NewLine }, System.StringSplitOptions.RemoveEmptyEntries);for (int i = 0; i < lines.Length; i++){string[] fields = lines[i].Split(':');if (questions.Count > 0){usedQuestions.Clear();}if (fields.Length >= 7){string questionID = fields[0];string questionTitle = fields[1];string[] options = new string[] { fields[2], fields[3], fields[4], fields[5] };int correctOptionIndex;if (int.TryParse(fields[6], out correctOptionIndex)){questions.Add(new Question(questionID, questionTitle, options, correctOptionIndex));}else{Debug.LogErrorFormat("文件格式错误:第 {0} 行中的正确答案索引不是有效的整数", i + 1);}}else{Debug.LogErrorFormat("文件格式错误:第 {0} 行没有足够的字段", i + 1);}}}else{Debug.LogError("找不到文本文件 test_01");}
}
完整脚本
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class LoadQuestions : MonoBehaviour
{public Text titleText;public Button[] optionButtons;private string[] lines; // 保存所有问题的数组private List<Question> questions = new List<Question>(); // 问题列表private Question currentQuestion;private void Start(){LoadQuestionsFromFile();UpdateQuestionUI();}public void LoadQuestionsFromFile(){TextAsset textFile = Resources.Load<TextAsset>("test_01");if (textFile != null){lines = textFile.text.Split("\n");for (int i = 0; i < lines.Length; i++){string[] fields = lines[i].Split(":");if (fields.Length >= 7){string questionID = fields[0];string questionTitle = fields[1];string[] options = new string[] { fields[2], fields[3], fields[4], fields[5] };//int correctOptionIndex = int.Parse(fields[6]); // 将正确答案的索引作为整数类型存储int correctOptionIndex;if (int.TryParse(fields[6], out correctOptionIndex)) // 使用 TryParse 避免异常{questions.Add(new Question(questionID, questionTitle, options, correctOptionIndex));}else{Debug.LogError("文件格式错误:第 " + (i + 1) + " 行中的正确答案索引不是有效的整数");}}else{Debug.LogError("文件格式错误:第 " + (Array.IndexOf(lines, lines) + 1) + " 行没有足够的字段");}}}}public void UpdateQuestionUI(){int randomIndex = UnityEngine.Random.Range(0, questions.Count - 1);currentQuestion = questions[randomIndex];string question = currentQuestion.questionTitle;string[] options = currentQuestion.options; // 获取选项数组int correctIndex = currentQuestion.correctOptionIndex; // 获取正确选项的索引titleText.text = question;for (int i = 0; i < optionButtons.Length; i++){optionButtons[i].onClick.RemoveAllListeners();int index = i; // 创建局部变量保存索引值optionButtons[i].GetComponentInChildren<Text>().text = options[i];optionButtons[i].interactable = true; // 启用按钮}}public void CheckAnswer(int optionIndex){for (int i = 0; i < optionButtons.Length; i++){optionButtons[i].interactable = false; // 禁用按钮}if (optionIndex == currentQuestion.correctOptionIndex){Debug.Log("回答正确!" + optionIndex + currentQuestion.correctOptionIndex);}else{Debug.Log("回答错误!" + optionIndex + currentQuestion.correctOptionIndex);}}
}
四、收集事件
1、新增文本
(1) 新增文本:ItemsText, TipText(可设置有透明度的黑色背景)
(2) 给JohnLemon添加标签Player
2、更改Enemies下的子物体
(1) 复制PointOfView预制体,重命名为ExitEventTriggers
(2) 删除Ghost (3)的PointOfView子物体,在该位置添加ExitEventTriggers子物体
(3) 编辑PointOfView预制体,删除Observer.cs组件,添加CollectedItems.cs组件
3、新建CollectedItems.cs
(1) 设置隐藏答题面板按钮,同时调用新题目
public GameObject questionsPanel;
public Button quitBtn;
public LoadQuestions LoadQuestions;void Start()
{questionsPanel.SetActive(false);
}public void HideQuestionPanel()
{if (questionsPanel != null){questionsPanel.SetActive(false); // 隐藏物体LoadQuestions.UpdateQuestionUI();}
}
(2) 增加显示背包物品
public Text itemsText;
static int nucleus = 0, mitochondria = 0, GolgiApparatus = 0, endoplasmicReticulum = 0, ribosome = 0;void Start()
{ItemsUpdate();
}void ItemsUpdate()
{itemsText.text = "当前持有:……" + nucleus + ";……" + mitochondria + ";……" + GolgiApparatus + ";……" + endoplasmicReticulum + ";……" + ribosome;
}
(3) 增加冷却事件
bool canTriggerEffect = true;void RandomEvent()
{if (canTriggerEffect){//执行接触事件……else{tipText.text = "你什么都没有发现……";StartCoroutine(ClearTextRoutine(2f));//2秒后文本消失} canTriggerEffect = false;StartCoroutine(CooldownRoutine());}
}IEnumerator CooldownRoutine()//接触事件有1分钟的冷却时间
{yield return new WaitForSeconds(60f); // 等待1分钟canTriggerEffect = true; // 冷却结束,可以再次触发效果
}
IEnumerator ClearTextRoutine(float delay)//提示文本显示后消失
{yield return new WaitForSeconds(delay);tipText.text = "";
}
(4) 增加收集事件
void Start(){if (itemsText == null || tipText == null){Debug.LogError("ItemsText 或 TipText 未被正确分配!");}questionsPanel.SetActive(false);}private void OnTriggerEnter(Collider other){if (other.CompareTag("Player")){RandomEvent();}}void RandomEvent(){int numericValue = Random.Range(0, 100);if (numericValue < 5 && nucleus == 0){nucleus = 1;tipText.text = "你发现了nucleus!";}else if (numericValue < 26){mitochondria++;tipText.text = "你得到了mitochondria!";}else if (numericValue < 41){endoplasmicReticulum++;tipText.text = "你得到了endoplasmicReticulum*1!";}else if (numericValue < 56){GolgiApparatus++;tipText.text = "你得到了GolgiApparatus*1!";}else if (numericValue < 76){ribosome++;tipText.text = "你得到了ribosome*1!";}else if (numericValue < 86){questionsPanel.SetActive(true);}else{tipText.text = "你什么都没有发现……";}ItemsUpdate();}void ItemsUpdate(){itemsText.text = "当前持有:nucleus" + nucleus + ";……" + mitochondria + ";……" + GolgiApparatus + ";……" + endoplasmicReticulum + ";……" + ribosome;}
}
本节完整脚本
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;public class CollectedItems : MonoBehaviour
{public Text itemsText;public Text tipText;static int nucleus = 0, mitochondria = 0, GolgiApparatus = 0, endoplasmicReticulum = 0, ribosome = 0;public GameObject questionsPanel;public Button quitBtn;public LoadQuestions LoadQuestions;bool canTriggerEffect = true;void Start(){if (itemsText == null || tipText == null){Debug.LogError("ItemsText 或 TipText 未被正确分配!");}questionsPanel.SetActive(false);ItemsUpdate();}private void OnTriggerEnter(Collider other){if (other.CompareTag("Player")){RandomEvent();}}void RandomEvent(){if (canTriggerEffect){int numericValue = Random.Range(0, 100);if (numericValue < 5 && nucleus == 0){nucleus = 1;tipText.text = "你发现了nucleus!";StartCoroutine(ClearTextRoutine(2f));}else if (numericValue < 26){mitochondria++;tipText.text = "你得到了mitochondria!";StartCoroutine(ClearTextRoutine(2f));}else if (numericValue < 41){endoplasmicReticulum++;tipText.text = "你得到了endoplasmicReticulum*1!";StartCoroutine(ClearTextRoutine(2f));}else if (numericValue < 56){GolgiApparatus++;tipText.text = "你得到了GolgiApparatus*1!";StartCoroutine(ClearTextRoutine(2f));}else if (numericValue < 76){ribosome++;tipText.text = "你得到了ribosome*1!";StartCoroutine(ClearTextRoutine(2f));}else if (numericValue < 86){questionsPanel.SetActive(true);}else{tipText.text = "你什么都没有发现……";StartCoroutine(ClearTextRoutine(2f));}ItemsUpdate();canTriggerEffect = false;StartCoroutine(CooldownRoutine());}}void ItemsUpdate(){itemsText.text = "当前持有:nucleus" + nucleus + ";mitochondria" + mitochondria + ";GolgiApparatus" + GolgiApparatus + ";endoplasmicReticulum" + endoplasmicReticulum + ";ribosome" + ribosome;}IEnumerator CooldownRoutine(){yield return new WaitForSeconds(60f); // 等待1分钟canTriggerEffect = true; // 冷却结束,可以再次触发效果}IEnumerator ClearTextRoutine(float delay){yield return new WaitForSeconds(delay);tipText.text = "";}public void HideQuestionPanel(){if (questionsPanel != null){questionsPanel.SetActive(false); // 隐藏物体LoadQuestions.UpdateQuestionUI();//调用新题目}}
}
五、增加生命系统
1、新建PlayerHealth.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class PlayerHealth : MonoBehaviour
{public int maxHealth = 3;private int currentHealth;public Text life;public GameEnding gameEndings;private void Start(){currentHealth = maxHealth; // 先设置currentHealth的值UpdateHealthText();}public void AddHealth(){if (currentHealth < maxHealth){currentHealth++;UpdateHealthText();}}public void RemoveHealth(){if (currentHealth > 0){currentHealth--;UpdateHealthText();if (currentHealth == 0){gameEndings.CaughtPlayer(); // 玩家死亡}}}private void UpdateHealthText(){life.text = "当前生命" + currentHealth.ToString();}
}
2、增加文本显示生命值
(1) 增加显示当前生命值文本
(2) 增加减少生命值提醒图片
3、处理加减生命的方法
public class LoadQuestions : MonoBehaviourpublic PlayerHealth PlayerHealth;public void CheckAnswer(int optionIndex)
{
if (optionIndex == currentQuestion.correctOptionIndex)
{Debug.Log("回答正确!" + optionIndex + currentQuestion.correctOptionIndex);PlayerHealth.AddHealth();TipAnswerText.text = "回答正确!生命值+1!退出按Quit";
}
else
{Debug.Log("回答错误!" + optionIndex + currentQuestion.correctOptionIndex);PlayerHealth.RemoveHealth();TipAnswerText.text = "回答错误!生命值-1!\n+退出按Quit";
}
}
六、 更改死亡事件
using UnityEngine;
using UnityEngine.SceneManagement;public class GameEnding : MonoBehaviour
{public float fadeDuration = 1f;public float displayImageDuration = 1f;public GameObject player;public CanvasGroup exitBackgroundImageCanvasGroup;public AudioSource exitAudio;public CanvasGroup caughtBackgroundImageCanvasGroup;public AudioSource caughtAudio;bool m_IsPlayerAtExit;bool m_IsPlayerCaught;float m_Timer;bool m_HasAudioPlayed;void OnTriggerEnter (Collider other){if (other.gameObject == player){m_IsPlayerAtExit = true;}}public void CaughtPlayer (){m_IsPlayerCaught = true;}void Update (){if (m_IsPlayerAtExit){EndLevel (exitBackgroundImageCanvasGroup, false, exitAudio);}else if (m_IsPlayerCaught){EndLevel (caughtBackgroundImageCanvasGroup, true, caughtAudio);}}void EndLevel (CanvasGroup imageCanvasGroup, bool doRestart, AudioSource audioSource){if (!m_HasAudioPlayed){audioSource.Play();m_HasAudioPlayed = true;}m_Timer += Time.deltaTime;imageCanvasGroup.alpha = m_Timer / fadeDuration;if (m_Timer > fadeDuration + displayImageDuration){if (doRestart){//SceneManager.LoadScene ("MainScene1");Application.Quit();//退出游戏}else{Application.Quit ();}}}
}
七、增加敌人,增加随机死亡事件
八、添加手柄
(1) 下载并导入 资源包
(2) 打开Prefabs 文件夹,将 Fixed Joystick 拖放到Hierarchy面板的Canvas下,调整大小和位置
(3) 调整脚本
public FixedJoystick fixedJoystick;
public float moveSpeed = 1f;void Start()
{……fixedJoystick = GameObject.FindObjectOfType<FixedJoystick>();
}private void FixedUpdate()
{// 手柄控制代码
if (fixedJoystick != null)
{float joystickHorizontal = fixedJoystick.Horizontal;float joystickVertical = fixedJoystick.Vertical;if (!Mathf.Approximately(joystickHorizontal, 0f) || !Mathf.Approximately(joystickVertical, 0f)){horizontal = joystickHorizontal;vertical = joystickVertical;m_Movement.Set(horizontal, 0f, vertical);m_Movement.Normalize();}
}
}
完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;public class PlayerMovement : MonoBehaviour
{public FixedJoystick fixedJoystick;public InputAction MoveAction;public float turnSpeed = 20f;public float moveSpeed = 1f;Animator m_Animator;Rigidbody m_Rigidbody;AudioSource m_AudioSource;Vector3 m_Movement;Quaternion m_Rotation = Quaternion.identity;void Start (){m_Animator = GetComponent<Animator> ();m_Rigidbody = GetComponent<Rigidbody> ();m_AudioSource = GetComponent<AudioSource> ();MoveAction.Enable();fixedJoystick = GameObject.FindObjectOfType<FixedJoystick>();}void FixedUpdate(){// 原有的键盘控制代码float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");m_Movement.Set(horizontal, 0f, vertical);m_Movement.Normalize();// 手柄控制代码if (fixedJoystick != null){float joystickHorizontal = fixedJoystick.Horizontal;float joystickVertical = fixedJoystick.Vertical;if (!Mathf.Approximately(joystickHorizontal, 0f) || !Mathf.Approximately(joystickVertical, 0f)){horizontal = joystickHorizontal;vertical = joystickVertical;m_Movement.Set(horizontal, 0f, vertical);m_Movement.Normalize();}}bool hasHorizontalInput = !Mathf.Approximately(horizontal, 0f);bool hasVerticalInput = !Mathf.Approximately(vertical, 0f);bool isWalking = hasHorizontalInput || hasVerticalInput;m_Animator.SetBool("IsWalking", isWalking);if (isWalking){if (!m_AudioSource.isPlaying){m_AudioSource.Play();}}else{m_AudioSource.Stop();}Vector3 desiredForward = Vector3.RotateTowards(transform.forward, m_Movement, turnSpeed * Time.deltaTime, 0f);m_Rotation = Quaternion.LookRotation(desiredForward);// 移动角色m_Rigidbody.MovePosition(m_Rigidbody.position + m_Movement * moveSpeed * Time.fixedDeltaTime);}void OnAnimatorMove (){m_Rigidbody.MovePosition (m_Rigidbody.position + m_Movement * m_Animator.deltaPosition.magnitude);m_Rigidbody.MoveRotation (m_Rotation);}
}
九、发布
1、安装 WebGL Build Support
2、更改Color Space(other……)下
3、在File下进行打包
4、在publish中上传