嗟星症問卷RSQ-6|開發小結|BoooMGameJam2023UnbalancedDice


3樓貓 發佈時間:2023-10-28 10:32:25 作者:_不行_ Language

前置條件

Jam結束後的第一個週二中午去喝酒,古巴程序員大哥同事問我:“最近你還有參加什麼遊戲比賽嘛?”
我說暫時沒有了,“上次jam我已經把所有學會的本事都用上了!”
所以,這次Jam其實是一個練兵場,最核心的目的是把之前學會的技能用一用。
今年的第一個BooomGameJam我做了一個推箱子解謎小遊戲,做完之後把源碼發給獅子看過,然後被狠狠教育了哈哈,感受到了野路子編程的侷限。這次Jam我主要是想試一試用狀態機寫角色控制器,試一試3D遊戲,再試一試使用對話管理器寫文本。
那現在我十分驕傲地宣佈,三個目標都完美完成!雖然因為國慶假期跑出去玩導致最後沒時間寫文本,但是總算在最後一刻打包上傳了一個完整的沒有bug的遊戲,了不起!
這次開發也同樣受到了很多網絡鄰居的幫助,每次在搜索引擎中鍵入關鍵字的時候我都在感嘆生活在這個時代真好,互聯網上有這麼多資源和好心人。希望全球各地各個方面的收緊可以再慢點到來。我接下來會嘗試拆分這次開發所用到的一些模塊,給出一點我自己的粗淺理解和參考來源,希望能給遊戲開發新新手們一點指路作用。

使用到的主要模塊

1. 有限狀態機(Finite State Machine)

其實我分不太清楚有限還是無限還是其他類型的狀態機的異同,總之都是狀態機。
我自己在反覆對比學習了很多教程以後,發現狀態機是對於我來說最自然的一種編程方式(似乎大名鼎鼎的Unity第三方插件playmaker就是使用狀態機的思路)。
對比其他原始的Unity教程中用到的把所有功能寫到一個update裡,然後用很多if去控制執行哪一部分代碼來說,使用狀態機相當於是把這些用來控制的if代碼塊拆分成一個個其他的腳本。這樣的好處是單個腳本不會十分臃腫,在代碼量增加的時候也更加容易維護。對於我個人來說,我覺得最好的一點是,使用狀態機的編程方式會強迫我先思考一個物體有哪些狀態,提前計劃好才進行程序的編寫,使我擺脫了脫離網絡教程就不會編程的情況。(網絡上的新手教程特別容易讓人產生依賴性,剛剛開始學習的時候,經常會看著教程跟著來覺得一切OK,到自己上手寫新功能的時候就抓瞎了。)
寫這個部分代碼的時候,我主要參考了兩個人的視頻:
主要的一位是油管上的iHeartGameDev,他有許多和狀態機相關的視頻教程,做得非常用心,通俗易懂;
另一位是Bilibili的up主M_Studio,當時看完上面大哥的視頻以後,總覺得還需要再看看別人是怎麼做的,碰巧麥扣老師就上傳了關於狀態機的新視頻,非常幸運。
在反覆參考兩位的狀態機寫法之後,活用搜索引擎惡補其中遇到的零碎知識,湊出來了我自己的有限狀態機。我覺得對於我來說,寫狀態機的最大難點在於代碼新手看到這種陌生的代碼結構和裡面用到的陌生詞語就十分害怕,經常看一半就會打退堂鼓(以前我也嘗試學習過,但是放棄了)。這次能堅持下來,還是多虧了上次jam完成了從0到1的突破,真的獨立做完了一個小遊戲,給了我很大的勇氣和信心。
我自己總結的狀態機的樣貌是由如下三個部分組成的:
  • StateManager(狀態機管理器):
該管理器除了負責切換狀態外,最重要的是給其他States提供Context。換句話說就是,當切換到某個具體狀態後,在該狀態下這個物體需要的參數都應當由該StateManager提供,這樣就能保證在寫抽象的State時代碼中只包含和State相關的內容而不用操心獲取具體的信息參數。
左側:狀態機管理器中包含的所有狀態的變量;右側:狀態機收集參數,並且控制狀態的切換。

左側:狀態機管理器中包含的所有狀態的變量;右側:狀態機收集參數,並且控制狀態的切換。

  • BaseState(抽象的基礎狀態)
該狀態包含所有可能存在的實際狀態,但是該狀態不是一個實際會被用到的狀態。打個比方,這個抽象的狀態就像是一個模板,它規定了其餘的狀態大致的樣子,剩下的實際狀態一定會遵循這個模板,在模板允許的範圍內,每個實際狀態可以有自己的特例。
除了規定一共有哪些狀態外,抽象的基礎狀態還可以包含會被其他具體狀態頻繁使用的方法,這樣其他的具體狀態通過繼承抽象基礎狀態就可以直接使用。
抽象狀態如果和狀態機管理器解耦的足夠好,那麼此時應當只需要傳遞給每個狀態一個參數,這個參數就是場景中存在的對應管理器。
只需要一個PlayerStateManager作為各個狀態的參數。

只需要一個PlayerStateManager作為各個狀態的參數。

  • State A, State B, State C, etc.(具象的特例狀態)
具體狀態其實就是在不使用狀態機來寫東西的時候,包含在各個if判斷裡的代碼內容。唯一的不同是要注意多寫一個判斷何時轉換狀態的代碼。
除了BaseState和Manager外,還包含了Idle,jump和walk三個狀態。

除了BaseState和Manager外,還包含了Idle,jump和walk三個狀態。

除此之外,你可能還需要直到什麼是繼承。一個題外話,我也是到此時才明白每個新建的腳本都會有的XXX:MonoBehaviour到底是啥意思。

2. 3D角色控制器

這個部分純粹是因為個人愛好,我想試一試開發3D遊戲(夢想是做Arkane那樣的浸入式模擬!但是最好能再輕度一點,更有好一點!),那麼自然先從角色控制器開始。
這裡我主要參考了這些內容:
首先,也是最重要的,是Toyful Games,他們除了在自己的官方網站上更新DevLog之外還在油管上有視頻Devlog分享。我一直想試試用rigidbody寫一個角色控制器,這次我寫的角色控制器思路就是來自於他們的分享,即用一個懸浮的膠囊體碰撞體作為角色本身。他們詳細在開發日誌中分享了為什麼要用懸浮膠囊體,以及如何實現。
簡單來說,他們的思路是在角色的懸浮碰撞體下方安裝一個彈簧,使得角色始終和地面保持一定距離。我自己嘗試下來覺得這樣做最大的優點是不用考慮地面上的小高差變化,也不用像傳統的控制器那樣寫在斜坡上讓rigidbody停止不動的特殊代碼。同時這樣做的視覺表現也更加生動,因為角色從高處落下時會自然的抖動(腳底有彈簧),十分適合卡通風格的美術表現。
懸浮膠囊體。右側通過改變彈簧參數影響動畫表現。

懸浮膠囊體。右側通過改變彈簧參數影響動畫表現。

在學習Toyful Games的方法時,我發現他們的分享更多的是關於原理,很少涉及到代碼,對我這個新手來說即使想逆向工程也難度很大。幸運的是我還找到了Joe Binns的油管頻道,他的這一期視頻就是在學習Toyful Games的方法重現該角色控制器。於是順藤摸瓜我找到了Github上Joe Binns分享的源代碼,也多虧了他,我才能順利的進行逆向工程和學習,幫助非常之大。這次逆向工程難度正正好,基本每一段代碼都有新的知識需要我去搜索或者參考Unity文檔進行學習和消化,進度非常之慢,但還好我堅持下來了,曾經像是天數一樣的代碼慢慢地都變得可以理解了。
理解了代碼之後,剩下的內容就十分簡單了。我只要把這個巨大的角色控制器腳本拆開來,有序地分散到寫好的狀態機代碼裡就可以了。

3. 對話管理器

