コンテンツへスキップ

QualiArtsengineer blog

大量のSD制作を支える「IDOLY PRIDE」におけるSpine活用事例③ 〜バリエーションの実現/データ分割編〜

大量のSD制作を支える「IDOLY PRIDE」におけるSpine活用事例③ 〜バリエーションの実現/データ分割編〜

14 min read

はじめに

株式会社QualiArtsでUnityエンジニアをしている吉成です。2021年6月リリースの「IDOLY PRIDE」(以降、アイプラ)の開発に携わり、主にUnityでのゲーム開発やSD周りの設計を担当しています。

本記事は、3回に渡ってアイプラにおけるSpineの活用事例を紹介する記事の第3回目の記事です。

過去の記事はこちらをご覧になってください。

最終回となる本記事では『バリエーションの実現/データ分割編』として、様々なバリエーションを実現するために行った工夫と、効率的な制作や運用を行うために行ったデータ分割の工夫の紹介を行います。

お仕事の実装

アイプラには大きくファンイベント、プロモーション、リフレッシュという3種類のお仕事のジャンルが存在しています。 また、それぞれのジャンルに対して様々な種類のエリアが存在しています。 本章では様々なお仕事の表現をどのように実現しているかを紹介します。

プロモーションとリフレッシュ

まずはプロモーションとリフレッシュを見ていきます。 次のようにプロモーションとリフレッシュには様々なエリアが存在しています。

この時、キャラクターなどの立ち位置を指定したり、移動処理を実装したり、タイミングに合わせたモーションを指定したりと、お仕事ごとに細かな実装を行うのは大変です。 また、キャラクターがアイテムを持ったり持たなかったり、背景が動いたり動かなかったり、NPCがいることもあったりと、表現パターンも様々です。

今回はお仕事用の固有モーションを用意し、細かい挙動は全てSpine側で吸収することにしました。

キャラクターなどのSpineは全て原点に配置し、立ち位置も移動も全てモーションに持たせることにしました。 背景のSpine(動く背景)も同様に原点に配置しています。

例えば、次の水着グラビア撮影のお仕事では、3人のキャラクターのSpineや、カメラマンや水面といった背景のSpineも全て原点に配置し、モーションで位置をずらして表示を行っています。

この時、全てのSpineを原点に置いているので、Spine同士の前後関係を取得して重なり順を制御する必要があります。 次の左の画像が重なり順を制御する前の状態で、右の画像が重なり順を制御した後の状態です。

今回は全てのSpineに基準点となるボーンを設定しています。 モーションによってキャラクターの立ち位置が指定されたり、移動によってキャラクターの立ち位置が変更された際に、基準点のボーンも一緒に移動するので、基準点のボーンを監視することでキャラクターなどの立ち位置を取得することができます。 そして、Y座標が大きいほど奥にあるものと捉えることができるので、基準点のボーンのY座標を監視することでSpineの重なり順を制御することができます。

また、キャラクターが移動をする場合には、Y座標の大小が入れ替わったら、そのタイミングで重なり順を変更する必要があります。 ちなみに、キャラクターがジャンプをする場合には、ジャンプをしてもキャラクターが奥に行くわけではないので、基準点は地面に置いたままにする必要があります。

そして運用で発生する様々なパターンはマスターデータで吸収をしています。

これで運用で実装を変更せずにお仕事を追加できるようになりました。 とはいえ例外もあります。

ファンイベント

ファンイベントはユーザーの操作が介入したり、Spineの動きにロジックが絡むため、プロモーションやリフレッシュのようにただモーションを流すだけというわけにはいきません。

そのため、ファンイベントではキャラクターやファンのSpineを原点には置かず、配置や移動はコード側で実装をしました。 ただ握手や撮影やサインを渡している瞬間などの位置関係がシビアになるタイミングでは、キャラクターとファンを同じ座標に配置してモーションで細かな位置調整を行っています。

お仕事の実装まとめ

様々な種類のお仕事を実現するための細かな挙動はSpineに逃しました。 それにより運用でエリアが増えるたびに実装を行う必要がなくなりました。その際、Spineの重なり順には注意が必要です。

また、運用で発生する様々なパターンはマスターデータで吸収しました。 それにより運用でエリアが増えるたびに実装を行う必要がなくなりました。 この実現のためにはやりたい表現を網羅できるマスターデータの設計を行う必要がありますが、やって良かったと思います。

ただ、ファンイベントのように自分で細かな実装をしてしまった方が良いものもあります。 その際も工夫できる部分に関しては工夫を行いました。 運用負荷とコードの複雑さの良い塩梅のところを取れると良いと思います。

モーションの出し分け

個性豊かなアイプラのキャラクターたちを魅力的に表現するには、モーションを様々な状況に合わせて出し分ける必要があります。

性格によるモーションの出し分け

例えば元気な子と落ち着きのある子では、リアクションが大きく異なるので、その子の性格に合わせて出し分けたいです。 アイプラではキャラクターたちを5つの性格タイプに分類しました。

5つの性格タイプはそれぞれ「元気(LIVELY)」「清楚(ELEGANT)」「純真(PURE)」「スタイリッシュ(STYLISH)」「気まま(CAREFREE)」となっており、それぞれの性格に対応した喜びのモーションは次のようになります。

利き手によるモーションの出し分け

また、キャラクターたちには利き手の設定があるので、サインを書く動きや料理で包丁を扱う動きなどでは、しっかり分けて表現をする必要があります。

利き手ごとのサインモーションは次のようになります。

キャラクターによるモーションの出し分け

同じ性格タイプでも、それぞれ個性的な特徴があるので、特定のキャラクターだけモーションを差し替えたいこともあります。

次の買い物モーションでは、優だけ紙袋が異なりサングラスを掛けているモーションが再生されていることが分かると思います。

シチュエーションによるモーションの出し分け

キャラクターたちの関係性も重要な要素なので、特定のキャラクターが同時に存在するときにも出し分けができると、世界観に深みが出ます。

寮で過ごすリフレッシュでは、沙季がいるときだけ、すずのモーションが勉強をするモーションに切り替わります。

ランダムな出し分け

アイテムをランダムに変えられると、モーション制作工数は抑えながらバリエーションを増やすこともできます。

次のラジオの手紙を読むモーションは読む手紙をランダムに出し分けています。

モーションの出し分け方法

モーションの出し分け方法は、モーション名の末尾にオプションを付け、そのモーション名が存在したら再生するという対応を取りました。 一般的にSpineはボーン及びメッシュの構成とモーションデータは一つのSkeletonDataとしてまとめられますが、アイプラではモーションデータを分けて後から注入できるようにし、そのモーションデータをモチーフごとに分け、複数のモーションをオプション付きで入れておき、対象キャラに合う最も優先度の高いオプションが付いたものを検索して再生するという方法を取りました。こちらの詳細は次の章で紹介します。

次の表はゲーム全体でのオプションの優先度設定です。

指定内容オプション
プロモーションとリフレッシュ限定の指定--<キャラID/性格タイプID>&<キャラID/性格タイプID>@POS<n>
対象キャラクターのキャラID--skr--ngs--ktn、…
対象キャラクターの性格タイプ--PURE--LIVELY、…
対象キャラクターの利き腕--LEFTY(左利き用のみ明示的に指定)
ランダム指定--RAND0--RAND1--RAND2、…

上に行くほど優先度が高くなり、下に行くほど優先度は低くなり、複数のパターンでモーション名がマッチした際には優先度が高いモーションが選択されます。

プロモーションとリフレッシュのみ、アクションを行う場所や、誰と一緒にいるか、という追加のオプション設定があります。

プロモーションとリフレッシュでは、キャラクターにはそれぞれ「POS1〜POS3」の位置が割り当てられています。 そして、それぞれの位置で「pos1〜pos3」のモーションを再生します。

--<キャラID/性格タイプID>&<キャラID/性格タイプID>@POS<n>はポジション<n>に<キャラID/性格タイプID>がいるときに有効なオプションです。 場所や&の前を省略するケースもあり、実際にこのようなモーションは用意してはいないのですが、具体例は次のようになります。

モーション名モーション内容
dormitory/pos1--mei&suz@POS2寮ですずがPOS2にいるときの芽衣のPOS1でのモーション
pool/pos1--LIVELY&LIVELY@POS2プールで元気っ子がPOS2にいるときの元気っ子のPOS1でのモーション

また、先ほど紹介したモーションの名前も見ていきます。

性格によるモーションの出し分けにおける命名

例えば喜びのモーションは、それぞれの性格を表すオプションを付けたモーションをまとめて入れておくことで、キャラクターに合わせて適切に再生されます。

cmn/glad--LIVELYcmn/glad--ELEGANTcmn/glad--PUREcmn/glad--STYLISHcmn/glad--CAREFREE

利き手によるモーションの出し分けにおける命名

サインを書く動きには利き手のオプションを付けたモーションを入れます。

sign/talk--LEFTYsign/talk

キャラクターによるモーションの出し分けにおける命名

特定のキャラクターだけ再生したいモーションは、オプションとしてキャラIDを付けています。

shopping/pos2shopping/pos2--yushopping/pos2

シチュエーションによるモーションの出し分けにおける命名

特定のキャラが同時にいるときのオプションは、できるだけ製作時に意味が汲み取りやすく、打ちやすい&マークを使う形で表現しています。

dormitory/pos1--suz&skidormitory/pos1

ランダムな出し分けにおける命名

ランダムもキャラIDやエリアIDと被らないよう、大文字の「RAND」で検索して、出し分けるようにしています。

radio-distribution/pos2--RAND0radio-distribution/pos2--RAND1

また、オプションは種類によっては組み合わせて指定することが可能です。

モーションの出し分けまとめ

リッチな表現を行うためにモーションの出し分けは行った方が良いです。 利き手違いはじっくり見れば誰でも違和感に気づいてしまうものなので、モーションの共通化をする予定がある場合は必須で対応するべきものです。

出し分け方法は、ルールを作ってモーションの命名に落とし込みました。 オプションは複数併用もできるようにもしていることで少し検索が複雑になっており、毎回検索を掛けるのは大変なので、最初に再生すべきモーション名を検索してキャッシュしておくと効率よく再生を行えます。

データの分割

最後にデータの分割について話します。 前章のモーションの出し分けで使用箇所や衣装やキャラクターなどに依存したモーションが存在することを説明しました。 これらのモーションを分けずに制作を行ってしまうと、100種類近いモーションが全てFIXするまで、衣装の量産を行えず、後からのモーションの追加も難しくなってしまいます。

なので本章では、使われどころや変更の発生タイミングなどに着目し、データを分割して制作を行う方法について考えます。

まず、モーションは大きく6パターンに分類されます。

① ゲーム全体で使う、どのキャラクターにも適用する共通モーション
② ゲーム全体で使う、キャラクターに依存したモーション
③ ゲーム全体で使う、衣装に依存した依存モーション
④ 特定のエリア(カフェなど)で使う、どのキャラクターにも適用する共通モーション
⑤ 特定のエリア(カフェなど)で使う、キャラクターに依存したモーション
⑥ 特定のエリア(カフェなど)で使う、衣装に依存したモーション

そして運用を考慮し、それぞれ下記のSkeletonDataファイルに分けて管理します。

・衣装キャラSkeletonData(②、③、⑥)
・全体モーションSkeletonData(①)
・エリア固有モーションSkeletonData(④、⑤)

衣装に依存するモーションデータとキャラクターに依存するモーションデータを分ける管理も考えられるのですが、分けると取り回しが悪くなることもあるので、今回は運用を考慮して同じモーションデータとして管理しました。

衣装キャラSkeletonData

衣装キャラSkeletonDataには下記のモーションが含まれています。

② ゲーム全体で使う、キャラクターに依存したモーション
③ ゲーム全体で使う、衣装に依存した依存モーション
⑥ 特定のエリア(カフェなど)で使う、衣装に依存したモーション

それぞれ具体的には次のようなモーションが含まれます。

モーション
・待機モーション(ゲーム全体で使われるのでキャラクターごとに変えている)
・口パクモーション
・身長補正用モーション
・衣装用追加モーション(衣装に特殊な装飾がある時に制作)
・衣装構造によって看過できない破綻があった時にモーションを上書き
costumeSkeletonData

