github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw6/Patrol
视频传送门:https://space.bilibili.com/472759319
要求
- 游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
- 程序设计要求:
- 必须使用订阅与发布模式传消息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
- 工厂模式生产巡逻兵
订阅与发布模式
为什么需要订阅与发布模式
就像我们订阅报刊,有什么时事新闻出现,报社(发布者)可以第一时间印刷数百份乃至数千份报纸(传播媒介),发到各个订阅了该报纸的人(订阅者)手上,这样就完成了信息传播。
与观察者模式的区别
在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应,往往发布者需要对所有的观察者维持一个列表,从而在每次发生变化时通知所有观察者。在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件,以此避免发布者和订阅者之间产生依赖关系,从而降低代码的耦合性。
在本次游戏中的应用
我们通过订阅与发布模式,让巡逻兵将自己的状态传递展示出来,然后由EventManager观察并做出反应。
代码
其他的模式如工厂模式产生士兵,以及MVC架构这里就不再赘述了,感兴趣的读者可以看之前的博客。
这里主要讲一下订阅与发布的模式以及巡逻兵的行动逻辑。
void Update () {
//Debug.Log("here1");
if (Vector3.Distance(gameStatusOp.getHeroPosition().position, gameObject.transform.position) <= 10f)
{
if (!isCatching)
{
isCatching = true;
}
addAction.addDirectMovement(this.gameObject);
}
else
{
if (isCatching)
{
//stop catching
gameStatusOp.heroEscapeAndScore();
isCatching = false;
addAction.addRandomMovement(this.gameObject, false);
}
else
addAction.addRandomMovement(this.gameObject, true);
}
}
在PatrolControl中,如果hero出现在当前patrol的附近,那么开始追逐;如果hero离开了,那么停止追逐,同时将hero逃跑的信息通过firstController也就是之类的gameStatusOp发布。然后在决定当前状态后,和之前一样(MVC模式),调用actionManager来控制移动。
void OnCollisionStay(Collision e)
{
if (e.gameObject.name.Contains("Patrol"))
{
isCatching = false;
addAction.addRandomMovement(this.gameObject, false);
gameStatusOp.heroEscapeAndScore();
}
if (e.gameObject.name.Contains("hero"))
{
gameStatusOp.patrolHitHeroAndGameover();
Debug.Log("Game Over!");
}
}
当与物体相撞时,如果是和其他巡逻兵撞在一起,那么同样停止追逐,同时发布逃跑状态;如果撞到了hero,则发布游戏结束的状态。
public class GameEventManager : MonoBehaviour {
public delegate void GameScoreAction();
public static event GameScoreAction myGameScoreAction;
public delegate void GameOverAction();
public static event GameOverAction myGameOverAction;
private FirstControl scene;
void Start () {
scene = (FirstControl)Director.getInstance().sceneCtrl;
scene.gameEventManager = this;
}
void Update () {
}
//hero escape
public void heroEscapeAndScore() {
if (myGameScoreAction != null)
myGameScoreAction();
}
//hero gets caught
public void patrolHitHeroAndGameover() {
if (myGameOverAction != null)
myGameOverAction();
}
}
而每次发布的状态都有eventManager订阅,在被调用相应的函数后实现对游戏状态和分数的调整。
至于巡逻兵的巡逻轨迹则主要由下一个方向决定。我将每个巡逻兵上一次行动的方向保存在PatrolLastDir中,将行动的距离保存在PatrolMoveLength中,然后在非追逐状态下,巡逻兵会走一个一定距离的矩形。
int getRandomDirection(int index, bool isActive) {
int randomDir;
if (!isActive) {
if(PatrolLastDir[index] < 2)
randomDir = PatrolLastDir[index] + 1;
else
randomDir = -1;
PatrolLastDir[index] = randomDir;
PatrolMoveLength[index] = 0;
}
else {
PatrolMoveLength[index]++;
Debug.Log(PatrolMoveLength[index]);
if(PatrolMoveLength[index] > 1000f){
if(PatrolLastDir[index] < 2)
randomDir = PatrolLastDir[index] + 1;
else
randomDir = -1;
PatrolLastDir[index] = randomDir;
PatrolMoveLength[index] = 0;
}
else{
randomDir = PatrolLastDir[index];
}
}
return randomDir;
}
其他具体的代码就不贴上来了,感兴趣的请移步github。