智能巡邏兵
提交要求:
遊戲設計要求:
- 創建一個地圖和若干巡邏兵(使用動畫);
- 每個巡邏兵走一個3~5個邊的凸多邊型,位置數據是相對地址。即每次確定下一個目標位置,用自己當前位置爲原點計算;
- 巡邏兵碰撞到障礙物,則會自動選下一個點爲目標;
- 巡邏兵在設定範圍內感知到玩家,會自動追擊玩家;
- 失去玩家目標後,繼續巡邏;
- 計分:玩家每次甩掉一個巡邏兵計一分,與巡邏兵碰撞遊戲結束;
程序設計要求:
-
必須使用訂閱與發佈模式傳消息
-
subject:OnLostGoal
-
Publisher: ?
-
Subscriber: ?
工廠模式生產巡邏兵 -
友善提示1:生成 3~5個邊的凸多邊型
隨機生成矩形 -
在矩形每個邊上隨機找點,可得到 3 - 4 的凸多邊型
5 ? -
友善提示2:參考以前博客,給出自己新玩法
參考UML類圖:
訂閱與發佈模式
在“發佈者-訂閱者”模式中,稱爲發佈者的消息發送者不會將消息編程爲直接發送給稱爲訂閱者的特定接收者。這意味着發佈者和訂閱者不知道彼此的存在。存在第三個組件,稱爲代理或消息代理或事件總線,它由發佈者和訂閱者都知道,它過濾所有傳入的消息並相應地分發它們。換句話說,它是用於在不同系統組件之間傳遞消息的模式,而這些組件不知道關於彼此身份的任何信息。
項目結構:
主要預製體及設置:
Hero:
Patrol:
關鍵模塊
Actions
實現動作的管理,與之前幾次的作業類似。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameObject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callBack { get; set; }
protected SSAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
public enum SSActionEventType : int { Started, Completed }
public enum SSActionTargetType : int { Normal, Catching }
public interface ISSActionCallback
{
void SSActionEvent(SSAction source,
SSActionEventType eventType = SSActionEventType.Completed,
SSActionTargetType intParam = SSActionTargetType.Normal,
string strParam = null,
Object objParam = null);
}
public class CCSequeneActions : SSAction, ISSActionCallback
{
public List<SSAction> actionList;
public int repeatTimes = -1;
public int subActionIndex = 0;
public static CCSequeneActions CreateSSAction(List<SSAction> _actionList, int _repeatTimes = 0)
{
CCSequeneActions action = ScriptableObject.CreateInstance<CCSequeneActions>();
action.repeatTimes = _repeatTimes;
action.actionList = _actionList;
return action;
}
public override void Start()
{
foreach (SSAction action in actionList)
{
action.gameObject = this.gameObject;
action.transform = this.transform;
action.callBack = this;
action.Start();
}
}
public override void Update()
{
if (actionList.Count == 0)
return;
else if (subActionIndex < actionList.Count)
{
actionList[subActionIndex].Update();
}
}
public void SSActionEvent(SSAction source,
SSActionEventType eventType = SSActionEventType.Completed,
SSActionTargetType intParam = SSActionTargetType.Normal,
string strParam = null, Object objParam = null)
{
source.destroy = false;
this.subActionIndex++;
if (this.subActionIndex >= actionList.Count)
{
this.subActionIndex = 0;
if (repeatTimes > 0)
repeatTimes--;
if (repeatTimes == 0)
{
this.destroy = true;
this.callBack.SSActionEvent(this);
}
}
}
void OnDestroy()
{
}
}
public class CCMoveToAction : SSAction
{
public Vector3 target;
public float speed;
public bool isCatching;
public static CCMoveToAction CreateSSAction(Vector3 _target, float _speed, bool _isCatching)
{
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
action.target = _target;
action.speed = _speed;
action.isCatching = _isCatching;
return action;
}
public override void Update()
{
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed);
if (this.transform.position == target)
{
this.destroy = true;
if (!isCatching)
this.callBack.SSActionEvent(this);
else
this.callBack.SSActionEvent(this, SSActionEventType.Completed, SSActionTargetType.Catching);
}
}
}
public class SSActionManager : MonoBehaviour
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingAdd = new List<SSAction>();
private List<int> waitingDelete = new List<int>();
protected void Update()
{
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
waitingDelete.Add(kv.Key);
else if (ac.enable)
ac.Update();
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
public void runAction(GameObject gameObj, SSAction action, ISSActionCallback manager)
{
for (int i = 0; i < waitingAdd.Count; i++) //destroy actions
{
if (waitingAdd[i].gameObject.Equals(gameObj))
{
SSAction ac = waitingAdd[i];
waitingAdd.RemoveAt(i);
i--;
DestroyObject(ac);
}
}
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.gameObject.Equals(gameObj))
{
ac.destroy = true;
}
}
action.gameObject = gameObj;
action.transform = gameObj.transform;
action.callBack = manager;
waitingAdd.Add(action);
action.Start();
}
}
PartolFactory
巡邏兵工廠,負責生產巡邏兵,初始化位置等屬性。
public class PatrolFactory : System.Object {
private static PatrolFactory instance;
private GameObject PatrolItem;
private Vector3[] PatrolPosSet = new Vector3[] { new Vector3(-6, 0, 16), new Vector3(-1, 0, 19), //set patrol's position
new Vector3(6, 0, 16), new Vector3(-5, 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[] getPosSet() {
return PatrolPosSet;
}
}
PatrolController
實現對巡邏兵行爲的控制,檢測附近的Hero位置,對碰撞事件做出相應的響應。當碰撞對象是巡邏兵或圍欄時不進行抓捕,當對象爲Hero時進行抓捕。
private IAddAction addAction;
private IStatusOptions op;
public int ownIndex;
public bool isCatching; //if sense the hero
private float range = 3.0f;
void Start () {
addAction = SceneController.getInstance() as IAddAction;
op = SceneController.getInstance() as IStatusOptions;
ownIndex = getOwnIndex();
isCatching = false;
}
void Update () {
checkNearByHero();
}
int getOwnIndex() { //get self index
string name = this.gameObject.name;
char cindex = name[name.Length - 1];
int result = cindex - '0';
return result;
}
void checkNearByHero () { //check if the hero is into it's range
if (op.getHeroArea() == ownIndex) { //step into the range
if (!isCatching) {
isCatching = true;
addAction.addDirectMovement(this.gameObject);
}
}
else {
if (isCatching) { //stop catching
op.heroScore();
isCatching = false;
addAction.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;
addAction.addRandomMovement(this.gameObject, false);
}
if (e.gameObject.name.Contains("Hero")) {
op.caughtHero();
}
}
HeroController
主要實現了對Hero所在區域的判斷。通過對比Hero位置與欄杆位置來判斷Hero所在區域,並以此來觸發巡邏兵的捕捉事件。
void modifyStandOnArea() {
float posX = this.gameObject.transform.position.x;
float posZ = this.gameObject.transform.position.z;
if (posZ >= RangeLimit.horiLimit) {
if (posX < RangeLimit.leftLimit)
standOnArea = 0;
else if (posX > RangeLimit.rightLimit)
standOnArea = 2;
else
standOnArea = 1;
}
else {
if (posX < RangeLimit.leftLimit)
standOnArea = 3;
else if (posX > RangeLimit.rightLimit)
standOnArea = 5;
else
standOnArea = 4;
}
}
Interface
系統需要用到的一些接口。
public interface IUserAction
{
void heroMove(int dir);
}
public interface IAddAction
{
void addRandomMovement(GameObject sourceObj, bool isActive);
void addDirectMovement(GameObject sourceObj);
}
public interface IStatusOptions
{
int getHeroArea();
void heroScore();
void caughtHero();
}
SceneController
實現對場景的控制。主要實現上面的接口函數。並實現幾個getter和setter函數,與GameController做鏈接。
public class Diretion {
public const int UP = 0;
public const int DOWN = 2;
public const int LEFT = -1;
public const int RIGHT = 1;
}
public class RangeLimit {
public const float horiLimit = 12.42f;
public const float leftLimit = -3.0f;
public const float rightLimit = 3.0f;
}
public class SceneController : System.Object, IUserAction, IAddAction, IStatusOptions {
private static SceneController instance;
private GameModel myGameModel;
private GameController myGameController;
public void heroMove(int dir) { myGameModel.heroMove(dir); }
public void addRandomMovement(GameObject sourceObj, bool isActive) { myGameModel.addRandomMovement(sourceObj, isActive); }
public void addDirectMovement(GameObject sourceObj) { myGameModel.addDirectMovement(sourceObj); }
public int getHeroArea() { return myGameModel.getHeroArea(); }
public void heroScore() { myGameController.heroScore(); }
public void caughtHero() { myGameController.caughtHero(); }
public static SceneController getInstance() {
if (instance == null)
instance = new SceneController();
return instance;
}
internal void setGameModel(GameModel _myGameModel) {
if (myGameModel == null) {
myGameModel = _myGameModel;
}
}
internal void setGameController(GameController _myGameController) {
if (myGameController == null) {
myGameController = _myGameController;
}
}
}
GamController
加載場景,通過調用接口函數實現Hero的得分和Hero的被捕事件。
public delegate void GameScoreAction();
public static event GameScoreAction myGameScoreAction;
public delegate void GameOverAction();
public static event GameOverAction myGameOverAction;
private SceneController scene;
void Start () {
scene = SceneController.getInstance();
scene.setGameController(this);
}
public void heroScore() {
if (myGameScoreAction != null)
myGameScoreAction();
}
public void caughtHero() {
if (myGameOverAction != null)
myGameOverAction();
}
GameModel
實現對遊戲對象的加載和動作實現。用list來存放所有的巡邏兵。定義了Hero的四方向基本移動操作以及組合移動操作,同時對巡邏兵巡邏範圍進行了判斷,當超出巡邏範圍則不再抓捕。
private List<GameObject> patrolList;
private List<int> patrolRemainList;
private const float normalSpeed = 0.03f;
private const float catchSpeed = 0.05f;
public GameObject PatrolItem, HeroItem, sceneModelItem, canvasItem;
private SceneController scene;
private GameObject myHero, sceneModel, canvasAndText;
public void SSActionEvent(SSAction source, //ssActionCallBack interface
SSActionEventType eventType = SSActionEventType.Completed,
SSActionTargetType intParam = SSActionTargetType.Normal,
string strParam = null, Object objParam = null)
{
if (intParam == SSActionTargetType.Normal)
addRandomMovement(source.gameObject, true);
else
addDirectMovement(source.gameObject);
}
void Awake() {
PatrolFactory.getInstance().initItem(PatrolItem);
}
protected new void Start () {
scene = SceneController.getInstance();
scene.setGameModel(this);
genHero();
genPatrols();
sceneModel = Instantiate(sceneModelItem);
canvasAndText = Instantiate(canvasItem);
}
protected new void Update() {
base.Update();
}
void genHero() { //get hero
myHero = Instantiate(HeroItem);
}
void genPatrols() { //get patrols
patrolList = new List<GameObject>(6);
patrolRemainList = new List<int>(6);
Vector3[] posSet = PatrolFactory.getInstance().getPosSet();
for (int i = 0; i < 6; i++) {
GameObject newPatrol = PatrolFactory.getInstance().getPatrol();
newPatrol.transform.position = posSet[i];
newPatrol.name = "Patrol" + i;
patrolRemainList.Add(-2);
patrolList.Add(newPatrol);
addRandomMovement(newPatrol, true);
}
}
public void heroMove(int dir) { //hero movement
myHero.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
switch (dir) {
case Diretion.UP:
myHero.transform.position += new Vector3(0, 0, 0.1f);
break;
case Diretion.DOWN:
myHero.transform.position += new Vector3(0, 0, -0.1f);
break;
case Diretion.LEFT:
myHero.transform.position += new Vector3(-0.1f, 0, 0);
break;
case Diretion.RIGHT:
myHero.transform.position += new Vector3(0.1f, 0, 0);
break;
}
}
public void addRandomMovement(GameObject sourceObj, bool isActive) {
int index = getIndex(sourceObj);
int dir = getdirection(index, isActive);
patrolRemainList[index] = dir;
sourceObj.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
Vector3 target = sourceObj.transform.position;
switch (dir) {
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;
}
addSingleMoving(sourceObj, target, normalSpeed, false);
}
int getIndex(GameObject sourceObj) {
string name = sourceObj.name;
char cindex = name[name.Length - 1];
int result = cindex - '0';
return result;
}
int getdirection(int index, bool isActive) {
int dir = Random.Range(-1, 3);
if (!isActive) { //when hit
while (patrolRemainList[index] == dir || isOutOfRange(index, dir)) {
dir = Random.Range(-1, 3);
}
}
else { //when not hit
while (patrolRemainList[index] == 0 && dir == 2
|| patrolRemainList[index] == 2 && dir == 0
|| patrolRemainList[index] == 1 && dir == -1
|| patrolRemainList[index] == -1 && dir == 1
|| isOutOfRange(index, dir)) {
dir = Random.Range(-1, 3);
}
}
return dir;
}
//judge if patrol out range
bool isOutOfRange(int index, int dir) {
Vector3 patrolPos = patrolList[index].transform.position;
float posX = patrolPos.x;
float posZ = patrolPos.z;
switch (index) {
case 0:
if (dir == 1 && posX + 1 > RangeLimit.leftLimit
|| dir == 2 && posZ - 1 < RangeLimit.horiLimit)
return true;
break;
case 1:
if (dir == 1 && posX + 1 > RangeLimit.rightLimit
|| dir == -1 && posX - 1 < RangeLimit.leftLimit
|| dir == 2 && posZ - 1 < RangeLimit.horiLimit)
return true;
break;
case 2:
if (dir == -1 && posX - 1 < RangeLimit.rightLimit
|| dir == 2 && posZ - 1 < RangeLimit.horiLimit)
return true;
break;
case 3:
if (dir == 1 && posX + 1 > RangeLimit.leftLimit
|| dir == 0 && posZ + 1 > RangeLimit.horiLimit)
return true;
break;
case 4:
if (dir == 1 && posX + 1 > RangeLimit.rightLimit
|| dir == -1 && posX - 1 < RangeLimit.leftLimit
|| dir == 0 && posZ + 1 > RangeLimit.horiLimit)
return true;
break;
case 5:
if (dir == -1 && posX - 1 < RangeLimit.rightLimit
|| dir == 0 && posZ + 1 > RangeLimit.horiLimit)
return true;
break;
}
return false;
}
public void addDirectMovement(GameObject sourceObj) { //catch movement
int index = getIndex(sourceObj);
patrolRemainList[index] = -2;
sourceObj.transform.LookAt(sourceObj.transform);
Vector3 oriTarget = myHero.transform.position - sourceObj.transform.position; //get original target
Vector3 target = new Vector3(oriTarget.x / 4.0f, 0, oriTarget.z / 4.0f);
target += sourceObj.transform.position;
addSingleMoving(sourceObj, target, catchSpeed, true);
}
void addSingleMoving(GameObject sourceObj, Vector3 target, float speed, bool isCatching) {
this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, speed, isCatching), this);
}
void addCombinedMoving(GameObject sourceObj, Vector3[] target, float[] speed, bool isCatching) {
List<SSAction> acList = new List<SSAction>();
for (int i = 0; i < target.Length; i++) {
acList.Add(CCMoveToAction.CreateSSAction(target[i], speed[i], isCatching));
}
CCSequeneActions MoveSeq = CCSequeneActions.CreateSSAction(acList);
this.runAction(sourceObj, MoveSeq, this);
}
public int getHeroArea() { //get hero area
return myHero.GetComponent<HeroController>().standOnArea;
}
運行結果: