Unity 3D遊戲開發學習筆記(2) 牧師與魔鬼
遊戲事物:
3牧師,3惡魔,2河岸,河,船。
遊戲故事:3牧師和3惡魔需要用一艘船全部到達彼岸,但是船上和岸上都不能出現惡魔比牧師多的情形,否則惡魔會把牧師K.O,玩家輸掉比賽;直到所有牧師惡魔都到達對岸,玩家取得勝利。
MVC架構:
IUserAction:是個介面,定義了行為的型別。
UserGUI:建立GUI物件,實現玩家互動,處理玩家操作,並通過IUserAction介面實現具體行為。
ISceneController:是個介面,定義了載入資源的方法。
SSDirector:是個單例項物件,不受Unity記憶體管理,只有返回例項化物件的方法,也聲明瞭一個ISceneController方便各個介面呼叫。
FirstController:
規則表(列出玩家動作的表)
指令碼和預製
遊戲介面:(沒有優化UI 有點吃藕)
遊戲初始狀態,左上方是所有操作按鈕,分別對應牧師上下船,魔鬼上下船,由預製可知,方塊是牧師,球是魔鬼:
上船:
GO:
(至於顏色為什麼不一樣的了,場景用的是實時光,要烘焙之後,重新載入才會保留之前的效果,我這裡沒有進行烘焙,並且這是我玩了好幾次不斷重新載入後再截的圖,重新載入的意思是我遊戲寫的restart()呼叫的重新載入場景函式)
下船:
勝利:
(隱藏了之前的所有操作按鈕,只顯示Win!提示和Restart!按鈕,點選Win!不會發生任何動作,點選Restart!會重新載入場景,遊戲可以重新進行,如前文所講,沒有烘培,光照變了)
輸掉比賽:
按鈕設定與【勝利】類似
實現思路:
使用棧進行儲存兩個河岸的牧師和惡魔。比如A岸有3個牧師就push3個牧師進棧A,當牧師上傳,則pop掉1個牧師,並把這個牧師的父物件設為船,位置變成相對位置後,能實現在船上跟著船移動。
實現程式碼:
FirstController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
SSDirector my;
public State state = State.BSTART;
public enum State { BSTART, BSEMOVING, BESMOVING, BEND, WIN, LOSE };
/* 借鑑別人的想法,進行列舉,列出一共6種狀態,實際上也可以用123456表示,但這裡可讀性強。
* BSTART: boat stops on start shore
* BEND: boat stops on end shore
* BSEMOVING: boat is moving from start shore to end shore
* BESMOVING: boat is moving from end shore to start shore
* WIN: win
* LOSE: lose
*/
GameObject[] boat = new GameObject[2]; // 船的容量是2,並且每個容量都是一個物件,用來儲存實際的物件
GameObject boat_obj; // 船本身
Stack<GameObject> priests_start = new Stack<GameObject>();
Stack<GameObject> priests_end = new Stack<GameObject>();
Stack<GameObject> devils_start = new Stack<GameObject>(); //
Stack<GameObject> devils_end = new Stack<GameObject>();
/*
使用棧進行儲存兩個河岸的牧師和惡魔。比如A岸有3個牧師就push3個牧師進棧A,
當牧師上傳,則pop掉1個牧師,並把這個牧師的父物件設為船,位置變成相對位置
後,能實現在船上跟著船移動。
*/
float gap = 1f; // 牧師惡魔每個物件的間隔,不加f Unity編譯不過
Vector3 priestStartPos = new Vector3(-8f, 0, 0);//以下均為預設位置
Vector3 priestEndPos = new Vector3(8f, 0, 0);
Vector3 devilStartPos = new Vector3(-5f, 0, 0);
Vector3 devilEndPos = new Vector3(5f, 0, 0);
Vector3 boatStartPos = new Vector3(-1.5f, 0, 0);
Vector3 boatEndPos = new Vector3(1.5f, 0, 0);
public float speed = 20; // 預設速度
void Awake() {
SSDirector director = SSDirector.getInstance();
director.currentSceneController = this; // 設定“場記”,和“導演”聯絡在一起
director.currentSceneController.LoadResources();
}
public void LoadResources () {
GameObject myGame = Instantiate<GameObject> (Resources.Load<GameObject> ("prefabs/main"),
Vector3.zero, Quaternion.identity);
myGame.name = "main";
//Debug.Log("load main...");
boat_obj = Instantiate(Resources.Load("prefabs/Boat"), new Vector3(-1.5f, 0f, 0f), Quaternion.identity) as GameObject;
for(int i = 0; i < 3; i++) {
priests_start.Push(Instantiate(Resources.Load("prefabs/Priest")) as GameObject); //加入棧中
devils_start.Push(Instantiate(Resources.Load("prefabs/devil")) as GameObject);
}
}
int boatCapacity() { // 檢測船是否有容量
int capacity = 0;
for (int i = 0; i < 2; i++) {
if(boat[i] == null) capacity++;
}
return capacity;
}
void setCharacterPositions(Stack<GameObject> stack, Vector3 pos) { //設定牧師惡魔的具體位置,這方法厲害。。
GameObject[] array = stack.ToArray();
for (int i = 0; i < stack.Count; ++i) {
array[i].transform.position = new Vector3(pos.x + gap*i, pos.y, pos.z);
}
}
/*
這裡下面是具體的動作規則實現,具體和我文件裡規則表可以對的上
*/
public void priestOnBoat() { // 牧師上船,這個是沒引數的,方便UserGUI呼叫。
if(priests_start.Count != 0 && boatCapacity() != 0 && this.state == State.BSTART) //船在左岸,有牧師在岸上,船有位置
priestOnBoat_(priests_start.Pop());
if(priests_end.Count != 0 && boatCapacity() != 0 && this.state == State.BEND)
priestOnBoat_(priests_end.Pop());
}
public void priestOnBoat_(GameObject obj) {
if (boatCapacity() != 0) {
obj.transform.parent = boat_obj.transform;
if (boat[0] == null) {
boat[0] = obj;
obj.transform.localPosition = new Vector3(-0.2f, 1.2f, 0f);
} else {
boat[1] = obj;
obj.transform.localPosition = new Vector3(0.2f, 1.2f, 0f);
}
}
}
public void priestOffBoat() {
for (int i = 0; i < 2; i++) {
if (boat[i] != null) {
if (this.state == State.BEND) {
if (boat[i].name == "Priest(Clone)") {
priests_end.Push(boat[i]);
boat[i].transform.parent = null;
boat[i] = null;
break;
}
}
else if (this.state == State.BSTART) {
if (boat[i].name == "Priest(Clone)") {
priests_start.Push(boat[i]);
boat[i].transform.parent = null;
boat[i] = null;
break;
}
}
}
}
}
public void devilOnBoat() {
if(devils_start.Count != 0 && boatCapacity() != 0 && this.state == State.BSTART)
devilOnBoat_(devils_start.Pop());
if(devils_end.Count != 0 && boatCapacity() != 0 && this.state == State.BEND)
devilOnBoat_(devils_end.Pop());
}
public void devilOnBoat_(GameObject obj) {
if (boatCapacity() != 0) {
obj.transform.parent = boat_obj.transform;
if (boat[0] == null) {
boat[0] = obj;
obj.transform.localPosition = new Vector3(-0.2f, 1.2f, 0f);
} else {
boat[1] = obj;
obj.transform.localPosition = new Vector3(0.2f, 1.2f, 0f);
}
}
}
public void devilOffBoat() {
for (int i = 0; i < 2; i++) {
if (boat[i] != null) {
if (this.state == State.BEND) {
if (boat[i].name == "Devil(Clone)") {
devils_end.Push(boat[i]);
boat[i].transform.parent = null;
boat[i] = null;
break;
}
}
else if (this.state == State.BSTART) {
if (boat[i].name == "Devil(Clone)") {
devils_start.Push(boat[i]);
boat[i].transform.parent = null;
boat[i] = null;
break;
}
}
}
}
}
public void moveBoat() {
if(boatCapacity() != 2) {
if(this.state == State.BSTART) {
this.state = State.BSEMOVING;
}
else if (this.state == State.BEND) {
this.state = State.BESMOVING;
}
}
}
public void restart() {
Application.LoadLevel (Application.loadedLevelName); // 重新載入
state = State.BSTART; // 預設引數
}
void check() { //檢查狀態是處於停靠還是運動或是勝利或是輸掉
int priests_s = 0, devils_s = 0, priests_e = 0, devils_e = 0;
int pOnBoat = 0, dOnBoat = 0;
if (priests_end.Count == 3 && devils_end.Count == 3) {
this.state = State.WIN;
return;
}
for (int i = 0; i < 2; ++i) {
if (boat[i] != null && boat[i].name == "Priest(Clone)") pOnBoat++;
else if (boat[i] != null && boat[i].name == "Devil(Clone)") dOnBoat++;
}
//Debug.Log(pOnBoat);
if (this.state == State.BSTART) {
priests_s = priests_start.Count + pOnBoat;
devils_s = devils_start.Count + dOnBoat;
priests_e = priests_end.Count;
devils_e = devils_end.Count;
}
else if (this.state == State.BEND) {
priests_s = priests_start.Count;
devils_s = devils_start.Count;
priests_e = priests_end.Count + pOnBoat;
devils_e = devils_end.Count + dOnBoat;
}
if ((priests_s != 0 && priests_s < devils_s) || (priests_e != 0 && priests_e < devils_e)) {
this.state = State.LOSE;
}
}
public bool isWin() { //贏了的話
if(this.state == State.WIN) return true;
return false;
}
public bool isLose() { //輸了的話
if(this.state == State.LOSE) return true;
return false;
}
void Start() {}
void Update() { //設定牧師和惡魔的位置,並且開船的時候實現船移動
setCharacterPositions(priests_start, priestStartPos);
setCharacterPositions(priests_end, priestEndPos);
setCharacterPositions(devils_start, devilStartPos);
setCharacterPositions(devils_end, devilEndPos);
if (this.state == State.BSEMOVING) {
boat_obj.transform.position = Vector3.MoveTowards(boat_obj.transform.position, boatEndPos, speed*Time.deltaTime);
if (boat_obj.transform.position == boatEndPos) {
this.state = State.BEND;
}
}
else if (this.state == State.BESMOVING) {
boat_obj.transform.position = Vector3.MoveTowards(boat_obj.transform.position, boatStartPos, speed*Time.deltaTime);
if (boat_obj.transform.position == boatStartPos) {
this.state = State.BSTART;
}
} else {
check();
}
}
}
ISceneController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController {
void LoadResources();
}
IUserAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction {
//void priestOnBoat(GameObject obj);
void priestOnBoat();
void priestOffBoat();
void devilOnBoat();
void devilOffBoat();
void moveBoat();
void restart();
bool isWin();
bool isLose();
}
SSDirector.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSDirector : System.Object {
private static SSDirector _instance;
public ISceneController currentSceneController {get; set;}
public static SSDirector getInstance() {
if (_instance == null) {
_instance = new SSDirector();
}
return _instance;
}
}
UserGUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour {
private IUserAction action;
void Start () {
action = SSDirector.getInstance().currentSceneController as IUserAction;
}
// Update is called once per frame
void OnGUI () {
float width = Screen.width / 6;
float height = Screen.height / 12;
if(action.isWin()) { //贏了的話
GUI.Button(new Rect(0,Screen.height-3f*height, Screen.width, height), "Win!");
if(GUI.Button(new Rect(0,Screen.height-height, Screen.width, height), "Restart!")) {
action.restart();
}
} else if(action.isLose()) { //輸了的話
GUI.Button(new Rect(0,Screen.height-3f*height, Screen.width, height), "Lose!");
if(GUI.Button(new Rect(0,Screen.height-height, Screen.width, height), "Restart!")) {
action.restart();
}
} else { //遊戲還在進行中
if(GUI.Button(new Rect(0, 0, width, height), "PriestOnBoat")) {
action.priestOnBoat();
}
if(GUI.Button(new Rect(0+width, 0, width, height), "PriestOffBoat")) {
action.priestOffBoat();
}
if(GUI.Button(new Rect(0, 0+height, width, height), "DevilOnBoat")) {
action.devilOnBoat();
}
if(GUI.Button(new Rect(0+width, 0+height, width, height), "DevilOffBoat")) {
action.devilOffBoat();
}
if(GUI.Button(new Rect(0+2*width, 0, width, height), "GO")) {
action.moveBoat();
}
}
}
}