RolePlayingDesign

出自gamedev
跳至導覽 跳至搜尋



這可能是構成Arianne的所有中間件中最複雜的部分.
Role Playing Design 角色扮演設計是決定創建一個新遊戲有多 easy 的關鍵因素. 我們選擇簡化基於有限回合時間的遊戲的創建。 Arianne在此類遊戲(也稱為實時遊戲)上工作的更好。

角色扮演設計試圖做到通用且與遊戲無關(獨立於所製作的遊戲)。 RPManager幕後的基本思想是:

  forever
    {
    Execute Actions   // 执行动作
    Send Perceptions  // 发送感知
    Wait for next turn // 等待下一回合
    }

為此,我們使用以下幾個類:

  • RPManager 代碼在 Marauroa 中,不需要修改.
  • IRPRuleProcessor 是您需要修改以個性化遊戲將執行的動作的接口.
  • RPWorld 是您需要擴展以實現onInit和onFinish方法的類,這些方法可以對初始化伺服器時發生的情況以及在關閉伺服器時發生的情況進行個性化.
  • IRPZone 如果您想實現最大程度的遊戲個性化,這可能是您要實現的接口。但是, 建議使用 MarauroaRPZone, 因為它很好地實現了 Delta2 特點.


RPManager[編輯]

RP Manager 的目標是處理遊戲中的RP. 也就是說:

  • run RPActions from clients // 從客戶端執行RPAction
  • manage RPWorld // 管理RPWorld
  • control triggers for events // 控制事件的觸發
  • control AI // 控制AI

這是一個非常複雜的任務。 因此,我們將它分為較小的子類.

RPManager 暴露一個簡單的接口給 GameManager:

  • addRPAction
    此函數將特定玩家的動作排隊,在下一個回合執行.
  • getRPObject
    這是管理RPWorld的接口,可在退出遊戲時簡化對RPObject的獲取.
  • onInit Player
  • onExit Player
    這些是GameManager用來通知玩家已經進入或退出遊戲時的回調函數.
  • transferContent
    回調函數。 RPRuleProcessor 調用這個函數,將遊戲內容傳給玩家.

RPManager 的主要流程如下:

forever
  {
  Proceed through every action in this turn   // 处理此回合中的所用动作
    {
    rpRuleProcessor executes action           // rpRuleProcessor执行动作
    }

  Build Perception                            // 构造感知
  Remove timed out players                    // 移除超时的玩家  

  Wait for Turn completion.                   // 等待回合结束
  Go to Next Turn                             // 进入下一回合
  }

RPScheduler 是處理每個玩家排隊的動作的類。 動作管理的複雜性應該在這裡處理。

RPRuleProcessor 是動作代碼以及初始化和退出代碼的包裝類.
所有動作代碼必須放在這裡.
通過實現 RPRuleProcessor,您可以將 Marauroa 個性化為您想要製作的遊戲。 但是,請記住,您僅限於實時風格的遊戲。

對象和動作[編輯]

整個 Marauroa 系統由兩個主要實體 RPAction 和 RPObject 管理。 還有幾個輔助類,如 Attributes、RPSlot 和 RPClass

屬性(Attributes)[編輯]

屬性是名稱-值形式的值對的集合. 我們可以在 Attribute 對象中存儲幾乎所有基本類型:

  • strings
  • integer
  • floats
  • boolean

我們不能在屬性中存儲結構,但您可以將一組數據轉換為字符串並將其存儲為字符串。 Marauroa 為此提供了助手方法(helper method)。

Objects[編輯]

Marauroa 伺服器中的所有信息都包含在 RPObjects 中。 一個對象由幾個屬性(一個屬性類似於一個變量,它有一個名稱並包含一個值)和插槽組成。 Slot 是屬於某個對象的容器或容器數組,用於在其中託管(存儲)其他對象。

強制對象屬性: id, type and zoneid

id 是對象的唯一標識,zoneid 是對象所在區域的標識,type 是對象的類型,也就是class,因此您可以為一個類的所有實例共享屬性。

id 僅在包含該對象的區域內是唯一的。

