如何在游戲里當(dāng)好一個反派——用Unity簡單復(fù)刻《勇者別囂張》(上)

作者:沈琰
本篇難度:★☆☆☆☆
前言
這期準(zhǔn)備復(fù)刻一個曾經(jīng)沉迷好久的PSP上的小游戲:《勇者別囂張》(勇者のくせになまいきだ)。
不得不說一下,這個游戲名的翻譯真的是很出彩,完美體現(xiàn)了游戲詼諧幽默的風(fēng)格。
首先簡單介紹一下游戲玩法:
游戲一反玩家代表正義的傳統(tǒng)套路,扮演一個破壞神指引大魔王攻占地表。
大魔王一開始蜷縮在地下并且沒有任何戰(zhàn)斗力,所能依賴的只有一把鋤頭。敲開地牢內(nèi)的磚塊能根據(jù)磚塊吸收的養(yǎng)分生成不同類型的魔物。

魔物并不受大魔王的控制,而是有著自己的行為邏輯與生息規(guī)則。除此之外當(dāng)發(fā)現(xiàn)勇者時會主動展開進攻。
游戲內(nèi)每間隔一段時間會有勇者來進攻,如果讓勇者抓到大魔王并帶出地牢,游戲就失敗了。反之如果能殺光每一波來犯的勇者,堅持一定波數(shù)后大魔王就能占領(lǐng)地表。

游戲的類型是策略類,核心玩法也是最為有趣的地方是通過敲磚塊構(gòu)成一個合理的生態(tài)鏈,來抵御勇者的攻擊。

接下來就用相對簡單的方法嘗試在Unity中去復(fù)刻這個游戲的基本玩法,那么就不多說了,開始干活。
(另:就用這篇文章祭奠我那成天上課不好好聽講,趴在最后一排玩從同桌那借來的PSP的高中生活....)
素材獲取
這個步驟本來沒打算寫,但是想到可能會有同學(xué)在復(fù)刻小項目時受限于資源問題,加之這次尋找素材的經(jīng)歷有點糾結(jié),就順帶簡單提一下,權(quán)當(dāng)給大家一個參考。
既然是復(fù)刻一般是先去找有沒原版素材,這種游戲要是去扣圖怕是得累死。從常見的素材網(wǎng)站找了一圈最后一無所獲,只能把打主意在解包上面。
之前沒有接觸過PSP游戲的解包加上年代久遠,尋找辦法著實費了一番功夫,最后在一個PSP漢化的帖子里得到方法。
簡而言之就是:
1.能被PSP模擬器識別的.iso格式文件可以直接修改擴展名為.zip進行解壓縮。
2.圖片類的素材文件一般保存在解壓縮后的"PSP_GAME\USRDIR\data\graph""路徑中。
3.PSP游戲內(nèi)的圖片格式是.gim,還需要專門的gim->png轉(zhuǎn)換工具轉(zhuǎn)換一道。
經(jīng)過以上三個步驟后,得到了大部分的圖片素材,但唯獨缺少了勇者和怪物的動畫素材。我之前認(rèn)為這種像素游戲的動畫可能也是以幀動畫的形式逐幀保存為圖片文件的,但翻遍了整個解包出來的文件夾也找不到,最后目光落在了graph文件夾內(nèi)的一個文件上:

看著這個可疑的名字和大小,我猜測很大幾率這就是我要找的東西,但是使盡渾身解數(shù)也沒弄明白這個.fbe的格式究竟是個啥,無奈之下只能另找別的素材來代替原版的角色動畫。希望知道的大佬能在評論區(qū)留言以解我心頭之惑。
最后再多嘴一句,原則上來說不應(yīng)該發(fā)布解包的過程和素材,無論如何也算是侵權(quán)的行為,以往文章中的復(fù)刻小項目盡量都是通過別的方法繞開的素材限制。
但這次的游戲想要自制或是替換素材都很麻煩,也很難體現(xiàn)原版的玩法和風(fēng)格,所以大部分還是使用的原版素材,希望感興趣的同學(xué)們僅作學(xué)習(xí)之用,不要濫用。
地圖搭建
總而言之素材基本準(zhǔn)備齊全了,開始動工。

首先開始搭建地圖,原版游戲里的場景里除了上面的背景,其他都是網(wǎng)格狀的結(jié)構(gòu)。這應(yīng)該是最適合使用TileMap組件的類型了,所以千萬別傻傻的手動去搭地圖了。
關(guān)于TileMap的使用,專欄里有一期文章專門介紹過,不熟悉的同學(xué)可以先去看看。
傳送門:【Unity2D】關(guān)卡編輯好幫手——TileMap
從原版來看游戲的場景地圖分兩種:背景圖和能敲碎的磚塊。關(guān)于背景圖搭建就不一步步的細(xì)說了,記得有些連在一起的地面圖片先切分一下,然后參考上面那篇文章,最后搭出來大概是這個樣子:

這里主要簡單說下能敲碎的磚塊的處理。
磚塊就沒辦法光用TileMap組件本身就能實現(xiàn),因為要處理磚塊敲開的邏輯并且每一個磚塊都會根據(jù)所吸收的養(yǎng)分顯示不同的樣子,所以每一個磚塊是獨立的預(yù)制體,也就是說不能把所有磚塊精靈圖片納入一個tilemap內(nèi)。那是不是意味著磚塊只能靠手搭?并不是。
TileMap不止是能把精靈圖片按網(wǎng)格大小均勻排列的,同樣的邏輯也能用在預(yù)制體上,這一點在官方TileMap教程視頻上有展示過。

