ゲームサウンドクリエーターの役割

こんにちは。
セガゲームス開発技術部の塚越です。
主に戦国大戦や三国志大戦などのサウンド制作を担当して来ました。

これまでのこのブログの記事ではプログラマやテクニカルアーティストの方の記事が多かったため、そちらに興味の強い読者の方が多いかもしれません。
またゲームに必要不可欠なサウンドであってもあまりサウンドデザイナーと関わることがなく、何をやっているのか知らないという方も居ることでしょう。
そんな方達にぜひ知ってほしい、ゲームサウンドクリエーターの役割を簡単に解説していきます。

サウンドクリエーターの役割

サウンドクリエーターには実は色々な役割があります。
その中からいくつかの例を紹介していきます。

  • 世界観やビジュアルから音の方向性を考える
  • 音楽を作る
  • 効果音を作る
  • 音声を収録して編集する

この他にも色々な仕事があり、ゲーム内容や部署による違いなどもありますが、サウンドクリエーターがどんなことを考えてどのように仕事をしているのか、少しでも知っていただければと思います。

1.世界観やビジュアルから音の方向性を考える

いきなりですが少し想像して見てください。
音楽や効果音があるゲームの世界観や映像の質感にあっていない場合どう感じるでしょうか?
音ばかり気になってしまってそのゲームに没頭できなかったり、主人公に感情移入できなかったり、ということが起こりそうです。

音楽や効果音、ボイスでプレイヤーの感情が動かされるものであって欲しいものです。
そのためには世界観や映像の質感などをきちんと取り入れた音になっていなければいけません。

いきなり本番のデータを作り始めるのではなく、プロデューサーやディレクタ、ビジュアルデザイナーと相談をしながらどのようなサウンドスケープ(音の風景)にしていくのかを考えいくのか、まずはいわば音のコンセプトを考えていくことが重要となります。

ゲームのジャンルや取り扱っている題材などで何が重要な音になるのかが変わってきます。


方法は実に簡単です。
スマホなどで製作中のゲームをムービーで撮影します。
その後Apple Logic Pro XSteinberg Cubase ProなどのDAWアプリケーション(Digital Audio Workstation)で、そのムービーに試しに作った音を置いていきます。そうすることでどんな音が必要か、どんな音楽なら心が動くか、を探っていくことができます。
さらにそれをチームの皆んなで試聴してもらい意見を交わし、この提案と修正を繰り返して行きます。
またその中で、サウンド再生の仕組みを考えたり実験したりする際にはプログラマの助けが必要不可欠です。

少し時間がかかってしまいますが最初にこの作業ができるのとできないのとでは、仕上がりに差が出てくるのです。
このようなコンセンサスがないまま作り進めていけば、いびつでチグハグな音になってしまうのは想像に難くないですね。

2.ゲームのBGMを作るとは

最近のゲーム音楽はゲーム中とサウンドトラックで聴くのとでは違う場合があることにお気づきでしょうか?
実はゲーム中ではゲームのプレイによって状況が変化するのとともに音楽が追従し変化しているのです。
このような手法はインタラクティブミュージックと言ったり、アダプティブミュージックと言ったりしますが、言葉は違えどほぼ同じことを指しています。

BGMは聞く人にどういう気持ちになってほしいかをダイレクトに伝えることができる手段の一つです。
例えば『ファンタシースターオンライン2』を思い浮かべてみてください。
フィールドで敵が居ない時は静かな音楽ですが、敵と戦闘状態になると激しい音楽へと音楽的に自然に変化しますね。*1*2
この様にBGMは気持ちを切り替えていたり、場面転換を気づかせたりという役割を担っています。

これは昔のゲームであっても実は使われている手法で、制限時間が近くなると音楽が早くなったり、水で溺れそうになると違う音楽が流れたりしていました。
それが今ではとても音楽的で自然な流れで行われるようになったのです。

f:id:sgtech:20180624230527p:plain
インタラクティブミュージックの手法の例

このため、作る曲の量はとても増えましたし、プログラマさんとの連携もとても大切です。
ゲーム中のどのパラメータが変化したら音楽をどの様に変化させるかという設計図をしっかり作るところからがゲームのBGMづくりが始まっているということなのです。

3.効果音を作る

効果音を作るのには大まかに2つの手法があります。
1. 現実にある音を使う
2. シンセサイザーで作る

現実にある音を作るには、現実にある音を使うというわりと当たり前と思われる手法を使います。
効果音の素材集の様なものを使ったりもしますが、フィールドレコーディングやフォーリーレコーディングをしたものを集めて使うこともあります。*3*4
f:id:sgtech:20180624230523p:plain

現実にはない音を作るには、シンセサイザーを使って作るしかありません。

「プニョっ」とした音や、「ギュイーン」とした音など、想像でしかない音を効果音として形にします。
レーザー銃を想像してみてください。
実際本物のレーザーが「ビィーーーー」などと音はしませんが、ゲーム中では音がないと寂しいですよね。

ゲーム中の効果音には大切な音と、そこまで大切ではない音全然大切ではないけど雰囲気を作る音がありますので、バランスを取りながら作って行くことになります。
※大切ではないからと言っていい加減な音は作りませんよ。

実際に音を作っていくプロセスを説明しますと

  • 音のイメージを具体的に思い描く(頭の中で音を鳴らすイメージです)
  • その音の要素を分解しDAW上でシンセサイザーや実際の音を使い再構築していく

という手順で作っていきます。

現実音に忠実な簡単な音は誰が作っても同じ音(近い音)になりますが、想像上の音や演出の入った音はとてもクリエイティブで、作るのに時間がかかります。*5

ディレクターやエフェクトデザイナと相談しながら作っていくことが多く、密なコミュニケーションが求められます。

f:id:sgtech:20180624230520p:plain

DAWで効果音を作っている画面

4.音声の収録〜編集

近頃のゲームはスマホゲームでもよく喋りますよね。
この音声を収録したりゲームで使えるように編集したりするのも仕事です。

収録現場ではどの様な演技をして欲しいのかを声優さんに伝え、ゲームディレクターやシナリオライターが思い描いている演技に近づけるということもします。
それに加えてノイズが入ってしまっていないかなどを常にチェックしながら、台本も追って行くのでかなり気を使う仕事です。

ゲームの種類にもよりますが、5千〜数万のセリフを収録するのが最近では普通です。
こうなると何日もかかって収録することになりますので、演技のブレがないか、掛け合いがある部分に不自然さがないかなど確認することも多くなります。

また、ゲーム中でしっかりと聞こえる様にするために音量の調整をしたり、チェックを漏れたノイズを除去したりという編集作業があります。
ノイズが残ってしまうと、音量の調整でノイズが大きく聞こえてしまったり、不自然さが強調されてしまうため地味ですが大切な作業です。

近年ではiZotope RX6などのノイズ除去作業に優れたツールが出てきたため、少しくらいのノイズであれば演技重視でOKにしてしまうことも増えました。このソフトが出る以前のノイズ除去は波形を拡大してノイズ部分を特定し、全て手作業で修正するといった大変苦痛を伴う作業でした。
ですからなるべく収録現場でノイズを混入させずに収録するといったことが重要だったわけです。


f:id:sgtech:20180624230516p:plain

RXによる音声の修正

まとめ

ここまで紹介した仕事以外にも多数の役割がありますが、長くなってきましたので今回はこの辺で終わります。なんとなくサウンドデザイナーの仕事をご想像いただけたでしょうか。
サウンドデザイナーはこれら全ての仕事を少人数で行なっています。また、ディレクターやデザイナー、プログラマーとの関わりをより強く意識する部門でもあります。

またサウンドは色々な感情(燃える、ドキドキする、爽快になる、優しい気持ちになる、暗い闇を感じる、など)と密接に関わっています。
サウンドデザイナーは自然に、時には主張して色々な感情を刺激する様に設計しています。
次にゲームをプレイする時には音に少し注目(注耳)してみてください。

(C)SEGA

*1:参考1:CEDEC 2012 2012年8月20日 Phantasy Star Online 2におけるプロシージャルBGMシステム

