unity 智能巡逻兵

该博客围绕Unity智能巡逻兵游戏展开,介绍了游戏设计与程序设计要求,如创建地图和巡逻兵、巡逻兵按多边形轨迹巡逻、使用订阅与发布模式等。还阐述了游戏实现过程,包括巡逻兵数据存储、动作控制、事件管理、碰撞检测等,最后给出项目的github地址。

unity 智能巡逻兵

在这里插入图片描述

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

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

二、游戏效果

在这里插入图片描述

视频演示

三、实现过程

本次项目参考自师兄的项目https://blog.csdn.net/C486C/article/details/80153548
并在此基础上增加了自己的设计。

整体的设计思路在师兄的博客中已经讲得很清楚了,我就挑部分比较重要以及我做了修改的代码进行阐述。

PatrolData

存储了巡逻兵的数据。

public class PatrolData : MonoBehaviour
{
    public int sign;                      //标志巡逻兵在哪一块区域
    public bool follow_player = false;    //是否跟随玩家
    public int wall_sign = -1;            //当前玩家所在区域标志
    public GameObject player;             //玩家游戏对象
    public Vector3 start_position;        //当前巡逻兵初始位置     
    public bool alive = true;
}

GoPatrolAction

和之前的代码结构一样,Action继承自SSAction。GoPatrolAction是控制士兵进行巡逻的动作。士兵默认按四角矩形的轨迹运动,如果跟随玩家的时机被触发,则销毁巡逻动作,增加跟随玩家动作。

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.5f;            //移动速度
    private bool move_sign = true;              //是否到达目的地
    private Dirction dirction = Dirction.EAST;  //移动的方向
    private PatrolData data;                    //侦察兵的数据
    

    private GoPatrolAction() { }
    public static GoPatrolAction GetSSAction(Vector3 location)
    {
        GoPatrolAction action = CreateInstance<GoPatrolAction>();
        action.pos_x = location.x;
        action.pos_z = location.z;
        //设定移动矩形的边长
        action.move_length = Random.Range(7, 10);
        return action;
    }
    public override void Update()
    {
        if (data.alive == false) {
            this.destroy = true;
            this.callback.SSActionEvent(this, 2, this.gameobject);
            return;
        }
        
        //侦察移动
        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()
    {
        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)
        {
            transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);
        }
        else
        {
            dirction = dirction + 1;
            if(dirction > Dirction.SOUTH)
            {
                dirction = Dirction.EAST;
            }
            move_sign = true;
        }
    }
}

PatrolFollowAction

控制士兵跟随玩家的动作,如果玩家脱离了追捕范围或者出了士兵的巡逻区域,则跟随动作结束并变回巡逻动作。

public class PatrolFollowAction : SSAction
{
    private float speed = 2.5f;            //跟随玩家的速度
    private GameObject player;           //玩家
    private PatrolData data;             //侦查兵数据

    private PatrolFollowAction() { }
    public static PatrolFollowAction GetSSAction(GameObject player)
    {
        PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
        action.player = player;
        return action;
    }

    public override void Update()
    {
        if (data.alive == false) {
            this.destroy = true;
            this.callback.SSActionEvent(this, 2, this.gameobject);
            return;
        }
        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);
    }
}

上述两个动作的切换是通过callBack函数实现的

public override 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 if (intParam == 1) 
    {
        //侦察兵按照初始位置开始继续巡逻
        GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
        this.RunAction(objectParam, move, this);
        //玩家逃脱
        Singleton<GameEventManager>.Instance.PlayerEscape();
    }
    else {
        
    }
}
GameEventManager

与事件的发布相关的管理器。

public class GameEventManager : MonoBehaviour
{
    //分数变化
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    //游戏结束变化
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;
    //水晶数量变化
    public delegate void CrystalEvent();
    public static event CrystalEvent CrystalChange;
    //玩家射击
    public delegate void Shoot();
    public static event Shoot ShootChange;
    //玩家逃脱
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //玩家被捕
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
    //减少水晶数量
    public void ReduceCrystalNum()
    {
        if (CrystalChange != null)
        {
            CrystalChange();
        }
    }

    public void shoot() {
        if (ShootChange != null) {
            ShootChange();
        }
    }
}

当某一个事件发生时,通过调用GameEventManager中的某一个函数来发布这个事件,订阅这个事件的其他地方将会接收到此次事件,并作出相应操作。
比如控制器检测到鼠标点击时通过GameEventManage的shoot发布射击事件,订阅这个事件的音频管理器将会播放一段射击的音频。
可以通过以下方法订阅:

public AudioClip shootClip;
void OnEnable()
{
    GameEventManager.ShootChange += Shoot;
}
void OnDisable()
{
    GameEventManager.ShootChange -= Shoot;
}

void Shoot() {
    FirstSceneController scene = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    //在一个玩家的位置播放音乐
    AudioSource.PlayClipAtPoint(shootClip, scene.player.transform.position);
}
IUserAction

用户接口,定义了玩家操作

public interface IUserAction                          
{
    //移动玩家
    void MovePlayer(float translationX, float translationZ, Vector3 mousePosition);
    //得到分数
    int GetScore();
    //得到水晶数量
    int GetCrystalNumber();
    //得到游戏结束标志
    bool GetGameover();
    //重新开始
    void Restart();
    //射击
    void shoot();
}
碰撞检测

巡逻兵需要做两个碰撞检测:
与玩家直接接触;检测玩家进入巡逻范围。

由于有两个检测,所以需要设置两个碰撞体,一个放在巡逻兵对象上,另一个放在巡逻兵的子对象上,然后分别附加上不同的碰撞处理代码。
对于直接接触的碰撞,则游戏结束,如果是进入巡逻范围,则触发追赶玩家的条件。

工厂模式产生巡逻兵

以及生产了水晶。

private GameObject patrol = null;                              //巡逻兵
    private List<GameObject> used = new List<GameObject>();        //正在被使用的巡逻兵
    private GameObject crystal = null;                             //水晶
    private List<GameObject> usedcrystal = new List<GameObject>();      //正在被使用的水晶
    private float range = 18;                                      //水晶生成的坐标范围
    private Vector3[] vec = new Vector3[9];                        //保存每个巡逻兵的初始位置

    public FirstSceneController sceneControler;                    //场景控制器

    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -12, 4, 20 };
        int[] pos_z = { -20, -3, 12 };
        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 < 9; i++)
        {
            patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
            patrol.transform.position = vec[i];
            patrol.GetComponent<PatrolData>().sign = i + 1;
            patrol.GetComponent<PatrolData>().start_position = vec[i];
            used.Add(patrol);
        }   
        return used;
    }


    public List<GameObject> GetCrystal()
    {
        for(int i=0;i<12;i++)
        {
            crystal = Instantiate(Resources.Load<GameObject>("Prefabs/Crystal"));
            float ranx = Random.Range(-range, range);
            float ranz = Random.Range(-range, range);
            crystal.transform.position = new Vector3(ranx, 0, ranz);
            usedcrystal.Add(crystal);
        }

        return usedcrystal;
    }
   
玩家跟随鼠标转动、键盘移动
//玩家移动
    public void MovePlayer(float translationX, float translationZ, Vector3 mousePosition)
    {
        if(!game_over)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //Debug.Log(ray);
            RaycastHit hitInfo;
            if(Physics.Raycast(ray, out hitInfo)){
                Vector3 target = hitInfo.point;
                //Debug.Log(target);
                target.y = player.transform.position.y;
                player.transform.LookAt(target);
            }

            if (translationX != 0 || translationZ != 0)
            {
                player.GetComponent<Animator>().SetBool("run", true);
            }
            else
            {
                player.GetComponent<Animator>().SetBool("run", false);
            }
            //移动和旋转
            player.transform.Translate(translationX * player_speed * Time.deltaTime, 0, translationZ * player_speed * Time.deltaTime);
            
        }
点击鼠标射击
public void shoot() {
        player.GetComponent<Animator>().SetBool("run", false);
        player.GetComponent<Animator>().SetTrigger("shoot");
        Singleton<GameEventManager>.Instance.shoot();
        Ray ray = new Ray(player.transform.position, player.transform.forward);

        RaycastHit hit;
        if (Physics.Raycast(ray, out hit)) {
            GameObject obj = hit.transform.gameObject;
            if (obj.tag == "Patrol") {
                obj.GetComponent<PatrolData>().alive = false;
                obj.GetComponent<Animator>().SetBool("run", false);
                obj.GetComponent<Animator>().SetBool("dead", true);
            }
        }
    }
相机跟随玩家移动
public class CameraFlow : MonoBehaviour
{
    public GameObject follow;            //跟随的物体
    public float smoothing = 5f;          //相机跟随的速度
    Vector3 offset;                      //相机与物体相对偏移位置
    public Transform player;
    void Start()
    {
        offset = transform.position - follow.transform.position;
    }

    void FixedUpdate()
    {
        Vector3 target = follow.transform.position + offset;
        //摄像机自身位置到目标位置平滑过渡
        transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);
    }
}

项目地址

github地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值