學習Unity(2)用GUI制作口算小游戲

游戲規則

這個游戲考驗你的口算能力,你需要在規定時間內輸入口算題的答案,然后才能進入下一題;如果超時,游戲失敗。
游戲結束以后會顯示你的答對題目數,你可以不斷挑戰自己的記錄!

游戲流程
游戲展示1
游戲展示2
游戲展示3

先放上我的代碼:

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

public class puzzle : MonoBehaviour {
    // these two is used to make time bar
    public Texture progressBackground;  // green background
    public Texture progressFrontground;  // red 'frontground'


    private int mode; // 1->introduction, 2->playing, 3->gameover
    private float start_time;
    private float answer_time;

    private int operand1;
    private int operand2;
    private int _operator; // 0->+, 1->-, 2->*, 3->/
    private int answer;
    private GUIStyle question_style;
    private string user_answer;
    private int count;

    // Use this for initialization
    void Start () {
        mode = 1;
        question_style = new GUIStyle ();
        question_style.fontSize = 50;
    }

    void start_game() {
        mode = 2;
        start_time = Time.time;
        answer_time = 10F;
        user_answer = "";
        generate_question ();
        count = 0;
    }

    void OnGUI() {
        if (mode == 1) {
            page_introduction ();
        } else if (mode == 2) {
            page_game ();
        } else if (mode == 3) {
            page_gameover ();
        }
    }

    void page_introduction() {
        GUI.Label (new Rect (80, 20, 400, 60), "You should input the answer before time run out!");
        if (GUI.Button (new Rect (80, 60, 150, 50), "Start")) {
            start_game ();
        }
    }

    void page_game() {
        if (Time.time - start_time > answer_time) {
            mode = 3;
        }

        if (user_answer == answer + "") {   // right answer
            start_time = Time.time;
            generate_question ();
            user_answer = "";
            count++;
        }

        draw_time_bar ((Time.time - start_time) / answer_time);
        draw_question ();


        if (GUI.GetNameOfFocusedControl () == string.Empty) {
            GUI.FocusControl ("User_answer");
            GUI.SetNextControlName ("User_answer");
        }
        draw_text_area ();
    }


    void page_gameover() {
        GUI.Label (new Rect (80, 20, 400, 60), "You lose!\nYou get "+count+" right");
        if (GUI.Button (new Rect (80, 60, 150, 50), "Restart")) {
            start_game ();
        }
    }

    // // // // // // // // // utility function // // // // // // // // //

    void generate_question() {
        _operator = Mathf.FloorToInt(Random.value * 4);
        if (_operator == 0) {   // +
            operand1 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            answer = operand1 + operand2;
        }

        else if (_operator == 1) {  // -
            operand1 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            answer = operand1 - operand2;
        }

        else if (_operator == 2) {  // *
            operand1 = Mathf.FloorToInt(Random.Range(-10F , 10F));
            operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            answer = operand1 * operand2;
        }

        else if (_operator == 3) {  // /
            operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            while (operand2 == 0) {
                operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
            }
            answer = Mathf.FloorToInt(Random.Range(-10F , 10F));
            operand1 = operand2 * answer;
        }
        //Debug.Log (operand1 + " " + _operator + " " + operand2);
    }

    void draw_time_bar(float percent) {
        GUI.DrawTexture(new Rect(100, 10, 260, 10), progressBackground);
        GUI.DrawTexture(new Rect(100, 10, 260*percent, 10), progressFrontground);
    }

    void draw_question() {
        string question = "";
        question += operand1;
        question += " ";
        if (_operator == 0) {   // +
            question += "+";
        }

        else if (_operator == 1) {  // -
            question += "-";
        }

        else if (_operator == 2) {  // *
            question += "*";
        }

        else if (_operator == 3) {  // /
            question += "/";
        }
        question += " ";
        question += operand2;
        question += " = ?";

        GUI.Label(new Rect(100, 40, 400, 300), question, question_style);
    }

