简单巡逻兵

@[toc]

游戏规则与要求

  • 智能巡逻兵
    • 游戏设计要求:
      • 创建一个地图和若干巡逻兵(使用动画);
      • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
      • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
      • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
      • 失去玩家目标后,继续巡逻;
      • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
    • 程序设计要求:
      • 必须使用订阅与发布模式传消息
        • subject:OnLostGoal
        • Publisher: GameEventManager
        • Subscriber: 场景控制器
      • 工厂模式生产巡逻兵

项目地址与演示视频

项目地址 -> 传送门?

视频连接 -> 传送门?

Unity3D小技巧:

  • 窗口是可以锁定的:

    当你制作预制体需要把脚本拖到检查器中时,按一下窗口右上角的锁就可以锁定窗口,选中其他对象检查器也不会变,告别手残·~·

  • 去掉轮廓:

    有时布置场景时感觉对象图标和轮廓太碍事,去掉他们!

    Gizmos->调整图标大小,显示轮廓网格选择线。 在这里插入图片描述

具体实现

基本逻辑:玩家通过键盘控制人物在地图上移动,地图分为九个部分,有八个巡逻兵在出生点以外的部分巡逻,当玩家进入一个部分时该部分的巡逻兵就会追逐玩家,若玩家甩开巡逻兵则分数加一,若玩家与巡逻兵触碰则游戏结束。

由于本次项目代码量比较大,框架部分还是重用之前写的代码,在此只叙述重点部分。

本章标题是动画与模型,于是我将重点放在了人物模型与动画上。

我所使用的模型素材:?

人物模型部分:

由于这部分内容比较多放在了另一篇博客中:?

人物模型如下:

在这里插入图片描述

人物动画器如下,一个Layer五个动作:

在这里插入图片描述

巡逻兵部分:

巡逻兵与玩家使用同一个模型和动画器,不过通过代码控制其只有行走和奔跑两个动作,巡逻时行走,追逐玩家时奔跑。

在这里插入图片描述

巡逻兵的移动策略是参考C486C师兄(师姐?)博客的,不过师兄所有的动作都是使用运动学的函数MoveTowards来实现并且使用Update来更新,而巡逻兵是刚体且没有勾选运动学,我认为使用运动学的函数来移动刚体是不太好的,所以改为物理引擎的移动方式并且使用FixedUpdate来更新;另一点是师兄将本次巡逻兵的两个动作切换写在了动作管理器基类SSActionManager中,我认为应该在巡逻兵的动作管理器中GuardActionManager中实现该功能。

以下分析部分代码:

  • GuardPatrolAction:

    Update只更新平面移动的向量,FixedUpdate使用该向量进行移动,通过修改rigid的velocity来移动。

    /*变量*/
    private Rigidbody rigid;    //刚体组件
    private Vector3 planarVec;  // 平面移动向量
    
    /*更新函数*/
    public override void Update() {
        //保留供物理引擎调用
        planarVec = gameobject.transform.forward * data.walkSpeed;
    }
    
    public override void FixedUpdate() {
        //巡逻
        Gopatrol();
        //玩家进入该区域,巡逻结束,开始追逐
        if (data.playerSign == data.sign) {
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Competeted, 0, this.gameobject);
        }
    }
    
    /*in Gopatrol()*/
    rigid.velocity = new Vector3(planarVec.x, rigid.velocity.y, planarVec.z);
    
  • GuardFollowAction:

    与GuardPatrolAction相同,改为物理引擎的方式。

     public override void Update() {
         //保留供物理引擎调用
         planarVec = gameobject.transform.forward * speed;
     }
    
    public override void FixedUpdate() {
        transform.LookAt(player.transform.position);
        rigid.velocity = new Vector3(planarVec.x, rigid.velocity.y, planarVec.z);
    
        //如果玩家脱离该区域则继续巡逻
        if (data.playerSign != data.sign) {
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Competeted, 1, this.gameobject);
        }
    }
    
  • GuardActionManager:

    主要实现巡逻兵动作切换的功能,实现回调函数SSActionEvent,当一个动作被销毁时调用另一个动作。

    public void SSActionEvent(
        SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, GameObject objectParam = null) {
        if (intParam == 0) {
            //追逐
            GuardFollowAction follow = GuardFollowAction.GetSSAction(player);
            this.RunAction(objectParam, follow, this);
        } else {
            //巡逻
            GuardPatrolAction move = GuardPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<GuardData>().start_position);
            this.RunAction(objectParam, move, this);
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }
    

地图部分:

地图分为九个格子,每个区域有自己的碰撞器,并挂载AreaCollide.cs。

在这里插入图片描述

在这里插入图片描述

  • AreaCollide:

    当玩家进入时设置场景控制器中的玩家区域标志,然后场景控制器通知对应的巡逻兵追逐玩家。

public class AreaCollide : MonoBehaviour {
    public int sign = 0;
    private FirstSceneController sceneController;

    private void Start() {
        sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    }
	
    void OnTriggerEnter(Collider collider) {
        if (collider.gameObject.tag == "Player") {
            sceneController.playerSign = sign;
        }
    }
}

观察者模式:

  • GameEventManager发布消息:

    public class GameEventManager : MonoBehaviour {
        //定义回调函数类型和subject
        public delegate void ScoreEvent();
        public static event ScoreEvent ScoreChange;
    
        public delegate void GameoverEvent();
        public static event GameoverEvent GameoverChange;
    
        public void PlayerEscape() {
            if (ScoreChange != null) {
                ScoreChange();
            }
        }
    
        public void PlayerGameover(){
            if (GameoverChange != null) {
                GameoverChange();
            }
        }
    }
    
  • FirstSenceController类订阅消息:

    void OnEnable() {
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
    }
    void OnDisable() {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
    }
    
    void AddScore() {
        recorder.AddScore();
    }
    
    void Gameover() {
        game_over = true;
    }
    

总结

本次作业主要是了解了物理引擎在Unity3D中的使用,包括FixedUpdate函数,刚体组件,对游戏对象速度和力的操作,使用物理引擎就不需要模拟运动学的位置变换,使用复杂的公式计算。总的来说过程比较顺利,没有太大的阻碍。

参考资料

优秀博客 C486C