注意: 引擎提供了兩種特殊類型的屬性: - 以 ! 開頭的屬性 對除對象所有者之外的所有其他用戶隱藏。 - 以 # 開頭的屬性對所有用戶隱藏。

對象類型的解釋[編輯]

對象類是構建 Marauroa 數據結構的基本方式。 給定對象的屬性類型必須等於您希望使用的類型類(type class)的 RPClass 名稱。

類定義屬性的類型、其可見性並為其分配一個內部代碼,用於加速查找和節省帶寬。 您可以基於另一個類創建新類,此功能稱為繼承(從已經存在的類創建一個新類,並獲取所有原始類的方法和數據並對其進行擴展)。

可用的數據類型有:

  • Strings
  • Short strings ( 最多 255 字節 )
  • Integers ( 4 字節 )
  • Shorts ( 2 字節 )
  • Byte ( 1 字節 )
  • Flag ( 二進制屬性 )

屬性可以是可見的,這意味著客戶在更改時可以看到它們,也可以是不可見的,那樣客戶就看不到它們。

Slots[編輯]

對象可以駐留在其他對象中,就像您將鑰匙放在口袋裡一樣。 Slots 的目標是提供更豐富的遊戲玩法,同時減少區域中的對象數量。

為了在裡面放置對象,我們需要我們的宿主對象有插槽來放置它們。一個插槽只能處理一個對象。

例如,角色可以有:

  • left hand 左手
  • right hand 右手
  • backpack 背包
  • left pocket 左口袋
  • right pocket 右口袋

我們可以在每個插槽中存儲對象。

一旦對象存儲在對象槽中,訪問存儲對象的唯一方法是通過包含存儲對象的對象。

和屬性一樣,插槽也有兩種特殊類型:

  • 以 ! 開頭的插槽名稱 僅發送給擁有它的玩家。 (所以只有屬主能看到)
  • 以 # 開頭的插槽名稱不會發送給玩家。 (所有人都看不到)

Actions[編輯]

為了表達客戶端做某事的意願,它必須向伺服器發送 MessageC2SAction 消息。

一個動作由幾個屬性組成。 (屬性類似於變量,因為它有一個名稱並包含一個值)。

有可選和強制屬性。 如果找不到強制屬性,則 RPServerManager 會跳過該消息。

強制操作屬性是 action_id 和 type。

action_id 用於識別感知中的結果響應中包含的動作

Perceptions[編輯]

向客戶端發送的世界更新的基本結構稱為感知。

有兩種感知:

  • 同步感知:用於將客戶端與伺服器世界同步。 這是了解世界狀態的唯一有效方式.
  • Delta 感知:這用於僅發送世界自上次感知以來的變化。

我們實際的感知系統稱為 Delta2。 它與 Marauroa 核心緊密相連,因此我建議您使用它 :)

感知和動作是怎麼工作的[編輯]

動作從客戶端發送到伺服器,以使角色執行動作。 為了讓客戶端知道操作的結果,伺服器需要向客戶端發送回復。 那麼,這些是怎麼完成的呢?

剛開始的時候,我們向客戶端發送一個它的操作結果的操作。 然而,這使得代碼變得非常困難,因為我們必須更新兩個不同的東西,感知和動作。 一個直觀的解決方案是:為什麼不把動作結果和感知放在一起呢?

因此,動作結果存儲在每個對象(執行動作)中,並帶有一組屬性,這些屬性確定動作返回狀態和屬性。 這種發送結果的方式給 RPManager 編程帶來一些困難,但它大大簡化了新客戶端的創建。

請參閱 Objects 文檔中的 Actions 響應以了解返回的內容。 但是,請記住,返回結果取決於每個特定的遊戲。

Delta2 感知算法[編輯]

DPA 背後的想法是避免每次都將所有對象發送給客戶端,而只發送那些已修改的對象。

假設我們有 1000 個對象,只有 O1 和 O505 是活動對象,每回合都會修改。

傳統方法是:

- 获取玩家应该看到的对象 ( 1000 objects )
- 将它们发送给玩家 ( 1000 objects )
- 下一回合
- 获取玩家应该看到的对象 ( 1000 objects )
- 将它们发送给玩家
- 下一回合
...

我希望你已經看出問題了……我們發送的對象每回合都沒有改變。

delta感知算法:

- 获取玩家应该看到的对象 ( 1000 objects )
- 将列表缩减为修改后的列表( 1000 objects )
- 同时存储不再可见的对象 ( 0 objects )
- 将它们发送给玩家 ( 1000 objects )
- 下一回合
- 获取玩家应该看到的对象 ( 1000 objects )
- 将列表缩减为修改后的列表 ( 2 objects )
- 同时存储不再可见的对象 ( 0 objects )
- 将它们发送给玩家 ( 2 objects )
- 下一回合
...

delta 感知算法的下一步非常明確:delta2 想法是只發送發生變化的對象。 通過這種方式,我們可以節省更多帶寬,使感知大小約為原始增量感知大小的 20%。

delta2 算法基於四個容器:

  • List of added objects
  • List of modified added attributes of objects
  • List of modified deleted attributes of objects
  • List of deleted objects

與 DPA 非常相關的一個領域是 RPZone(請參閱本文檔後面部分)

如您所知,MPEG 視頻每 X 幀添加一個完整幀,可以用作同步,以防文件損壞。 這個想法是,如果你無法繼續解壓縮數據,那麼可以忽略一些數據,直到獲得下一個完整的幀,從而再次同步。 這裡的想法是類似的,如果我們無法與伺服器同步,我們會向它發送一個 Out of Sync 消息,以便伺服器發送一個同步感知,使客戶端同步。 請記住,UDP 不是安全傳輸。

為了讓感知起作用,調用 RPZone 中的 modify 方法很重要,這樣修改的對象就會存儲在修改列表中。

Zones and Worlds[編輯]

Marauroa 的世界可能非常大,我們需要把它們分成幾塊。 每一塊都是我們所說的RPZone。

所以世界是由幾個相互獨立的 的RPZones 組成的。 要從一個RPZone移動到另一個RPZone,您必須在RPRuleProcessor中編寫正確的行為。看看我們的代碼示例就知道了。

RPWorld[編輯]

正如我們已經說過的,RPWorld存儲了幾個相互獨立的RPZones。
RPWorld 提供了 onInit 和 onFinish 方法在伺服器初始化和伺服器結束時被調用,它們定義了如何處理這些事件。這其中不存在默認行為,你需要擴展這個類來重新定義行為。

它還提供了添加和獲取新RPZones的方法:

  • addRPZone
  • getRPZone, 用於獲取RPZone.ID或者RPObject.ID。

最後,它還包含管理RPObjects的方法:

  • addRPObject, 需要RPObject 在它的RPObject.ID中包含一個有效的RPZone.ID。
  • getRPObject
  • modifyRPObject
  • changeZone 將一個對象從舊zone移動到新zone並添加正確的有效id。

最後,RPWorld還包含一個名為nextTurn的方法,RPManager調用該方法來從一個回合移動到下一個回合。 它重置了delta2 數據。

RPZone[編輯]

對象必須存儲在某個地方,我們使用區域Zones來存儲它們。 區域Zone只是一個具有名稱的對象容器。

每個RPZone必須 有一個惟一的名稱。

為了改善Marauroa平台的可修改性,我們使RPZone成為接口,以便您可以根據需要實現它.

但在大多數情況下, 如果你認為Delta2 系統很好並且貼合遊戲風格,您可以使用MarauroaRPZone,它是我們Delta2算法的參考實現。

實際的Marauroa RPZone由幾個數據結構組成:

  • a HashMap of RPObject.ID to RPObject
  • a List of RPObject
  • a Perception

想法是事先已經在區域中計算了感知,從而節省了通常生成感知所需的大量時間。 所有數據結構都包含相同的對象,但 hashmap 用於使用 RPObject.ID 快速搜索對象。 這是定位具有已知 ID 的對象的最常用方法。 列表用於改善建立總體感知所需的時間。 感知用於預先計算增量感知(即查找區域的當前狀態與上一回合發送給客戶端的先前狀態之間的變化)

區域內所有玩家的感知都是相同的.

為了使perceptions工作, 您必須手動調用modify方法,以便將角色或物品的更改告訴zone 。 {{#breadcrumbs: Marauroa | Internals | Role Playing Design }}