日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

游戏编程技巧分析:策划变心太快?也许可以使用组合

發布時間:2024/8/26 编程问答 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 游戏编程技巧分析:策划变心太快?也许可以使用组合 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作為一個飽經風霜的程序員,你一定早就習慣了游戲開發中的反復。記得剛來公司的時候就聽師兄講過一個故事:策劃在國慶節前突然想模仿微信做一個紅包系統,還沒等他實現完畢,紅包二期的案子就已經寫好了。不巧,這個幸運的師兄碰巧要去同學聚會,就暫時沒有做。等他請假2天回到公司的時候,驚奇地發現紅包二期已經被推翻了,變成了紅包三期,師兄長舒一口氣,我想此刻他的內心是復雜的,甚至復雜到不能用代碼表達~~

那我們的代碼到底能不能經得起折騰呢?大概有兩種類型的代碼讓我印象深刻:

1.復雜的繼承

計算機專業出身的同學對面向對象編程一定不陌生,這基本上是所有學校的計算機必修課,其實它也對很多公司的游戲架構產生了深遠的影響,在我們公司的代碼庫里,也有它的身影。在長期的維護中,這些本身設計良好的繼承關系由于需求的變動不斷改變著繼承關系,最后的結果就是,出現了很深很深的繼承,這樣的代碼很難維護,就像一個人很難一下說出自己應該如何稱呼自己的爸爸的爸爸的爸爸的爸爸的兒子,一個需求的改變可能要求你理清這些類之間的關系,是要先調用父類的函數呢,還是先調用父類的父類的函數呢,還是重寫它。你甚至會陷入一種旋渦,這樣的設計很難讓邏輯清晰。

2.單個文件包含多種功能以及特判

這個可以舉一個實際的例子,游戲中有多種不同類型的戰斗,在戰斗的過程中,我需要顯示血條、倒計時、連擊數、方向盤、技能面板、傷害排行榜、暫停按鈕、托管按鈕等等等等。但是不巧,每一種戰斗需要的內容是不一樣的,比如排行榜只在組隊模式下需要,而暫停按鈕則不能在PK模式中顯示,血條在PK模式要換另一種表現方式。一開始這對我們來說很簡單,幾個ifelse就可以輕松搞定,但是看看維護了一年之后的UI代碼吧,3000行的代碼和充斥著整個文件的20多種戰斗的特判,沒有人敢維護這塊代碼,因為它基本一改就會在別的關卡中出現BUG。

之前的代碼大概是這個樣子的,是不是感覺似曾相識呢。
?

  • function GameBattleUI:init()
  • ? ? if gameMode == A then
  • ? ?? ???hP:setVisible(false)
  • ? ?? ???skillBoard:setVisible(true)
  • ? ?? ???joystick:setVisible(true)
  • ? ?? ???skillBoard.skillA:setVisible(true)
  • ? ?? ???skillBoard.skillB:setVisible(false)
  • ? ?? ???--some code
  • ? ? elseif gameMode == B then
  • ? ?? ???hP:setVisible(true)
  • ? ?? ???skillBoard:setVisible(true)
  • ? ?? ???joystick:setVisible(true)
  • ? ?? ???skillBoard.skillA:setVisible(true)
  • ? ?? ???skillBoard.skillB:setVisible(true)
  • ? ?? ???skillBoard.skillC:setVisible(true)
  • ? ? elseif gameMode == C then
  • ? ?? ???--some code
  • ? ? end
  • ? ? skillBoard.skillA:addTouchEventListener(
  • ? ?? ???function (sender, eventType)
  • ? ?? ?? ?? ?if eventType == ccui.TouchEventType.ended then
  • ? ?? ?? ?? ?? ? if gameMode == A then
  • ? ?? ?? ?? ?? ?? ???--do something
  • ? ?? ?? ?? ?? ? elseif gameMode == B then
  • ? ?? ?? ?? ?? ?? ???--do something
  • ? ?? ?? ?? ?? ? end
  • ? ?? ?? ?? ?end
  • ? ?? ???end
  • ? ? )
  • end
  • function GameBattleUI:update()
  • ? ? --和上面差不多
  • ? ? if gameMode == A then
  • ? ? elseif gameMode == B then
  • ? ? elseif gameMode == C then
  • ? ? end
  • end
  • 復制代碼


    面對上面的問題,明顯我們應該做點什么。很不幸,當時老大把重構戰斗UI的任務交給了我,都說做成事情的第一步就是開始動手,在和大家進行了簡單的討論后,我們得到了幾個結論:

    1.很多功能在不同的戰斗中是相同的,比如方向盤,不管在哪都是上下左右

    2.功能模塊之間互相不需要交互,比如方向盤和血條技能什么的沒有任何關系

    3.我們需要它變得更靈活,可以很方便地在某種戰斗中增加一個新的自定義UI類型

    這仿佛就是在告訴我們:你快用組件啊,就像堆砌樂高積木一樣去創造你的游戲世界!是的,用組件模式來重構真的再適合不過了。
    ?


    大概是這個樣子,ui_game_battle_normal是某種戰斗的UI,我只需要創建一個空殼,然后把每個需要的組件添加進去就夠了,對于ui_game_battle_city_pk這個界面,我只需要倒計時、方向盤和HP,那我就只把他們增加進去。
    ?

  • function cls_uiGameBattleCity3PK:InitUI()
  • ? ? local timeComponent = CreateComponentTime()
  • ? ? self:AddComponent({
  • ? ?? ???component = timeComponent,
  • ? ?? ???zoder = 10,
  • ? ?? ???layerTag = data.battle.UI_TAG_BATTLE_UI_NORMAL
  • ? ? })
  • ? ? local playerHpComponent = CreateComponentPlayerHPUI()
  • ? ? self:AddComponent({
  • ? ?? ???component = playerHpComponent,
  • ? ?? ???zoder = 10,
  • ? ?? ???layerTag = data.battle.UI_TAG_BATTLE_UI_NORMAL
  • ? ? })
  • ? ? local joystickComponent = CreateComponentJoystick()
  • ? ? self:AddComponent({
  • ? ?? ???component = joystickComponent,
  • ? ?? ???zoder = 10,
  • ? ?? ???layerTag = data.battle.UI_TAG_BATTLE_UI_NORMAL
  • ? ? })
  • end
  • 復制代碼


    addComponent和管理組件的方法寫在一個基類ui_game_battle_base中,因為我們不關心QQ靚號買賣平臺組件具體的內容,只需要對它進行初始化和更新就行了,以下代碼中的OnUpdate由游戲引擎的時鐘驅動,然后分別調用組件的更新。

  • function ui_game_battle_base:AddComponent(args)
  • ? ? local component = args.component
  • ? ? local zoder? ???= args.zoder
  • ? ? local layerTag??= args.layerTag
  • ? ? local componentName = component.__cname
  • ? ? local layer = self[tag_to_layer[layerTag]]
  • ? ? if layer then
  • ? ?? ???layer:addChild(component, zoder)
  • ? ?? ???if self.ComponentList[componentName] ~= nil then
  • ? ?? ?? ?? ?self.ComponentList[componentName]:removeFromParent()
  • ? ?? ???end
  • ? ?? ???self.ComponentList[componentName] = component
  • ? ? else
  • ? ?? ???echoError("[ui_game_battle_base][AddComponent] layer error" .. tostring(layerTag))
  • ? ? end
  • end
  • function ui_game_battle_base:OnUpdate()
  • ? ? for k,v in pairs(self.ComponentList) do
  • ? ?? ???if v.OnUpdate then
  • ? ?? ?? ?? ?v:OnUpdate()
  • ? ?? ???end
  • ? ? end
  • end
  • 復制代碼



    如此這般的設計,使代碼比原來靈活許多,邏輯也變得清晰,我們先實現一堆component,再用各種方式組合,形成類似ui_game_battle_normal這樣的實體,不僅代碼得到了解耦,以后策劃再要增加一個新的功能也變得異常簡單,要么是修改其中的一個組件,要么就是直接新建一個組件,然后把它add到實體中就可以了。重構完成后,看著簡單的目錄結構,內心覺得很舒服,LZ再也不怕策劃改這塊代碼了!
    ?


    不過除了讓人舒服的特性,在實現的過程中也遇到了一些小麻煩。因為某種原因,倒計時模塊需要通知血條模塊進行變化,當時有點傻眼,組件之間到底應該怎么交互呢?之前的結構貌似完全沒有考慮過這個問題,如果我們隨便去獲取其他組件,本來清晰的結構不是又要亂掉了嗎?組件之間會互相依賴!fuck!

    《游戲編程模式》中提出了3種解決方案,和之前公司分享時講到的方法幾乎完全相同,看來這是經過無數人實踐得出的優質答案:

    1.由容器儲存公用的變量,組件間可以通過訪問這個容器中的變量進行通信(缺點:可能有些容器中不需要這個變量,但是通用的容器還是要定義,浪費內存)

    2.保存另一個組件的引用。如果我們確定兩個組件之間有關系,可以在初始化的時候就傳入需要的組件的引用。(缺點:容器之間的關系很可能變得很復雜,甚至初始化順序都要有要求)

    3.在容器中實現通用的消息機制,每個組件可以通過容器發送廣播,感興趣的組件自己監聽對應的消息就可以了(缺點:事件太多之后,你會往返于事件之間,搞不清楚誰的更新依賴誰)

    這3種方式各有優缺點,比如說一個對象的位置信息是很常見的,完全就可以使用第一種方式,把它定義在容器中,所有組件都可以訪問。這時候后面兩種就顯得很不合適。具體要使用哪種方案要根據實際的需要進行分析,選取最優的。就像《游戲編程模式》中說到的:意料之外的是,沒有哪個選擇是最好的。你最終有可能將上述所說的三種方法都用到

    最后的最后,發表一下對組合的感嘆,這是一種非理性的感嘆:組合是創造之魂,它符合世界本身的運行規律,就像質子、中子、電子組成分子,細胞組成人,各種不同的硬件組成了你的臺式電腦。組合存在著無數的可能性,來來來,嘗試把這兩個家伙放在一起,看看會發生什么!

    總結

    以上是生活随笔為你收集整理的游戏编程技巧分析:策划变心太快?也许可以使用组合的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。