*2:参考2:CEDEC 2015 2015年8月27日 それからのPSO2BGM:プロシージャルを利用したオーダーメイドな場面表現・サウンドツールとプログラムの連携による簡略化

*3:フィールドレコーディング:簡単に言えば外に音を録りに行くことです。山奥や工場など色々なところへ出かけ目的の音を録音します。

*4:フォーリー:足音やものが当たる音をなどを専門に取ることができるスタジオを使った録音です。靴の質感や地面の素材などによって変化する足音を録ることができます。

*5:本当に現実音に忠実に音を作ろうとするととても複雑にレイヤーを重ねて周りの状況の変化に応じた変化を再現する様な作り方もありますが、とても大変な作業です。

Anima2Dのセットアップを自動化しよう!編


f:id:sgtech:20180520233811p:plain

こんにちは。
セガゲームス 開発統括部 アート&デザイン部 TAセクション 廣田です。

おかげさまで、コトダマンはサービス開始1ヶ月で500万ダウンロードを越え、たくさんの方に楽しんでいただいております!
今回も引き続き、コトダマンを題材にAnima2Dの紹介をしていきたいと思います。

f:id:sgtech:20180520234033g:plain:w300

前回はAnima2Dでセットアップを行い、アニメーションを作るところまで紹介しました。
セットアップがとても大変だったと思いますが、他の2Dアニメーションツールで用意されている、
・Photoshpからのエクスポート
・Unityへのインポート

がありませんでした。
今回はこの面倒な部分の自動化と描画最適化を行っていきましょう。

■Photoshopからのエクスポート

まずはPhotoshopからパーツ画像と位置情報を出力するスクリプトを作成していきます。

Photoshopデータの作り方

スクリプトで処理するために、データに決まりを作っておきます。
全てのデータ構成に対応しようとすると処理が複雑になりますので、ある程度決めごとを作ります。
今回は下記のようなデータを想定して進めていきます。

  • 1パーツ=1レイヤー
  • グループでまとめられているものも1パーツとして扱う
  • レイヤー第一階層にあるものを出力対象にする
  • レイヤー名を画像ファイル名,Anima2Dのスプライト名として使う

f:id:sgtech:20180520233817j:plain

必要な処理を考えよう

いきなりスクリプトを書き始めずに、まずは必要な処理をリストアップしてから取り掛かります。

  • 各レイヤーを画像として書き出し。
  • Anima2Dでパーツを並べる時に必要な情報を出力。
    • 各パーツの画像位置, サイズ
    • レイヤー名(=画像名)
    • レイヤー順

便利な処理も追加しよう

  • 画像の縮小処理
    • ゲームで使うには、元のPSDサイズでは大きすぎると思います。
    • 大きさはゲーム中でキャラが表示される最大サイズに合わせて決めましょう。
  • レイヤー名のチェック
    • 全角文字のチェック(ゲームデータでは全角は使わないというお約束として)
    • スペースのチェック

仕様

上記をまとめて、このような仕様でスクリプトに実装してきます。

  • 事前にPSDのエラーチェックを行う
  • 画像を適正サイズに縮小
  • 1レイヤー=1パーツとしてpngファイルを出力
  • PSDファイルと同名のフォルダを作成してpngファイルを出力
  • PSDファイルと同名の位置情報ファイルを出力
  • 位置情報ファイルの出力形式:json形式

パーツの画像位置を取得する

エクスポートのキモの部分、パーツ位置を取得する部分について詳しく説明します。
PhotoshopとUnityで原点(0,0)の基準位置が違います。
Photoshop:左上
Unity:中央

f:id:sgtech:20180520233804j:plain

エクスポートする時にUnityの座標でエクスポートするようにしましょう。

f:id:sgtech:20180520233924j:plain

画像の中心を求める

  • Photoshop画像サイズの縦横半分の位置を中心にします。(例:512x512の半分の256,256)

各パーツの位置を求める

  • レイヤーの選択範囲を作成し、selection.boundsで選択範囲のバウンディング座標を取得できます。この座標を利用してパーツ位置を割り出します。こちらもバウンディングサイズの半分をパーツ位置とします。(例:329, 296)
  • そして、先ほど求めた画像の中心を引くことによって、Unity座標(=中心からどのくらい離れた位置にいるか)を求めることができます。(例:+73, -40)

f:id:sgtech:20180520233956j:plain

JSONファイルに情報を出力

求めたパーツ位置をUnityから読めるように、ファイルに記録しましょう。
Order in Layerを設定するためのレイヤーの順序も記録します。
ファイル形式はJSONを使用し、レイヤー数分、下記のようなフォーマットで情報を書き込んでいきます。

{
	"[レイヤー名1]":{"index":[レイヤー順序], "x":[パーツx座標], "y":[パーツy座標]},
	"[レイヤー名2]":{"index":[レイヤー順序], "x":[パーツx座標], "y":[パーツy座標]},
	"[レイヤー名3]":{"index":[レイヤー順序], "x":[パーツx座標], "y":[パーツy座標]},
	"[レイヤー名4]":{"index":[レイヤー順序], "x":[パーツx座標], "y":[パーツy座標]},
                  ・
                  ・
}

スクリプトの流れ

1. エラーチェック
 そもそもデータが仕様にあっていなければエクスポートする状況ではないので、チェック処理は最初に行います。

2. PSDを新ドキュメントに複製
 PSDに対してスクリプトで色々操作しますので、元のPSDを破壊しないように複製してから操作します。

3. 画像を縮小
 出力サイズに縮小します。

4. レイヤー位置を求める
 Photoshopに画像位置を問い合わせる

5. 画像をバウンディングサイズで切り抜く
 パーツレイヤーをそのまま出力すると余白が多いですので、パーツ画像のサイズに切り抜きます

6. JSONファイルに情報を出力

完成スクリプト

以上をまとめたのが、こちらのスクリプトになります。


ExportAnima2D.jsx
f:id:sgtech:20180520234002j:plain
▶クリックで展開



スクリプトの保存先(Windows PhotoshopCC2018の場合)

C:\Program Files\Adobe\Adobe Photoshop CC 2018\Presets\Scripts
(Photoshopを起動したままスクリプトを入れた方は、Photoshopを再起動するとメニューに出てきます)
f:id:sgtech:20180520233807j:plain

実行してみる

出力したいPSDファイルを開いて、実行します。
(スクリプト中でUndoを実行しているところがあるので、もし途中で止まってしまうようでしたらUndo回数を上げてください)
PSDと同名の下記ファイルが無事に出力されましたでしょうか?
f:id:sgtech:20180520234005j:plain

Textureアトラスの作成

フォルダ内にパーツ画像のpngファイルが出力されていますので、TexturePackerなどを使って1枚のテクスチャーにまとめます。
f:id:sgtech:20180520234049j:plain

f:id:sgtech:20180520234238g:plain:w300


■Unityへの取り込み

Photoshop情報を元にAnima2Dスプライトを並べていきます。

事前準備(MiniJSON)

PhotoshopからJSON形式で画像位置情報を出力しましたので、UnityでもJSONを扱えるようにしましょう。
今回はMiniJSONを使用します。

MiniJSONの入れ方

  • https://gist.github.com/darktable/1411710にアクセス
  • ページ右上のDownload ZIPを押してダウンロード&解凍
  • 出てきたMiniJSON.csをプロジェクト内のPluginsフォルダに入れる(無ければ作成)

必要な処理を考えよう

Unity側に関しては、難しい処理はありません。
Photoshopで出力したJSONを読み込みながら、スプライトとボーンの配置を行います。

  • Meshの配置(JSONから位置情報を読み込んで、Photoshopパーツ位置の再現)
  • ボーンのアサイン
  • OrderInLayerの設定(JSONから読み込み)


前回の記事を引き継いで、シーン構成は下記の構成を予定しています。
f:id:sgtech:20180520234100j:plain


仕様

  • 選択されているスプライトを処理の対象にする
  • 選択されているものと同じフォルダにあるJSONを参照
  • 選択されているスプライト名からJSON内の配置情報を見つける
  • スプライトをmesh階層に配置,位置設定
  • ボーンをbone階層に配置,位置設定
  • メッシュにボーンをアサイン

