Game-unity hw7

智能巡逻兵

作业要求

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

游戏设计

操作方法

玩家控制的角色为一只僵尸,通过按键W、S、A、D控制角色的移动并,躲避巡逻兵的巡逻并夺取金币!

游戏机制

玩家在90秒内收集到5个金币则完成任务,若期间被巡逻兵触碰或超时,则任务失败。地图上一共有六个巡逻兵,每个巡逻兵在一个特点房间内巡逻,若玩家进入房间,巡逻兵则会跟踪玩家,直到玩家离开特定范围。

游戏实现

预设

游戏中的游戏对象都必须通过预设生成。

player

玩家控制的角色预设为playr,其拥有的组件有:Animator、Rigidbody和BoxCollider。
在这里插入图片描述

patrol

巡逻兵的预设有三种,只有模型上的区别,都拥有组件Animator、Rigidbody和BoxCollider。
在这里插入图片描述

maze

maze是游戏的场景,其包含若干个Floor和Wall预制,其拥有的组件有BoxCollider和Rigidbody。
在这里插入图片描述

Coin

金币是一个扁平圆柱体加上特制的Material组成的预设,其包含的组件有:Animator和BoxCollider。
在这里插入图片描述

动画

玩家角色动画

玩家控制的角色一开始就保持奔跑的动画,如果碰到巡逻兵则转为死亡动画,如果游戏重新开始则回到奔跑动画,对应的Animator Controller设计如下:
在这里插入图片描述

巡逻兵动画

巡逻兵和玩家角色类似,对应的Animator Controller设计如下:
在这里插入图片描述

代码

巡逻兵
PatrolData.cs

该脚本声明了每个巡逻兵的初始位置、管理的房间,同时实时记录玩家所在的房间以判断是否追踪玩家。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PatrolData : MonoBehaviour
{
    public int manageFloor;       // 管理的房间
    public int plyerFloor;        // 玩家当前所在房间
    public Vector3 initPosition;  // 初始位置
}

PatrolFactory.cs

利用工厂模式生产巡逻兵:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PatrolFactory : MonoBehaviour
{
    private GameObject patrolPrefab = null;
    private Vector3[] position = new Vector3[6]; //记录巡逻兵的位置
    private List<GameObject> patrols = new List<GameObject>();

    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -10, 3, 18, -11, 3, 15 };
        int[] pos_z = { -18, -15, -15, 12, 10, 13 };

        for(int i = 0; i < 6; i++)
        {
            position[i] = new Vector3(pos_x[i], 0, pos_z[i]);
            if(i <2){
                patrolPrefab = Object.Instantiate(Resources.Load<GameObject>("Prefabs/patrol1"), position[i], Quaternion.identity);
            }
            else if(i < 4){
                patrolPrefab = Object.Instantiate(Resources.Load<GameObject>("Prefabs/patrol2"), position[i], Quaternion.identity);
            }
            else{
                patrolPrefab = Object.Instantiate(Resources.Load<GameObject>("Prefabs/patrol3"), position[i], Quaternion.identity);
            }
            patrolPrefab.name = "patrol" + i;
            patrolPrefab.AddComponent<PatrolData>();

            patrolPrefab.GetComponent<PatrolData>().manageFloor = i + 1;
            patrolPrefab.GetComponent<PatrolData>().initPosition = position[i];
            patrols.Add(patrolPrefab);
        }
        return patrols;
    }

    public void Reset()
    {
        for(int i = 0; i < patrols.Count; i++)
        {
            patrols[i].transform.position = position[i];
            patrols[i].GetComponent<Animator>().SetBool("shoot", false);
        }
    }

}
PatrolMoveAction.cs

每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算。该脚本则是实现了巡逻兵在不追踪玩家时的移动动作,通过生成一个5到8的随机数确定往某一方向移动的距离:

    public void Move()
    {
        if (reach)
        {
            // 如果已到达,就换方向
            switch (dirction)
            {
                case Dirction.EAST:
                    posX -= rectLength;
                    break;
                case Dirction.NORTH:
                    posZ += rectLength;
                    break;
                case Dirction.WEST:
                    posX += rectLength;
                    break;
                case Dirction.SOUTH:
                    posZ -= rectLength;
                    break;
            }
            reach = false;
        }

        //面朝目的地
        this.transform.LookAt(new Vector3(posX, 0.5f, posZ));

        //计算距离
        float distance = Vector3.Distance(transform.position, new Vector3(posX, 0.5f, posZ));
        if (distance > 1)
        {
            transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(posX, 0.5f, posZ), speed * Time.deltaTime);
        }
        else
        {
            dirction = dirction + 1;
            if (dirction > Dirction.SOUTH)
            {
                dirction = Dirction.EAST;
            }
            reach = true;
        }
    }

同时巡逻兵碰撞到障碍物,则会自动选下一个点为目标:

		//防止碰撞发生后的旋转
        if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
        {
            transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
        }
        if (transform.position.y != 0.5f)
        {
            transform.position = new Vector3(transform.position.x, 0.5f, transform.position.z);
        }
        // 移动
        Move();

巡逻兵在设定范围内感知到玩家,会自动追击玩家

// 如果所在房间相同,摧毁当前动作并回调
        if (data.manageFloor == data.plyerFloor)
        {
            this.destory = true;
            this.callback.SSActionEvent(this, SSActionEventType.Compeleted, 0 ,"follow player", this.gameObject);
        }
PatrolFollowAction.cs

首先要实现巡逻兵追踪玩家角色的动作。

