遊戲《蔚藍山》教我的程式設計道理
《Celeste》(譯名:“蔚藍山”)就是這麼一款遊戲。在遊戲裡,你扮演一個名為 Madeline 的女孩,通過跳躍、抓牆、衝刺等動作,去努力登頂一座名為 “Celeste” 的高山。
圖:《蔚藍山》遊戲畫面,它是一款點陣畫風 2D 平臺動作遊戲
正如我在開頭說的,這款遊戲的難度高到令人髮指,玩家平均得死上千次才能通關。但奇怪的是,這款遊戲獲得的成就似乎和它的難度一樣高。在 2018 發售那年,它獲得了 TGA “年度遊戲”提名併成功拿下了“最佳獨立遊戲”獎項。截止到 2018 年底,它總共賣出了超過 50 萬份。
極低的犯錯成本
讓《蔚藍山》大獲成功的原因有很多。精妙的關卡設計、出色的動作手感、令人驚豔的遊戲配樂,以及劇情裡流露出的真誠人文關懷,都是非常關鍵的因素。但除開這些,我在玩遊戲時,還注意到了一個有意思的細節:在遊戲裡,玩家的犯錯成本非常低。
假如你操作跳躍的時機不對,角色掉入坑裡死掉了。然後,在 不到 3 秒鐘 內, Madeline 就會在房間入口處復活。你可以對自己的打法稍作調整,馬上進行下一次嘗試。
並非所有遊戲都給予了玩家這種快速試錯能力。比如在 PS4 遊戲《血源詛咒》裡,一次死亡可能代表你過去一小時獲得的資源全都化為烏有。
註解:
在《血源詛咒》中,玩家死亡後會喪失當前擁有的所有血之迴響(一種遊戲內資源)。如果要找回它們,你需要從一個又一個怪物堆裡穿過,回到你的死亡地點。如果你在路上再次死掉,那麼那些血之迴響就會全部消失。
所以,在《蔚藍山》裡,遊戲設計者給了玩家一種可以 “低成本犯錯” 的能力。有了它,我們可以快速從錯誤中學習,更好的完成挑戰。那麼,如果用程式設計來類比,我們在寫程式碼時的犯錯成本又如何呢?
程式設計時的“犯錯成本”
假設我在開發一個新聞稿管理系統,系統裡目前只有一種使用者:“管理員”。但因為需求變更,我現在得給系統加上兩個新角色:“編輯”和“主編”。
每類角色能做的事是有區別的:
- 編輯:可以提交稿件、修改自己的稿件
- 主編:在編輯的許可權上,增加刊登稿件的功能
- 管理員:可以做任何事以及管理所有人的許可權
為了支援不同的角色,我需要改進現有的使用者許可權體系。首先,我得把和許可權控制相關的所有功能點整理出來,然後開始寫許可權控制相關的程式碼。
沒人能一次寫出不出錯的程式碼,所以寫程式碼,其實就是一個在不斷重複 “開發” -> “試錯” -> “修改” 的過程:
- 修改後端程式碼,增加新角色:“主編”
- 在“主編”相關的功能點,增加許可權保護程式碼片段
- 儲存程式碼,等待本地伺服器重啟載入改動 (5-10 秒)
- 開啟瀏覽器,點選各個功能頁面,確認我的改動是否生效 (10 秒以上)
- 如果測出問題,回到步驟 2,重複整個過程
在很長一段時間裡,我在工作時的開發流程就是上面這樣。我總是在接到需求後就馬上對程式碼修修改改,然後開啟瀏覽器,點點這裡、點點那裡,用肉眼觀察一切是否正常。
使用這種開發方式,假如我某次寫的程式碼有問題,那麼從我每次改完程式碼,到一直走完步驟 3、4、5,整個過程至少得花費超過 30 秒。
如果你不覺得 30 秒很多,請你想想《蔚藍山》吧。在《蔚藍山》裡,角色每次死亡到下次重試的時間間隔是不到 3 秒鐘,二者相差 10 倍。所以,上面這種開發模式的“犯錯成本”太高了。
如何降低“犯錯成本”
其實,在開發這類 web API 時,我們完全沒有必要傻乎乎的手工用瀏覽器點來點去。作為功能的開發者,我們可以(而且有義務)利用自動化測試來加速整個試錯過程。
很多 web 框架都為這類測試提供了幫助。拿 Django 為例,你可以使用 django.test.Client 來輕鬆編寫這類測試:
- # 以下程式碼片段來自 Django 官方文件
- import unittest
- from django.test import Client
- class SimpleTest(unittest.TestCase):
- def test_details(self):
- client = Client()
- response = client.get('/customer/details/')
- # 測試某次請求是否返回了 200 狀態碼
- self.assertEqual(response.status_code, 200)
對於前面的需求,我們可以直接編寫下面這樣的單元測試程式碼。
- # 針對不同的角色定義不同的單元測試類
- class RoleEditorTestCases(TestCase):
- """編輯角色的測試類
- """
- def test_create_post(self):
- # 編輯角色可以正常呼叫建立帖子介面
- response = self.request_post('/posts/', {'title': 'foo'}, current_user=self.user)
- assert response.status_code == 201
- assert isinstance(response.data, dict)
- def test_create_admin(self):
- # 編輯應該無權呼叫建立管理員介面
- response = self.request_post('/admins/', {'user_id': 100}, current_user=self.user)
- assert response.status_code == 403
- class RoleAdminTestCases(TestCase):
- """管理員角色的測試類
- """
- def test_create_admin(self):
- # 管理員可以呼叫建立管理員介面
- response = self.request_post('/admins/', {'user_id': 100}, current_user=self.user)
- assert response.status_code == 201
有了這些單元測試後,整個試錯流程可以得到極大改進。每當我改完程式碼後,只要執行 pytest 命令跑一遍相關的單元測試,就能知道改動是否奏效了。
- ❯ pytest
- ======== test session starts ========
- platform darwin -- Python 3.8.1, pytest-5.3.5
- collected 5 items
- tests/api/test_permissions.py .....
- ======== 5 passed in 0.72s ========
不需要等待開發伺服器載入變更、不需要開啟瀏覽器點這點那。一切試錯任務都可以在幾秒鐘之內完成。
編寫測試其實也是 DRY
我在前面說過,在遊戲《蔚藍山》裡,如果角色死掉了,那麼她馬上會從當前這個 房間入口處 重生。讓我們設想一下,假如遊戲沒有采用這種設計:在新機制下,角色每次死亡後,玩家都得回到本章開始的地方,重新挑戰一遍好幾十個已經通過的房間。那會怎麼樣?估計很多人會氣的把手柄摔地上。
但是,依賴人工測試的開發流程,其實就非常接近於讓人摔手柄的設計。
拿使用者許可權功能來說,因為這個功能非常關鍵,所以我每次做出大改動後,都需要重複驗證一下每個功能點在各角色下的表現是否正常。假如系統裡一共有 20 個功能點需要和許可權掛鉤,那麼 20 * 3 個角色,就是 60 個需要測試的點。
即便我有三頭六臂,每個功能點只花 20 秒測試,整套東西測下來也需要 20 分鐘。
但是,如果你已經為這些場景寫好了單元測試,那麼事情就變得簡單多了。每次做了改動之後,你只需要重新執行一遍單元測試,就能把所有場景都驗證一次。
Django 框架有一條設計哲學叫 “Don't repeat yourself (DRY)” - “不要重複你自己”。多數情況下,我們說 DRY 是指不要寫重複程式碼。但我認為“不要重複手工測試已經測過的東西”其實也可以算是 DRY 的一種。
所以,每當你手動測試一次功能時,其實就是在重複你自己。既然如此,何不將它寫成一個單元測試呢?
“所以,就是在勸我寫單元測試?”
是的,我就是在勸你寫單元測試。作為對比,讓我們看看利用單元測試的開發流程是什麼樣的:
- 修改後端程式碼,增加新角色:“主編”
- 在“主編”相關的功能點,增加許可權保護程式碼片段
- 編寫與功能程式碼相關的單元測試程式碼,與 2 同步進行
- 執行單元測試,如果失敗,從 2 開始調整程式碼,重複整個過程 (幾秒鐘)
通過把測試行為自動化,我們可以大大減少整個開發過程的試錯成本。事實上,自從若干年前養成了寫單元測試的習慣,我就一直堅持至今。那麼,我到底是因為什麼在寫單元測試呢?
- 單元測試讓我的程式碼 Bug 更少?
- 單元測試幫助我寫出擴充套件性更強的程式碼?
- 單元測試讓我在重構時更不容易出錯?
作者:Piglei
來源:Piglei
地址:https://www.zlovezl.cn/articles/what-celeste-teaches-me-about-programming/
相關文章
- 每個遊戲人都要爬座山,或許可以在《蔚藍》找一座山去爬遊戲
- 《蔚藍》:找一座山去爬
- 《蔚藍》音效設計師介紹遊戲獨特的對話聲音系統遊戲
- 《蔚藍》開發團隊公開遊戲新作《Earthblade》遊戲
- 《LUCID》:一款“類蔚藍銀河惡魔城”遊戲的開發故事遊戲
- 【程式設計師的遊戲開發之路】 遊戲架構程式設計師遊戲開發架構
- 這款不需要空格鍵的平臺遊戲,比起《蔚藍》都毫不遜色遊戲
- 遊戲程式設計入門指南遊戲程式設計
- canvas蔚藍星空效果Canvas
- 不爬山的人很少摔跤——Celeste蔚藍的敘事理念與關卡設計(一)
- 幽默:遊戲程式設計與其他程式設計完全不同? - hillelogram遊戲程式設計
- TGDC | 一個遊戲程式設計師的堅持 —— 論向量化程式設計遊戲程式設計師
- 3D遊戲程式設計與設計4——遊戲物件與圖形基礎3D遊戲程式設計物件
- 安利一個好玩的JS程式設計遊戲—warriorjsJS程式設計遊戲
- 【遊戲設計隨筆10】解密遊戲設計的30堂課遊戲設計解密
- 遊戲架構設計——高效能並行程式設計遊戲架構並行行程程式設計
- 遊戲文件與遊戲設計遊戲設計
- 騰訊[程式設計題] 紙牌遊戲程式設計遊戲
- 學習程式設計從遊戲開始程式設計遊戲
- 四大遊戲程式設計網站,邊玩遊戲,邊學Python,拒絕枯燥快樂程式設計遊戲程式設計網站Python
- 幀同步遊戲的設計遊戲
- 【遊戲設計】從“通關率”檢驗遊戲設計遊戲設計
- 遊戲UX設計:地圖設計的考量遊戲UX地圖
- 遊戲設計方法論——非玄學的遊戲設計知識譜系遊戲設計
- 設計模式【8】-- 手工耿教我寫裝飾器模式設計模式
- 【譯】闖入遊戲開發 #3:程式設計遊戲開發程式設計
- 《Python遊戲程式設計入門》7.4習題Python遊戲程式設計
- 3D遊戲程式設計作業93D遊戲程式設計
- 全景探祕遊戲設計藝術(1):遊戲設計師遊戲設計師
- 一腔熱血做遊戲——遊戲程式設計師必須瞭解的事遊戲程式設計師
- 遊戲機制設計:資源管理挑戰與遊戲中的AI設計遊戲AI
- 一名遊戲設計師的思考——遊戲性遊戲設計師
- 遊戲設計-Roguelike類遊戲的一些思考遊戲設計
- 遊戲戰鬥的設計分析遊戲
- 遊戲中的活動設計遊戲
- 遊戲名詞設計的平衡遊戲
- 如何設計好玩的消除遊戲遊戲
- “應對型遊戲”和“計劃型遊戲”的設計特點遊戲