1. 程式人生 > >完美世界GameJam參加報告——《解字》遊戲的設計與開發

完美世界GameJam參加報告——《解字》遊戲的設計與開發

前言

這次我們組成了7人團隊去參加完美世界組織的位於天河軟體園的GameJam。GameJam的規則是利用48小時開發一個遊戲,這次的遊戲主題是:烎 。我們的團隊由三個程式、三個美術、一個音樂組成(由於GameJam的特性,一般不會特化安排專職的策劃),來自中山大學和廣州美術學院。
本遊戲最終取得廣州賽區的第一名,並取得五萬獎金。

演示視訊https://www.bilibili.com/video/av24163649/

遊戲概念構建

這次的題目來源於網路語言,實際上,由於網路聯通的便利性古字新用變的越來越常見,而網路賦予他們的意義往往和古義完全不同。烎就是其中一個。
而現在我們的遊戲主要就是想讓人們重新發現漢字,有些結構很簡單的字許多人甚至都不認識,我們希望能通過這個遊戲能捍衛漢字的尊嚴

遊戲設計

這是一個2d橫板策略類遊戲我。我們的核心玩法是兩個(或多個)簡單字組合起來成為一個技能字。每個技能字有獨特的效果,可能會攻擊敵人(異形字)或者恢復自己或者造成某種buff。每隔一段時間會有新的隨機生成的字進入字槽。為了點題,我們設定開火組成的“烎”作為大招,可以打出必殺。組成技能字的基礎字越多,技能的效果越強
當攻擊累計到一定的時候,敵人的部首會被擊飛。敵人可能抄起它的部首對主角操控的阿呆進行攻擊。在我們原先的設計中,被擊飛的部首可以以金錢(墨團)或道具的形式成為玩法的一部分,可惜時間關係沒能實現。
另外一點迫於時間沒能實現是敵人可能擁有一些特色部首比如木,而主角如果打出火系攻擊(炎等)則會造成超量傷害,達成擊潰效果。
懲罰機制:敵人會隔一段時間對主角進行攻擊,主角受傷掉血的時候,底部的字槽會同步、等比例的被覆蓋。被覆蓋的字將無法被選中攻擊。血量可以通過回覆系技能回、森、沐等回覆。但hp清零,即字槽被完全覆蓋的時候,遊戲結束,玩家失敗。
本來我們在關卡中加入了同時面對多個敵人的場景,這樣一開始是想著可以讓作為敵人的字組成詞語營造一種幽默感。但後來時間問題程式上有一些bug沒有解決,就沒有在demo中加入這個要素
對於場景設計,我提出了使用《設計詩》裡面的一些場景作為過場動畫來營造剋制的幽默感,不過束於時間限制以及一些審美上的爭議,這一步也沒有實現。

實現技術

我負責的部分主要是UI互動、場景滾動、點選拖動卡牌效果、部分動效、部分動畫、部分粒子系統、部首脫離效果、死亡演出效果、圖鑑(成就)的實現。具體的程式碼出於後續運營考慮不會完整貼上來,此處講解下思路。

UI互動——血條覆蓋層的兩種方案

一開始打算使用Strech的方法,生成一個長方體,然後設定其邊界屬性
造成延伸。這樣實現的好處在於可以完全自定義長方形邊界的左右兩端。缺點在於不好定義血條的樣式,而且思想的時候多次遇到了世界座標和本地座標的神奇轉換。
後來考慮到我們的背景色是白色,所以我們可以直接拿一個白色的sprite隨著hp值變化向左移動。這樣做的唯一缺點在於右端必須從canvas外開始,雖然在本次的demo中看不到這個缺點。

場景滾動(棄用)

一開始選用的場景方法是彈幕射擊類遊戲中常見的技巧——螢幕滾動而人物不動,後來發現這是我沒有吃透這樣做的原理。對於彈幕類遊戲這樣能減少工作量,但是對於2d橫板來講這樣是無意義的,既不增加工作量也不減少工作量,實際上平白增加思維難度

UI部件拖拽效果

這一部分主要使用了unity的OnDrag函式。主要遇到的難點在於拖拽後要改變被拖拽物體的parent,還有就是localposition轉換的問題需要注意

演出效果——技能擊出後sprite的放大縮小動畫

本來有兩種實現方法,一種是使物體更加靠近攝像機然後在移動回原先的位置。這樣的主要問題是不相容正交攝像機,所以最終沒有使用這個方案
最終選用了調整Animation裡property Scale的方法,因為Unity自帶動畫編輯,這部分還是比較輕鬆的。

特效——粒子系統

