打飛碟遊戲

今天我們做一個簡單的飛碟遊戲

有了上一章點擊地面出現攻擊目標而引出的單例模式,這一次的遊戲可以很好設計出來。

這個遊戲中的主要角色有

  • 飛碟:最基本的要素,就是一個ganmeobject

  • 遊戲場景:作爲遊戲美化

  • 導演:控制全局,場景轉換(本遊戲中還沒有體現它的作用,因爲只有一個場景。。。)

  • 場記:加載資源,給飛碟工廠、記分員、場次控制員下達命令,協調工作

  • 飛碟工廠:用於飛碟的製造和回收,以及發射

  • 飛碟加工廠:要發射的飛碟要根據場記的要求調整顏色、大小

  • 記分員:統計打下來的飛碟,根據難度,給玩家計算相應分數

  • 場次控制員:遊戲分3個Round,玩家選擇以後場次控制員要做相關調控

他們之間的關係可以用下圖表示(不表示導演)
這裏寫圖片描述

實現單例模式我是通過GetInstance來實現的,代碼幾乎都是

if (instance == null)
    instance = new 類名;
return instance;

這樣編程的好處是分工合理,每個人各司其職,比如我們發現飛碟製造有問題,可以直接找飛碟工廠,計分有問題,可以直接找記分員,這樣的思路相比於把所有代碼都混在一個類裏面,要好理解、更清晰得多

下面講一下遊戲規則
遊戲難度分爲三種難度,玩家選擇後遊戲開始,飛碟會隨機從玩家的左邊或右邊飛出,顏色隨機,大小、速度則嚴格按照選擇的遊戲難度進行復雜的概率分佈得到的(好吧就是隨機數把比例改變一下)每一局一共有20個飛碟,難度不同,飛碟飛出的時間間隔不同,難度三會同時多個飛碟一起飛出。玩家需要通過鼠標點擊飛碟把它打下來(一開始想做一個瞄準鏡,還搞了一把槍,但是槍在鼠標移出遊戲界面後就會變亂,選的準星圖片背景不能變透明很難看就放棄了,果斷用光標吧。。。Low就Low了)玩家的槍一共有三發子彈,按空格就是換子彈

遊戲規則弄清楚了,那麼我說一下每個類

ISceneController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISceneController {

    void LoadResources();
    void Pause();
    void Resume();

}

SSDirector

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSDirector : System.Object {

    private static SSDirector _instance;

    public ISceneController currentSceneController { get; set; }

    public static SSDirector getInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }

    public int getFPS()
    {
        return Application.targetFrameRate;
    }

    public void setFPS(int fps)
    {
        Application.targetFrameRate = fps;
    }

}

RoundController

這個類很好理解,由於遊戲只需要一個Round,所以有一個靜態成員變量Round用於記錄當前是哪個Round。用了OnGUI來給玩家提供難度以及重新開始的選項

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RoundController : MonoBehaviour {
    private static RoundController instance;
    private static int Round;

    public static RoundController GetInstance()
    {
        if (instance == null)
            instance = new RoundController();
        return instance;
    }

    private void Start()
    {
        Init();
    }

    public void Init()
    {
        Round = 0;
    }

    public static int GetRound()
    {
        return Round;
    }

    void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.normal.background = null;
        style.normal.textColor = Color.red;
        style.fontSize = 30;
        if (GUI.Button(new Rect(0, 410, 60, 30), "Round 1"))
        {
            Round = 1;
        } else if (GUI.Button(new Rect(0, 440, 60, 30), "Round 2"))
        {
            Round = 2;
        } else if (GUI.Button(new Rect(0, 470, 60, 30), "Round 3"))
        {
            Round = 3;
        } else if (GUI.Button(new Rect(0, 500, 60, 30), "Reset"))
        {
            Round = 4;
        }
        if (Round < 4 && Round != 0)
        {
            GUI.Label(new Rect(0, 0, 60, 30), "Round " + Round.ToString(), style);
        } else
        {
            GUI.Label(new Rect(0, 0, 60, 30), "Select a round to start", style);
        }
    }

}

