Unity3D筆記(三)牧師和魔鬼的遊戲

這次做的是一個魔鬼和牧師的遊戲
遊戲DEMO

Priests and Devils is a puzzle game in which you will help the
Priests and Devils to cross the river within the time limit.
There are 3 priests and 3 devils at one side of the river.
They all want to get to the other side of this river, but
there is only one boat and this boat can only carry two
persons each time. And there must be one person steering the
boat from one side to the other side. In the flash game, you
can click on them to move them and click the go button to move
the boat to the other direction. If the priests are out
numbered by the devils on either side of the river, they get
killed and the game is over. You can try it in many ways. Keep
all priests alive! Good luck!

源碼可以直接到最後看


遊戲的規則還是很簡單,做遊戲的第一步首先是列出遊戲中對象:

  • 牧師
  • 魔鬼
  • 兩岸

    之後再列出遊戲中的行爲表

行爲 條件
開船 船上至少有一人
牧師上船 船上有空位
魔鬼上船 船上有空位
左邊下船 左邊船上有乘客
右邊下船 右邊船上有乘客
遊戲勝利 6人均抵達對岸
遊戲失敗 一邊的魔鬼人數大於牧師數

之後我們需要製作出遊戲中的資源:
* 牧師
* 魔鬼
* 船
* 兩岸

預製件
具體方式比較簡單,就不多說了
本次的3D過河小遊戲遵循了MVC架構,在架構中,有導演,場記。所有的場記擁有統一的接口,由導演控制各個場景的進入和退出,而場記負責管理自己所在場景的資源,對象,動作,模型等。

UML圖如下:
UML

首先根據行爲表定義UI應該批准用戶完成什麼操作:

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

public interface IUserAction {
    void reset();
    void drive();

    void priest_to_boat_at_begin();
    void devil_to_boat_at_begin();
    void priest_to_boat_at_end();
    void devil_to_boat_at_end();

    void left_off_boat();
    void right_off_boat();
}

這裏意思大概是UI會向模型傳遞這些消息,然後模型中要實現這些操作,使得UI可以調用接口中的這些函數,完成UI和模型之間的通訊。
具體UI的實現佈局由UserGUI實現,其擁有一個成員變量,IUserAction。

之後就開始完成GenGameObject, 這個類主要負責加載資源,管理遊戲對象,負責遊戲邏輯。
首先是使用c#的collection進行遊戲對象的管理:

// Collections to manage gameobject
    private Queue<GameObject> priest_begin;
    private Queue<GameObject> priest_end;

    private Queue<GameObject> devil_begin;
    private Queue<GameObject> devil_end;

    private Queue<GameObject> onTheWay;

在每個位置分別用一個集合去管理遊戲對象
由於定位這些遊戲對象需要用到很多vector的位置信息,又弄了一堆變量存儲位置信息:

private Vector3 land_begin_position;
    private Vector3 land_end_position;

    private Vector3 priest_begin_position;
    private Vector3 priest_end_position;

    private Vector3 devil_begin_position;
    private Vector3 devil_end_position;

    private Vector3 object_move;
    private Vector3 object_move_at_the_boat;

    private Vector3 on_ship_left_position;
    private Vector3 on_ship_right_positon;

    private Vector3 ship_begin_position;
    private Vector3 ship_end_position;

爲了實現船的運動,添加枚舉類型ship_state,update通過這個變量進行判斷船是否應該運動,以達到運動的目的。

    private enum ship_location {
        begin, end, moving_to_begin, moving_to_end
    }
    private ship_location ship_info;

那麼drive這個函數就很好實現了

public void drive() {
        if (onTheWay.Count > 0) {
            if (ship_info == ship_location.begin) {
                ship_info = ship_location.moving_to_end;
            } else if (ship_info == ship_location.end) {
                ship_info = ship_location.moving_to_begin;
            }
            game_state = State.moving;
        }
    }

同時爲了實現船上的乘客和船一起運動,乘客上船時將其transform.parent設爲船的transform,那麼乘客就可以跟隨船一起運動。

public void priest_to_boat_at_begin() {
        if (priest_begin.Count > 0 && onTheWay.Count < 2 && ship_info == ship_location.begin) {
            GameObject temp = priest_begin.Dequeue();
            temp.transform.parent = ship.transform;
            onTheWay.Enqueue(temp);
        }
    }

