智能巡逻兵
- 提交要求:
- 游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
- 程序设计要求:
- 必须使用订阅与发布模式传消息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
- 工厂模式生产巡逻兵
- 必须使用订阅与发布模式传消息
本次作业要求是做一个智能巡逻兵的作业,下载的资源动画用上之后,游戏是参照一个师兄的做出来的,当然我做的是bug无穷的小游戏.......,主要还是在写的基础上添加了一些趣味性吧。
本次作业还要求用一种新学习的设计模式,订阅与发布模式,该模式也称为观察者模式,发布者和订阅者没有直接的耦合,是实现模型与视图分离的重要手段。
本次作业中的地图是自己参照着用正方体弄出来的,所以感觉可能是这个原因导致很多的bug
可以展示一下自己弄的小模型:
虽然说比较丑吧,但是毕竟还是自己做的
本次作业的UML图设计如下:
以下是一些主要代码,主要分为几个部分:
1.订阅与发布模式的部分,发布游戏结束和游戏得分的事件
GameEventManager的代码:
public class GameEventManager : MonoBehaviour
{
public delegate void GameScoreAction();
public static event GameScoreAction myGameScoreAction;
public delegate void GameOverAction();
public static event GameOverAction myGameOverAction;
private SceneController scene;
private bool isGameOver;
void Start()
{
scene = SceneController.getInstance();
scene.gameEventManager = this;
isGameOver = false;
}
//hero逃离巡逻兵,得分
public void getScore()
{
if (myGameScoreAction != null && !isGameOver)
{
myGameScoreAction();
}
}
//当巡逻兵捕捉到英雄,游戏结束
public void gameOver()
{
if (myGameOverAction != null)
{
isGameOver = true;
myGameOverAction();
}
}
public bool getGameState()
{
return isGameOver;
}
}
GameText的代码,程序就像搭积木一样,用的时候加上去,不用的时候就撤去,就像GameText中代码一样,需要加分的时候i就执行加分的事件,否则就执行游戏结束的事件:
public enum TextType { SCORE, GAMEOVER };
public class GameText : MonoBehaviour
{
private IGameState states;
private int score = 0;
private TextType textType = TextType.SCORE;
void Start()
{
states = SceneController.getInstance() as IGameState;
checkTheTypeOfText();
}
private void checkTheTypeOfText()
{
if (gameObject.name.Contains("Score"))
{
textType = TextType.SCORE;
}
else
{
textType = TextType.GAMEOVER;
}
}
void OnEnable()
{
GameEventManager.myGameScoreAction += gameScore;
GameEventManager.myGameOverAction += gameOver;
}
void OnDisable()
{
GameEventManager.myGameScoreAction -= gameScore;
GameEventManager.myGameOverAction -= gameOver;
}
//得分事件
void gameScore()
{
if (textType == TextType.SCORE)
{
int index = states.getIndexOfHero();
switch(index)
{
case 0:
score += 1;
break;
case 1:
score += 2;
break;
case 2:
score += 3;
break;
case 3:
score += 4;
break;
case 4:
score += 5;
break;
case 5:
score += 6;
break;
}
//score++;
this.gameObject.GetComponent<Text>().text = "Score: " + score;
}
}
//游戏结束事件
void gameOver()
{
if (textType == TextType.GAMEOVER)
{
this.gameObject.GetComponent<Text>().text = "Game Over!";
}
}
}
检测英雄状态和颜色改变的代码:
public class HeroState : MonoBehaviour
{
public int indexOfHero = -1;
void Update()
{
getIndexOfHero();
colorChange();
}
//检测巡逻兵所在的位置,总共有6个位置
void getIndexOfHero()
{
float posX = this.gameObject.transform.position.x;
float posZ = this.gameObject.transform.position.z;
if (posZ >= FenchLocation.FenchHori)
{
if (posX < FenchLocation.FenchVertLeft)
indexOfHero = 0;
else if (posX > FenchLocation.FenchVertRight)
indexOfHero = 2;
else
indexOfHero = 1;
}
else
{
if (posX < FenchLocation.FenchVertLeft)
indexOfHero = 3;
else if (posX > FenchLocation.FenchVertRight)
indexOfHero = 5;
else
indexOfHero = 4;
}
}
public void colorChange()
{
switch (indexOfHero)
{
case 0:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Black");
break;
case 1:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Blue");
break;
case 2:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Gray");
break;
case 3:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Pink");
break;
case 4:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Red");
break;
case 5:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Yellow");
break;
default:
this.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Red");
break;
}
}
}
工厂模式,生成地图上的6个巡逻兵:
public class PatrolFactory : System.Object
{
private static PatrolFactory instance;
private GameObject PatrolItem;
//初始化6个巡逻兵的位置
private Vector3[] PatrolPosition = new Vector3[] {
new Vector3(-4, 0, 16), new Vector3(-1, 0, 16),
new Vector3(6, 0, 16), new Vector3(-4, 0, 7),
new Vector3(0, 0, 7), new Vector3(6, 0, 7)
};
public static PatrolFactory getInstance()
{
if (instance == null)
instance = new PatrolFactory();
return instance;
}
public void initItem(GameObject _PatrolItem)
{
PatrolItem = _PatrolItem;
}
public GameObject getPatrol()
{
GameObject newPatrol = Camera.Instantiate(PatrolItem);
return newPatrol;
}
public Vector3[] getPosition()
{
return PatrolPosition;
}
}
用户控制英雄的游戏接口
public class UserInterface : MonoBehaviour
{
private IUserAction action;
void Start()
{
action = SceneController.getInstance() as IUserAction;
}
void Update()
{
detectKeyInput();
}
void detectKeyInput()
{
if (Input.GetKey(KeyCode.UpArrow))
{
action.moveAction(Diretion.UP);
}
if (Input.GetKey(KeyCode.DownArrow))
{
action.moveAction(Diretion.DOWN);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
action.moveAction(Diretion.LEFT);
}
if (Input.GetKey(KeyCode.RightArrow))
{
action.moveAction(Diretion.RIGHT);
}
}
}
巡逻兵的行为代码,主要是悬挂在巡逻兵上面的
public class PatrolBehaviour : MonoBehaviour
{
private IAddAction action;
private IGameState states;
public int IndexOfPatrol;
private bool isCatching;
void Start()
{
action = SceneController.getInstance() as IAddAction;
states = SceneController.getInstance() as IGameState;
IndexOfPatrol = this.gameObject.name[this.gameObject.name.Length - 1] - '0';
isCatching = false;
}
void Update()
{
if (states.getIndexOfHero() == IndexOfPatrol)
{
if (!isCatching)
{
changeState();
action.addDirectMovement(this.gameObject);
}
}
else
{
if (isCatching)
{
states.getScore();
changeState();
action.addRandomMovement(this.gameObject, false);
}
}
}
void OnCollisionStay(Collision e)
{
if (e.gameObject.name.Contains("Patrol") || e.gameObject.name.Contains("fence")
|| e.gameObject.tag.Contains("FenceAround"))
{
isCatching = false;
action.addRandomMovement(this.gameObject, false);
}
if (e.gameObject.name.Contains("Hero"))
{
//e.gameObject.GetComponent<MeshRenderer>().material = Resources.Load<Material>("Blue");
states.gameOver();
}
}
public void changeState()
{
if (isCatching)
{
isCatching = false;
} else
{
isCatching = true;
}
}
}
场景控制器的代码:
public class SceneController : System.Object, IUserAction, IAddAction, IGameState
{
private static SceneController instance;
public FirstSceneController scene;
public GameEventManager gameEventManager;
public static SceneController getInstance()
{
if (instance == null)
{
instance = new SceneController();
}
return instance;
}
/*-------------------------IUserAction-----------------------*/
public void moveAction(int dir)
{
scene.moveAction(dir);
}
/*-------------------------IUserAction-----------------------*/
/*---------------------------IAddAction-----------------------*/
public void addRandomMovement(GameObject sourceObj, bool isActive)
{
scene.addRandomMovement(sourceObj, isActive);
}
public void addDirectMovement(GameObject sourceObj)
{
scene.addDirectMovement(sourceObj);
}
/*---------------------------IAddAction-----------------------*/
/*---------------------------IGameState-----------------------*/
public int getIndexOfHero()
{
return scene.getIndexOfHero();
}
public void getScore()
{
gameEventManager.getScore();
}
public void gameOver()
{
gameEventManager.gameOver();
}
public bool getGameState()
{
return gameEventManager.getGameState();
}
/*---------------------------IGameState-----------------------*/
}
主要场景,在里面实现了巡逻兵的两种动作,即巡逻动作和追捕动作,其中巡逻动作的速度在不同的层里面是不一样的
public class FirstSceneController : SSActionManager, ISSActionCallback
{
public GameObject PatrolItem, HeroItem, sceneModelItem, canvasItem;
private SceneController currentSceneController;
private GameObject Hero, sceneModel, canvasAndText;
private List<GameObject> Patrolmans;
private List<int> PatrolLastDir;
GUIStyle style;
void Awake()
{
PatrolFactory.getInstance().initItem(PatrolItem);
}
protected new void Start()
{
currentSceneController = SceneController.getInstance();
currentSceneController.scene = this;
InitChcaracters();
style = new GUIStyle();
style.fontSize = 40;
style.alignment = TextAnchor.MiddleCenter;
}
protected new void Update()
{
base.Update();
}
void OnGUI()
{
GUI.Label(new Rect(Screen.width / 2 + 500, Screen.height / 4, 180, 110), "Rule:\nRegion1: 1 point\nRegion2: 2 points\nRegion3: 3 points" +
"\nRegion4: 4 points\nRegion5: 5 points\nRegion6: 6 points", style);
}
private void InitChcaracters()
{
//英雄
Hero = Instantiate(HeroItem);
//巡逻兵
PatrolFactory factory = PatrolFactory.getInstance();
Patrolmans = new List<GameObject>(6);
PatrolLastDir = new List<int>(6);
Vector3[] position = factory.getPosition();
for (int i = 0; i < 6; i++)
{
GameObject newPatrol = factory.getPatrol();
newPatrol.transform.position = position[i];
newPatrol.name = "Patrol" + i;
PatrolLastDir.Add(0);
Patrolmans.Add(newPatrol);
addRandomMovement(newPatrol, true);
}
//游戏场景
sceneModel = Instantiate(sceneModelItem);
//记录版
canvasAndText = Instantiate(canvasItem);
}
//英雄移动的动作
public void moveAction(int dir)
{
Hero.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
switch (dir)
{
case Diretion.UP:
Hero.transform.position += new Vector3(0, 0, 0.1f);
break;
case Diretion.DOWN:
Hero.transform.position += new Vector3(0, 0, -0.1f);
break;
case Diretion.LEFT:
Hero.transform.position += new Vector3(-0.1f, 0, 0);
break;
case Diretion.RIGHT:
Hero.transform.position += new Vector3(0.1f, 0, 0);
break;
}
}
//动作回调函数
public void SSActionEvent(SSAction source, SSActionEventType eventType = SSActionEventType.Completed,
SSActionTargetType intParam = SSActionTargetType.Patroling, string strParam = null, object objParam = null)
{
if (intParam == SSActionTargetType.Patroling)
addRandomMovement(source.gameObject, true);
else
addDirectMovement(source.gameObject);
}
//判定巡逻兵走出了自己的区域
bool PatrolOutOfArea(int index, int randomDir)
{
bool isPatrolOutOfArea = false;
Vector3 patrolPos = Patrolmans[index].transform.position;
float posX = patrolPos.x;
float posZ = patrolPos.z;
switch (index)
{
case 0:
if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft
|| randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
isPatrolOutOfArea = true;
break;
case 1:
if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight
|| randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft
|| randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
isPatrolOutOfArea = true;
break;
case 2:
if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight
|| randomDir == 2 && posZ - 1 < FenchLocation.FenchHori)
isPatrolOutOfArea = true;
break;
case 3:
if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft
|| randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
isPatrolOutOfArea = true;
break;
case 4:
if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight
|| randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft
|| randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
isPatrolOutOfArea = true;
break;
case 5:
if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight
|| randomDir == 0 && posZ + 1 > FenchLocation.FenchHori)
isPatrolOutOfArea = true;
break;
}
return isPatrolOutOfArea;
}
public int getIndexOfHero()
{
return Hero.GetComponent<HeroState>().indexOfHero;
}
/*--------------------------------------------IAddAction----------------------------------------------*/
//巡逻兵的追捕动作方法
public void addDirectMovement(GameObject sourceObj)
{
int index = sourceObj.name[sourceObj.name.Length - 1] - '0';
PatrolLastDir[index] = -2;
sourceObj.transform.LookAt(sourceObj.transform);
Vector3 oriTarget = Hero.transform.position - sourceObj.transform.position;
Vector3 target = new Vector3(oriTarget.x / 4.0f, 0, oriTarget.z / 4.0f);
target += sourceObj.transform.position;
//添加动作参数,执行动作
this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, 0.08f, true), this);
}
//巡逻兵执行巡逻的方法,获得一个随机的运动方向,isActive说明是否主动变向(动作结束)
public void addRandomMovement(GameObject sourceObj, bool isActive)
{
int index = sourceObj.name[sourceObj.name.Length - 1] - '0';
//给巡逻兵获得一个随机方向
int randomDir = Random.Range(-1, 3);
if (!isActive)
{
while (PatrolLastDir[index] == randomDir || PatrolOutOfArea(index, randomDir))
{
randomDir = Random.Range(-1, 3);
}
}
else
{
while (PatrolLastDir[index] == 0 && randomDir == 2
|| PatrolLastDir[index] == 2 && randomDir == 0
|| PatrolLastDir[index] == 1 && randomDir == -1
|| PatrolLastDir[index] == -1 && randomDir == 1
|| PatrolOutOfArea(index, randomDir))
{
randomDir = Random.Range(-1, 3);
}
}
PatrolLastDir[index] = randomDir;
sourceObj.transform.rotation = Quaternion.Euler(new Vector3(0, randomDir * 90, 0));
Vector3 target = sourceObj.transform.position;
switch (randomDir)
{
case Diretion.UP:
target += new Vector3(0, 0, 1);
break;
case Diretion.DOWN:
target += new Vector3(0, 0, -1);
break;
case Diretion.LEFT:
target += new Vector3(-1, 0, 0);
break;
case Diretion.RIGHT:
target += new Vector3(1, 0, 0);
break;
}
//添加动作参数,执行动作
//在不同的区域内的随即运动速度是不一样的
float speed = 0;
switch (index)
{
case 0:
speed += 0.03f;
break;
case 1:
speed += 0.04f;
break;
case 2:
speed += 0.05f;
break;
case 3:
speed += 0.06f;
break;
case 4:
speed += 0.07f;
break;
case 5:
speed += 0.08f;
break;
}
this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, speed, false), this);
}
/*--------------------------------------------IAddAction----------------------------------------------*/
}
以上所展示的都只是部分代码而已,具体的代码可以转到我的Github上面下载查看,这次作业的难度还是非常大的,很多东西基本懵逼,而且还是对着师兄的代码来写的,感觉自己在写游戏的过程中基本上在搬砖,做了非常长的时间,基本上也就看明白了代码吧,可能平时花在3d上面的时间还是比较少的,但是我自己尽量得学习一下游戏设计过程中的设计模式和一些UML图的设计,总得来说,还是能够有所收获。
最后附上一张效果图: