日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

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

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

作為一個(gè)飽經(jīng)風(fēng)霜的程序員,你一定早就習(xí)慣了游戲開發(fā)中的反復(fù)。記得剛來公司的時(shí)候就聽?zhēng)熜种v過一個(gè)故事:策劃在國慶節(jié)前突然想模仿微信做一個(gè)紅包系統(tǒng),還沒等他實(shí)現(xiàn)完畢,紅包二期的案子就已經(jīng)寫好了。不巧,這個(gè)幸運(yùn)的師兄碰巧要去同學(xué)聚會(huì),就暫時(shí)沒有做。等他請(qǐng)假2天回到公司的時(shí)候,驚奇地發(fā)現(xiàn)紅包二期已經(jīng)被推翻了,變成了紅包三期,師兄長舒一口氣,我想此刻他的內(nèi)心是復(fù)雜的,甚至復(fù)雜到不能用代碼表達(dá)~~

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

1.復(fù)雜的繼承

計(jì)算機(jī)專業(yè)出身的同學(xué)對(duì)面向?qū)ο缶幊桃欢ú荒吧?#xff0c;這基本上是所有學(xué)校的計(jì)算機(jī)必修課,其實(shí)它也對(duì)很多公司的游戲架構(gòu)產(chǎn)生了深遠(yuǎn)的影響,在我們公司的代碼庫里,也有它的身影。在長期的維護(hù)中,這些本身設(shè)計(jì)良好的繼承關(guān)系由于需求的變動(dòng)不斷改變著繼承關(guān)系,最后的結(jié)果就是,出現(xiàn)了很深很深的繼承,這樣的代碼很難維護(hù),就像一個(gè)人很難一下說出自己應(yīng)該如何稱呼自己的爸爸的爸爸的爸爸的爸爸的兒子,一個(gè)需求的改變可能要求你理清這些類之間的關(guān)系,是要先調(diào)用父類的函數(shù)呢,還是先調(diào)用父類的父類的函數(shù)呢,還是重寫它。你甚至?xí)萑胍环N旋渦,這樣的設(shè)計(jì)很難讓邏輯清晰。

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

這個(gè)可以舉一個(gè)實(shí)際的例子,游戲中有多種不同類型的戰(zhàn)斗,在戰(zhàn)斗的過程中,我需要顯示血條、倒計(jì)時(shí)、連擊數(shù)、方向盤、技能面板、傷害排行榜、暫停按鈕、托管按鈕等等等等。但是不巧,每一種戰(zhàn)斗需要的內(nèi)容是不一樣的,比如排行榜只在組隊(duì)模式下需要,而暫停按鈕則不能在PK模式中顯示,血條在PK模式要換另一種表現(xiàn)方式。一開始這對(duì)我們來說很簡(jiǎn)單,幾個(gè)ifelse就可以輕松搞定,但是看看維護(hù)了一年之后的UI代碼吧,3000行的代碼和充斥著整個(gè)文件的20多種戰(zhàn)斗的特判,沒有人敢維護(hù)這塊代碼,因?yàn)樗疽桓木蜁?huì)在別的關(guān)卡中出現(xiàn)BUG。

之前的代碼大概是這個(gè)樣子的,是不是感覺似曾相識(shí)呢。
?

  • 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
  • 復(fù)制代碼


    面對(duì)上面的問題,明顯我們應(yīng)該做點(diǎn)什么。很不幸,當(dāng)時(shí)老大把重構(gòu)戰(zhàn)斗UI的任務(wù)交給了我,都說做成事情的第一步就是開始動(dòng)手,在和大家進(jìn)行了簡(jiǎn)單的討論后,我們得到了幾個(gè)結(jié)論:

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

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

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

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


    大概是這個(gè)樣子,ui_game_battle_normal是某種戰(zhàn)斗的UI,我只需要?jiǎng)?chuàng)建一個(gè)空殼,然后把每個(gè)需要的組件添加進(jìn)去就夠了,對(duì)于ui_game_battle_city_pk這個(gè)界面,我只需要倒計(jì)時(shí)、方向盤和HP,那我就只把他們?cè)黾舆M(jìn)去。
    ?

  • 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
  • 復(fù)制代碼


    addComponent和管理組件的方法寫在一個(gè)基類ui_game_battle_base中,因?yàn)槲覀儾魂P(guān)心QQ靚號(hào)買賣平臺(tái)組件具體的內(nèi)容,只需要對(duì)它進(jìn)行初始化和更新就行了,以下代碼中的OnUpdate由游戲引擎的時(shí)鐘驅(qū)動(dòng),然后分別調(diào)用組件的更新。

  • 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
  • 復(fù)制代碼



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


    不過除了讓人舒服的特性,在實(shí)現(xiàn)的過程中也遇到了一些小麻煩。因?yàn)槟撤N原因,倒計(jì)時(shí)模塊需要通知血條模塊進(jìn)行變化,當(dāng)時(shí)有點(diǎn)傻眼,組件之間到底應(yīng)該怎么交互呢?之前的結(jié)構(gòu)貌似完全沒有考慮過這個(gè)問題,如果我們隨便去獲取其他組件,本來清晰的結(jié)構(gòu)不是又要亂掉了嗎?組件之間會(huì)互相依賴!fuck!

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

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

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

    3.在容器中實(shí)現(xiàn)通用的消息機(jī)制,每個(gè)組件可以通過容器發(fā)送廣播,感興趣的組件自己監(jiān)聽對(duì)應(yīng)的消息就可以了(缺點(diǎn):事件太多之后,你會(huì)往返于事件之間,搞不清楚誰的更新依賴誰)

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

    最后的最后,發(fā)表一下對(duì)組合的感嘆,這是一種非理性的感嘆:組合是創(chuàng)造之魂,它符合世界本身的運(yùn)行規(guī)律,就像質(zhì)子、中子、電子組成分子,細(xì)胞組成人,各種不同的硬件組成了你的臺(tái)式電腦。組合存在著無數(shù)的可能性,來來來,嘗試把這兩個(gè)家伙放在一起,看看會(huì)發(fā)生什么!

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。