實踐出真知:零基礎小白學編程作遊戲的 14 周曆程

人人都應該學編程嗎?隨着每一個人的工做與電腦連結愈發緊密,也許這是真的。php

我是遊戲設計師,在分工細緻的國內網遊業界,不須要研發或美術背景也能擔當遊戲設計重任的角色多了起來。有時候他們甚至只需負責撰寫劇情文檔或遊戲文案,一切涉及程序的工做內容都有開發同窗代爲解決。不離開本身的 comfort zone 也能很好地完成任務。python

但在本職以外,瞭解程序如何工做,能帶來許多好處:平常工做中重複的工序能夠自行使用程序解決;易犯的錯誤能夠經過程序避免;更不用提編寫腳本的能力,可以讓你直接控制你所設計的內容的每一個細節,瞭解設計的邊界及內部空間。程序員

畢竟,太初之時,只有程序。程序員想:「專職美術、策劃、設計、產品經理是好的。」便有了他們一干人等。算法


「每週一遊」:每一個星期快速開發一個遊戲,連續進行數個星期。這是許多開發者們磨練本身想法和技巧的方式。編程

我沒有計算機背景或美術基礎,但乘中國遊戲行業大發展,卻也幸運入行成爲一名遊戲策劃。我但願在平常工做之餘,用一個辦法來鍛鍊本身對遊戲系統設計和開發過程的理解。所以,我參加了 Coursera 上的幾個課程,而且用課程提供的方便工具來實現設想中的功能。segmentfault

一開始的成果很是基本、很是簡單,但到後面挑戰等級逐漸上升,到最後已經能獨立完成 600 行左右的程序。函數

接下來我就給各位看看我在這近四個月中的成果,以及我從中學習和體會到的內容。我儘可能省略比較枯燥的實現細節,一來能夠避免無聊,二來須要下功夫的東西仍是親手實踐比較有幫助。若有興趣可來個人微博交流。工具

第一週:包剪錘蜥史波克(Rock-paper-scissors-lizard-Spock)

請輸入圖片描述
Sheldon 喜歡的遊戲。學習

第一週運行的結果

謝耳朵愛玩的遊戲,石頭剪子布的升級版。內容最最基本,只要在控制檯裏輸入命令,命令經過 if-elif-else 轉化成數字(0-4,分別表明出的5個東西)。測試

電腦則會隨機生成一個數字,轉換成字符串。再根據雙方數字,用 if-else 判斷勝負便可。

對我來講這是本身親手編寫的第一個遊戲。它雖然簡單,但包含了一個遊戲必須的所有要素:它有着固定的開始和結束,以及勝負的規則。

第二週:猜數字

猜數字遊戲

猜數字遊戲就是由電腦隨機生成指定範圍內的一個數字,由你來猜,電腦告訴你是高仍是低,必定次數後未猜中則輸掉的遊戲。

在這個遊戲中第一次引入全局變量的概念。初始化時,上下限以及容許你猜想的次數都是讀取全局變量。這樣一來,咱們能夠在遊戲核心的方法以外,使用別的方法來修改全局變量,讓玩家能夠本身選擇數字範圍和猜想次數。遊戲自己則依然是 if-elif-else 這樣寫成的。

這是我親手編寫的第一個能夠由玩家調整遊戲設置的遊戲。

第三週:秒錶遊戲

秒錶遊戲

秒錶遊戲是個考反應的遊戲。點擊開始後秒錶開始向前走,若你按停秒錶時,秒錶的時間恰巧停在整數(小數點後爲0),則你得1分。遊戲會記錄你按停的總次數和得分數。

這個遊戲中涉及到爲每一個功能編寫單獨的方法。如玩家控制的按鈕start()stop()reset();遊戲自己時間前進的tick()等。同時,爲了讓時間正確地顯示在屏幕上,還有一個將時間轉化爲「A:BC:D」這種形式的方法。

咱們計時的方法是定義一個叫 time 的變量。因爲這個遊戲中最小的計時單位是 0.1 秒,因此每通過 100 毫秒咱們就讓這個數字 +1。與此同時,編寫一個 format() 方法通過一系列計算將這個數字轉化爲分、秒和0.1秒,顯示在屏幕上便可。判斷玩家是否得分仍然使用 if-else 結構。

這是第一次涉及到玩家進行的複雜操做,也是第一次認識到,在遊戲畫面的表象之下究竟應該有些什麼機制在運行。

第四周:乒乓(Pong)

Pong

終於咱們從小朋友玩的遊戲進入了街機時代!

傳說 Pong 是世界上第一個電子遊戲。在那個遊戲機只有滾軸操做的年代,這個有着極簡單畫面的遊戲啓發了無限後來者。看着它在手下造成還有些小感動呢。

這個遊戲也是我製做的第一個不模擬現實中的「邏輯」,而是模擬「物理」的遊戲。它的核心部分是球的速度變化、板子的速度變化,以及球與邊界和板子的碰撞。

爲了讓這個遊戲不至於無限地進行下去,我讓球的速度隨着每一次板子碰撞上升。但上升的公式寫成了指數函數,因而這球就啪啪啪越打越快每一回合很快就結束了。若改成對數函數,則會緩慢地趨近一個上限,令每一回合後期的雙人對局很是緊張、充滿變數。

這是我第一次體會到遊戲的「手感」究竟是怎麼回事。每一次對參數的細微調整對手感帶來的變化,可讓設計者與遊戲自己有着更深入的接觸。這是在目前分工充分的網遊公司的平常工做中體會不到的感受。

除此以外,很快地你就能從一個簡單原型中看出將來變化的可能。是否能夠加入:

  • 「球擊打在板子的不一樣部位,會彈向不一樣方向」?
  • 「當板子擊球時,板子自己的速度會令球曲線飛出」?
  • 或者「連續擊中球數次後玩家能夠發出大招」?

等等諸如此類。想到這裏,這個遊戲能成爲數十年遊戲業的起點,也是有其道理的。

第五週:記憶遊戲

記憶遊戲

記憶遊戲就是將多對牌打亂順序朝下放置,玩家一次翻開兩張,若相同則原樣留着,若不一樣則翻回去。全部牌都翻開後玩家勝利。

在這個遊戲中,暫且用數字來代替撲克牌。咱們用了一個 list (我有點搞不太清 list, array, tuple, set 幾個詞的中文翻譯,不亂講了……)來以 Boolean 值(True 和 False)記錄每張牌是否翻開的狀態。當設爲翻開時,露出數字,不然在相應位置繪製一張牌背。

這個遊戲的邏輯方面比較 tricky 的地方就是整個遊戲實際上有三種狀態,須要分別處理:

  1. 新遊戲,一張牌都沒翻開
  2. 翻開了(本回合內)第一張牌,等待翻開第二張
  3. 翻開了(本回合內)第二張牌,等待判斷是否相同

因而我使用一個叫作 state 的變量,分別以 0, 1, 2 表明三種狀態。在覈心方法中利用 state 的值來決定接下來要作什麼。

第六週:21點(Blackjack)

21點

啊,21 點。我人生中接觸的第一個撲克遊戲。是的,在我會打「拖拉機」以前,7歲的我就在DOS下的初代大航海時代的酒館裏學會了 21 點。這是年幼的我在那個遊戲裏玩懂的惟一一個系統……

這是個賭博遊戲。簡單來講規則是:莊家給本身和玩家各發(deal)一張暗牌、一張明牌,玩家決定是否繼續加牌(hit);玩家加牌結束(stand)後莊家自行加牌,接着雙方攤牌。擁有最高點數的玩家獲勝,其點數必須等於或低於21點。

在編寫這個遊戲的過程當中第一次引入了類(class)概念。由於在遊戲中許多物件都會重複出現,使用類能夠很方便地重複製造它們:

  • 每一張牌是 Class Card

    • 方法 get_suit() 能夠獲取它的花色;
    • 方法 get_rank() 能夠獲取它的數字;
    • 還有一個方法來把它繪製出來。
  • 手牌是 Class Hand

    • 方法 add_card() 能夠在手牌中增長一張牌;
    • 方法 get_value() 能夠算出手牌的分數。
  • 牌庫則是 Class Deck

    • 方法 shuffle() 能夠洗牌庫;
    • 方法 deal_card() 用來發牌。

規定好這些基礎方法之後,重發牌、加牌、攤牌均可以經過這些功能的組合來實現。例如開局就是洗牌庫,向雙方發牌;雙方手牌加上兩張發出來的牌。等等。
圖片:牌表

此外這個遊戲還第一次涉及到怎樣在畫面上繪製固定的圖形。整張牌表是一張大圖,怎麼樣根據牌的值定位到對應的牌面也是要好好算一下。

第七週:小行星(Asteroid)

小行星

經典街機遊戲的復刻版!大製做來臨了!

這回的遊戲涉及的內容比之前多,除了控制小飛船打來打去以外,動畫、音效、UI 等也都引入了遊戲中。但每一部分的實現均可以經過以前嘗試的小功能疊加實現。簡單地瞭解遊戲圖像和聲音到底怎麼運做後,並沒有特別的困難。只是這一次我學着一個模塊一個模塊漸次開發和測試,一個功能調通無誤,再進行下一個。

反而是在遊戲設計方面,製做這個遊戲的過程給我帶來不少思考。在這個遊戲中可供調整的變量太多了:飛船須要推動和旋轉;但推動是給飛船一個向前的加速度,而飛船自己還會有向着其餘方向的速度。宇宙空間中微小的摩擦力、和隕石撞擊後受到的力,都要考慮而且編入遊戲中。

這時你會發現,一樣的一些參數,通過調整會讓整個遊戲變得完全不一樣。這艘飛船究竟是笨重、轉向慢、射速慢、射程遠的戰列艦,仍是輕盈、轉向快、射速快、射程近的戰鬥機?你要躲閃的是從一個方向襲來的流星羣(隕石都從一邊來,並且向一個方向阻力特別大),仍是四面八方出現的亂石?每一種選擇,好像都挺好玩的……

到這時我才瞭解到一個遊戲設計者腦中「指揮意圖」清晰的重要性。你到底要作一個什麼樣的遊戲,給玩家帶來什麼樣的情感?只有一個大概的「我要爽」是不夠的:到底是控制巨大戰艦緩慢機動將將擦過一塊流星的那種屏氣凝神的,仍是控制戰鬥機高速穿梭在流星羣中那種險象環生的?有時候本身也會猶豫。只有記住一開始你要提供的是怎麼樣的情感,而且在全程中反覆回看,纔不會偏離目標。

一我的製做尚且如此,當須要團隊合做的時候,若不把一個肯定的思路貫徹到底,怎麼行呢?

第八週:2048

2048

啊,HTML 小遊戲。在這個星期,2048 遊戲忽然流行了起來。因而我也跟風復刻了一個。看似簡單的遊戲,真的要作出來,對於新手來講仍是挺費腦筋的。

第一個問題就是,這個網格怎麼作呢?我採用的轉化方法是使用一個二維的list。看起來就是:

[[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2, 3]]

這樣一來,若是我要定位到第三(2)行第一(0)個格子,我就讀取這個 list 中的 List[2][0] 便可。這樣一來看起來頗爲直觀,又能解決問題。

接下來又有好幾個問題須要一一解決。首先,當你按下一個方向鍵之後,全部行(列)的數字都會向着那個方向合併。這件事怎麼辦呢?

首先我單獨寫了一個 merge() 方法。只要傳來一個 list,就逐個 iterate 並將合併後的值返回去。而後在主要 Class 中間的移動方法 move() 中規定,向哪一個方向移動,就以那個方向的四個格子爲排頭創建四個 list,傳去 merge() 那邊再替換回來。這樣一來這個遊戲的核心規則就實現完成,剩下的邊邊角角多測試修繕便可。當測試成功的那一刻真是有一種爆棚的成就感——不多有解謎遊戲的謎題能這樣讓你研究琢磨幾個小時。

當你把遊戲的每一個部分分入不一樣的 Class 和方法中後,能夠感受到效率提高很多。例如你在製做模塊 B ,此時要用到模塊 A 中的功能,你能夠徹底無論模塊 A 怎麼實現的,只要把指定的數據傳進去,等着它傳出結果來就行了。

第九周:Cookie Clicker(點擊-放置遊戲)

Cookie Clicker

這是個挺有病(誤)的遊戲。你只要點這塊餅乾就能夠加餅乾數,餅乾能夠買幫你加餅乾的道具,越高級的道具加餅乾越快,子子孫孫無窮匱也。據說最近這種放置類遊戲在一些小圈子裏挺流行的……

遊戲自己的設計相對簡單。加餅乾數,加加餅乾速度,獲取各類升級和冷卻的時間,購買道具等等,並不複雜。

但咱們不想本身玩,咱們想要電腦自動玩,算出最快速的策略,看看到底能得到多少餅乾。
模擬出來的餅乾數增加及結果

爲了這樣,咱們專門作了一個叫 simulator_clicker() 的方法,它會根據輸入的策略,在合適的時間購買固定道具;而每一個策略均可以另外定義。這樣一來,這個方法裏引用的方法又引用了別的方法,複雜性上了一個臺階。

至於「策略」,就進入了 AI 的範疇。此時咱們雖然只能使用最基本的條件判斷,但反覆計算應該讓 AI 怎樣動做,仍是挺有挑戰性的。只不過,發現讓 AI 採用「純隨機策略」亂買道具出來的結果比你辛苦計算的結果還好,就有點蛋疼……

第十週:Yahtzee

Yahtzee遊戲

這是個投骰的遊戲,一樣涉及本身的「手」概念。你們本身玩一玩這個就明白了。 這一次製做的只涉及分數表上半區的部分。
Yahtzee遊戲打印出的策略
Yahtzee遊戲打印出的策略

爲這個遊戲編寫 AI 最有趣的地方是涉及到了機率和指望。我手上還有這麼些骰子,那麼接下來可能出現的全部手我都要算一遍,列成一個樹,而後找到機率最大的一種。我把列出全部可能手、爲一手計算指望值、爲一手計算分數和 AI 策略分別寫在 4 個方法裏。

第十一週:殭屍末日

殭屍末日
一羣人類(綠點)被殭屍(紅點)包圍在破敗廢墟中的場景。請自行腦補。

啊,殭屍。也不知道誰規定的,殭屍及其變種的怪物成了無數影視遊戲中人類能夠毫無道德顧慮地擊殺的遊戲怪物。

這個遊戲的畫面如上圖所示:

  • 黑色是障礙物。能夠理解爲房子、籬笆、爛掉的車什麼的;任何單位不能經過。
  • 紅色是殭屍。它們能夠向上下左右四個方向移動,會自動前往最近的人類。
  • 綠色是人類。他們能夠向8個方向移動,會自動遠離殭屍。請不要吐槽爲何顏色好像應該反過來。
  • 紫色是感染者。被殭屍抓到的人類就會這樣,不會動。能夠理解爲啃翻在地上,過一下子就要變成殭屍起來。

全部的格子都是能夠由玩家自行佈置的。所以這是個樂趣在於 YY 的沙盒遊戲。

點擊 "humans flee" 按鈕則人類移動一回合,點擊 "zombies stalk" 按鈕則殭屍移動一回合。它們採起的尋路策略都是廣度優先搜索。遊戲不會結束,你能夠在這個沙盒中給本身安排勝利條件。佈置各類各樣的場面看着它們行動,也還能支撐個半小時的樂趣,是到目前爲止製做的可玩性最強的遊戲……

一樣的,這個遊戲也是一個具備充分擴展性的遊戲。感染者會不會轉化成殭屍?人類能不能拿到武器反擊殭屍?殭屍中間會不會有特殊感染者,可以範圍攻擊、遠程拉住人類、能跳來跳去或者會爆炸?玩家這個上帝的力量有多大?跳出「玩家扮演遊戲中的某個角色」的框框,會發現沙盒類遊戲的樂趣所在。

第十二週:猜詞遊戲

猜詞遊戲

猜詞遊戲就是這樣:你指定一個詞,電腦會搜索詞庫,將這個詞的字母能組成的全部詞以星號遮住,你逐個嘗試將他們列出來的遊戲。

這個遊戲中第一次涉及到讀取文件。

爲了成功的讀取到輸入的詞彙而且匹配全部可能組成的詞,咱們須要使用一個 merge_sort() 方法來將一個打亂的列表變成有序的。這時我第一次接觸到「遞歸(recursion)」。

要理解遞歸,首先要理解遞歸(誤)。也就是說這個方法本身不斷引用本身。看起來就像

merge_sort(something):
    ...
    merge_sort(something_again)
    ...

這樣。

設計一個遞歸方法前,首先要明確中止遞歸的條件(base case)。在這個基礎上推算每一步應該怎麼辦。能夠拿一個簡單的例子在紙上演示,無誤後寫出來看看效果。

個人設想中,當給出一個 list 後,首先應當將其分紅兩半,當字母的個數小於等於 1 就應該中止遞歸。

最後寫成的方法看起來像這樣:

def merge_sort(list1):
    """
    Sort the elements of list1.
    Return a new sorted list with the same elements as list1.
    This function should be recursive.
    """
    new_list = list(list1)
    # base case: when length is 1 or 0
    if len(new_list) <= 1:
        return new_list
    # recurrences:
    if len(new_list) > 1:
        # split in half
        mid = len(new_list) / 2
        half_list1 = new_list[0:mid]
        half_list2 = new_list[mid:]
        # call merge_sort on each half
        list1_sorted = merge_sort(half_list1) 
        list2_sorted = merge_sort(half_list2)
        # and merge each sorted <- 在這個方法中會對2個元素進行排序
        return merge(list1_sorted, list2_sorted)

對我來講遞歸仍是挺複雜的。一個簡單的遞歸就要想好久,不過想清楚了以後的效果仍是不錯的。很多複雜的遊戲設計中都會出現相似的規則。

固然,你也能夠不使用遞歸,而是設定一些條件重複地調用一個方法。但那樣的話代碼量就變得很大,執行效率可能也會變慢。你是要犧牲易理解性換取效率,仍是犧牲效率換取易理解性呢?不少時候玩家也會試圖來理解你遊戲的內在邏輯,能不能讓他們輕鬆辦到呢?

第十三週:九宮格(tic-tac-toe)

九宮格

九宮格,世界各地的小朋友可能都玩過的經典遊戲。放大到5連就是五子棋。

爲這個遊戲編寫電腦對手採用的是所謂的「蒙特卡羅方法」。也就是從目前這一步開始,推算出每個可能的遊戲結果。勝則加分,負則扣分,和則不加不減;最後選定分數最高的一步落子。這種算法在棋盤複雜的的狀況下很難實用,但應付九宮格是綽綽有餘。

而後,爲了測試這個對手到底強不強,我把遊戲規則反了一下變成「逆九宮格」。也就是誰先連到 3 個就算輸。這種模式下,沒有下中間那個位置的不敗手,更能看出電腦的實力。第一盤我還沒反應過來,結果輸掉了。

逆九宮格

逆九宮格:先達成三個一線者負

到這裏,我編寫的 AI 就擺脫了特別直覺的 if-else 或者廣度優先搜索規則,進入了一個發揮其強大計算力的時代。假如把棋盤擴大幾倍,勝利條件相應放大,人類就很難打敗電腦了。

第十四周:數字推盤遊戲(n-Puzzle)

15-puzzle

一開始的遊戲是15個方格,數字錯亂了,須要你來把它們移動回正確的位置。有一種改進型就是拼圖,首先你要找出圖片的正確順序,而後還要推回正確位置。

遊戲自己的規則不難,但要作一個自動解 Puzzle 的 AI 就有點意思了,根據反覆試玩觀察,一個盤面能夠分爲幾個區域,各自有固定解法:

  • 第二行如下第一列右側的的
  • 第二行如下最左邊一列的
  • 第一行的
  • 第二行的
  • 最末階段左上角的4個

你們能夠觀察動畫裏面解開的過程,研究一下在這些區域我讓電腦怎麼動做的……

一個個模塊分別編寫和測試,在內部再分狀況討論,真是件體力活!但只要測試無誤,不管這個 puzzle 擴展到多大,解開它也就是時間問題。之後誰再拿這種東西爲難你,只要把題目輸入進去,就能看着電腦瞬間自動解開而且給你一個操做順序了。

35-puzzle

結語

在整個的 14 周過程當中間,我從能寫簡單的幾十行程序,逐漸進步到能完成較複雜的600行程序(不含UI部分)。在此過程當中,我逐步學到和應用的知識有:

  • python 基礎語法
  • 變量
  • list
  • 方法(function)
  • 類(class)
  • 各類算法
  • 遞歸
  • 編程的 style 要求

……等等,族繁不及備載。這些知識以及應用的方法有可能忘卻,但在此過程當中有着更多東西是令我體會深入,很難忘記的:

  • 將「手感好」、「手感很差」等感受分析成多個具體部分,進行調整。
  • 評估各類實現某個功能的手段,依據其複雜程度或者實現效率。
  • 分步計劃並實現你指望的功能,最後組合成完整的遊戲。

這些是在布魯姆教育目標分類法被列爲比較高級的認知類型。知識能夠被忘記,理解應用的過程會讓你有一些印象,而分析評估合成的過程則能夠逐步內化成你本身的能力。你從別人那裏聽來的經驗是知識,也許你在本身行事的過程當中可以理解一些、應用一些,但更高級的認知,則非親手實踐不能取得。

布魯姆分類法

若是你在遊戲或者互聯網行業,但你並不知道程序同窗們怎麼工做、想些什麼;或者總以爲本身的設想與實現之間有着一道障壁。也許本身親手實現(implement)本身設想的過程會帶給你啓發。

至少我在這 14 周每週作一個遊戲的過程當中,確實有這樣的體會。除此以外,親手實現設計的快感,掌握本身做品的快感,也是無可比擬的。

相關文章
相關標籤/搜索