遊戲的新手引導應該如何進行程序設計?

傳統的新手引導方式一般是設置一個全局的靜態變數來保存當前新手引導進度,然後在項目中每個可能出現新手引導的位置添加一句判斷:若當前新手引導步驟等於我所期望的步驟就執行引導部分的邏輯,新手引導流程越長越容易出現BUG,且傳統的新手引導做法會極大地破壞代碼的耦合性。請問有什麼比較好的設計方法可以參考嗎?


用傳統的命令式編程語言是沒有辦法解決這個事情的,你需要語句級別的mixin(感覺上就像AOP一樣,但那個是函數級別的),但是據說基本沒有人這麼做過。如果你要把這件事情變簡單,你就去改你們遊戲自己的腳本語言的編譯器,加上這個功能就好了。


不知道有沒有什麼更好的辦法,不過倒是可以盡量把所需的操作抽象出來。

比如打開界面,播特效,強制點擊某一個區域什麼的,這些作為一個操作枚舉,在需要引導的地方調用GuideManager里的執行引導方法時當做參數傳進去,一同穿進去的還有操作所對應的一些其他參數,這樣代碼還是能簡化不少,也清晰不少。

這樣做的話基本上所需引導的邏輯裡面就調一個方法就可以搞定了,具體的實現都在GuideManager 里寫。

坐等大神提供新思路。


新手引導確實是個麻煩事,邏輯雜亂,而且跟界面的邏輯常常交叉在一塊,弄的不好的話代碼里到處都是if else,保存各種臨時狀態變數。但這裡面其實也是有規律可循的,不說完美解決了所有問題,但至少可以提供一個框架,一種問題解決的思路。這裡面有3個關注點:

  1. 引導的觸發點

  2. 引導的進度

  3. 引導的表現內容

傳統的方案實際上也是在解決這3個問題,只不過用的是生硬粗暴的硬插的方式去編寫代碼,代價是容易出現bug,不好維護,而且幾乎不可復用。

就像把狀態機轉化為行為樹一樣,思想上需求去抽象各個點,然後讓它們有機組合起來,形成一個模塊,加入說是新手引導模塊。

假設這個模塊叫做Guide,下面分別闡述以上3個問題的解決方式。

  1. 新手引導的觸發點,一般新手引導是在界面切換後發生的,那比如這個界面有個OnEnter,那一般界面都會有一個基類,所以我們可以在這個基類的OnEnter里注入一句Guide::Instance().Trigger(...界面名稱等參數),當然我們也可以在其他界面統一調度的地方去調用這個Trigger。有時候也可能需要在遊戲幀里去調用Guide::Instance().Update(dt)。這一點上,不一樣的地方在於以前是硬在各個界面里寫觸發,現在是在統一的地方寫,數量上明顯少了。

有觸發入口了,然後到底是否要觸發引導?這個時候就需要Guide里維護一個Trigger列表,簡單比如說是List&,每一個ITrigger上有一個斷言方法bool CheckCondition(...參數自己定),這樣簡單來說,你只要遍歷這個Trigger列表的CheckCondition方法,就可以實現條件觸發了。而裡面的CheckCondition的實現,簡單的可以直接查詢Player的屬性來定(比如說到10級才觸發),或者當前判斷當前是在什麼界面,複雜的甚至可以用IPredicate,AddPredicate,OrPredicate,NotPredicate來實現一棵條件判斷樹,也可以觸髮腳本。遊戲在啟動的時候會通過配置或腳本載入這麼一個trigger列表,已經觸發的Trigger可以從列表中刪掉。

ITrigger想要實現的話可以支持嵌套,變成一棵跟行為樹一樣的結構。

2. 引導的進度,有些引導進度是根據玩家屬性相關的,比如玩家等級,這個不需要專門保存。而有些引導步驟進度通常需要保存,這些可以在伺服器資料庫(網遊)或客戶端存檔(單機的話)保存一個引導表,每一行有引導Id(對應Trigger),進度Index,再附加內容。已經完成的引導可以從玩家上標記。如果只支持引導按序觸發的話,那玩家身上可以保存一個當前引導Id,可以加快不少效率。如果支持非線性觸發,那就從引導記錄上做標記也可。

3.引導的表現內容,一般引導的內容就是切換界面,禁止一些按鈕等等,這個在ITrigger的Run里來實現,可以由你發揮了,看是直接在裡面寫UI或者觸髮腳本什麼的。