ScoreRecorder

有一個靜態成員變量Score用於記錄當前分數,與RoundController一樣,都只需要一個實例,所以用了單例模式GetInstance。Record函數用於統計分數,根據飛碟的大小來統計

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder : MonoBehaviour {

    private static ScoreRecorder instance;
    private static int Score;

    public static ScoreRecorder GetInstance()
    {
        if (instance == null)
            instance = new ScoreRecorder();
        return instance;
    }

    public static int GetScore()
    {
        return Score;
    }

    private void Start()
    {
        Score = 0;
    }

    public void Reset()
    {
        Score = 0;
    }

    public void Record(GameObject disk)
    {
        if (disk.GetComponent<Transform>().localScale == new Vector3(1, 0.1f, 1))
            Score += 3;
        else if (disk.GetComponent<Transform>().localScale == new Vector3(2, 0.1f, 2))
            Score += 2;
        else
            Score += 1;
        Debug.Log(Score.ToString());
    }

    void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.normal.background = null;
        style.normal.textColor = Color.yellow;
        style.fontSize = 30;
        GUI.Label(new Rect(800, 0, 60, 30), "Score: " + Score.ToString(), style);
    }

}

DiskData

是飛碟的加工廠,不同於生產廠,這裏記錄了飛碟的color、size。在我的認知中,List中存的應該是對象本身,所以我覺得DiskData不是作爲記錄飛碟數據的存在(可能是我認知有缺陷)而是實實在在的飛碟本身,所以包含一個遊戲對象disk,可以說,在我的設計中,DiskData纔是真正飛碟的製造商

關鍵方法:

  • DiskData:從預設中克隆一個飛碟,把它放到地圖中玩家看不到的地方

  • build:根據設定的顏色、大小對飛碟進行改造,並決定它是放在玩家右邊還是玩家左邊

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskData : MonoBehaviour {
    private string color;
    private int size;
    public GameObject disk;
    Texture img;
    public DiskData()
    {
        disk = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
        SetColor("red");
        SetSize(1);
        disk.transform.position = Vector3.zero;
        disk.transform.Rotate(new Vector3(-25, 0, 0));
    }



    public void SetColor(string col)
    {
        color = col;
    }

    public void SetSize(int n)
    {
        size = n;
    }



    public void build(int Pos = 0)
    {
        if (color == "red")
        {
            img = (Texture)Resources.Load("red"); // 這是紋理
        } else if (color == "blue")
        {
            img = (Texture)Resources.Load("blue");
        } else if (color == "yellow")
        {
            img = (Texture)Resources.Load("yellow");
        }
        disk.GetComponent<Renderer>().material.mainTexture = img;
        if (size == 1)
            disk.GetComponent<Transform>().localScale = new Vector3(1, 0.1f, 1);
        else if (size == 2)
            disk.GetComponent<Transform>().localScale = new Vector3(2, 0.1f, 2);
        else if (size == 3)
            disk.GetComponent<Transform>().localScale = new Vector3(3, 0.1f, 3);

        if (Pos == 0)
        {
            System.Random pos = new System.Random();
            int where = pos.Next(0, 2);
            if (where == 0)
            {
                disk.transform.position = new Vector3(-3.5f, 0, 5f);
            }
            else
            {
                disk.transform.position = new Vector3(3.5f, 0, 5f);
            }
        } else
        {
            if (Pos == 1) // right
                disk.transform.position = new Vector3(3.5f, 0, 5f);
            else
                disk.transform.position = new Vector3(-3.5f, 0, 5f);
        }
    }


}

DiskFactory

飛碟工廠,場記發送指令,告訴DiskFactory要什麼顏色、什麼大小、什麼速度的飛碟,DiskFactory得到指令後開始生產(交給它的DiskData小弟做完)然後完成發射。同時,當玩家打掉一個飛碟時,場記通知DiskFactory回收飛碟。

