3D-07-巡逻兵

本文是3D游戏编程与设计第七次作业的博客,内容为编写游戏“巡逻兵(Patrol Man)”

任务说明

编写一个智能巡逻兵游戏

游戏内容要求

游戏设计要求:

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

程序设计要求:

  • 必须使用订阅与发布模式传消息
  • 工厂模式生产巡逻兵

成果说明

项目地址—Gitee

预制说明

迷宫采用 3*3 结构,由九个正方形组成,每个迷宫上挂载 AreaCollide.cs,当玩家进入后通知场记更新玩家位置信息
在这里插入图片描述
Guard 与 Player 采用了潘老师推荐的资源 YBoy
依照步骤配置完成后,我感觉原地跳与下落两个动作在这种追逐游戏中不太实用,所以对动作进行改进,即修改动画控制器 Player Controller 为(同时需要删除代码文件中的相关部分,主要为其消息发布函数)
在这里插入图片描述
同时在jump -> roll中设置exit time,缩短跳跃动作持续的时间,ground 动作混合树维持不变
在这里插入图片描述

此时以翻滚作为跳跃的着地动作,提高游戏中跳跃的可玩性

代码说明

注:框架部分的代码,如SSAction.cs、SSActionManager.cs、Interface.cs等此前已多次使用,其说明可以参考上次作业的博客,此处仅就与本次作业相关的部分进行说明

GuardData

保存了巡逻兵的基本数据

    public GameObject model;
    public float walkSpeed;		//巡逻速度
    public float runSpeed;		//追逐速度
    public int sign;                      //标志巡逻兵在哪一块区域
    public bool isFollow = false;         //是否跟随玩家
    public int playerSign = -1;           //当前玩家所在区域标志
    public Vector3 start_position;        //当前巡逻兵初始位置   

GuardFactory

巡逻兵工厂类,由位置对巡逻兵进行初始化并储存

    private GameObject guard = null;                               //巡逻兵
    private List<GameObject> used = new List<GameObject>();        //正在使用的巡逻兵列表
    private Vector3[] vec = new Vector3[9];                        //每个巡逻兵的初始位置

    public List<GameObject> GetPatrols() {
        int[] pos_x = { -6, 4, 13 };
        int[] pos_z = { -4, 6, -13 };
        int index = 0;
        for(int i=0;i < 3;i++) {
            for(int j=0;j < 3;j++) {
                vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);
                index++;
            }
        }
        for(int i = 0; i < 8; i++) {
            guard = Instantiate(Resources.Load<GameObject>("Prefabs/Guard"));
            guard.transform.position = vec[i];
            guard.GetComponent<GuardData>().sign = i + 1;
            guard.GetComponent<GuardData>().start_position = vec[i];
            guard.GetComponent<Animator>().SetFloat("forward", 1);
            used.Add(guard);
        }   
        return used;
    }

GuardPatrolAction&GuardFollowAction

巡逻兵的巡逻动作类与追逐动作类。当巡逻兵巡逻时,不断检测玩家是否进入自己所在迷宫,是则利用回调函数(在GuardActionManager中实现,第三个参数即为动作的标志位)开始追逐玩家;巡逻机制则为不断向矩形的顶点移动,每当到达时转向,以实现在矩形路线上的移动

/* public class GuardPatrolAction */
    public override void FixedUpdate() {
        //巡逻
        Gopatrol();
        //玩家进入该区域,巡逻结束,开始追逐
        if (data.playerSign == data.sign) {
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Competeted, 0, this.gameobject);
        }
    }

    void Gopatrol() {
        if (move_sign) {
            //不需要转向则设定一个目的地,按照矩形移动
            switch (dirction) {
                case Dirction.EAST:
                    pos_x -= move_length;
                    break;
                case Dirction.NORTH:
                    pos_z += move_length;
                    break;
                case Dirction.WEST:
                    pos_x += move_length;
                    break;
                case Dirction.SOUTH:
                    pos_z -= move_length;
                    break;
            }
            move_sign = false;
        }
        this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
        float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));

        if (distance > 0.9) {
            rigid.velocity = new Vector3(planarVec.x, rigid.velocity.y, planarVec.z);
        } else {
            dirction = dirction + 1;
            if(dirction > Dirction.SOUTH) {
                dirction = Dirction.EAST;
            }
            move_sign = true;
        }
    }

当巡逻兵追逐时,则不断向玩家方向移动,同时检测玩家是否已离开自己所在迷宫,是则回调巡逻动作