完成スクリプト

以上をまとめたのが、こちらのスクリプトになります。

スクリプトの保存先

UnityプロジェクトのEditorフォルダに入れてください(無ければ作成)。

実行してみる

Projectウインドウで配置したいスプライトを選択し、Window -> Anima2D -> ImportAnima2D を実行します。
mesh階層に選択したキャラパーツのメッシュが配置され、ボーンが自動的に割り当てられます。
この後は、前回の記事を参考に、ボーンの向きの設定・メッシュ割り・ウエイトの調整を行ってください。
ここまで自動でできるとずいぶん楽ですね。

f:id:sgtech:20180520234054j:plain
f:id:sgtech:20180520234057j:plain

f:id:sgtech:20180520234211g:plain:w350


■描画最適化

さて、セットアップは自動化されて一件落着なのですが、FrameDebugger(Window->Frame Debugger)で確認すると、パーツ数分DrawMeshが走っています。
今回はシンプルなキャラクターですが、パーツ数の多いキャラクターや、モバイル環境などではパフォーマンスに影響が出てきますので、まとめて描画できるものは1回で描画するようにしましょう。

f:id:sgtech:20180520233917j:plain
FrameDebuggerでの確認(最適化前)

SkinnedMeshCombinerとは

前回の記事で、Examplesの中に入っているSkinnedMeshCombinerのお話をしました。
SkinnedMeshCombinerは登録してあるSpriteMeshをまとめて、1メッシュにしてくれるスクリプトです。
ゲーム再生時に1つのメッシュにまとめられます。
(プロジェクトに入っていない方は、Anima2Dのアセットからインポートしましょう)
f:id:sgtech:20180520233820p:plain

SkinnedMeshCombinerの使い方

SkinnedMeshCombinerコンポーネントを追加し、Sizeに登録したいメッシュ数を入力後、レイヤーが下のパーツから順に登録していきます。(この辺はPhotoshopのレイヤー並びと逆順になるので、混乱しますね)
1番目に登録してあるものから、順番に重ねていき、最終結果としてまとめられるイメージです。

f:id:sgtech:20180520233914j:plain

ですが、これでめでたく1ドローで描画されてめでたしめでたし!という訳にはいきません。
再生すると、SpriteMeshAnimationで付けた、目パチ・口パクが動かなくなっています。
どうやらパターンチェンジまで1メッシュに統合されて、パターンチェンジできなくなってしまうようです。

コンバインしてはいけないもの

メッシュ自体を操作している場合は、コンバインすると操作できなくなりますので、対象から外します。

  • SpriteMeshAnimation(パターンチェンジ)
  • 表示非表示アニメ(こちらも1メッシュにされてしまうと、ActiveのON/OFFアニメが効かなくなります)

SortingGroupの設定

さて、コンバインしてはいけないものを分けたとして、また新しい問題が出てきます。
今までパーツの前後関係はOrderInLayerで設定していましたが、内部的に生成されたコンバインメッシュに対して設定する方法がありません。
そこで、コンバインするもの/しないものを階層で分け、SortingGroupを使ってそれぞれの前後関係を指定します。

描画最適化(SkinnedMeshCombiner + SortingGroup)のキャラセットアップ例

以上の状況をふまえ、キャラクターセットアップは画像のようになります。
基本的に

  • 体 (draw1階層)
  • 目パチ, 口パク=SpriteMeshAnimation (draw2階層)→コンバインしない
  • 目口より手前のもの (draw3階層)

で4DrawMeshに収まると思います。

キャラトップ階層にもSortingGroupを付けておくことで、ゲーム中に複数のAnima2Dキャラクターを出したときの、キャラ間の前後関係にも対応することができます。
(SortingGroupはコンポーネントが付いている同階層内で前後関係を並べてくれますので、draw階層とは別の番号を割り当てて制御することができます)

f:id:sgtech:20180520233953j:plain
(基本構造説明のため、今まで説明していた青いキャラとは違うキャラの階層構造の画像を使用しています)

結果

FrameDebuggerで確認すると、かなり描画数が減ったと思います。(体+目+口)
f:id:sgtech:20180520233910j:plain

FrameDebuggerでの確認(最適化後)

スクリプト化する

以上の手順をスクリプト化したいのですが、デフォルトの状態ではSkinnedMeshCombinerにアクセスできないパラメーターがありますので、SkinnedMeshCombiner.csのコードを書き換えます。

SkinnedMeshCombiner.cs
修正前

using UnityEngine;
using System.Collections.Generic;
using Anima2D;

public class SkinnedMeshCombiner : MonoBehaviour
{
	[SerializeField]
	SpriteMeshInstance[] m_SpriteMeshInstances;

	SpriteMeshInstance[] spriteMeshInstances {
		get {
			return m_SpriteMeshInstances;
		}
	}


修正後

using UnityEngine;
using System.Collections.Generic;
using Anima2D;

public class SkinnedMeshCombiner : MonoBehaviour
{
	[SerializeField]
	SpriteMeshInstance[] m_SpriteMeshInstances;

	public SpriteMeshInstance[] spriteMeshInstances {
		get {
			return m_SpriteMeshInstances;
		}
		set {
			m_SpriteMeshInstances = value;
		}
	}

10行目に「public」を追加
14行目に「set {~}」の部分を追加

スクリプトの流れ

SpriteMeshAnimationを別グループにまとめるように処理をします。
1.階層をOrderInLayerの順で並べ替え
2.下の階層からSpriteMeshAnimationが出てくるまでSkinnedMeshCombinerに登録し、drawグループを作成していく。
3.SpriteMeshAnimationが出てきたら新規drawグループを作成。SpriteMeshAnimationが無くなるまで上にたどる。
4.SpriteMeshAnimationが無くなったら、引き続きSkinnedMeshCombinerに登録してdrawグループを作成していく。
階層の一番上まで繰り返す。

完成スクリプト

スクリプトの保存先

UnityプロジェクトのEditorフォルダに入れてください。

実行してみる

Winodw -> Anima2D -> Create Drawgroupを実行してください。
SpriteMeshAnimationが付いているものを避け、drawグループが作成されます。


まとめ

以上、2回に渡ってAnima2Dを紹介してきましたが、いかがでしたでしょうか?
Unity内のアニメーションツールなので、足りない機能などがあれば今回のように追加したり、ShurikenやSpringBoneなどの他のUnityコンポーネントとも自由に組み合わせて使うことができます。
今回PhotoshopからのエクスポートとUnityへのインポートスクリプトを掲載しましたが、基本的な部分のみですので、みなさんの作業に合わせてコードを改造して使ってみてください。
他のツールに比べて、制作物に合わせたフローを構築しやすいと思います。

f:id:sgtech:20180520233847g:plain


 TAセクションではこのようにアーティストの制作に役立つ環境を提供できるよう力を注いでおります。やりたいことを遠回りせずに行える環境でお仕事したい方はぜひ下記の弊社グループ採用サイトをご確認ください。現在さまざまな職種でアーティストを多数募集しております!
採用情報 | セガ企業情報サイト


(C)SEGA

最近のアーケードゲーム開発

皆様、こんにちは。
セガ・インタラクティブ 第三研究開発本部 開発一部 プログラマの渡邉です。
新年度も始まり、心機一転といった感じでしょうか。本ブログも開始してから1年と8ヶ月程度と相成りました。
時が経つのは早いなと思いつつ、日々ゲーム開発に勤しんでいます。
私は近年アーケード(業務用)ゲーム開発を行っていますが、以前は主にコンシューマ(家庭用)ゲーム開発に携わっていました。
セガグループではコンシューマ、アーケード、PC、モバイル、ブラウザ等々と様々な機種でゲームを開発、リリースしています。
ここまでの技術ブログの記事を振り返ってみれば、様々な所属部門の記事があることがお分かり頂けると思います。
機種が違えば、開発する際の前提やお約束が変わってくるものです。そこで本記事では、自身の経験を元にコンシューマゲーム開発との比較も交えつつ、最近のアーケードゲーム開発について紹介させていただければと思います。なお、アーケードゲームは開発内容によって、紹介したものと環境が違うこともあることを予め申し上げておきます。あくまで一例ということをご理解いただければ幸いです。
過去記事では特定の職種向けのテクニックや事例の紹介が多かったかと思いますが、概括的なお題ということもあり、極力職種問わずの内容になっております。技術的な側面を期待されている方には退屈かもしれませんがご容赦ください。


目次


開発環境/ハードウェア

コンシューマゲーム開発では各プラットフォーマーが提供する開発キットを使用します。(周辺機器等の拡張を除くと)そのハードウェア構成は基本変動することはないので、ハードウェア性能は一定です。ハードウェア性能が決まっている分、素早くソフトウェア開発に移行出来ますが、その制約下で開発することになります。特にマルチプラットフォームタイトルは厳しい制約下でソフトウェア開発を強いられることになるでしょう。
アーケードゲーム開発ではハードウェア構成から検討することになります。その為、タイトルに合わせた最適なハードウェアを構築可能ですが、その分ハードウェア構成の選定には時間がかかりがちです。また、タイトルリリース後しばらくして採用した部品が製造中止になる可能性もあるため、注意が必要です。
なお、アーケード筐体は家庭用ゲーム機に比べ若干大き目なので、スペースの確保が大変です。マルチプレイになると尚更です。


f:id:sgtech:20180423190402j:plain
弊社アーケードゲーム「Wonderland Wars」の開発用筐体エリアです。ちょっとしたゲームセンターですね。

使用言語/ソフトウェア

こちらはコンシューマ・アーケードあまり変わりありません。
クライアントアプリケーションの使用言語は C++ あるいは C# が主です。
最近では Unity や Unreal Engine 等といったゲームエンジンでの開発タイトルが増えています。
敢えて違う点を挙げるとすれば、プラットフォーマーの制約下に縛られないアーケードゲーム開発のほうがオープンソースや外部ライブラリを採用し易いかもしれません。
また、サーバー等インフラも自前で管理する為、専用モバイルアプリケーションやWebサイトとの連動も比較的容易です。(担当者の作業が容易だとは言いませんが…)


f:id:sgtech:20180423190419j:plain
弊社では、アーケードゲームのIP(知的財産)を様々なデバイスに展開する「マルチデバイス×ワンサービス」戦略を掲げており、タイトルに連動したスマートフォンアプリやWebサイト等を同時に開発、提供しています。例えば「Wonderland Wars」では、Web上で戦績等を確認出来る「Wonder.NET」や、スマートフォン向けのサポートアプリケーション「Wonderland LIBRARY APP」の提供を行っております。

ゲームデザイン

アーケードゲームではコンシューマゲームとの差別化を図る為、「家庭での再現は難しい」ことを念頭に置いたデザインを心がけることが多いです。
ディスプレイからコントローラーまでタイトルに最適な構成を構築出来ることはゲームデザイナーにとって喜ばしいことではありますが、やりすぎると移植性に欠けるため、注意が必要です。
また、パッケージ売りのコンシューマゲームと、1プレイ幾らのアーケードゲームではマネタイズが大きく違うわけですが、アーケードゲームでは店舗の利益を考慮し、プレイ回数を稼ぐことが可能なゲームデザインが求められます。1プレイ時間は短く(数分~十数分程度)、それでいてライブで盛り上がるための体験を重視したデザインが好まれます。

エラー処理/データ分析

アーケードゲームでは直接硬貨を投入して遊んでいただいている為、エラー周りの例外処理には気を遣います。
例えば、停電等による電断や通信不良等が発生した場合でも極力不利益が生じないよう、大切なプレイヤーデータを随時バックアップしたり、ある程度の追跡および状況分析が出来るようプレイログ情報を専用サーバーにアップロード等しています。

開発規約/出荷基準

コンシューマゲーム開発では「一定時間以上、画面を静止してはならない」といった各プラットフォーマーが定めた規約(Technical Requirements Checklist 等)を遵守する必要があります。また、CERO や ESRB 等といったレーティング機構の審査もパスしなくてはなりません。
対してアーケードゲーム開発では、共通の規約・審査機構は無く、各メーカー独自で定めています。
弊社でも独自のガイドラインを用意し、デバッグサポートチームや品質保証部の元、厳格にチェック・パスしたタイトルをリリースしています。
また、アーケードゲームは風営法の適用対象なので、仕様作成の段階で法務部と擦り合わせを行う等、法令遵守を心がけています。


f:id:sgtech:20180423190415p:plain
「Wonderland Wars」のチェックリストの一部です。実に 500 超の中項目があり、それぞれに数十~百程度のチェック項目が並んでいます。

顧客層

コンシューマゲームの顧客層は、各プラットフォーマーのターゲット機保有者、ターゲット機で遊んで下さるユーザーです。
アーケードゲームの場合、店舗に足を運んで遊んで下さるユーザーに加え、店舗(オペレーター)も顧客層に含みます。(いわゆる B to B to C の取引形態ですね)
ユーザーに楽しんでいただけるゲーム内容にするのは勿論ですが、店舗がオペレーションしやすい機能を実装することも忘れてはいけません。その為、入力テストや、クレジットの確認、店舗大会用の設定等を可能とする「テストモード」を通常用意します。


f:id:sgtech:20180423190410j:plainf:id:sgtech:20180423190406j:plain
こちらは「Wonderland Wars」のテストモードの一部で、入力デバイスが正常に動作するかのテスト画面です。アーケードゲームでは毎日様々なユーザーにプレイいただくため、パーツの摩耗も激しいです。オペレーターが筐体の状態を確認する際の補助となるよう、このようなモードを用意しております。

まとめ

いかがでしたでしょうか。
簡単な解説ではありましたが、アーケードゲーム開発とコンシューマゲーム開発、同じゲーム開発でも違いが見て取れたのではないでしょうか。
本記事が皆様の気付きの一助となれば幸甚です。
もしアーケードゲーム開発にご興味を持たれましたらセガ・インタラクティブ採用サイトを、
逆にコンシューマゲーム開発のほうが面白そうだと思われた方はセガゲームス採用サイトを、
とにかくゲーム作りに関わりたいと思われた方はセガグループ採用サイトを是非ご確認ください。
我々セガグループは共にゲームを作り続けてくれる方々を歓迎します。
採用情報 | 株式会社セガ・インタラクティブ -【SEGA Interactive Co., Ltd.】
採用情報 | 株式会社セガゲームス -【SEGA Games Co., Ltd.】
採用情報 | セガ企業情報サイト

(C)SEGA

Anima2Dでキャラクターアニメーションを作ろう!編

■ 共闘ことばRPG 「コトダマン」

こんにちは。
セガゲームス 開発統括部 アート&デザイン部 TAセクション 廣田です。


今春リリースの「共闘ことばRPG コトダマン」では全てのキャラクターアニメーション(700キャラ以上!)をAnima2D で作成しました。タイトルリリース前ですが、一足先にコトダマンのキャラクターを使って、Anima2D のセットアップからアニメーション作成を解説していきます。

さらに次回、Anima2Dでの制作をサポートするスクリプトについても書いていく予定です。

公式

好評配信中です!
kotodaman.sega.jp

Twitter

この記事で紹介しているキャラクターは、全てコトダマンのAnima2Dキャラクターです。
Twitterでは他のキャラクターもたくさん登場していますので、Anima2Dのサンプルとして是非ご覧ください!
(フォローもお願いします!)

twitter.com


■Anima2D とは

f:id:sgtech:20180326002518p:plain
2017 年から Unity テクノロジーが提供しているアセットで、無料で利用できます。
将来的に Unity への組み込みを予定しているようです。
これを利用することで、Unity 内で 2D ボーンアニメーションができるようになります。
3DCGツールを使っている方なら、板ポリゴンにテクスチャーを貼り、ボーンを入れて2D イラストを動かすという工程がUnity内でできるようになったと思ってもらうと分かりやすいでしょう。

ドキュメント

上記画像にある、DocumentationというリンクからPDFをダウンロードできます(英語)

f:id:sgtech:20180326002249g:plain:w256

■Anima2D のメリット

