1. 程式人生 > >Unity3D課程學習筆記(六)

Unity3D課程學習筆記(六)

智慧巡邏兵
  • 提交要求:
  • 遊戲設計要求:
    • 建立一個地圖和若干巡邏兵(使用動畫);
    • 每個巡邏兵走一個3~5個邊的凸多邊型,位置資料是相對地址。即每次確定下一個目標位置,用自己當前位置為原點計算;
    • 巡邏兵碰撞到障礙物,則會自動選下一個點為目標;
    • 巡邏兵在設定範圍內感知到玩家,會自動追擊玩家;
    • 失去玩家目標後,繼續巡邏;
    • 計分:玩家每次甩掉一個巡邏兵計一分,與巡邏兵碰撞遊戲結束;
  • 程式設計要求:
    • 必須使用訂閱與釋出模式傳訊息
      • subject:OnLostGoal
      • Publisher: ?
      • Subscriber: ?
    • 工廠模式生產巡邏兵

本次作業要求是做一個智慧巡邏兵的作業,下載的資源動畫用上之後,遊戲是參照一個師兄的做出來的,當然我做的是bug無窮的小遊戲.......,主要還是在寫的基礎上添加了一些趣味性吧。

本次作業還要求用一種新學習的設計模式,訂閱與釋出模式,該模式也稱為觀察者模式,釋出者和訂閱者沒有直接的耦合,是實現模型與檢視分離的重要手段。

本次作業中的地圖是自己參照著用正方體弄出來的,所以感覺可能是這個原因導致很多的bug

可以展示一下自己弄的小模型:

雖然說比較醜吧,但是畢竟還是自己做的



本次作業的UML圖設計如下:



以下是一些主要程式碼,主要分為幾個部分:

1.訂閱與釋出模式的部分,釋出遊戲結束和遊戲得分的事件

GameEventManager的程式碼:

public class GameEventManager : MonoBehaviour
{
    public delegate void GameScoreAction();
    public static event GameScoreAction myGameScoreAction;

    public delegate void GameOverAction();
    public static event GameOverAction myGameOverAction;

    private SceneController scene;
    private bool isGameOver;

    void Start()
    {
        scene = SceneController.getInstance();
        scene.gameEventManager = this;
        isGameOver = false;
    }



    //hero逃離巡邏兵,得分
    public void getScore()
    {
        if (myGameScoreAction != null && !isGameOver)
        {
            myGameScoreAction();
        }
    }

    //當巡邏兵捕捉到英雄,遊戲結束
    public void gameOver()
    {
        if (myGameOverAction != null)
        {
            isGameOver = true;
            myGameOverAction();
        }
    }
    
    public bool getGameState()
    {
        return isGameOver;
    }
}

GameText的程式碼,程式就像搭積木一樣,用的時候加上去,不用的時候就撤去,就像GameText中程式碼一樣,需要加分的時候i就執行加分的事件,否則就執行遊戲結束的事件:

public enum TextType { SCORE, GAMEOVER };
public class GameText : MonoBehaviour
{
    private IGameState states;
    private int score = 0;
    private TextType textType = TextType.SCORE;

    void Start()
    {
        states = SceneController.getInstance() as IGameState;
        checkTheTypeOfText();
    }
    
    private void checkTheTypeOfText()
    {
        if (gameObject.name.Contains("Score"))
        {
            textType = TextType.SCORE;
        }
        else
        {
            textType = TextType.GAMEOVER;
        }
    }

    void OnEnable()
    {
        GameEventManager.myGameScoreAction += gameScore;
        GameEventManager.myGameOverAction += gameOver;
    }

    void OnDisable()
    {
        GameEventManager.myGameScoreAction -= gameScore;
        GameEventManager.myGameOverAction -= gameOver;
    }

    //得分事件
    void gameScore()
    {
        if (textType == TextType.SCORE)
        {
            int index = states.getIndexOfHero();
            switch(index)
            {
                case 0:
                    score += 1;
                    break;
                case 1:
                    score += 2;
                    break;
                case 2:
                    score += 3;
                    break;
                case 3:
                    score += 4;
                    break;
                case 4:
                    score += 5;
                    break;
                case 5:
                    score += 6;
                    break;
            }
            //score++;
            this.gameObject.GetComponent<Text>().text = "Score: " + score;
        }
    }

    //遊戲結束事件
    void gameOver()
    {
        if (textType == TextType.GAMEOVER)
        {
            this.gameObject.GetComponent<Text>().text = "Game Over!";
        }
    }
}

