Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ゲームの抽選ロジックにGenericsを使ってみたら開発が楽になった話

 ゲームの抽選ロジックにGenericsを使ってみたら開発が楽になった話

ゲーム開発ではランダムドロップやガチャといった抽選を必要とする機能が多く存在するため、Interfaceを利用し各抽選ロジックの共通化を行ったのでその設計について紹介します。

また、Go1.18でGenericsが導入された後は、上記ロジックにGenericsを適用することで開発効率が向上したため、Generics導入前後の事例についても共有します。

QualiArts

June 02, 2023
Tweet

More Decks by QualiArts

Other Decks in Programming

Transcript

  1. type Reward struct { CardID string Ratio int } //

    1.各アイテムの提供割合データを用意する var Rewards = []*Reward{ {CardID: "Sレアカード", Ratio: 1}, {CardID: "レアカード", Ratio: 3}, {CardID: "ノーマルカード", Ratio: 6}, } type Drawable interface { GetRatio() int } func (e *Reward) GetRatio() int { return e.Ratio } 提供割合データRewardsを用意
  2. type Reward struct { CardID string Ratio int } //

    1.各アイテムの提供割合データを用意する var Rewards = []*Reward{ {CardID: "Sレアカード", Ratio: 1}, {CardID: "レアカード", Ratio: 3}, {CardID: "ノーマルカード", Ratio: 6}, } type Drawable interface { GetRatio() int } func (e *Reward) GetRatio() int { return e.Ratio } Drawableインターフェースを定義
  3. type Reward struct { CardID string Ratio int } //

    1.各アイテムの提供割合データを用意する var Rewards = []*Reward{ {CardID: "Sレアカード", Ratio: 1}, {CardID: "レアカード", Ratio: 3}, {CardID: "ノーマルカード", Ratio: 6}, } type Drawable interface { GetRatio() int } func (e *Reward) GetRatio() int { return e.Ratio } RewardはDrawableを実装
  4. func draw(drawables []Drawable) Drawable { // 2.提供割合の合計値を計算する var total int

    for _, d := range drawables { total += d.GetRatio() } // 3.合計値の範囲で乱数を生成する random := rand.Intn(total) // 4.乱数をもとに抽選結果を決定する var temp int for _, d := range drawables { temp += d.GetRatio() if temp > random { return d } } return nil } draw関数に抽選ロジック2〜4実装 2.提供割合の合計値を計算する 3.合計値の範囲で乱数を生成する 4.乱数をもとに抽選結果を決定する
  5. func draw(drawables []Drawable) Drawable { // 2.提供割合の合計値を計算する var total int

    for _, d := range drawables { total += d.GetRatio() } // 3.合計値の範囲で乱数を生成する random := rand.Intn(total) // 4.乱数をもとに抽選結果を決定する var temp int for _, d := range drawables { temp += d.GetRatio() if temp > random { return d } } return nil } []Drawableを受け取る
  6. func draw(drawables []Drawable) Drawable { // 2.提供割合の合計値を計算する var total int

    for _, d := range drawables { total += d.GetRatio() } // 3.合計値の範囲で乱数を生成する random := rand.Intn(total) // 4.乱数をもとに抽選結果を決定する var temp int for _, d := range drawables { temp += d.GetRatio() if temp > random { return d } } return nil } 提供割合を取得し抽選
  7. func draw(drawables []Drawable) Drawable { // 2.提供割合の合計値を計算する var total int

    for _, d := range drawables { total += d.GetRatio() } // 3.合計値の範囲で乱数を生成する random := rand.Intn(total) // 4.乱数をもとに抽選結果を決定する var temp int for _, d := range drawables { temp += d.GetRatio() if temp > random { return d } } return nil } 抽選結果をDrawableで返却
  8. func main() { drawables := make([]Drawable, 0, len(Rewards)) for _,

    e := range Rewards { drawables = append(drawables, e) } fmt.Printf("抽選されたカード: %s", (draw(drawables).(*Reward)).CardID) } Rewardsを[]Drawableに詰め替えて draw関数を実行
  9. func main() { drawables := make([]Drawable, 0, len(Rewards)) for _,

    e := range Rewards { drawables = append(drawables, e) } fmt.Printf("抽選されたカード: %s", (draw(drawables).(*Reward)).CardID) } draw関数の戻り値をキャストして値を取得
  10. func main() { drawables := make([]Drawable, 0, len(Rewards)) for _,

    e := range Rewards { drawables = append(drawables, e) } fmt.Printf("抽選されたカード: %s", (draw(drawables).(*Reward)).CardID) } • 抽選ロジックを共通化できた。 • しかし、呼び出し元の処理が煩雑になった。
  11. type Reward struct { CardID string Ratio int } //

    1.各アイテムの提供割合データを用意する var Rewards = []*Reward{ {CardID: "Sレアカード", Ratio: 1}, {CardID: "レアカード", Ratio: 3}, {CardID: "ノーマルカード", Ratio: 6}, } type Drawable interface { GetRatio() int } func (e *Reward) GetRatio() int { return e.Ratio } Interface版から変更なし 引き続き、Drawableインターフェース使う
  12. func draw[T Drawable](drawables []T) T { // 2.提供割合の合計値を計算する var total

    int for _, d := range drawables { total += d.GetRatio() } // 3.合計値の範囲で乱数を生成する random := rand.Intn(total) // 4.乱数をもとに抽選結果を決定する var temp int for _, d := range drawables { temp += d.GetRatio() if temp > random { return d } } // return nilだとコンパイルエラーになる var ret T return ret } Drawable型の型パラメータTを定義 引数、戻り値もTに変更
  13. // Interfaceを利用した場合 func main() { drawables := make([]Drawable, 0, len(Rewards))

    for _, e := range Rewards { drawables = append(drawables, e) } fmt.Printf("抽選されたカード: %s", (draw(drawables).(*Reward)).CardID) } // Genericsを利用した場合 func main() { fmt.Printf("抽選されたカード: %s", draw(Rewards).CardID) } Rewardsをそのまま引数に指 定し、draw関数を実行
  14. // Interfaceを利用した場合 func main() { drawables := make([]Drawable, 0, len(Rewards))

    for _, e := range Rewards { drawables = append(drawables, e) } fmt.Printf("抽選されたカード: %s", (draw(drawables).(*Reward)).CardID) } // Genericsを利用した場合 func main() { fmt.Printf("抽選されたカード: %s", draw(Rewards).CardID) } draw関数の戻り値からそ のまま値を取得
  15. // Interfaceを利用した場合 func main() { drawables := make([]Drawable, 0, len(Rewards))

    for _, e := range Rewards { drawables = append(drawables, e) } fmt.Printf("抽選されたカード: %s", (draw(drawables).(*Reward)).CardID) } // Genericsを利用した場合 func main() { fmt.Printf("抽選されたカード: %s", draw(Rewards).CardID) } • 抽選ロジックの呼び出し元がシンプルになった。