智能巡逻兵
游戏要求:
-
游戏设计要求
- 创建一个地图和若干巡逻兵。
- 每个巡逻兵走一个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后运行即可。