Extreme Drift赛车游戏C#源码详解(1)

接着上次的源码分析:

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);


    }
11-28 00:34