    void draw_text_area() {
        user_answer = GUI.TextField(new Rect(170, 100, 80, 20), user_answer, 200);
    }
}

使用方法:這是一個2D項目,我的所有代碼就是這個C#的Script,掛載在camera上面。progressBackground和progressFrontground是兩個圖片資源(asset),我們自己做一張紅色和一張綠色的圖片加入Asset,然后將這兩個資源拖到Inspector的對應欄目里面,就可以運行了。

綁定資源

下面我來解釋我的代碼:

void Start () {
    mode = 1;
    question_style = new GUIStyle ();
    question_style.fontSize = 50;
}

mode是一個私有int變量,表示現在的游戲狀態,1->introduction,2->playing,3->gameover。question_style是一個GUIStyle類型的變量,我們后面用它來控制GUI.Label的樣式。Start ()只在程序啟動的時候執行一次。

GUI.Label是一個GUI組件,用來顯示文字、圖片等內容。我們等一下會解釋它的使用。


void start_game() {
    mode = 2;
    start_time = Time.time;
    answer_time = 10F;
    user_answer = "";
    generate_question ();
    count = 0;
}

start_game()將在我們每次點擊Start、Restart按鈕之后執行,用來將一些變量設成游戲初始狀態。generate_question ()用來生成一個新的題目。題目信息將保存在
private int operand1;
private int operand2;
private int _operator; // 0->+, 1->-, 2->*, 3->/
private int answer;
這四個變量中。
我們將start_time設成了現在的時間,answer_time 表示時間限制,在這里是10秒,我們等一下會用這兩個變量來計算時間的進度、檢查是否超時。count 表示已經答對的題目數,我們要將它初始化為0。

Time.time返回從游戲程序啟動到現在過了多少秒。


void OnGUI() {
    if (mode == 1) {
        page_introduction ();
    } else if (mode == 2) {
        page_game ();
    } else if (mode == 3) {
        page_gameover ();
    }
}

OnGUI()函數很簡單,因為我們將畫出頁面的工作封裝在了這3個函數中。

OnGUI()函數每一幀調用一次,用來畫出這一幀要顯示的界面,在上一幀OnGUI畫出的界面不會保留到下一幀,因此游戲程序在不斷地清除、畫圖、清除、畫圖。。。


我們來看看page_introduction()是怎么畫出介紹頁面的

void page_introduction() {
    GUI.Label (new Rect (80, 20, 400, 60), "You should input the answer before time run out!");
    if (GUI.Button (new Rect (80, 60, 150, 50), "Start")) {
        start_game ();
    }
}

