1. 程式人生 > >unity3d 牧師與惡魔

unity3d 牧師與惡魔

本次遊戲的設計採用MVC模式,遊戲中的所有GameObject就是Model,View包括toSolveClick和GuiCtrl,Controller包括FirstController以及FirstController裡的各個controller(boat,environment,people)。
這裡寫圖片描述

首先先把預設都做出來,黑色方塊代表惡魔,白色方塊代表牧師(黑色是邪惡,白色是正義,很不錯的設計)
這裡寫圖片描述

導演的程式碼主要參考老師的,當然別的程式碼也借鑑了不少師兄部落格上的程式碼。但我個人感覺UesrAction應該是每個場景通用的動作,如暫停,開始,重置等,雖然本次只有一個場景,但個人感覺邏輯上要有一個類(firstScenceUserAction)代表第一個場記繼承的動作,因為如果要做幾個場景的話,其他場記與第一個場記應該有相同的,也有不同的,因此繼承的類應該另外再寫,下一關繼承的就不是firstScenceUserAction。

namespace baseCode{
    public class Director : System.Object {
        private static Director _instance;
        public sceneController currentSceneController { get; set; }

        public sceneController CurrentScenceController{ get; set; }
        public static Director getInstance() {
            if
(_instance == null) { _instance = new Director (); } return _instance; } } public interface sceneController{ void loadResources(); } } public interface UserAction{ void reset(); } public interface firstScenceUserAction: UserAction{ void
boatMove (); void getBoatOrGetShore (string name); void reset(); string getStatus(); }

本次作業中遇到一個比較棘手的問題就是如何在玩家點選了遊戲物件(船,人)後作出反應,解決的辦法是給船和人都掛上一個指令碼,當滑鼠點選後,通過導演找到場記,達到通知場記的目的。

public class firstScenceSolveClick : MonoBehaviour {
    firstScenceUserAction action ;
    string characterName ;
    // Use this for initialization
    void Start () {
        action = Director.getInstance().currentSceneController as firstScenceUserAction;
    }
    public void setName(string name){
        characterName = name;
    }
    void Update(){
    }
    // Update is called once per frame
    void OnMouseDown(){
        if (action.getStatus() != "playing") {
            return;
        }
        else{
            if (characterName == "boat") {
                action.boatMove ();
            }
            else {
                action.getBoatOrGetShore (name);
            }
        }
    }
}

其次就是場記判斷人或者船可以動了,但是如何讓他們動起來的問題,我的解決辦法是給他們都掛上一個運動指令碼,也即他們一直運動,運動到目的地就不會運動了,那麼場記的任務就只要給這個運動指令碼設定目標即可。下面是該運動指令碼的程式碼,只寫了船的運動,沒有寫人的運動。(因為感覺人的運動有點奇怪,按照我的場景佈置,人要下船不能直接在岸上走到船上,因為那樣就和前面的人撞在一起了,感覺很奇怪,當然也可以讓他跳起來,跳到船上,但是就不要在意這些細節了,會寫船的運動就好了,因此點選人是直接將人放到要放的地方。)

public class boatMoveBeahave: MonoBehaviour{
    Vector3 aim = new Vector3 (4f, 0.7f, 0f);
    float speed = 20.0f;
    string status = "waiting" ;

    void Update(){
        if (this.transform.position == aim) {
            status = "waiting";
        }
        else {
            this.transform.position = Vector3.MoveTowards (this.transform.position, aim , speed * Time.deltaTime);  
        }
    }

    public void setAim(Vector3 aim){
        this.aim = aim;
        status = "running"; 
    }

    public string getState(){
        return status;
    }
}

在寫程式碼的時候還寫了幾個控制器類,畢竟一下子做出場記是很難的,分幾個部分使得程式碼更有層次。也更有邏輯,例如人要上船,那麼人的控制器就要去找船的控制器提供位置,人要上岸,然的控制器就找環境的控制器要位置,這樣就可以減少場記中的程式碼,場記只要告訴人的控制器要上船,要上岸就可以了。

下面是船的控制器。

public class boatController{
    GameObject boat;
    readonly boatMoveBeahave updateBoatMove;
    readonly firstScenceSolveClick toSolveClick;
    Vector3 leftPos = new Vector3 (-4f, 0.7f, 0f);
    Vector3 rightPos = new Vector3 (4f, 0.7f, 0f);
    string []nameOfPeopleOnBoat = {"" , ""};
    Vector3 []boatPos = { new Vector3( -0.25f , 1.5f , 0f ) , new Vector3( 0.25f , 1.5f , 0f ) };
    public string size ;
    private string defaultSize ;

