用Unity3D实现智能巡逻兵游戏

用Unity3D实现智能巡逻兵游戏

项目地址

智能巡逻兵游戏

完成效果图

请添加图片描述

类图

请添加图片描述

要求

  • 游戏设计要求:
    • 创建一个地图和若干巡逻兵(使用动画);
    • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
    • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
    • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
    • 失去玩家目标后,继续巡逻;
    • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
  • 程序设计要求:
    • 必须使用订阅与发布模式传消息
    • 工厂模式生产巡逻兵
  • 友善提示1:生成 3~5个边的凸多边型
    • 随机生成矩形
    • 在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
    • 5 ?
  • 友善提示2:参考以前博客,给出自己新玩法

实现心得

首先要设计出玩法规则,比如:

  • 按方向键进行移动
  • 每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束
  • 游戏中会随机出现10个宝箱,收集完所有的宝箱那么游戏获胜

然后创建游戏预制:

游戏对象预制可以从Unity3D的资源商店导入,地图和宝箱用到的资源是HighQualityBricks&WallsPup Up Productions,玩家对象用到的资源是RPGHero,巡逻兵用到的资源是ToonyTinyPeople,将导入资源的预制放到Resource/Prefabs目录下,接着为玩家对象和巡逻兵对象创建AnimatorController。

请添加图片描述

玩家对象的AnimatorController:

请添加图片描述

将玩家对象的普通状态动画放到Normal状态中,跑步动画放到Run状态中,Die动画放到Death状态中,在Normal状态到Run状态的箭头处设置bool类型run参数,为真则从Normal状态到Run状态,为假则从Run状态到Normal状态,在Any State状态到Death状态的箭头处设置trigger类型death参数。

巡逻兵对象的AnimatorController:

请添加图片描述

将巡逻兵对象的普通状态动画放到Normal状态中,跑步动画放到Run状态中,攻击动画放到Attack状态中,在Normal状态到Run状态的箭头处设置bool类型run参数,为真则从Normal状态到Run状态,为假则从Run状态到Normal状态,在Any State状态到Attack状态的箭头处设置trigger类型attack参数。

接着要设计单实例类:

因为要求场景单实例,所以可以导入一个Singleton单实例模板类:

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
	protected static T instance;

	public static T Instance {
		get {
			if (instance == null) {
				instance = (T)FindObjectOfType (typeof(T));
				if (instance == null) {
					Debug.LogError ("An instance of " + typeof(T) +
					" is needed in the scene, but there is none.");
				}
			}
			return instance;
		}
	}
}

或者使用导演类,使主控制器实现唯一:

public class Director : System.Object {
    private static Director instance;
    public MainController mainController { get; set; }
    public static Director GetInstance() {
        if (instance == null) {
            instance = new Director();
        }
        return instance;
    }
}

然后实现游戏对象创建部分,使用工厂模式创建巡逻兵和宝箱:

请添加图片描述

GameObjectFactory.cs:

游戏对象工厂类负责创建巡逻兵和宝箱,其在场景中是单实例的,且使用了对象池,实现了缓存功能,由于游戏中巡逻兵的数量不变,宝箱的数量也只会减少,所以回收方法中只有使巡逻兵停止移动的方法,实现如下:

public class GameObjectFactory : MonoBehaviour {
    private List<GameObject> usedPatrol = new List<GameObject>();  // 正在被使用的巡逻兵对象
    private List<GameObject> usedTreasure = new List<GameObject>();  // 正在被使用的宝箱对象

    // 巡逻兵获取方法
    public List<GameObject> GetPatrols() {
        // 巡逻兵游戏对象
        GameObject patrolPrefab = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"), Vector3.zero, Quaternion.identity);
        patrolPrefab.SetActive(false);
        float[] posX = { -5.5f, 4.5f, 12.5f };
        float[] posZ = { -4.5f, 5.5f, -12.5f };
        // 生成巡逻兵的初始位置
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                Vector3 startPos = new Vector3(posX[i], 0, posZ[j]);
                GameObject patrol = GameObject.Instantiate(patrolPrefab, startPos, Quaternion.identity);
                patrol.SetActive(true);
                patrol.GetComponent<PatrolData>().patrolAreaId = i * 3 + j + 1;
                patrol.GetComponent<PatrolData>().startPos = startPos;
                usedPatrol.Add(patrol);
            }
        }
        return usedPatrol;
    }

    // 巡逻兵回收方法
    public void FreePatrols() {
        // 巡逻兵停止
        for (int i = 0; i < usedPatrol.Count; ++i) {
            usedPatrol[i].gameObject.GetComponent<Animator>().SetBool("run", false);
        }
    }

    // 宝箱获取方法
    public List<GameObject> GetTreasures() {
        // 宝箱游戏对象
        GameObject treasurePrefab = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/Treasure"), Vector3.zero, Quaternion.identity);
        treasurePrefab.SetActive(false);
        for (int i = 0; i < Director.GetInstance().mainController.GetTreasureNumber(); ++i) {
            int xIndex = Random.Range(0, 24) - 12;
            int zIndex = Random.Range(0, 24) - 12;
            GameObject treasure = GameObject.Instantiate(treasurePrefab, new Vector3(xIndex, 0, zIndex), Quaternion.identity);
            treasure.SetActive(true);
            usedTreasure.Add(treasure);
        }
        return usedTreasure;
    }
}

PatrolData.cs:

PatrolData是巡逻兵的模型,包含了巡逻兵相应的数据:

public class PatrolData : MonoBehaviour {
    public int patrolAreaId;  // 巡逻兵所在区域序号
    public bool isTrackPlayer = false;  // 是否跟随玩家
    public GameObject trackPlayer;  // 追踪的玩家对象
    public Vector3 startPos;  // 巡逻兵初始位置     
}

这样就使用工厂方法 + 单实例 + 对象池完成了巡逻兵和宝箱的创建,实现要求。

接着实现记分管理类:

ScoreRecorder.cs:

记分类中有一个分数成员变量,一个获取方法和一个加分方法:

public class ScoreController : MonoBehaviour {
    private int score = 0;  // 分数

    public int GetScore() {
        return score;
    }

    public void IncreaseScore() {
        score++;
    }
}

然后实现游戏对象动作部分,利用回调方法,完成了订阅-发布模式传递消息:

请添加图片描述

SSAction.cs和SSActionManager.cs:

SSAction是动作基类,SSActionManager是动作管理者的基类,其实现分别为:

public class SSAction : ScriptableObject {
    public bool enable = true;  // 动作可进行
    public bool destroy = false;  // 动作已完成可被销毁
    public GameObject gameobject { get; set; }  // 附着游戏对象
    public Transform transform { get; set; }  // 游戏对象的的运动
    public ISSActionCallback callback { get; set; }  // 回调函数

    public virtual void Start() {}  // Start()重写方法
    public virtual void Update() {}  // Update()重写方法
}

public class SSActionManager : MonoBehaviour, ISSActionCallback {
    // 动作集
    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);
            Object.Destroy(ac);
        }
        // 清空等待删除队列
        waitingDelete.Clear();
    }

    // 初始化动作并加入到等待加入队列
    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
    {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }

    // 巡逻兵正常巡逻或追踪玩家行为结束后的回调方法
    public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competed,
        int intParam = 0,
        string strParam = null,
        GameObject objectParam = null)
    {
        // 如果消息的回调参数为1,追踪行为结束,开始正常巡逻
        if(intParam == 1) {
            // 继续巡逻
            PatrolNormalAction move = PatrolNormalAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().startPos);
            this.RunAction(objectParam, move, this);
            // 玩家逃脱消息
            Singleton<GameEventManager>.Instance.PlayerGetAway();
        }
        // 如果消息的回调参数为0,正常巡逻结束,开始追踪玩家
        else {
            // 追踪玩家
            PatrolTrackAction trackAction = PatrolTrackAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().trackPlayer);
            this.RunAction(objectParam, trackAction, this);
        }
    }

    // 清除所有动作
    public void DestroyAll() {
        foreach (KeyValuePair<int, SSAction> kv in actions) {
            SSAction ac = kv.Value;
            ac.destroy = true;
        }
    }
}

ISSActionCallback.cs:

ISSActionCallback是巡逻兵正常巡逻或追踪玩家行为结束后的回调接口,当巡逻兵由正常巡逻转为追踪玩家,或由追踪玩家转为正常巡逻,就会调用这个接口里的SSActionEvent方法,开始新的行为和消息操作,由SSActionManager这个类继承:

public enum SSActionEventType : int { Started, Competed }
public interface ISSActionCallback {
    // 回调函数
    void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null);
}

PatrolNormalAction.cs:

PatrolNormalAction是巡逻兵的普通巡逻方法,巡逻兵按照一个矩形进行移动,如果玩家进入巡逻兵的感知范围,那么会发送普通巡逻行为结束消息:

public class PatrolNormalAction : SSAction {
    private Vector3 pos;  // 巡逻兵位置
    private float rectLength;  // 矩形长度
    public float speed = 1f;  // 移动速度
    private bool isDest;  // 是否每个方向的终点
    private int direction;  // 巡逻兵的移动方向
    private PatrolData patrol;  // 巡逻兵数据
    private MainController mainController;

    private const int EAST = 0;
    private const int NORTH = 1;
    private const int WEST = 2;
    private const int SOUTH = 3;
    
    // 获取动作
    public static PatrolNormalAction GetSSAction(Vector3 location) {
        PatrolNormalAction action = CreateInstance<PatrolNormalAction>();
        action.pos = new Vector3(location.x, 0, location.z);
        // 设定矩形边长
        action.rectLength = Random.Range(4, 8);
        return action;
    }

    public override void Start() {
        this.gameobject.GetComponent<Animator>().SetBool("run", true);
        patrol = this.gameobject.GetComponent<PatrolData>();
        mainController = Director.GetInstance().mainController;
        isDest = true;
        direction = EAST;
    }

    public override void Update() {
        // 巡逻兵按矩形移动
        MoveRect();
        // 如果巡逻兵和玩家在同一区域并在巡逻兵感知范围内,那么开始追踪
        if (mainController.GetPlayerAreaId() == patrol.patrolAreaId && patrol.isTrackPlayer) {
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Competed, 0, null, this.gameobject);
        }
    }

    void MoveRect() {
        float newPosX = pos.x;
        float newPosZ = pos.z;
        if (isDest) {
            // 设定每个方向的新的移动终点
            switch (direction) {
                case EAST:
                    newPosX -= rectLength;
                    break;
                case NORTH:
                    newPosZ += rectLength;
                    break;
                case WEST:
                    newPosX += rectLength;
                    break;
                case SOUTH:
                    newPosZ -= rectLength;
                    break;
            }
            isDest = false;
        }
        Vector3 newPos = new Vector3(newPosX, 0, newPosZ);
        this.transform.LookAt(newPos);
        float distance = Vector3.Distance(transform.position, newPos);
        // 沿着方向直走
        if (distance > 1) {
            transform.position = Vector3.MoveTowards(this.transform.position, newPos, speed * Time.deltaTime);
        }
        // 在转弯处改变方向
        else {
            direction = (direction + 1) % 4;
            isDest = true;
        }
        // 设定新的位置
        pos.x = newPosX;
        pos.z = newPosZ;
    }
}

PatrolTrackAction.cs:

PatrolTrackAction是巡逻兵的追踪方法,当玩家进入巡逻兵的感知范围内时,巡逻兵会使用这一方法,开始追踪玩家,如果玩家走出了巡逻兵的感知范围,则会发送追踪行为结束消息:

public class PatrolTrackAction : SSAction {
    private GameObject trackPlayer;  // 追踪的玩家
    private PatrolData patrol;  // 巡逻兵
    private MainController mainController;

    public override void Start() {
        patrol = this.gameobject.GetComponent<PatrolData>();
        mainController = Director.GetInstance().mainController;
    }

    public override void Update() {
        // 朝玩家方向走
        transform.position = Vector3.MoveTowards(this.transform.position, trackPlayer.transform.position, 2 * Time.deltaTime);
        this.transform.LookAt(trackPlayer.transform.position);

        // 如果玩家和巡逻兵不在同一个区域,或巡逻兵没有感知到玩家
        if (mainController.GetPlayerAreaId() != patrol.patrolAreaId || !patrol.isTrackPlayer) {
            // 发送追踪行为结束消息
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Competed, 1, null, this.gameobject);
        }
    }

    // 获取动作
    public static PatrolTrackAction GetSSAction(GameObject player) {
        PatrolTrackAction action = CreateInstance<PatrolTrackAction>();
        action.trackPlayer = player;
        return action;
    }
}

PatrolMoveManager.cs:

PatrolMoveManager是巡逻兵的移动方法管理器,里面有一个按照矩形移动的方法:

public class PatrolMoveManager : SSActionManager {
    // 按照矩形移动
    public void MoveRect(GameObject patrol) {
        PatrolNormalAction moveRect = PatrolNormalAction.GetSSAction(patrol.transform.position);
        this.RunAction(patrol, moveRect, this);
    }
}

接着实现游戏事件处理部分,以及为游戏对象编写的脚本所完成,使用订阅与发布模式传递消息:

请添加图片描述

GameEventManager.cs:

GameEventManager是处理游戏事件的代理模型,有三个事件的代理,分别是增加分数,减少宝箱和游戏结束:

public class GameEventManager : MonoBehaviour {
    // 增加分数
    public delegate void AddScoreEvent();
    public static event AddScoreEvent AddScoreAction;
    // 减少宝箱
    public delegate void DecreaseTreasureEvent();
    public static event DecreaseTreasureEvent DecreaseTreasureAction;
    // 游戏结束
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverAction;

    // 玩家逃脱
    public void PlayerGetAway() {
        if (AddScoreAction != null) {
            AddScoreAction();
        }
    }

    // 减少宝箱数量
    public void DecreaseTreasureNum() {
        if (DecreaseTreasureAction != null) {
            DecreaseTreasureAction();
        }
    }

    // 玩家被抓到了
    public void PlayerGetCaught() {
        if (GameoverAction != null) {
            GameoverAction();
        }
    }
}

TreasureCollision.cs:

TreasureCollision是宝箱的碰撞的脚本,当玩家碰撞到宝箱时,宝箱会从图中消失,并发送减少宝箱数量的消息:

public class TreasureCollision : MonoBehaviour {
    void OnTriggerEnter(Collider collider) {
        // 如果是玩家碰到还在显示中的宝箱
        if (collider.gameObject.tag == "Player" && this.gameObject.activeSelf) {
            this.gameObject.SetActive(false);
            // 减少宝箱数量
            Singleton<GameEventManager>.Instance.DecreaseTreasureNum();
        }
    }
}

PlayerCollision.cs:

PlayerCollision是巡逻兵与玩家碰撞的处理脚本,当巡逻兵感知到与其发生碰撞的对象是玩家,那么调整玩家状态,触发玩家被抓事件,如果碰撞是墙,那么根据刚体性质,改换方向移动:

public class PlayerCollision : MonoBehaviour {
    void OnCollisionEnter(Collision collider) {
        // 当和巡逻兵相撞的是玩家,那么调整玩家状态,触发玩家被抓事件,如果碰撞是墙,那么改换方向移动
        if (collider.gameObject.tag == "Player") {
            collider.gameObject.GetComponent<Animator>().SetTrigger("death");
            this.GetComponent<Animator>().SetTrigger("attack");
            Singleton<GameEventManager>.Instance.PlayerGetCaught();
        }
    }
}

AreaTrigger.cs:

AreaTrigger是地图中每个区域的触发脚本,地图一共被分割成9个区域,每个区域有一个巡逻兵,当玩家进入某个区域时,区域会感知到玩家,并记录玩家进入区域的序号:

public class AreaTrigger : MonoBehaviour {
    public int areaId;  // 区域序号
    MainController mainController;
    private void Start() {
        mainController = Director.GetInstance().mainController as MainController;
    }

    void OnTriggerEnter(Collider collider) {
        // 记录玩家进入区域的序号
        if (collider.gameObject.tag == "Player") {
            mainController.SetPlayerAreaId(areaId);
        }
    }
}

PatrolTrigger.cs:

PatrolTrigger是巡逻兵的触发脚本,当玩家进入或退出巡逻兵的感知范围使,设置巡逻兵追踪玩家的状态和追踪玩家的对象:

public class PatrolTrigger : MonoBehaviour {
    void OnTriggerEnter(Collider collider) {
        // 如果玩家进入巡逻兵感知范围
        if (collider.gameObject.tag == "Player") {
            // 设置巡逻兵正在追踪玩家,设置追踪的玩家对象
            this.gameObject.transform.parent.GetComponent<PatrolData>().isTrackPlayer = true;
            this.gameObject.transform.parent.GetComponent<PatrolData>().trackPlayer = collider.gameObject;
        }
    }

    void OnTriggerExit(Collider collider) {
        // 如果玩家退出巡逻兵感知范围
        if (collider.gameObject.tag == "Player") {
            // 设置巡逻兵不在追踪玩家,设置追踪的玩家对象为空
            this.gameObject.transform.parent.GetComponent<PatrolData>().isTrackPlayer = false;
            this.gameObject.transform.parent.GetComponent<PatrolData>().trackPlayer = null;
        }
    }
}

最后实现主控制类和视图部分:

请添加图片描述

MainController.cs:

主控制类MainController的代码如下:

public class MainController : MonoBehaviour {
    private ScoreController scoreRecorder;  // 记分器
    private PatrolMoveManager moveManager;  // 移动管理器
    private int playerAreaId;  // 玩家所处区域序号
    private GameObject player;  // 玩家对象
    public Camera mainCamera;  // 主相机
    public int treasureNumber = 10;  // 宝箱数量
    public float moveSpeed = 5;  // 移动速度
    public float rotateSpeed = 135f;  // 旋转速度
    private bool isGameOver;  // 游戏是否结束
    
    void Start() {
        Director director = Director.GetInstance();
        director.mainController = this;
        scoreRecorder = gameObject.AddComponent<ScoreController>() as ScoreController;
        moveManager = gameObject.AddComponent<PatrolMoveManager>() as PatrolMoveManager;
        LoadResources();
        mainCamera.GetComponent<CameraFollow>().follow = player;
        isGameOver = false;
    }

    void Update() {
        CheckGameOver();
    }

    // 加载资源
    public void LoadResources() {
        // 生成地图
        Instantiate(Resources.Load<GameObject>("Prefabs/Map"));
        // 生成玩家
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
        // 生成宝箱
        Singleton<GameObjectFactory>.Instance.GetTreasures();
        // 生成巡逻兵并让其移动
        MovePatrol();
    }

    public void MovePatrol() {
        // 让所有巡逻兵都移动
        List<GameObject> patrols = Singleton<GameObjectFactory>.Instance.GetPatrols();
        for (int i = 0; i < patrols.Count; i++) {
            moveManager.MoveRect(patrols[i]);
        }
    }

    // 玩家移动
    public void MovePlayer(float translationX, float translationZ) {
        if(!isGameOver) {
            MovePlayerAction(translationX, translationZ);
            if (player.transform.position.y != 0) {
                player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
            }     
        }
    }

    // 设置玩家移动时的跑步动作
    public void MovePlayerAction(float translationX, float translationZ) {
        if (translationX != 0 || translationZ != 0) {
            player.GetComponent<Animator>().SetBool("run", true);
        }
        else {
            player.GetComponent<Animator>().SetBool("run", false);
        }
        // 移动和旋转动作
        player.transform.Translate(0, 0, translationZ * moveSpeed * Time.deltaTime);
        player.transform.Rotate(0, translationX * rotateSpeed * Time.deltaTime, 0);
    }

    public void IncreaseScore() {
        scoreRecorder.IncreaseScore();
    }

    public void CheckGameOver() {
        // 所有宝箱都被收集,游戏结束
        if(treasureNumber == 0) {
            Gameover();
        }
    }

    // 游戏结束,释放所有的巡逻兵
    public void Gameover() {
        isGameOver = true;
        Singleton<GameObjectFactory>.Instance.FreePatrols();
        moveManager.DestroyAll();
    }

    public void DecreaseTreasureNumber() {
        treasureNumber--;
    }

    public int GetScore() {
        return scoreRecorder.GetScore();
    }

    public void SetPlayerAreaId(int areaId) {
        playerAreaId = areaId;
    }

    public int GetPlayerAreaId() {
        return playerAreaId;
    }

    public int GetTreasureNumber() {
        return treasureNumber;
    }

    public bool GetGameover() {
        return isGameOver;
    }

    public void Restart() {
        SceneManager.LoadScene("Scenes/SampleSence");
    }

    void OnEnable() {
        GameEventManager.AddScoreAction += IncreaseScore;
        GameEventManager.GameoverAction += Gameover;
        GameEventManager.DecreaseTreasureAction += DecreaseTreasureNumber;
    }

    void OnDisable() {
        GameEventManager.AddScoreAction -= IncreaseScore;
        GameEventManager.GameoverAction -= Gameover;
        GameEventManager.DecreaseTreasureAction -= DecreaseTreasureNumber;
    }
}

CameraFollow.cs:

CameraFollow是摄像机跟随脚本,摄像机可以跟随玩家移动,提高可玩性:

public class CameraFollow : MonoBehaviour {
    public GameObject follow;  // 跟随的物体
    public float speed = 5f;  // 相机跟随物体的的速度
    Vector3 offsetPos;  // 相机和物体的相对偏移位置

    void Start() {
        offsetPos = transform.position - follow.transform.position;
    }

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

View.cs:

视图类经过主控制器的控制,显示相应内容:

public class View : MonoBehaviour {
    private MainController mainController;

    void Start() {
        mainController = Director.GetInstance().mainController as MainController;
    }

    void Update() {
        Move();
    }

    void OnGUI() {
        ShowScore();
        ShowRules();
        GUIStyle textStyle = new GUIStyle();
        textStyle.fontSize = 30;
        if(mainController.GetGameover() && mainController.GetTreasureNumber() != 0) {
            GUI.Label(new Rect(Screen.width / 2 - 55, Screen.width / 2 - 250, 100, 100), "游戏结束", textStyle);
            if (GUI.Button(new Rect(Screen.width / 2 - 45, Screen.width / 2 - 170, 100, 50), "重新开始")) {
                mainController.Restart();
            }
        }
        else if(mainController.GetTreasureNumber() == 0) {
            GUI.Label(new Rect(Screen.width / 2 - 55, Screen.width / 2 - 250, 100, 100), "恭喜胜利!", textStyle);
            if (GUI.Button(new Rect(Screen.width / 2 - 45, Screen.width / 2 - 170, 100, 50), "重新开始")) {
                mainController.Restart();
            }
        }
    }

    public void Move() {
        float translationX = Input.GetAxis("Horizontal");
        float translationZ = Input.GetAxis("Vertical");
        mainController.MovePlayer(translationX, translationZ);
    }

    public void ShowScore() {
        GUIStyle scoreStyle = new GUIStyle();
        GUIStyle textStyle = new GUIStyle();
        scoreStyle.normal.textColor = Color.yellow;
        scoreStyle.fontSize = 20;
        textStyle.fontSize = 20;
        GUI.Label(new Rect(Screen.width - 100, 5, 200, 50), "分数:", textStyle);
        GUI.Label(new Rect(Screen.width - 50, 5, 200, 50), mainController.GetScore().ToString(), scoreStyle);
        GUI.Label(new Rect(10, 5, 50, 50), "剩余宝箱数:", textStyle);
        GUI.Label(new Rect(125, 5, 50, 50), mainController.GetTreasureNumber().ToString(), scoreStyle);
    }

    // 展示规则
    public void ShowRules() {
        GUIStyle ruleStyle = new GUIStyle();
        ruleStyle.fontSize = 17;
        GUI.Label(new Rect(Screen.width / 2 - 80, 10, 100, 100), "按方向键进行移动", ruleStyle);
        GUI.Label(new Rect(Screen.width / 2 - 190, 30, 100, 100), "每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束", ruleStyle);
        GUI.Label(new Rect(Screen.width / 2 - 130, 50, 100, 100), "收集完所有的宝箱那么游戏获胜", ruleStyle);
    }
}

Director.cs:

导演类,返回主控制器的唯一实例:

public class Director : System.Object {
    private static Director instance;
    public MainController mainController { get; set; }
    public static Director GetInstance() {
        if (instance == null) {
            instance = new Director();
        }
        return instance;
    }
}

核心算法

MainControllerMovePlayerAction方法中,因为要模拟玩家的跑步动作,需要在普通状态和跑步状态之间切换,就需要根据位移对状态机进行判断操作,以实现跑步的效果,所编写的代码如下:

// 设置玩家移动时的跑步动作
public void MovePlayerAction(float translationX, float translationZ) {
    if (translationX != 0 || translationZ != 0) {
        player.GetComponent<Animator>().SetBool("run", true);
    }
    else {
        player.GetComponent<Animator>().SetBool("run", false);
    }
    // 移动和旋转动作
    player.transform.Translate(0, 0, translationZ * moveSpeed * Time.deltaTime);
    player.transform.Rotate(0, translationX * rotateSpeed * Time.deltaTime, 0);
}

PatrolTrackActionUpdate方法中,实现巡逻兵对玩家的追踪,之后如果玩家走出了巡逻兵所在或者感知区域,那么就会发送追踪行为结束消息,在代理处实现分数加一行为:

public override void Update() {
    // 朝玩家方向走
    transform.position = Vector3.MoveTowards(this.transform.position, trackPlayer.transform.position, 2 * Time.deltaTime);
    this.transform.LookAt(trackPlayer.transform.position);

    // 如果玩家和巡逻兵不在同一个区域,或巡逻兵没有感知到玩家
    if (mainController.GetPlayerAreaId() != patrol.patrolAreaId || !patrol.isTrackPlayer) {
        // 发送追踪行为结束消息
        this.destroy = true;
        this.callback.SSActionEvent(this, SSActionEventType.Competed, 1, null, this.gameobject);
    }
}

游戏截图:

游戏界面:

请添加图片描述

被巡逻兵抓到,巡逻兵攻击,倒地游戏失败:

请添加图片描述

请添加图片描述

收集完全部宝箱,游戏成功:

请添加图片描述

巡逻兵偏离巡逻轨迹,开始追踪:

请添加图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值