unity 智能巡逻兵

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

三、实现过程
本次项目参考自师兄的项目https://blog.csdn.net/C486C/article/details/80153548
并在此基础上增加了自己的设计。
整体的设计思路在师兄的博客中已经讲得很清楚了,我就挑部分比较重要以及我做了修改的代码进行阐述。
PatrolData
存储了巡逻兵的数据。
public class PatrolData : MonoBehaviour
{
public int sign; //标志巡逻兵在哪一块区域
public bool follow_player = false; //是否跟随玩家
public int wall_sign = -1; //当前玩家所在区域标志
public GameObject player; //玩家游戏对象
public Vector3 start_position; //当前巡逻兵初始位置
public bool alive = true;
}
GoPatrolAction
和之前的代码结构一样,Action继承自SSAction。GoPatrolAction是控制士兵进行巡逻的动作。士兵默认按四角矩形的轨迹运动,如果跟随玩家的时机被触发,则销毁巡逻动作,增加跟随玩家动作。
public class GoPatrolAction : SSAction
{
private enum Dirction { EAST, NORTH, WEST, SOUTH };
private float pos_x, pos_z; //移动前的初始x和z方向坐标
private float move_length; //移动的长度
private float move_speed = 1.5f; //移动速度
private bool move_sign = true; //是否到达目的地
private Dirction dirction = Dirction.EAST; //移动的方向
private PatrolData data; //侦察兵的数据
private GoPatrolAction() { }
public static GoPatrolAction GetSSAction(Vector3 location)
{
GoPatrolAction action = CreateInstance<GoPatrolAction>();
action.pos_x = location.x;
action.pos_z = location.z;
//设定移动矩形的边长
action.move_length = Random.Range(7, 10);
return action;
}
public override void Update()
{
if (data.alive == false) {
this.destroy = true;
this.callback.SSActionEvent(this, 2, this.gameobject);
return;
}
//侦察移动
Gopatrol();
//如果侦察兵需要跟随玩家并且玩家就在侦察兵所在的区域,侦查动作结束
if (data.follow_player && data.wall_sign == data.sign)
{
this.destroy = true;
this.callback.SSActionEvent(this,0,this.gameobject);
}
}
public override void Start()
{
this.gameobject.GetComponent<Animator>().SetBool("run", true);
data = this.gameobject.GetComponent<PatrolData>();
}
void Gopatrol()
{
if (move_sign)
{
//不需要转向则设定一个目的地,按照矩形移动
switch (dirction)
{
case Dirction.EAST:
pos_x -= move_length;
break;
case Dirction.NORTH:
pos_z += move_length;
break;
case Dirction.WEST:
pos_x += move_length;
break;
case Dirction.SOUTH:
pos_z -= move_length;
break;
}
move_sign = false;
}
this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));
//当前位置与目的地距离浮点数的比较
if (distance > 0.9)
{
transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);
}
else
{
dirction = dirction + 1;
if(dirction > Dirction.SOUTH)
{
dirction = Dirction.EAST;
}
move_sign = true;
}
}
}
PatrolFollowAction
控制士兵跟随玩家的动作,如果玩家脱离了追捕范围或者出了士兵的巡逻区域,则跟随动作结束并变回巡逻动作。
public class PatrolFollowAction : SSAction
{
private float speed = 2.5f; //跟随玩家的速度
private GameObject player; //玩家
private PatrolData data; //侦查兵数据
private PatrolFollowAction() { }
public static PatrolFollowAction GetSSAction(GameObject player)
{
PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
action.player = player;
return action;
}
public override void Update()
{
if (data.alive == false) {
this.destroy = true;
this.callback.SSActionEvent(this, 2, this.gameobject);
return;
}
Follow();
//如果侦察兵没有跟随对象,或者需要跟随的玩家不在侦查兵的区域内
if (!data.follow_player || data.wall_sign != data.sign)
{
this.destroy = true;
this.callback.SSActionEvent(this,1,this.gameobject);
}
}
public override void Start()
{
data = this.gameobject.GetComponent<PatrolData>();
}
void Follow()
{
transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
this.transform.LookAt(player.transform.position);
}
}
上述两个动作的切换是通过callBack函数实现的
public override void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
{
if(intParam == 0)
{
//侦查兵跟随玩家
PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
this.RunAction(objectParam, follow, this);
}
else if (intParam == 1)
{
//侦察兵按照初始位置开始继续巡逻
GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
this.RunAction(objectParam, move, this);
//玩家逃脱
Singleton<GameEventManager>.Instance.PlayerEscape();
}
else {
}
}
GameEventManager
与事件的发布相关的管理器。
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 CrystalEvent();
public static event CrystalEvent CrystalChange;
//玩家射击
public delegate void Shoot();
public static event Shoot ShootChange;
//玩家逃脱
public void PlayerEscape()
{
if (ScoreChange != null)
{
ScoreChange();
}
}
//玩家被捕
public void PlayerGameover()
{
if (GameoverChange != null)
{
GameoverChange();
}
}
//减少水晶数量
public void ReduceCrystalNum()
{
if (CrystalChange != null)
{
CrystalChange();
}
}
public void shoot() {
if (ShootChange != null) {
ShootChange();
}
}
}
当某一个事件发生时,通过调用GameEventManager中的某一个函数来发布这个事件,订阅这个事件的其他地方将会接收到此次事件,并作出相应操作。
比如控制器检测到鼠标点击时通过GameEventManage的shoot发布射击事件,订阅这个事件的音频管理器将会播放一段射击的音频。
可以通过以下方法订阅:
public AudioClip shootClip;
void OnEnable()
{
GameEventManager.ShootChange += Shoot;
}
void OnDisable()
{
GameEventManager.ShootChange -= Shoot;
}
void Shoot() {
FirstSceneController scene = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
//在一个玩家的位置播放音乐
AudioSource.PlayClipAtPoint(shootClip, scene.player.transform.position);
}
IUserAction
用户接口,定义了玩家操作
public interface IUserAction
{
//移动玩家
void MovePlayer(float translationX, float translationZ, Vector3 mousePosition);
//得到分数
int GetScore();
//得到水晶数量
int GetCrystalNumber();
//得到游戏结束标志
bool GetGameover();
//重新开始
void Restart();
//射击
void shoot();
}
碰撞检测
巡逻兵需要做两个碰撞检测:
与玩家直接接触;检测玩家进入巡逻范围。
由于有两个检测,所以需要设置两个碰撞体,一个放在巡逻兵对象上,另一个放在巡逻兵的子对象上,然后分别附加上不同的碰撞处理代码。
对于直接接触的碰撞,则游戏结束,如果是进入巡逻范围,则触发追赶玩家的条件。
工厂模式产生巡逻兵
以及生产了水晶。
private GameObject patrol = null; //巡逻兵
private List<GameObject> used = new List<GameObject>(); //正在被使用的巡逻兵
private GameObject crystal = null; //水晶
private List<GameObject> usedcrystal = new List<GameObject>(); //正在被使用的水晶
private float range = 18; //水晶生成的坐标范围
private Vector3[] vec = new Vector3[9]; //保存每个巡逻兵的初始位置
public FirstSceneController sceneControler; //场景控制器
public List<GameObject> GetPatrols()
{
int[] pos_x = { -12, 4, 20 };
int[] pos_z = { -20, -3, 12 };
int index = 0;
//生成不同的巡逻兵初始位置
for(int i=0;i < 3;i++)
{
for(int j=0;j < 3;j++)
{
vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);
index++;
}
}
for(int i=0; i < 9; i++)
{
patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
patrol.transform.position = vec[i];
patrol.GetComponent<PatrolData>().sign = i + 1;
patrol.GetComponent<PatrolData>().start_position = vec[i];
used.Add(patrol);
}
return used;
}
public List<GameObject> GetCrystal()
{
for(int i=0;i<12;i++)
{
crystal = Instantiate(Resources.Load<GameObject>("Prefabs/Crystal"));
float ranx = Random.Range(-range, range);
float ranz = Random.Range(-range, range);
crystal.transform.position = new Vector3(ranx, 0, ranz);
usedcrystal.Add(crystal);
}
return usedcrystal;
}
玩家跟随鼠标转动、键盘移动
//玩家移动
public void MovePlayer(float translationX, float translationZ, Vector3 mousePosition)
{
if(!game_over)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//Debug.Log(ray);
RaycastHit hitInfo;
if(Physics.Raycast(ray, out hitInfo)){
Vector3 target = hitInfo.point;
//Debug.Log(target);
target.y = player.transform.position.y;
player.transform.LookAt(target);
}
if (translationX != 0 || translationZ != 0)
{
player.GetComponent<Animator>().SetBool("run", true);
}
else
{
player.GetComponent<Animator>().SetBool("run", false);
}
//移动和旋转
player.transform.Translate(translationX * player_speed * Time.deltaTime, 0, translationZ * player_speed * Time.deltaTime);
}
点击鼠标射击
public void shoot() {
player.GetComponent<Animator>().SetBool("run", false);
player.GetComponent<Animator>().SetTrigger("shoot");
Singleton<GameEventManager>.Instance.shoot();
Ray ray = new Ray(player.transform.position, player.transform.forward);
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
GameObject obj = hit.transform.gameObject;
if (obj.tag == "Patrol") {
obj.GetComponent<PatrolData>().alive = false;
obj.GetComponent<Animator>().SetBool("run", false);
obj.GetComponent<Animator>().SetBool("dead", true);
}
}
}
相机跟随玩家移动
public class CameraFlow : MonoBehaviour
{
public GameObject follow; //跟随的物体
public float smoothing = 5f; //相机跟随的速度
Vector3 offset; //相机与物体相对偏移位置
public Transform player;
void Start()
{
offset = transform.position - follow.transform.position;
}
void FixedUpdate()
{
Vector3 target = follow.transform.position + offset;
//摄像机自身位置到目标位置平滑过渡
transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);
}
}
该博客围绕Unity智能巡逻兵游戏展开,介绍了游戏设计与程序设计要求,如创建地图和巡逻兵、巡逻兵按多边形轨迹巡逻、使用订阅与发布模式等。还阐述了游戏实现过程,包括巡逻兵数据存储、动作控制、事件管理、碰撞检测等,最后给出项目的github地址。

1069

被折叠的 条评论
为什么被折叠?



