Extreme Drift賽車游戲C#源碼詳解(1) 接著上次的源碼分析: MainMenu場景的UGUI部分: Canvas中的EnoughMoney: 作用:當買車後金幣不足彈出的頁面 內部一個Text和一個Button,讓我有點疑惑的是,Button綁定的點擊事件是BuyVehicle函數: ...
接著上次的源碼分析:
MainMenu場景的UGUI部分:
Canvas中的EnoughMoney:
作用:當買車後金幣不足彈出的頁面
內部一個Text和一個Button,讓我有點疑惑的是,Button綁定的點擊事件是BuyVehicle函數:
public void BuyVehicle() { //金幣足夠或者車輛未買才會執行 if ((gameScore >= vehicleSetting[currentVehicleNumber].price) && !vehicleSetting[currentVehicleNumber].Bought) { //數據持久化 PlayerPrefs.SetInt("BoughtVehicle" + currentVehicleNumber.ToString(), 1); //減錢 gameScore -= vehicleSetting[currentVehicleNumber].price; //防止負數 if (gameScore <= 0) { gameScore = 1; } //保存金幣數量 PlayerPrefs.SetInt("GameScore", gameScore); //保存車輛購買狀態 vehicleSetting[currentVehicleNumber].Bought = true; } else { menuPanels.EnoughMoney.SetActive(true); } }
下一個對象LevelChooser:
內含三個對象,Top,Bottom,Levels:
Top:帶有OutlineLogo的簡單的Text
Bottom:內含Back和Start,返回上一個場景,原理是傳入一個索引值,函數如下
public void CurrentPanel(int current) { //這裡為什麼要傳一個INT呢?因為開頭有定義 //public enum Panels { MainMenu = 0, SelectVehicle = 1, SelectLevel = 2, Settings = 3 } //activePanel預設為0,這裡傳入的是1,就是選車 activePanel = (Panels)current; //PlayerPrefs是數據持久化,從存檔取出數據驗證 if (currentVehicleNumber != PlayerPrefs.GetInt("CurrentVehicle")) { currentVehicleNumber = PlayerPrefs.GetInt("CurrentVehicle"); //迴圈所有的車輛 foreach (VehicleSetting VSetting in vehicleSetting) { //當前車激活狀態,否則不激活 if (VSetting == vehicleSetting[currentVehicleNumber]) { VSetting.vehicle.SetActive(true); currentVehicle = VSetting; } else { VSetting.vehicle.SetActive(false); } } } //根據傳入值做一些操作 switch (activePanel) { case Panels.MainMenu: menuPanels.MainMenu.SetActive(true); menuPanels.SelectVehicle.SetActive(false); menuPanels.SelectLevel.SetActive(false); if (menuGUI.wheelColor) menuGUI.wheelColor.gameObject.SetActive(true); break; case Panels.SelectVehicle: menuPanels.MainMenu.gameObject.SetActive(false); menuPanels.SelectVehicle.SetActive(true); menuPanels.SelectLevel.SetActive(false); break; case Panels.SelectLevel: menuPanels.MainMenu.SetActive(false); menuPanels.SelectVehicle.SetActive(false); menuPanels.SelectLevel.SetActive(true); break; case Panels.Settings: menuPanels.MainMenu.SetActive(false); menuPanels.SelectVehicle.SetActive(false); menuPanels.EnoughMoney.SetActive(true); menuPanels.SelectLevel.SetActive(false); break; } }
Start對應的函數如下:調用StartGame,然後調用下麵的兩個函數
public void StartGame() { //防止出錯 if (startingGame) return; //設置背景 FadeBackGround.SetBool("FadeOut", true); //調用協程載入(由於不能直接調用C#的多線程,這個函數實現多線程) StartCoroutine(LoadLevelGame(1.5f)); startingGame = true; } IEnumerator LoadLevelGame(float waitTime) { yield return new WaitForSeconds(waitTime); //激活載入頁面 menuGUI.loading.SetActive(true); //載入 StartCoroutine(LoadLevelAsync()); } IEnumerator LoadLevelAsync() { yield return new WaitForSeconds(0.4f); sceneLoadingOperation = Application.LoadLevelAsync(currentLevelNumber + 1); sceneLoadingOperation.allowSceneActivation = false; while (!sceneLoadingOperation.isDone || sceneLoadingOperation.progress < 0.9f) { menuLoadTime += Time.deltaTime; yield return 0; } }
Levels是一個帶有Scroll Rect的Image,內含Group,下有五張地圖對象
每一個地圖下有star1,star2,star3,簡單的Image表實獲取到的成績,以及一個locked表示是否解鎖,本質是一個簡單的Image
還有一個CurrentLevel對象,簡單的Image背景,裡面有兩個簡單的Text表示曾經獲取的成績
每一張地圖對象只是一個圖片,並不實際含有地圖,並且綁定一個函數,根據傳參進行下一步:
public void currentLevel(int current) { for (int i = 0; i < levelSetting.Length; i++) { if (i == current) { currentLevelNumber = current; levelSetting[i].panel.image.color = Color.white; levelSetting[i].panel.enabled = true; levelSetting[i].lockImage.gameObject.SetActive(false); PlayerPrefs.SetInt("CurrentLevelNumber", currentLevelNumber); } else if (levelSetting[i].locked == false) { levelSetting[i].panel.image.color = new Color(0.3f, 0.3f, 0.3f); levelSetting[i].panel.enabled = true; levelSetting[i].lockImage.gameObject.SetActive(false); } else { levelSetting[i].panel.image.color = new Color(1.0f, 0.5f, 0.5f); levelSetting[i].panel.enabled = false; levelSetting[i].lockImage.gameObject.SetActive(true); } //根據保存過的信息進行顯示 if (levelSetting[i].bestTime) { if (PlayerPrefs.GetFloat("BestTime" + (i + 1).ToString()) != 0.0f) { if (PlayerPrefs.GetInt("LevelStar" + (i + 1)) == 1) { levelSetting[i].stars.Star1.color = Color.white; } else if (PlayerPrefs.GetInt("LevelStar" + (i + 1)) == 2) { levelSetting[i].stars.Star1.color = Color.white; levelSetting[i].stars.Star2.color = Color.white; } else if (PlayerPrefs.GetInt("LevelStar" + (i + 1)) == 3) { levelSetting[i].stars.Star1.color = Color.white; levelSetting[i].stars.Star2.color = Color.white; levelSetting[i].stars.Star3.color = Color.white; } levelSetting[i].bestTime.text = "BEST : " + GetComponent<FormatSecondsScript>().FormatSeconds(PlayerPrefs.GetFloat("BestTime" + (i + 1))).ToString(); } } } }
下一個選項,Settings:設置界面
一個深色的BackGround
SettingsPanel:下麵有七個對象
close是一個簡單的Button,綁定MainMenu的函數:
public void SettingActive(bool activePanel) { menuPanels.Settings.gameObject.SetActive(activePanel); }
Quality下有四個Button,用於設置畫質,一個line用來分割,綁定的函數如下:
直接調用系統畫質API,然後持久化保存數據
public void QualitySetting(int quality) { QualitySettings.SetQualityLevel(quality - 1, true); PlayerPrefs.SetInt("QualitySettings", quality); }
Sound音量設置下是兩個Toggle:選項框綁定函數:設置後保存
public void DisableAudioButton(Toggle toggle) { if (toggle.isOn) { AudioListener.volume = 1; PlayerPrefs.SetInt("AudioActive", 0); } else { AudioListener.volume = 0; PlayerPrefs.SetInt("AudioActive", 1); } } public void DisableMusicButton(Toggle toggle) { if (toggle.isOn) { menuMusic.GetComponent<AudioSource>().mute = false; PlayerPrefs.SetInt("MusicActive", 0); } else { menuMusic.GetComponent<AudioSource>().mute = true; PlayerPrefs.SetInt("MusicActive", 1); } }
Control下四個對象,用於設置操作相關
兩個選項框用於設置操作模式
public void ControlModeButtons(Toggle value) { if (value.isOn) PlayerPrefs.SetString("ControlMode", "Buttons"); } public void ControlModeAccel(Toggle value) { if (value.isOn) PlayerPrefs.SetString("ControlMode", "Accel"); }
下麵是音量設置:都是一些簡單的函數
public void DisableAudioButton(Toggle toggle) { if (toggle.isOn) { AudioListener.volume = 1; PlayerPrefs.SetInt("AudioActive", 0); } else { AudioListener.volume = 0; PlayerPrefs.SetInt("AudioActive", 1); } } public void DisableMusicButton(Toggle toggle) { if (toggle.isOn) { menuMusic.GetComponent<AudioSource>().mute = false; PlayerPrefs.SetInt("MusicActive", 0); } else { menuMusic.GetComponent<AudioSource>().mute = true; PlayerPrefs.SetInt("MusicActive", 1); } }
保存敏感繫數函數:
public void EditSensitivity() { PlayerPrefs.SetFloat("Sensitivity", menuGUI.sensitivity.value); }
最後一個Erase All Data是激活下麵的Reset Panel,進行數據清除
Reset Panel是一個紅色背景和選擇YES或者NO
public void EraseSave() { //清除所有數據後重新載入 PlayerPrefs.DeleteAll(); currentVehicleNumber = 0; Application.LoadLevel(0); foreach (Material mat in allRestMaterials) mat.SetColor("_Color", new Color(0.7f, 0.7f, 0.7f)); }
Fade Background是一個隱藏對象,如果激活後,會顯示車輪樣的載入圖片,帶有一個綁定了動畫的Animator
到這裡,UGUI的Canvas就分析完了
下一個,先看簡單的Vehicle Root對象
裡面裝有12輛車和一個Shadow陰影
這12輛車就是汽車的3D模型,沒什麼好說的,Shadow是汽車的陰影
Shadow實現是利用Projector投影,用到一些陰影資源,這裡就不深究了
下一個Garage對象,也很簡單,就是一個車庫3D模型
最後來看Main Camera:
Main Camera下有兩個Audio Source,分別是背景音樂和點擊音效
Main Camera首先綁定的是MainMenu:
這兩篇文章都是圍繞MainMenu講的,現在從頭來看:
先看一些全局變數:這裡大部分的意思我都看懂了,不過有一部分我無法理解
//金幣 private int gameScore { get; set; } //視角移動速度 public float cameraRotateSpeed = 5; //載入背景 public Animator FadeBackGround; //背景音樂 public AudioSource menuMusic; //簡單的GO public Transform vehicleRoot; //所有材質 public Material[] allRestMaterials; //主界面:含有五個場景對象 public MenuPanels menuPanels; //界面GUI,含有多個對象 public MenuGUI menuGUI; //車輛數組,保存了汽車的各種信息 public VehicleSetting[] vehicleSetting; //關卡數組,保存了關卡的信息 public LevelSetting[] levelSetting; //預設以第一個界面開啟 private Panels activePanel = Panels.MainMenu; //含義暫定 private bool vertical, horizontal; private Vector2 startPos; private Vector2 touchDeltaPosition; //滑鼠按下位置記錄 private float x, y = 0; //當前車輛以及各種信息 private VehicleSetting currentVehicle; private int currentVehicleNumber = 0; private int currentLevelNumber = 0; private Color mainColor; private bool randomColorActive = false; private bool startingGame = false; private float menuLoadTime = 0.0f; private AsyncOperation sceneLoadingOperation = null;
自定義的幾個類:
//游戲的GUI [System.Serializable] public class MenuGUI { public Text GameScore; public Text VehicleName; public Text VehiclePrice; public Slider VehicleSpeed; public Slider VehicleBraking; public Slider VehicleNitro; public Slider sensitivity; public Toggle audio; public Toggle music; public Toggle vibrateToggle; public Toggle ButtonMode, AccelMode; public Image wheelColor, smokeColor; public Image loadingBar; public GameObject loading; public GameObject customizeVehicle; public GameObject buyNewVehicle; }
//五個場景的Panel [System.Serializable] public class MenuPanels { public GameObject MainMenu; public GameObject SelectVehicle; public GameObject SelectLevel; public GameObject EnoughMoney; public GameObject Settings; }
//車輛信息 [System.Serializable] public class VehicleSetting { public string name = "Vehicle 1"; public int price = 20000; public GameObject vehicle; public GameObject wheelSmokes; public Material ringMat, smokeMat; public Transform rearWheels; public VehiclePower vehiclePower; //是否購買,隱藏 [HideInInspector] public bool Bought = false; //車輛性能參數 [System.Serializable] public class VehiclePower { public float speed = 80; public float braking = 1000; public float nitro = 10; } }
//可以使用的幾張地圖信息 [System.Serializable] public class LevelSetting { public bool locked = true; public Button panel; public Text bestTime; public Image lockImage; public StarClass stars; //該地圖獲得了幾顆星 [System.Serializable] public class StarClass { public Image Star1, Star2, Star3; } }
這個腳本其他所有的函數都有說過了,除了Awake和Update函數:
先來看看Awake函數:
//初始化 void Awake() { AudioListener.pause = false; Time.timeScale = 1.0f; //震動選項,這裡沒用 menuGUI.vibrateToggle.isOn = (PlayerPrefs.GetInt("VibrationActive") == 0) ? true : false; //預設金幣999999 gameScore = 999999; //載入第一個場景 CurrentPanel(0); //設置畫質 if (PlayerPrefs.GetInt("QualitySettings") == 0) { PlayerPrefs.SetInt("QualitySettings", 4); QualitySettings.SetQualityLevel(3, true); } else { QualitySettings.SetQualityLevel(PlayerPrefs.GetInt("QualitySettings") - 1, true); } //設置車輛敏感度 if (PlayerPrefs.GetFloat("Sensitivity") == 0.0f) { menuGUI.sensitivity.value = 1.0f; PlayerPrefs.SetFloat("Sensitivity", 1.0f); } else { menuGUI.sensitivity.value = PlayerPrefs.GetFloat("Sensitivity"); } //設置操作模式 switch (PlayerPrefs.GetString("ControlMode")) { case "": menuGUI.ButtonMode.isOn = true; break; case "Buttons": menuGUI.ButtonMode.isOn = true; break; case "Accel": menuGUI.AccelMode.isOn = true; break; } //當前車輛 currentLevelNumber = PlayerPrefs.GetInt("CurrentLevelNumber"); //地圖解鎖情況處理 for (int lvls = 0; lvls < levelSetting.Length; lvls++) { if (lvls <= PlayerPrefs.GetInt("CurrentLevelUnlocked")) levelSetting[lvls].locked = false; } //設置地圖 currentLevel(currentLevelNumber); //讀取預設控制信息設置 switch (PlayerPrefs.GetString("ControlMode")) { case "": PlayerPrefs.SetString("ControlMode", "Buttons"); menuGUI.ButtonMode.isOn = true; break; case "Buttons": menuGUI.ButtonMode.isOn = true; break; case "Accel": menuGUI.AccelMode.isOn = true; break; } //預設只買了第一輛車 PlayerPrefs.SetInt("BoughtVehicle0", 1); //背景音樂和音量設置 menuGUI.audio.isOn = (PlayerPrefs.GetInt("AudioActive") == 0) ? true : false; AudioListener.volume = (PlayerPrefs.GetInt("AudioActive") == 0) ? 1.0f : 0.0f; menuGUI.music.isOn = (PlayerPrefs.GetInt("MusicActive") == 0) ? true : false; menuMusic.mute = (PlayerPrefs.GetInt("MusicActive") == 0) ? false : true; currentVehicleNumber = PlayerPrefs.GetInt("CurrentVehicle"); currentVehicle = vehicleSetting[currentVehicleNumber]; int i = 0; //遍歷所有車輛,進行輪胎顏色煙霧設置 foreach (VehicleSetting VSetting in vehicleSetting) { if (PlayerPrefsX.GetColor("VehicleWheelsColor" + i) == Color.clear) { vehicleSetting[i].ringMat.SetColor("_DiffuseColor", Color.white); } else { vehicleSetting[i].ringMat.SetColor("_DiffuseColor", PlayerPrefsX.GetColor("VehicleWheelsColor" + i)); } if (PlayerPrefsX.GetColor("VehicleSmokeColor" + i) == Color.clear) { vehicleSetting[i].smokeMat.SetColor("_TintColor", new Color(0.8f, 0.8f, 0.8f, 0.2f)); } else { vehicleSetting[i].smokeMat.SetColor("_TintColor", PlayerPrefsX.GetColor("VehicleSmokeColor" + i)); } //Bought=true說明車沒有買 if (PlayerPrefs.GetInt("BoughtVehicle" + i.ToString()) == 1) { VSetting.Bought = true; if (PlayerPrefs.GetInt("GameScore") == 0) { PlayerPrefs.SetInt("GameScore", gameScore); } else { gameScore = PlayerPrefs.GetInt("GameScore"); } } //顯示當前車輛 if (VSetting == vehicleSetting[currentVehicleNumber]) { VSetting.vehicle.SetActive(true); currentVehicle = VSetting; } else { VSetting.vehicle.SetActive(false); } i++; } }
再看看Update方法:
void Update() { //如果載入了下一個Scene if (sceneLoadingOperation != null) { //根據時間顯示載入輪胎動畫(圓形逐漸圓滿) /*public static Vector3 MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta); 作用是將當前值current移向目標target。(對Vector3是沿兩點間直線) maxDistanceDelta就是每次移動的最大長度。 返回值是當current值加上maxDistanceDelta的值,如果這個值超過了target,返回的就是target的值*/ menuGUI.loadingBar.fillAmount = Mathf.MoveTowards(menuGUI.loadingBar.fillAmount, sceneLoadingOperation.progress + 0.2f, Time.deltaTime * 0.5f); //載入完成 if (menuGUI.loadingBar.fillAmount > sceneLoadingOperation.progress) sceneLoadingOperation.allowSceneActivation = true; } if (menuGUI.smokeColor.gameObject.activeSelf || randomColorActive) { //車輛的後輪旋轉 vehicleSetting[currentVehicleNumber].rearWheels.Rotate(1000 * Time.deltaTime, 0, 0); //開啟車輛輪胎煙霧 vehicleSetting[currentVehicleNumber].wheelSmokes.SetActive(true); } else { vehicleSetting[currentVehicleNumber].wheelSmokes.SetActive(false); } //車輛參數的顯示 menuGUI.VehicleSpeed.value = vehicleSetting[currentVehicleNumber].vehiclePower.speed / 100.0f; menuGUI.VehicleBraking.value = vehicleSetting[currentVehicleNumber].vehiclePower.braking / 100.0f; menuGUI.VehicleNitro.value = vehicleSetting[currentVehicleNumber].vehiclePower.nitro / 100.0f; menuGUI.GameScore.text = gameScore.ToString(); //如果車已購買 if (vehicleSetting[currentVehicleNumber].Bought) { //一些簡單的設置 menuGUI.customizeVehicle.SetActive(true); menuGUI.buyNewVehicle.SetActive(false); menuGUI.VehicleName.text = vehicleSetting[currentVehicleNumber].name; menuGUI.VehiclePrice.text = "BOUGHT"; PlayerPrefs.SetInt("CurrentVehicle", currentVehicleNumber); } else { //車沒買的話一些簡單的設置 menuGUI.customizeVehicle.SetActive(false); menuGUI.buyNewVehicle.SetActive(true); menuGUI.VehicleName.text = vehicleSetting[currentVehicleNumber].name; menuGUI.VehiclePrice.text = "COST: " + vehicleSetting[currentVehicleNumber].price.ToString(); } //我改了下源碼,因為暫時用不到安卓系統 if (Input.GetMouseButton(0) && activePanel != Panels.SelectLevel) { //這些數學函數我看不懂,但明白意思,就是滑鼠點擊屏幕出現是視覺效果和旋轉效果 x = Mathf.Lerp(x, Mathf.Clamp(Input.GetAxis("Mouse X"), -2, 2) * cameraRotateSpeed, Time.deltaTime * 5.0f); Camera.main.fieldOfView = Mathf.Clamp(Camera.main.fieldOfView, 50, 60); Camera.main.fieldOfView = Mathf.Lerp(Camera.main.fieldOfView, 50, Time.deltaTime); } else { x = Mathf.Lerp(x, cameraRotateSpeed * 0.01f, Time.deltaTime * 5.0f); Camera.main.fieldOfView = Mathf.Lerp(Camera.main.fieldOfView, 60, Time.deltaTime); } //慢慢旋轉視角 transform.RotateAround(vehicleRoot.position, Vector3.up, x); }