あかすくぱるふぇ

同人サークル「あかすくぱるふぇ」のブログです。

PMX(MMDモデル)ファイルビューワーの制作で必要になったので。

PMXファイル内の文字列はUTF-8とUTF-16のどちらかで書かれている。
どちらで書かれているかはヘッダで判断できる。
つまり、ヘッダに応じて、UTF-8とUTF-16のどちらで読むかの場合分けが必要となる。

その違いを吸収するための関数を作ってみた。
typedef std::basic_string<TCHAR, std::char_traits<TCHAR>, std::allocator<TCHAR> > tstring;

tstring read(std::ifstream& fileStream, const int size)
{
tstring str;

WCHAR wcharTmp[MAX_CHAR_LENGTH];
fileStream.read(reinterpret_cast<char*>(&wcharTmp), size);

// 終端文字付きの文字列を得るためにstringを利用。
// stringは終端文字を持たない代わりに、size()関数で文字列の長さを保持する。
// そして、c_str()で終端文字付の文字列配列を返す。
std::wstring wstr = wcharTmp;
wstr.resize(size / 2);
#ifdef _UNICODE
str = wstr;
#else
char charTmp[MAX_CHAR_LENGTH];
int multiByteSize = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, wcharTmp, -1, charTmp, multiByteSize, NULL, NULL);
str = charTmp;
str.resize(multiByteSize - 1);
#endif

return str;
}
参考ページ
http://d.hatena.ne.jp/sirocco/20130418/1366253252

個人的な備忘録として。

Visual Studioでは、Unicodeとマルチバイト文字に両対応するためのTCHARという型がある。
文字列を扱う時には基本的にTCHARを使うことになるが、

"1byteずつファイルから読み込みたい"
といった場合に以下のようにしてはいけない。

TCHAR hoge;
ifs.read(hoge, sizeof(TCHAR));
こうしてしまうと、
"マルチバイト文字セットを使用する場合は1バイトずつ、Unicode文字セットを使用する場合は2バイトずつ読み込む"
といった処理になってしまう。

1バイトずつ読み込みたいなら使用文字セットに関わらず、charを使う必要がある。

MMD4MecanimはUnityでMMDを扱うためのアセットです。
http://stereoarts.jp/

今回はこのMMD4Mecanimを使って、俺妹iP的なものを作ってみました。
画面上のキャラクタに触れると反応を返します。
https://twitter.com/kano_sawa/status/726865536282284032?lang=ja

MMDモデルとモーションは以下のものをお借りしました。
http://www.nicovideo.jp/watch/sm25194175
http://www.nicovideo.jp/watch/sm18000763
http://www.nicovideo.jp/watch/sm21073414

まずは公式のチュートリアルを参考にMMDモデルとモーションをFBXファイルに変換し、Animator Controllerを登録します。
http://stereoarts.jp/MMD4Mecanim%20%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%88%E5%9F%BA%E6%9C%AC%E7%B7%A8%EF%BC%89.pdf

ここで、Animator Controllerは以下のように、触れていない時のモーションが無限ループされるようにしておきます。
Animator

次に、以下のサイトの"モデルのボーンに対してUnityの剛体を追加する"を参考に、Colliderを追加します。
http://qiita.com/mkt_/items/638516e12eec2e5419fd

そして、触れた箇所を判定して個別の反応(モーション)を返すために、Colliderにタグを設定します。
下図では頭部のColliderにHeadタグを設定しています。
タグ

同様に、胸部のColliderにBreastタグを設定します。
タグ2

続いて、ユーザーが触れた部位に応じてアニメーションを切り替えるスクリプトを作成します。
スクリプトは以下のサイトを参考に作成しました。
http://qiita.com/mkt_/items/638516e12eec2e5419fd
using UnityEngine;
using System.Collections;

public class Tap : MonoBehaviour {

float timer = 0.0f; // 元のアニメーションに戻すためのタイマー
const float distance = 1000.0f; // 光線を飛ばす距離
MMD4MecanimModel model = null;

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

// 元のアニメーションに戻すためのタイマー
if (timer > 0.0f)
{
timer -= Time.deltaTime;
if (timer < 0.0f)
{
timer = 0.0f;
model.GetComponent<Animator>().CrossFade("立01表情無し.vmd", 0.1f);
}
}

if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;

// 光線がヒットしたら
if (Physics.Raycast(ray, out hit, distance))
{
// MMDのアニメーションを取得する
Collider collider = hit.collider;
model = collider.GetComponentInParent<MMD4MecanimModel>();

// 触れた部位によって設定するvmdファイルを切り替える
switch (collider.tag)
{
case "Head":
SetCrossFade(model, "えへへへへ_.vmd");
break;
case "Breast":
SetCrossFade(model, "ふんっ!_ikari08.vmd");
break;
default:
break;
}
}
}
}

void SetCrossFade(MMD4MecanimModel model, string vmdName)
{
model.GetComponent<Animator>().CrossFade(vmdName, 0.1f);
timer = 3.0f;
}
}
以上で完成です。
以下、補足です。

・補足1
2016/05/06現在のMMD4Mecanim最新版であるBeta_20150821はモバイルに対応していません。
またMMD4Mecanim公式サイトで旧版の配布は行っていません。
スマホなどで動かしたい場合は書籍についてくる旧版などを利用する必要があります。

・補足2
今回利用させていただいたモーションは、0フレーム目に棒立ちのポーズが入っているため、
そのまま使うと、一瞬だけ棒立ちになってからモーションを開始して不自然に見えます。
自然に見せるためには、VMD Editorなどを使って事前にモーションを編集しておく必要があります。

↑このページのトップヘ