檢測英雄狀態和顏色改變的程式碼:

public class HeroState : MonoBehaviour
{
    public int indexOfHero = -1;

    void Update()
    {
        getIndexOfHero();
        colorChange();
    }

    //檢測巡邏兵所在的位置,總共有6個位置
    void getIndexOfHero()
    {
        float posX = this.gameObject.transform.position.x;
        float posZ = this.gameObject.transform.position.z;
        if (posZ >= FenchLocation.FenchHori)
        {
            if (posX < FenchLocation.FenchVertLeft)
                indexOfHero = 0;
            else if (posX > FenchLocation.FenchVertRight)
                indexOfHero = 2;
            else
                indexOfHero = 1;
        }
        else
        {
            if (posX < FenchLocation.FenchVertLeft)
                indexOfHero = 3;
            else if (posX > FenchLocation.FenchVertRight)
                indexOfHero = 5;
            else
                indexOfHero = 4;
        }
    }

    public void colorChange()
    {
        switch (indexOfHero)
        {
            case 0:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Black");
                break;
            case 1:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Blue");
                break;
            case 2:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Gray");
                break;
            case 3:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Pink");
                break;
            case 4:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Red");
                break;
            case 5:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Yellow");
                break;
            default:
                this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Red");
                break;
        }
    }
}

工廠模式,生成地圖上的6個巡邏兵:

public class PatrolFactory : System.Object
{
    private static PatrolFactory instance;
    private GameObject PatrolItem;

    //初始化6個巡邏兵的位置
    private Vector3[] PatrolPosition = new Vector3[] {
            new Vector3(-4, 0, 16), new Vector3(-1, 0, 16),
            new Vector3(6, 0, 16), new Vector3(-4, 0, 7),
            new Vector3(0, 0, 7), new Vector3(6, 0, 7)
        };

    public static PatrolFactory getInstance()
    {
        if (instance == null)
            instance = new PatrolFactory();
        return instance;
    }

    public void initItem(GameObject _PatrolItem)
    {
        PatrolItem = _PatrolItem;
    }

    public GameObject getPatrol()
    {
        GameObject newPatrol = Camera.Instantiate(PatrolItem);
        return newPatrol;
    }

    public Vector3[] getPosition()
    {
        return PatrolPosition;
    }
}

使用者控制英雄的遊戲介面

public class UserInterface : MonoBehaviour
{
    private IUserAction action;

    void Start()
    {
        action = SceneController.getInstance() as IUserAction;
    }

    void Update()
    {
        detectKeyInput();
    }

    void detectKeyInput()
    {
        if (Input.GetKey(KeyCode.UpArrow))
        {
            action.moveAction(Diretion.UP);
        }
        if (Input.GetKey(KeyCode.DownArrow))
        {
            action.moveAction(Diretion.DOWN);
        }
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            action.moveAction(Diretion.LEFT);
        }
        if (Input.GetKey(KeyCode.RightArrow))
        {
            action.moveAction(Diretion.RIGHT);
        }
    }
}

巡邏兵的行為程式碼,主要是懸掛在巡邏兵上面的

public class PatrolBehaviour : MonoBehaviour
{
    private IAddAction action;
    private IGameState states;

    public int IndexOfPatrol;
    private bool isCatching;

    void Start()
    {
        action = SceneController.getInstance() as IAddAction;
        states = SceneController.getInstance() as IGameState;

        IndexOfPatrol = this.gameObject.name[this.gameObject.name.Length - 1] - '0';
        isCatching = false;
    }

    void Update()
    {
        if (states.getIndexOfHero() == IndexOfPatrol)
        {
            if (!isCatching)
            {
                changeState();
                action.addDirectMovement(this.gameObject);
            }
        }
        else
        {
            if (isCatching)
            {
                states.getScore();
                changeState();
                action.addRandomMovement(this.gameObject, false);
            }
        }

    }

    void OnCollisionStay(Collision e)
    {
        if (e.gameObject.name.Contains("Patrol") || e.gameObject.name.Contains("fence")
            || e.gameObject.tag.Contains("FenceAround"))
        {
            isCatching = false;
            action.addRandomMovement(this.gameObject, false);
        }
        
        if (e.gameObject.name.Contains("Hero"))
        {
            //e.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Blue");
            states.gameOver();
        }
    }

    public void changeState()
    {
        if (isCatching)
        {
            isCatching = false;
        } else
        {
            isCatching = true;
        }
    }
}

場景控制器的程式碼:

public class SceneController : System.Object, IUserAction, IAddAction, IGameState
{
    private static SceneController instance;
    public FirstSceneController scene;
    public GameEventManager gameEventManager;

    public static SceneController getInstance()
    {
        if (instance == null)
        {
            instance = new SceneController();
        }
        return instance;
    }

    /*-------------------------IUserAction-----------------------*/
    public void moveAction(int dir)
    {
        scene.moveAction(dir);
    }
    /*-------------------------IUserAction-----------------------*/




    /*---------------------------IAddAction-----------------------*/
    public void addRandomMovement(GameObject sourceObj, bool isActive)
    {
        scene.addRandomMovement(sourceObj, isActive);
    }

    public void addDirectMovement(GameObject sourceObj)
    {
        scene.addDirectMovement(sourceObj);
    }
    /*---------------------------IAddAction-----------------------*/




    /*---------------------------IGameState-----------------------*/
    public int getIndexOfHero()
    {
        return scene.getIndexOfHero();
    }

    public void getScore()
    {
        gameEventManager.getScore();
    }

    public void gameOver()
    {
        gameEventManager.gameOver();
    }

    public bool getGameState()
    {
        return gameEventManager.getGameState();
    }
    /*---------------------------IGameState-----------------------*/
}

主要場景,在裡面實現了巡邏兵的兩種動作,即巡邏動作和追捕動作,其中巡邏動作的速度在不同的層裡面是不一樣的

public class FirstSceneController : SSActionManager, ISSActionCallback
{
    public GameObject PatrolItem, HeroItem, sceneModelItem, canvasItem;
    private SceneController currentSceneController;
    private GameObject Hero, sceneModel, canvasAndText;
    private List<GameObject> Patrolmans;
    private List<int> PatrolLastDir;
    GUIStyle style;

    void Awake()
    {
        PatrolFactory.getInstance().initItem(PatrolItem);
    }

    protected new void Start()
    {
        currentSceneController = SceneController.getInstance();
        currentSceneController.scene = this;
        InitChcaracters();
        style = new GUIStyle();
        style.fontSize = 40;
        style.alignment = TextAnchor.MiddleCenter;
    }

    protected new void Update()
    {
        base.Update();
    }
    
    void OnGUI()
    {
        GUI.Label(new Rect(Screen.width / 2 + 500, Screen.height / 4, 180, 110), "Rule:\nRegion1: 1 point\nRegion2:  2 points\nRegion3: 3 points" +
            "\nRegion4: 4 points\nRegion5: 5 points\nRegion6: 6 points", style);
    }

    private void InitChcaracters()
    {
        //英雄
        Hero = Instantiate(HeroItem);

        //巡邏兵
        PatrolFactory factory = PatrolFactory.getInstance();
        Patrolmans = new List<GameObject>(6);
        PatrolLastDir = new List<int>(6);
        Vector3[] position = factory.getPosition();
        for (int i = 0; i < 6; i++)
        {
            GameObject newPatrol = factory.getPatrol();
            newPatrol.transform.position = position[i];
            newPatrol.name = "Patrol" + i;
            PatrolLastDir.Add(0);
            Patrolmans.Add(newPatrol);
            addRandomMovement(newPatrol, true);
        }

        //遊戲場景
        sceneModel = Instantiate(sceneModelItem);

        //記錄版
        canvasAndText = Instantiate(canvasItem);
    }

    //英雄移動的動作
    public void moveAction(int dir)
    {
        Hero.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
        switch (dir)
        {
            case Diretion.UP:
                Hero.transform.position += new Vector3(0, 0, 0.1f);
                break;
            case Diretion.DOWN:
                Hero.transform.position += new Vector3(0, 0, -0.1f);
                break;
            case Diretion.LEFT:
                Hero.transform.position += new Vector3(-0.1f, 0, 0);
                break;
            case Diretion.RIGHT:
                Hero.transform.position += new Vector3(0.1f, 0, 0);
                break;
        }
    }

    //動作回撥函式
    public void SSActionEvent(SSAction source, SSActionEventType eventType = SSActionEventType.Completed,
        SSActionTargetType intParam = SSActionTargetType.Patroling, string strParam = null, object objParam = null)
    {
        if (intParam == SSActionTargetType.Patroling)
            addRandomMovement(source.gameObject, true);
        else
            addDirectMovement(source.gameObject);
    }


    //判定巡邏兵走出了自己的區域
    bool PatrolOutOfArea(int index, int randomDir)
    {
        bool isPatrolOutOfArea = false;
        Vector3 patrolPos = Patrolmans[index].transform.position;
        float posX = patrolPos.x;
        float posZ = patrolPos.z;
        switch (index)
        {
            case 0:
                if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft
                    || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
                    isPatrolOutOfArea = true;
                break;
            case 1:
                if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight
                    || randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft
                    || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
                    isPatrolOutOfArea = true;
                break;
            case 2:
                if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight
                    || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
                    isPatrolOutOfArea = true;
                break;
            case 3:
                if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft
                    || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
                    isPatrolOutOfArea = true;
                break;
            case 4:
                if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight
                    || randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft
                    || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
                    isPatrolOutOfArea = true;
                break;
            case 5:
                if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight
                    || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
                    isPatrolOutOfArea = true;
                break;
        }
        return isPatrolOutOfArea;
    }
    
    public int getIndexOfHero()
    {
        return Hero.GetComponent<HeroState>().indexOfHero;
    }


    /*--------------------------------------------IAddAction----------------------------------------------*/
    //巡邏兵的追捕動作方法
    public void addDirectMovement(GameObject sourceObj)
    {
        int index = sourceObj.name[sourceObj.name.Length - 1] - '0';
        PatrolLastDir[index] = -2;

        sourceObj.transform.LookAt(sourceObj.transform);
        Vector3 oriTarget = Hero.transform.position - sourceObj.transform.position;
        Vector3 target = new Vector3(oriTarget.x / 4.0f, 0, oriTarget.z / 4.0f);
        target += sourceObj.transform.position;

        //新增動作引數,執行動作

        this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, 0.08f, true), this);
    }

    //巡邏兵執行巡邏的方法,獲得一個隨機的運動方向,isActive說明是否主動變向(動作結束)
    public void addRandomMovement(GameObject sourceObj, bool isActive)
    {
        int index = sourceObj.name[sourceObj.name.Length - 1] - '0';

        //給巡邏兵獲得一個隨機方向
        int randomDir = Random.Range(-1, 3);
        if (!isActive)
        {
            while (PatrolLastDir[index] == randomDir || PatrolOutOfArea(index, randomDir))
            {
                randomDir = Random.Range(-1, 3);
            }
        }
        else
        {
            while (PatrolLastDir[index] == 0 && randomDir == 2
                || PatrolLastDir[index] == 2 && randomDir == 0
                || PatrolLastDir[index] == 1 && randomDir == -1
                || PatrolLastDir[index] == -1 && randomDir == 1
                || PatrolOutOfArea(index, randomDir))
            {
                randomDir = Random.Range(-1, 3);
            }
        }


        PatrolLastDir[index] = randomDir;
        sourceObj.transform.rotation = Quaternion.Euler(new Vector3(0, randomDir * 90, 0));
        Vector3 target = sourceObj.transform.position;
        switch (randomDir)
        {
            case Diretion.UP:
                target += new Vector3(0, 0, 1);
                break;
            case Diretion.DOWN:
                target += new Vector3(0, 0, -1);
                break;
            case Diretion.LEFT:
                target += new Vector3(-1, 0, 0);
                break;
            case Diretion.RIGHT:
                target += new Vector3(1, 0, 0);
                break;
        }

        //新增動作引數,執行動作
        //在不同的區域內的隨即運動速度是不一樣的
        float speed = 0;
        switch (index)
        {
            case 0:
                speed += 0.03f;
                break;
            case 1:
                speed += 0.04f;
                break;
            case 2:
                speed += 0.05f;
                break;
            case 3:
                speed += 0.06f;
                break;
            case 4:
                speed += 0.07f;
                break;
            case 5:
                speed += 0.08f;
                break;
        }
        this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, speed, false), this);
    }
    /*--------------------------------------------IAddAction----------------------------------------------*/
}

以上所展示的都只是部分程式碼而已,具體的程式碼可以轉到我的Github上面下載檢視,這次作業的難度還是非常大的,很多東西基本懵逼,而且還是對著師兄的程式碼來寫的,感覺自己在寫遊戲的過程中基本上在搬磚,做了非常長的時間,基本上也就看明白了程式碼吧,可能平時花在3d上面的時間還是比較少的,但是我自己儘量得學習一下游戲設計過程中的設計模式和一些UML圖的設計,總得來說,還是能夠有所收穫。

最後附上一張效果圖: