屬性計算
在遊戲之中,我們操縱的角色和一些非玩家角色都會有相關的數值描述,例如血量、等級、攻擊、防禦等等,下面就是暗黑破壞神3的乙個角色的屬性面板。
事實上,乙個角色的屬性描述的幾十個欄位並不是毫無邏輯關係的,同時完整的屬性關係之間可能還有其他的面板不可見屬性作為中間變數存在。所有可見不可見的變數會組成乙個有向無環圖。圖中的每個節點都有乙個對應的值,當這個值更新之後,會觸發當前節點可及的節點的更新,並遞迴執行更新過程。更新過程完成之後,通知外部模組進行響應,例如重新整理血量、重新整理面板等操作。
下面的就是乙個典型的dota計算公式:
> 護甲 = 基礎護甲 + 額外護甲 + 敏捷 / 3
而敏捷這個變數其實也是由計算公式生成的
> 敏捷 = (基礎敏捷 + 等級 * 敏捷成長) *(1 + 敏捷放大倍數) + 額外敏捷
這裡可以看出,等級提公升之後,敏捷會相應提公升,並因此更新護甲。
角色的屬性系統除了讓面板變得更好看之外,最重要的作用是計算角色之間的傷害,一次攻擊,附加傷害是多少,是否會暴擊,準確率是多少,是否被會閃避。
> 我這一刀下去你可能會死
這種屬性更新計算在大型的pvp活動中呼叫非常頻繁,需要乙個高效的結構去維護更新。同時由於技能和buff、道具系統的的無限擴充,整個屬性圖中可能有上千個節點,人工維護公式邏輯已經不太現實,需要提供更明了的工具來編輯公式及轉換為**。當前專案就是因此而生:
編輯器
屬性編輯器 直接復用了我之前的那個樹形編輯器,沒有其他的依賴:
這裡說一下這個屬性公式編輯器的工作流程:
1. 葉子節點有三種型別,字面值常量,輸入變數(`input name`),引用變數(`import name`), 輸入變數只能被外部修改,而引用變數則是通過計算公式計算出來的值,無法直接被外部修改,只能通過修改輸入變數來更新到引用變數。每個引用變數都有對應名字的公式輸出檔案。
2. 提供`choice.json` 裡面有兩個字段 乙個是`input_attrs`,這個是所有的輸入節點的名字 乙個是`import_attrs`,這個是所有的輸出節點的名字。如果需要新增輸入變數或者輸出變數,則需要更新這個檔案
3. 提供`formula_nodes.json` 裡面有所有型別節點的編輯器相關字段,如果想新增計算函式,需要更新這個檔案執行時
執行時依賴的如下幾個庫:
1. nlohmann_json 公式檔案格式
2. magic_enum 處理列舉與字串之間的轉換
3. any_container 我自己的乙個庫 處理型別與json之間的轉換
乙個角色的所有屬性所需公式被稱為一組公式,裡面有所有外部可見的輸出變數名稱。
1. 公式系統會裝載所有提供的公式,並對公式內部所引用的輸出變數也進行遞迴載入。
2. 每個對輸出變數的引用都會生成一條輸出變數的root節點到當前import節點的邊,通過這樣的連線,組成了乙個有向圖。如果這個有向圖裡面有環的話,代表變數之間互相引用了,這是乙個非法的公式。
3. 在組成乙個有向無環圖之後,我們在刪除所有的引用節點,把從引用節點出發的邊的起點都轉移到對應的輸出變數的root節點上。降低一點樹的深度。
自此,乙個角色的公式計算圖構建完成,現在我們來描述一下更新邏輯。
簡單版本的公式更新就是:每更新乙個節點,就深度優先的更新他的可達節點。但是這樣的更新有很嚴重的問題,如果從這個節點a出發到某個節點b有多條路徑,則b節點及從b出發可達的節點會被重複更新多次。類似的問題在一次性更新多個變數的時候也存在,如果節點a依賴於輸入節點b和輸入節點c, 如果b、c的值都發生了改變,會導致a被更新多次。所以我們需要提供乙個最優更新邏輯,保證乙個節點最多隻被更新一次。因此,在上面構建的公式計算圖的基礎上,我們需要標註節點的額外資訊:
> 對每個節點進行高度標記,所有的輸入節點的高度設定為0,然後進行遞迴更新,每個節點的高度等於所有子節點的高度最大值再加上1
在這個新增加的資訊基礎上, 我們提供了單變數更新和批量更新,其實單變數更新就是只有乙個變數的批量更新,所以我們這裡只闡述批量更新的邏輯:
1. 將所有的需要更新的輸入變數放到乙個任務佇列中
2. 只要佇列不為空,從佇列中取出高度值最低的節點,進行更新計算,如果值進行了改變,則將當前節點的所有可達節點中不在任務佇列中的節點加入到任務佇列
3. 如此重複直到任務隊列為空
在上面的更新結構下,我們就保證了乙個節點最多被更新一次。優化
這個專案是我花了清明兩天在原來的行為樹編輯器的基礎上做出來的,還殘缺了很多實用的功能:
1. 編輯器通過手動輸入公式來自動新增子節點
2. 編輯器儲存時檢查是否有環 並警告
3. 執行時可以將邏輯和資料分離,每個角色只負責儲存資料,整個公式結構由單例來管理,這樣分配記憶體更簡單。更新時只需要將資料的引用傳遞到公式即可
C 字段,屬性和屬性封裝字段
c 類成員包括 字段,屬性和方法。對於日常對於乙個類我們新增成員時會有幾種方法。如對於乙個person 類,我們對於其中乙個成員 性別 gender 進行定義,可以寫成 1,public string gender 2,public string gender 3,private string ge...
c 欄位和屬性
一 字段 field 可以在宣告欄位的同時賦給它乙個初始值。二 屬性 property 1 引入 假如將字段宣告為public,則在類的外部都可以對該字段進行訪問和更改,違背了類的封裝特性。如果宣告為private,則這種形式的封裝通常又過於徹底。例如,你可能希望欄位從外部唯讀,但從內部可以更改,但...
欄位和屬性(C )
類成員預設為私有成員。如果不為類成員新增訪問修飾符,那麼預設的是private。公共成員需要顯式指定。欄位和屬性是不同的兩個概念 欄位是類中實打實的一種變數。用來儲存與物件相關聯的資料。所以當欄位規定為public時,在當前類之外其他類中 隨 時隨地訪問和修改字段值 只要定義好當前類的例項即可,除s...