$30 off During Our Annual Pro Sale. View Details »

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

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

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

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

QualiArts

April 23, 2022
Tweet

More Decks by QualiArts

Other Decks in Programming

Transcript

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

    View Slide

  2. 自己紹介
    島田 裕司 / Shimada Yuji
    株式会社CyberAgent -> QualiArts
    スマートフォン向けゲームの開発を担当
    Goは今のプロジェクトから使い始めました
    (2年程度)

    View Slide

  3. ゲーム開発におけるデバッグツール
    ● テストプレイやデバッグのため、特定の状態を作りたいケースが多い
    ○ ユーザーのレベルを50に変更
    ○ 回復アイテムを100個付与
    ○ クエストをステージ10までクリア
    ● 状態操作を簡単に行えるツールが必要
    ⇒ 低コストで追加開発できるデバッグツール基盤の事例を紹介

    View Slide

  4. 用語説明
    ● 弊社ではデバッグツールのことをデバッグコマンド(デバコマ)と呼称
    ● ソースコード中での表現
    ○ Command
    ■ 「ゲームユーザーのレベルを操作する」といった単一の操作、およびそれに関する情報
    ■ ツール画面上のメニューにあたる
    ○ CommandGroup, Group
    ■ 「カード」や「クエスト」等の区分で、複数のCommandをまとめた情報
    ■ ツール画面上のカテゴリにあたる

    View Slide

  5. ツールイメージ
    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{},
    },
    },
    }
    }

    View Slide

  6. 3. フォーム一覧をリクエスト
    4. フォーム一覧情報を返却
    5. デバコマ実行をリクエスト
    6. 実行結果を返却
    処理の流れ
    2. 起動時にフォーム情報を生成
    1. フォーム定義を記述

    View Slide

  7. 3. フォーム一覧をリクエスト
    4. フォーム一覧情報を返却
    5. デバコマ実行をリクエスト
    6. 実行結果を返却
    処理の流れ
    2. 起動時にフォーム情報を生成
    1. フォーム定義を記述

    View Slide

  8. ポイント
    1. 共通インターフェースとStructタグでフォーム定義を記述
    2. リフレクションを用いてStructタグからフォーム情報を生成
    3. フォームの一覧情報を返却するAPIを定義

    View Slide

  9. ポイント
    1. 共通インターフェースとStructタグでフォーム定義を記述
    2. リフレクションを用いてStructタグからフォーム情報を生成
    3. フォームの一覧情報を返却するAPIを定義

    View Slide

  10. 共通インターフェースの実装(1)
    type CardHandler interface {
    BaseHandler
    UpdateRarity(c echo.Context) error
    }
    type handlerImpl struct{}
    type BaseHandler interface {
    GetBaseCommandGroup() *Group
    }
    ● ハンドラのインターフェースに、
    ベースインターフェースを埋め込む
    ● ハンドラは、ベースインターフェー
    スのメソッドを実装
    ● ハンドラ = APIの受け口となるメ
    ソッド

    View Slide

  11. 共通インターフェースの実装(2)
    func (h *handlerImpl) GetBaseCommandGroup()
    *CommandGroup {
    return &CommandGroup{
    Name: "カード",
    Commands: []*Command{
    {
    HandlerFunc: h.UpdateRarity,
    URL: "/updateCardRarity",
    Name: "レアリティ操作 ",
    Model: UpdateRarityRequest{},
    },
    },
    }
    }
    ● CommandGroupを定義 (≒ カテゴリ)
    ○ カテゴリ名
    ○ Commandの一覧
    ● Commandを定義 (≒ 各メニュー)
    ○ ハンドラメソッド
    ○ URL
    ○ デバコマ機能名
    ○ フォームの基になる構造体

    View Slide

  12. 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タグが身近

    View Slide

  13. ポイント
    1. 共通インターフェースとStructタグでフォーム定義を記述
    2. リフレクションを用いてStructタグからフォーム情報を生成
    3. フォームの一覧情報を返却するAPIを定義

    View Slide

  14. リフレクションで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

    View Slide

  15. セレクトボックスの選択肢を作成
    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タグ
    ○ 指定マスタデータからリストを作成
    ○ マスタデータ = レベルやクエストと
    いったゲームパラメータ
    ○ (例)クエスト一覧

    View Slide

  16. ポイント
    1. 共通インターフェースとStructタグでフォーム定義を記述
    2. リフレクションを用いてStructタグからフォーム情報を生成
    3. フォームの一覧情報を返却するAPIを定義

    View Slide

  17. 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を登録

    View Slide

  18. まとめ
    ● StructタグやInterfaceを活用し、追加開発しやすい基盤を実装
    ○ 共通インターフェースとStructタグでフォーム定義を記述
    ○ リフレクションを用いてStructタグからフォーム情報を生成
    ○ フォームの一覧情報を返却するAPIを定義
    ● 補完が効かない・記述ミスに気づきにくいデメリット
    ● 開発するシステムの要件に合わせて実装方法を検討

    View Slide

  19. ご静聴ありがとうございました!

    View Slide