還有類似的函數實現魔鬼和牧師從各個方向上船。這樣就實現了基本的運動。
這裏寫圖片描述

爲了實現模型向UI通訊,添加枚舉量

    public enum State {
        win, over, moving, normal
    }
    public State game_state;

再添加check函數,每幀都check一次,如果遊戲結束,或者遊戲勝利,立刻通知UI結束遊戲。

private void check() {
        count_num_of_object();
        // check at the begin
        if (num_of_devil_at_the_begin > num_of_priest_at_the_begin && num_of_priest_at_the_begin != 0) {
            game_state = State.over;
        } else if (num_of_devil_at_the_end > num_of_priest_at_the_end && num_of_priest_at_the_end != 0) {
            game_state = State.over;
        } else if (num_of_devil_at_the_end + num_of_priest_at_the_end == 6) {
            game_state = State.win;
        }
    }

最後上一波完整代碼

SSDirector.cs:

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

public class SSDirector : System.Object {
    private static SSDirector _instance;

    public ISceneController currentSceneController { get; set; }
    public bool running { get; set; }

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

ISceneController.cs

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

public interface ISceneController {
    void LoadResources ();
    // void Pause ();
    // void Resume ();
}

IUserAction.cs

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

public interface IUserAction {
    void reset();
    void drive();

    void priest_to_boat_at_begin();
    void devil_to_boat_at_begin();
    void priest_to_boat_at_end();
    void devil_to_boat_at_end();

    void left_off_boat();
    void right_off_boat();
}

GenGameObjects.cs

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

// generator gameobject in each frame

public class GenGameObjects : MonoBehaviour, ISceneController, IUserAction {

    // Collections to manage gameobject
    private Queue<GameObject> priest_begin;
    private Queue<GameObject> priest_end;

    private Queue<GameObject> devil_begin;
    private Queue<GameObject> devil_end;

    private Queue<GameObject> onTheWay;
    private GameObject ship;
    private enum ship_location {
        begin, end, moving_to_begin, moving_to_end
    }
    private ship_location ship_info;
    // the number of object in each side
    private int num_of_priest_at_the_begin;
    private int num_of_priest_at_the_end;
    private int num_of_devil_at_the_begin;
    private int num_of_devil_at_the_end;
    // position information
    private Vector3 land_begin_position;
    private Vector3 land_end_position;

    private Vector3 priest_begin_position;
    private Vector3 priest_end_position;

    private Vector3 devil_begin_position;
    private Vector3 devil_end_position;

    private Vector3 object_move;
    private Vector3 object_move_at_the_boat;

    private Vector3 on_ship_left_position;
    private Vector3 on_ship_right_positon;

    private Vector3 ship_begin_position;
    private Vector3 ship_end_position;

    public enum State
    {
        win, over, moving, normal
    }
    public State game_state;
    // Use this for initialization
    void Start () {

    }
    // Update is called once per frame
    void Update () {
        setPostion(priest_begin, priest_begin_position, object_move);
        setPostion(devil_begin, devil_begin_position, object_move);
        setPostion(priest_end, priest_end_position, object_move);
        setPostion(devil_end, devil_end_position, object_move);
        setPostion(onTheWay, on_ship_left_position, object_move_at_the_boat);

        if (ship_info == ship_location.moving_to_end) {
            float step = 3 * Time.deltaTime;
            ship.transform.position = Vector3.MoveTowards(ship.transform.position, ship_end_position, step);
            if (ship.transform.position == ship_end_position){
                ship_info = ship_location.end;
                game_state = State.normal;  
            }
        } else if (ship_info == ship_location.moving_to_begin) {
            float step = 3 * Time.deltaTime;
            ship.transform.position = Vector3.MoveTowards(ship.transform.position, ship_begin_position, step);
            if (ship.transform.position == ship_begin_position) {
                ship_info = ship_location.begin;
                game_state = State.normal;
            }
        } else {
            check();
        }
    }

