アピリッツ コンテンツデザイン部の金井と申します。前回に引き続き、ソーシャルゲームにおけるミッション機能に対する考察をしていきます。
関連:ソーシャルゲームにおけるミッション機能のサーバーサイド実装の考察:問題編
前回のあらすじ
ミッション機能というものは構造的に、
- 全てのAPIに影響が及ぶが故に改修へのQAコストが非常に高く
- 処理に時間が掛かりやすく
- ユーザーデータが膨大になるが故にそれらの問題が更に面倒な事になりやすい
ものである。
また、実際に実装/改修するとしても、仕様面の都合で
- ミッション受注処理
- ミッション進捗処理
- ミッション報酬受け取り処理
の3つが複雑に絡み合う事が多く、所謂スパゲッティコードというものに、しかも見るからに不味いそれになりやすい。
どのように実装/改修すべきか
注:
ここから記述する内容は、”これから実装する“もしくは”既存のソースを流用して新しいゲームを開発する“と言った、運用中ではない事を前提としたものとなります。
既存のミッション機能が如何にクソコード汚いものでも改修する事によって得られるメリットは、精々、
- レスポンスが早くなる
- コードが見やすくなる
- 改修等に伴うバグの発生率を減らす事が出来る
等と言ったユーザーからは見えないものが多く、更に
- 影響が膨大な以上、QAを入念に介してもバグを除き切れずに本番障害を引き起こす可能性が高い
- バグを引き起こした場合の長時間メンテ、修正、補填などで大きな損害も被る
- マスタの改修なども必要となった場合、マスタデータを作るExcel…これまで使用していたマクロなども改修しなければいけない事となり、プランナー側、そしてクライアント側にも大きな負担が掛かる
と言ったそれ以上のデメリットが膨大です。
如何にエンジンが錆び付いてガションガションうるさい音を立てていようとも、熱効率が悪かろうとも、動いている状態のまま幾つかの部品を取り替える事なんて早々出来ないですからね。
それでもやらなければいけないとなる場合は、レスポンスの重さでユーザー離脱が激しいとか、原因不明のバグが頻発して、更に手をつけようにも秘伝のソース過ぎて壊さないとどうしようも出来ないとかそんな、それを使い続ける事で極度の不利益が出る場合に限るでしょう。
そうでない場合は、その汚いコードと付き合っていくのがベストです。
ベストです。
ベストなんですよ。悲しい事に。とても、とても悲しい事に。
普遍的に良く見られるであろう悪くなっていくコード
まず前回でも書いたような、カードの強化で例えてみましょう。
カードを強化させた回数のミッションと指定アイテムを手に入れた回数のミッションが存在するとします。
そして、カードを最大レベルまで強化するとカードマスタに指定されたアイテムが貰えるとします。
すると、さっくり疑似コードとして書いてみると以下のようになるでしょう。
class User < ActiveRecord::Base
def カード強化(カードID, 使用素材群, 時刻)
カードが存在するか確認
カードが既に最大レベルでないか確認
使用素材群それぞれに対し do
消費数分持っているか、またそれらがレベルアップ用アイテムか確認
消費数分の減算、獲得経験値を計算
end
カードをレベルアップ(獲得経験値)
カードを強化したミッションを進捗させる
if カードが最大レベルならば
アイテム付与(カードマスタに定義されている報酬アイテムマスタコード, カードマスタに定義されている報酬アイテム量, 時刻)
end
end
def アイテム付与(アイテムマスタコード, 付与量, 時刻)
指定されたアイテムマスタが存在するか、また受け取れる時間であるか確認
ユーザーにアイテムを付与
アイテムを獲得したミッションを進捗させる
end
end
こんな感じですね。
確かにミッションがシンプルな仕様ならば、そんなに時間も掛からないでしょう。
しかし例えばカードの強化を促進させる施策の一環で、カードを指定レベルまで成長させた、カードのレベルを最大まで上げたというミッションが追加されたとすると、最大、合計で4つのミッションの進捗確認を1個1個する事になります。
段々雲行きが怪しくなってきましたね!
そして今度はミッションを進捗させる方を詳しく見ていきましょう。分かりやすさの為に、ミッション進捗は別のクラスに纏めましょうか。
class ミッション進捗
def 進捗させる(ユーザー, 進捗させるミッション種, 進捗させるミッションカウント, ...)
進捗させるミッション種に属する、現在有効なミッション群を確認する
そのミッション群に対して do
ユーザーの進捗を加算する
end
end
end
簡単に書くとこうなりますね。
さて。ここで例えば前回でも書いたような、ミッションの仕様に良くある、ミッションをクリアしたら次のミッションが解放されるという仕様が入ってくるとこうなりますね。
class ミッション進捗
class << self
def 進捗させる(ユーザー, 進捗させるミッション種, 進捗させるミッションカウント, ...)
進捗させるミッション種に属する、現在有効なミッション群を確認する
そのミッション群に対して do
ユーザーの進捗を加算する
if クリアしたならば
それのクリアを前提条件とするミッション群を解放させる
end
end
end
end
end
シンプルにこのような感じになるでしょうね。
さて。ミッション進捗そのものも重くなりましたね。それがカードを強化するだけで最大4回走りますね。
雨が降ってきたようです。
そしてトドメに、ある特定のミッション種別だったらクリアまでではなく、報酬付与までするとの仕様が入ってきたとしましょうか。
class ミッション進捗
class << self
def 進捗させる(ユーザー, 進捗させるミッション種, 進捗させるミッションカウント, ...)
進捗させるミッション種に属する、現在有効なミッション群を確認する
そのミッション群に対して do
ユーザーの進捗を加算する
if クリアしたならば
それのクリアを前提条件とするミッション群を解放させる
そのミッションが特定の種別だった場合、報酬付与まで行う
end
end
end
end
end
はい。報酬付与の中ではこのミッション進捗が呼ばれていますね。雷まで落ちてきたようです。
こうならない為にはどうするべきか
自分なりの考えとなりますが、結論としては単純です。
同じようなコードを何度も通さない仕組みにすれば良い。
ただ、それだけです。
上のコードを書き換えるとこんな感じでしょうか。
class User < ActiveRecord::Base
def カード強化(カードID, 使用素材群, 時刻)
カードが存在するか確認
カードが既に最大レベルでないか確認
使用素材群それぞれに対し do
消費数分持っているか、またそれらがレベルアップ用アイテムか確認
消費数分の減算、獲得経験値を計算
end
カードをレベルアップ(獲得経験値)
ミッション用にカードを強化したというデータを保持
if カードが最大レベルならば
アイテム付与(カードマスタに定義されている報酬アイテムマスタコード, カードマスタに定義されている報酬アイテム量, 時刻)
end
end
def アイテム付与(アイテムマスタコード, 付与量, 時刻)
指定されたアイテムマスタが存在するか、また受け取れる時間であるか確認
ユーザーにアイテムを付与
ミッション用にアイテム付与したというデータを保持
end
end
そしてカード強化の後、他に何もやらなくなったタイミングで保持したデータに対して一括でミッションを進捗させる。
class ミッション進捗
class << self
# 進捗させるミッションデータ群 = {ミッション種別: 進捗カウント}
def 進捗させる(ユーザー, 進捗させるミッションデータ群, ...)
進捗させるミッション種別群に属する、現在有効なミッション群を確認する
そのミッション群に対して do
ユーザーの進捗を加算する
end
クリアしたミッション群それぞれに対して do
それのクリアを前提条件とするミッション群を解放させる
そのミッションが特定の種別だった場合、報酬付与まで行う
end
if 報酬付与まで行なった場合
ミッション進捗をまた呼ぶ
end
end
end
end
ざっくりこのような形ですね。
工夫が足りず、このミッション進捗はこれでも複数回呼ばれる可能性はありますが、少なくとも元々の形よりは呼ばれる頻度は落ちるでしょう。
カードを強化させた回数のミッション、指定アイテムを手に入れた回数のミッションに加えてカードを指定レベルまで成長させた、カードのレベルを最大まで上げたというミッションがあったとしても、このミッション進捗を通る回数は1回で変わらないのですから。
結論
自分ではそれしか思いつきませんでした。
ミッションという機能は性質上、1APIで様々なミッションの進捗が進むという事が絶対にあります。
幾らそのミッションの進捗1回分を軽量化しようとしても、無理な軽量化はどこかに歪みを生みます。コードがより複雑になったり、新たな仕様によって軽量化の前提が上等な料理にハチミツをぶちまけるが如くに崩れる事があったり。
1個1個に対して愚直に進捗を進ませるという事柄自体をやめなければ、ミッションという機能は健全に運営に組み込めないという結論です。
しかし、それはやはり運営フェーズに入ってからは絶対に出来ない事です。
全てのAPIに浸透し、隆盛しているようなそんな文化を一気にひっくり返すなど、濃口のラーメンが流行りの極みの時に薄口のラーメンのみを売り出して儲けを出そうとするようなものでしょう。
次回は、そんなコードを実際に改修した時に起きた事などを書こうと思います。