Unity 3D游戏八:智能巡逻兵

前言

中山大学数据科学与计算机学院3D游戏课程学习记录博客。
游戏代码: gitee
游戏视频: bilibili
参考师兄的博客: 师兄博客

游戏要求

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

游戏分析

  • 订阅与发布模式:
    subject:OnLostGoal
    Publisher:GameEventManager
    Subscriber:FirstSceneController

发布者是消息或信息的拥有者,订阅者是请求信息的类。

  • 人物模型:
    人物模型处理见博客:人物模型

  • 代码结构:
    代码大致分为四个部分:玩家和巡逻兵的生成(这部分放到人物模型博客中)、玩家和巡逻兵追逐逻辑(包括动作管理)、游戏场景的逻辑(使用订阅与发布模式)、UI界面的生成(包括分数的计算)、。
    所以本文从这四个方面来设计代码。

游戏实现

一些文件在上次代码的基础上进行小幅度改动或者无改动,可以在本次作业中使用。比如计分器、单例模式、动作管理器等相关代码。

1.玩家和巡逻兵的生成:人物模型

2.玩家和巡逻兵追逐逻辑:
这部分代码需要完成动作管理和追逐逻辑两部分。
动作管理器需要控制巡逻兵的动作;
生成巡逻兵追逐玩家的动作;
生成玩家移动的动作;
生成巡逻兵继续巡逻的动作;
调用产生的动作。

  • 巡逻兵追逐玩家:PatrolFollowAction.cs
    追逐玩家时需要三个变量来记录玩家位置,巡逻兵速度和巡逻兵数据;
    追逐玩家时需要实现Start和Update函数;
    该类继承自SSAction.
public class PatrolFollowAction : SSAction
{
    private float speed = 2f;            //跟随玩家的速度
    private GameObject player;           //玩家
    private PatrolData data;             //侦查兵数据
    //更新
    public override void Update()
    {         
        Follow();
        //如果玩家跑出区域
        //取消追逐
        if (!data.follow_player || data.wall_sign != data.sign)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this,1,this.gameobject);
        }
    }
    //初始化
    public override void Start()
    {
        data = this.gameobject.GetComponent<PatrolData>();
    }
	
	//追逐玩家
    void Follow()
    {
        transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
        this.transform.LookAt(player.transform.position);
    }
}
  • 巡逻兵继续巡逻:GoPatrolAction.cs
    巡逻兵巡逻动作让巡逻兵按照特点的轨迹运动巡逻;
    需要变量记录巡逻兵的移动速度、移动距离、具体数据等;
    需要实现Start和Update函数;
    需要在Gopatrol函数中为巡逻兵规划路线。
public class GoPatrolAction : SSAction
{
    private enum Dirction { EAST, NORTH, WEST, SOUTH };
    private float pos_x, pos_z;                 //移动前的初始x和z方向坐标
    private float move_length;                  //移动的长度
    private float move_speed = 1.2f;            //移动速度
    private bool move_sign = true;              //是否到达目的地
    private Dirction dirction = Dirction.EAST;  //移动的方向
    private PatrolData data;                    //侦察兵的数据
    
    public override void Update()
    {
        //侦察移动
        Gopatrol();
        //玩家进入巡逻兵区域,追逐玩家
        if (data.follow_player && data.wall_sign == data.sign)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this,0,this.gameobject);
        }
    }
    //初始化
    public override void Start()
    {
        this.gameobject.GetComponent<Animator>().SetBool("run", true);
        data  = this.gameobject.GetComponent<PatrolData>();
    }

    void Gopatrol()
    {
        //根据当前的移动方向以及是否到达终点
        //如果到达终点,选定下一个移动方向
        //如果没到达终点,继续前进
    }
}
  • 调用巡逻兵动作:PatrolActionManager.cs/SSActionManager.cs
    SSActionManager.cs重新实现了SSActionEvent函数,当intParam为0时巡逻兵追逐玩家,当intParam为1时巡逻兵继续巡逻。