關鍵變量:

  • used:正在被用的集合,表示正在飛的飛碟

  • free:庫存中的飛碟,即回收和生產後多餘的飛碟,可以拿來使用

  • Speed :記錄場記要求的速度,添加到Fly組件中

關鍵方法:

  • GetDisk:生產飛碟的函數,四個參數分別代表顏色、大小、速度、以及位置。執行該函數,工廠首先會判斷是否有庫存,有直接拿來用,沒有的話判斷當前是否已經生產了20個(工廠也要錢運作,不能浪費),已經有20個則告訴場記沒錢了不幹了,沒有則默默生產,並把生產的添加到used中,對其進行相關改造(build),然後給它岸上飛翔的翅膀(Fly組件)

  • FreeDisk:把飛碟的翅膀拔掉(刪除組件),並對其進行回收,放在玩家看不見的地方等候使用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFactory : MonoBehaviour {
    private static DiskFactory instance; 
    public static List<DiskData> used = new List<DiskData>();
    public static List<DiskData> free = new List<DiskData>();
    public static float Speed;
    public static DiskFactory GetInstance()
    {
        if (instance == null)
            instance = new DiskFactory();
        return instance;
    }

    public void Init()
    {
        while (used.Count != 0)
        {
            FreeDisk(used[0].disk);
        }
    }

    public void GetDisk(string color, int size, float speed, int pos = 0)
    {
        DiskData t;
        if (free.Count ==0)
        {
            if (used.Count == 20)
            {
                Debug.LogError("There are too many disks!");
                return;
            }
            t = new DiskData();
        } else
        {
            t = free[0];
            free.Remove(t);
        }
        Speed = speed;
        t.SetColor(color);
        t.SetSize(size);
        t.build(pos);
        used.Add(t);
        if (t.disk.GetComponent<Fly>() != null)
            t.disk.GetComponent<Fly>().life = 1;
        else
        t.disk.AddComponent<Fly>();
    }

    public void FreeDisk(GameObject Disk)
    {
        Disk.GetComponent<Fly>().life = 0;
        free.Add(used[0]);
        used.RemoveAt(0);
        Disk.transform.position = Vector3.zero;
        print("free: " + free.Count);
        print("used: " + used.Count);
    }
}

這裏添加了一個Fly組件,所以我還要說一下Fly組件。
由於遊戲創建和銷燬的成本很高,組件也是一樣,所以我在這個組件中加入了一個變量life,要用的時候設爲1,不用的時候設爲0,這樣子可以節省一大部分的資源

Fly

給飛碟添加飛行動作,包含飛碟的速度以及終點位置。
Mathf函數與隨機函數結合動態地給飛碟一個相對合理的終點,增加遊戲可玩性(好吧,本身這遊戲就很無聊)。飛碟的運動包括朝終點的飛行以及自身的旋轉,當飛碟到達終點以後,被工廠回收

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Fly : DiskFactory{
    private Vector3 dir;
    float time;
    float x;
    float y;
    public float speed;
    public int life;
    // Use this for initialization
    void Start () {
        life = 1;
        speed = Speed;
        System.Random pos = new System.Random();
        float z = 50;
        y = pos.Next(7, 15);
        if (Mathf.Min(transform.position.x, 0) == transform.position.x)
        {            
            x = pos.Next(2, 10);

        } else
        {
            x = pos.Next(-11, -2);
        }


        dir = new Vector3(x, y, z);
    }

    // Update is called once per frame
    void Update () {
        if (life == 1)
        {
            time += Time.deltaTime;
            if (transform.position == dir)
            {
                FreeDisk(transform.gameObject);
            }
            else
            {
                transform.position = Vector3.MoveTowards(transform.position, dir, Time.deltaTime * 15);
                transform.Rotate(Vector3.up * 500 * Time.deltaTime);
            }
        }
    }
}

FirstController

作爲場記,有着協調各部門工作的任務,所以肯定有飛碟工廠、場景控制員、記分員這幾個變量。主要功能是加載資源、接受這三個部門發來的消息並進行處理、同時給他們下達命令。還要時時監察遊戲進行情況,包括當前子彈數、飛碟剩餘數,有一個名叫cam的遊戲對象,是用於放入攝像機從而進行撞擊檢測的,以及一個time記錄時間確定是否需要飛碟。

