智能巡逻兵

智能巡逻兵

游戏要求:

  • 游戏设计要求

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

    • 必须使用订阅与发布模式传消息

      • subject:OnLostGoal
      • Publisher:?
      • Subscriber:?
    • 工厂模式生产巡逻兵

  • 友善提示1:

    • 随机生成矩阵
    • 在矩形每个边上随机找点,可得到3-4的凸多边形。
    • 5?
  • 友善提示2:

    • 参考以前的博客 ,给出自己的玩法

游戏预制:

从assetstore中下载资源,得到人物模型以及地图模型。

地图预制

在这里插入图片描述

如图,该地面被分为九个部分,其中每个部分都添加一个BoxCollider组件,并勾选上is Trigger选项,可以作为触发器接收到玩家进入的事件。

在这里插入图片描述

同时为了实现墙壁的隔绝效果(让玩家无法穿墙),还需要为每个墙壁加上mesh collider,不需要勾选is Trigger选项,因为没有额外的碰撞事件。

在这里插入图片描述

巡逻兵预制:

巡逻兵需要添加rightbody组件,以使巡逻兵具有物理引擎。
在这里插入图片描述

还需要添加capsule collider组件,并调整capsule的大小与人物模型大小相符,用于检测与玩家的碰撞事件。

在这里插入图片描述
在这里插入图片描述

还需要添加box collider组件并勾选is Trigger选项以及设置范围大小,用于检测玩家是否进入检测范围,如果进入则追击。

在这里插入图片描述

巡逻兵动画预制:

如图所示

当游戏未开始时,巡逻兵的动作是idle,游戏开始之后的动作就为run,当游戏玩家不在范围内时,巡逻兵的run动作目的地随机,而当游戏玩家进入范围内之后,就会追逐玩家。而当游戏玩家与巡逻兵碰撞时,巡逻兵的动作就会变为attack。

在这里插入图片描述

下面详细介绍控制巡逻兵动作的方法。

首先添加一个布尔变量run和一个触发器attack。

当run变量为false时,巡逻兵的动作为idle,即保持静止。

当run变量为true时,巡逻兵的动作就会从idle变为run。

当attack触发器被触发时,巡逻兵的动作就会变为attack。

玩家角色预制:

玩家角色预制与巡逻兵预制基本相同,唯一的区别就是不需要添加box collider组件。

玩家角色动画预制:

如图

在这里插入图片描述

这也基本与巡逻兵预制相同,区别就是触发器变成了death,当death被触发时玩家的动作就变为die。

代码介绍:

1. mapcollide

挂载在每个区域上的脚本,用于检测玩家所在的区域。将场景控制器的标识更改为当前区域的标识,全局都能访问。

public class mapcollide : MonoBehaviour
{
    public int sign = 0;
    FirstSceneController mysceneController;
    private void Start()
    {
        mysceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    }
    void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.tag == "Player")
        {
            mysceneController.wall_sign = sign;
        }
    }
}

2. playercollide

挂载在玩家模型上的脚本,用于当玩家与巡逻兵相遇时,触发巡逻兵的attack触发器和玩家的death触发器,使巡逻兵做劈砍的动作,使玩家倒地。

public class playcollide : MonoBehaviour
{

    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.tag == "Player")
        {
            other.gameObject.GetComponent<Animator>().SetTrigger("death");
            this.GetComponent<Animator>().SetTrigger("attack");
            Singleton<GameEventManager>.Instance.PlayerGameover();
        }
    }
}

3. patrolcollide

挂载在巡逻兵模型上的脚本,用于管理巡逻兵追击玩家与否,当玩家在巡逻兵的检测范围内,则追击,否则不追击。

public class patrolcollide : MonoBehaviour
{
    void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.tag == "Player")
        {
            this.gameObject.transform.parent.GetComponent<patrol>().follow_player = true;
            this.gameObject.transform.parent.GetComponent<patrol>().player = collider.gameObject;
        }
    }
    void OnTriggerExit(Collider collider)
    {
        if (collider.gameObject.tag == "Player")
        {
            this.gameObject.transform.parent.GetComponent<patrol>().follow_player = false;
            this.gameObject.transform.parent.GetComponent<patrol>().player = null;
        }
    }
}

4.minecollide

挂载在金矿上的脚本。当玩家与金矿碰撞时,使金矿消失并减少金矿的数量。

public class minecollide : MonoBehaviour
{
    void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.tag == "Player" && this.gameObject.activeSelf)
        {
            this.gameObject.SetActive(false);
            Singleton<GameEventManager>.Instance.ReduceGoldmineNum();
        }
    }
}

5.patrol

申明了巡逻兵所具有的属性,sign属性表示巡逻兵所在的区域编号,follow_player属性表示是否跟随玩家,wall_sign表示玩家所在的区域编号,start_position表示巡逻兵初始位置。

public class patrol  : MonoBehaviour
{
    public int sign;                      
    public bool follow_player = false;    
    public int wall_sign = -1;            
    public GameObject player;             
    public Vector3 start_position;            
}

6.myfactory

生产金矿和巡逻兵的工厂,创建了九个巡逻兵并给定了初始位置,创建十二个金矿并随机生产初始化的位置。

public class myfactory : MonoBehaviour
{
    private GameObject patrol = null;                              
    private List<GameObject> usedPatrol = new List<GameObject>();        
    private GameObject goldmine = null;                             
    private List<GameObject> usedgoldmine = new List<GameObject>();      


    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -6, 4, 13 };
        int[] pos_z = { -4, 6, -13 };
        int index = 0;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                Vector3 vec = new Vector3(pos_x[i], 0, pos_z[j]);
                patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
                patrol.transform.position = vec;
                patrol.GetComponent<patrol>().sign = index + 1;
                patrol.GetComponent<patrol>().start_position = vec;
                usedPatrol.Add(patrol);
                index++;
            }
        }

        return usedPatrol;
    }


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

        return usedgoldmine;
    }
    
    public void StopPatrol()
    {
        int size = usedPatrol.Count;
        for (int i = 0; i < size; i++)
        {
            usedPatrol[i].gameObject.GetComponent<Animator>().SetBool("run", false);
        }
    }
}

7.SSActionManager

定义了一个Dictionary类型的actiondic,表示执行动作列表。定义了两个List类型的addlist和deletelist,分别表示等待执行的动作队列和等待删除的动作队列。每次更新都会将addlist中的动作添加到执行动作列表中,并将deletelist中的动作从执行动作列表中删除。

public class SSActionManager : MonoBehaviour, ISSActionCallback
{
    private Dictionary<int, SSAction> actiondic = new Dictionary<int, SSAction>();    
    private List<SSAction> addlist = new List<SSAction>();                       
    private List<int> deletelist = new List<int>();                                            

    protected void Update()
    {
        foreach (SSAction myssaction in addlist)
        {
            actiondic[myssaction.GetInstanceID()] = myssaction;
        }
        addlist.Clear();
        foreach (KeyValuePair<int, SSAction> kv in actiondic)
        {
            SSAction myssaction = kv.Value;
            if (myssaction.destroy)
            {
                deletelist.Add(myssaction.GetInstanceID());
            }
            else if (myssaction.enable)
            {
                myssaction.Update();
            }
        }
        foreach (int key in deletelist)
        {
            SSAction myssaction = actiondic[key];
            actiondic.Remove(key);
            DestroyObject(myssaction);
        }
        deletelist.Clear();
    }
 
    public void RunAction(GameObject gameobject, SSAction ssaction, ISSActionCallback manager)
    {
        ssaction.gameobject = gameobject;
        ssaction.transform = gameobject.transform;
        ssaction.callback = manager;
        addlist.Add(ssaction);
        ssaction.Start();
    }

    public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
    {
        if(intParam == 0)
        {
            patrolchaseaction follow = patrolchaseaction.GetSSAction(objectParam.gameObject.GetComponent<patrol>().player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            Patrolaction move = Patrolaction.GetSSAction(objectParam.gameObject.GetComponent<patrol>().start_position);
            this.RunAction(objectParam, move, this);
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }

	
    public void DestroyAll()
    {
        foreach (KeyValuePair<int, SSAction> kv in actiondic)
        {
            SSAction myssaction = kv.Value;
            myssaction.destroy = true;
        }
    }
}

8.Patrolaction

巡逻兵动作类,规定了巡逻兵的动作,主要是定义了巡逻兵没有追击玩家时巡逻的动作和追击玩家的动作。

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 GoldmineEvent();
    public static event GoldmineEvent GoldmineChange;

    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
    public void ReduceGoldmineNum()
    {
        if (GoldmineChange != null)
        {
            GoldmineChange();
        }
    }
}

9.Patrolactionmanager

管理巡逻兵动作的管理类。

public class Patrolactionmanager : SSActionManager
{
    private Patrolaction patrolact;

    public void GoPatrol(GameObject patrol)
    {
        patrolact = Patrolaction.GetSSAction(patrol.transform.position);
        this.RunAction(patrol, patrolact, this);
    }
    public void DestroyAllAction()
    {
        DestroyAll();
    }
}

10.gameeventmanager

订阅与发布模式中的publisher,管理三个动作,分别是玩家逃脱追捕后的加分,玩家获得所有金矿后或被抓住时游戏结束,以及玩家碰到金矿后金矿数量减少。

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 GoldmineEvent();
    public static event GoldmineEvent GoldmineChange;

    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
    public void ReduceGoldmineNum()
    {
        if (GoldmineChange != null)
        {
            GoldmineChange();
        }
    }
}

11.FirstSceneController

该项目中最主要的控制器,同时也是订阅与发布模式中的subscriber,订阅了Gameeventmanager的事件,如果订阅的事件发生,就会导致场景控制器调用注册的方法。同时还有加载资源,更新游戏状态等功能。

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

public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
    public myfactory patrol_factory;                               
    public graderecorder 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 = 250f;                               
    private List<GameObject> patrols;                                
    private List<GameObject> goldmines;                               
    private bool game_over = false;                                 

    void Update()
    {
        for (int i = 0; i < patrols.Count; i++)
        {
            patrols[i].gameObject.GetComponent<patrol>().wall_sign = wall_sign;
        }
        if(recorder.GetGoldmineNumber() == 0)
        {
            Gameover();
        }
    }
    void Start()
    {
        SSDirector director = SSDirector.GetInstance();
        director.CurrentScenceController = this;
        patrol_factory = Singleton<myfactory>.Instance;
        action_manager = gameObject.AddComponent<Patrolactionmanager>() as Patrolactionmanager;
        LoadResources();
        main_camera.GetComponent<camera>().follow = player;
        recorder = Singleton<graderecorder>.Instance;
    }

    public void LoadResources()
    {
        Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
        goldmines = patrol_factory.GetGoldmine();
        patrols = patrol_factory.GetPatrols();
        for (int i = 0; i < patrols.Count; i++)
        {
            action_manager.GoPatrol(patrols[i]);
        }
    }
    public void MovePlayer(float translationX, float translationZ)
    {
        if(!game_over)
        {
            if (translationX != 0 || translationZ != 0)
            {
                player.GetComponent<Animator>().SetBool("run", true);
            }
            else
            {
                player.GetComponent<Animator>().SetBool("run", false);
            }
            player.transform.Translate(0, 0, translationZ * player_speed * Time.deltaTime);
            player.transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0);
            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)
            {
                player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
            }     
        }
    }

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

    public int GetGoldmineNumber()
    {
        return recorder.GetGoldmineNumber();
    }
    public bool GetGameover()
    {
        return game_over;
    }
    public void Restart()
    {
        SceneManager.LoadScene("Scenes/mySence");
    }

    void OnEnable()
    {
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
        GameEventManager.GoldmineChange += ReduceGoldmineNumber;
    }
    void OnDisable()
    {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
        GameEventManager.GoldmineChange -= ReduceGoldmineNumber;
    }
    void ReduceGoldmineNumber()
    {
        recorder.ReduceGoldmine();
    }
    void AddScore()
    {
        recorder.AddScore();
    }
    void Gameover()
    {
        game_over = true;
        patrol_factory.StopPatrol();
        action_manager.DestroyAllAction();
    }
}

项目地址及演示视频

项目地址
打开Scenes中的myscene后运行即可。

演示视频

猜你喜欢

转载自blog.csdn.net/weixin_52801288/article/details/128358202
今日推荐