這樣下來,就把引導的邏輯和界面邏輯隔離開了,不過實際上因為引導是強關聯到界面的行為的,所以在實現引導的時候,也經常需要獲取當前界面的某個按鈕等等操作,不過至少清晰可維護。

這個方案我自己以前實踐過,還算能滿足需求。


我寫過一個新手引導系統,三個上線項目使用過。兩個頁游一個手游。

思路是,xml配置命名空間、類名以及屬性和條件等,然後update檢測。進度根據配置保存在本地或者伺服器上。

GitHub - nashnie/Guide: Guide,感興趣可以在這裡看一下,不清楚可以問我。希望能幫到你。


不建議刷新的方式,

推薦兩種方式

1.觸發點處拋偵聽

2.架構上給一層底層繼承(不建議給介面,介面需要各自實現代碼太分散)

(偽代碼)

public class UISuper

{

private static Hash _hash = null;

/// 根據配置文件初始化引導信息

private static void initHash()

{

}

public UISUper()

{

if(null == _hash)

initUI();

if(_hash.HasKey(typeof(this))

done();

}

/// 執行引導 --- 判定 執行內容

void done()

{

}

}


引導的分割:

1.對整個遊戲的新手引導進行了分割(依據是什麼?)

首先對整個新手引導進行分割,分割依據是以引導完成的功能來分割。舉例來說:有的引導玩家如何進行簽到,有的引導教會玩家如何進行抽獎等。那麼簽到和抽獎都是一個被分割的單獨的引導。

2.對引導進行了抽象(抽象級別如何?)

這些被分割出來的引導是獨立的,也是之後所有類抽象的最高層

3.Tutorialbase是所有引導的基類,所有被分割的獨立引導都繼承改類。

引導模塊的構成:

1.TutorialTriggerManager管理引導之間的切換(切換的概念)

切換:是指完成一個單獨的引導後,做好進度保存工作,準備下一個引導。

TutorialTriggerManager這個類主要做了下面兩個工作:

一.維護了一個Tuorials列表(讀取一個TutorailConfig的json文件),裡面包含了整個遊戲的新手引導,是一個有序列表,這些引導是按照順序執行的,比如舉例:簽到的引導在裝備升級的前面,只有完成了前面一個引導,才會進入下一個引導。這個json文件已經寫好了所有的被分割後的引導,TutorialTriggerManager會從從第一個開始一直讀取和執行(條件滿足後)到最後一個引導。

二.當完成一個引導後,切換下一個引導,同時把引導的進度上傳到伺服器。

2.每個被分割的Tutorial

這些從Tutorialbase繼承過來的類。每次執行這些單獨引導的時候實例化。它們構成了整個新手引導,他們都有如下的功能

一.包含了當前引導具體的具體步驟,細化到了每一次點擊什麼按鍵。

二.每一個具體的引導都有一個CS文件與之對應

引導模塊的觸發

1.觸發開始

對於每一個單獨的引導來說,從返回該引導開始的界面時,執行檢察看是否達到執行該引導的要求,如果達到則執行。舉例來說,手游裡面都有一個主界面,裝備就在主界面上,所以裝備升級的開始界面就是主界面。所以每次返回主界面時,都執行一次檢查,看是否可以執行裝備升級的引導(已經執行過的引導直接忽略,而且引導是有序的,這很容易實現)。但有些引導不是在主界面開始,比如裝備的有些引導是要進入裝備界面之後才開始引導的,那麼就每次進入裝備的界面要執行檢察,看是否可以進行引導。

2.觸髮結束

這個就比較簡單,在每一個單獨的引導的最後一步,關閉該引導(將他的finished=true)

引導的執行過程:

一.遊戲啟動進入主界面,TutorialTriggerManager被實例化,同時調用它的Init()方法,做一些初始化的工作,具體的工作如下。

1.讀取tutorialconfig,如下圖

這個文件其實就是一個tutorial鏈表,每一個Node都是被分割的引導的信息。這個鏈表是有序的,而且不用在程序裡面實例化,因為在讀取的時候轉換成JsonData了。

進入主界面就會執行檢查,做三件事情,(1)從伺服器拿到該用戶當前解鎖的引導進度tutorialStep,(2)檢查該引導是否已經完成,3.檢查是否達成了啟動下一個引導的條件。

2.開始引導,如果用戶當前引導的finished = false,重新開始該引導。如果finished = true,檢查下一個引導是否可以引導,如果可以則引導下一個引導,如果不行,不做任何事情。

每個分割出來的引導的執行過程:(這個圖可能不太規範,那個介面是ITutorial,作圖軟體不能輸入名字)

主要用到的方法和屬性都體現在TutorialBase中了,Step:當前分割引導(比如說簽到)執行到哪一步(比如按那個Botton,簽到第一步就是主界面的「簽到」按鈕)。首先TutorialTriggerManager會從伺服器拿取引導進度tutorialStep,檢查引導鏈表裡面的step=tutorialStep引導是否完成,如果finished=false,實例化這個引導。當這個Tutorial的Instance被激活之後,首先調用StartTutorial()方法,初始化Step=0,初始化Step用到的字元串。這裡我用圖說明

就是這些,每一步的具體涉及到的按鈕,在有界面的路徑。

StepProcessing()這個方法在Update中被調用。它無時無刻都在執行。它的功能有下面三個,(1).找到這個UI路徑下面的按鈕,Copy到引導的Mask層,並且高亮它,並且獲得按鈕點擊事件,並且把事件綁定到新copy的這個按鈕上。(2)當前步驟完成的時候設置StepFinished=true的時候,對Step+=1,調用下一個步驟,直到最後一個步驟執行完。執行完之後調用TutorialTriggerManager的UpdateTutorial()方法,更新用戶的引導進程,並且發送這個數據到伺服器。並且把該引導的finished設置為true。

如果從伺服器拿到的step=tutorialStep的引導的finished=true,則檢查下一個引導是否可以執行,如果可以執行,重複上面的步驟。如果不可以執行,則什麼也不做。

以上是我的做法,也用在了現在的手游項目上,但用起來不是很方便。不過框架是這樣,遇到要修改的,修改配置文件還有被分割引導裡面的Step字元串即可


不是我寫的,不過跟我的實現竟然一毛一樣 遊戲新手指引的一種具體做法


獨立搭建過兩個大型頁游的前端,也做過新手引導的設計,對於全遮罩固定流程必做的新手引導可以試下這種方式:

1.程序初期框架設計好,盡量降低耦合性。通訊與顯示嚴格分離。

2.前台後台各配置好新手引導信息文件,前台主要是一些顯示、位置、對應操作序號等信息。後台是每一步新手操作後數據變動信息。

3.將新手引導做成模擬操作。做一個位於頂層的新手引導層。比如點擊某個按鈕彈出對話框,或者打開某一界面的功能,相當於點擊到新手引導層對應位置,然後根據數據發送模擬信息,完成操作後,返回後台,後台記錄當前狀態信息。

4.除個別特殊操作,一般的修改新手以及加步驟都可以讓策劃直接修改配置文件即可。

如果是比較靈活的新手方式,比如用戶可點可不點那種我就沒有什麼太好的方式了。大家如果有歡迎交流。


如果是簡單的指引, 無非就是配置表裡每個指引定義id及步驟id, 觸發條件. 然後在遊戲update方法里監聽條件是否滿足, 然後把指引箭頭顯示出來, 指向正確的界面元素.


一句話回答就是不行,因為在通用語言里沒有這樣的功能。所以理論上要麼做源代碼變換要麼做進語言的擴展或者執行的引擎什麼的。

現有的通用語言還是以描述程序的執行為主,並不注重描述需求,程序的源代碼也是基於層級的,所以我們平時寫代碼也都是在「硬插」,只不過「新手引導」作為一個正式的特性讓這個問題暴露出來了而已。

css遲早會被用到編程語言上的呵呵呵。


前排丟標記,最近正頭疼中,策劃是不會考慮這個有多噁心的,瘋狂的追加功能直接導致心都快操碎了


沒做過遊戲,是不是可以試試面向切面,比如Python有裝飾器功能


推薦閱讀:

如何看待守望先鋒的足球亂斗卡Bug換英雄的行為?
質量效應劇情簡介?
假設存儲空間足夠,沙盒遊戲的自由度能被開發成什麼樣子?
最適合拍成電影的遊戲是什麼?

TAG:遊戲 | 手機遊戲 | 新手引導 |