關鍵方法:

  • LoadResources:加載我做的遊戲場景(好大好大,畢竟100多M的遊戲)

  • Init :初始化相關數據,包括場景控制器、工廠、記分員的初始化,以及子彈數、剩餘飛盤數的初始化

  • GetColor、GetSize、GetSpeed:通過隨機變量,以當前難道爲依據,得到需要生產的飛碟的相關數據

  • Update:
    1.累加當前遊戲進行時間
    2.判斷是否需要飛碟,如果需要,獲取飛碟生產的相關參數,通知飛碟工廠生產飛碟
    3.判斷是否按了Reset,如果按了則遊戲重置,等候玩家選擇難度重新開始
    4.控制子彈的減少(鼠標點擊)以及裝彈(空格)
    5.檢測是否擊中飛碟,如果擊中,通知飛碟工廠回收

值得一提的是判斷飛碟是否被點擊,如果單純用if(Physics.Raycast(ray, out hit))是不行的,因爲我在場景中加入了別的元素,當他們被點擊時,由於他們不能被回收,所以會報錯,我發現克隆後的飛碟名字叫“disk(Clone)”,所以可以這樣子

if (Physics.Raycast(ray, out hit))
            {
                if (!hit.collider.gameObject.tag.Contains("Finish"))
                {
                    if (hit.collider.gameObject.name == "disk(Clone)")
                    {
                        MyFactroy.FreeDisk(hit.transform.gameObject);
                        Recorder.Record(hit.transform.gameObject);
                    }
                }
            }

