Unity实战之智能巡逻兵

本文介绍了一个Unity游戏项目,玩家需躲避多个巡逻兵以得分。游戏中,巡逻兵在特定区域之间巡逻,遇到玩家会进行追击,而玩家需巧妙避开。通过碰撞检测和触发事件来实现交互,采用mvc架构并结合订阅发布模式处理游戏事件。代码中包括巡逻兵的碰撞、追踪、工厂和动作等关键逻辑。
摘要由CSDN通过智能技术生成

Unity实战之智能巡逻兵

项目源码




游戏说明

一个地图,其中有多个巡逻兵,靠近巡逻兵会被追击,远离一定距离停止追击。每甩掉一个巡逻兵记一分。





项目预制

项目一共需要三种预制资源:

  1. 地图

    为了让巡逻兵在特定的地方追踪,给每个区域都增加了一个空气墙(绿色线)在这里插入图片描述



  1. 玩家

    增加碰撞器和刚体属性

    在这里插入图片描述

    添加动画控制器

    在这里插入图片描述



  2. 巡逻兵

    由两部分组成,其中Patrol和root都分别挂载了碰撞器(如下图绿线)

    在这里插入图片描述

动画:
在这里插入图片描述

​ 其中大的盒碰撞器勾选isTrigger,由OnTriggerEnterOnTriggerExit函数触发





项目结构

本次项目依旧采用mvc架构,并引入订阅/发布模式。整体UML图如下

在这里插入图片描述





相关代码

PatrolData:保存了巡逻兵的相关属性

public class PatrolCollisionAction : MonoBehaviour
{
    void OnCollisionEnter(Collision collider){
        
        if(collider.gameObject.tag == "player"){
            Debug.Log("1111");
            collider.gameObject.GetComponent<Animator>().SetTrigger("death");
            this.GetComponent<Animator>().SetTrigger("attack");
            Singleton<GameEventManager>.Instance.PlayerBeCaught();
        }

        if(collider.gameObject.tag == "wall"){
            Debug.Log("撞墙");
            int curr = this.GetComponent<PatrolData>().curr;
            curr = (curr+2)%4;
            this.GetComponent<PatrolData>().curr = curr;
        }
    }
}

PatrolTrigger:巡逻兵的触发脚本,用于处理和玩家靠近,远离

public class PatrolTrigger : MonoBehaviour
{
    void OnTriggerEnter(Collider collider) {
        if (collider.gameObject.tag == "player") {
            Debug.Log("enter");
            this.gameObject.transform.parent.GetComponent<PatrolData>().isTracing = true;
            this.gameObject.transform.parent.GetComponent<PatrolData>().trackPlayer = collider.gameObject;
        }
    }

    void OnTriggerExit(Collider collider) {
        if (collider.gameObject.tag == "player") {
            Debug.Log("exit");
            this.gameObject.transform.parent.GetComponent<PatrolData>().isTracing = false;
            this.gameObject.transform.parent.GetComponent<PatrolData>().trackPlayer = null;
        }
    }
}


PatrolFactory:巡逻兵工厂,用于巡逻兵的生产和释放

public class PatrolFactory : MonoBehaviour
{
    private List<GameObject> used = new List<GameObject>();
    private List<GameObject> free = new List<GameObject>();
    GameObject patrol;

    FirstController firstController;
    // Start is called before the first frame update
    void Start()
    {
        firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
    }

    public List<GameObject> GetPatrols() {
        float[] x = {-6f, -2f, 3f};
        float[] z = {-6f, -1.6f, 3f};

        for(int i = 0; i < 3; i++){
            for(int j = 0; j < 3; j++){
                Vector3 start_pos = new Vector3(x[i], 0, z[j]);
                patrol = GameObject.Instantiate(
                    Resources.Load<GameObject>("Prefabs/Patrol"),
                    start_pos,
                    Quaternion.identity
                );
                patrol.SetActive(true);
                patrol.GetComponent<PatrolData>().patrolAreaId = i*3+j+1;
                patrol.GetComponent<PatrolData>().startPos = start_pos;
                used.Add(patrol);
            }
        }
        return used;
    }

    public void FreePatrols() {
        foreach(GameObject gameObject in used){
            gameObject.GetComponent<Animator>().SetBool("run", false);
        }
    }
}


PatrolMoveAction:巡逻兵正常巡逻动作,规定好四个点,走到某一个点就把目的地置成下一个点。当巡逻动作结束时,就调用回调函数切换成追击动作。

public class PatrolFactory : MonoBehaviour
{
    private List<GameObject> used = new List<GameObject>();
    private List<GameObject> free = new List<GameObject>();
    GameObject patrol;

    FirstController firstController;
    // Start is called before the first frame update
    void Start()
    {
        firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
    }

    public List<GameObject> GetPatrols() {
        float[] x = {-6f, -2f, 3f};
        float[] z = {-6f, -1.6f, 3f};

        for(int i = 0; i < 3; i++){
            for(int j = 0; j < 3; j++){
                Vector3 start_pos = new Vector3(x[i], 0, z[j]);
                patrol = GameObject.Instantiate(
                    Resources.Load<GameObject>("Prefabs/Patrol"),
                    start_pos,
                    Quaternion.identity
                );
                patrol.SetActive(true);
                patrol.GetComponent<PatrolData>().patrolAreaId = i*3+j+1;
                patrol.GetComponent<PatrolData>().startPos = start_pos;
                used.Add(patrol);
            }
        }
        return used;
    }

    public void FreePatrols() {
        foreach(GameObject gameObject in used){
            gameObject.GetComponent<Animator>().SetBool("run", false);
        }
    }
}


PatrolTrackAction:巡逻兵的追击事件

public class PatrolTrackAction : SSAction
{
    public GameObject trackPlayer;
    public PatrolData patrol;
    public FirstController firstController;
    public bool isLookAt = false;
    // Start is called before the first frame update

    public static PatrolTrackAction GetSSAction(GameObject player){
        PatrolTrackAction patrolTrackAction = CreateInstance<PatrolTrackAction>();
        patrolTrackAction.trackPlayer = player;
        return patrolTrackAction;
    }

    public override void Start()
    {
        patrol = this.gameObject.GetComponent<PatrolData>();
        firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
    }

    // Update is called once per frame
    public override void Update()
    {
        transform.position = Vector3.MoveTowards(this.transform.position, trackPlayer.transform.position, Time.deltaTime);
        this.transform.LookAt(trackPlayer.transform.position);
        if(patrol.patrolAreaId != firstController.GetPlayerMapId() || !patrol.isTracing){
            this.destroy = true;
            this.callback.SSActionEvent(this, SSActionEventType.Completed, 1, null, this.gameObject);
        }
    


PatrolCollisionAction:巡逻兵的撞击事件。若和玩家撞击,则调整状态为攻击;若和墙撞击,则换巡逻方向

public class PatrolCollisionAction : MonoBehaviour
{
    void OnCollisionEnter(Collision collider){
        
        if(collider.gameObject.tag == "player"){
            Debug.Log("1111");
            collider.gameObject.GetComponent<Animator>().SetTrigger("death");
            this.GetComponent<Animator>().SetTrigger("attack");
            Singleton<GameEventManager>.Instance.PlayerBeCaught();
        }

        if(collider.gameObject.tag == "wall"){
            Debug.Log("撞墙");
            int curr = this.GetComponent<PatrolData>().curr;
            curr = (curr+2)%4;
            this.GetComponent<PatrolData>().curr = curr;
        }
    }
}


MapTrigger:地图的触发器

public class MapTrigger : MonoBehaviour
{
    public int areaID;
    FirstController firstController;
    // Start is called before the first frame update
    private void Start()
    {
        firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
    }

    void OnTriggerEnter(Collider collider){
        if (collider.gameObject.tag == "player") {
            firstController.SetPlayerAreaId(areaID);
        }
    }
}


UserGUI:整体视图

public class UserGUI : MonoBehaviour
{
    private FirstController firstController;
    GUIStyle textStyle;
    GUIStyle topicStyle;
    // Start is called before the first frame update
    void Start()
    {
        SetStyle();
        firstController = SSDirector.GetInstance().CurrentScenceController as FirstController;
    }

    // Update is called once per frame
    void Update()
    {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        firstController.MovePlayer(x, z);
    }

    void SetStyle(){
        textStyle = new GUIStyle();
        textStyle.normal.textColor = Color.black;
        textStyle.fontSize = 30;

        topicStyle = new GUIStyle();
        topicStyle.normal.textColor = Color.black;
        topicStyle.fontSize = 60;
    }

    public void Move(){
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        firstController.MovePlayer(x, z);
    }

    void OnGUI(){
        if(firstController.isGameOver) {
            GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 240, 100, 100), "score: "+firstController.score, topicStyle);
            GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 300, 100, 100), "Game Over!", topicStyle);
            if (GUI.Button(new Rect(Screen.width/2 - 45, Screen.width/2 - 170, 100, 50), "Start Game")) {
                firstController.Restart();
            }

        }
    }
}


FirstController:主控制器,用于资源加载、事件调度

public class UserGUI : MonoBehaviour
{
    private FirstController firstController;
    GUIStyle textStyle;
    GUIStyle topicStyle;
    // Start is called before the first frame update
    void Start()
    {
        SetStyle();
        firstController = SSDirector.GetInstance().CurrentScenceController as FirstController;
    }

    // Update is called once per frame
    void Update()
    {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        firstController.MovePlayer(x, z);
    }

    void SetStyle(){
        textStyle = new GUIStyle();
        textStyle.normal.textColor = Color.black;
        textStyle.fontSize = 30;

        topicStyle = new GUIStyle();
        topicStyle.normal.textColor = Color.black;
        topicStyle.fontSize = 60;
    }

    public void Move(){
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        firstController.MovePlayer(x, z);
    }

    void OnGUI(){
        if(firstController.isGameOver) {
            GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 240, 100, 100), "score: "+firstController.score, topicStyle);
            GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 300, 100, 100), "Game Over!", topicStyle);
            if (GUI.Button(new Rect(Screen.width/2 - 45, Screen.width/2 - 170, 100, 50), "Start Game")) {
                firstController.Restart();
            }

        }
    }
}


订阅发布模式

定义一个游戏的管理者,用于充当消息发布媒体

GameEventManager

public class GameEventManager : MonoBehaviour
{
    public delegate void AddScoreEvent();
    public static event AddScoreEvent AddScoreAction;
    public delegate void GameOverEvent();
    public static event GameOverEvent GameOverAction;

    public void PlayerRunAway() {
        if(AddScoreAction != null){
            AddScoreAction();
        }
    }

    public void PlayerBeCaught() {
        if(GameOverAction != null){
            GameOverAction();
        }
    }
}


并在FirstController中添加客户代码

void OnEnable() {
	GameEventManager.AddScoreAction += AddScore;
	GameEventManager.GameOverAction += EndGame;
}

void OnDisable() {
	GameEventManager.AddScoreAction -= AddScore;
	GameEventManager.GameOverAction -= EndGame;
}

最后在玩家身上加一个镜头跟随的脚本

CameraFollow

public class CameraFollow : MonoBehaviour {
    private Vector3 offset;
    public GameObject role;
 
    void Start () {
        role = (SSDirector.GetInstance().CurrentScenceController as FirstController).player;
        offset = role.transform.position - this.transform.position;
	}
 
    void Update () {
        transform.position = role.transform.position - offset;
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值