智能巡逻兵

智能巡逻兵

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

友善提示1:生成 3~5个边的凸多边型

  • 随机生成矩形
  • 在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
3. 消息订阅发布模式:

**Publisher(发布者)**为事件或消息的拥有者,**Subject(渠道)**为发布媒体,**Handle(接收器2)**为处理消息的方法,**Subscriber(订阅者)**为事件或消息感兴趣的人。

该模式的特点是发布者和订阅者没有直接的耦合,可以进行多对多通讯。

C# 对订阅/发布模式做了语言级别的实现,称为事件-代理机制(代理模型),发布者要点在于:

  • delegate 关键字定义了函数类型 AttackAction 的代理类型
  • 然后我们申明了 静态 变量 OnAttackAction ,即模型中的 Subject
  • 当 OnAttackAction 被调用时,会自动通知所有侦听者

订阅者要点在于:

  • 有 Teleport 这样的函数,它的函数签名与 delegate 的定义一样
  • 订阅就是加赋值
  • 取消订阅就是减赋值
4. UML图
5.实现过程
5.1 预期实现

本次实验会实现一个简单的巡逻者游戏,在一个场景中包含障碍物和几个巡逻者,巡逻者按照既定的巡逻路线行走,当闯入者靠近巡逻者到一定的范围内,巡逻者将开始追捕闯入者,我们需要通过WSAD四个键来控制任务向四个方向的移动,每避开一次巡逻者将得到一分,与巡逻者发生碰撞之后游戏结束。

5.2 发布者实现:碰撞事件,为巡逻者添加一个碰撞器,当碰到玩家时,发布一个消息。
public class PlayerCollide : MonoBehaviour
{
    void OnCollisionEnter(Collision other)
    {
        //当player与zombie相撞,在预制时要为palyer加一个Tag:Player
        if (other.gameObject.tag == "Player")
        {
            Singleton<GameEventManager>.Instance.Gameover();
        }
    }
}
5.3 发布者实现: 玩家进入巡逻者侦察范围内,开始追踪;玩家逃离巡逻者侦察范围,停止追踪,玩家得分加一。
//scripts/control/SSActionManager.cs
    public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
    {
        if (intParam == 0)
        {
            //侦查兵跟随玩家
            FollowAction follow = FollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            //侦察兵继续巡逻
            PatrolAction move = PatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().startPosition);
            this.RunAction(objectParam, move, this);
            //玩家逃脱侦察者追踪范围
            Singleton<GameEventManager>.Instance.Escape();
        }
    }
5.4 定义可订阅的主题 GameEventManer
//Asserts/Scripts/Control/Event/GameEventManger.cs

public class GameEventManager : MonoBehaviour{
    //分数变化
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    //游戏结束变化
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;
    //玩家逃脱
    public void Escape(){
        if (ScoreChange != null){
            ScoreChange();  //玩家逃脱,分数加一
        }
    }
    //玩家被捕
    public void Gameover(){
        if (GameoverChange != null){
            GameoverChange();   //玩家与巡逻者碰撞,游戏结束
        }
    }
}

5.5 在场景管理器 FirstController中实现订阅者,订阅上面实现的主题。
    //订阅者模式
    void OnEnable()
    {
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
    }

    void OnDisable()
    {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
    }

    private void AddScore()
    {
        scoreRecorder.Add();
    }

    private void Gameover()
    {
        this.gameState = GameState.OVER;
        player.GetComponent<Animator>().enabled = false;
        for (int i = 0; i < patrols.Count; i++)
        {
            patrols[i].GetComponent<Animator>().enabled = false;
        }
        actionManager.DestroyAll();
    }
5.6 动作管理器:父类SSAction与SSActionManger,主要实现了巡逻者实现与追踪的动作切换。
//SSAction中定义了动作的初始状态、一个回调函数和一个初始化函数Reset
    public bool enable = true;                      //是否正在进行此动作
    public bool destroy = false;                    //是否需要被销毁

    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //回调函数
//SSActionManger是动作管理的具体实现,实现了巡逻者巡逻与追踪
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 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(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                //运动学运动更新
                ac.Update();
            }
        }

        foreach (int key in waitingDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            Destroy(ac);
        }
        waitingDelete.Clear();
    }
//删除动作
    public void DestroyAll()
    {
        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            ac.destroy = true;
        }
    }
