智能巡逻兵
游戏基本内容
玩家
- 玩家操控人物在地图中移动
- 靠近巡逻兵一定距离会被追赶,摆脱后得1分
- 被巡逻兵抓到游戏结束
- 吃到蓝色水晶得2分
- 吃到紫色水晶得5分
- 每轮游戏开始玩家会在复活区域开始游戏
巡逻兵
- 每个区域有2个巡逻兵
- 巡逻兵无法跨越区域,当碰到每个区域的出入口时被消灭,在所属区域内重新生成新的的巡逻兵,进行巡逻
- 以一个随机的四边形路线巡逻
- 碰到墙壁后重新生成巡逻路线
- 当玩家靠近到一定距离会追赶玩家,被摆脱后继续巡逻
- 抓到玩家游戏结束
水晶
- 有两种水晶,蓝色水晶2分,紫色水晶5分
- 普通区域有1个紫水晶,3个蓝水晶
- 特殊区域有3个紫水晶
- 被玩家吃后,当局游戏不再重新生成
- 每局游戏开局自动生成,出现位置确定
地图
- 如图
- 最上端区域为安全区域,不会出现巡逻兵,玩家会在安全区域开始游戏
- 中间区域为特殊区域,只有一个出入口。
- 其余区域为普通区域
游戏编程方法
- 单例模式
- 工厂模式
- 动作管理
- 订阅与发布模式
游戏素材
玩家
巡逻兵
天空盒
墙贴图
水晶贴图
自制预制
地图预制,Plane
当然素材下载下来之后还要进行各种调整来符合需求。
设置上的小技巧
出入口的设置
出入口需要能通过且能感知到,因此可以用空游戏对象,加上触发器,并设置其标签为Gate。这样可以让角色通过,并且可以通过角色的触发器感知到出入口的存在
巡逻兵对出入口的感知
巡逻兵的根节点上有一个触发器,用来感知玩家的。但这个触发器范围太广,会导致离出入口还很远就感知到出入口,然后被消灭掉。
因此不要把感知出入口的函数放在根节点,可以放在子节点,调整该节点的触发器范围小一点,这样就可以让巡逻兵在足够靠近出入口时感知到,然后被消灭。
摄像机的跟随
当将摄像机跟随的目标设为玩家角色时,发现怎么调整都不能得到令人满意的第三人称视角。
我就在玩家角色中增加了一个子对象cube,去掉渲染,放在头顶合适的位置。让摄像跟随的目标变成这个cube,就可以实现满意的跟随效果。
UML图
游戏主要代码分析
CamareFollow.cs
实现摄像机的跟随,平滑移动
public Transform target;
public float distanceH = 10f;
public float distanceV = 5f;
public float smoothSpeed = 10f; //平滑参数
void LateUpdate()
{
Vector3 nextpos = target.forward * -1 * distanceH + target.up * distanceV + target.position;
this.transform.position =Vector3.Lerp(this.transform.position, nextpos, smoothSpeed * Time.deltaTime);
this.transform.LookAt(target);
}
跟随效果:
PatrolData.cs
巡逻兵的数据
public GameObject player; //玩家
public float speed; //速度
public bool isFollow = false; //是否跟随玩家
public bool isGate = false; //是否撞到区域的出入口
public bool isWall = false; //是否撞到墙
public int area; //所在的区域
PatrolFactory.cs
巡逻兵工厂,用于生成与回收巡逻兵
getPatrol,用于根据区域在该区域中随机生成巡逻兵
public GameObject getPatrol(int area){
GameObject newPatrol = null;
float baseX = 0;
float baseZ = 0;
//根据区域确定基本坐标
switch (area) {
case 0:
baseX = -100;
baseZ = -50;
break;
case 1:
baseX = 80;
baseZ = -180;
break;
case 2:
baseX = 250;
baseZ = -180;
break;
case 3:
baseX = 250;
baseZ = -50;
break;
case 4:
baseX = 50;
baseZ = -50;
break;
}
//当巡逻兵有库存时
if (free.Count > 0) {
newPatrol = free [0].gameObject;
//在基本坐标的基础上生成有随机的偏移坐标
float dev = Random.Range(-20, 20);
newPatrol.transform.position = new Vector3(baseX + dev, 0, baseZ + dev);
free.Remove (free[0]);
} else {
//在基本坐标的基础上生成有随机的偏移坐标
float dev = Random.Range(-20, 20);
newPatrol = Instantiate<GameObject> (patrolPrefab, new Vector3(baseX + dev, 0, baseZ + dev), Quaternion.identity);
}
newPatrol.SetActive (true);
newPatrol.GetComponent<PatrolData> ().area = area;
//加入使用中队列
used.Add (newPatrol.GetComponent<PatrolData>());
return newPatrol;
}
freePatrol方法,回收巡逻兵,与Hit UFO中基本相同
public void freePatrol(GameObject oldPatrol){
for (int i = 0; i < used.Count; i++) {
if (used [i].gameObject == oldPatrol) {
PatrolData move = used[i];
used.Remove (move);
free.Add (move);
return;
}
}
Debug.Log ("Exception: No such disk int used list");
}
Director.cs
单例的导演类
public ScenceController scence; //场景控制器
private static Director instance; //单例
public bool gameOver = false; //游戏状态
public static Director getInstance(){
if (instance == null) {
instance = new Director ();
}
return instance;
}
//开始游戏
public void gameStart(){
scence.gameStart ();
}
GameEventManager.cs
事件发布类,连接触发条件和响应方法
//游戏结束事件
public delegate void GameoverEvent();
public static event GameoverEvent GameoverChange;
//玩家逃脱事件
public delegate void AddscoreEvent();
public static event GameoverEvent Addscore;
//巡逻兵撞到出入口事件