if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
        {
            transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
        }
        if (transform.position.y != 0.5f)
        {
            transform.position = new Vector3(transform.position.x, 0.5f, transform.position.z);
        }

        transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
        //追踪时面朝玩家
        this.transform.LookAt(player.transform.position);

失去玩家目标后,继续巡逻:

//丢失目标,停止追踪
        //如果侦察兵没有跟随对象,或者需要跟随的玩家不在侦查兵的区域内
        if (data.manageFloor != data.plyerFloor)
        {
            this.destory = true;
            this.callback.SSActionEvent(this, SSActionEventType.Compeleted, 1, "stop follow", this.gameObject);
        }
PatrolActionManager.cs

巡逻动作结束条件是需要追捕玩家,所以调用了回调函数,用回调函数来进行追捕动作。而当玩家离开追捕范围后,需要重新巡逻,也需要调用回调函数,从初始的位置和方向继续巡逻。

    public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Compeleted,
        int intParam = 0,
        string strParam = null,
        GameObject objectParam = null)
    {
        //回调函数,动作执行完后调用
        if (intParam == 0)
        {
            //开始跟随玩家
            PatrolFollowAction follow = PatrolFollowAction.GetSSAction(sceneController.player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            //丢失目标,继续巡逻
            PatrolMoveAction move = PatrolMoveAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().initPosition);
            this.RunAction(objectParam, move, this);
            //玩家逃脱
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }
PatrolCollide.cs

该脚本用于判断巡逻兵是否与玩家角色发生了碰撞,若是则游戏结束且任务失败,通知GameEventManager发送玩家被捕的事件信息。

using UnityEngine;
using System.Collections;

public class PatrolCollide : MonoBehaviour
{
    void OnCollisionStay(Collision other)
    {
        //当侦察兵与玩家相撞
        if (other.gameObject.name == "player")
        {
            other.gameObject.GetComponent<Animator>().SetBool("death", true);
            this.GetComponent<Animator>().SetBool("shoot", true);
            Singleton<GameEventManager>.Instance.PlayerArrested();
        }
    }
}
玩家角色
角色移动

在UserGUI.cs脚本中读取玩家输入的移动指令,再调用IUserAction.cs中的MovePlayer()函数移动角色。

        float transitionX = Input.GetAxis("Horizontal");
        float transitionZ = Input.GetAxis("Vertical");
        //移动玩家
        action.MovePlayer(transitionX, transitionZ);
    public void MovePlayer(float x, float z)
    {
        if (!gameOver)
        {
            //移动
            player.transform.Translate(new Vector3(x*13.5f , 0, z*13.5f)*Time.deltaTime,Space.World);
            //转向
            player.transform.LookAt(new Vector3(x*180,0,z*180));
            //防止碰撞带来的移动
            if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0)
            {
                player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);
            }
            if (player.transform.position.y != 0.5f)
            {
                player.transform.position = new Vector3(player.transform.position.x, 0.5f, player.transform.position.z);
            }
        }
    }
地图
AreaCollide.cs

因为游戏需要实时记录玩家所在的区域,所以当玩家进入该区域的时候,会设置场景控制器的区域标识为自己的标识,这样其他的巡逻兵就知道玩家现在在哪个区域了。

using UnityEngine;
using System.Collections;

public class AreaCollide : MonoBehaviour
{
    public int sign = 0;
    SceneController sceneController;
    private void Start()
    {
        sceneController = SSDirector.GetInstance().CurrentSceneController as SceneController;
    }
    void OnTriggerEnter(Collider collider)
    {
        //标记玩家进入自己的区域
        if (collider.gameObject.name == "player")
        {
            Debug.Log("player enter floor " + sign);
            sceneController.floorNumber = sign;
        }
    }
}
CoinCollider.cs

因为玩家角色需要收集金币,所以需要判断角色和金币的碰撞:

using UnityEngine;
using System.Collections;

public class CoinCollide : MonoBehaviour
{
    void OnTriggerEnter(Collider collider)
    {
        //玩家吃到金币事件触发
        if (collider.gameObject.name == "player")
        {
            this.gameObject.SetActive(false);
            Singleton<GameEventManager>.Instance.RecudeCoinNum();
        }
    }
}
订阅与发布模式部分
GameEventManager.cs(发布者)

设计一个类,用于发布事件如:玩家获得金币、玩家成功逃脱、玩家任务失败等。

using UnityEngine;
using System.Collections;

public class GameEventManager : MonoBehaviour
{
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;

    public delegate void GameOverEvent();
    public static event GameOverEvent GameOver;

    public delegate void CoinEvent();
    public static event CoinEvent CoinNumberChange;

    //玩家逃脱
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //玩家被捕
    public void PlayerArrested()
    {
        if (GameOver != null)
        {
            GameOver();
        }
    }

    public void RecudeCoinNum()
    {
        if(CoinNumberChange != null)
        {
            CoinNumberChange();
        }
    }

    public void TimeOut()
    {
        if (GameOver != null)
        {
            GameOver();
        }
    }
}

订阅者

在场景控制器中,一旦接收到特定的事件信息,则调用对应的函数对场景进行改变如分数增加、游戏结束:

    void OnEnable()
    {
        //注册事件
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameOver += GameOver;
        GameEventManager.CoinNumberChange += ReduceCoinNumber;
    }
    void OnDisable()
    {
        //取消注册事件
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameOver -= GameOver;
        GameEventManager.CoinNumberChange -= ReduceCoinNumber;
    }

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

    void GameOver()
    {
        gameOver = true;
        actionManager.DestroyAll();
    }

游戏效果

在这里插入图片描述

项目代码在我的仓库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值