iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 28
0
自我挑戰組

我要和天一樣高!!!(Unity 2D手機小遊戲開發日誌)系列 第 28

Day 28: 排行榜

(本篇文章網誌版:http://shineright.blogspot.tw/2016/12/day-28.html)

增加遊戲可玩性的方法有很多種,其中最簡單的方式就是加入排行榜機制,讓玩家有破紀錄、比較成續的快感。

今天我要為遊戲加入一個簡單的排行榜。這個排行榜可紀錄在該裝置中的前三高分,以及前三高分所使用的角色。

http://ithelp.ithome.com.tw/upload/images/20161228/20103149iy8BJ4NyVD.png
(示意圖)

在Start Scene中建立一個新的Game Object,並為它新增一段名為「ScoreBoardDataControl.cs」的Script。這段Script會比較複雜,因為排行榜機制牽涉到把資料序列化(Serialize)儲存至裝置,以及把裝燈的資料反序列化(Deserialize)變回程式可理解的物件。下圖是該段程式的流程簡圖。

http://ithelp.ithome.com.tw/upload/images/20161228/20103149FbFBOJI27L.png
(反序列化是Deserialize,圖片裡忘了改)

馬上進入程式碼:

using UnityEngine;
using System.Collections;
using System;  //Serializable
using System.Runtime.Serialization.Formatters.Binary; //BinaryFormatter
using System.IO;  //FileStream

public class ScoreBoardDataControl : MonoBehaviour 
{
    //Singleton Pattern,可使任何程式呼叫ScoreBoardDataControl.instance取得該物件
    public static ScoreBoardDataControl instance;
     
   //實際儲存分數資料的物件(ScoreData定義在下方)
    private ScoreData data;
     
    //要顯示的名次數量(3表示只顯示前三名)     
    private const int Places = 3;
     
    void Awake()
    {
        //Singleton Pattern,只能擁有一個ScoreBoardDataControl物件
        if (instance == null) {

            //轉換場景時不要移除此Game Object
            DontDestroyOnLoad (gameObject);
        
            //載入排行榜資料
            LoadData ();

            //把instance設好,以供其他地方以ScoreBoardDataControl.instance取得物件
            instance = this;
        
        } else if (instance != this) {
          
            //第二次進入開始畫面,把新的ScoreBoardDataControl刪掉
            Destroy (gameObject);
        }
    }
       
    void LoadData()
    {
        //如果檔案存在(表示不是第一次開啟遊戲)
        if (File.Exists (Application.persistentDataPath + "/scoreInfo.dat")) {
            BinaryFormatter bf = new BinaryFormatter ();
            FileStream file = File.Open (Application.persistentDataPath + "/scoreInfo.dat", FileMode.Open);

            //把裝置中的二進位檔案反序列化,存入data變數中
            data = (ScoreData) bf.Deserialize (file);
            file.Close ();
        } else {     //如果檔案不存在(第一次開啟遊戲)
            InitData (); //初始化資料
            SaveData (); //儲入裝置
        }
    }
       
    void InitData()
    {
        //初始化data、data的陣列
        data = new ScoreData ();
        data.scores = new int[Places];
        data.playerTypes = new int[Places];
          
        for (int i = 0; i < Places; i++) {
            data.scores [i] = 0;
            data.playerTypes [i] = -1;
        }
    }
      
    void SaveData()
    {
        BinaryFormatter bf = new BinaryFormatter ();
 
        //新增檔案
        FileStream file = File.Create (Application.persistentDataPath + "/scoreInfo.dat");

        //序列化,存入裝置
        bf.Serialize (file, data);
        file.Close ();
    }
        
    public void NewScore(int playerType, int score)
    {
        //判斷此分數能排在第幾名
        int place = Places - 1;
            
        while (place >= 0 && score > data.scores [place]) {
            place--;
        }
            
        place++;

        //無法進入排行榜
	    if (place >= Places)
            return;
                
        //把此分數之後的排名向後挪一名
        for (int i = Places - 2; i >= place; i--) {
            data.scores [i + 1] = data.scores [i];
            data.playerTypes [i + 1] = data.playerTypes [i];
        }
           
        //更新名次
        data.scores [place] = score;
        data.playerTypes [place] = playerType;
           
        //存入裝置
        SaveData ();
    }
        
    //取得place名次的分數
    public int GetScore(int place)
    {
        return data.scores [place];
    }
        
        
    //取得place名次所使用的角色
    public int GetPlayerType(int place)
    {
        return data.playerTypes [place];
    }
}

[Serializable]  //可序列化的類別
class ScoreData
{
    public int[] scores; //儲存前三名的分數
    public int[] playerTypes; //儲存前三名使用的角色
}

大致解說一下上面的程式碼。首先,我使用了類似Singleton Pattern的設計模式(Design Pattern),使整個遊戲只允許一個ScoreBoardDataControl物件存在,並在任何程式碼都可藉ScoreBoardDataControl.instance取得該物件的參考(reference)。

Awake()函數中,我判斷instance是否已經存在,若已經存在,則把新的ScoreBoardDataControl物件刪除,確保只有一個ScoreBoardDataControl物件存在。若不存在,則把自己指定給instance,並以DontDestroyOnLoad()使該物件不會在轉換場景時被移除。

LoadDataSaveData把資料存入或取出裝置中的二進位檔案。其中的Application.persistentDataPath是Unity依照不同作業系統定義的檔案儲存位置。

NewScore(int score)之後會在遊戲結束時被呼叫,用以判斷新的分數是否足以登上排行榜。如果可以,則調整排行榜資料,並存入裝置。

GetScoreGetPlayerType則會在瀏覽排行榜時被呼叫,取得data變數的資料。

最後,[Serializable]屬性(Attribute)讓ScoreData類別可被序列化,存入裝置。

寫完Script後,開個新的Canvas來佈置一下排行榜。

http://ithelp.ithome.com.tw/upload/images/20161228/20103149QWjmm9P7s4.png

排行榜主要由三個Image和三個Text構成,分別代表前三名所使用的角色和分數。

為每一個Text加上下面的Script。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class ScoreboardScoreText : MonoBehaviour 
{
    //第幾名 (第一名:0,第二名:1,第三名:2)
    public int place;
    
    void Start()
    {
        //由ScoreBoardDataControl依照名次取得分數
        int score = ScoreBoardDataControl.instance.GetScore (place);

        //若分數不為零,更新分數。為零則不顯示。
        if (score != 0) {
            GetComponent<Text> ().text = score.ToString () + "cm";
        } else {
            gameObject.SetActive (false);
        }
    }
}

為每一個Image加入以下Script。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class ScoreboardIcon : MonoBehaviour 
{
    //名次
    public int place;
    
    //存儲各角色sprite的陣列
    public Sprite[] characterTypes;
    
    void Start()
    {
        //依照名次取得使用的角色
        int charType = ScoreBoardDataControl.instance.GetPlayerType (place);

        //如果不為-1,則根據角色更改Sprite。為-1(沒有該名次)則不顯示
        if (charType != -1) {
            GetComponent<Image> ().sprite = characterTypes [charType];
        } else {
            gameObject.SetActive (false);
        }
    }
}

在Inspector把角色的Sprite依照順序拉入Character Types陣列,並把Text和Image的Place變數設定好。

接著在GameOverCanvas.cs的Start()函數加入

ScoreBoardDataControl.instance.NewScore (GameObject.FindObjectOfType<Character> ().charIndex, score);

來儲存該次遊戲結束的分數,排行榜就完成了!

在開始畫面和結束畫面新增一個開啟排行榜的按鈕,玩家就可觀看排行榜的成續了。

http://ithelp.ithome.com.tw/upload/images/20161228/20103149aAvhQ4PqwZ.png

http://ithelp.ithome.com.tw/upload/images/20161228/20103149vQLTXZKF5u.png

待續。


上一篇
Day 27: 選擇角色、開始動畫
下一篇
Day 29: 廣告(Unity Ads)
系列文
我要和天一樣高!!!(Unity 2D手機小遊戲開發日誌)30

尚未有邦友留言

立即登入留言