遊戲規則和背景我就不說了,首先我們要知道這個遊戲中出現了那些對象:魔鬼,牧師,兩個岸,一條河,一艘船。那麼每個對象有什麼行爲呢,如下
魔鬼
1.上下船
2.當數量大於牧師時,可以殺死牧師,遊戲結束
3.划船
牧師
1.上下船
2.划船
船
1.河岸間移動
至於岸,河就是個場景佈置,沒有什麼行爲。
在開始之前,首先我在Assets中創建了三個文件夾,Materials存放一些簡單素材,Resources存放資源,Scripts存放腳本,Resources中有一個Prefabs文件用於存儲預製。
行爲確定了,我們先把具體模型創建出來,用立方體來做岸和河,用球來作爲魔鬼和牧師,在做一些簡單的裝飾,得到這樣的效果(畢竟沒有藝術細胞)
我創造了五個類:岸,河,船,角色,上帝。河就只是簡單的生成,確定位置;岸記錄所在岸上的牧師數和魔鬼數;船有兩個位置;上帝判斷遊戲的輸贏,以及重來操作。它們的具體關係如圖
由於角色的操作會對岸,船都有影響,所以我把岸、船座位角色的一部分,一邊修改方便,同時,上帝判斷輸贏也需要對船、角色、岸進行控制,所以船、角色、岸又作爲了上帝的組成部分,這裏面存在岸、船被角色和上帝共同使用。
下面每個類具體說明
1.河流
這個不多說,直接上代碼
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 河流 : MonoBehaviour {
// Use this for initialization
void Start () {
transform.position = new Vector3(-2.81f, 3.0f, 0.0f);
}
// Update is called once per frame
void Update () {
}
}
2.岸
因爲有兩個岸,爲了區分,添加一個public int變量 num,可以在外部賦值,0爲起始岸,1爲終點岸。兩個int類型NumberOfPriests、NumberOfDevils很好理解。下面是函數解釋:
Initialization:初始化函數會把兩個岸移到相應位置,將起始岸的牧師、魔鬼數都設爲3
SetNumber:置數,兩個參數Which,add,Which表示給魔鬼還是牧師的數目進行修改,add是具體添加或減小多少
GetNumberOfPriests:返回牧師數
GetNumberOfDevils:返回惡魔數
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 岸 : MonoBehaviour {
public int num;
int NumberOfPriests;
int NumberOfDevils;
// Use this for initialization
void Start () {
Initialization();
}
public void Initialization()
{
if (num == 0)
{
transform.position = new Vector3(13.2f, 3.47f, 0.0f);
NumberOfDevils = 3;
NumberOfPriests = 3;
}
else
{
transform.position = new Vector3(-18.1f, 3.47f, 0.0f);
}
if (num == 1)
{
NumberOfPriests = 0;
NumberOfDevils = 0;
} else
{
NumberOfPriests = 3;
NumberOfDevils = 3;
}
}
public void SetNumber(int Which, int add)
{
if (Which == 0)
NumberOfDevils += add;
else
NumberOfPriests += add;
}
public int GetNumberOfPriests()
{
return NumberOfPriests;
}
public int GetNumberOfDevils()
{
return NumberOfDevils;
}
}
3.船
因爲船正在航行時,對其點擊要求無效,所以定義了一個IsMoving來確定運動狀態。同時用WhereToGo確定船運動的方向。sets數組顯示作爲情況,-1爲無人,0爲惡魔,1爲牧師,2爲暫時被佔用,可能是正在上下船,where表示船目前在哪。
函數解釋:
Initialization:對船的位置初始化,運動狀態設爲靜止,朝終點岸方向,位置在起始岸,並把所有座位置爲空
GetEmpty:當角色上船時被調用,找到當前爲空的位置並返回,沒有則返回-1
GetIsMoving:返回船的運動狀態
Set:對座位進行狀態修改,因爲角色上下船需要時間,這個時候若是設置船座位已爲空或者坐下了,鼠標點擊那麼船就直接跑了,因此這一段時間船不能移動,爲了區分這個狀態,設置2爲暫時被使用
Empty:判斷船是否爲空
GetNumberOfPriests:返回船上坐的牧師
GetNumberOfDevils:返回船上坐的惡魔
IAmBusy: 船若是暫時被佔用,則很忙,否則空閒
Idle:判斷船是否準備好,即船不空且每個座位都沒有被暫時使用
CanMove:判斷船是否可以出發了
OnMouseDown:鼠標點擊觸發,如果可以出發,就改變船的運動狀態
Stop:遊戲結束時所有物體都不能被操作,因此需要Stop
GetWhere:獲得船當前的位置
GetWhereToGo:獲得船的移動方向
Update:當船的運動狀態爲運動時,此時需要進行判斷,若運動位置已達到目標位置,則停止運動,並修改所在位置以及方向,否則繼續移動,這裏的移動我用了MoveTowards函數判斷是否已達目的地用了例如if(transform.position == new Vector3(-8.0f, 5.0f, 0.0f))
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 船 : MonoBehaviour {
int IsMoving; // 若船爲航行狀態,將使點擊無效
int WhereToGo; // 判斷船向爲起始岸還是終點岸
int[] sets = new int[2]; // 判斷座位是否爲空,2爲正在佔用狀態,不予執行操作
int where;
void Start()
{
Initialization();
}
public void Initialization()
{
transform.position = new Vector3(3, 5, 0);
IsMoving = 0; // 0爲未航行, 1爲正在航行
WhereToGo = 1; // 0爲朝起始岸,1爲朝終點岸
for (int i = 0; i < 2; i++)
sets[i] = -1; // -1表示座位爲空,0爲坐的是魔鬼,1坐的是牧師,2是正在被佔用
where = 0;
}
public int GetEmpty() // 哪個位置空就返回哪個位置,否則返回-1
{
for (int i = 0; i < 2; i++)
{
if (sets[i] == -1)
return i;
}
return -1;
}
public int GetIsMoving()
{
return IsMoving;
}
public void Set(int num, int value) // 修改座位情況
{
sets[num] = value;
}
public bool Empty()
{
for (int i = 0; i < 2; i++)
if (sets[i] != -1)
return false;
return true;
}
public int GetNumberOfPriests()
{
int num = 0;
for (int i = 0; i < 2; i++)
if (sets[i] == 1)
num++;
return num;
}
public bool IAmBusy()
{
for (int i = 0; i < 2; i++)
if (sets[i] == 2)
return true;
return false;
}
public int GetNumberOfDevils()
{
int num = 0;
for (int i = 0; i < 2; i++)
if (sets[i] == 0)
num++;
return num;
}
bool Idle() // 判斷船是否準備好了好
{
if (Empty())
return false;
for (int i = 0; i < 2; i++)
if (sets[i] == 2)
return false;
return true;
}
public bool CanMove()
{
if (IsMoving == 1 || !Idle() || IsMoving == 2)
return false;
return true;
}
void OnMouseDown()
{
if (!CanMove())
return;
IsMoving = 1;
}
public void Stop()
{
IsMoving = 2;
}
public int GetWhere()
{
return where;
}
public int GetWhereToGo()
{
return WhereToGo;
}
void Update()
{
if (IsMoving == 1)
{
if (WhereToGo == 1)
{
if (transform.position == new Vector3(-8.0f, 5.0f, 0.0f))
{
IsMoving = 0;
WhereToGo = 0;
where = 1;
} else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(-8.0f, 5.0f, 0.0f), 5 * Time.deltaTime);
}
} else
{
if (transform.position == new Vector3(3, 5, 0))
{
IsMoving = 0;
WhereToGo = 1;
where = 0;
} else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(3.0f, 5.0f, 0.0f), 5 * Time.deltaTime);
}
}
}
}
}
3.角色
因爲魔鬼與牧師基本相同,所以用num來給他們編號,同時用Ideneity來區分他們的身份。where表示角色的位置,角色上下船會改變船位置的狀態,所以船也應該爲角色的組成,同理,兩個岸也應該是角色的組成。角色的運動狀態用IsMoving表示,爲了確定角色是否在船上有參數IsInBoat。點擊角色,角色會有不同的動作,如靜止、起始岸上下船、終點岸上下船,爲了在Update函數中區分相應行爲,用action來表示不同行爲,WhichSet則表示角色上船坐的是哪個位置
Initialization:對位置初始化,一開始都在起始岸,靜止,不在船上,同時,需要對起始岸、終點岸進行初始化
Stop:遊戲結束,不能進行操作,所以對每個角色Stop
OnMouseDown:鼠標點擊,若是角色正在移動,則無效,否則根據角色所處位置,是否在船上修改相應的action,同時設置與角色相關的位置爲暫時佔用狀態。若是角色要上船,要看船是否有位置,若有則獲得一個空位給WhichSet
Update:首先由於我將船設爲了角色的組成部分,所以划船的時候會使船走了,角色還漂在水上。爲了解決這個問題,我只好修改當船正在移動時,角色也相應移動,做到相對靜止,這是划船行爲。上下船與船移動實質差不多,都是找一個目標位置開始移動。在上下船成功後,需要將船上相應位置狀態由2改爲相應狀態,同時將角色的action設爲0,運動狀態設爲靜止,修改所在地和是否在船上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 角色 : MonoBehaviour {
public int num;
public int Ideneity; // 1表示牧師,0表示惡魔
int where; // 0在起始岸,1在終點岸
public 船 boat;
public 岸 start, end;
int IsMoving;
bool IsInBoat; // 是否在船上
int action;
int WhichSet; // 絕對坐在哪個位置
// Use this for initialization
void Start () {
Initialization();
}
public void Initialization()
{
transform.position = new Vector3(4 + num * 2, 6.0f, 0);
where = 0;
IsMoving = 0;
IsInBoat = false;
action = 0;
WhichSet = -1;
start.Initialization();
end.Initialization();
}
public void Stop()
{
IsMoving = 2;
}
void OnMouseDown()
{
if (IsMoving == 1 || IsMoving == 2)
return;
if (!IsInBoat)
WhichSet = boat.GetEmpty();
if (boat.GetIsMoving() == 1 || IsMoving == 1) // 船或人物正在移動不能上下船
{
action = 0;
return;
}
if (where == 0 && IsInBoat) // 起始岸下船
{
IsMoving = 1;
boat.Set(WhichSet, 2);
action = 1;
start.SetNumber(Ideneity, 1);
}
if (where == 0 && !IsInBoat && boat.GetWhere() == 0) // 起始岸上船
{
if (WhichSet == -1) // 船滿了,上不了船
return;
action = 2;
boat.Set(WhichSet, 2);
IsMoving = 1;
start.SetNumber(Ideneity, -1);
}
if (where == 1 && IsInBoat) // 終點岸下船
{
IsMoving = 1;
boat.Set(WhichSet, 2);
action = 3;
end.SetNumber(Ideneity, 1);
}
if (where == 1 && !IsInBoat && boat.GetWhere() == 1) // 終點岸上船
{
if (WhichSet == -1) // 船滿了,上不了船
return;
action = 4;
boat.Set(WhichSet, 2);
IsMoving = 1;
end.SetNumber(Ideneity, -1);
}
}
// Update is called once per frame
void Update ()
{
if (boat.GetIsMoving() == 1 && IsInBoat)
{
if (boat.GetWhereToGo() == 1)
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(-9 - WhichSet * 2.0f, 6.0f, 0.0f), 5 * Time.deltaTime);
where = 1;
} else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(4 + WhichSet * 2.0f, 6.0f, 0.0f), 5 * Time.deltaTime);
where = 0;
}
}
if (IsMoving == 1)
{
if (action == 1) // 起始岸下船
{
if (transform.position == new Vector3(4 + num * 2.0f, 6.0f, 0.0f))
{
action = 0;
IsMoving = 0;
where = 0;
IsInBoat = false;
boat.Set(WhichSet, -1);
} else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(4 + num * 2.0f, 6.0f, 0.0f), 5 * Time.deltaTime);
}
}
if (action == 2) // 起始岸上船
{
if (transform.position == new Vector3(2 + WhichSet * 2.0f, 6.0f, 0.0f))
{
action = 0;
IsMoving = 0;
where = 0;
IsInBoat = true;
boat.Set(WhichSet, Ideneity);
} else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(2 + WhichSet * 2.0f, 6.0f, 0.0f), 5 * Time.deltaTime);
}
}
if (action == 3) // 終點岸下船
{
if (transform.position == new Vector3(-23 + num * 2.0f, 6.0f, 0.0f))
{
action = 0;
IsMoving = 0;
where = 1;
IsInBoat = false;
boat.Set(WhichSet, -1);
}
else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(-23 + num * 2.0f, 6.0f, 0.0f), 5 * Time.deltaTime);
}
}
if (action == 4) // 終點岸上船
{
if (transform.position == new Vector3(-9 + WhichSet * 2.0f, 6.0f, 0.0f))
{
action = 0;
IsMoving = 0;
where = 1;
IsInBoat = true;
boat.Set(WhichSet, Ideneity);
}
else
{
transform.position = Vector3.MoveTowards(transform.position, new Vector3(-9 + WhichSet * 2.0f, 6.0f, 0.0f), 5 * Time.deltaTime);
}
}
}
}
}
4.上帝
作爲上帝,一個簡單的例子,重新開始函數就需要對一切對象初始化,所以自然每個對象(河流可以除外)都是上帝的組成,同時爲了判斷遊戲是否勝利,是否輸了,引入了一個GameOver來判斷遊戲進行狀態
Initialization:將遊戲狀態設爲未結束,並將各個對象初始化。
Stop:遊戲結束所有對象都需要暫停
Reset:重新開始,自然需要初始化函數
OnGUI:若是Reset按鈕被按了則重新開始,若遊戲結束根據遊戲結束情況在Label上顯示相應的文字
IfWin():判斷是否勝利。若船在運動或者船正在裝載人,此時不應該判斷。若終點岸牧師、魔鬼的數目加在一起爲6則贏了。排除以後,判斷遊戲是否輸了我是按船所在位置判斷的,若是船在起始岸,若是船上的惡魔加上起始岸的惡魔大於船上的牧師加上起始岸的牧師,且牧師數不爲0;或者終點岸惡魔大於終點岸牧師且牧師數不爲0,則輸,船在終點岸同理
Update:很簡單,當遊戲沒有結束時,時刻監視遊戲進行情況
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 上帝 : MonoBehaviour {
public 岸 start;
public 岸 end;
public 角色 魔鬼1;
public 角色 魔鬼2;
public 角色 魔鬼3;
public 角色 牧師1;
public 角色 牧師2;
public 角色 牧師3;
public 船 boat;
int GameOver;
// Use this for initialization
void Start ()
{
Initialization();
}
void Initialization()
{
GameOver = 0;
start.Initialization();
end.Initialization();
魔鬼1.Initialization();
魔鬼2.Initialization();
魔鬼3.Initialization();
牧師1.Initialization();
牧師2.Initialization();
牧師3.Initialization();
boat.Initialization();
}
void Stop()
{
魔鬼1.Stop();
魔鬼2.Stop();
魔鬼3.Stop();
牧師1.Stop();
牧師2.Stop();
牧師3.Stop();
boat.Stop();
}
private void Reset()
{
Initialization();
}
private void OnGUI()
{
if (GUI.Button(new Rect(450, 400, 100, 50), "Reset"))
Reset();
if (GameOver == 1)
GUI.Label(new Rect(450, 50, 200, 200), "You win!");
else if (GameOver == 2)
GUI.Label(new Rect(450, 50, 200, 200), "You loose!");
}
void IfWin()
{
if (boat.GetIsMoving() == 1 || boat.IAmBusy())
return;
if (end.GetNumberOfDevils() + end.GetNumberOfPriests() == 6)
{
GameOver = 1;
Stop();
return;
}
if (boat.GetWhere() == 0) // 船開來了
{
if ((start.GetNumberOfDevils() + boat.GetNumberOfDevils() > start.GetNumberOfPriests() + boat.GetNumberOfPriests() && start.GetNumberOfPriests() + boat.GetNumberOfPriests() != 0) ||
end.GetNumberOfDevils() > end.GetNumberOfPriests() && end.GetNumberOfPriests() != 0)
{
Stop();
GameOver = 2;
}
} else
{
if ((start.GetNumberOfDevils() > start.GetNumberOfPriests() && start.GetNumberOfPriests()!= 0) ||
end.GetNumberOfDevils() + boat.GetNumberOfDevils() > end.GetNumberOfPriests() + boat.GetNumberOfPriests() && end.GetNumberOfPriests() + boat.GetNumberOfPriests() != 0)
{
Stop();
GameOver = 2;
}
}
}
// Update is called once per frame
void Update () {
if (GameOver == 0)
IfWin();
}
}
所有的腳本都寫好了,創建一個空遊戲對象——上帝,將所有對象都引入上帝中,再將腳本掛載到相對對象,再給相應public元素賦值,就可以正常運行這個遊戲了。
但是還沒完,這裏要介紹以下MVC結構
MVC結構
製作遊戲其實和拍電影一樣,電影裏面需要導演,導演的工作就是協調各個場記,給各個場記分發任務,場記在接到任務後通知演員、化妝師、特效師、佈置場景。MVC就是用這種思想,有一個導演掌控全局,當需要這個場景的時候會通知場記,場記加載資源,這裏用到了LoadResources,這就要求資源必須存放在Resources中的Prefabs中,因此需要我們把之前製作的遊戲整體作爲一個預製存入。MVC結構,必須要編寫三個腳本SSDirector(導演),FirstController(場記,本遊戲只需要一個場記),ISceneController(接口)。編寫完後,將FirstController掛載到一個空遊戲對象,這樣就可以使得整個遊戲僅有主攝像機和一個Empty對象, 其他對象都由代碼動態生成。
SSDirector
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;
}
public int getFPS()
{
return Application.targetFrameRate;
}
public void setFPS(int fps)
{
Application.targetFrameRate = fps;
}
}
ISceneController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController {
void LoadResources();
void Pause();
void Resume();
}
FirstController
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController {
void Awake() {
SSDirector director = SSDirector.getInstance();
director.setFPS(60);
director.currentSceneController = this; // 導演把任務交給了這個場記
director.currentSceneController.LoadResources();
}
// Use this for initialization
public void LoadResources()
{
GameObject Global = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/上帝"), Vector3.zero, Quaternion.identity);
Global.name = "上帝";
}
void Start()
{
}
public void Pause()
{
throw new NotImplementedException();
}
public void Resume()
{
throw new NotImplementedException();
}
}
這樣,我們的遊戲就大致完成了!
由於一開始沒有弄清楚各個對象之間的相互關係,所以有些邏輯十分混亂,不夠清晰,以後製作遊戲一定會優先繪製關係圖,分析每個類需要的函數、屬性,這樣能更快捷、高效地編程。
感謝閱讀!