游戏《蔚蓝山》教我的编程道理
如果有這么一款游戲,你操作的角色平均每 20 秒就會死亡一次,正常通關一次,總共需要死掉超過 2000 次。你猜這是一款神作還是垃圾?
《Celeste》(譯名:“蔚藍山”)就是這么一款游戲。在游戲里,你扮演一個名為 Madeline 的女孩,通過跳躍、抓墻、沖刺等動作,去努力登頂一座名為 “Celeste” 的高山。
?
正如我在開頭說的,這款游戲的難度高到令人發指,玩家平均得死上千次才能通關。但奇怪的是,這款游戲獲得的成就似乎和它的難度一樣高。在 2018 發售那年,它獲得了 TGA “年度游戲”提名并成功拿下了“最佳獨立游戲”獎項。截止到 2018 年底,它總共賣出了超過 50 萬份。
極低的犯錯成本
讓《蔚藍山》大獲成功的原因有很多。精妙的關卡設計、出色的動作手感、令人驚艷的游戲配樂,以及劇情里流露出的真誠人文關懷,都是非常關鍵的因素。但除開這些,我在玩游戲時,還注意到了一個有意思的細節:在游戲里,玩家的犯錯成本非常低。
假如你操作跳躍的時機不對,角色掉入坑里死掉了。然后,在 不到 3 秒鐘 內, Madeline 就會在房間入口處復活。你可以對自己的打法稍作調整,馬上進行下一次嘗試。
并非所有游戲都給予了玩家這種快速試錯能力。比如在 PS4 游戲《血源詛咒》里,一次死亡可能代表你過去一小時獲得的資源全都化為烏有。
注解:
在《血源詛咒》中,玩家死亡后會喪失當前擁有的所有血之回響(一種游戲內資源)。如果要找回它們,你需要從一個又一個怪物堆里穿過,回到你的死亡地點。如果你在路上再次死掉,那么那些血之回響就會全部消失。
所以,在《蔚藍山》里,游戲設計者給了玩家一種可以 “低成本犯錯” 的能力。有了它,我們可以快速從錯誤中學習,更好的完成挑戰。那么,如果用編程來類比,我們在寫代碼時的犯錯成本又如何呢?
編程時的“犯錯成本”
假設我在開發一個新聞稿管理系統,系統里目前只有一種用戶:“管理員”。但因為需求變更,我現在得給系統加上兩個新角色:“編輯”和“主編”。
每類角色能做的事是有區別的:
?
- 編輯:可以提交稿件、修改自己的稿件
- 主編:在編輯的權限上,增加刊登稿件的功能
- 管理員:可以做任何事以及管理所有人的權限
為了支持不同的角色,我需要改進現有的用戶權限體系。首先,我得把和權限控制相關的所有功能點整理出來,然后開始寫權限控制相關的代碼。
沒人能一次寫出不出錯的代碼,所以寫代碼,其實就是一個在不斷重復?“開發” -> “試錯” -> “修改”?的過程:
?
- 修改后端代碼,增加新角色:“主編”
- 在“主編”相關的功能點,增加權限保護代碼片段
- 保存代碼,等待本地服務器重啟加載改動 (5-10 秒)
- 打開瀏覽器,點擊各個功能頁面,確認我的改動是否生效 (10 秒以上)
- 如果測出問題,回到步驟 2,重復整個過程
在很長一段時間里,我在工作時的開發流程就是上面這樣。我總是在接到需求后就馬上對代碼修修改改,然后打開瀏覽器,點點這里、點點那里,用肉眼觀察一切是否正常。
使用這種開發方式,假如我某次寫的賣QQ靚號代碼有問題,那么從我每次改完代碼,到一直走完步驟 3、4、5,整個過程至少得花費超過 30 秒。
如果你不覺得 30 秒很多,請你想想《蔚藍山》吧。在《蔚藍山》里,角色每次死亡到下次重試的時間間隔是不到 3 秒鐘,二者相差 10 倍。所以,上面這種開發模式的“犯錯成本”太高了。
如何降低“犯錯成本”
其實,在開發這類 web API 時,我們完全沒有必要傻乎乎的手工用瀏覽器點來點去。作為功能的開發者,我們可以(而且有義務)利用自動化測試來加速整個試錯過程。
很多 web 框架都為這類測試提供了幫助。拿 Django 為例,你可以使用?django.test.Client?來輕松編寫這類測試:
對于前面的需求,我們可以直接編寫下面這樣的單元測試代碼。
有了這些單元測試后,整個試錯流程可以得到極大改進。每當我改完代碼后,只要運行 pytest 命令跑一遍相關的單元測試,就能知道改動是否奏效了。
?
不需要等待開發服務器加載變更、不需要打開瀏覽器點這點那。一切試錯任務都可以在幾秒鐘之內完成。
編寫測試其實也是 DRY
我在前面說過,在游戲《蔚藍山》里,如果角色死掉了,那么她馬上會從當前這個 房間入口處 重生。讓我們設想一下,假如游戲沒有采用這種設計:在新機制下,角色每次死亡后,玩家都得回到本章開始的地方,重新挑戰一遍好幾十個已經通過的房間。那會怎么樣?估計很多人會氣的把手柄摔地上。
但是,依賴人工測試的開發流程,其實就非常接近于讓人摔手柄的設計。
拿用戶權限功能來說,因為這個功能非常關鍵,所以我每次做出大改動后,都需要重復驗證一下每個功能點在各角色下的表現是否正常。假如系統里一共有 20 個功能點需要和權限掛鉤,那么?20 * 3?個角色,就是 60 個需要測試的點。
即便我有三頭六臂,每個功能點只花 20 秒測試,整套東西測下來也需要 20 分鐘。
但是,如果你已經為這些場景寫好了單元測試,那么事情就變得簡單多了。每次做了改動之后,你只需要重新執行一遍單元測試,就能把所有場景都驗證一次。
Django 框架有一條設計哲學叫?“Don't repeat yourself (DRY)”?- “不要重復你自己”。多數情況下,我們說 DRY 是指不要寫重復代碼。但我認為“不要重復手工測試已經測過的東西”其實也可以算是 DRY 的一種。
所以,每當你手動測試一次功能時,其實就是在重復你自己。既然如此,何不將它寫成一個單元測試呢?
“所以,就是在勸我寫單元測試?”
是的,我就是在勸你寫單元測試。作為對比,讓我們看看利用單元測試的開發流程是什么樣的:
?
- 修改后端代碼,增加新角色:“主編”
- 在“主編”相關的功能點,增加權限保護代碼片段
- 編寫與功能代碼相關的單元測試代碼,與 2 同步進行
- 執行單元測試,如果失敗,從 2 開始調整代碼,重復整個過程 (幾秒鐘)
通過把測試行為自動化,我們可以大大減少整個開發過程的試錯成本。事實上,自從若干年前養成了寫單元測試的習慣,我就一直堅持至今。那么,我到底是因為什么在寫單元測試呢?
?
- 單元測試讓我的代碼 Bug 更少?
- 單元測試幫助我寫出擴展性更強的代碼?
- 單元測試讓我在重構時更不容易出錯?
以上可能都是。但現在,我可以往上面的列表里再加上一點:使用單元測試來開發的過程,有一種流暢感,失敗后就馬上重試,一切就猶如在操作 Madeline 登頂那座蔚藍色的山。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的游戏《蔚蓝山》教我的编程道理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 白鹭引擎助力《迷你世界》研发团队开发3D
- 下一篇: Unity 分离贴图 alpha 通道实