    void Awake() {
        land_begin_position.Set(-6, 0, 0);
        land_end_position.Set(6, 0, 0);

        priest_begin_position.Set(-8, 1, 0);
        priest_end_position.Set(4, 1, 0);

        devil_begin_position.Set(-5.3f, 1, 0);
        devil_end_position.Set(6.5f, 1, 0);

        object_move.Set(1, 0, 0);
        object_move_at_the_boat.Set(0.5f, 0, 0);
        on_ship_left_position.Set(-0.3f, 1.3f, 0);
        on_ship_right_positon.Set(0.3f, 1.5f, 0);

        ship_begin_position.Set(-1.7f, 0, 0);
        ship_end_position.Set(1.7f, 0, 0);

        priest_begin = new Queue<GameObject>();
        priest_end = new Queue<GameObject>();
        devil_begin = new Queue<GameObject>();
        devil_end = new Queue<GameObject>();
        onTheWay = new Queue<GameObject>();

        game_state = State.normal;

        ship_info = ship_location.begin;
        SSDirector director = SSDirector.getInstance();
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
    }

    public void LoadResources() {
        // generate gameobject from prefabs
        GameObject land_left = Instantiate(Resources.Load("Prefabs/land") as GameObject, land_begin_position, Quaternion.identity);
        GameObject land_right = Instantiate(Resources.Load("Prefabs/land") as GameObject, land_end_position, Quaternion.identity);
        ship = Instantiate(Resources.Load("Prefabs/ship") as GameObject, ship_begin_position, Quaternion.identity);
        for (int i = 0; i < 3; ++i) {
            devil_begin.Enqueue(Instantiate(Resources.Load("Prefabs/devil"), devil_begin_position + i * object_move, Quaternion.identity) as GameObject);
            priest_begin.Enqueue(Instantiate(Resources.Load("Prefabs/priest"), priest_begin_position + i * object_move, Quaternion.identity) as GameObject);
        }
        Debug.Log("in LoadResources");
        Debug.Log(priest_begin.Count);
    }
    // set object postion on each update()
    // implement it later
    void setPostion(Queue<GameObject> obj_queue, Vector3 first, Vector3 move) {
        GameObject[] temp = obj_queue.ToArray();
        for (int i = 0; i < temp.Length; ++i) {
            temp[i].transform.localPosition = first + i * move;
        }
    }
    // get on the boat
    public void priest_to_boat_at_begin() {
        if (priest_begin.Count > 0 && onTheWay.Count < 2 && ship_info == ship_location.begin) {
            GameObject temp = priest_begin.Dequeue();
            temp.transform.parent = ship.transform;
            onTheWay.Enqueue(temp);
        }
    }
    public void devil_to_boat_at_begin() {
        if (devil_begin.Count > 0 && onTheWay.Count < 2 && ship_info == ship_location.begin) {
            GameObject temp = devil_begin.Dequeue();
            temp.transform.parent = ship.transform;
            onTheWay.Enqueue(temp);
        }
    }
    public void priest_to_boat_at_end() {
        if (priest_end.Count > 0 && onTheWay.Count < 2 && ship_info == ship_location.end) {
            GameObject temp = priest_end.Dequeue();
            temp.transform.parent = ship.transform;
            onTheWay.Enqueue(temp);
        }
    }
    public void devil_to_boat_at_end() {
        if (devil_end.Count > 0 && onTheWay.Count < 2 && ship_info == ship_location.end) {
            GameObject temp = devil_end.Dequeue();
            temp.transform.parent = ship.transform;
            onTheWay.Enqueue(temp);
        }
    }
    // get off the boat
    public void left_off_boat() {
        if (onTheWay.Count > 0) {
            GameObject temp = onTheWay.Dequeue();
            getOffTheBoat(temp);
        }
    }

    public void right_off_boat() {
        if (onTheWay.Count > 1) {
            GameObject temp_1 = onTheWay.Dequeue();
            GameObject temp_2 = onTheWay.Dequeue();
            onTheWay.Enqueue(temp_1);
            getOffTheBoat(temp_2);
        }
    }
    public void drive() {
        if (onTheWay.Count > 0) {
            if (ship_info == ship_location.begin) {
                ship_info = ship_location.moving_to_end;
            } else if (ship_info == ship_location.end) {
                ship_info = ship_location.moving_to_begin;
            }
            game_state = State.moving;
        }
    }
    // private void getOnTheBoat(GameObject obj) {

    // }
    private void getOffTheBoat(GameObject obj) {
        obj.transform.parent = null;
        if (ship_info == ship_location.begin) {
            if (obj.tag == "devil") {
                devil_begin.Enqueue(obj);
            } else {
                priest_begin.Enqueue(obj);
            }
        } else {
            if (obj.tag == "devil" ) {
                devil_end.Enqueue(obj);
            } else {
                priest_end.Enqueue(obj);
            }
        }
    }

    void count_num_of_object() {
        num_of_devil_at_the_begin = devil_begin.Count;
        num_of_devil_at_the_end = devil_end.Count;
        num_of_priest_at_the_begin = priest_begin.Count;
        num_of_priest_at_the_end = priest_end.Count;
        GameObject[] temp = onTheWay.ToArray();
        foreach (GameObject item in onTheWay)
        {
            if (item.tag == "priest") {
                if (ship_info == ship_location.begin) {
                    num_of_priest_at_the_begin++;
                } else {
                    num_of_priest_at_the_end++;
                }
            } else {
                if (ship_info == ship_location.begin) {
                    num_of_devil_at_the_begin++;
                } else {
                    num_of_devil_at_the_end++;
                }
            }
        }
    }
    private void check() {
        count_num_of_object();
        // check at the begin
        if (num_of_devil_at_the_begin > num_of_priest_at_the_begin && num_of_priest_at_the_begin != 0) {
            game_state = State.over;
        } else if (num_of_devil_at_the_end > num_of_priest_at_the_end && num_of_priest_at_the_end != 0) {
            game_state = State.over;
        } else if (num_of_devil_at_the_end + num_of_priest_at_the_end == 6) {
            game_state = State.win;
        }
    }

    public void reset() {
        Application.LoadLevel(Application.loadedLevelName);  
        game_state = State.normal;
    }
}

UserGUI.cs:

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

public class UserGUI : MonoBehaviour {
    private IUserAction action;
    private GenGameObjects sc;
    private float button_width;
    private float button_height;
    private float button_y;
    private float move_x;

    // Use this for initialization
    void Start () {
        sc = SSDirector.getInstance().currentSceneController as GenGameObjects;
        action = SSDirector.getInstance ().currentSceneController as IUserAction;
        button_width = 100;
        button_height = 50;
        button_y = 80;
        move_x = 100;
        Debug.Log("UI start");
        Debug.Log(action);
    }

    void OnGUI() {
        // reset button
        if (GUI.Button(new Rect(350, button_y + 150, button_width, button_height), "Reset")) {
            action.reset();
        }
        if (sc.game_state == GenGameObjects.State.over) {
            GUI.Label(new Rect(350, button_y, button_width * 2, button_height * 2), "GameOver!");
            return;
        }
        if (sc.game_state == GenGameObjects.State.win) {
            GUI.Label(new Rect(350, button_y, button_width * 2, button_height * 2), "Win");
            return;
        }
        // priest to boat at the begin
       if (GUI.Button(new Rect(70, button_y, button_width, button_height), "On")) {
            action.priest_to_boat_at_begin();
            Debug.Log("button clicked");
       }
       // devil to boat at the begin
       if (GUI.Button(new Rect(70 + move_x, button_y, button_width, button_height), "On")) {
            action.devil_to_boat_at_begin();
       }
       // priest to boat at the end
       if (GUI.Button(new Rect(540, button_y, button_width, button_height), "On")) {
            action.priest_to_boat_at_end();
       }
       // devil to boat at the end
       if (GUI.Button(new Rect(540 + move_x, button_y, button_width, button_height), "On")) {
            action.devil_to_boat_at_end();
       }
       // left of boat off
       if (GUI.Button(new Rect(320, button_y, button_width, button_height), "Off")) {
            action.left_off_boat();
       }
       // right of boat off
       if (GUI.Button(new Rect(320 + move_x, button_y, button_width, button_height), "Off")) {
            action.right_off_boat();
       }
       // go button
       if (GUI.Button(new Rect(350, button_y - 80, button_width, button_height), "GO!")) {
            action.drive();
       }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章