一、原版修改
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中上傳