2018年4月20日 星期五

[翻譯]Rogue Legacy設計筆記

原文網址:
http://cellardoorgames.com/rogue-legacy-design-notes/





這些不是你所想像的正規設計文件,它們不只難以閱讀、而且有很多缺陷。這也不是一般的遊戲製造流程,我們是一對兄弟在家裡工作,我們用SKYPE去和美術及音效溝通。若你將其應用在更大的團隊上,事情可能不會進行的這麼完美。我們同時也蒐集很多的素材。

Rogue Legacy需要兩週的時間來開發基本引擎及關卡編輯工具來製作這款遊戲。主要的設計文件都是在這時期設計的。所以這遊戲的內容及工具同時間被一起做出來,像是雞生蛋問題一樣。

很多的文件只是初期設計,額外的文件是在Rogue Legacy開發期間一邊進行的,但是有更多特別的狀況是在產品階段被發現的。這些並沒有特定的時間順序,我們也不知道它們的正確順序是什麼。

Teddy Lee
Creative Designer

#1

一開始這款遊戲叫做Rogue Castle,之後我們將名字改為Rogue Legacy,讓它更貼近遊戲本意。我現在還是滿喜歡Rogue Castle這名字。

這是最早遊戲各個狀態的設計。有兩項數值之後被移除了體力(Stamina)及移動速度(Move Speed),體力是個玩家揮砍的可用量,限制玩家無用的揮砍。移動速度則被移到符文系統。玩家本來是通過擊殺怪物來取得經驗值。經驗值可用來升級技能樹。而這些技能被稱為特徵,因為你利用經驗值去強化你基因的強度。這頗令人混肴。這個名稱在開發期造成很大的困擾。

本來的金錢可以兌換技能及符文,被改成金錢兌換符文、經驗值換特徵,而且還有些特徵是這邊看不到的(例如:色盲、肌肉萎縮)。

由於我們曾經更改過命名,我們兩個的溝通常常不是很順利。我們名字用了越久,要更改它的難度越大。所以如果你要做款遊戲,你的系統是很相似但是卻又不一樣的,最好在一開始就確定其術語。

雖然你也許不相信,Rogue Legacy不是我們第一次做城堡升級的系統。我們第一次做這個系統是在Villainous、一個守塔遊戲。我們需要做個相似的系統在RL。這有很多工作需要做,所以與其做個複雜的城堡,我們決定做個相對簡單的樹狀結構。(我們也參考了族譜的樹狀)。我們決定停止Villainous,它是個可完整成長的莊園。因為它是個完整遊戲的一部份,所以早期稍有不注意就會造成錯誤。

因為這是個容易死亡的遊戲,升級畫面也同時出現在這時間點。

當你死亡時,家族傳記同時也被當成排名榜。我們最後只稱呼其排行榜,讓其比較容易視覺化。

#2

這份筆記描述更多有關我們的舊的技能樹。

Rogue Legacy的技能樹原始設計其它是像是Kingdom Rush,而我們稱呼技能為"圖騰"。當你放置技能在較低階的圖騰,更高階的技能就被解鎖。

這系統現在看起來很奇怪,但是回頭來看遊戲的主要目標一直沒被設計。我們想要讓擊倒各個頭目會得到極大的獎勵,所以他們本來會解鎖更多圖騰讓玩家來裝備技能。

接下來是圖騰技能的獎勵,這並不是完整列表,因為早期設計還不需要這麼明確的列表。取而代之,更重要的是讓玩家知道你未來想要的技能什麼時候能取得。讓長期規劃變得更加重要。

#原始的技能樹設計。這還真的是棵樹。

你可以看到筆記上我們比較注重被動技能。但即使他們是被動的,每項被列出的技能都有其特別的實作方式,他們有其各自特別的實作方法(雖然我不是工程師)。

例如:

-14:陷阱的傷害降低,這表示我們需要能特別調降環境造成的傷害。
-19:更高的掉寶率,這表示我們需要一個公式來調整寶物的取得及機率。
-20:減傷系統,這包含了對所有傷害的抵抗力或是對特定傷害的防禦力。
2,3,4 我忘了這到底在寫什麼,看來十分愚蠢。

#3

更多關於技能樹的筆記。在右上方可以看到我所畫的遊戲循環。

Play->Fun+Change->Death->RPG

這個循環是我們一開始就打算做的。分析所有遊戲內的機制都是為了滿足這份體驗,像是你在其它RPG所遇到的一樣。這是一小部份的筆記,但這是真的很重要的一部份,這也是為什麼它會被寫下來。

這裡也有寫下特徵系統為是如何被看待的。它們看起來像是圖騰。

我們也有記下角色職業的筆記。所以你可以看到原始概念,騎士可以擋住傷害、忍者可以瞬移。我們也有另外的職業可以衝刺攻擊,但在早期的設計被砍掉了。衝刺移動為了重新滿足符文系統後來被加回去。

在這你也可以看到原始的遊戲控制設計。B鍵是職業的特別能力,Y是藥水(我們本來打算設計讓玩家可以有可消耗道具)。

大多的項目在最後版本都被徹底簡化。像是以上所說的,衝刺變成符文,Y鍵變職業特別能力,技能變成單鍵使用而不是方向鍵加動作鍵。而藥水成為臨時消耗品。

這些大部份的原始設計大多沒有成為產品。我們常在重新檢視我們的遊戲,並將某些項目開始做之前就刪除。這也是另一個原因,為什麼我們不看重設計文件。它讓我們的行動不再敏捷。

很多人說實作雛型是重要的,但同時給予雛型方向也是很重要的。如果你做的事只有10%機會放到遊戲內,則這是浪費時間的。所以將某些意見刪除是可以省下很多時間及金錢。

#4

這份文件描述地圖生成器是如何運作。

上面的字都很簡單,關鍵字足以勾起我的回憶。我的經驗中,一長串的文字解說未必有幫助。有以下幾點原因。

1 可以節省設計者的時間
2 這只有設計者會讀這文件
3 這限制了創意的發想
4 這不是個理想的資料給程式或美術。因為我並不是專長於那些領域,所以寫下我所知道的細節,對工作並不會有幫助。

當這些事被寫下來,這同時也賦與了這些意像其創造力。因為它是真正被正式記錄下來,而不是只是隨口說說。我知道這聽起來很好笑,但這是真的。至少我認為是真的,對我而言。

同時,事情也從未照所寫下的執行。這也幾乎不可能去預測這個點子多困難被實現,這最好能保持單純。這樣當另外的團隊成員加入時,可以隨時更改它。同時,人們也總時很懶散,而非愚蠢。他們總會將點子的印象記在腦子,如果他們忘記了,沒有人能從60頁的厚重設計文件內去找出一個正確的答案。

** 重申一次這份文件在製作時持續改進,而且我也從未在5人以上團隊工作。

#5

這份筆記詳述了舊職業及特徵系統。本來基因特徵系統並未存在於遊戲內,特徵有點像是天賦。一個角色可以擁有主要加強法力的天賦,而副天賦力可以小幅強化其它能力。這雖然有點複雜,但這就是在我們重新建構新系統前的原始天賦系統(新的是基因系統)。這是少部份被移出正式版的特色,另一方面它也讓工作變得相當麻煩。

這遊戲同時也有可以多次升級職業。下面的例子是"礦工、冒險家",而騎士及法師就沒有舉例了,因為他們是相業單純的職業。我們必須先確定這個系統能應付最複雜的職業需求。這份筆記上,冒險家能強化藥水能力。

這算是非常無用的能力。

#6

這頁描寫的很單純是整個遊戲的概略設計,你可以在腦中視覺化這些項目,且你的筆記會涵蓋這些項目。這頁算是一種思緒的整理,像是重點筆記,這歸納了我的所有想法,確保沒有遺漏的部份。

歸納自已的點子也是個將工作確定的好方法,因為這可以強迫自己去想過一次整個遊戲的各個項目。你會發現其中多餘的設計。歸納可以讓你整合及移除遊戲中不需要的元素。

這頁的下方簡述了戰鬥,而更下方則是初始關卡結構。

我們知道自己想要找到方法去固定部份遊戲,但又要能提供每次遊玩的不同體驗。初始想法是擊倒一個魔王後有部份的關卡會被鎖定。這想法很早就被丟棄了。理由如下:
1 太難去實作
2 太難讓玩家瞭解
3 太愚蠢的點子

#7

