Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Goの標準機能で簡易システムを低コストで作成するテクニック

QualiArts
April 23, 2022

 Goの標準機能で簡易システムを低コストで作成するテクニック

Goによるゲーム開発のプロジェクトにて、Go標準の機能を複数活用し、実装コストの低いデバッグツール開発基盤を作成しました。 本資料のデバッグツールとは、動作確認やデバッグのため、ユーザーのレベル操作や所持アイテムの増減等、データの状態を操作・参照するAPI群、及びwebツールを指します。

デバッグツールの拡充は、ゲームに限らず様々な分野で成果物の品質に直結しますが、ドメイン固有のロジック実装に集中するためにはより低コストで開発できることが望ましいです。 本資料では、低コストで開発を行えるデバッグツール基盤をどのように実現したのかを紹介します。

QualiArts

April 23, 2022
Tweet

More Decks by QualiArts

Other Decks in Programming

Transcript

  1. 用語説明 • 弊社ではデバッグツールのことをデバッグコマンド(デバコマ)と呼称 • ソースコード中での表現 ◦ Command ▪ 「ゲームユーザーのレベルを操作する」といった単一の操作、およびそれに関する情報 ▪

    ツール画面上のメニューにあたる ◦ CommandGroup, Group ▪ 「カード」や「クエスト」等の区分で、複数のCommandをまとめた情報 ▪ ツール画面上のカテゴリにあたる
  2. ツールイメージ type UpdateRarityRequest struct { UserID string `label:"ユーザーID"` CardID string

    `master:"card" type:"select"` Rarity int32 `enum:"rarity" type:"radios"` } func (h *handlerImpl) GetBaseCommandGroup() *Group { return &Group{ Name: "カード", Commands: command.Commands{ { HandlerFunc: h.UpdateRarity, URL: "/updateCardRarity", Name: "レアリティ操作", Model: UpdateRarityRequest{}, }, }, } }
  3. 共通インターフェースの実装(1) type CardHandler interface { BaseHandler UpdateRarity(c echo.Context) error }

    type handlerImpl struct{} type BaseHandler interface { GetBaseCommandGroup() *Group } • ハンドラのインターフェースに、 ベースインターフェースを埋め込む • ハンドラは、ベースインターフェー スのメソッドを実装 • ハンドラ = APIの受け口となるメ ソッド
  4. 共通インターフェースの実装(2) func (h *handlerImpl) GetBaseCommandGroup() *CommandGroup { return &CommandGroup{ Name:

    "カード", Commands: []*Command{ { HandlerFunc: h.UpdateRarity, URL: "/updateCardRarity", Name: "レアリティ操作 ", Model: UpdateRarityRequest{}, }, }, } } • CommandGroupを定義 (≒ カテゴリ) ◦ カテゴリ名 ◦ Commandの一覧 • Commandを定義 (≒ 各メニュー) ◦ ハンドラメソッド ◦ URL ◦ デバコマ機能名 ◦ フォームの基になる構造体
  5. Structタグによるフォームの設定 type UpdateRarityRequest struct { UserID string `label:"ユーザーID"` CardID string

    `master:"card" type:"select"` Rarity int32 `enum:"rarity" type:"radios"` } func (h *handlerImpl) UpdateRarity(c echo.Context) error { var r UpdateRarityRequest err := c.Bind(&r) // 実際の処理… } • リクエストパラメータをバインドす る構造体にタグを設定 • Structのフィールドに対してメタ情 報を付加できる ◦ jsonタグが身近
  6. リフレクションでStructタグを取得 func CreateSchema(c *Command) *Schema { // 構造体の型情報を取得 rt :=

    reflect.TypeOf(c.Model) var schema Schema for i := 0; i < rt.NumField(); i++ { // フィールド毎にタグ情報を取得 f := rt.Field(i) schema.Label = f.Tag.Get("label") // 各タグを処理… } return &schema } • リフレクションによりタグ情報を取 得しフォーム情報を生成 ◦ type:テキスト、セレクトボック ス、ラジオボタン... ◦ label:フォーム名 ◦ required:必須チェック ◦ enum, master:次スライドで説明 • 型ごとにデフォルト値も設定してあ るのでタグは指定しなくてもOK
  7. セレクトボックスの選択肢を作成 func CreateValues(f *reflect.StructField) Values { // Enum指定 if e

    := f.Tag.Get("enum"); e != "" { return CreateEnumValues(e) } // マスタデータの指定 if m := f.Tag.Get("master"); m != "" { return CreateMasterValues(m) } return Values{} } • セレクトボックスやラジオボタンの 選択肢もタグを基に生成 • enumタグ ◦ 指定Enumからリストを作成 ◦ (例)アイテムタイプ定義のEnum • masterタグ ◦ 指定マスタデータからリストを作成 ◦ マスタデータ = レベルやクエストと いったゲームパラメータ ◦ (例)クエスト一覧
  8. Commandの初期化とルーティング handlers := []BaseHandler{card.New(), …} groups := make(CommandGroups, 0, len(handlers))

    for _, h := range handlers { group := h.GetBaseCommandGroup() // フォーム情報の生成とルーティング InitCommand(group) for _, c := range group.Commands { e.POST(command.URL, c.HandlerFunc) } groups = append(groups, group) } // フォーム情報の一覧を返す API e.GET("/api/list", func(c echo.Context) error { return c.JSON(http.StatusOK, groups) }) • サーバー起動時に各Commandの初期 化を実行 ◦ フォーム情報の生成 • 各デバコマをルーティング • フォームの一覧を返すAPIを登録