pixi.jsとLive2Dで雪降るシーンを作る

この記事は[WebGL Advent Calendar 2016](http://qiita.com/advent-calendar/2016/webgl)の24日目の記事です。
たまたま空いてたのでWebGLの軽いネタを書こうかと。

pixi.jsのLive2Dプラグインを作ってたら、すでに作ってる人がいたので使ってみました。
中身のソース見てたら自分と同じく、オフスクリーンにLive2D描画しSpriteのテクスチャとしてました。
pixi.jsを使う事でお手軽に雪降るシーンが作れてクリスマスっぽくできましたっ！
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">pixi.js(WebGL)と組み合わせてクリスマスシーンできた！ <a href="https://t.co/e5jvZcz4dK">pic.twitter.com/e5jvZcz4dK</a></p>&mdash; naotaro (@naotaro0123) <a href="https://twitter.com/naotaro0123/status/812498990566547458">2016年12月24日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# 開発環境
[Live2D WebGL SDK2.1](http://sites.cybernoids.jp/cubism-sdk2/webgl2-1)
[Pixi.js v4.1.1](http://www.pixijs.com/)
[Pixi-live2dプラグイン](https://github.com/avgjs/pixi-live2d)

# 導入方法
[Pixi-live2d](https://www.npmjs.com/package/pixi-live2d)はnpmにも公開されてるので、パチパチとnpmコマンドを打っていけば簡単に導入できます。

#### １）ターミナルを開いて、まずは作業フォルダを作ります

```bash:
# npm initコマンドで怒られないように大文字は使わない
mkdir pixi_live2d
cd pixi_live2d
```

#### ２）npmコマンドで必要なライブラリをインストール
pixi.jsはv4.2.1以上だと仕様が変わったようで、activeTexture切り替えエラーで描画できなかった...。
そのため、pixi.jsはver.4.1.1をインストールします

```bash:
# initコマンド実行後に色々と聞かれるがEnter連打
npm init
# pixi.jsをインストール
npm install pixi.js@4.1.1
# pixi-live2dをインストール
npm install pixi-live2d
# webpackをインストール
npm install webpack
```

#### ３）Live2D SDKからLive2d.min.jsをコピーしてくる

```bash:
# libフォルダを作り、live2d.min.jsをおく
mkdir lib
```
<img width="319" alt="スクリーンショット 2016-12-24 13.38.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/04dc99c8-b3c3-3bf8-50b7-5faed9682957.png">

#### ４）Live2D SDKからLive2Dモデルをコピーしてくる

```bash:
# assetsフォルダを作り、Live2Dモデルファイルをおく
mkdir assets
```
<img width="467" alt="スクリーンショット 2016-12-24 13.38.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/1c0b9898-4ed2-0208-cf9f-8c100a541854.png">

#### ５）webpackでライブラリのjsをまとめる

```bash:
# webpackの設定ファイルを作る
touch webpack.config.js
```

中身は以下の通りでwebpackコマンド実行後にbundle.jsが出力されます

```json:webpack.config.js
module.exports = {
    entry :{
        lib: ['./lib/live2d.min.js',
              './node_modules/pixi.js/bin/pixi.min.js',
              './node_modules/pixi-live2d/dist/index.js',
             ]
    },
    output : {
        filename : './lib/bundle.js'
    }
};
```

```bash:
# webpackコマンド実行し、bindle.jsを生成
webpack
```

#### ６）index.htmlとindex.jsを作成し、以下のように記述する。
このあたりはpixi-live2dの[サンプル](https://github.com/avgjs/pixi-live2d/tree/master/example)とほぼ同じです。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Live2D Core Library -->
  <!--<script src="lib/live2d.min.js"></script>-->
  <!--<script src="node_modules/pixi.js/bin/pixi.min.js"></script>-->
  <!--<script src="node_modules/pixi-live2d/dist/index.js"></script>-->
  <script src="lib/bundle.js"></script>
  <title>pixi-live2d</title>
</head>
<body>
<div id="app"></div>
<!-- User Script -->
<script src="index.js"></script>
</body>
</html>
```

#### ７）index.jsを作成し、以下のようにする
これもpixi-live2dの[サンプル](https://github.com/avgjs/pixi-live2d/tree/master/example)のほぼコピペ

```js:index.js
// レンダラーの生成
const renderer = new PIXI.WebGLRenderer(800, 600);
document.getElementById('app').appendChild(renderer.view);
// ステージの生成
const stage = new PIXI.Container();

// Live2Dのモデル定義
const modelHaru = {
  "type":"Live2D Model Setting",
  "name":"haru",
  "model":"assets/haru/haru_01.moc",
  "textures":
  [
    "assets/haru/haru_01.1024/texture_00.png",
    "assets/haru/haru_01.1024/texture_01.png",
    "assets/haru/haru_01.1024/texture_02.png"
  ],
  "physics":"assets/haru/haru.physics.json",
  "pose":"assets/haru/haru.pose.json",
  "expressions":
  [
    {"name":"f01","file":"assets/haru/expressions/f01.exp.json"},
    {"name":"f02","file":"assets/haru/expressions/f02.exp.json"},
    {"name":"f03","file":"assets/haru/expressions/f03.exp.json"},
    {"name":"f04","file":"assets/haru/expressions/f04.exp.json"},
    {"name":"f05","file":"assets/haru/expressions/f05.exp.json"},
    {"name":"f06","file":"assets/haru/expressions/f06.exp.json"},
    {"name":"f07","file":"assets/haru/expressions/f07.exp.json"},
    {"name":"f08","file":"assets/haru/expressions/f08.exp.json"}
  ],
  "layout":
  {
    "center_x": 0,
    "y": 1.2,
    "width": 2.9
  },
  "hit_areas":
  [
    {"name":"head", "id":"D_REF.HEAD"},
    {"name":"body", "id":"D_REF.BODY"}
  ],
  "motions":
  {
    "idle":
    [
      {"file":"assets/haru/motions/idle_00.mtn" ,"fade_in":2000, "fade_out":2000},
      {"file":"assets/haru/motions/idle_01.mtn" ,"fade_in":2000, "fade_out":2000},
      {"file":"assets/haru/motions/idle_02.mtn" ,"fade_in":2000, "fade_out":2000}
    ],
    "tap_body":
    [
      { "file":"assets/haru/motions/tapBody_00.mtn" , "sound":"assets/haru/sounds/tapBody_00.mp3"},
      { "file":"assets/haru/motions/tapBody_01.mtn" , "sound":"assets/haru/sounds/tapBody_01.mp3"},
      { "file":"assets/haru/motions/tapBody_02.mtn" , "sound":"assets/haru/sounds/tapBody_02.mp3"}
    ],
    "pinch_in":
    [
      { "file":"assets/haru/motions/pinchIn_00.mtn", "sound":"assets/haru/sounds/pinchIn_00.mp3" }
    ],
    "pinch_out":
    [
      { "file":"assets/haru/motions/pinchOut_00.mtn", "sound":"assets/haru/sounds/pinchOut_00.mp3" }
    ],
    "shake":
    [
      { "file":"assets/haru/motions/shake_00.mtn", "sound":"assets/haru/sounds/shake_00.mp3","fade_in":500 }
    ],
    "flick_head":
    [
      { "file":"assets/haru/motions/flickHead_00.mtn", "sound":"assets/haru/sounds/flickHead_00.mp3" }
    ]
  }
};

// Live2DのSpriteを生成
const live2dSprite = new PIXI.Live2DSprite(modelHaru, {
  debugLog: false,
  randomMotion: false,
  eyeBlink: false,
});
stage.addChild(live2dSprite);

// 位置調整
live2dSprite.x = -105;
// live2dSprite.y = -150;
// スケール
live2dSprite.adjustScale(0,0,0.7);
// 移動
live2dSprite.adjustTranslate(0.4, 0);
// ランダムモーション
live2dSprite.startRandomMotion('idle');
// マウス操作
live2dSprite.on('mousemove', (evt) => {
  const point = evt.data.global;
  live2dSprite.setViewPoint(point.x, point.y);
});

function animate() {
    requestAnimationFrame(animate);
    renderer.render(stage);
}
animate();
```

８）実行結果
これでpixi.js上にLive2Dが描画できました！
<img width="400" alt="スクリーンショット 2016-12-24 13.41.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/488087bf-57a9-a34e-afe8-6ad088889c84.png">

詳しいAPIの使い方は[pixi-live2d API](https://github.com/avgjs/pixi-live2d/blob/master/docs/API.md)を見ると良いです。
目パチやリップシンクなどtrue/falseを渡すだけでON/OFFできるようですね。

# ソースコード
以下のサイトを参考に雪を降らせたソースもgithubにアップしておきました。
<img width="500" alt="スクリーンショット 2016-12-24 14.15.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/738cf1be-239c-ef98-cfff-8bcc2b51220d.png">
・[pixi.jsを使って雪を降らせてみた。](http://nxpg.net/blog/?p=5669)
・[github - Live2D-Pixijs-Test](https://github.com/naotaro0123/Live2D-Pixijs-Sample)

pixi.jsは、stageごとにブラーフィルタかけられるのは便利ですね。
これで簡単にインタラクティブなサイトが作れそうな気がします！

では、よいクリスマスをお過ごしください！