在這里我們畫出了一個Label控件,new Rect (80, 20, 400, 60)這個參數表示:這個控件距離上邊80,距離左邊20,寬400,高60。
GUI.Button (new Rect (80, 60, 150, 50)創建了一個Button控件,注意這個表達式返回的是這個button在這一幀是否被點擊(bool),所以每次我們一點擊Start按鈕就會調用start_game(),開始游戲。


接下來我們看看要怎么畫出口算進行時的界面:

void page_game() {
        if (Time.time - start_time > answer_time) {
            mode = 3;
        }

        if (user_answer == answer + "") {   // right answer
            start_time = Time.time;
            generate_question ();
            user_answer = "";
            count++;
        }

        draw_time_bar ((Time.time - start_time) / answer_time);
        draw_question ();


        if (GUI.GetNameOfFocusedControl () == string.Empty) {
            GUI.FocusControl ("User_answer");
            GUI.SetNextControlName ("User_answer");
        }
        draw_text_area ();
    }

第一個if判斷是否超時。第二個if判斷是否已經輸入正確答案(user_answer表示目前的輸入,answer + ""巧妙地將answer從int轉變為了string類型),如果輸入正確則將進入下一題(重新生成題目、將輸入清空、增加已答題數量并將題目開始時間設為現在)。
draw_time_bar用來畫出一個進度條,傳進去的參數其實就是一個0~1地小數,表示時間已經進行了百分之幾。
draw_question用來將題目畫出來。
下一個if語句比較難理解,它的作用是讓輸入框自動聚焦(也就是你玩游戲的時候不需要用鼠標去點輸入框,直接輸入就行了)。SetNextControlName給下一個產生的控件定了一個名字User_answer(我們將在最后一句draw_text_area ();中畫出一個輸入框)。FocusControl就將光標聚焦到這個輸入框上。

因為OnGUI()函數會不斷執行,所以這兩句話的順序無關緊要,也就產生一幀的差別。

GUI.GetNameOfFocusedControl () == string.Empty這個判斷語句的意思是只在光標沒有聚焦的時候執行下面的代碼塊,用來增加一點性能,大大減少了這段代碼的執行次數。
draw_text_area畫出了一個輸入框。


page_gameover畫出gameover頁面的方式與之前的page_introduction類似,我就不介紹了。


接下來介紹我們之前調用的一些工具函數是怎么實現的

void generate_question() {
    _operator = Mathf.FloorToInt(Random.value * 4);
    if (_operator == 0) {   // +
        operand1 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        answer = operand1 + operand2;
    }

    else if (_operator == 1) {  // -
        operand1 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        answer = operand1 - operand2;
    }

    else if (_operator == 2) {  // *
        operand1 = Mathf.FloorToInt(Random.Range(-10F , 10F));
        operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        answer = operand1 * operand2;
    }

    else if (_operator == 3) {  // /
        operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        while (operand2 == 0) {
            operand2 = Mathf.FloorToInt(Random.Range(-100F , 100F));
        }
        answer = Mathf.FloorToInt(Random.Range(-10F , 10F));
        operand1 = operand2 * answer;
    }
    //Debug.Log (operand1 + " " + _operator + " " + operand2);
}

我們先隨機產生了一個操作符(_operator分別用0123來表示加減乘除)。然后我們產生2個操作數,這里我限制了操作數的范圍,控制了一下口算難度(否則蹦出一個345*674那就不用玩了!)。注意我們如果產生除法題目的方法,先產生operand2和answer,再相乘得到operand1,這樣可以保證題目都是整數。


draw_time_bar用兩句話畫出了時間進度條!

void draw_time_bar(float percent) {
    GUI.DrawTexture(new Rect(100, 10, 260, 10), progressBackground);
    GUI.DrawTexture(new Rect(100, 10, 260*percent, 10), progressFrontground);
}

GUI.DrawTexture用來畫出一個矩形內容框


draw_question主要是將幾個題目數字轉化成了一個string,然后在GUI.Label中顯示:

void draw_question() {
    string question = "";
    question += operand1;
    question += " ";
    if (_operator == 0) {   // +
        question += "+";
    }

    else if (_operator == 1) {  // -
        question += "-";
    }

    else if (_operator == 2) {  // *
        question += "*";
    }

    else if (_operator == 3) {  // /
        question += "/";
    }
    question += " ";
    question += operand2;
    question += " = ?";

    GUI.Label(new Rect(100, 40, 400, 300), question, question_style);
}

draw_text_area用來畫出輸入框。GUI.TextField返回的是在這一幀輸入框的內容,我們又將它賦值給了user_answer,注意user_answer為什么在這句話中出現2次,這樣才能讓user_answer一直是我們的輸入。

void draw_text_area() {
    user_answer = GUI.TextField(new Rect(170, 100, 80, 20), user_answer, 200);
}

你們還可以在原有代碼的基礎下做出一些改進:

  1. 讓用戶可以選擇難度(難度可以通過答題時間、操作數范圍來控制)
  2. 目前的難度波動還是有點大,比如有時候乘法會有點難,如何降低這個難度?
  3. 做一個排行榜!展示你的歷史最高紀錄!
  4. 用一個文件放所有代碼太長了,能不能將工具函數放到另一個script中?
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容