07-模型与动画

本博客介绍了Unity中实现智能巡逻兵的项目,包括游戏设计和程序设计要求。巡逻兵能根据预设路径行走,遇到障碍自动转向,感知到玩家则进行追击。项目运用了订阅与发布模式,工厂模式以及动作队列来管理动画和行为。
摘要由CSDN通过智能技术生成

Homework07

项目地址
使用说明:创建一个空GameObject将GameModel.cs挂载到新建游戏对象即可

结果演示

在这里插入图片描述

智能巡逻兵

1. 游戏设计要求

  • 创建一个地图和若干巡逻兵(使用动画);
  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
  • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
  • 失去玩家目标后,继续巡逻;
  • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;

2. 程序设计要求

  • 必须使用订阅与发布模式传消息
    • subject:OnLostGoal
    • Publisher: ?
    • Subscriber: ?
  • 工厂模式生产巡逻兵

实现过程

制作预制

制作主角Hero、巡逻兵Patrol、地图SceneMode的预制如下图
在这里插入图片描述

主要代码实现如下
  1. GameModel.cs

    游戏逻辑主体,实现包括生产英雄、巡逻兵、移动、追捕等。在师兄博客的基础上做了改进,实现了hero碰撞栅栏和边界的检测,只能从栅栏之间的空隙穿过(原实现hero可任意穿过栅栏并在走出边界后会掉落)

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Com.Patrols;
    
    public class GameModel : SSActionManager, ISSActionCallback {
        public GameObject PatrolItem, HeroItem, sceneModelItem, canvasItem;
        private SceneController scene;
        private GameObject myHero, sceneModel, canvasAndText;
        private List<GameObject> PatrolSet;
        private List<int> PatrolLastDir;
        private Holes myHoles;
    
        // 巡逻兵正常状态的速度
        private const float PERSON_SPEED_NORMAL = 0.01f;
        // 巡逻兵追捕状态的速度
        private const float PERSON_SPEED_CATCHING = 0.01f;
    
        void Awake() {
            PatrolFactory.getInstance().initItem(PatrolItem);
            myHoles = new Holes();
            myHoles.leftUpHole = new Hole(-4.0f, -1.0f, 16.0f, 17.0f);
            myHoles.leftMidHole = new Hole(-6.0f, -5.0f, 10.0f, 14.0f);
            myHoles.leftDownHole = new Hole(-4.0f, -1.0f, 6.5f, 7.5f);
            myHoles.MidHole = new Hole(0.0f, 1.5f, 10.0f, 14.0f);
            myHoles.rightUpHole = new Hole(2.0f, 4.0f, 17.0f, 18.0f);
            myHoles.rightMidHole = new Hole(4.5f, 5.5f, 10.0f, 14.0f);
            myHoles.rightDownHole = new Hole(2.0f, 4.0f, 8.0f, 9.0f);
        }
    
        protected new void Start () {
            scene = SceneController.getInstance();
            scene.setGameModel(this);
    
            // 生产英雄+巡逻兵
            genHero();
            genPatrols();
            sceneModel = Instantiate(sceneModelItem);
            canvasAndText = Instantiate(canvasItem);
        }
    
        protected new void Update() {
            base.Update();
        }
    
        
        void genHero() {
            myHero = Instantiate(HeroItem);
        }
    
        void genPatrols() {
            PatrolSet = new List<GameObject>(6);
            PatrolLastDir = new List<int>(6);
            Vector3[] posSet = PatrolFactory.getInstance().getPosSet();
            for (int i = 0; i < 6; i++) {
                GameObject newPatrol = PatrolFactory.getInstance().getPatrol();
                newPatrol.transform.position = posSet[i];
                newPatrol.name = "Patrol" + i;
                PatrolLastDir.Add(-2);
                PatrolSet.Add(newPatrol);
                addRandomMovement(newPatrol, true);
            }
        }
    
        // hero移动
        public void heroMove(int dir) {
            myHero.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
            int index = getHeroStandOnArea();
    
            Vector3 herolPos = myHero.transform.position;
            float posX = herolPos.x;
            float posZ = herolPos.z;
            switch (dir) {
                case Diretion.UP:
                    heroMoveUp(index, posX, posZ);
                    // myHero.transform.position += new Vector3(0, 0, 0.1f);
                    break;
                case Diretion.DOWN:
                    heroMoveDown(index, posX, posZ);
                    // myHero.transform.position += new Vector3(0, 0, -0.1f);
                    break;
                case Diretion.LEFT:
                    heroMoveLeft(index, posX, posZ);
                    // myHero.transform.position += new Vector3(-0.1f, 0, 0);
                    break;
                case Diretion.RIGHT:
                    heroMoveRight(index, posX, posZ);
                    // myHero.transform.position += new Vector3(0.1f, 0, 0);
                    break;
            }
        }
    
        private void heroMoveUp(int index, float posX, float posZ) {
            switch (index) {
                case 0:
                case 1:
                case 2:
                    if (posZ + 1 < FenchLocation.FenchTop)
                        myHero.transform.position += new Vector3(0, 0, 0.1f);
                    break;
                case 3:
                    if (posZ + 1 < FenchLocation.FenchHori) {
                        myHero.transform.position += new Vector3(0, 0, 0.1f);
                    } else if(myHoles.leftMidHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(0, 0, 1.0f);
                    }
                    break;
                case 4:
                    if (posZ + 1 < FenchLocation.FenchHori) {
                        myHero.transform.position += new Vector3(0, 0, 0.1f);
                    } else if(myHoles.MidHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(0, 0, 1.0f);
                    }
                    break;
                case 5:
                    if (posZ + 1 < FenchLocation.FenchHori) {
                        myHero.transform.position += new Vector3(0, 0, 0.1f);
                    } else if(myHoles.rightMidHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(0, 0, 1.0f);
                    }
                    break;
            }
        }
    
        private void heroMoveDown(int index, float posX, float posZ) {
            switch (index) {
                case 0:
                    if (posZ - 1 > FenchLocation.FenchHori) {
                        myHero.transform.position += new Vector3(0, 0, -0.1f);
                    } else if(myHoles.leftMidHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(0, 0, -1.0f);
                    }
                    break;
                case 1:
                    if (posZ - 1 > FenchLocation.FenchHori) {
                        myHero.transform.position += new Vector3(0, 0, -0.1f);
                    } else if(myHoles.MidHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(0, 0, -1.0f);
                    }
                    break;
                case 2:
                    if (posZ - 1 > FenchLocation.FenchHori) {
                        myHero.transform.position += new Vector3(0, 0, -0.1f);
                    } else if(myHoles.rightMidHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(0, 0, -1.0f);
                    }
                    break;
                case 3:
                case 4:
                case 5:
                    if (posZ - 1 > FenchLocation.FenchBottom)
                        myHero.transform.position += new Vector3(0, 0, -0.1f);
                    break;
            }
        }
    
        private void heroMoveLeft(int index, float posX, float posZ) {
            switch (index) {
                case 0:
                case 3:
                    if (posX - 1 > FenchLocation.FenchLeft)
                        myHero.transform.position += new Vector3(-0.1f, 0, 0);
                    break;
                case 1:
                    if (posX - 1 > FenchLocation.FenchVertLeft) {
                        myHero.transform.position += new Vector3(-0.1f, 0, 0);
                    } else if(myHoles.leftUpHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(-1.0f, 0, 0);
                    }
                    break;
                case 4:
                    if (posX - 1 > FenchLocation.FenchVertLeft) {
                        myHero.transform.position += new Vector3(-0.1f, 0, 0);
                    } else if(myHoles.leftDownHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(-1.0f, 0, 0);
                    }
                    break;
                case 2:
                    if (posX - 1 > FenchLocation.FenchVertRight) {
                        myHero.transform.position += new Vector3(-0.1f, 0, 0);
                    } else if(myHoles.rightUpHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(-1.0f, 0, 0);
                    }
                    break;
                case 5:
                    if (posX - 1 > FenchLocation.FenchVertRight) {
                        myHero.transform.position += new Vector3(-0.1f, 0, 0);
                    } else if(myHoles.rightDownHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(-1.0f, 0, 0);
                    }
                    break;
            }
        }
    
        private void heroMoveRight(int index, float posX, float posZ) {
            switch (index) {
                case 0:
                    if (posX + 1 < FenchLocation.FenchVertLeft) {
                        myHero.transform.position += new Vector3(0.1f, 0, 0);
                    } else if(myHoles.leftUpHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(1.0f, 0, 0);
                    }
                    break;
                case 3:
                    if (posX + 1 < FenchLocation.FenchVertLeft) {
                        myHero.transform.position += new Vector3(0.1f, 0, 0);
                    } else if(myHoles.leftDownHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(1.0f, 0, 0);
                    }
                    break;
                case 1:
                    if (posX + 1 < FenchLocation.FenchVertRight) {
                        myHero.transform.position += new Vector3(0.1f, 0, 0);
                    } else if(myHoles.rightUpHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(1.0f, 0, 0);
                    }
                    break;
                case 4:
                    if (posX + 1 < FenchLocation.FenchVertRight) {
                        myHero.transform.position += new Vector3(0.1f, 0, 0);
                    } else if(myHoles.rightDownHole.isInHole(posX, posZ)) {
                        myHero.transform.position += new Vector3(1.0f, 0, 0);
                    }
                    break;
                case 2:
                case 5:
                    if (posX + 1 < FenchLocation.FenchRight)
                        myHero.transform.position += new Vector3(0.1f, 0, 0);
                    break;
            }
        }
    
        // 动作结束后
        public void SSActionEvent(SSAction source, SSActionEventType eventType = SSActionEventType.Completed, 
            SSActionTargetType intParam = SSActionTargetType.Normal, string strParam = null, object objParam = null) {
            if (intParam == SSActionTargetType.Normal) {
                addRandomMovement(source.gameObject, true);
            } else {
                addDirectMovement(source.gameObject);
            }
        }
    
        // isActive说明是否主动变向(动作结束)
        public void addRandomMovement(GameObject sourceObj, bool isActive) {
            int index = getIndexOfObj(sourceObj);
            int randomDir = getRandomDirection(index, isActive);
            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;
            }
            addSingleMoving(sourceObj, target, PERSON_SPEED_NORMAL, false);
        }
        int getIndexOfObj(GameObject sourceObj) {
            string name = sourceObj.name;
            char cindex = name[name.Length - 1];
            int result = cindex - '0';
            return result;
        }
        int getRandomDirection(int index, bool isActive) {
            // -1左  0上  1右  2下
            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);
                }
            }
            return randomDir;
        }
        // 判断巡逻兵是否越界
        bool PatrolOutOfArea(int index, int randomDir) {
            Vector3 patrolPos = PatrolSet[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)
                        return true;
                    break;
                case 1:
                    if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight || randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
                        return true;
                    break;
                case 2:
                    if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
                        return true;
                    break;
                case 3:
                    if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
                        return true;
                    break;
                case 4:
                    if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight || randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
                        return true;
                    break;
                case 5:
                    if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
                        return true;
                    break;
            }
            return false;
        }
    
        // 巡逻兵追捕hero
        public void addDirectMovement(GameObject sourceObj) {
            int index = getIndexOfObj(sourceObj);
            PatrolLastDir[index] = -2;
    
            sourceObj.transform.LookAt(sourceObj.transform);
            Vector3 oriTarget = myHero.transform.position - sourceObj.transform.position;
            Vector3 target = new Vector3(oriTarget.x / 4.0f, 0, oriTarget.z / 4.0f);
            target += sourceObj.transform.position;
            addSingleMoving(sourceObj, target, PERSON_SPEED_CATCHING, true);
        }
    
        void addSingleMoving(GameObject sourceObj, Vector3 target, float speed, bool isCatching) {
            this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, speed, isCatching), this);
        }
    
        void addCombinedMoving(GameObject sourceObj, Vector3[] target, float[] speed, bool isCatching) {
            List<SSAction> acList = new List<SSAction>();
            for (int i = 0; i < target.Length; i++) {
                acList.Add(CCMoveToAction.CreateSSAction(target[i], speed[i], isCatching));
            }
            CCSequeneActions MoveSeq = CCSequeneActions.CreateSSAction(acList);
            this.runAction(sourceObj, MoveSeq, this);
        }
    
        // 获取hero所在区域编号
        public int getHeroStandOnArea() {
            return myHero.GetComponent<HeroStatus>().standOnArea;
        }
    }
    
    
  2. SSActionManager.cs

    用动作队列实现巡逻兵正常和追捕这两个动作的流畅切换和管理,包括碰撞的检测,正常巡逻时的转向等

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    
    //并发顺序
    public class SSActionManager : MonoBehaviour {
    
        private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
        private List<SSAction> waitingAdd = new List<SSAction>();
        private List<int> waitingDelete = new List<int>();
    
        protected void Start () {
    	
    	}
    
        protected void Update() {
            foreach (SSAction ac in waitingAdd) {
                actions[ac.GetInstanceID()] = ac;
            }
            waitingAdd.Clear();
    
            foreach (KeyValuePair<int, SSAction> kv in actions) {
                SSAction ac = kv.Value;
                if (ac.destroy) {
                    waitingDelete.Add(kv.Key);
                } else if (ac.enable) {
                    ac.Update();
                }
            }
    
            foreach (int key in waitingDelete) {
                SSAction ac = actions[key];
                actions.Remove(key);
                Object.Destroy(ac);
            }
            waitingDelete.Clear();
        }
    
        public void runAction(GameObject gameObj, SSAction action, ISSActionCallback manager) {
            // 先把该对象现有的动作销毁(与原来不同的动作类型)
            for (int i = 0; i < waitingAdd.Count; i++) {
                if (waitingAdd[i].gameObject.Equals(gameObj)) {
                    SSAction ac = waitingAdd[i];
                    waitingAdd.RemoveAt(i);
                    i--;
                    Object.Destroy(ac);
                }
            }
            
            foreach (KeyValuePair<int, SSAction> kv in actions) {
                SSAction ac = kv.Value;
                if (ac.gameObject.Equals(gameObj)) {
                    ac.destroy = true;
                }
            }
    
            action.gameObject = gameObj;
            action.transform = gameObj.transform;
            action.callBack = manager;
            waitingAdd.Add(action);
            action.Start();
        }
    }
    
    
  3. PatrolBehaviour.cs

    挂载到每个巡逻兵,实现巡逻兵对hero的感知和追踪,以及正常和追捕状态的转换。若处于追捕状态,而发现玩家已不在自己的区域,说明在刚一瞬间,玩家逃离了自己区域,此时会添加随机动作,即继续巡逻。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Com.Patrols;
    
    public class PatrolBehaviour : MonoBehaviour {
        private IAddAction addAction;
        private IGameStatusOp gameStatusOp;
    
        public int ownIndex;
        // 判断是否在追捕状态
        public bool isCatching;
    
        void Start () {
            addAction = SceneController.getInstance() as IAddAction;
            gameStatusOp = SceneController.getInstance() as IGameStatusOp;
    
            ownIndex = getOwnIndex();
            isCatching = false;
        }
    	
    	void Update () {
            checkNearByHero();
    	}
    
        int getOwnIndex() {
            string name = this.gameObject.name;
            char cindex = name[name.Length - 1];
            int result = cindex - '0';
            return result;
        }
    
        // 检测所在区域有无hero
        void checkNearByHero () {
            // hero在自己区域
            if (gameStatusOp.getHeroStandOnArea() == ownIndex) {    
                if (!isCatching) {
                    isCatching = true;
                    addAction.addDirectMovement(this.gameObject);
                }
            } else {
                if (isCatching) {
                    gameStatusOp.heroEscapeAndScore();
                    isCatching = false;
                    addAction.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;
                addAction.addRandomMovement(this.gameObject, false);
            }
    
            // 碰到hero,游戏结束
            if (e.gameObject.name.Contains("Hero")) {
                gameStatusOp.patrolHitHeroAndGameover();
                Debug.Log("Game Over!");
            }
        }
    }
    
    
  4. GameEventManager.cs

    采用订阅和发布模式,传递得分、游戏结束等消息,当玩家得分或游戏结束时,都会GameEventManager的得分/游戏结束方法

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Com.Patrols;
    
    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;
    
        void Start () {
            scene = SceneController.getInstance();
            scene.setGameEventManager(this);
        }
    	
    	void Update () {
    		
    	}
    
        // hero逃离巡逻兵追捕,得分+1
        public void heroEscapeAndScore() {
            if (myGameScoreAction != null) {
                myGameScoreAction();
            }
        }
    
        // hero被巡逻兵捕获,游戏结束
        public void patrolHitHeroAndGameover() {
            if (myGameOverAction != null) {
                myGameOverAction();
            }
        }
    }
    
    
  5. GameStatusText.cs

    传递消息的辅助函数

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class GameStatusText : MonoBehaviour {
        private int score = 0;
        // 0为score,1为gameover
        private int textType;
    
    	void Start () {
            distinguishText();
    	}
    	
    	void Update () {
    		
    	}
    
        void distinguishText() {
            if (gameObject.name.Contains("Score"))
                textType = 0;
            else
                textType = 1;
        }
    
        void OnEnable() {
            GameEventManager.myGameScoreAction += gameScore;
            GameEventManager.myGameOverAction += gameOver;
        }
    
        void OnDisable() {
            GameEventManager.myGameScoreAction -= gameScore;
            GameEventManager.myGameOverAction -= gameOver;
        }
    
        void gameScore() {
            if (textType == 0) {
                score++;
                this.gameObject.GetComponent<Text>().text = "Score: " + score;
            }
        } 
    
        void gameOver() {
            if (textType == 1)
                this.gameObject.GetComponent<Text>().text = "Game Over!";
        }
    }
    
    

参考文章

往届师兄博客

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值