工作中,項目組有在使用一個對話管理器插件。在處理小體量的對話的時候,完全夠用了:它需要在引擎內手動一個一個去按照對話ID配置對話文本和手動做跳轉邏輯。雖然它能夠在引擎內看到可視化的文本邏輯,但仍然,在處理多分支文本的時候顯然力不從心了,包括在內容量增加之後,如何方便策劃進行維護修改也是一個大難題。
正巧去年看到了機核默顏老師的這篇文章:遊戲分支劇情創作中的挑戰和工具裡面十分詳細的介紹了幾個主流的專門用於有大量文本的項目的對話管理器,其中包括 Twine, Yarn Spinner, ink, articy:draft等,我就不在此贅述了。在今年的頭幾個月裡我斷斷續續的嘗試使用了Twine, Yarn Spinner, 還有ink三個工具。其中給我感受最好的是Yarn Spinner,我認為它更像是一個服務於引擎的插件,而不像其他兩個工具更像是一個獨立的多分支文本寫作軟件。而且在學習的過程中,我發現Twine有太多版本,實在不是一時半會能消化的;ink雖然寫起來很爽,但是響應式的文本語法對於中文文本來說也不方便,而且還沒有圖形化的分支文本展示;對比之下,Yarn Spinner處在一個恰好的甜點,最重要的是,他們的開發者比較活躍,官方文檔和範例也十分清晰好學。那麼自然這次Jam我選擇使用Yarn Spinner來管理文本內容。
那麼,在選定完工具後,自然是改造工具以適應我的遊戲設計目的。我不是很喜歡Galgame式的文字展示方式,我認為把文字顯示在屏幕下方並且交互一次就播放下一段文字的顯示方式,先天上就很難給我一種閱讀的體驗,更像是在進行對話(可不,這就是Galgame要的!):把長段文字切成短句,然後一句一句地顯示給玩家。
在明確方向後,剩下的工程對於我來說仍然是讓人畏懼的。在能夠大致讀懂並且使用 Yarn Spinner的官方文檔後,我開始了緩慢的逆向工程。幸運的是,Yarn Spinner的官方示例中正好有我所希望的文本顯示樣式——一個模擬手機聊天的示例工程。
左側為YarnSpinner示例工程中的文本顯示;右側為這次Jam的遊戲文本顯示。

左側為YarnSpinner示例工程中的文本顯示;右側為這次Jam的遊戲文本顯示。

我自己對這個示例工程的理解是這樣的:每次玩家希望顯示下一個對話的時候,在顯示對話的區域內(一個豎直的Scroll View區域,內部的元素排列由Vertical Layout Group控制,這些和Unity UI相關的東西也花了很長時間去學習,可以說是處處都是攔路虎了)不斷地Instantiate新的對話氣泡Prefab,每一個氣泡內部的文字就是使用Yarn Spinner編寫的文本中的一句Line,直到一個對話中的所有Line被顯示完畢,那麼停止Instantiate新的氣泡。
於是,在反反覆覆地實驗如何使用Unity的這些UI組件後,我總算是湊出來了一個期望的對話展示形式。(題外話,在打包完成之後的一次測試上我發現我的滑動條方向反了……)接下來就是……把故事放進遊戲中!
把故事放進遊戲這件事情本身的難度,大部分來自於玩家和遊戲交互的方式上比較怪異——推動一個骰子而不是點擊按鈕。所以我簡略描述一下游戲中是如何實現繼續播放下一段故事這件事情的:
首先,骰子是一個物理骰子,它和玩家間的交互是通過物理碰撞實現的。玩家推動骰子,骰子受力發生旋轉,所以骰子的質量、玩家的質量還有玩家運動給出的力是在幾次手工調整後確定的——骰子被玩家撞擊後不會有太大的平移,而是發生旋轉,並且這個力也不會讓骰子轉的太多(這樣骰子始終會有一個面非常穩定地觸地)。
然後,我在骰子的六個面中心放置了一個球形的碰撞體,這樣當一個碰撞體碰到地面時,就代表玩家推動骰子發生了一次旋轉。並且由於骰子相對的兩個面上的點數之和為7,那麼自然的,當1點數的面觸地,代表6向上,即玩家期望骰出的點數是6。以此類推,即可完成其餘面的判斷邏輯。但是這樣還不夠,因為玩家期望骰出的點數可能不能夠一次轉動就得到,那麼我的方法是加入一個等待的時間——當一個面觸地達到三秒之後才判斷這個點數是玩家所期望得到的點數。
接下來,我設置了一個變量用來追蹤這是玩家第幾次骰出骰子,這樣我就能夠根據這個變量來判斷故事發展的階段,進而讓我的對話系統給出不同階段的文本。我沒有使用Yarn Spinner內置的變量儲存器,我也沒有時間再去學習怎麼自己寫一個變量儲存器了,於是這次我把變量全部用static的形式在Unity裡聲明,這樣也不用管理引擎和對話管理器之間的變量同步問題了(事實上,Yarn Spinner的官方文檔中建議把所有變量只儲存在一邊,避免出現Unity和 Yarn Spinner中都聲明瞭變量的情況)。
最後就是在Unity裡寫一些讓Yarn Spinner能夠獲取、改變變量的方法,方便編輯文本的時候可以編輯變量(能夠寫文本的時候直接使用方法控制Unity裡的邏輯實在太方便了!)。至此,算是完成了播放對話的事情。
接下來就是一些交互上的優化:比如在播放對話的時候把骰子的質量調整到1000kg防止玩家在播放對話的時候轉動了骰子。其實這裡也可以做別的對話,但是我程序能力有限,實在是沒有精力去研究怎麼使用Yarn Spinner加速、終止對話的過程中還不出奇怪bug了,所以乾脆不讓骰子動。再比如給玩家和骰子的地面做成一個圓形,並且讓這個圓形地面跟隨玩家和骰子,避免骰子被推出邊界(這裡也能做別的有趣的事情,但是……沒時間了!)。