このSkeletonDataは各キャラクターや衣装に合わせたボーン構成になっており、キャラクターに依存した待機モーションや、イルミネーションなど特殊な衣装用のモーション、そして基本的には3Dでも破綻しない範囲のデザインなので、めったにないですが、どうしても破綻が大きいときにのみモーション名指定で上書きのできるモーションを入れています。 以降で説明する全体モーションSkeletonDataエリア固有モーションSkeletonDataに定義されているモーションは⑥のモーションによって上書きされる可能性があります。

全体モーションSkeletonData

全体モーションSkeletonDataには下記のモーションが含まれています。

① ゲーム全体で使う、どのキャラクターにも適用する共通モーション

commonSkeletonData

具体的には、歩く、走る、ジャンプ、喜ぶ、しょんぼり、応援、お辞儀、などのゲーム全体で使うモーションが用意されています。 また性格差分などによる出し分けはこの中に用意しています。それによりかなり沢山のモーションが入っていますが、衣装キャラと分かれていることで、あとからのブラッシュアップも容易になっています。

エリア固有モーションSkeletonData

エリア固有モーションSkeletonDataには下記のモーションが含まれています。

④ 特定のエリア(カフェなど)で使う、どのキャラクターにも適用する共通モーション
⑤ 特定のエリア(カフェなど)で使う、キャラクターに依存したモーション

areaSkeletonData

このSkeletonDataはモチーフごとに分かれており、オプションによる差分出しが気軽に行えるようになっています。 このとき、キャラクター依存モーションはオプションで出し分けを行いますが、衣装依存モーションはオプションで出し分けを行いません。

その理由としては衣装依存モーションをエリア固有モーションの中に用意すると、衣装追加によって既に本番で使われているエリア固有モーションSkeletonDataに変更が発生してしまう可能性があるからです。

そのため、エリアに紐づいた衣装依存モーションは衣装キャラSkeletonDataの⑥に用意しています。 それにより、逆にエリア追加のたびに衣装キャラSkeletonDataに変更が入る可能性はあるのですが、エリア追加の方が圧倒的に頻度が低いためこちらの方法を取っています。

そもそもエリア追加のときは危なそうな過去衣装は一通り考慮して、それらが破綻しない範囲内での動きを付けるので、発生頻度は低いです。

データ分割を考慮したUnity上でのSDの生成

再掲となりますが、Spineで単純に書き出したままの構成でのSD生成は次のようになります。

create simple sd

しかしアイプラでは多彩な組み合わせを効率よく実現するために、複数のSkeletonDataファイルを合成することで必要に応じてモーションを注入しています。 そのため実際は効率化を考慮した次のようなSDの生成方法になります。

create sd

また、エリア固有アイテムは衣装に紐づいた変更はないため組み合わせが発生せず、キャラクターによる差分はオプションによって実現しているので、一般的な構成と同じ生成方法となります。

create sd item

そしてアイテムを持ったキャラクターを実現するために、これらのファイルを実機で合成しています。 その際、SkeletonRenderSeparatorで分割を行ってから合成することで、キャラクターとアイテムの重なり順が破綻しないようにしています。

create sd full

データの分割まとめ

データの分割を行うことで効率的な運用が行えるようになります。その際、使われどころと変更の発生タイミングを考慮して分割を行うと良いです。

おわりに

様々なバリエーションを実現するために行った工夫と、効率的な制作や運用を行うために行ったデータ分割の工夫の紹介を行いました。

以上で、3回に渡って行ったアイプラにおけるSpineの活用事例を紹介は終了となります。

アイプラのSD実装においては各記事で紹介したような効率化を行いました。 Spineの実務ベースの利用法の情報は少なく苦労もしましたが、最終的には大量のSD制作に耐えられる工夫を行えたのではないかなと思います。 本記事がこれからSpineを用いてゲーム製作を行う皆さまの参考に少しでもなりましたら幸いです。

2013年にサイバーエージェントに新卒入社。QualiArtsにてUnityエンジニアとして複数のゲームの開発・運用に携わる。現在は、ゲーム・エンターテイメント事業部(SGE)のエンジニアボードとして、事業部全体のエンジニアの新卒採用と若手育成および組織作りも行っている。著書:『ステップアップUnity プロが教える現場の教科書』