這額外的筆記你可以看到更多天賦的例子(例如:舊的特徵)

這天賦下方是我們的新消費系統。之前有提到Rogue Legacy原本擁有XP及金錢系統。金錢可以升級圖騰(後來的莊園)。這個金錢即使死亡還是會存在,XP則會被重置,除非你拿去升級,否則死亡時所有XP都會歸零。

每次升級你的基礎能力都會上升,而且這有個公式確保玩家可以不斷升級。這個系統太複雜了,而且我們遊戲要用XP升級,看似不太適合。

這是個困難的抉擇,但我們決定最佳方案是拿掉其中之一的消費系統。所以我們把XP給拿掉,將金錢變成唯一的升級方式,而且金錢也取代原來的XP作用。

這個改動造就了我們最具代表性的想法之一。舊的XP系統提供了rogue-like的概念,因為每次你死亡你就流失了所有XP。我們不能對金錢做同樣的事,所以我們必須用其它方法解決。最終決定建造這座城堡。甚至將死亡掉落金錢給拿掉,迫使你在重新進去城堡內前,花光身上的錢。

最後一點,我真的討厭砍去XP系統。不只因為我喜歡它,因為我曾經加入了它,我應該更有遠見去考慮到這是不適合的。我並沒有仔細去想過,我應該可以避費浪費許多開發時間。

XP是另個被完全砍去的系統。XP內的項目也沒有被保留在遊戲的其它部份。連帶受影響的不只設計者,而是整個團隊。所以嚴格看待你提出每個點子。

#8

這個篇幅比較短。只是記下通常的平衡問題。我一開始希望

玩家技術 >>>>> (遠大於) >>>>>>  遊戲時間

但我們仍然希望即使玩家技術不好的人可以過關,所以花時間累積是個可行的選項。這面就是在說如何管控玩家傷害、生命等。確保玩家在短期遊玩時不會感到突兀,但長時間會對遊戲有影響。

#9

有關Fairy Chests的概念寫在這裡,連帶發展出符文系統。

符文系統允許玩家將用類型的符文裝在身上,與能力相乘成長。

原本符文系統的點子是比字面上更加強大。例如:一種符文是給予陷阱無效,而加上額外符文時會再有另一項傷害無效。當然不能強到過頭,所以我們限制符文的上限是5個。這仍然使技能無效的點子太過複雜,不只針對程式,還包含如何顯示給玩家。所以我們拿掉所有傷害無效的符文功能,為了更加簡潔的系統。

陷阱無效最終成為了特別的道具(飛鞋)。
#Hermes boots宙斯之子Hermes的鞋子,法國時裝品牌愛馬仕。

飛鞋讓故事更多了些浪漫的氣氛。因為我們會不斷重複遊玩,所以我們決定拿掉有特別背景的技能,讓我們有更多的創作空間。

#結尾

就這樣。希望從這些枯燥的東西帶給大家些樂趣。

2018年2月7日 星期三

[翻譯]製作20XX的故事(上)

原文:
https://www.gamasutra.com/blogs/ChrisKing/20171115/307769/Making_20XX_Listening_Well_and_Lucking_Out.php

嗨,我是Chris King,我最近釋出了遊戲20XX。20XX是個洛克人X、ROUGE-LITE、結合傳統橫向捲軸動作(classic action platforming)及自動生成關卡(random level generation)及永久死亡(permanent death)的遊戲。
20XX的最終美術畫面,我們重新設計了好幾次。

這文章中,我將說明開發中的重大時間點,並且稍微說明過去這四年我們學到了什麼。

開發真相


20XX是用C++自製引擎製作。我為了履歷而自己製作引擎,本來打算遊戲完成後可以用遊戲開發者的稱號來找工作。我從如何畫出三角形開始做起,20XX也是從這時候開始。我們實在應該直接用Unity的。

產品開發時間軸

第一天: 2013/7/1

Steam Early Access: 2014/11/25(512個開發日)

推出1.0版(離開Early Access) 2017/8/16(1507個開發日)

詳述時間軸

2013/7/1 開始開發20XX

我做了一個非常簡單的20XX雛型,且到reddit(/r/gamedevclassifieds, /r/forhire)去找到遊戲美術,Zach Urtes。我們簽下了6個月的專案合約,準備去完成我們絕對完成不了大架構。他創造出了第一個Rollster角色(看下面的雛型美術)。

我存下了第一個雛型的畫面,還有"ix"的血量條。
ix代表了第9個我們開發的角色模組,但我們在Mighty No. 9出現後,把它改掉了。
紫色的條狀是梯子,這是我們所捨棄的企劃,很可怕的梯子。

2013/8/31 洛克人之父"稻船敬二"發起了Mighty No. 9 Kickstarter

我花了一個星期的時間去觀查他們的Kickstarter及它所帶來的群眾效應,我覺得被完全打敗了。我花了很多時間(我那時候非常沮喪)研究MN9的專案價值,而且我得到2個重點:

1 MN9的盛大迴響證明了傳統洛克人遊戲在市場上仍有需求

2 我們都有著各自的想法,但我們是非常不同的遊戲。我知道嘗試去做一款和洛克人一模一樣的遊戲是不明智的,所以我做了一些基本上的重大變更。加入我最喜歡的roguelikes及重複可玩性,我們給予了20XX隨機生成關卡及永久死亡懲罰。加入我喜觀的各種怪物及莫明奇妙的特色。我們同時設計區域及線上的多人遊玩模式。這些都是MN9沒有的大賣點。

我們幾乎要放棄20XX了。但是我選擇了去相信這世界是足夠容納20XX及Mighty No. 9的。

2014/1/1 Zach的合約超過6個月的預訂週期

我們決定四月去PAX參展,並開始我們的Kickstartar及Greenlight專案,幾乎是同一時間。我為所有我能找到的發行商做介紹,並全部被拒絕了。(我相何他們做了很合理的決定,我們在那時候還不是很好的遊戲。我們用來簡報的雛型也很簡陋)

我開始尋找音效設計師(啊!!我們真的用暫代的音效展示給開發商)。我們第一次出擊並不讓人印象深刻,我們是這樣告訴自己的。

- 我現在還記得我很興奮地送出開發版本,也記得因為對方不想幫20XX做音效所遭受的打擊 -- 因為這是個不好的專案。我很想為自己辯解,但我選擇問他哪裡不好、哪裡我們該改進,並且感謝他給予我們一長串的改進建議。這給了我們很大的改進空間,並讓遊戲變很更好。這教了我如何從批評中取得資訊。這成為20XX開發的基本要素之一。

我們找到了音效設計師Brandon Ellis(Cityfires),他為我們做了20XX中所有驚人的音樂。Brandon 確定了我們的風格,讓遊戲立刻大大地躍進。

2014/4月  我們同時參加PAX East、開始greenlight及Kickstartar專案。我們開始有點厭惡自己了。

Kickstartar是個全職的工作,在第一天我們決定不要一直盯著專案網頁看。若你打算將Kickstartar與展覽結合,在展覽結束後再開始KS並積極地搜集所有展場上對你有興趣的玩家的EMAIL,可以讓你不會忙到崩潰。

- 我們從第一次出展PAX中學到了很多,這些經驗我過去曾經記錄過,過去的我可能比現在的我更能說明這些經驗。

- KS是殘酷且高壓的事情,我們做的是正確的事但沒辦法將所有時間花在其中。事情過後,我們瞭解到專案過早進行了,等視覺做的更好後可以有更好的成果。但在那個時間我們並不知道這些事。我們的回覆也太過於輕浮且不專業,這會傷害到我們自己。

KS的這個月是我人生中最痛苦的一個月。我若沒有強大的耐心及朋友的幫助是撐不過來的,包含了Bianca(早期行銷、社群推廣、注意東方市場、處理KS之外的垃圾事務、和我結婚"這特別重要")、DJ(雖然沒有和我結婚但幫我建立了初期我們所需的網路推廣)、Chase(測試一堆我們早期的糟糕版本、在我做出一堆垃圾時還持續支援我)。
2014/4月的美術畫面

2014/9月 我們與Fire Hose Games合作

我在IGDA 的會議上遇到了Eitan。我們同意他幫助我們讓遊戲變得更專業,令它可以到足夠販賣的程度。這是我們其中做過比較好的決定之一。Eitan有很多我們所缺乏的業界經驗與人脈。

我們將專案的規模放大了一點。取而代之不在11月釋出最終版本,而是決定釋出Early Access版本,且藉由社群的意見建構剩下的部份。

2017/11月 20XX釋出Early Access

第一天我們只賣出了64份20XX。我嚇到了,我們做了一連串的承諾給社群,而且盡了我們最大的力氣去維持。

- 我們承諾在Early Access期間,每兩週更新一次,我們在每二個週三的晚上6:00更新。我們在遊戲內放了個時鐘,清楚地告訴玩家下次更新什麼時候會出。我們很準時依Early Access的計畫更新,大家都很喜歡我們的更新時鐘。這使我們獲得很好的社群印像,而且給我們很穩定、有週期的方式得到我們接下來工作的反饋。

* 千萬不要許下自己做不到的承諾。若你訂下個日期但沒做到,那會比沒有任何承諾還糟糕。如果我沒記錯,在70次的更新內,我們有2次沒有準時達到,一次延遲了1天、一次延遲了1小時。

- 我讀過每篇在Steam社群的文章、每篇在reddit /r/20xxgame 的回覆。(/r/20xx是被廢棄的舊討論區了),及每篇e-mail。我回覆每一個人,而且當社群成長,有一部份玩家會對我提出更多平常的問題。

* 我們的成功一大部份來自於長時間的分析社群反饋。篩去情緒上的字眼再去分析剩下的。當玩家很喜歡你的遊戲時,不會只用客觀的角度提出建議。同時也不要將憤怒玩家的好建議給踢除。

- 我儘量將我們所做的事對社群公開,讓玩家的意見來決定我們的規劃。若大部份的玩家對某Bug十分在意,我們會停下手邊工作來先修復它。若社群認為我們的方向錯了,我們就認真的論是否我們要修正方向。

* 在2015 9月時,我們花了很多時間改造Ace及Nina的角色設計。
上面是20XX重新設計的角色(2015夏季)
下面則是之前的設計

- 新的設計讓我們獲得了前所未見的負評。我們十分沈重的討論,放掉所有我們決定在Beta所做的項目,將心力花在重新打造舊的角色設計(藉由之前錯誤設計的教訓)且剛好完成新的展示影片及宣傳在beta釋出前。
重新設計後的角色們,在推出前又再次改進了一次

- 這次的調整是我們最大幅度的修改我們的計劃。我很確定如果我們堅持修正我們的槍而不去重新設計我們的角色,我們在beta時就會淹死了。

* 對社群開放不表示你要過渡承諾,分享你可以掌控的項目。

2015/3月 了解到我們的遊戲需要提供視覺設計,我們雇用了Clemens Scott

Clemens(最近的作品是Broken Rules工作室的Old Man’s Journey)花了6個月,將我們的遊戲由"沒有吸引力的"變成"極具特色的"(巨大的躍進)。沒有了Clemens的幫忙,20XX宣傳不會如此成功。

2015/4月 我們第二次在PAX展出我們的作品,這次在我們喜愛的Indie MEGABOOTH展出。

這次比我們上次展出好多了,而且我們甚至得到了些宣傳。接下來的一個月,MANvsGAME 及Zeke(Ezekiel_III)開始實況20XX,那時是凌晨兩點,我正在Bianca(女友/現在是未婚妻)的家裡。我一直看到凌晨五點,好險他們沒有把我怎樣。有了Clemens的第一波美術改進很令人興奮,這是很有幫助的合作,我們在隔天就賣出了64套遊戲。而且我簡直如同過節般的快樂。

在9月15號,Mighty No. 9也宣佈了他們的第一次延期。這並不是什麼壞事,延期總是會發生,這時他們也解釋了為什麼會延期、且告訴大家正在努力。

2015/5月 資金快要見底了。Bianca私人大力的贊助我們。她是真的是我們的天使。我去銀行貸款了,所以我可以繼續雇用Zach。

