本文同時(shí)發(fā)布至我的個(gè)人博客增显,點(diǎn)擊進(jìn)入我的個(gè)人博客閱讀。本博客供技術(shù)交流與經(jīng)驗(yàn)分享隔嫡,可自由轉(zhuǎn)載甸怕。轉(zhuǎn)載請(qǐng)?jiān)谠u(píng)論區(qū)或私信簡(jiǎn)單通知,感謝腮恩!
游戲簡(jiǎn)介
打飛碟小游戲是一款射擊類小游戲梢杭,游戲規(guī)則如下:
- 每回合開(kāi)始,玩家按“空格鍵”發(fā)射若干飛碟(飛碟數(shù)量隨回合數(shù)增加而增加)秸滴,玩家通過(guò)鼠標(biāo)控制點(diǎn)擊武契,擊中飛碟時(shí)獲得10分。
- 若飛碟直到掉落在地還未被玩家擊中荡含,則玩家扣除10分咒唆。
- 若玩家分?jǐn)?shù)低于或等于0分時(shí),游戲失敗释液。
- 玩家分?jǐn)?shù)每達(dá)到一定分?jǐn)?shù)(20分全释、40分等),則關(guān)卡升級(jí)误债。發(fā)射飛碟數(shù)量增多浸船,發(fā)射速度加快。
游戲展示
設(shè)計(jì)思路
? 這次游戲設(shè)計(jì)繼續(xù)采用MVC架構(gòu)寝蹈,根據(jù)自頂向下的設(shè)計(jì)思路李命,我首先考慮最頂層的控制類GameScenceController
,它應(yīng)該以單實(shí)例形式存在箫老,并且通過(guò)對(duì)其它類提供功能不同的接口來(lái)實(shí)現(xiàn)向類轉(zhuǎn)達(dá)信息封字。所以從功能考慮,GameScenceController
實(shí)現(xiàn)了IUserInterface
(用戶操作接口), IQueryStatus
(狀態(tài)查詢接口), setStatus
(狀態(tài)設(shè)置接口), IScore
(分?jǐn)?shù)相關(guān)功能接口)耍鬓,這樣我們就完成了Control
層的設(shè)計(jì)阔籽。
? Model
層負(fù)責(zé)游戲邏輯,這次游戲邏輯較為簡(jiǎn)單牲蜀,所以我用GameModel
類來(lái)實(shí)現(xiàn)控制游戲分?jǐn)?shù)仿耽、判斷游戲輸贏的邏輯功能。
? View
層較為復(fù)雜各薇。首先需要一個(gè)場(chǎng)景類Scence
來(lái)設(shè)置飛碟的數(shù)量大小速度等屬性、控制發(fā)射飛碟和隱藏飛碟,并且它要負(fù)責(zé)判斷場(chǎng)景中的飛碟狀態(tài)并告知控制類峭判。其次开缎,為了實(shí)現(xiàn)飛碟對(duì)象的重復(fù)使用,使用工廠類DiskFactory
類創(chuàng)建和回收飛碟林螃。最后奕删,UI部分需要提供兩個(gè)類:一個(gè)UserInterface
類負(fù)責(zé)處理用戶的操作,UI
類負(fù)責(zé)實(shí)現(xiàn)游戲的UI視圖疗认。
開(kāi)發(fā)過(guò)程
(一)理解“接口”與使用接口
? 由于之前做的更多是一些C和C++的小項(xiàng)目完残,對(duì)于C#這種純面向?qū)ο笳Z(yǔ)言中“接口”的概念總覺(jué)得沒(méi)辦法很好地理解,相信也有許多新手和我有相同的困惑横漏。通過(guò)這次項(xiàng)目的開(kāi)發(fā)經(jīng)歷谨设,我覺(jué)得自己已經(jīng)能較完整的理解“接口”這個(gè)概念了。下面結(jié)合代碼來(lái)談?wù)勎覀€(gè)人對(duì)“接口”設(shè)計(jì)的理解:
public class GameScenceController : IUserInterface, IQueryStatus, setStatus, IScore
{
//...
}
public class UserInterface : MonoBehaviour
{
//...
void Start()
{
//將控制類實(shí)例轉(zhuǎn)化為特定的接口類型
_uerInterface = GameScenceController.getGSController() as IUserInterface;
_queryStatus = GameScenceController.getGSController() as IQueryStatus;
_changeScore = GameScenceController.getGSController() as IScore;
}
}
? 像許多新手一樣缎浇,此前我對(duì)接口的理解停留在重要性上——使用接口可以使代碼可讀性更強(qiáng)扎拣,結(jié)構(gòu)更加清晰。其實(shí)素跺,使用接口在架構(gòu)設(shè)計(jì)的層面上看二蓝,有一定的必要性。就像上面的代碼所示指厌,對(duì)于UserInterface
類刊愚,他需要向控制類獲取游戲狀態(tài)、分?jǐn)?shù)的相關(guān)信息踩验,同時(shí)需要向控制類傳達(dá)用戶操作信息鸥诽,但是它并不需要也不應(yīng)該得到設(shè)置游戲狀態(tài)的功能。像這種晰甚,需要向一個(gè)類提供部分功能的情況衙传,使用接口就能容易地實(shí)現(xiàn),只要將控制類實(shí)例轉(zhuǎn)化為特定的接口類型厕九,就能限制其提供的功能蓖捶。這符合面向?qū)ο笤O(shè)計(jì)的封裝性原則。
(二)關(guān)于Unity剛體—— Rigidbody
? 當(dāng)Unity中的一個(gè)對(duì)象被添加了Rigidbody
組件扁远,它便成為了一個(gè)“剛體”俊鱼,即Unity會(huì)對(duì)其應(yīng)用物理引擎,于是對(duì)象便會(huì)像現(xiàn)實(shí)物體一樣存在受到力的作用畅买。這次開(kāi)發(fā)過(guò)程在設(shè)置Disk
時(shí)簡(jiǎn)單的使用了一下Rigidbody
組件并闲。
public void sendDisk(List<GameObject> usingDisks)
{
this.usingDisks = usingDisks;
this.Reset(round);
for (int i = 0; i < usingDisks.Count; i++)
{
var localScale = usingDisks[i].transform.localScale;
usingDisks[i].transform.localScale = new Vector3(localScale.x * diskScale, localScale.y * diskScale, localScale.z * diskScale);
usingDisks[i].GetComponent<Renderer>().material.color = diskColor;
usingDisks[i].transform.position = new Vector3(startPosition.x, startPosition.y + i, startPosition.z);
//使用Rigidbody
Rigidbody rigibody;
rigibody = usingDisks[i].GetComponent<Rigidbody>();
rigibody.WakeUp();
rigibody.useGravity = true;
rigibody.AddForce(startDiretion * Random.Range(diskSpeed * 5, diskSpeed * 8) / 5, ForceMode.Impulse);
GameScenceController.getGSController().setScenceStatus(ScenceStatus.shooting);
}
}
public void destroyDisk(GameObject disk)
{
disk.GetComponent<Rigidbody>().Sleep();
disk.GetComponent<Rigidbody>().useGravity = false;
disk.transform.position = new Vector3(0f, -99f, 0f);
}
? 結(jié)合這次的工廠回收機(jī)制,一個(gè)Disk使用完畢后并不是將它銷毀谷羞,而是暫時(shí)隱藏等待再次使用帝火。這樣的情況下溜徙,我們將Disk暫時(shí)保存時(shí),需要暫時(shí)讓其Rigidbody
不工作犀填。原因很簡(jiǎn)單蠢壹,如果不停用其Rigidbody
,那么Disk會(huì)一直受到力的作用而不停的運(yùn)動(dòng)九巡,這顯然是會(huì)消耗游戲性能的图贸,所以我們不希望這種情況出現(xiàn)。這里我選擇了使用Rigidbody.Sleep()
和Rigidbody.WakeUp()
來(lái)停用和啟用Rigidbody
組件冕广。
(三)再談工廠模式
? 上次制作簡(jiǎn)單工廠項(xiàng)目時(shí)曾小試過(guò)一次工廠模式的使用疏日,這次在項(xiàng)目中我同樣也引入工廠模式來(lái)創(chuàng)建和回收飛碟。
public class DiskFactory : System.Object
{
public GameObject diskPrefab;
private static DiskFactory _diskFactory;
List<GameObject> usingDisks;
List<GameObject> uselessDisks;
public static DiskFactory getFactory()
{
if (_diskFactory == null)
{
_diskFactory = new DiskFactory();
_diskFactory.uselessDisks = new List<GameObject>();
_diskFactory.usingDisks = new List<GameObject>();
}
return _diskFactory;
}
public List<GameObject> prepareDisks(int diskCount)
{
for (int i = 0; i < diskCount; i++)
{
if (uselessDisks.Count == 0)
{
GameObject disk = GameObject.Instantiate<GameObject>(diskPrefab);
usingDisks.Add(disk);
}
else
{
GameObject disk = uselessDisks[0];
uselessDisks.RemoveAt(0);
usingDisks.Add(disk);
}
}
return this.usingDisks;
}
public void recycleDisk(GameObject disk)
{
int index = usingDisks.FindIndex(x => x == disk);
uselessDisks.Add(disk);
usingDisks.RemoveAt(index);
}
}
? 這次的工廠模式實(shí)現(xiàn)較為完整撒汉,使用兩個(gè)List
——usingDisks
和uselessDisks
來(lái)分別儲(chǔ)存正在使用和使用完畢的Disk沟优。需要注意的是,由于DiskFactory
并非是繼承MonoBehaviour
的類神凑,所以無(wú)法直接導(dǎo)入預(yù)設(shè)净神,只能在其它類中導(dǎo)入Disk
預(yù)設(shè)再將其傳給DiskFactory
。