智能巡逻兵
游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
程序设计要求:
巡逻兵动作
- 巡逻
每次获取巡逻动作时都会生成一个长度为4到7的随机距离,巡逻兵就会以这个距离的长方形轨迹巡逻。如果感知到了玩家在设定范围内,则巡逻动作被销毁,转为追踪动作。每Update一次,判断一次巡逻兵是否达到一个巡逻凸多边形的端点,若达到则改变方向,否则向目标点移动。
public static PatrolAction getSSAction(Vector3 location) {
PatrolAction action = CreateInstance<PatrolAction>();
action.posX = location.x;
action.posZ = location.z;
action.moveLength = UnityEngine.Random.Range(4, 7);
return action;
}
public override void Start() {
this.gameObject.GetComponent<Animator>().SetBool("run", true);
data = this.gameObject.GetComponent<PatrolData>();
}
public override void Update() {
if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0) {
transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
}
if(transform.position.y != 0) {
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
}
goPatrol();
if(data.ifFollowPlayer && data.areaSign == data.sign) {
this.destory = true;
this.callback.SSActoinEvent(this, 0, this.gameObject);
}
}
private void goPatrol() {
if (isArrivedAnEndPoint) {
switch (direction) {
case Direction.EAST:
posX -= moveLength;
break;
case Direction.NORTH:
posZ += moveLength;
break;
case Direction.WEST:
posX += moveLength;
break;
case Direction.SOUTH:
posZ -= moveLength;
break;
}
isArrivedAnEndPoint = false;
}
this.transform.LookAt(new Vector3(posX, 0, posZ));
float distance = Vector3.Distance(transform.position, new Vector3(posX, 0, posZ));
if(distance > 0.9) {
transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(posX, 0, posZ), speed * Time.deltaTime);
}
else {
direction += 1;
if(direction > Direction.SOUTH) {
direction = Direction.EAST;
}
isArrivedAnEndPoint = true;
}
}
- 追踪
只要玩家进入巡逻兵设定的追踪范围,巡逻兵即追踪玩家,若追踪到玩家发生碰撞玩家即失败。若玩家脱离追踪范围,得分加一。
public static FollowAction getSSAction(GameObject player) {
FollowAction action = CreateInstance<FollowAction>();
action.player = player;
return action;
}
public override void Start() {
data = this.gameObject.GetComponent<PatrolData>();
}
public override void Update() {
if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0) {
transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
}
if (transform.position.y != 0) {
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
}
transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
this.transform.LookAt(player.transform.position);
if (!data.ifFollowPlayer || data.areaSign != data.sign) {
this.destory = true;
this.callback.SSActoinEvent(this, 1, this.gameObject);
}
}
- 订阅与发布模式
发布/订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者),而是通过消息通道广播出去,让订阅改消息主题的订阅者消费到。
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 void playerEscape() {
if(ScoreChange != null) {
Debug.Log("Add score!");
ScoreChange();
}
}
public void gameOver() {
if(GameOverChange != null) {
GameOverChange();
}
}
public void ReduceCrystalNum() {
if(CrystalChange != null) {
CrystalChange();
}
}
}
public class PatrolFactory : MonoBehaviour {
private List<GameObject> usedPatrols = new List<GameObject>();
private Vector3[] vec = new Vector3[9];
public List<GameObject> getPatrols() {
int[] pos_x = { -6, 4, 13 };
int[] pos_y = { -4, 6, -13 };
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_y[j]);
index++;
}
}
for(int i = 0; i < 9; i++) {
GameObject 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];
usedPatrols.Add(patrol);
}
return usedPatrols;
}
public void stopPatrol() {
for(int i = 0; i < usedPatrols.Count; i++) {
usedPatrols[i].gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}