這個擴展Brush(筆刷)集成在官方的2d-extras(https://github.com/Unity-Technologies/2d-extras )包內(nèi),下載導(dǎo)入GitHub上的工程包后,在Project面板中就能新建Prefab Brush,命名后拖入預(yù)制體就能使用。

然后選取剛創(chuàng)建的預(yù)制體筆刷,指定想要畫出的預(yù)制體。如果你添加了不止一個預(yù)制體,它會在這些預(yù)制體中隨機選取進行繪制。

交互邏輯
在原版游戲中唯一能做的事就是敲磚塊生成道路和魔物(其實還能直接敲死魔物和迷宮中隨機生成的寶箱,先暫時不考慮),接下來就是實現(xiàn)這個功能。
第一步是檢測到鼠標(biāo)點擊的磚塊,這里開始遇到一個小問題:磚塊顯示在場景內(nèi)是用的Sprite Renderer組件。它無法像UI下的Image組件一樣響應(yīng)鼠標(biāo)點擊事件,如果把顯示組件換成Image也感覺怪怪的,因為磚塊是屬于場景中的物體,跟UI并沒有什么關(guān)系。
那么備選方案還有射線檢測,從鼠標(biāo)點擊的位置打一條射線到場景中。不過同樣有個問題,3D的射線無法檢測到2D的碰撞盒,所以磚塊上要掛上3D的Box Collider組件。作為一個2D游戲來說雖然可行,但同樣顯得怪怪的。
所以以上方法通通PASS,用個稍微繞一些的方法。(原因以后會說)
把整張地圖看做一個二維數(shù)組,那么地圖上的磚塊都有一個唯一的整數(shù)二維坐標(biāo)。所要做的就是在地圖初始化的時候把每個磚塊在場景中的坐標(biāo)轉(zhuǎn)換成二維數(shù)組坐標(biāo)存入字典里。
在鼠標(biāo)點擊時獲取鼠標(biāo)當(dāng)前的屏幕坐標(biāo)轉(zhuǎn)換到世界坐標(biāo)再轉(zhuǎn)成二維數(shù)組坐標(biāo),用這個二維坐標(biāo)去字典中獲取磚塊。
這時候就需要自己定義一個結(jié)構(gòu)來保存二維數(shù)組坐標(biāo),順便寫上轉(zhuǎn)換函數(shù):
public struct Pos:IEquatable<Pos>
{
?? public int x;
?? public int y;
?
???
??? public Pos(int x, int y)
??? {
??????? this.x = x;
??????? this.y = y;
??? }
?
??? public override string ToString()
??? {
??????? return "X:" + x + "|" + "Y:" + y;
??? }
?
??? public static Pos Float2IntPos(Vector2 pos)
??? {
??????? int x = Mathf.FloorToInt((pos.x + 0.09f) / 0.18f);
??????? int y = Mathf.FloorToInt((pos.y + 0.09f )/ 0.18f);
??????? return new Pos(x, y);
??? }
?
??? public static Vector2 Pos2Vector2(Pos pos)
??? {
??????? float x = pos.x * 0.18f ;
??????? float y = pos.y * 0.18f ;
??????? return new Vector2(x, y);
??? }
??? public bool Equals(Pos p)
??? {
??????? if (this.x == p.x && this.y == p.y)
??????? {
??????????? return true;
??????? }
??????? return false;
??? }
}
?
為什么從int轉(zhuǎn)float是乘以0.18f?因為圖片里一個block的大小就是18*18:

這樣一來就把問題轉(zhuǎn)換了一下,通過在字典里匹配鼠標(biāo)轉(zhuǎn)換坐標(biāo)的方式解決了這個問題。
還有一個需要注意的地方,按原版游戲邏輯,能夠消除的磚塊必須是四面至少有一個缺口的,不然把大魔王方到地牢中央的死路中勇者就只能抓瞎了....
這一步就很簡單了,用2D射線檢測或是2D相交球檢測,在執(zhí)行點擊操作之前判斷一下周圍鄰居的個數(shù)。
public bool IsCanBroke
??? {
??????? get
??????? {
??????????? return !CheckNei***or(Vector2.up) || !CheckNei***or(Vector2.down) || !CheckNei***or(Vector2.left) || !CheckNei***or(Vector2.right);
??????? }
??? }
?
?bool CheckNei***or(Vector2 dir)
??? {
??????? RaycastHit2D [] hit;
??????? hit = Physics2D.RaycastAll(transform.position, dir, 0.18f,1<<LayerMask.NameToLayer("Block"));
??????? return hit.Length>1;
??? }
?
接下來編輯磚塊的腳本,讓磚塊顯示的的圖片根據(jù)當(dāng)前所儲存的養(yǎng)料變化。
public Sprite[] sprites;
?
SpriteRenderer Renderer;
?
int nutrient = 0;
?
private void Awake()
??? {
??????? Renderer = GetComponent<SpriteRenderer>();
??????? nutrient = Random.Range(-9, 15);
???????
??? }
?void Start ()
??? {
??????? SpriteUpdate();
?
???????? }
void SpriteUpdate()
??? {
??????? //change by nutrient
??????? int n=0;
??????? if(nutrient<=-50)
??????? {
??????????? MonsterIndex = 5;
??????????? n = 6;
??????? }
??????? else if(nutrient<=-30)
??????? {
??????????? MonsterIndex = 4;
??????????? n = 5;
??????? }
??????? else if(nutrient<=-10)
??????? {
??????????? MonsterIndex = 3;
??????????? n = 4;
??????? }
??????? else if(nutrient<=10)
??????? {
??????????? MonsterIndex = -1;
??????????? n = 3;
??????? }
??????? else if(nutrient<=30)
??????? {
??????????? MonsterIndex = 2;
??????????? n = 2;
??????? }
??????? else if(nutrient<=50)
??????? {
??????????? MonsterIndex = 1;
??????????? n = 1;
??????? }
??????? else if(n>50)
??????? {
??????????? MonsterIndex = 0;
??????????? n = 0;
??????? }
?
??????? Renderer.sprite = sprites[2 * n + Random.Range(0, 2)];
??? }
public bool OnClick()
?? ?{
??????? if(IsCanBroke)
??????? {
??????????? if(MonsterIndex>=0)
??????????? {
??????????????? Debug.Log("on click");
??????????? }
??????????? Destroy(gameObject);
??????????? return true;
??????? }
??????? else
??????? {??? Debug.Log("cant broke");
? ??????????return false;
??????? }
??? }
?
?
??? public void ChangeNutrient(int vaule)
??? {
??????? nutrient += vaule;
??????? SpriteUpdate();
?
??? }
?
接下來試一試效果:

結(jié)束
這期應(yīng)該只算是把準(zhǔn)備工作基本做完了,可以看到代碼方面基本沒什么難度,主要是熟悉TileMap組件的使用,即便新手也能輕松做出來。順便說一句,TileMap能做到的事遠不止如此,對畫筆的自定義擴展能極大的提升開發(fā)2D游戲的效率,有興趣的同學(xué)可以自行查閱。
下期內(nèi)容主要是游戲核心玩法邏輯的實現(xiàn),包括怪物的狀態(tài)機和勇者的尋路AI等等。
本期工程地址:https://github.com/tank1018702/unity-005
最后想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問?http://levelpp.com/???
游戲開發(fā)攪基QQ群:869551769??
微信公眾號:皮皮關(guān)