2015/8月 Mighty No. 9第二次延期了。這次他們處理的比較不好(有很多文章講這件事-例如這篇https://kotaku.com/the-story-behind-mighty-no-9s-shady-delay-1722766843)。Mighty No. 9損失了不少支持,我們覺得這是宣傳的好機會。

在MN9宣佈延期不久後,我們發佈了Beta版本。我們受到前所未有的媒體關注,我們由販賣不到多少款變成銷售成績不錯的遊戲。

我們仍然很辛苦的在開發,但從現在開始20XX賺得的錢比開發費用還要多了。蜂擁而來的玩家、收入、與評價完全出乎我們意料之外。之後的一個月,我們保持著穩定的銷售狀況且終於還完了借款。

2016/1月 20XX已經成長了。我們決定在每個更新前都需要社群的意見。

我開啟了我們的"超級好友"群組,為了在上線前的早期開發就能取得玩家的意見。我們盡了最大努力讓早期版本能覺得好玩。上線前看新的物品及關卡是很有趣的,但測試引擎的改版則是不是。

我們提供他們初期的開發關卡,能完成這些關卡真的是需要很多好運。第一版的這些關卡還需要很大部份的改進,而超級好友的意見正是改進的重要來源。

2016/5月 我們決定20XX應該有些過場畫面(所有專業的遊戲都有過場畫面),所以我們開始徵人。

過場畫面真的是件很難做好的工作。我們花超出預算且整個流程花了14個月完成。我們之中沒有人專精於此,所以要討論我們到底要找什麼東西是困難的,但經過了這一切,相較於我們學到了許多東西,我更覺得我們是僥倖存活了下來。

2016/6月 Mighty No.9 PC正式版釋出
玩Mighty No. 9不如玩這個吧.

在MN9釋出之前,20XX最中肯的評論是"如果你不想再等Mighty No. 9,你可以來玩這款"。Mighty No. 9發售之後,雖然它做得還不錯,但仍不如它所宣稱的那麼好。這些評論也改變了,很好運地,MN9釋出的日期正好差不多是Steam的夏日特賣,20XX剛好接收了對MN9失望的玩家。

從此開始20XX的開發變得順利許多。我們維持了一定的銷售量,並在每次特價時期提供20-25%的折扣。直到2017/8月1.0正式版釋出為止都十分順利。

2016/12月 我們發佈了"piecemaker"試驗,讓玩家可以使用我們恐怖的關卡設計工具。

這個遊戲工具糟透了。超過一百位自願實驗者,只有兩位有拿出一點成果,而其中之一將它放入了遊戲。這百分之百是我們的錯 - 我們的關卡設計系統太笨重且很難以上手。

- 做一個更好的編輯器成了1.0之後的問題。我們仍然(至2017/11月為止)在思考這是否為20XX合理的計畫。(如果你很有經驗做遊戲編輯工具快來聯絡我吧!)

2017/8月 過了71個持續性的隔週更新,20XX離開了Early Access,在PC上推出了。

我們做了新的展示影片,但這100%仍然是動作取向。(歡迎大家關於這點提出意見,我們認為這方面還有很大的改進空間)

之後的1個月,我們和幾乎每個對我們遊戲有實況主接觸,並親自寫了信件給媒體。我們並沒有得到很多回應。有3個比較大的媒體會在發行時幫助我們,這是其中一個。(Tim的影片十分強大,即使你不喜歡20XX)。當Kotaku影片發佈時(約遊戲發行後的第3天),我們的銷量比前面的2天暴增了30%。雖然很難指出多少是被影片吸引的(20XX在週末也總是賣得比較好),但這仍然給了我們很大的幫助。

有一大部份來自於我們的發售時在Steam的銷售排行前20-25名。Steam的排名拯救了我們的能見度。雖然我們在媒體上沒有很到很大的曝光,但在Steam的演算法下我們賺到了不少,我們在發佈的首週表現得很有競爭力。

最終產品

未完待續(下篇會歸納從製作中習得的經驗)

2018年1月30日 星期二

[翻譯]專案管理 Part1: 避免遊戲工作破壞腦袋


原文:
http://www.midnighthub.com/blog/2017/11/01/project-management-part-1-avoid-brain-damage-from-working-with-games/



作者:Sara Casen
遊戲開發者,過去在Paradox及Tarsier任職。現在經營Midnight Hub,之前在Casen Crowd,社群經營公司。

為什麼這麼多的遊戲工作室超時工作?如何避免你的組員的熱情燃燒殆盡?以及如何讓你的開發夥伴投注熱情及創意在工作上?及有什麼方法可以取代惡名昭彰的遊戲開發?若你是個管理者從專案開始便將超時工作視為正常,或是認為燃燒熱情本來就是製作遊戲的一部份,那這一系列的開發文章不是為你所寫的。但是你若好奇為何我們花上數個小時來寫這篇文章,你想去最小化你的團隊的壓力、或你想要些遊戲專案管理的秘方,那就讀下去吧。

遊戲製作生涯的平均長度

這個夏天我參加了一個遊戲CEO及管理者的聚會。這個聚會在德國旁的小島上。北歐國家的管理人都在一起討論遊戲產業的現況。在這會議中,大家談論了北歐遊戲製作生涯的平均長度。猜猜看是多長?答案是4年!平均開發者會在4年之後離開遊戲產業。他們可能燒光了熱情、犧牲了健康、或花光了資產。我並沒有確切的數字,但有報導指出這個產業是如何一次次的消耗熱情,使其產業活下去。超時工作被視為常態,一週工作80小時並不少見。但科學證明這並不能讓遊戲變得更好。

在過去百年來的工業研究證明了以上的問題,過渡工作的工人造成更多的錯誤、計劃延宕、損壞工具、多餘的支出、降低貨物的品質、甚至影響收益。他們對於專案、管理、員工、其它人甚至自己都是危險的。
– Evan Robinson, IGDA “Six Lessons On Why Crunch Mode Doesn’t Work”.

在2015年我們開始了自己的工作室 "Midnight Hub",我們想要做些改變。之前我們曾經在Minecraft、The Division及其它專案工作。我們的目標是用穩定的方法持續產出獨特的遊戲。我們是5位獨立開發者,現在花了兩年在第一個遊戲上。"Lake Ridden"。這是個單人第一人稱充滿神祕的解謎遊戲。我們並非總是做對所有事情,但我們仍然努力去嘗試。

這段時間我們儘量不超時工作,我們仍有效利用工作時間且產出許多我們遊戲的文件。我們團隊儘力做出最大效率的產出,並且確實記下每個人的壓力狀況。我將在這分享專案管理的哲學。我們是如何最小化壓力所帶來的影響,並且讓遊戲可以準時在2018春季上市。這包含了許多的層面,所以我將它拆成三篇文章,每篇專注在不同的方向。

提醒一下大家,我們是生活在社會主義的瑞典,這邊一般工作約佔每週40小時,而且員工有權力要求數週的休假及帶薪產假、陪產假。遊戲產業在這邊會更加的扁平化及外國人稱讚我們會主動幫助工作上的夥伴。我們通常也鼓勵彼此做各種嘗試,並且也不認為上司會知道所有的知識。不幸地我們也是會被壓榨的,很多人在遊戲業的工作時數也很長。犧牲假期去追求永遠也無法完成的完工日期。最後迫使他們帶著極大的工作傷害離開遊戲業。

為什麼我們以這種方式工作?


我第一個遇到擁有真實人生的遊戲開發者,是在DICE的Battlefield 1942團隊。他告訴我團隊是如何被消耗到最終失去了他們的熱誠。他們睡在自己辦公桌而不回家,他們的腦袋漸漸地變得緩慢,人們忘了如何去談話、憂鬱、失眠、漸忘。這些都是持續強烈的壓力的徵兆。這些逐漸地傷害你的身體、神經系統、及腦袋。思考看看由製作遊戲造成的腦部傷害。這是有可能被修復的,即使在有社會醫療良好的瑞典,也要花一段很長且辛苦的時間。

在這種工作環境很容易發現一個問題,一些很優秀且具熱情的開發者會不斷的流失。而且當他們開始覺得這是正常的,所以認為忍耐才可以讓事情完成,認為這樣才可以讓遊戲做出來。遊戲當成藝術媒介或故事媒介,這並不是個累積業界經驗的方法。因為有想像力的創作者會在幾年內被榨乾,大多數的人們不會想再次回到遊戲業。那個他們花費許多投資及時間去完成作品的地方,同時也將他們燃燒殆盡。若你也曾經因過渡工作而腸思枯竭,甚至你還需要面對這種症狀數十年。而我們是如何讓遊戲媒體帶邁出這種困境的。

當你擁有創意的人、對工作的熱誠、完美主義者,並將他們混進數千萬預算及糟糕的排程,事情通常會變得很糟糕。人們被強迫超時工作,最終失去他們。不僅是過渡工作同時也無法讓遊戲品質提升。

公司的佈告欄,給予大家分享及鼓勵彼此

為何要用穩定的方式製做遊戲?


所以為什麼我們想要減少壓力、避免超時工作及從第一天就開始防止員工流失?也許是我瘋了。但能成為一個正常的人類感覺很好。若你想用同一批優秀的人才做出更多好遊戲,你該避免讓他們過渡工作。壓力降低了我們的耐心及溝通(你是否看過團隊成員在類似狀況"我不想立刻和你解釋,快去處理它")。遊戲開發是一個團隊運動,若你不打算溝通會造成分崩離析。據我的經驗,壓力是人們開始爭吵的主要因素。

另一方面,人類(抱歉,我遇到某些管理者視人類只是種資源),我的專案管理經驗中,大多份管理者只把人類視為一個可壓迫的變因。若金錢、時間、其它因素是不可變的,那人們就會驅向更加壓迫人類變因。那是因為你很難去衡量能由人類身上取得多少。有的人每週可工作80小時,有些人每天工作6小時後就沒力了。所以管理傾向儘量壓迫人類資源。你知道你在銀行的金錢什麼時候會燒光,但你的員工呢?當你知道迫使人們會失去熱誠,可能已經為時已晚。在我的觀點中,取得金錢要比修復人們的工作傷害要簡單多了。

較短工時的科學根據


有很多研究顯示更多的工作時間並不等於更多的產出。就像是做多件工作比較划算一樣(還需要付出切換工作的成本),讓你感到很有效率但事實不然。若你是在做高腦力的工作,像是編寫程式、寫故事,實際上人腦一天只能夠專注約六小時。過了這段時間,你開始懈怠且開始犯錯(無論如何,這仍然會讓人們感到有效率),然而你必須在隔天整理昨天幹的蠢事。專注於聰明的工作,而不是努力的工作。我相信聰明的管理者該妥善利用各項資源,來做出最佳的遊戲。為什麼不該把這些技能也用在員工身上。

找到新的團隊成員、安置他們、提升效率、確定他們符合團隊等等,是一項艱難的工作。所以照顧員工一直以來都是個好的投資,總比盡力壓榨他們然後再找新的人來取代。老實說這也是因為我們相信一定有更好、更人性的方法來做遊戲,讓開發者有效開發及玩家喜愛。

製作迭代前後比較。迭代是Midnight Hub的主要遊戲製作流程。白色方塊是測試元件,如上圖

事實上大部份的壓榨及超出排程、浪費金錢來自於錯誤的計畫、規模。當然像是引擎意外更新、相同市場上的競爭者、主要成員的離開等重要的因素也是。但我想你若能用乾淨的方法管理你的遊戲專案,會比你增加員工的工時,讓遊戲更有機會準時上市且打造更長遠的團隊。更重要的是,這是你少數你可以真正控制的變因,你無法控制主企劃突然去了地球另一端、或是你的遊戲被破解、或新的遊戲引擎版本讓你的遊戲壞了。但你可以且應該控制你的計劃、遊戲規模、及專案管理,不該有任何藉口。

關於縮減壓力的實際例子


這是我們讓所有人不被壓垮在Midnight Hub做的事。記著若你是領導者或資深開發人員,所有員工都會看著你所做的事。若你不想要讓別人超時工作且親自執行,那所有人會開始真的相信他們不該待在公司太久,言教不如身教。

-減少在公司的時間。我們待在公司9:00-17:00,且有1小時的午餐時間。
-所有人同時間離開公司,沒有人會獨自留下辬公。
-管理必需避免員工超時工作及壓榨。
-所有員工放的到特休
-若你正在處理巨大的任務,可以調整成只工作80%或50%時間。因為那些任務可能會榨乾你的精力。他們可能是你從來沒做過的事,非常困難,要花了數週、數月去完成,而且做錯了會有很嚴重的後果。像是擬定開發合約、對投資人的簡報等。
-規畫時間緩衝放假的心情
-在假期內完全關閉辦公室,不讓任何人單獨工作。
-員工生病假的第一天就可以拿到80%薪水(瑞典通常第一天不支薪)
-在規劃時期給出更佳且更實際的時間估算
-用ABC法則製做遊戲(接下幾篇文章會提到)
-在每週開會評測每人的壓力


整理一下

在Midnight Hub開始時,我寫了有關如何白箱測試遊戲的文章,迭代開發每個部份。我們如何用敏捷開發的方法,將測試用美術轉成可正式美術素材每次一個迭代。這方法讓我們不用花費過多時間就可以有機會測試它,我們現在將其粹取出來。我會在下一章解釋如何用這方法,讓你有機會試用這配方,我們叫它"ABC"。之後我將告訴你,我們如何用實際的金錢去估算時間且規劃我們的迭代。我的目的是分享這方法幫助大家建構長久的團隊,且讀者的回饋也可以幫助改良我們的方法。你在工作上所耗費的心血將不會對你自身造成傷害。

2017年11月10日 星期五

如何避免你的程式大爆炸了?

原文連結

在1960/10/24,蘇聯拜科努爾太空發射場發生了一場大爆炸。在準備飛行的期間,火箭意外爆炸了,造成了火災及無數的破壞。超過70個人在這天死亡。USSR的戰略火箭計劃大大的退步。這場大災難被命名為Nedelin,這個計劃死去的執行負責人。

我們重新檢視造成這場大災難的步驟:

1. 工程師在非常緊湊的排程下工作

2. 因為時間不夠,許多安全程序都被省略或者沒正確執行。

3. 許多意外及缺失在發射準備期間被忽略或沒仔細分析過程

4. 在爆炸當天,為了加速發射流程,許多測試被迫不照順序同時執行。

5. 因為火箭啟動的延遲,工程師沒有正常理由就直接在正式環境作業。

6. 當天有人看到發射按鈕沒在原位而將它擺回原始位置。

7. 其它元件也被放在錯誤的地方,造成系統執行發射命令並且點燃了第二階段引擊,使得沒準備好的火箭爆炸了。

對照遊戲開發,我列出了以下狀況


Initialization, update and deinitialization patterns
Bad input data
Too much control exposed in data
Dereferencing null pointer
Big classes
Division
Vector normalization

同時我也提供了如何防範錯誤的做法及範例。

防衛性程式及合約式設計(Design by Contract)

防衛性程式
不該相信輸入的資料,而是預先檢查資料的正確性並給予安全的數值。
下例:即使不正確的參數,仍然給予安全的答案:零。

float GetSquareRoot(float Argument)
{
    if (Argument <= 0.0f)
    {
        return 0.0f;
    }
    return sqrt(Argument);
}

合約式設計:
設定程式與用戶關聯性
程式必須設定前置條件、後置條件和其不變性。

條件可被下列方式呈現:
assert
comment
etc. (a set of test)

下例:使用註解(comment)及assert設定程式的合約
// Argument needs to be equal to or greater than zero.
float GetSquareRoot(float Argument)
{
assert(Argument >= 0.0f );
return sqrt(Argument);
}


這兩項方法都能幫助我們寫出更良好的程式。
無論如何,我相信遊戲程設要能其特別的狀況而被特別對待。

1. 最大化執行速度是遊戲的重點

防衛性程式無法用在程式的每個地方,因為這會拖慢程式速度,且額外的程式會造成其它錯誤。我們的遊戲就遭遇過執行速度的問題。同時太多的防衛性程式使錯誤不容易發現(被防衛性程式擋下)。

2. 程式在變更迅速的環境下被完成。

遊戲程設包含了快速開發、創意驗証。但是因為要求速度會造成未來維護的問題。

3. 程式必須同時能用於產品及測開發階段

當我們在製做新的功能時,編輯器的新工具或改良也在同時進行,我們的第一個使用者是其它團隊成員、設計師、美術人員。他們也在製作遊戲及使用我們的工具。這表示了我們的程式同時被開發團隊及消費者所使用,即使在釋出給玩家之前。用assert強制程式對於工程師是好方法,但是同時會中斷其它成員的工作流程。

另外你要面對的問題可能和你遊戲、團隊、工作模式有關。Rendering的工作就會以速度為優先考量,而AI的工作就會需要更多穩定的程式。

我們將會介紹遊戲開發會遇到的常見幾種狀況。

Initialization, update and deinitialization patterns

讓我們看這個RAII(Resource Acquisition Is Initialization)的實作
class MyClass
{
public:
    MyClass()
    {
        m_Data1 = new DataClass1;
        m_Data2 = new DataClass2;
    }
    ~MyClass()
    {
        delete m_Data1;
        delete m_Data2;
    }

    void Update(float FrameTime)
    {
        m_Data1->Update(FrameTime);
        m_Data2->Update(FrameTime);
    }

private:
    DataClass1* m_Data1;
    DataClass2* m_Data2;
};
我們在constructor創造2個物件,然後在destructor刪除它們。並且包含了一個常見的Update函式,會在每個frame被呼叫一次。

現在讓我們把記憶體管理移出constructor、destructor,並且創造一個函式來控制這些資源。這是為了避免在同一個地方執行太多的操作。通常資源管理是個佔效能的操作,所以我們會希望在遊戲中能方便的控制它們。我們創造2個函式Initialize and Deinitialize來管理記憶體。

這是新的程式碼:


class MyClass
{
public:
    MyClass() { }
    ~MyClass() { }

    void Initialize()
    {
        m_Data1 = new DataClass1;
        m_Data2 = new DataClass2;
    }

    void Deinitialize()
    {
        delete m_Data1;
        delete m_Data2;
    }

    void Update(float FrameTime)
    {
        m_Data1->Update(FrameTime);
        m_Data2->Update(FrameTime);
    }

private:
    DataClass1* m_Data1;
    DataClass2* m_Data2;
};

這程式仍然十分單純,但這有許多的問題。

1 這沒有適當的initialization在constructor中
2 如果Initialize被呼叫了兩次會發生什麼事?這有可能造成memory leak
3 在Deinitialize也有可能會發生相似的問題
4 另外個問題關於Update,若沒有先呼叫Initialize而直接執行Update會發生什麼事情?會因空指標或被初始化物件造成crash.
5 若複製這個物件,有可能造成多次刪除的問題。

我們這裡沒有特別做合約式設計,我們只是考慮這個類別有可能的使用狀況。

為了使此程式穩定、減少出錯。我們需要

1 提供適當的initialization給我們的成員變數
2 加上bool參數m_Initialized 使memory leak、及多次刪除不會發生。
3 正常狀況下,若指標已被釋放,我們應將指標位置清除
4 由destructor呼叫Deinitialize,避免使用者忘記呼叫。
5 定義private copy constructor and copy operator, 防止它們被複製。

更改後如下:

class MyClass
{
public:
    MyClass();
    ~MyClass();

    void Initialize();
    void Deinitialize();

    void Update(float FrameTime);

private:
    MyClass(const MyClass&);
    MyClass& operator=(const MyClass&);

    bool m_Initialized;
    DataClass1* m_Data1;
    DataClass2* m_Data2;
};

MyClass::MyClass()
    : m_Initialized(false)
    , m_Data1(nullptr)
    , m_Data2(nullptr)
{
}

MyClass::~MyClass()
{
    //Prevent a memory leak if the client 

    //does not call Deinitialize.
    Deinitialize();
}

void MyClass::Initialize()
{
    if (m_Initialized)
        return;

    m_Data1 = new DataClass1;
    m_Data2 = new DataClass2;

    m_Initialized = true;
}

void MyClass::Deinitialize()
{
    if (!m_Initialized)
        return;

    delete m_Data1;
    m_Data1 = nullptr;

    delete m_Data2;
    m_Data2 = nullptr;

    m_Initialized = false;
}

void MyClass::Update(float FrameTime)
{
    if (!m_Initialized)
        return;

    // Defensive approach to check the pointers separately.
    if (!m_Data1 || !m_Data2)
        return;

    m_Data1->Update(FrameTime);
    m_Data2->Update(FrameTime);
}

Bad input data
(壞掉的輸入資料)

壞掉的輸入資料從資訊產業誕生的那一天就有了。檔案遺失或例外參數都會造成遊戲或編輯器當掉。

通常工程師會特別注意這問題。我們的程式需要為了參數遺失,特別寫預設的參數,像是預設的貼圖或可視的物件來提醒輸入錯誤,給予錯誤訊息及記錄也是有效的方法。沒有程式應該直接掛點。

在多數的遊戲引擊,我們可以很輕易地給予編輯器參數。讓我們可以利用這參數在一行指令就生成數個物件。

int NumberOfProjectilesToSpawn;

通常我們只會給他0,1,5這種數量。但有時使用者會犯錯或給與一個超大的數值像是100萬、或負數?那時候我們的程式碼還可以正常運作嗎?

好的方法是去考量現況決定參數的預設值及範圍。另外一種方案,如果我們想要不被限制的超大參數,我們提醒使用者輸入的參數可能會造成問題。

在防衛性程式,我們可以很清楚的區分有危險的參數。我們將所有input視為不可信的,但有些被驗證的內部資料是可以相信的。例如,我們認定所有外部檔案、編輯器輸入參數、玩家輸入參數、網路上參數都是有不可信的。驗證及修正函式需要將這些參數轉成可信的參數。

void SetNumberOfProjectilesToSpawn(int NumberOfProjectilesToSpawn)
{
    m_NumberOfProjectilesToSpawn = NumberOfProjectilesToSpawn;
    ValidateAndCorrectParameters();
}

void ValidateAndCorrectParameters()
{
    m_NumberOfProjectilesToSpawn = clamp(m_NumberOfProjectilesToSpawn, 0, 8);
}

在遊戲開發中,越早驗證是越好。例如:在編輯器輸入參數時就驗證,在存成檔案之前,或是在檔案一讀進來。越早確認使其更容易減少執行遊戲不必要的測試及效能衝擊。

Too much control exposed in data
(太多對資料操作的方式)

由壞掉的輸入資料所造成的問題再更進一步思考,我們所有的系統都是資料導向的。這些系統能被設定或在沒有工程師的狀況下被操作,完全透過所輸入的資料。

通常會將所有操作及遊戲特色給每個組員,尤其是非工程師。但是工程師必須問問自己,我們真的可以提供所有組合參數嗎?或者反而造成更多例外狀況?

創造一個真正資料導向的系統是非常困難及花時間的。

當我們提供任一個參數,這都會成為整個巨大資料導向系統的一部份。很自然的,我們會加入一個介於程式與資料之間中間層。

讓我們看這用來控制敵人的簡單結構

struct EnemyBehavior
{
    float m_WalkSpeed;
    float m_TurningRate;
    bool m_JumpAllowed;
    float m_JumpSpeed;
    // …
};

這個程式提供m_WalkSpeed 的小參數,也同時提供m_TurningRate 大參數。當m_WalkSpeed 很大時跳躍的功能仍然可以正常運作嗎?

一個較安全的方案是準備一組預先設定好的參數組,而這些參數都是可正常執行的。使用者只允許使用這組先定好的參數。

enum EnemyBehaviorPreset
{
    EnemyBehaviorPreset_TightCorridors,
    EnemyBehaviorPreset_IndoorHalls,
    EnemyBehaviorPreset_OutdoorOpenSpace
};

我們只提供一個參數讓使用都可以在編輯器使用。

EnemyBehaviorPreset m_EnemyBehavior;

這個方法雖然限制使用者的自由,但是對工程師有更佳的穩定及維護性。

Dereferencing null pointer
釋放空指標

Dereferencing null pointer是造成遊戲或編輯器當掉的一項常見因素。

在Unreal Engine 2 and 3,其中一項程式語言UnrealScript的設計目標就是防止這類問題。並且創造不需指標的開發環境。存取物件references在UnrealScript一直是安全的,即使它們沒有指向任何物件。

因為執行速度是很重要的,所以我們不能在所有釋放指標的地方做檢查。無論如何每個部門處理指標的方針也許不一樣。繪圖工程師會犧牲防衛機制去換取速度,但是AI或gameplay則會希望更多的防衛機制,尤於gameplay時常更動。所以如何處理指標會按其環境而定。

-Avoiding pointers(避免使用指標)

第一、我們可以避免使用指標當函式的參數且用C++參照(references)取代,下例即是說明如何取代指標

void MyFunction(ControlData* InputData)
{
    if (InputData)
    {
        int parameter1 = InputData->GetParameter1();
        // …
    }
}

改成用reference的版本

void MyFunction(ControlData& InputData)
{
    int parameter1 = InputData.GetParameter1();
    // …
}

這樣使用者在用這個函式時就會提供適合的參數,並確認及釋放指標。

-Public and private methods(公開及私有函式)

實際上,有項不好的慣例是在公開的函式傳入的指標當作參數,

若這函式是屬於公開介面的話,我建議要檢查這函式是否為空。

void MyClass::AnalyzeInputData(Data* Input)
{
    if (!Input)
        return;
    // …
}

若函式是屬於私有實作的一部份,我會傾向將先前的程式移除。在這例子,函式有註解標明、或是函式會發出警告訊息去確認指標狀況。

class MyClass
{
public:
    // Method always tests if the pointer argument is null.
    void AnalyzeInputData(Data* Input);

private:
    void UpdateObject_NoTestForZeroPointer(ObjectClass* Object);

    // Method does not check validity of pointer arguments:
    void UpdateObject(ObjectClassA* Object1, ObjectClassB* Object2);
};

另外在私有函式UpdateObject及UpdateObject_NoTestForZeroPointer的開頭,我們可以利用assert驗證指標

// Method does not check validity of pointer arguments:
void MyClass::UpdateObject(ObjectClassA* Object1, ObjectClassB* Object2)
{
    assert(Object1);
    assert(Object2);
    // …
}

無論如何這不是最實際的做法,若我們的assert中斷成員在使用編輯器。要改進這個方法,我們可以創造一個防禦性軟驗證。

-“Soft” assertions

軟驗證應該允許程式繼續執行,但仍會回報問題給開發者

void MyClass::UpdateObject_NoTestForZeroPointer(ObjectClass* Object)
{
#ifdef CONTRACT_SAFE_CHECK
    reportIfNull(Object); // Soft assert
    if (!Object)
        return;
#endif
    // …
}

這方法通知程式人員不正常的狀況(將訊息送到資料庫)且不會中斷執行。利用#ifdef設定是否要執行這項檢查。開發設定應效能分析(profiling)及正式環境可執行。

編譯前的參數也要考慮到是否在編輯器執行或是遊戲內會造成其它問題。

Big classes

有些class成長速度太快了。所以有些原始檔會變得比其它檔案還大。若你去檢查你正在執行的專案,你會發現有些檔案很明顯地較其它檔案大上不少。這些檔案通常是負責角色、玩家或是遊戲管理者(manager)。這並不令人驚訝。當我們增加新的特色進遊戲時,即使是最小的步驟,我們也會將它加入現有的class底下。這種增加速度是會造成問題的。這使得我們很難去維護preconditions, postconditions and invariants。維護大型的類別是很困難的,且他們也會造成更多問題。從Initialization, update and deinitialization patterns要發現問題會比較簡單。通常他們包含了許多沒有分類好的子元素。這有許多可能的方法可以解決這個問題。我在這展示其中一個方法。

若我需要在現有的class中加入一項新的元素,我開始評估是否該為其建立nested class。下例在ObjectBehavior 內新增成員參數及函數去為了完成Special Action 1。

class ObjectBehavior
{
public:
    // …
private:
    // …
    // Begin Special Action 1.
    bool m_ShouldStartSpecialAction1;
    float m_TimeLeftToStartSpecialAction1;
    float m_SpecialAction1Parameter;
    void InitializeSpecialAction1();
    void DeinitializeSpecialAction1();
    void UpdateSpecialAction1();
    // End Special Action 1.
    // …
};

以nested class方式實作:
以子類別
class ObjectBehavior
{
public:
    // …
private:
    // …
    class SpecialAction1
    {
public:
        void Initialize();
        void Deinitialize();
        void Update(ObjectBehavior* ParentObject);
    private:
        bool m_ShouldStart;
        float m_TimeLeftToStart;
        float m_Parameter;
    };

    SpecialAction1 m_SpecialAction1;
    // …
};

哪一個方法比較好?我們適度的包裝了新的參數在獨立的私有類別中。只有SpecialAction1 nested class被存取到。這使得類別在未來可以更容易被區分出來。

除法(Division

電玩遊戲用了大量的浮點數運算。例如,我們經常使用除法在我們的程式內。這是非常基本的數學運算,但這仍然要注意不要讓除數為0。若我們除數為0時,會造成浮點數的例外狀況,或者浮點數的例外被擋掉了,算出來的結果會是無限(INF)或非數字(NaN)。

請也考慮到下面的狀況。當兩個數字都是合法的浮點數,但是結果會是INF,因為他們超過32-bit浮點數可表現範圍。

float f1 = 100000000000000000000.0f; // 1.0e20f
float f2 = 0.0000000000000000001f; // 1.0e-19f
float f3 = f1 / f2;

f3即為INF。另一個例子:

float f4 = 0.0f;
float f5 = 0.0f;
float f6 = f4 / f5;

參數f6是NaN

我們可以繼續執行程式,因為INF是個合法且可計算的特別記號。無論如何,在多數狀況下,我們不會想去處理這種狀況。許多函式庫會因這些例外狀況中斷。

Unreal Engine 4 提供了時別的函式去偵測浮點數太接近零。

bool FMath::IsNearlyZero(float Value, float ErrorTolerance = SMALL_NUMBER);

在 C# Unity Engine可以被寫成這樣:

bool IsNearlyZero(float Value)
{
    return Mathf.Abs( Value ) <= 0.00000001f;
}

我們可以在做除法之前先確認我們的除數。

float denominator = b – a + c;
if (!IsNearlyZero(denominator))
{
    result = d / denominator;
}

要確認特別的浮點數,我們擁有以下函式:

float.IsInfinity(float) and float.IsNaN(float) in Unity Engine
FMath::IsFinite(float) in Unreal Engine 4

同時我們經常忘記某些特別狀況會用到除法。請考慮以下的update函數。

void Object::UpdateLogic(float FrameTime)
{
    Vector3 Velocity = (GetCurrentPosition() – m_PreviousPosition) / FrameTime;
    // …
}

我們可以很簡單的假設FrameTime總是大於0。當然程式會在FrameTime為零時出錯。這是有機會發生的,例如當遊戲在製作時我們相要將時間暫停,但是update仍然被呼叫。在編輯器,我們通常不會更新world logic,但在某些特別的狀況,我們會為了模擬而更某些物件的邏輯。這簡單的程式會因為這些特例而掛掉。

Vector normalization

在Vector normalization會出現某些例子上。通常是計算3d數學時。normalization是為了計算出單位向量的方法,會將其向量除以自身長度。這些函式都非常單純,我們再次回想起了除法的例外。當向量為零時或非常接近零時會出錯。

在Unreal Engine 4,我們有以下兩個函式:

FVector::GetUnsafeNormal() – 這是不會做額外檢查的normalization,但比較快速。

FVector::GetSafeNormal() – 這是會檢查除數是否為零,若長度接近零時,結果回傳零。

有趣的是它用很直接的寫在函式名稱上提醒使用者。

Unity Engine在C#則是只有提供安全的normalization

Vector3.normalized

Vector3.Normalize()

所以它會回傳單位向量或是零向量。

“Soft” methods

我同時也會提供些通用的方式來提升程式品質。

測試你自己的程式碼:
自動化測試、
視覺化除錯及文字記錄、
採用有意義的命名、
程式檢閱(code review)、
靜態程設分析工具、
程式文件

總結:

在這文章中,我展示了遊戲程式設計上常見的錯誤及一般問題的來源。我們知道追求安全及穩定的工程方法並非新的挑戰。這也不是簡單的工作,尤其在特殊的狀況下,像是遊戲開發。主要困難來自於去知道、預測及決定什麼地方該用防禦性程式及該用多少的安全措施才足夠。

將高安全性軟體工程的做法完全放到遊戲開發是不明智的。例如:太空計劃。無論如何,這可以用來了解人們在這些產業是如何完成他們的目標。

NASA太空中心主管及Saturn V rocket首席工程師,特別喜歡實作大型及安全的軟體工程。這些通常會在開始時花不少時間,但最終帶領美國完成了阿波羅11的成功。第一個上太空的人也安全的回來了。Saturn V 也是唯一帶著人類離開地球的火箭。


2017年9月25日 星期一

Failbetter Games首席編劇的五點建議

原文連結

在經過了幾年的鬥爭及無數個敵手的死亡,去年我成為了Failbetter Games的首席編劇。我管理內部的劇情小組及外包工作,包含有Sunless Sea, Fallen London, Dragon Age: the Last Court及其它專案。

我仍然在尋找正確的道路!這裡有五項我過去學到的教訓。

1 優先提升團隊能力,即使這需要犧牲劇情的品質。

"這份專案是Fallen London,別把他搞砸了"

很明顯的我當時十分緊張。我必須完成這個專案,我是個超級的完美主義者。如果這有曾經做錯的事,就是要團隊照著範例做事。

我深信提供案例可以讓團隊快速學習,但這包裝好的學習方式,使我放棄了團隊自我成長的機會,他們可以自己克服困難並發展出他們自身的解決方式。而我應該學會退後一步並點出幾個重複的問題。我的建議必須是清楚、泛用、可達成的。例如:"將字數減半"、"確定玩家知道自己的每個步驟"。

團隊提出的解決方案通常比我的還好。這花了我一段時間了解,我最重要的工作是去幫忙團隊去做到最好。有時候這代表著你要咬緊牙關讓團隊去做你不喜歡的事。而且總是會不斷發生。

2 防止你的團隊的不斷的提出新點子

我在這件事上有著慘痛的經驗。我們是創意產業,每個人都會貢獻自己的意見。我們有邏輯及也會批評(必要時)。如果你的團隊提出一大堆不實用的點子,當你是個管理者時,你應該要坐下和他們好好談談,幫團隊踩剎車。

問些嚴肅的問題,尤其是他們並沒有辦法確實回答(例如:"這有商業的案例嗎?"、"這件事是如何做錯的?"、"這件事真的實踐會花費多少時間")。如果這些點子不能通過你的檢驗,那自然也無法通過別人的檢驗。

最差的狀況是一個無法執行的點子沒被阻止。它進入了開發流程並且浪費了許多資源,且最終還是必須結束它。沒有任何一個人會因此開心。

3 不得不犧牲好點子

有時候你必須對些好點子說NO。因為現在不是適當的時間、適當的專案、適當的團員。但是不要就這麼浪費它們。將他們記下來。規劃時間去檢視它們。過了三個月後,它們也許會看起來不一樣。

4 你的知識是珍貴的,保護並且栽種它

了解小說世界的基礎。他的定義是什麼(哥德!),不是什麼(蒸氣龐克!)。闡述你的故事。不斷檢視更新對它的認知。使用書籍、影集、電視、音樂為它寫評論:"這感覺像是Melville,而不是Roddenberry"。評論自己的每次更新,不只是內部的,對外包工作者也是。

外包工作者並不會被工作環境每天影響。為了補足這部份,我們會讓團隊及外包工作者加入同一對話群組。用這方法,他們可以提出問題並參與部份團隊的討論。

5 培養自主權

Fallen London有超過150萬字。Sunless Sea則有30萬字。一個人的腦袋很難去管理這些。我們可以將這些做成摘要文件,或讓我們的文件系統是可以搜尋的。但是前者很難完成、而後者很花時間。

取而代之,我們開始讓團員擁有它們自己的劇情。像是Cash創作出了角色W,且我希望在故事中使用W,我會請Cash來幫助我了解問題。如果對開發角色有不同意見的時候,Cash會被負責做最終決定,這很有效率,而且對創意有正面的回饋。

2017年9月16日 星期六

深入遊戲設計:創造適合Reigns的故事

原文連結


Who: Nerial的遊戲設計師François Alliot


我是一位獨立遊戲設計師及遊戲開發者。我開發及設計Devolver Digital發行的冒險遊戲Reigns。我從三年前開始做遊戲,這是我的第二項職業。


What:撰寫包含機率的故事


與美術Mieko第一件決定的事就是遊戲的核心機制:利用滑過去的動作(有點像是Tinder)使國王選擇2個王國的選項,透過一組卡片來表示你的不同顧問。

每個選項都會衝擊到你的王國,你的目標就是讓王座能維持越久越好。事情很快變得越來越糟糕,而你會在各式狀況下悲慘的死去。

一開始、Reigns很像是債務管理的模擬遊戲:你必須去平衡四項權力(宗教、人民、軍隊、金錢)且應付顧問提出的各項選擇。

初始遊戲機制在情感及實際都運作的很好。這是一個隱含驚喜的玩具(一個很聰明的人某天告訴我的)。我們評估選項對四項權力的影響。我們給予這個玩具深層的意義。

前十分鐘我們感到很有趣。相信我這是個很好的開始。我們只需要再吸引玩家2個小時。

我們發現了玩家很快就把50張設計的卡片給玩完了。在不同的事件間又產生了不同的意義,我們自己從未想過的連結。像是飢荒或婚約。

我們決定建立在這結果之上,更加充實玩家在這前十分鐘所創造的故事。



Why: 為什麼?


為了達成這目標,我們將隨機抽出的卡片加入機率系統。這很難用簡單的方法來解釋。

想像一個包包擁有適合你內容的大小。一般來說,這包包含有所有遊戲卡片。當我選出一張卡片時,我開始將不適合這王國現況的卡片由包包中移除。

這有很多的組合,例如:如果這區塊的卡片都和皇后有關,但是你還沒結婚,就會被移除。像是卡片只會在宗教強大的時候觸發,但你的宗教很虛弱,也該被移除。同時也移掉玩家最近玩過的卡片。

這創造出整組玩家每次都玩不膩卡片。最後讓卡片都是佔有不同空間。卡片越大張會佔住包包越多空間。

最後你會擁有一個混合卡片的背包,大的卡片佔住大部份空間及其它小卡片。我就可以從包包內挑一張卡片來給玩家看。

卡片佔空間造成有趣的現像。大卡片被選到的機率大於小卡片。但是若大卡片在前幾步已經被抽掉了,那剩下的卡片機率就會大大增加。

這就是包包的擴張、縮小尺吋能力為什麼這麼重要。若你與鄰國開始了戰爭,那與些戰爭相關的10張卡會佔比較大的機率。且會把包包大部份的空間佔去,大於其它不相關連的卡片,例如小丑卡。這最大卡你挑到戰爭相關的卡片,而縮小挑到小丑的機率。

一旦戰爭結束,戰爭卡將被移出包包,系統有更大的機率去挑到小丑卡,因為它的機率上升了。



有些事件、像是地下城、決鬥會把系統縮限在一小塊區域。這時包包會非常小,只包含一點點的卡片。這小區域也可能只會有一張卡片,用來創造線性的故事(像是遇到惡魔)。

這機率系統的多樣性是創造遊戲的關鍵。我認為這樣的工具或規則來使遊戲變得奇幻及不可預期,甚至比明確定義故事內容的封閉系統還要完美。

我不介意Reigns是不完美的,這是這遊戲的不可缺少的一部份,這就是遊戲的特色。我們總是小心的創造每個事件能圍繞遊戲核心(有趣及遊戲特性),但我們不特別限制讓玩家特別磇到驚喜劇情(或者干擾玩家)。

創作Reigns真的是件類似探索的工作。你可以把事件照你所想的一樣緊緊包在一塊。我從單張的卡片開始,接著嘗試短時間的短篇故事線,然後持續增加,像是新角色及新的事件永久加入牌組,再來是惡魔的故事線及一堆圍繞著核心遊戲的卡片,帶領玩家進入未知的體驗。

例如(背叛者):有在森林中盲目奔跑、挑選奇幻香菇、在瘟疫中破解大臣的問題、處理沒有人性的奴隸問題、如何在愛情中表現適當、進入鴿子世界、和鴿子約會...



結論:


透過這個系統,我們可以創造不確定的故事,在非常廣泛的劇情卡、特別事件的分支劇情、碰到連製作團隊都沒遇過的特別組合。

Reigns遵從著Failbetter Games(製作SUNLESS SEA的團隊)高品質故事的製作方針。我們的品質來自於每次在有限的包包內放置的卡片組合。

最後,Reigns的核心體驗混合了每次所選的卡片,由特定方式隨機抽出的組合。這產生出非常有趣的組合。一但玩家發現有些牌是由前些選項所造成的,會使得每張卡都具有意義,因為玩家很難去辬別出隨機卡片會怎麼出現。

即使一小部份的卡片讓你覺得曾經看過,但整個遊戲會變得奇幻且難以預測,促使玩家去創造他們的故事。誰能說發現奇幻香菇不是僱用女巫造成的影響?又或者是瘟疫的一部份?

我也不能如此斷言。

2017年8月31日 星期四

[翻譯]Darkwood的開發故事

原始連結:http://imgur.com/a/xVhDz
如果你喜歡這些內容,可以去原始連結給製作者一些鼓勵。

我們都很害怕玩恐怖遊戲,所以我們離職去做一款沒有jump scares的恐怖遊戲。這是我們的堅持,我們的故事。

我們是Acid Wizard Studio。3位從波蘭來做遊戲的大學生(及一條狗)。經過4年的開發,我們剛剛將Darkwood完成了。一款由上往下視角且沒有jump scares的恐怖生存遊戲(但是仍然能嚇你一大跳)。

一開始我想先說這不是一趟輕鬆的旅程。事實上,我們每一個人在開發過程中都經歷過崩潰的低潮。

在做Darkwood之前,我們並非恐怖遊戲的玩家。在開發遊戲的過程,我們計劃逐漸克服自身對於恐怖遊戲及電影的恐懼。即使如此,Alien: Isolation對我們來說還是太可怕了。

在開始做Darkwood之前,我們並不太瞭解遊戲開發。我們在動畫及視覺設計上有經驗,但是在製作、程式、遊戲設計上沒有太多的涉略。

最原始的想法是在一個月內做一款塔防遊戲。
在過了無數個沒有睡眠及惡夢的夜晚之後,啟發了這個專案未來的雛型,我們在2013年做出了這個遊戲的雛型。我們利用它做出了遊戲展示影片,然後玩家的反應大大的超出了我們的遇料。

這個時候我們覺得這是個遊戲開發的大好機會,所以我們後半年開始在Indiegogo上募資,幫助我們完成遊戲。這次募資比我們原先所想的大大幫助了我們。

在募資本來狀況並不好,玩家覺得我們無法做好它。但是在最後一週,我們釋出了第2個遊戲的展示影片。這讓我們得到很多的助力,且讓募資趨向成功。我們訂下遊戲的完成日期為西元2014年。還真是天真的想法。

不幸的,我們在開發上的缺乏經驗使得日期大大地延遲。我們擴大了Darkwood的規模,及在遊戲核心上做出了些重大的改變,這花掉了我們很多時間。

財務上的問題迫使我們需要在Steam Early Access推出。玩家給予遊戲很好的評價,且銷售狀況也大大出乎我們意料。這鼓舞了我們,我們打算再次的將遊戲的規模擴大。

在這之後我們持續投入心力來儘快完成這遊戲。我們原定計劃沒有一項是準時完成的。由於過慢的開發速度,玩家的負評開始湧進來。我們也因此被影響使得開發速度更慢,甚至興起了放棄這遊戲的念頭。

開發變得非常的緩慢且沮喪。我們選了一條遠超過我們所能承擔的道路,但是我們知道我們擁有和別人不同的東西。我們必須完成我們理想中的遊戲。

現在是2017年,我們終於辦到了。遊戲已經發佈了1個禮拜。遊戲的評價都非常正面,我們被玩家的反應嚇到了。

我們沒有很多的時間來慶祝,還必須花很多時間來修正錯誤,解決玩家遇到的各式問題。我們其中一位成員(Gustaw)最近才剛滿30歲。他花了一整個晚上嘗試決解玩家遇到的存檔問題。

我們被湧入的信件所淹沒了。它們大大的超多我們所能回覆的數量。不幸的是有一大部份是圾垃信件。有些人稱自己是實況主或部落格來索取遊戲序號。這些序號有些流入了二手交易平台。事實上,我們十分厭惡這件事。這使我們無法從這些事情中脫身,或者將序號真的送到沒有錢購買遊戲的人。

Steam允許玩家在遊玩2小時內退貨,而且開發者可以看到這些人退貨的理由。我們看到一位玩家因為不想被家長看到月底的消費帳單而退貨。這讓我們感到十分不開心。

我們決定做件事來改變這狀況。如果你沒有錢可以買這款遊戲,我們放出了安全的種子在Pirate Bay,完全不綁定平台(DRM-free)。裡面沒有添加任何的惡意修改。我們只有一個要求,如果你喜歡這個遊戲,並且希望我們未來能持續做遊戲,請考慮未來在Steam、GOG、Humble Store買下它,即便是特價期間。但請不要在序號交易網站購買。若你在序號交易網站購買,你只會助長了非法交易的人們,他會逐漸侵蝕這整個產業。

感謝大家看完我們的故事。