Unity3D課程學習筆記(六)
阿新 • • 發佈:2018-12-20
智慧巡邏兵
- 提交要求:
- 遊戲設計要求:
- 建立一個地圖和若干巡邏兵(使用動畫);
- 每個巡邏兵走一個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圖的設計,總得來說,還是能夠有所收穫。
最後附上一張效果圖: