物件導向的程式設計在遊戲開發中使用(七):享受勞動果實

遊資網發表於2019-08-30
物件導向的程式設計在遊戲開發中使用(七):享受勞動果實

我並不是遊戲開發的從業人員,甚至連軟體開發都不是,但至少我是程式設計師。我認為,一個【物件導向】的理念在學習過程中的重要性遠大於對於程式碼本身含義的認識。這一點可以在諸多書籍中得到論證,但很奇怪的一件事是,絕大部分的視訊入門教程和並沒有過多的強調這一點。

系列收藏夾:https:/cowlevel.net/collection/3476202

1增加我們的類和修改漏洞

既然我們已經把數字獨立出來了,該輪到我們享受勞動成果啦!

我們需要創造一個新的數,分數,從基礎的數繼承

因此要遵循本來的【協議】來實現

作為一個分數,我們同時接受分號(|)

  1. func can_push(num):
  2.         return ((num is int) or (num == '|'))
  3.         pass
複製程式碼

另外一個地方,我們將接受分母和分子,每次輸入“分號”就把“游標”定位到另外一方

  1. func push_number(num):
  2.         if num is int:
  3.                 if pushMode :
  4.                         number[0]*=10
  5.                         number[0]+=num
  6.                 else :
  7.                         number[1]*=10
  8.                         number[1]+=num
  9.         else:
  10.                 pushMode = !pushMode
  11.         pass
複製程式碼

在我們顯示數字的時候,我們將分母與分子使用分號隔離

  1. func get_number():
  2.         return (str(number[0])+"|"+str(number[1]))
  3.         pass
複製程式碼

而我們作為一個“等級”更高的數字,應該從更低的數字接受輸入

  1. func upgread(num):
  2.         if num.get_class() == "AbsBaseNumber":
  3.                 number[0] = num.get_data()
  4.         pushMode = false
  5.         pass

  6. func push_number(num):
  7.         if number.can_push(num) :
  8.                 number.push_number(num)
  9.         elif AbsFracNumber.new().can_push(num):
  10.                 var tN = AbsFracNumber.new()
  11.                 tN.upgread(number)
  12.                 number = tN
  13.         pass
複製程式碼

最後,我們更改加減的實現

並且為分數加入一個約分的功能

  1. func redu():
  2.         var redN
  3.         redN = number[1] if number[0]>number[1] else number[0]
  4.         while(                                                                                                                \
  5.                 (number[0] %redN != 0) or (number[0] %redN != 0)                \
  6.         ):
  7.                 redN -= 1

  8.         if redN!=1 :
  9.                 number[0] /=redN
  10.                 number[1] /=redN
  11.         pass

  12. func do_add(num, ans):
  13.         var numD = num.get_data()
  14.         if num.get_class() == "AbsBaseNumber":
  15.                 ans.set_number([number[0] + numD*number[1], number[1]])
  16.                 ans.redu()
  17.         else:
  18.                 ans.set_number([        number[0]*numD[1]+number[1]*numD[0]        \
  19.                         ,                                        number[1]*numD[1]                                ])
  20.                 ans.redu()
  21.         return ans
  22.         pass
複製程式碼

當我們寫到這裡突然會發現一個問題

A-B不等於B-A。這是一個BUG,而我們要去修復他

在現有邏輯之上,當我們需要B-A的時候,我們需要為結果裝上一個負號

  1. if num1.can_operation(form[0]):
  2.                 tNum = num1.operation(form[0], symb, tNum)
  3.         else:
  4.                 tNum = form[0].operation(num1, symb, tNum)

  5.                 if symb == BusiLogic.BT["SUB"]:
  6.                         tNum.do_mins()
複製程式碼

也因為如此,我們會把MyNumber和所有的抽象數字都增加取反的函式

物件導向的程式設計在遊戲開發中使用(七):享受勞動果實

Yeah,這是一個能用的計算器對吧?但是我們的程式碼夠好嗎

2堆疊程式碼

我們可以注意到,我們的“分式”是int/int

但是對於一個更復雜的計算器,或者一個更抽象的數字,我們期待的是一個A/B

所以對於我們的分式,我們更期待一個

[MyNumber,MyNumber]

而並不是

[int,int]

在修改之後,我們在推入數字

  1. func push_number(num):
  2.         if num is int:
  3.                 if pushMode :
  4.                         number[0].push_number(num)
  5.                 else :
  6.                         number[1].push_number(num)
  7.         else:
  8.                 pushMode = !pushMode
  9.         pass
複製程式碼

這樣我們在引入根號或者其他什麼東西之後,我們可以很簡單的引入成為根號分之根號,

由於在分數運算中我們引入了乘法,而我們並沒有對乘法的具體實現,所以在這個專案中並沒有辦法完成這種操作,但是這並不會阻擋我們的討論

在一個更直觀的例子中,我實現了一個右鍵的“彈出選單”

  1. func signal_press():
  2.         if message.size() == 1:
  3.                 emit_signal("en_press",[message[0]])
  4.         else:
  5.                 next_pop_node = next_pop.instance()
  6.                 add_child(next_pop_node)
  7.                 next_pop_node.init(message[1],get_parent().rect_position + Vector2(100,20*buttonN))
  8.                 next_pop_node.connect("popup_return",self,"_sub_popup")
  9.         pass
複製程式碼

當按鈕有子選單,就建立一個子選單,如果按鈕沒有子選單,就返回按鈕按下的資訊

很簡單對吧?

在這個例子裡,我們可以輕易的建立出二層到多層選單,而不用更多的程式碼來完成對多級選單的維護。

3Are We Cool Yet?

回到我們的程式碼當中,我們的程式碼足夠“完美”嗎?

首先我們實現了一個萬能數,這實際上是一個“狀態類”

關於狀態模式

但是我們萬能數裡的抽象數並不是一個抽象數類,而是像“整數”“分數”這樣具體的數字

我們可以使用程式語言提供的抽象類來將所有抽象數真的實現一個”抽象數類“然後所有抽象數繼承自這個類,這會是一個非常好的習慣。

雖然如此,我們實際上已經遵循了抽象類的原理,那就是所有子類都必須實現抽象類的功能,只不過這個抽象類並沒有被我們寫進程式碼裡。

另外,我們實際上把計算功能放在數字內部了,這是因為我們在設計之初就把計算作為數字的功能之一,這有利於在規劃不明的情況下可以更好的把一個新的抽象數加入到我們的程式中,這種偶合,在數字中看起來還行的通,但是很多時候,我們希望把“數字”和“運算”隔離開並且可以分開維護。

在加入新的抽象數之後,我們並不是簡單的把數字加入到程式碼中就可以了,我們還需要為新的數字增加按鍵,為新的數字編號。

更麻煩的是,每次加入一個新的數字型別,我們需要在萬能數的”升級“規則里加入相應的新型別,我們可以設立一個列表,把每個抽象數的”樣板“加入到列表中,使用迴圈遍歷這個列表直到找到適合的數字,替代我們”特立獨行“的升級規則

同時,雖然我並沒有展示介面生成的程式碼,當有了新的數字,和他專屬的符號,我們需要手動新增專屬的符號,然後定義他的位置

這對於一個計算器是理所當然的,但在遊戲裡,這明顯會增加維護成本

就像Minecraft中,製作Mod並不需要在遊戲裡指明位置,只需要通知主程式我們需要加入的東西,主程式就可以為我們安排新的按鈕和位置,那麼如果我們的目標是使得一些開發者可以為我們計算器提供擴充套件,製作一些MOD,我們會需要一個序號產生器制

額外的,我們在程式碼中沒有加入任何的損害管制,或者災難控制。

在任何程式中,我們不應該過於樂觀,而是要思考“如果不是怎麼辦”

這在多人開發中有助於維護多個開發者的同步性

我們在開發一個抽象數之前,我們本應該建立一個“測試”

所有的抽象數都應該進入這個“測試”來使用規則執行每個抽象數,這個測試應該儘可能的覆蓋所有的情況,並且不應該覆蓋所有錯誤的情況。只有通過了測試的抽象數才可以被納入到程式碼之中

3超程式設計

什麼是超程式設計?

超程式設計的含義核心是“用程式碼去製造程式碼”

這並不是一種程式設計技巧,超程式設計是一種更加方便的的高度自定義

方便的超程式設計從來不是一種程式設計語法糖,而是“菸草”會讓你上癮並把你的身體糟蹋的一塌糊塗

我們應該在一個專案的最初,更細節的規劃好要使用的功能,並把這些功能“固化“到程式碼裡

而不是在程式設計過程中把一些變化”超程式設計“進程式碼裡,因為這種變化並不是簡單的改變某個變數,或者某個字串,而是改變了程式本身

這會使得程式更加難以維護

同樣的,針對一個功能,使用超程式設計會更簡單,但會打破”統一“。我們應該只在必要的情況下作為最後方案使用超程式設計,而不是把超程式設計當作我們的生存必需品。

4總結

這個系列到此完結,

大家可以訪問

碼雲倉庫

來訪問我們所用到的程式碼,這並不是一個完整的專案,因為專案的程式碼實際上是很不完全的

如果有時間,未來可能會更新godot的學習歷程

godot的程式設計體驗很好,而且我相信godot在一段時間以內會發展的越來越好

因此,我希望人們更多的瞭解這個引擎

系列文章
物件導向的程式設計在遊戲開發中使用(一):類
物件導向的程式設計在遊戲開發中使用(二):方法
物件導向的程式設計在遊戲開發中使用(三):三大特性
物件導向的程式設計在遊戲開發中使用(四):五大原則

物件導向的程式設計在遊戲開發中使用(五):基本計算器
物件導向的程式設計在遊戲開發中使用(六):數與抽象數

物件導向的程式設計在遊戲開發中使用(七):享受勞動果實

作者:Kingfeng
來源:奶牛關
原地址:https://cowlevel.net/article/2055693


相關文章