    public boatController(string size){
        boat = Object.Instantiate (Resources.Load ("prefabs/boat", typeof(GameObject))
            , rightPos , Quaternion.identity, null) as GameObject ;
        boat.name = "boat";
        toSolveClick = boat.AddComponent (typeof(firstScenceSolveClick)) as firstScenceSolveClick;
        toSolveClick.setName (boat.name);
        updateBoatMove = boat.AddComponent (typeof(boatMoveBeahave)) as boatMoveBeahave;
        defaultSize = size;
        this.size = defaultSize;
    }

    public bool ifEmpty(){
        return nameOfPeopleOnBoat[0] == "" && nameOfPeopleOnBoat[1] == "";
    }
    public bool ifHaveSeat(){
        return nameOfPeopleOnBoat[0] == "" || nameOfPeopleOnBoat[1] == "";
    }

    public void move(){
        if (size == "right") {
            updateBoatMove.setAim (leftPos);
            size = "left";
        } 
        else {
            updateBoatMove.setAim (rightPos);
            size = "right";
        }
    }

    public string getRunningState(){
        return updateBoatMove.getState ();
    }

    public string[] getPassengerName(){
        return nameOfPeopleOnBoat;
    }

    public GameObject getBoat(){
        return boat;
    }

    public void outBoat(string name){
        if (nameOfPeopleOnBoat [0] == name) {
            nameOfPeopleOnBoat [0] = "";
        } 
        else if (nameOfPeopleOnBoat [1] == name) {
            nameOfPeopleOnBoat [1] = "";
        }
    }

    public Vector3 getBoatPos(string name ){
        Vector3 result =  Vector3.zero;
        for (int loop = 0; loop < 2; loop++) {
            if (nameOfPeopleOnBoat [loop].Length == 0) {
                nameOfPeopleOnBoat [loop] = name;
                result = boatPos [loop];
                break;
            }
        }
        return result;
    }

    public void reset(){
        nameOfPeopleOnBoat [0] = nameOfPeopleOnBoat [1] = "";
        size = defaultSize;
        updateBoatMove.setAim (rightPos);
    }
}

還有人的控制器

public class peopleController{

    GameObject people;
    private string status;
    public string size ;
    private string defaultSize ; 
    firstScenceSolveClick solveClick;
    int number ;

    public peopleController(string name , int number , Vector3 pos , string status , string size){
        people = Object.Instantiate (Resources.Load ("prefabs/" + name, typeof(GameObject))
            , pos, Quaternion.identity, null) as GameObject;
        people.name = name + number.ToString() ;
        solveClick = people.AddComponent (typeof(firstScenceSolveClick)) as firstScenceSolveClick;
        solveClick.setName (people.name);
        this.number = number;
        this.status = status;
        defaultSize = size;
        this.size = size;
    }
    public string getName(){
        return people.name;
    }
    public string getStatus(){
        return status;
    }
    public void getOnBoat(boatController boatCtrl){
        status = "boat";
        people.transform.parent = boatCtrl.getBoat().transform ;
        people.transform.localPosition = boatCtrl.getBoatPos( getName() ) ;
    }
    public void getOffBoat(environmentController envCtrl){
        status = "shore";
        people.transform.parent = null;
        people.transform.position = envCtrl.getPosVec(size , number );
    }

    public void reset( environmentController envCtrl ){
        status = "shore";
        size = defaultSize;
        people.transform.parent = null;
        people.transform.position = envCtrl.getPosVec(size , number );
    }

以及環境的控制器

public class environmentController{
    GameObject environment;
    Vector3 environmentPos =  Vector3.zero ;
    Vector3 leftShorePos = new Vector3 (-6f, 2f, 0f);
    Vector3 rightShorePos = new Vector3 (6f, 2f, 0f);
    public environmentController(){
        environment = Object.Instantiate (Resources.Load ("prefabs/environment", typeof(GameObject))
            , environmentPos, Quaternion.identity, null) as GameObject;
    }