/* public class GuardFollowAction */
    public override void FixedUpdate() {
        transform.LookAt(player.transform.position);
        //rigid.velocity = new Vector3(planeVec.x, rigid.velocity.y, planeVec.z);
        rigid.velocity =  planeVec;
        //如果玩家脱离该区域则继续巡逻
        if (data.playerSign != data.sign) {
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Competeted, 1, this.gameobject);
        }
    }

ActorController

这是角色控制类,控制角色的动画播放与移动机制

    //刷新每秒60次
    void Update() {
        //修改动画混合树
        /*1.从走路到跑步没有过渡*/
        /*anim.SetFloat("forward", pi.Dmag * (pi.run ? 2.0f : 1.0f));*/
        /*2.使用Lerp加权平均解决*/
        float targetRunMulti = pi.run ? 2.0f : 1.0f;
        anim.SetFloat("forward", pi.Dmag * Mathf.Lerp(anim.GetFloat("forward"), targetRunMulti, 0.3f));
        //播放翻滚动画
        if (rigid.velocity.magnitude > 1.0f) {
            anim.SetTrigger("roll");
        }
        //播放跳跃动画
        if (pi.jump) {
            anim.SetTrigger("jump");
        }
        
        //转向
        if(pi.Dmag > 0.01f) {
            /*1.旋转太快没有补帧*/
            /*model.transform.forward = pi.Dvec;*/
            /*2.使用Slerp内插值解决*/
            Vector3 targetForward = Vector3.Slerp(model.transform.forward, pi.Dvec, 0.2f);
            model.transform.forward = targetForward;
        }
        if(!lockPlanar) {
            //保存供物理引擎使用
            planarVec = pi.Dmag * model.transform.forward * walkSpeed * (pi.run ? runMultiplier : 1.0f);
        }
        
    }

    //物理引擎每秒50次
    private void FixedUpdate() {
        //Time.fixedDeltaTime 50/s
        //1.修改位置
        //rigid.position += movingVec * Time.fixedDeltaTime;
        //2.修改速度
        rigid.velocity = new Vector3(planarVec.x, rigid.velocity.y, planarVec.z) + thrustVec;
        //一帧
        thrustVec = Vector3.zero;
    }

订阅与发布模式

GameEventManager 发布消息,其中 PlayerEscape 由巡逻兵由追逐回调巡逻动作时发出,PlayerGameover 由 PlayerCollide 检测到玩家与巡逻兵相撞时发出

public class GameEventManager : MonoBehaviour {
    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();
        }
    }
}

FirstSceneController 订阅消息,在消息发出后就会调用之前订阅了它的方法

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

改进说明

  1. 增加每逃脱一次巡逻兵速度增加一次的规则
    在 GameEventManager 中为 AddRunSpeed 方法订阅 PlayerEscape 消息,在工厂类 GuardFactory 中增加方法 patrolsFaster,因为 GuardFollowAction 中每次巡逻速度初始化依据 GuardData 的 runSpeed,所以可以实现每次逃脱后巡逻速度的增加
/* FirstSceneController */
    void AddRunSpeed(){
        guard_factory.patrolsFaster();
    }
    void OnEnable() {
        ...
        GameEventManager.ScoreChange += AddRunSpeed;
    }
    
/* GuardFactory */
    private List<GameObject> used = new List<GameObject>();        //正在使用的巡逻兵列表
    public void patrolsFaster(){
        foreach (GameObject guardi in used)
            {
                guardi.GetComponent<GuardData>().runSpeed += 2.0f;
            }
    }
  1. 增加上帝视角模式
    在场景中新建 Camera 命名为 HeadCamera,并调整位置使其可以俯视整个迷宫;在 CameraController 中增加标志位 bool isAfterPlayer,标志当前视角模式,同时存储新建的 HeadCamera,然后依据该标志位决定采用哪个 Camera 作为实际游戏视角
/* CameraController */
    private GameObject headCamera;
    void Awake() {
        ...
        headCamera = GameObject.Find("HeadCamera");
    }
    void FixedUpdate() {
        if(isAfterPlayer){
            headCamera.GetComponent<Camera>().enabled = false;
            camera.GetComponent<Camera>().enabled = true;
        } else {
            headCamera.GetComponent<Camera>().enabled = true;
            camera.GetComponent<Camera>().enabled = false;
        }
        ...
    }

而标志位 isAfterPlayer 将在“切换视角”按钮点击后由 UserGUI 通知场记 FirstSceneController,然后由其调用 CameraController 的接口进行改变,实现视角切换

成果展示

正常游戏
在这里插入图片描述

动作展示
在这里插入图片描述

视角切换
在这里插入图片描述
参考博客 学长/学姐Tifinity

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值