  1. FCurve エディタが使用できる(MayaやSoftimageで慣れたアニメーターの作業・修正スピードが格段に違う)
  2. Unity のスクリプティングによる拡張ができる(セットアップの自動化や機能追加・作業支援ツールの作成など)
  3. Unity 内で作業できるので、他ツールからのエクスポート/インポートの手間が無い。
  4. メッシュをある程度自動で切ってくれる
  5. 学習コストが安い(Unityを触ったことがある+3DCGツールでアニメーションを作成した事があればすぐ)
  6. ゲーム画面内で見ながら作成・修正できる。(Shuriken のエフェクトも同時に作成可能)
  7. Unityの公式アセットになった。

コトダマンでは700キャラ以上のアニメーションを作るにあたり、Spine, Live2Dも検討したのですが、上記理由でAnima2Dを採用しました(数字は選択理由順)。
簡潔に言うと「作業スピード+スクリプティングによるカスタマイズ」が選択理由です。

f:id:sgtech:20180326002126g:plain:w200

■Anima2Dのデメリット

この場合は違うツールを選択した方が良いです。

  • Unity 以外で開発を行う可能性がある(Cocos2D など)
  • フリーフォームデフォーメーション(頂点を動かしての細かいアニメーション)を行う

フリーフォームでフォーメーションをやるかどうかが一番の判断基準ですが、作り方によっては、コトダマンのように魅力的に動かせますよ!

f:id:sgtech:20180326002111g:plain:w200

■Anima2D を準備する

では、さっそく始めてみましょう。
まずは、アセットストアから「Anima2D」で検索してダウンロードします。
後述する Skinned Mesh Combiner(複数のキャラパーツを1ドローにまとめてくれるスクリプト)が入っているので、Examples も含めてインポートを行っておきましょう。
(最低限でも、Examples/Scriptsのみはインポートしてください)


f:id:sgtech:20180326002151p:plain

■全体の流れ

イラスト → パーツ分け → パーツ配置 → ボーン配置 → ウエイト付け → アニメーション作成 → 描画最適化
という流れで進みます。


f:id:sgtech:20180326000954p:plain:w1000
f:id:sgtech:20180326002142g:plain:w200

■イラストの準備(パーツ分け)

Anima2Dで動かすために、キャラクターのパーツ分けを行います。

f:id:sgtech:20180326001438p:plain

コツ

  • まずはキャラクターがどう動くのか、アニメーションを決定しましょう。ここをきちんと計画しておかないと、アニメーションを作り始めてから、パーツが分かれていなくて動かせない!などの問題が出てきます。
  • それでも、どう動かすかが決めきれない場合は、基本的に関節単位で分割します。
  • Photoshop上でCtrl+Tでパーツを回転させて、パーツの割り具合と動きを確認してみましょう。隙間が見える場合は塗り足すか、それをカバーするような動きに変更します。
  • 上腕・下腕・手、足などはパーツを分けておいた方が破綻せず、動きも大胆に作れるようになるので、分けておくのをオススメします。

Photoshopデータの作り方

  • パーツを切り分ける。1 パーツ 1 レイヤー。
  • 英数字でレイヤー名を付ける
  • パーツの書き出し。1パーツ1画像(.png)

後でこのルールを元に、Unity取り込みまでを自動化するスクリプトを作成します。

f:id:sgtech:20180326002331g:plain

■Unityへの取り込み

1枚のテクスチャーに収める

TexturePackerやSpriteUVなどを使って、1枚のテクスチャーにパーツを配置します。


f:id:sgtech:20180326000951p:plain:w128

マルチスプライトに設定

  • テクスチャーをUnityに読み込んだら、SpriteModeをMultipleに設定します。
  • 必要があれば、Sprite Editor ボタンを押して、スプライトを切り直します。


f:id:sgtech:20180326001435j:plain

Anima2D SpriteMesh に変換

  • テクスチャーを右クリック → Create → Anima2D → SpriteMesh でAnima2DのSpriteMeshを作成します。
  • テクスチャーファイルを選んで実行すると、まとめてSpriteMeshが生成されます
  • マルチスプライトの各要素(青い四角アイコン)を選んで、同じようにCreate → Anima2D → SpriteMeshとすれば、1個単位でSpriteMeshを生成されます。SpriteEditorで後からスプライトを追加した場合など、単品でSpriteMeshを作る事ができます。


f:id:sgtech:20180326002323p:plain
f:id:sgtech:20180326002319j:plain:w256
グリーンのオブジェクトがAnima2D のスプライトです。


f:id:sgtech:20180326002840g:plain

■Unity階層の準備

まず、メッシュとボーンを配置するための階層構造を準備しましょう。
GameObject -> Create Emptyで画像のような階層を組みます。

character → キャラクタートップ階層(全体に関するスクリプトはここに付けます)
/mesh → メッシュを置きます
/bone → ボーンを置きます


f:id:sgtech:20180326001422j:plain
f:id:sgtech:20180326001812g:plain:w256

■パーツ(SpriteMesh)を並べる

配置テンプレート(下敷き)の準備

キャラクターの1枚画像を下敷きとして用意し、これをガイドにパーツを配置していきます。

  • キャラの全身画像を用意し、Sprite(2D)に設定します。


f:id:sgtech:20180326002244j:plain

  • Hierarchy上にtemplate画像を置き、InspectorでColorのAlphaを半透明に設定し、配置ガイドにします。


f:id:sgtech:20180326002431p:plain

  • そして・・・頑張ってSpriteMeshを並べていきます!


f:id:sgtech:20180326002415p:plain

パーツの前後関係の設定(Order in Layer)

Order in Layerでパーツの前後関係を設定します。数字が大きい方が前面に描画されます。

  • 分かりやすいように、Hierarchy上でPhotoshopと同じ順に並べます。
  • Order in Layerを下から順に番号を振っていきます。


f:id:sgtech:20180326002426p:plain

この項目の工程は非常に面倒なので、後でスクリプトで自動化します!


f:id:sgtech:20180326002945g:plain:w200


■ボーンの配置と設定

配置

計画したアニメーションに合わせて、配置したパーツの上にボーンを並べていきます。


f:id:sgtech:20180326000958j:plain:w256

  • Hierarchy上で右クリックし、CreateObject→2D Object → Bone でボーンを出します。(またはAlt+Shift+B)


f:id:sgtech:20180326001419p:plain

  • ボーンの根本を掴むと移動、先の方を選ぶと回転ができます
  • Transform, Rotationツールを切り替える必要はありません。


f:id:sgtech:20180326002903g:plain

親子階層の設定

配置が終わったらHierarchy上で親子関係を組み立てます。


f:id:sgtech:20180326002420j:plain

boneの設定

Hierarchy上での親子関係とは別に、bone自体にChildという設定項目があります。
Anima2Dが計算する時に使うボーンの親子情報です。

  • IKで動かす場合は、必ず設定します。
  • 子階層に向かって、ボーンの向きと長さが自動で設定されるようになります。腕・脚など、関節部分は見た目が分かりやすくなります。

f:id:sgtech:20180326002628p:plain

Colorの項目でボーンに色を設定できます。
自分で分かりやすい色に設定しましょう。
f:id:sgtech:20180326002438p:plain

Color ボーンカラー。見た目の変更。
Child 子ボーン。IKを使うなら設定必須。
Length ボーンの長さ。Childが設定されていると自動計算されます。

コツ

  • 階層構造は最後に組みましょう。
  • 骨の配置時に階層構造も組みたくなるのですが、先にやってしまうと親の骨を動かした時に、子の位置もずれてしまうので、手戻りが発生します。


f:id:sgtech:20180326003427g:plain

■メッシュにボーンを割り当てる

メッシュを選んで、対応するボーンを設定します。

  • メッシュを選択
  • Bonesの右下の「+」を、割り当てたい骨の数だけ押します。
  • 出てきた空欄に、ドラッグ&ドロップでボーンを割り当てます
  • (まだメッシュにバインドしていないため警告が出ますが、今は大丈夫です)

f:id:sgtech:20180326002524p:plain

  • 階層をまとめて割り当てたい場合は、SetBonesにドラッグ&ドロップすれば、子階層まで一気に設定してくれます。

f:id:sgtech:20180326002617p:plain


f:id:sgtech:20180326003037g:plain

■メッシュ割り

メッシュはボーンに合わせて割ると綺麗に曲がりますので、ボーン配置後にメッシュ割りをします。

SpriteMesh Editor

Anima2D用のメッシュ割り&ウエイト付を行うエディタです。
Unity上部メニュー → Window → Anima2D → SpriteMeshEditor で表示します。

Sliceツールの利用

左上のSliceボタンを押すと、メッシュを自動で割ってくれます。
f:id:sgtech:20180326002620j:plain

Outline Detail 画像形状をどれだけ正しく追跡するか。値を上げると画像に沿ってメッシュが切られますが、頂点数が多くなります。
Alpha cutout メッシュを切る時のアルファの許容値。値を上げるほど画像の輪郭に近いメッシュが切られます。OutlineDetailの値が高くないとあまり効果はありません。
Tessellation メッシュの中を分割するか。画像の輪郭だけでなく、内部にも頂点が生成されます
Detect Holes ドーナツのような穴の空いた画像部分のメッシュをくり抜くか。

画像は自動で生成されたメッシュです。
手間を掛けたくない場合は、決め打ちパラメーターでもある程度の結果にはなりますが、Sliceのスライダーを動かし、自動で割ってから手で調整するのが楽でしょう。


f:id:sgtech:20180326003159j:plain

手動でのメッシュの調整

頂点の追加 Shift+クリック
頂点の削除 Del


f:id:sgtech:20180326003405g:plain

■ウエイトの設定

Bindボタンを押すと、メッシュに骨がバインドされ、自動的にウエイトが割り振られます。
ウインドウ下にWeightTool, Inspectorが出てきますので、これらのツールを使ってウエイトを調整していきます。

  • 頂点を選択(Ctrlを押しながらで複数選択可)
  • ウエイトを割り振りたい骨を選択
  • Weight Toolのスライダーでウエイトの割合を設定する


f:id:sgtech:20180326001940g:plain

Weight tool(SpriteMeshEditor:右下)

f:id:sgtech:20180326002516j:plain

Overlay メッシュ上にウエイトカラーを表示します
Pies 各頂点上にウエイトの割合を表示します
Smooth ウエイト分配をスムーズ化します。選択した頂点だけにも掛けられます
Auto 自動でウエイトを割り振ります。選択した頂点だけにも掛けられます

Inspector(SpriteMeshEditor:左下)

SpriteMeshEditorで選択したものの情報が表示されます。
画像は頂点を選択した場合です。
選択頂点に割り当てられている、ボーンとウエイトの確認・調整ができます。
f:id:sgtech:20180326002513j:plain

コツ

  • ウエイトを付けた後に、バインドしたまま、頂点の追加と削除ができます。
  • 後から足した頂点にはウエイトが割り振られていませんので(黒い点)、Autoボタンなどで忘れずにウエイト設定しましょう。
  • Unbindすると、設定したウエイトが初期化されてしまいます! 骨位置が違うと思ったら、早めにUnbindして骨位置を調整しましょう。ここはAnima2Dで改善して欲しい所です。

f:id:sgtech:20180326003412p:plain:w300

f:id:sgtech:20180326003347g:plain:w300

■リギング

IKの設定

  • 先端の骨を右クリック→2D Object→IK Limb(IK CCD2D) を選択して、IKコントローラーを出します。
  • グリーンの円がIKコントローラーです。


f:id:sgtech:20180326002522j:plain

IK Limb(2ボーンIK)

腕・脚など2本の骨を制御する時に使用します。
f:id:sgtech:20180326002626j:plain

Record IKコントローラーを動かした時に、ボーン側にキーを打ってくれます。実機上でIKの計算コストを掛けたくない時はONにしてアニメーションを作成します。
Target IKでコントロールされる骨。自動設定されます。
Restore Default Pose 計算を実行する前に初期ポーズを設定する。基本ON(現状ON以外の使い道が分かりません)
Flip 関節の曲がり方を反転。IK設定後に関節が逆に曲がってしまうならONにします。
Orient Child IKでコントロールされているボーンより、下階層にあるボーンの角度をどうするか。ON:IKコントローラーと同じ角度にする。OFF:階層の角度を維持する。

IK CCD2D(多関節IK)

尻尾などの長い関節の制御に使用します。
f:id:sgtech:20180326002623j:plain

Num Bones 多関節IKでコントロールするボーン数。自動設定されます。
Iterations 計算回数。大きいほど多関節ボーンがIKコントローラーを正しく追跡しますが、計算負荷が大きくなります。モバイルではデフォルトの10を基準とし、様子を見て値を下げましょう。
Damping 多関節ボーンの柔らかさ。1に近いほど固くなります。

Limb, CCD2Dの挙動の違い

IKによくある現象として、IKコントローラーとボーンが離れた時に、伸び切りが発生します。
伸び切った所から、再びIKコントローラーに接触すると、カクッとした動きになり非常に目立ちますが、
イラストを大胆に動かすと、どうしても伸び切り・カクつきが回避できない場合があります。
その場合は、2ボーンでもCCD2Dを使ってコントロールすると緩和されます。
Limbに比べて計算コストは高くなりますが、Iterationsの値(デフォルト10)を上げなければ、実機上でも問題ありません。
f:id:sgtech:20180326002756g:plain:w256

コントローラーの追加

ボーンに直接アニメーションを付けず、別階層でリグを組む時はコントローラーを利用します。

  • ボーンを右クリック → 2D Object → Controlでコントローラーを出します。
  • ボーンはコントローラーのTransformに追従するようになります。



f:id:sgtech:20180326003123g:plain

■ポーズマネージャー

定型ポーズの保存・読み込みができます。
ポーズライブラリや、同じ骨構造で違うキャラを作る場合など、色々活用できます。

Poseファイルの準備

  • Projectウインドウで右クリック → Create → Anima2D → Pose でポーズファイルを作成

コンポーネントのアタッチ

  • bone階層にPoseManagerコンポーネントをアタッチ。
  • 「+」ボタンを押してPoseファイルを設定
  • 必要なポーズ数分繰り返します。

f:id:sgtech:20180326000948p:plain

保存/読込

  • Save/Loadボタンで、ポーズの保存・読込ができます。

コツ

  • PoseManagerは、付いている階層以下のゲームオブジェクトのTransformを記録します。キャラクターの一番トップ階層につけてしまうと、メッシュの位置も記録されてしまい、違うキャラクター間でアニメーションを使いまわせなくなります。
  • アニメーションエディタをレコードにした状態で、PoseManagerのLoadボタンを押せば、ポーズがロードされて、自動的にキーが打たれます。モーションの開始/終了時に基本待機ポーズに戻す時などに使えます。
  • セットアップ中やアニメーション中に、デフォルトポーズに戻せるように、初期ポーズを登録しておくと良いでしょう。

f:id:sgtech:20180326003302g:plain

■アニメーション作成

Animation, Mecanimを準備

ここはUnity通常のフローなので、簡単に書きます。

  • Character階層を選択し、Animatorコンポーネントを付ける
  • Projectウインドウで右クリック→Create→Animator Controller。Animatorコンポーネントに設定。
  • Projectウインドウで右クリック→Create→Animation。
  • Animatorウインドウで右クリック → Create State → Empty。出てきたステートに、上のAnimationを設定。
  • Animationウインドウを開き(Ctrl+6)、ボーンを動かしてアニメーションを作成します。

パターンチェンジ(目パチ・口パク・着せ替え)

Anima2Dではパターンチェンジアニメーションを作成する事ができます。