https://github.com/kotomineshiki/Particle
這周剛好學完了粒子效果正好可以運用到裡面。我們的遊戲美術上的色調是黑白和紅,所以我們暫時選用了儘量貼近水墨風格的粒子特效
先上效果圖
這是專案中二十多個特效的其中兩個比較好看的,下面貼一些具體的引數
烎:由三種特效組成,一個是底部的“魔法陣”
這裡寫圖片描述

一個是從底部冒出的小氣泡這裡寫圖片描述還有就是兩個螺旋球
這裡寫圖片描述
這兩個螺旋球是有軌跡的這裡寫圖片描述

焱:這個特效由
這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

部首脫離效果——動作管理器

要求:在敵人血量每下降一定值的時候,敵人的一個部首會脫落並飛出。我一開始是使用一個canBlow的布林型別變數來控制這個,當canBlow為true的時候,在Update中給部首sprite新增一個二維剛體元件,設定一個速度和重力,並設定canBlow為false;
這樣的實現方法導致了一個嚴重的bug。如果某一個技能導致的傷害足夠導致兩次部首脫落,那麼將會出現如下呼叫(示意)

SET canBLow=true//第一次脫落
SET canBLow=true//第二次脫落
Update{
if canBlow =true
部首擊飛
canBlow=false;
}

我們注意到這時候實際上只會執行一次脫落。這個時候突然意識到,我們應該用佇列來解決這種衝突,如果我們把canBlow設計成佇列,那麼

canBlow.EnQueue(部首1)
canBlow.EnQueue(部首2)
Update{
    while(canBLow非空){
        canBlow.Dequeue();
        部首擊飛
    }
}

仔細想想,這不正是動作管理器嗎!實際上在課上學習到的動作管理器結構十分複雜,一個功能寫了好幾個類來實現。這次的使用說明了動作管理器其實可以很簡單,重要是佇列化動作的思想。

部首脫離效果——障眼法實現動畫和剛體效果的共存

上面提到,部首脫離效果是通過加2d剛體元件來實現的,但是這帶來了一個問題:動畫管理器是通過規定對應物體的位置來做動畫的,而剛體元件和其在原理上衝突。那麼如何解決這個問題呢?我採用了障眼法,把要吹飛的部分複製一下,吧原有的部分隱藏起來,再給複製的部分加上剛體元件,這樣效果和理想中一樣的。

協程——動效

這次大量使用了協程coroutine來實現動效,在某種程度上可以代替動畫機。比如部首脫落後過一段時間會變淡,再過一段時間會消失。

部首脫離效果——觀察者模式的一些坑

這一步遇到了一個有意思的問題。我在部首脫離效果的類中訂閱了Enemy的血槽變化和死亡兩個事件(但血槽為0時,角色也就死亡了)。但是發現了一些問題:角色死亡事件總是比最後一次血槽變化先傳給觀察者。這就是老師上課所說的“執行順序問題”。

序列化讀入Json資料——圖鑑的資料層

這個對我來說是一個巨大的挑戰,因為之前從來沒有寫過。之前在Unity中寫管理資料的類的時候,都是用程式碼在類裡的Start()中手動一條條寫進去。仔細想想這樣其實並不是符合軟體工程標準的方法。主要通過呼叫JsonUtility.FromJson這個函式並建立序列化類來實現讀入。這次選用的序列化讀入降低了耦合度,實現了資料與資料管理類的分離,也更加方便分工——可以由數值策劃編輯Json類裡的資料,由程式設計師寫程式。

觀察者模式——圖鑑的邏輯層

回想觀察者模式(訂閱釋出模式),我們知道這一模式被設計出來就是為了解決成就係統。我們的圖鑑管理器主要的資料結構是“字典” 我們可以通過鍵(技能字,用字串存)來訪問值(字的解釋)。那麼我們就把圖鑑類作為一個觀察者,觀察玩家使用技能這一事件,把每一次玩家使用技能記錄產生的字記錄下來,這樣就可以做到玩家每發現一個新字,都可以在圖鑑中更新。

單例模式——圖鑑

對於圖鑑,他應該是一個全域性性的類,因為在任何一個場景的任何一個位置發現了新字都應該被圖鑑記錄,所以我們採用了單例模式來方便訪問。

心得和教訓

之前上unity課的時候經常有疑惑:為什麼簡單的功能要用那麼多複雜的結構。實際上這些設計模式被髮明出來都是有他們道理的——他們能解決某些情況下潛在的BUG。
做遊戲時很重要的是對細節的把控。每一個動效一定要修改到理想的效果為止,不能因為技術或者各種原因妥協。這裡打折那裡打折一下子手感就下去了。