
複雑な演出フローを分かりやすく制御する
-
2021年10月6日
こんにちは!cocone tech blog編集長のYです!
今回は今年10周年を迎えた『ポケコロ』の開発に携わっているYさんに演出フローを制御するための方法についてご紹介いただきました!
目次
経緯
だいぶ昔。まだ世の中にスマートフォンも無く、私がFlash開発者だった頃のお話です。
当時開発していたカードバトルゲームのバトル開始時の演出や、戦闘演出フローを汎用的に分かりやすく記述するための社内製のクラスChainMethodというものがありました。
ChainMethodクラスは実行する演出を制御する関数をあらかじめ登録しておき、後からまとめて実行するような作りになっていました。
このクラスを利用することで実行の順番制御や差し替えも容易に行えるため、演出順の変更や差し込みがあっても制御のためにスパゲッティとなったコードと格闘することなくスマートに対応することができました。
なかなか複雑になっているポケコロの演出フローの制御のためにこのクラスを利用しようと考えたのですが、元のコードがActionScriptで記述されているのでそのままでは利用できずC++に移植してみました。
作ったもの
クラス名はChainStackとしました。
コアとなるメソッドは以下のようなものです。
- class ChainStack
- // 制御リストの追加
- void addFunc(std::function<void(ChainStack* c)> func);
- // 制御リストの実行
- void start(std::function<void()> complete, std::function<void()> canceled);
- void doNext();
- // 実行のキャンセル
- void cancel();
※実際にポケコロで利用されているクラスNvChainStackはもう少し複雑で、固有のタイマーによるウエイト関数や、途中分岐できるアンカーポイントの設定などもできます。
使い方
ChainStackの使い方は以下の通り、
(1)ChainStackの実体を生成し、そこに必要な数だけaddFunc()で制御処理を追加
(2)そして、最後にstart()で実行
です。
では、続いて本来の実装に近いコードで使い方を確認してみましょう。
【例1】抽選演出
要件
⑴初めに共通の演出を再生
⑵レア、スーパーレアの場合は確変演出を再生
⑶レアリティごとの排出演出を再生
⑷共通の結果画面表示
実装コード
- enum {
- kResult_NORMAL,
- kResult_RARE,
- kResult_SRARE,
- } GACHA_RESULT;
- void Gacha::playResult(GACHA_RESULT result) {
- auto chain = new ChainStack();
- // (1)初めに共通の演出を再生
- chain->addFunc([this](ChainStack *c) {
- playCommonEffect([c]() {
- c->doNext();
- });
- });
- // (2)レア、スーパーレアの場合は確変演出を再生
- if (result == kResult_RARE || result == kResult_SRARE) {
- chain->addFunc([this](ChainStack *c) {
- playKakuhenEffect([c]() {
- c->doNext();
- });
- });
- });
- // (3)レアリティごとの演出を再生
- chain->addFunc([this,result](ChainStack *c) {
- playResultEffect(result, [c]() {
- c->doNext();
- });
- });
- // (4)共通の結果画面表示
- chain->addFunc([this](ChainStack *c) {
- showCommonResult([c]() {
- c->doNext();
- });
- });
- chain->start([chain]() {
- // 全ての演出終了
- delete chain;
- });
- }
【例2】ゲームのステージ開始演出
要件
⑴ステージタイトル表示
⑵イベント期間中なら1回目だけ割り込み演出
⑶ボーナスアイテム使用時はその演出
⑷画面がタップされたら上記の演出は全てスキップ
実装コード
- GameStage::ChainStack *g_chainStack = NULL;
- void GameStage::playStartEffect(int eventID, std::vector<int> bonusItems)
- {
- if (g_chainStack) return;
- g_chainStack = new ChainStack();
- // (1)ステージタイトル表示
- g_chainStack->addFunc([this](ChainStack *c) {
- playStageTitleEffect([c]() {
- c->doNext();
- });
- });
- // (2)イベント期間中なら1回目だけ割り込み演出
- bool isEvent = (eventID != 0);
- bool isFirst = checkFirstEventTitle( eventID );
- if (isEvent && isFirst) {
- g_chainStack->addFunc([this](ChainStack *c) {
- playEventTitleEffect(eventID, [c]() {
- c->doNext();
- });
- });
- }
- // (3)ボーナスアイテム使用時はその演出
- if (bonusItems.size() > 0) {
- g_chainStack->addFunc([this,bonusItems](ChainStack *c) {
- playBonusItemEffect(bonusItems, [c]() {
- c->doNext();
- });
- });
- }
- g_chainStack->start([this]() {
- // 全ての演出終了
- delete g_chainStack;
- g_chainStack = NULL;
- },
- [this]() {
- // 途中キャンセル
- removeAllEffect();
- delete g_chainStack;
- g_chainStack = NULL;
- });
- }
- // (4)画面がタップされたら上記の演出は全てスキップ
- void GameStage::onTouchBegin()
- {
- if (g_chainStack) {
- g_chainStack->cancel();
- }
- }
まとめ
使い方のサンプルコードから、仕様変更の際にも修正すべき内容を容易に想像できたと思います。
また、OneCompilerで実際に動作するものを準備しました。
C++以外にもUnityなどでも利用できるようC#の実装もしてみたので確認してみてください。
C++による実装。
https://onecompiler.com/cpp/3xctaektb
C#による実装。
https://onecompiler.com/csharp/3xcvkjbjd
さあ!柔軟性のあるコードで、今日も笑顔でコーディングをしましょう。
Happy Coding!;)
また、ココネでは一緒に働く仲間を募集中です。
ご興味のある方は、以下のリンクからぜひご応募ください。