    public Vector3 getPosVec(string size , int number){
        Vector3 result = new Vector3(0 , 0 , 0);
        if (size == "right") {
            result = rightShorePos + number * Vector3.right;        
        }
        else {
            result = leftShorePos + number * Vector3.left;
        }
        return result;
    }
}

主要的場記的程式碼

public class firstSceneController : MonoBehaviour , sceneController , firstScenceUserAction{
    environmentController environment;
    boatController myBoat ;
    const int numOfPirestOrDevil = 3;
    peopleController[] peopleCtrl  = new peopleController[numOfPirestOrDevil * 2];
    string oriSize = "right";
    FirstSceneGuiCtrl guiCtrl;
    Vector3 environmentPos = Vector3.zero; 
    Vector3 leftShorePos = new Vector3 (-6f, 2f, 0f);
    Vector3 rightShorePos = new Vector3 (6f, 2f, 0f);
    string gameStatus = "playing";
    void Awake(){
        Director.getInstance ().currentSceneController = this;
        guiCtrl = gameObject.AddComponent <FirstSceneGuiCtrl>() as FirstSceneGuiCtrl;
        loadResources();

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

    }
//check Win Or Lost
    void Update(){
        if (myBoat.getRunningState () == "running") {
            return;
        }

        int leftDevil = 0, leftPriest = 0, rightDevil = 0, rightPriest = 0 , leftShorePeople = 0;
        for (int loop = 0; loop < numOfPirestOrDevil * 2; loop++) {
            if (peopleCtrl [loop].getStatus() == "shore" && peopleCtrl [loop].size == "left") {
                leftShorePeople++;
            } 
            if (peopleCtrl [loop].getName()[0] == 'd' && peopleCtrl [loop].size == "left") {
                leftDevil++;
            } else if (peopleCtrl [loop].getName()[0] == 'd' && peopleCtrl [loop].size == "right") {
                rightDevil++;
            } else if (peopleCtrl [loop].getName()[0] == 'p' && peopleCtrl [loop].size == "left") {
                leftPriest++;
            } else {
                rightPriest++;
            }
        }
        if ((leftDevil > leftPriest && leftPriest != 0) || (rightPriest != 0 && rightDevil > rightPriest)) {
            gameStatus = "lost";
        }
        else if (leftShorePeople == 6) {
            gameStatus = "win";
        }
    }

當然,這樣做其實也有點麻煩,就是不同的控制器是並列的,因此如果要呼叫別的控制器,那麼就要場記把控制器當成引數傳過去,但是在邏輯上比較好,不那麼混亂。

最後還要寫的就是一些使用者按的按鈕。

public class FirstSceneGuiCtrl : MonoBehaviour {
    firstScenceUserAction action ;
    bool ifShowHelp = true;
    string helpText = "黑色方塊為惡魔,白色方塊為牧師,目標:讓所有牧師和惡魔都到左岸,規則:點選牧師或者惡魔可以上岸或者上船,任意一邊岸的惡魔" +
                      "數量若多於牧師(包括那一邊船裡的人物),那麼遊戲失敗。";
    // Use this for initialization
    void Start () {
        action = Director.getInstance().currentSceneController as firstScenceUserAction;
    }

    // Update is called once per frame
    void OnGUI () {
        firstScenceUserAction action = Director.getInstance().currentSceneController as firstScenceUserAction;
        string status = action.getStatus ();
        if (ifShowHelp == true) {
            GUI.Box (new Rect (Screen.width / 2 - 100, Screen.height / 2 - 90, 200, 180), "" );
            GUI.Label (new Rect (Screen.width / 2 - 100, Screen.height / 2 - 90, 200, 180), helpText);
            if( GUI.Button (new Rect (Screen.width / 2 - 20, Screen.height / 2 + 60, 40, 30), "Ok") ){
                ifShowHelp = false;
            } 
        }
        if (GUI.Button (new Rect(10 , 10 , 100, 50), "help") ) {
            ifShowHelp = true;
        }
        if (status == "playing") {
            if (GUI.Button (new Rect(130 , 10 , 100, 50), "restart")) {
                action.reset ();
            }
        }
        else {
            string showMsg;
            if (status == "lost") {
                showMsg = "you lost!!";
            }
            else {
                showMsg = "you win!!";
            }
            if (GUI.Button (new Rect(Screen.width/2-50, Screen.height/2-25, 100, 50), showMsg) ) {
                action.reset ();
            }
        }
    }
}