其他模塊:

其他用到的模塊我都只是粗略跟著教程跑了一遍,然後自己做出來一個能用的版本。所以我就不展開多說了,實在是說不出來啥,我會把教程鏈接一起寫上來:
  • New Input System:
How to use NEW Input System Package! (Unity Tutorial - Keyboard, Mouse, Touch, Gamepad) - by Code Monkey
NEW INPUT SYSTEM in Unity - by Brackeys
Character Movement in Unity 3D | New Input System + Root Motion Explained - by iHeartGameDev
  • 像素化渲染
Make a PIXELATED CAMERA in Unity (A Short Hike case study) - by whateep (通俗易懂,非常好教程,愛來自瓷器)
How to Get a Pixelated Look | Unity Tutorial - by Thomas Friday
  • 描邊shader
How to make outline (Unity URP) - by Interdimensional Play (差不多的思路我在Blender裡也做過,所以這次連連看起來沒啥障礙,很輕鬆)
  • Cinemachine
THIRD PERSON MOVEMENT in Unity - by Brackeys (這個教程中主要是說如何做第三人稱角色控制器,但是也提到了如何設置第三人稱Cinemachine)

做這個怪遊戲的原因

拿到題目以後我覺得很矛盾的地方是:一個有意義的骰子,本來代表的就是公平;而如果骰子不公平,那就失去了作為骰子的意義。而且前段時間LD Jam好像題目就是骰子?去年GMTK Jam的題目也是和骰子有關,我都去玩了玩大家提交的遊戲,難免這次BoooM Jam逆反了!就是不想好好做個遊戲。這導致很長一段時間我都不知道做啥,感謝公司的同事也幫忙想了很多點子,但是我覺得都太“遊戲”了——這些點子難免會和數值設計/概率扯上關係,或者是之前Jam中出現過的玩法。如前文所說,主題是關於不公平的骰子(一個矛盾的命題),那玩法中又讓玩家去影響概率製造不公平(太……順著題目了?沒有那個矛盾的感覺),總覺得缺點意思,不得勁!
正巧那段時間看了默顏老師的文章:淺談電子遊戲中“選擇”的設計,還有身患威廉吉布森見登美彥症候群老師的文章:雜談丨交互性消失的地方。我想到極樂迪斯科裡找的那隻隱形的蟲——直到玩家玩到這個地方之前,奇怪動物學家夫婦倆都沒能找到的蟲——突然就出現在了玩家的屏幕上。我一直覺得這是很諷刺的事情,投骰子本身也似乎變成一種無意義行為——我記得極樂迪斯科的主創說過,他們希望無論玩家做了什麼,骰出怎樣的結果,是大成功還是大失敗,玩家都能得到豐富的體驗——於我而言,這是一種極致地控制下的不隨機。但即使如此,得知真相後我仍然會再玩下去,再骰出一個骰子,再做一次選擇。似乎人的感受是如此容易被糊弄,這樣的精緻的設計數量足夠後,也讓人覺得這是豐富多彩的世界了。
一切好像都是命中註定,那我就做一個關於不接受命運安排的故事吧,一個關於後悔的故事。
遊戲的文本沒什麼實際含義,我嘗試寫了一些小故事,各種風格都有。然後我嘗試把幾個小故事中間的意向變成其他故事中也出現的符號,看看會有怎樣的效果。非常惡趣味的是,我這麼安排是想看看有沒有我最討厭的互聯網懂哥來分析這些故事碎片是怎麼樣的關係。我多慮了,根本沒多少人玩!

結語

有一段時間我覺得很厲害的人是吹哥,最近我的榜樣變成了Lucas Pope。他在互聯網上的Devlog分享實在是讓人受益匪淺。感謝每一個願意在網絡世界分享知識的偉大的人!我很喜歡看開發故事和開發者的DevLog,有的時候我不期望能學到什麼新知識,單純的是我想看到不同的開發者們是如何開發,看他們描述開發中的困難,看他們分享開發中的喜悅。這些分享各不相同,但是他們的相同點是:他們告訴我創作是困難的。而我從他們的分享中得到了 決心(determination)和勇氣。
如果你看到了這裡,希望我的分享能幫助到你。也能帶給你決心和勇氣!

© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com