5.7 场景控制器调用动作管理器的接口 :PartActionManager
public class PatrolActionManager : SSActionManager
{
    //外界调用的动作
    private PatrolAction patrolAction;                            //巡逻兵巡逻
    //外界调用动作的接口
    public void Patrol(GameObject patrol)
    {
        patrolAction = PatrolAction.GetSSAction(patrol.transform.position);
        this.RunAction(patrol, patrolAction, this);
    }
    //判断游戏结束,停止所有动作
    public void DestroyAllAction()
    {
        DestroyAll();
    }
}
5.8 巡逻者的巡逻动作实现:PatrolAction
   //巡逻兵参数
    private enum Dirction { EAST, NORTH, WEST, SOUTH };
    private float posX, posZ;                 //初始x和z方向坐标
    private float moveLength;                  //移动的长度
    private float moveSpeed = 1.2f;            //移动速度
    private bool moveSign = true;              //是否到达目的地
    private Dirction dirction = Dirction.WEST;  //移动的方向
    private PatrolData data;                    //侦察兵的数据
 //定义巡逻动作,其它实现细节请看具体文件  
 private void Patrol()
    {
        if (moveSign)
        {
            //不需要转向则设定一个目的地,按照矩形移动
            switch (dirction)
            {
                case Dirction.EAST:
                    posX -= moveLength;
                    break;
                case Dirction.NORTH:
                    posZ += moveLength;
                    break;
                case Dirction.WEST:
                    posX += moveLength;
                    break;
                case Dirction.SOUTH:
                    posZ -= moveLength;
                    break;
            }
            moveSign = false;
        }
        this.transform.LookAt(new Vector3(posX, 0, posZ));
        float distance = Vector3.Distance(transform.position, new Vector3(posX, 0, posZ));

        //当前位置与目的地距离浮点数的比较决定是否转向
        if (distance > 0.9)
        {
            transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(posX, 0, posZ), moveSpeed * Time.deltaTime);
        }
        else
        {
            dirction = dirction + 1;
            if (dirction > Dirction.SOUTH)
            {
                dirction = Dirction.EAST;
            }
            moveSign = true;
        }
    }
5.9 巡逻者追踪动作:FollowAction
    //侦察时的动作
    private float speed = 2f;            //跟随玩家的速度
    private GameObject player;           //玩家
    private PatrolData data;             //侦查兵数据
    //如果侦察兵没有跟随对象,或者需要跟随的玩家不在侦查兵的区域内,销毁追踪动作,调用回调函数。
    if (data.wallSign != data.sign)
     {
         this.destroy = true;
         this.enable = false;
         this.callback.SSActionEvent(this, 1, this.gameobject);
     }
//追踪实现
void Follow(){
   this.transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
   this.transform.LookAt(player.transform.position);
}
5.10 巡逻者工厂模式:PatrolFactory实现巡逻兵的生产
//实现对象池:

private GameObject patrol = null;
//巡逻兵
private List<GameObject> used = new List<GameObject>();
//正在被使用的巡逻兵,该游戏巡逻兵不需要回收,所以不需要free表
private Vector3[] position = new Vector3[9];
//保存每个巡逻兵的初始位置
public FirstController sceneControler; 
//场景控制器
//巡逻兵设置
    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { 1, -4};
        int[] pos_z = { 4, -1};
        int index = 0;
        //生成不同的巡逻兵初始位置
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 2; j++)
            {
                position[index] = new Vector3(pos_x[i], 0, pos_z[j]);
                index++;
            }
        }
        for (int i = 0; i < 4; i++)
        {
            patrol = Instantiate(Resources.Load<GameObject>("Prefabs/zimbie"));
            patrol.transform.position = position[i];
            patrol.AddComponent<PatrolData>();
            patrol.GetComponent<PatrolData>().sign = i + 1;
            patrol.GetComponent<PatrolData>().startPosition = position[i];
            used.Add(patrol);
        }
        return used;
    }
5.11 UserGUI实现图形交互界面和人机互动操作(WSAD控制玩家)
   //实现WSAD移动
   void Update()
    {
        //获取方向键的偏移量
        float translationX = Input.GetAxis("Horizontal");
        float translationZ = Input.GetAxis("Vertical");
        //移动玩家
        if (action.GetGameState() == GameState.RUNNING)
            action.MovePlayer(translationX, translationZ);
    }
5.12 其它Singleton、ISceneController、ScoreRecoder、SSdirector、ISActionCallback的实现和上一次作业没有区别,可以直接使用。
6. 预制材料
  1. 设计一个Plane让玩家和巡逻者在上面运动:
    在这里插入图片描述

  2. 设计两个Animator分别为player与zimbie
    在这里插入图片描述

  3. 为player添加对应的animator、Avatar、Box Collider、Rigidbody

7. 效果展示:Patrol
8. 不足与改进:

场景中四个巡逻兵的动作相同,较为简单,player有时会跑出围栏外边。
可以考虑为player添加翻滚、跳跃的动作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值