  • メッシュを選択
  • Sprite Mesh Animation コンポーネントをアタッチ
  • 「+」を押して出てきた欄に、SpriteMeshを設定
  • Frameのスライダーを操作してパターンを切り替えます(上から順番に0, 1, 2・・・の値に対応しています)

f:id:sgtech:20180326003103g:plainf:id:sgtech:20180326000945j:plain

コツ

  • パターンチェンジするSpriteMeshの位置は、最初のメッシュ配置時にピッタリ同じ位置に合わせておきます。
  • アニメーションに追従する必要があるので、パターンチェンジメッシュも忘れずに骨をバインドしておきましょう。
  • 今回は目なので、パターンチェンジメッシュを全て頭の骨にバインドしています。これで頭の動きに合わせてついてきます。

IKによるアニメーション

IKコントローラーは、そのままアニメーションを付けて、実機再生可能です。

  • IK計算のコストを省くには、最後にアニメーションベイクする方法もありますが、全フレームにキーが打たれるのでアニメーションデータが大きくなります。(IKコントローラーを選択 → Unity上部メニュー → Window → Anima2D → Bake Animation
  • IKコントローラーのRecordボタンをONにすることで、IKコントローラーを動かした時に、骨側にアニメーションキーを打つ事ができます。(最後にIK Limb, IK CCD2DのActiveをOFFにするのを忘れずに。IK計算がされたままになります。)
  • IKのままアニメーション付けできるのがやはり楽ですが、実機での計算負荷を減らしたい場合は、Recordボタンの使用が落とし所だと思います。


f:id:sgtech:20180326003120p:plain


その他

2Dアニメーションを作成していますが、UnityなのでZ軸(奥行き)が存在します。
アニメーションで上手く使えば、面白い表現ができるかもしれません。

f:id:sgtech:20180326003249g:plain

■次回:Anima2Dを自動化しよう!スクリプトの作成

作業で面倒だった部分をスクリプトで自動化しましょう!

  • Photoshopからのエクスポート(各レイヤー毎の画像と、位置情報の出力)
  • 位置情報を元にAniam2Dメッシュを自動配置
  • Photoshopレイヤー順にOrderInLayerを設定
  • ボーンの配置
  • ゲームに出すための最適化

について書く予定です。


f:id:sgtech:20180326002657g:plain


 TAセクションではこのようにアーティストの制作に役立つ環境を提供できるよう力を注いでおります。やりたいことを遠回りせずに行える環境でお仕事したい方はぜひ下記の弊社グループ採用サイトをご確認ください。現在いろいろな職種でアーティストを多数募集しております!
採用情報 | セガ企業情報サイト


(C)SEGA

MayaからUnityへのUVアニメーションエクスポート

はじめに

こんにちは、セガ・インタラクティブ 第三研究開発本部 開発一部
ソフトウェアエンジニアのサカイと申します。
最近はアーケードゲームの開発をしています。

本日は、「MayaからUnityへのUVアニメーションエクスポート」
というテーマで書いていきます。作業記録のような書き方ですが、私の好みなので許してください。
いつもの作業の流れはこんな感じなのです。

まず、動機、計画について簡単にまとめた後、
本題のMayaからのエクスポートとUnityのインポートについて書きます。
最後に、まとめと今後の拡張案で締めます。

なるべく内容に間違いのないように努めますが、
必ず自身で調べて、確認するようお願いします。

概要

動機

現在、私のチームのアーティストはMayaでモデリングとアニメーションつけを行っています。
アニメーション作業の中にはUVアニメーションによるキャラクターの表情変化が含まれます。
しかし、Unityの標準機能ではUVアニメーションをMayaからインポートする方法がなさそうです。

Unity上で表情アニメーションだけ後付けすることは手間がかかりますし、不具合の元にもなります。
アーティストの要望は以下のような感じでしょうか。

  • Maya上でUVアニメーションもつけたい
  • Unityにインポートした後で手を加える必要がない

計画

少し調べたところでは、既存の拡張はなさそうです。位置等の別チャネルに入れる方法は手違いが生じそうです。
自作するしかないでしょう。下記2つの機能を使えば要望を満たせそうです。これらを使ってできない場合は、
別ファイルにUVアニメーションを出力 ⇒ インポート時に統合で実現は可能でしょう。プランBは常に考えておきます。

作業見積もりは、1日4~5時間作業できたとして、4日ぐらいでしょうか。調査が必要ですので長めに見積もっています。
後で不具合や修正要望が出たら片手間でも対応できるでしょう。

Mayaのエクスポート

”FBX Extensions Software Development Kit”というものが Autodeskさんから提供されています。
これを使うと、FBXのエクスポートのタイミングでFBXファイルの加工ができます。
Maya上のユーザデータを汚すこともなさそうです。

Unityのインポート

Unityには、FBXのロード後にユーザデータと共にフックする関数が用意されています。
AssetPostprocessor.OnPostprocessGameObjectWithUserProperties(Unityマニュアル)

適当なボックスを作成してテキスト形式のFBXで出力してみます。
以下のようにFBXファイルを修正すると、Unityでキーが”my_property”、値が”test”で取得できます。

Model: 1074780944, "Model::pCube1", "Mesh" {
    Version: 232
    Properties70:  {
        P: "RotationActive", "bool", "", "",1
        P: "InheritType", "enum", "", "",1
        P: "ScalingMax", "Vector3D", "Vector", "",0,0,0
        P: "DefaultAttributeIndex", "int", "Integer", "",0
        P: "currentUVSet", "KString", "", "U", "map1"
        P: "my_property", "KString", "", "U", "test"
    }
    Shading: T
    Culling: "CullingOff"
}

Mayaのエクスポートプラグイン

SDK

Autodesk FBXから"FBX Extensions Software Development Kit"をダウンロードします。
プラグインは C++ で書きます。詳細はドキュメントとサンプルで確認していただきたいですが、
FBXSDK_DLLEXPORT void MayaExt_ExportEnd(FbxScene* pFbxScene)で出力FBXを加工できそうです。
FbxSceneを作成した後に呼ばれる関数ということですね。

また、DCCツールのプラグイン開発ではツールのバージョンアップに対応する機会が多いため、
ビルド環境を構築する環境をCMakeで作成しておきます。

作成したDLLは以下のディレクトリにコピーします。2016 Extension 2で異なっているので注意します。

2016用: C:\Program Files\Autodesk\Maya2016\bin\plug-ins\FBX
2016.5用: C:\Program Files\Autodesk\Maya2016.5\plug-ins\fbx\plug-ins\FBX

UVアニメーションの検索

まずは、UVアニメーションはどこから取得できるか、Maya上で確認します。
アトリビュートエディタを見るとplace2dTextureにセットされているようです。
作り方によるでしょうが、上手く動かない場合が出たらその都度対応でよいと思います。
アトリビュートエディタ

ハイパーグラフ上では以下のようになっています。メッシュ ⇒ シェーダ ⇒ place2dTextureの順に辿れそうです。
ハイパーグラフ

シェーダがついたメッシュノードを走査するコードは以下になります。
これが一番速い易しいと思います。

{
    for(MItDependencyNodes nodeIterator(MFn::kMesh); !nodeIterator.isDone(); nodeIterator.next()){
        MObject node = nodeIterator.thisNode();
        if(node.isNull()){
            continue;
        }
        if(!node.hasFn(MFn::kDagNode)){
            continue;
        }

        MFnMesh mesh(node);
        MFnDagNode parent(mesh.parent(0));
        parent.getPath(nodePath_);
#ifdef APPDEBUG
        log_ << Log::indent << "path " << nodePath_.fullPathName().asChar() << Log::endl;
#endif

        for(unsigned int i=0; i<mesh.instanceCount(true); ++i){
            MObjectArray shaders;
            MIntArray indices;
            mesh.getConnectedShaders(i, shaders, indices);
            for(unsigned int j=0; j<shaders.length(); ++j){
                MFnDependencyNode shaderNode(shaders[j]);
#ifdef APPDEBUG
                log_ << Log::indent << "traverse shader " << shaderNode.name().asChar() << Log::endl;
#endif
                uuids_.clear();
                traverseConnections(shaderNode);
            }
        }
    }// for(MItDependencyNodes
}

次にマテリアルの属性を辿ります。ハイパーグラフ上の矢印を辿っているイメージです。
OpenMayaでこの辺りの詳細説明を見つけることができませんでした。
参考になるかはわかりませんが、MPlugについては以下の説明をリンクしておきます。
AutoDesk Area Japan 第68回:AttributeとPlugについて語る

ハイパーグラフを見るとわかりますが巡回しているので注意が必要です。
非巡回(DAG)なのはメッシュやスキンのノードツリーのところで、シェーダのところは巡回します。
UUIDで比較していると不具合がでそうですが、現在は問題がでていないのでそのままにしています。

void traverseConnections(const MFnDependencyNode& dst)
{
    //同じuuidが出現したら巡回している
#if 201516<MAYA_API_VERSION
    std::string uuid(dst.uuid().asString().asChar());
#else
    std::string uuid(dst.name().asChar());
#endif
    if(uuids_.end() != uuids_.find(uuid)){
        return;
    }
    uuids_.insert(uuid);
#ifdef APPDEBUG
    log_ << uuid.c_str() <<  Log::endl;
#endif
    Log::ScopeIndent scoped(log_);

    //繋がっているMPlugを全検索
    MPlugArray plugs;
    dst.getConnections(plugs);
    for(unsigned int i=0; i<plugs.length(); ++i){
        MPlugArray connections;
        connections.clear();
        plugs[i].connectedTo(connections, true, false);
        for(unsigned int j=0; j<connections.length(); ++j){
            if(connections[j].node().hasFn(MFn::kDagNode)){
                continue;
            }
            //アニメーション付きのplace2dTextureならアニメーション取得
            if(connections[j].isCompound()){
                for(unsigned int k=0; k<connections[j].numChildren(); ++k){
                    MPlug childPlug = connections[j].child(k);
                    if(MAnimUtil::isAnimated(childPlug)){
#ifdef APPDEBUG
                        log_ << "animated compound: " << childPlug.name().asChar() << Log::endl;
#endif
                        if(connections[j].node().apiType() == MFn::kPlace2dTexture){
                            runPlug(childPlug);
                        }
                    }
                }
            }else if(MAnimUtil::isAnimated(connections[j])){
#ifdef APPDEBUG
                log_ << "animated: " << connections[j].node().apiTypeStr() << Log::endl;
#endif
                if(connections[j].node().apiType() == MFn::kPlace2dTexture){
                    runPlug(connections[j]);
                }
            }
            MFnDependencyNode src(connections[j].node());
            traverseConnections(src);
        }
    }
}

最後に、アニメーションを取得してFBXにカスタムのプロパティを追加します。
プロパティは確実にUnityに渡すことができる文字列にしています。
コードを貼っているだけで申し訳ないですが、 本編ではこれで最後です。

void runPlug(const MPlug& plug)
{
    Log::ScopeIndent scoped(log_);
    MObjectArray animations;
    if(!MAnimUtil::findAnimation(plug, animations)){
        return;
    }
    for(unsigned int i=0; i<animations.length(); ++i){
        MObject animCurveNode = animations[i];
        if(!animCurveNode.hasFn(MFn::kAnimCurve)){
            continue;
        }
        MFnAttribute attribute(plug.attribute());
        if(attribute.name() == "offsetU"){
            writeAnimationCurve(plug, MFnAnimCurve(animCurveNode), "xxx_decal_tu");
        }else if(attribute.name() == "offsetV"){
            writeAnimationCurve(plug, MFnAnimCurve(animCurveNode), "xxx_decal_tv");
        }
    }
}

void writeAnimationCurve(const MPlug& plug, const MFnAnimCurve& animCurve, const char* name)
{
    //----
    //中略
    //----
    // ノードのパス文字列でFbxSceneからFbxNodeを検索する
    FbxNode* node = findNode(nodePath_.fullPathName());
    if(NULL == node){
        return;
    }
    FbxProperty propCurve = FbxProperty::Create(node, FbxStringDT, animName.Buffer());
    propCurve.ModifyFlag(FbxPropertyFlags::eUserDefined, true);
    //----
    //中略
    //----
    propCurve.Set(FbxString(animString.c_str()));
}

Unityのインポートエディタ拡張

Unity側は特に悩むところはないです。Maya側で設定したキー、上の例では"xxx_decal_tu"、"xxx_decal_tv"で
アニメーションが文字列としてインポートできるため、アニメーションを復元し他スキン用のアニメーションと合成すれば完成です。
気を付ける点は、設定する以下4つのシェーダプロパティはセットで設定します。Repeatのアニメーションがなければ適当に追加します。

private const string PropertyNameDecalTexRepeatU = "material._DecalTex_ST.x";
private const string PropertyNameDecalTexRepeatV = "material._DecalTex_ST.y";
private const string PropertyNameDecalTexOffsetU = "material._DecalTex_ST.z";
private const string PropertyNameDecalTexOffsetV = "material._DecalTex_ST.w";

表情をUVアニメーションで付けたかったため、デカールテクスチャです。
何の証明にもなりませんが、以下が復元合成したアニメーションです。
Unityアニメーション

まとめ

駆け足かつコードを貼り付けただけですが、MayaのUVアニメーションをUnityへインポートする方法の説明をしました。
作業自体のまとめは以下です。

  • 良かった点
    • 要望を達成した。
    • 作業期間は概ね予定どおりで終了した。
  • 問題点
    • DLL方式のプラグインの場合、DLLを解放する仕組みがないと開発がつらい。
      • Mayaの再起動に時間がかかる。だから、1日4~5時間作業で、他の仕事と同時進行の見積もり。
  • 改善点
    • Pythonだけで解決できないか。

発展

この記事を書くにあたりコードなどを見返していて、スキニングに削れそうなキーフレームがいくつかあることに気づきました。
現在は頑張って削る必要はないのですが、将来必要に応じてインポート拡張に入れる準備もしておきましょう。
コードだけですが。

public static AnimationCurve decimate(AnimationCurve src, float tolerance=1.0e-4f, int resolution=1024)
{
    System.Diagnostics.Debug.Assert(null != src);
    System.Diagnostics.Debug.Assert(0.0f<=tolerance);
    System.Diagnostics.Debug.Assert(0<resolution);

    AnimationCurve dst = new AnimationCurve(src.keys);
    int srcIndex=1;
    int dstIndex=1;
    float invResolution = 1.0f/resolution;
    for(;;) {
        if(dst.length<3) {
            break;
        }
        if((src.length-1)<=srcIndex) {
            break;
        }
        //キーを削除しても、セクションが似ているかチェック
        Keyframe key = dst[dstIndex];
        dst.RemoveKey(dstIndex);

        float start = src[srcIndex-1].time;
        float end = src[srcIndex+1].time;
        float duration = end - start;
        bool equal = true;
        if(1.0e-5f<duration) {
            for(int i = 1; i<resolution; ++i) {
                float time = i*invResolution*duration + start;
                float v0 = dst.Evaluate(time);
                float v1 = src.Evaluate(time);
                if(tolerance<Mathf.Abs(v0-v1)) {
                    equal = false;
                    break;
                }
            }
        }
        //似ていなかったらキーを元に戻す
        if(!equal) {
            dst.AddKey(key);
            ++dstIndex;
        }
        ++srcIndex;
    }
    return dst;
}

おわりに

いかがでしたでしょうか、何かしら楽しげな仕事風景を、というつもりがただコードのコピーになってしまいました。
私のように、コードを示された方が理解が進む人がたくさんいることを願います。
プロジェクト初期はこのような作業環境準備の仕事がほとんどです。
将来チームが楽をするためにしっかりと土台を作っておきたいものです。

私達、第三研究開発本部では、このような下支えの仕事に積極的な方、もちろんゲーム作りが好きな方と
一緒に働きたいと考えています。もしご興味を持たれましたら、弊社グループ採用サイトをご確認ください。

採用情報|株式会社セガ・インタラクティブ
採用情報|株式会社セガゲームス -【SEGA Games Co., Ltd.】

Powered by はてなブログ