解謎類游戲以精妙的謎題設(shè)計和引人入勝的故事敘述為特點,考驗著玩家的智慧與觀察力。《迷失島2》與《南瓜先生2九龍城寨》正是這一領(lǐng)域的佳作。游戲以獨特的藝術(shù)風格和玩法設(shè)計吸引了大量玩家,而它們背后隱藏著一套強大的框架。
上海胖布丁游戲的技術(shù)總監(jiān)駱奮強先生在 Unite Shanghai 2024 游戲?qū)錾希瑸榇蠹曳窒砹恕督庵i類游戲的框架設(shè)計》。這個框架以“傻瓜化”與“高效率”為主要目標,涵蓋鼠標點擊、拾取道具、選擇道具、關(guān)卡切換、NPC 對話、根據(jù)條件控制顯示、非線性路程、二周目、小游戲等內(nèi)容。目前該框架已經(jīng)在多款游戲中得到應用,如《迷失島》系列、《南瓜先生2九龍城寨》以及《怪物之家》等。經(jīng)過多個項目的迭代和優(yōu)化,已經(jīng)不僅僅局限于解謎類游戲,在模擬經(jīng)營、生存等其他類型上同樣勝任,適用于各種復雜度的游戲需求。
解謎類游戲的框架設(shè)計
大家好!我演講主題是《解謎類游戲的框架設(shè)計》,這個框架設(shè)計是我們公司游戲用的解謎類的框架,然后我是來自上海胖布丁游戲的技術(shù)總監(jiān),我叫駱奮強。
我們公司的游戲有很多,早期做解謎類的,現(xiàn)在我們做了很多包括模擬經(jīng)營、平臺跳躍、生存類,包括動作類的現(xiàn)在也在開發(fā),類型很多。
這次用這個框架的主要游戲是《迷失島2》,是解密類的游戲。這里面有一些小游戲,通過點觸的方式移動場景進行關(guān)卡切換、跟 NPC 對話,然后可以拾取一些道具,把道具用在一些地方,比如說觸發(fā)門開。
還有一個是《南瓜先生2九龍城寨》,是有個人走的這樣一個解謎游戲,這個游戲也是用的這個框架,它跟《迷失島》不太一樣,《迷失島》是點觸的,這個是通過手指點擊場景會自動尋路,包括可以用手柄或者鍵盤去操作角色,然后會有一個觸發(fā)區(qū)域的概念,只有走到觸發(fā)區(qū)域的時候才會進行一些交互。
我們看一下這個框架大概有哪些內(nèi)容,常見的是鼠標點擊、拾取道具,可以通過點擊場景中的道具區(qū)域拾取道具,可以選擇道具,然后通過點擊場景中的一些交互區(qū)域進行使用道具;還有一些關(guān)卡切換,點擊區(qū)域可以進行關(guān)卡的跳轉(zhuǎn)。
我們這些動畫用的是 Spine 的切換,有一些特殊的會用到 Spine 骨骼,還有皮膚。NPC 是可以對話的,有一些 NPC 可以切換對話,不同的游戲進程會播放不同的對話。有一些部分會根據(jù)條件控制物體的顯示隱藏,比如說本來是一條完整的魚,如果你用刀對它使用可以切掉它,然后得到一些道具之類的。
我們的游戲不像傳統(tǒng)的解謎游戲是線性的,必須通過這個場景之后才可以進行下一個。我們的游戲玩家可以在很多的場景自由地跳轉(zhuǎn)。不同場景中的一些游戲狀態(tài)會影響到別的場景,所以這也就是說我們設(shè)計框架的時候要考慮到這些情況。我們的游戲是二周目的,它的場景會有不太一樣的,跟第一次玩的時候不太一樣,比如說 NPC 的狀態(tài)或場景的一些內(nèi)容,包括可能一周目跳轉(zhuǎn)這個場景,二周目就變成了另外一個場景。
小游戲我們做了十幾二十幾個,跟游戲劇情不太關(guān)聯(lián),是比較獨立的小游戲。每個小游戲都會有三種隨機的,也就是每個玩家玩的小游戲不一樣,這也是我們在設(shè)計框架要考慮怎么實現(xiàn)這個。我們用隨機的方式,可能你每次打開這個場景,或者你關(guān)閉游戲進程再打開的時候,這個場景會隨機成另外一個,我們要想辦法怎么做到這次玩的時候關(guān)掉進程重新進入之后隨機出來的小游戲是一樣的。另外就是還有地圖。
這個框架的目的我提了一個關(guān)鍵詞叫傻瓜化,不用懂程序的概念他也可以編這些關(guān)卡出來,包括策劃的需求,我就算不用懂程序,我只要知道在 Unity 引擎這里面具體怎么操作的,簡單拖一些預制、改一些參數(shù)就可以實現(xiàn)大部分的需求。
這個方式的好處就是,開發(fā)起來很快,只要簡單拖一些組件上去,把美術(shù)的資源導入進來,然后我們可以進行一些組織,根據(jù)一些游戲進程我們就可以快速去控制它,并且可以快速測試它。現(xiàn)在的框架實現(xiàn)了我現(xiàn)在編好之后直接一運行可以快速測試我剛才編的邏輯正不正常。還有一個好處是風險隔離,關(guān)卡中有各種各樣的游戲進程,我不希望我現(xiàn)在改了這部分,這部分改好之后發(fā)現(xiàn)會影響到另外一個。這也是我們框架的目的,希望最小化地去隔離風險。
效果預覽
根據(jù)條件改變對話內(nèi)容
這是我們根據(jù)條件會去改變對話內(nèi)容,比如說一開始沒給到船票他會說這句話,給了他船票之后會觸發(fā)說別的話。
跨場景流程控制
在跨場景的時候,因為我們知道 Unity 跨場景會把場景的東西卸載掉,所以我們要解決跨場景的時候這部分的游戲狀態(tài)會影響到別的游戲狀態(tài),所以我們要解決這個問題。
GM 工具以及道具使用效果
我們可以開發(fā)一些GM 工具。我們可以快速測試,比如說這部分我按照這個框架的規(guī)范編輯好了,但是我現(xiàn)在快速測試,這個需要有一個道具,所以這里面可以通過一個工具添加一個道具出來可以直接使用,甚至我可以不添加道具直接解鎖,我直接模擬他使用了這個道具之后的一個游戲狀態(tài),然后去測試這個表現(xiàn)編輯的對不對。
多道具區(qū)域演示
道具還有一個我們需要考慮的。道具區(qū)域不是一對一的,大部分的情況是一個道具區(qū)域只能使用一個道具,使用不了的時候可能沒有任何的反應。但這個演示是有順序的,這幾個道具雖然可以使用但是要按照一定的順序使用,這里就弄了一個多道具的概念,一個道具區(qū)域可以識別多種道具,并且可以放上去之后可以再拾取。
小游戲
這里面是我們的一些小游戲。小游戲也是一個問題,我們應該要有一個大概的抽象思維統(tǒng)一的解決它們,就像我們游戲中有很多的二維數(shù)組的小游戲。如果每個小游戲單獨去實現(xiàn),一方面效率很慢,另一方面如果出現(xiàn)問題我們沒有辦法快速地去解決它,所以對于一些同樣類型的,比如說都是二維數(shù)組類型的,我們可以抽象成通用的二元數(shù)組的工具類解決這個問題。
實現(xiàn)方案
實現(xiàn)點擊的方案(一切的基礎(chǔ))
我們現(xiàn)在要看一下,因為要實現(xiàn)點擊,我們這個游戲大部分的邏輯都是點擊操作的,所以首先要考慮點擊是怎么實現(xiàn)的,一種是UGUI的組件,UGUI 組件就是點擊 UI 的東西,但是現(xiàn)在場景中的點擊操作比如說道具的拾取不是 UGUI 的組件,沒有辦法用 UGUI 的方式。
我們看大部分的時候大家怎么做的,一方面是可以直接在 MonoBehaviour 里寫出 OnMouseDown 等函數(shù),這樣如果你在物體上掛了 Collider 可以執(zhí)行到,這是第一種方式。第二種方式我們在 Update 里面用射線檢測,我們判斷鼠標點擊,然后去射線檢測,檢測到物體,然后再對它進行操作。
但我覺得這兩種不太好,剛剛說的可能還要考慮移動端,因為移動端需要使用 GetTouch 加上射線檢測做,我覺得這幾種不太好是因為它會跟 UGUI 的窗口會沖突,因為我們的 UI 是用 UGUI 做的,場景邏輯全部是用 3D 場景的,所以我們需要考慮說當 UI 的界面打開的時候,我們需要特殊處理,比如當 UI 點開我們不能點擊場景中的東西。還有我們移植到 Xbox、PS 的時候是用手柄操作的,我們通過虛擬鼠標的方式實現(xiàn)時,因為這些 OnMouseDown 還有 OnMouseUP 接口只支持鼠標或者觸摸屏,當我們有虛擬鼠標的方式,用手柄控制虛擬鼠標去操作物體的時候,就沒有辦法做到。
還有一些比如我們要獲取坐標的時候也比較麻煩,管理上也不太好。
我推薦是用這種方式,直接在 Camera 上掛一個 UGUI 的組件 Physics2DRayCaster,這個掛上去之后,它會把 UGUI 的事件轉(zhuǎn)發(fā)到相機照射的物體上,這樣可以在非 UGUI 的場景中實現(xiàn)在非 UGUI 場景中點擊包括拖動等事件。
這個就是我們在物體上掛一個Collider 2D 組件,然后我們實現(xiàn) UGUI 的 IPointerDown 等事件,可以監(jiān)聽到 UI 點擊等事件,這樣的好處就是我們可以不用管跟 UGUI 沖突的問題,另一方面我們做擴展的時候特別容易,比如說我剛剛說的用虛擬鼠標,包括我們后續(xù)有出現(xiàn)的擴展,就是因為我們?nèi)坑?UGUI 了,就統(tǒng)一了起來,如果有問題我們只要解決 UGUI 的問題就行了。
通過點擊區(qū)域的實現(xiàn),可以組成各種邏輯
通過剛才的方式,我們可以抽象出來一個點擊區(qū)域,我可以在游戲中指定我要執(zhí)行什么函數(shù),通過這個點擊區(qū)域可以實現(xiàn)很多東西的組合,比如說道具拾取,就是通過ClickArea 加上 PropResource這個組件,在點擊的時候執(zhí)行到自定義的道具數(shù)拾取的點擊,這樣就可以把道具拾取到背包里面。還有一些小游戲的操作,一些門的切換也是點擊之后進行的操作,包括跟 NPC,包括道具使用區(qū)域都是通過點擊實現(xiàn)的。
游戲流程控制實現(xiàn)
我們剛才解決了點擊區(qū)域的問題,我們還有一個問題要解決,我們的游戲表現(xiàn)有多種多樣,比如說這個地方開門,那個地方是動畫切換,包括物體的顯示隱藏,如果全部用代碼,會非常不高效,所以我們其實可以抽象出一個游戲 ID 和分鏡 ID的概念。
我們只要有一個游戲 ID的概念,就可以通過這個標記做任何的事情,比如說剛剛演示的時鐘,時鐘完成之后相當于解鎖了一個游戲 ID,這時候我們有一個標記,還有道具使用,比如說這個東西放上去之后讓我們給它解鎖了一個游戲 ID。有了這個游戲 ID,我們可以統(tǒng)一地根據(jù)這個游戲 ID 判斷做一些操作,比如說會監(jiān)聽游戲 ID 更新,改變物體的顯示隱藏,包括動畫的切換。
分鏡 ID是因為大部分游戲 ID 跟分鏡 ID 是一一對應的,一個游戲 ID 對應一個分鏡 ID,但是有一些特殊的一個游戲 ID 可能會解鎖多個表現(xiàn),比如說我現(xiàn)在通電了會導致燈亮了這是一個表現(xiàn),也可能導致風扇也轉(zhuǎn)了,這樣就是一個游戲 ID 加上風扇的分鏡 ID,然后再加上一個電燈的分鏡 ID,這些可以實現(xiàn)解鎖了一個游戲 ID 我們可以解鎖多種游戲狀態(tài)。
這個流程控制器是讓使用者不需要關(guān)注存檔的變化,他也可以進行一些高效的測試,比如說我現(xiàn)在測試這個游戲 ID 對應的是什么表現(xiàn),那我就可以用工具解鎖一下,測試一下看這個表現(xiàn)對不對,我們可以通過多個場景跳轉(zhuǎn)然后去測試一下它的表現(xiàn)。
這個是AnimationController的示例,可以看到它是一個游戲 ID 加上分鏡 ID,會有一些觸發(fā)器,我后面會講另外一個概念,就是觸發(fā)。首先游戲 ID 我們有好多種,比如說門的開放、時光機的表現(xiàn)、藍莓使用、火柴使用,會對應不同的分鏡 ID。比如說剛剛演示的通電燈亮了,包括說它通電風扇轉(zhuǎn)了,這是它的流程控制的作用。
道具使用區(qū)域
道具使用區(qū)域會跟道具進行比對,這個道具使用區(qū)域會設(shè)定支持的道具 ID,并且這個道具 ID 使用了之后會解鎖對應的游戲 ID,因為我現(xiàn)在的框架是所有的都是圍繞游戲 ID,其他的這些東西其實都是解鎖了游戲 ID,所以道具使用區(qū)域也是使用完道具以后會標記這個道具使用過的游戲 ID,我們通過這個道具使用,我們可以去做一些別的操作,如一些變化什么的。
道具拾取組件
道具拾取組件也是一樣的,拾取之后會有一個游戲 ID 的匹配,這個道具使用之后如果沒有判斷游戲 ID 的解鎖,可能這個道具還可以重復拾取,現(xiàn)在它會再判斷一個游戲 ID 的狀態(tài)。
門組件
門組件也是一樣,會跟 ClickArea 做一個組合。
觸發(fā)器與觸發(fā)區(qū)
還有觸發(fā)器的概念,因為我們有很多的表現(xiàn)不一樣,有的表現(xiàn)是門開的,有的表現(xiàn)是解鎖的動畫,切換動畫,有的表現(xiàn)是一些顯示狀態(tài)的切換,有的是一些音效的改變或者是 NPC 對話的改變。如果每個表現(xiàn)都用硬代碼寫,這樣會非常不高效,所以我引入了一個觸發(fā)器的概念,也就是我把每個東西給它最小粒度地抽象出來。
比如說顯示隱藏有一個顯示隱藏的觸發(fā)器,它是只管物體的顯示隱藏,還有一些比如說播放動畫有一個組件一個觸發(fā)器,它在觸發(fā)的時候會把動畫切到別的動態(tài),包括可以監(jiān)聽動畫的完成,一般情況下我們播放完之后我們需要監(jiān)聽它的完成事件,所以我們會切換做一些別的事情。還有包括跳轉(zhuǎn)場景、隨機播放,包括有一些條件的判斷的觸發(fā)器,我們可以分流不同的觸發(fā)器的執(zhí)行。
觸發(fā)器這個概念是比較像是行為樹,但是我覺得行為樹比較重,因為這個游戲大部分的東西比較簡單,所以我單獨抽象了一個觸發(fā)器出來,可以適用于游戲中的基本上所有邏輯。
這個觸發(fā)器也是可以自定義擴展的。
這是剛剛說的觸發(fā)器的條件,它可以根據(jù)不同的條件分流。
觸發(fā)器有一個擴展我們可以實現(xiàn)自定義的觸發(fā)器,有一個 TurnOn 和 TurnOff 的狀態(tài),我們在自定義觸發(fā)器里實現(xiàn)打開之后的狀態(tài)是什么樣的,關(guān)閉的狀態(tài)是什么樣的,我們就可以實現(xiàn)后續(xù)沒有辦法實現(xiàn)的需求,也即可以通過自定義觸發(fā)器實現(xiàn)。
這個觸發(fā)區(qū)的概念和觸發(fā)器有點像,首先要監(jiān)聽主角的走路,比如說我們有的主角的控制,要等待主角走到這個區(qū)域可能會顯示一個氣泡框,或者走過來之后會觸發(fā)一些流程的表現(xiàn)。這是觸發(fā)區(qū)的概念。
實操展示
這是我們剛剛演示的效果,具體怎么做的,有一個Animationid control,然后我們會有一個播放動畫的觸發(fā)器,這個時鐘是一個小游戲,時鐘解鎖之后會解鎖一個游戲 ID,這個 Animationid 會監(jiān)聽對應的游戲 ID 去播放門的打開狀態(tài),這樣我們可以通過不同的觸發(fā)器跟 Animationid control 的組合,快速實現(xiàn)這樣的需求,幾分鐘可以實現(xiàn)這個東西。
這是不同的邏輯之間會有串聯(lián),或者并聯(lián)會有互相影響的,這是它需要解鎖這邊的小游戲,把電通了,通了之后會影響剛剛這個場景中好多的部分,比如說風扇會轉(zhuǎn)導致墻上的海報掉下來,這時候跟 NPC 對話,本來停電的時候會很熱,通電之后會涼快,所以他更愿意跟你說一些別的對話,推進新的劇情,這是我們通過多個 Animation control 實現(xiàn)的,可以看到這個圖有多個不同的 Animation control,然后這個不同的 animation control 會判斷不同的游戲 ID 的狀態(tài),比如說這個游戲 ID 有沒有解鎖,另外的游戲 ID 有沒有解鎖,然后對應分流去控制更多的一個表現(xiàn)。
我們看一下我們是怎么操作編輯一套流程的,可以看到這是一個門,然后我們編輯一個游戲 ID 和分鏡 ID,然后這里面拖一個觸發(fā)器預制,這是控制動畫切換的觸發(fā)器,我們設(shè)計好它的動畫名字這時候監(jiān)聽門打開之后的動態(tài),然后切到門常開的動畫,然后我們直接運行,運行之后通過解鎖游戲 ID,我們就通過這個游戲 ID 解鎖快速測試到了這個表現(xiàn),很方便地去編輯,方便地去測試。并且這個不是程序員也可以操作,并不需要關(guān)注說它這個代碼邏輯是怎么樣的,怎么執(zhí)行的,不用知道它的原理,所以就是我剛剛說的傻瓜化的操作。這個會讓你的游戲開發(fā)會變得更高效,并且會更不容易出錯。
還有對應二維數(shù)組的小游戲我們可以抽象出通用的腳本,比如說抽象出來一個類,這個類是專門用于格子類的二維數(shù)組的小游戲,我可以設(shè)定橫向多少個,縱向多少個,我可以索引到左邊的格子、右邊的格子的狀態(tài),包括我可以獲取到某一個格子它里面的狀態(tài),比如說這個格子有沒有東西。在我們實現(xiàn)剛剛看到的小游戲的時候,我們可以很快地做出二維數(shù)組類的小游戲。
我們在做一些東西的時候我們要考慮好怎么把它抽象,因為我覺得抽象會讓這個游戲會更加快速,并且高效。
因為隨著游戲需求的越來越多,我們會發(fā)現(xiàn)到后面復雜度越來越高的游戲,用觸發(fā)器去管理的話,會繞來繞去的,會很容易出問題,并且不太直觀。所以后面我抽象出來一個狀態(tài)機的概念,這個狀態(tài)機它是針對一個物體,比如說一個 NPC,我們預先設(shè)定好有哪些狀態(tài),比如說狀態(tài)一是開口說話,狀態(tài)二是可能招手,狀態(tài)三可能是干嘛的,我們這樣通過切狀態(tài)的方式控制物體,并且很好地跟它解耦。狀態(tài)機不會關(guān)注游戲進程,你可以理解它是一個機器人,它是通過外部驅(qū)動它,你外部傳給它的狀態(tài)是對的,它就是對的,所以這個方式有一個好處,不容易出問題,并且我們可以很好地管理某一個對象。
其他的就是一些這個框架的其他系統(tǒng)。
比如說存檔,如果說我們剛才游戲 ID、分鏡 ID,因為要存的字段太多了,如果用 Unity 的 PlayerPrefers 去存的話可能不太夠,并且管理也不太好,我后續(xù)抽象出來一個通過文件的方式存,并且使用的方式也很簡單,但是最終存的時候會收集成一個文件,通過Json的方式轉(zhuǎn)成文本存到磁盤里,也支持存檔槽。一般游戲有不同的存檔槽,所以這個存檔系統(tǒng)也會支持對應的存檔槽功能。
音頻系統(tǒng)。因為如果說你直接通過創(chuàng)建 audio source 的方式去播放一些音頻的時候,這時候不太好管理,因為游戲有音效、音量的調(diào)節(jié)需求,如果一開始沒有考慮好,全部都要用 audio source,可能到時候音量管理是一個問題。另外的方式就是說創(chuàng)建出來的太多的 audio source 可能會導致 GC 的問題,所以我弄了一個音頻系統(tǒng)可以通過對象池的方式復用 audio source,并且它可以管理好不同音量的管理,包括可以放在不同的物體上,包括它可以直接通過代碼的方式,比如說我這個就是通過直接代碼的方式給它播放對應一個 key 的,我提前設(shè)定好一個 key,然后這個 key 是指定的哪一個音頻文件是不用管的,這樣做的好處如果有單獨一個音頻的人員他做音頻的時候不需要關(guān)注你這個游戲是什么樣的,他不需要關(guān)注代碼怎么寫的,你只要在特定的地方打上調(diào)用的代碼你就不用管,包括他也能控制這個音頻是不是隨機的音頻,比如說有一些腳步聲,他會隨機地播放一些音頻,包括音量多少,包括會有一些更高級的用法,這樣會跟音頻的開發(fā)人員區(qū)分開,各司其職。
本地化的問題。因為我們的游戲基本上都會面向全球,所以說我們會有一個本地化的問題,大部分本地化直接 Text,如果一開始沒有考慮到本地化的問題,可能直接把文本寫在代碼里面,這樣對未來本地化的時候可能是一個大災難,所以我們的本地化系統(tǒng)是基于配置表的,我們把所有的本地化語言都寫在一個配置表里,我們?nèi)ピO(shè)置一個 Key,通過比如說我們在 Text 里面掛上一個腳本對應,然后指定它對應的是哪一個 Key,然后會自動讀取對應語言的文本。
我們還會有一些資源管理要考慮,是用 Resource 或者是 AssetBundle,這些可能也要考慮一下。還有就是我們會有一些內(nèi)存泄露,內(nèi)存泄露有比較常見的問題我們需要關(guān)注,因為有一些時候你在場景里面你寫了一個靜態(tài)的方法,給它設(shè)置進去切換場景之后會導致這里面一些引用計數(shù)在的話它就不會被清掉,所以這些也要考慮。
《人類一敗涂地》高校游戲關(guān)卡設(shè)計創(chuàng)想賽正在進行中。如果有所啟發(fā),歡迎嘗試應用文章中的技術(shù)思路并參與大賽。大賽“有獎知乎問答”征集活動已開啟,歡迎參賽者入群(QQ群號:372759650)參與。
Unity 官方微信
第一時間了解Unity引擎動向,學習進階開發(fā)技能
每一個“在看”,都是我們前進的動力
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.