学习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中?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容