貼代碼

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstController : MonoBehaviour, ISceneController
{
    public GameObject cam;
    private DiskFactory MyFactroy;
    private RoundController Round;
    private ScoreRecorder Recorder;
    private int number;
    float time;
    private int now = 0;
    private int bullet;

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
    }

    public void LoadResources()
    {
        GameObject Environment = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Environment"), new Vector3(3.279565f, 0.5247455f, 9.926256f), Quaternion.identity);
        Environment.name = "Environment";
    }

    // Use this for initialization
    void Start () {
        Init();
    }

    void Init()
    {
        Round = RoundController.GetInstance();
        MyFactroy = DiskFactory.GetInstance();
        Recorder = ScoreRecorder.GetInstance();
        Debug.Log("start");
        time = 0;
        now = 0;
        number = 20;
        bullet = 3;
    }

    public string GetColor()
    {
        System.Random select = new System.Random();
        string color = "red";
        switch (select.Next(0, 3))
        {
            case 0:
                color = "red";
                break;
            case 1:
                color = "blue";
                break;
            case 2:
                color = "yellow";
                break;
        }
        return color;
    }

    private void Reset()
    {
        Init();
        MyFactroy.Init();
        Recorder.Reset();
    }

    private void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.normal.background = null;
        style.normal.textColor = Color.blue;
        style.fontSize = 30;
        GUI.Label(new Rect(400, 0, 60, 30), "剩餘飛盤數: " + number.ToString(), style);
        GUI.Label(new Rect(400, 30, 60, 30), "Bullet: " + bullet.ToString(), style);
        if (number == 0)
        {
            style.normal.textColor = Color.black;
            GUI.Label(new Rect(300, 200, 100, 30), "Game over. Your Score is: " + ScoreRecorder.GetScore(), style);
        }
    }

    public int GetSize(int round)
    {
        int size = 3;
        System.Random select = new System.Random();
        if (round == 1)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                case 2:
                    size = 3;
                    break;
                case 3:
                case 4:
                    size = 2;
                    break;
                case 5:
                    size = 1;
                    break;
            }
        } else if (round == 2)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                    size = 3;
                    break;
                case 2:
                case 3:
                case 4:
                    size = 2;
                    break;
                case 5:
                    size = 1;
                    break;
            }
        } else if (round == 3)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                    size = 3;
                    break;
                case 1:
                case 2:
                    size = 2;
                    break;
                case 3:
                case 4:
                case 5:
                    size = 1;
                    break;
            }
        }
        return size;
    }

    float GetSpeed(int round)
    {
        int speed = 15;
        System.Random select = new System.Random();
        if (round == 1)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                case 2:
                    speed = 15;
                    break;
                case 3:
                case 4:
                    speed = 20;
                    break;
                case 5:
                    speed = 25;
                    break;
            }
        }
        else if (round == 2)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                    speed = 15;
                    break;
                case 2:
                case 3:
                case 4:
                    speed = 20;
                    break;
                case 5:
                    speed = 25;
                    break;
            }
        }
        else if (round == 3)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                    speed = 15;
                    break;
                case 1:
                case 2:
                    speed = 20;
                    break;
                case 3:
                case 4:
                case 5:
                    speed = 25;
                    break;
            }
        }
        return speed;
    }
    // Update is called once per frame
    void Update ()
    {
        float speed = 15;
        if (RoundController.GetRound() == 4)
        {
            Reset();
        }
        if (number == 0)
        {
            return;
        }
        time += Time.deltaTime;
        System.Random select = new System.Random();
        string color = GetColor();
        int size = 3;
        if (now != RoundController.GetRound())
        {
            Reset();
            now = RoundController.GetRound();
        } 
        if (RoundController.GetRound() == 1)  // 難度一
        {

            if (time >= 6)
            {
                speed = GetSpeed(RoundController.GetRound());
                number--;
                time = 0;
                size = GetSize(RoundController.GetRound());
                MyFactroy.GetDisk(color, size, speed);
            }
        } else if (RoundController.GetRound() == 2)
        {
            if (time >= 4)
            {
                speed = GetSpeed(RoundController.GetRound());
                number--;
                time = 0;
                size = GetSize(RoundController.GetRound());
                MyFactroy.GetDisk(color, size, speed);
            }
        } else if (RoundController.GetRound() == 3)
        {
            if (time >= 3)
            {
                speed = GetSpeed(RoundController.GetRound());
                float speed2 = GetSpeed(RoundController.GetRound());
                number -= 2;
                time = 0;
                int size2 = GetSize(RoundController.GetRound());
                string color2 = GetColor();
                size = GetSize(RoundController.GetRound());
                MyFactroy.GetDisk(color, size, speed, 1);
                MyFactroy.GetDisk(color2, size2, speed2, 2);
            }
        }
        if (Input.GetAxis("Jump") > 0)
            bullet = 3; 

        if (Input.GetButtonDown("Fire1") && bullet > 0)
        {
            bullet--;
            Vector3 mp = Input.mousePosition;

            Camera ca = cam.GetComponent<Camera>();
            Ray ray = ca.ScreenPointToRay(mp);

            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                if (!hit.collider.gameObject.tag.Contains("Finish"))
                {
                    if (hit.collider.gameObject.name == "disk(Clone)")
                    {
                        MyFactroy.FreeDisk(hit.transform.gameObject);
                        Recorder.Record(hit.transform.gameObject);
                    }
                }
            }
        }
    }

    public void Pause()
    {
        throw new NotImplementedException();
    }

    public void Resume()
    {
        throw new NotImplementedException();
    }
}

所有類都構造完成了,我們需要把FirstController、RoundController和ScoreRecorder掛載到一個新建的空對象中,然後把Main Camera作爲 FirstController中的對象傳進去就完成了,爲什麼要掛載RoundController和ScoreRecorder?因爲我在裏面設置了GUI,我發現如果不掛載,僅僅作爲FirstController的組成是不會觸發GUI的,暫時還沒找到解決辦法。

然後我們可以根據之前學的場景美化,種點花花草草,挖點坑,造點山,可以做出挺不錯的效果

這裏寫圖片描述
我沒開玩笑!真的挖了個坑!

最後的效果還是蠻好看的
這裏寫圖片描述

感謝閱讀!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章