public class SSActionManager : MonoBehaviour, ISSActionCallback
{
    public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
    {
        if(intParam == 0)
        {
            //巡逻兵跟随玩家
            PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            //巡逻兵继续巡逻
            GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
            this.RunAction(objectParam, move, this);
            //玩家离开
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }
}

PatrolActionManager调用SSActionManager(父类)的SSActionEvent函数实现巡逻兵运动的控制。

  • 区域判断:AreaCollide.cs
    区域判断是通过每个区域特定的标识来实现,通过对比区域的标识可以知道玩家所处的区域是否和巡逻兵相同。
public class AreaCollide : MonoBehaviour
{
    public int sign = 0;
    FirstSceneController sceneController;
    private void Start()
    {
        sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    }
    void OnTriggerEnter(Collider collider)
    {
        //玩家进入区域
        if (collider.gameObject.tag == "Player")
        {
            sceneController.wall_sign = sign;
        }
    }
}

3.游戏场景的逻辑:
游戏场景的逻辑使用了订阅与发布模式;
模式的优点在于降低代码的耦合度;
订阅者没必要知道和功能调用有关的类,只需要知道发布者即可;
而发布者调用对应的方法来进行实际操作。

GameEventManager作为发布者,FirstSceneController作为订阅者。

  • 发布者:GameEvenManager.cs
    该类专门发布事件,订阅者可以订阅相应的事件,然后由该类去施行相应的动作。
    本游戏中发布者主要负责三个动作,分数变化、游戏结束、水晶数目。
public class GameEventManager : MonoBehaviour
{
    //分数变化
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //游戏结束
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
    //水晶数量
    public void ReduceCrystalNum()
    {
        if (CrystalChange != null)
        {
            CrystalChange();
        }
    }
}

  • 订阅者:FirstSceneController.cs
    该类向发布者请求事件。
    FirstSceneController包含一些必要的变量记录游戏信息;
    FirstSceneController需要实现载入资源函数;
    FirstSceneController需要实现玩家移动函数;
    FirstSceneController需要调用发布者提供的函数;
    FirstSceneController需要控制游戏的结束。
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
    public PropFactory patrol_factory; //巡逻兵工厂
    public ScoreRecorder recorder; //计分器
    public PatrolActionManager action_manager; //运动管理器
    public int wall_sign = -1; //当前玩家所处哪个格子
    public GameObject player; //玩家
    public Camera main_camera; //主相机
    public float player_speed = 5; //玩家移动速度
    public float rotate_speed = 135f; //玩家旋转速度
    private List<GameObject> patrols; //场景中巡逻者列表
    private List<GameObject> crystals; //场景水晶列表
    private bool game_over = false; //游戏结束标志
    
    //资源载入函数,利用预设
    public void LoadResources()
    {
        Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
        crystals = patrol_factory.GetCrystal();
        patrols = patrol_factory.GetPatrols();
        //巡逻兵巡逻
        for (int i = 0; i < patrols.Count; i++)
        {
            action_manager.GoPatrol(patrols[i]);
        }
    }
    
    //玩家移动
    public void MovePlayer(float translationX, float translationZ)
    {
        //如果游戏没有结束,进入玩家移动功能
        //玩家使用动画进行移动和旋转
    }

	//控制游戏结束
    void Gameover()
    {
        game_over = true;
        patrol_factory.StopPatrol();
        action_manager.DestroyAllAction();
    }
}

4.UI界面的生成
UI界面的生成要使用UserGUI.cs来构造UI界面,就是一些画标签画按钮的工作。和上次作业唯一的区别就是改了改文字,所以不再赘述。

详细代码见gitee主页。

总结

这次作业主要学习了设计方式中的发布者订阅者模式,降低耦合度。
另一方面学习了人物模型和动画的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值