跳轉到

遊戲後端限時道具系統設計:ExemptResourceType 架構決策

文檔資訊

  • 分類: architecture
  • 難度: intermediate
  • 預估閱讀時間: 5 分鐘
  • 標籤: game-backend, item-system, time-limited-items, stamina-pass, luban-config, architecture-pattern

摘要

討論遊戲後端限時道具(通行證)的判斷邏輯與架構設計。核心結論:道具是否可無限使用由「次要類型枚舉 + 限時時間欄位」共同判斷;同類型但不同時間戳的道具為獨立實體不可疊加;選擇方案 A 在 ItemConfig 加入 ExemptResourceType 欄位並於 PlayerService 啟動時建立 mapping,達到 O(1) 查找效率。

關鍵學習

  • 限時道具的「可無限使用」判斷由次要類型枚舉(SecondaryType)+ 時間欄位共同決定,非單純枚舉

  • 相同類型但不同時間戳的道具在後端是獨立實體,無法自動疊加;前端可合併顯示剩餘時間

  • 多個服務入口都需要判斷道具豁免資源類型時,應集中管理避免 bug,不要分散在各個 API 呼叫點

  • 方案 A(ItemConfig 加 ExemptResourceType 欄位)優於分散式設計,因為配置層集中、查找 O(1)

  • 未來擴充(3分鐘無限體力、不消耗金幣等)只需在 ItemConfig 配置新的 ExemptResourceType,不改程式碼

技術細節

道具判斷邏輯

限時道具的「是否可無限使用」判斷流程:

  1. 根據 DungeonMainConfig 中配置的 ID 掃描玩家背包
  2. 查找哪些次要類型(SecondaryType)屬於限時道具
  3. 確認玩家身上是否持有這些限時道具
  4. 同時滿足「次要類型枚舉匹配」和「時間欄位有效」才判定可無限使用

道具疊加行為

相同類型道具(例如兩個「飛機杯包5分鐘」)若在不同時間獲得,後端會視為兩個獨立實體(不同時間戳),不會自動疊加時間。前端若需顯示合計剩餘時間,需自行合併計算。

方案 A:ExemptResourceType 架構

在 Luban ItemConfig 新增欄位:

// Luban ItemConfig 新增欄位
ExemptResourceType: int32   // 0=無豁免, 1=stamina, 2=gold, 3=前置道具...

PlayerService 啟動時建立一次性 mapping:

// resourceType → []secondaryType
var exemptMapping map[int32][]int32

func (s *PlayerService) buildExemptMapping(configs []*ItemConfig) {
    for _, cfg := range configs {
        if cfg.ExemptResourceType != 0 {
            exemptMapping[cfg.ExemptResourceType] = append(
                exemptMapping[cfg.ExemptResourceType],
                cfg.SecondaryType,
            )
        }
    }
}

查詢時 O(1) lookup,不需每次掃全表。啟動成本低(僅遍歷一次 ItemConfig),實際效能影響可忽略。

What Changed

架構決策

選擇方案 A:在 ItemConfig(Luban 配置)中新增 ExemptResourceType 欄位,集中管理「哪種道具豁免哪種資源消耗」的對應關係。

解決的問題

原本的設計讓多個 API 入口點各自判斷道具豁免邏輯,容易因為遺漏導致 bug。方案 A 將邏輯集中到配置層,PlayerService 啟動時建立 resourceType → []secondaryType 的 mapping,統一查找入口。

未來擴充規劃

3 分鐘無限體力(企劃尚未配置)、不消耗金幣等需求,均可沿用此架構,只需在 ItemConfig 新增對應的 ExemptResourceType 配置即可,不需修改程式碼。

So What

為何值得記錄

遊戲道具系統的「限時豁免」邏輯是常見但容易設計錯誤的模式。這個討論明確了:

  • 不要把豁免邏輯散落在各個業務 API 中,應集中管理
  • 配置驅動(Config-Driven)的設計讓擴充成本接近零
  • 後端道具實體模型(不同時間戳 = 不同實體)是易踩的坑,前後端需對齊理解

這個模式可複用於任何「持有特定道具期間豁免某資源消耗」的遊戲系統設計。

Trade-offs

  • 方案 A 優點:配置集中、O(1) 查找、擴充只需改 Config 不改程式碼
  • 方案 A 缺點:ItemConfig 欄位增加,Luban 需重新生成;豁免邏輯與道具定義耦合
  • 不拆獨立模組的風險:若未來通行證邏輯複雜化(跨服務、多條件),可能需要重構
  • 道具不疊加的設計:對玩家體驗較不友好,但後端實作簡單;前端合併顯示是折衷方案

Try It Fast

// PlayerService 啟動時建立 exemptMapping
func (s *PlayerService) InitExemptMapping() {
    s.exemptMapping = make(map[int32][]SecondaryType)
    for _, cfg := range s.itemConfigs {
        if cfg.ExemptResourceType != 0 {
            s.exemptMapping[cfg.ExemptResourceType] = append(
                s.exemptMapping[cfg.ExemptResourceType],
                cfg.SecondaryType,
            )
        }
    }
}

// 查詢玩家是否持有豁免 stamina 的通行證
func (s *PlayerService) HasStaminaPass(player *Player) bool {
    exemptTypes := s.exemptMapping[ResourceType_Stamina] // O(1)
    for _, secType := range exemptTypes {
        if player.HasActiveItem(secType) {
            return true
        }
    }
    return false
}

Recommendation

  1. 確認 Luban ItemConfigExemptResourceType 欄位已加入並重新生成配置代碼
  2. PlayerService 啟動初始化中呼叫 InitExemptMapping(),確保 mapping 在處理任何請求前就已就緒
  3. 所有需要判斷「豁免資源消耗」的 API,統一透過 HasXxxPass() 方法查詢,禁止各自實作掃描邏輯
  4. 前端需自行計算「同類型通行證」的合計剩餘時間,後端不提供自動疊加
  5. 當「3分鐘無限體力」企劃配置完成時,只需在 ItemConfig 設定 ExemptResourceType = stamina,不需修改程式碼

本文檔由 Semi-Brain 自動生成

Session ID: fb39b34e-2ba6-4c2c-9f93-26e1e73d0371

分析信心度: 65%