CubismJsで時間当てゲームを作成する

今回は Cubism SDK for JavaScript (以下cubism-js)を使って、簡単なプログラムのゲームとLive2Dのパラメータを連動させてみます。
cubism-jsはLive2Dの公式(github)からダウンロードすることができます。

※現在(2020/11)は、Cubism SDK for Web に移行したためCubism SDK for JavaScriptをダウンロードすることはできません。Live2DをWebブラウザで動作させる場合には、Cubism SDK for Webを使用してください。

以下の記事で導入までの流れを紹介しています。

Cubism SDK for Web を使ってみる
この記事では Cubism SDK for Web の基本的な動作(モデルの表示とアニメーション)について確認しています。
Cubism SDK for Web
WebGLで実装されたSDKです。
主要なWebブラウザに対応しているため、幅広い環境で動作させることが可能です。
ソースコードはTypeScriptで書かれており、トランスパイルを行うことでJavaScriptから扱うことも可能です。

使用するイラスト

今回の作業で使用するイラストです。

使用するイラスト
© Unity Technologies Japan/UCL

cubism-jsで使用するパラメータの準備

前回の記事「cubism-jsによる視線追従」で作成したパラメータに加えて「口・眉・まつげ」を動かすパラメータを準備します。

ParamMouthOpenX・Y

ParamMouthOpenXで口の変形、ParamMouthOpenYで口の開閉を設定します。パラメータの作り方は「Live2Dで口の開閉を実装する」で紹介しています。

ParamBrowLForm・RForm、ParamEyeLash

ParamBrowLFormで左眉の変形、ParamBrowRFormで右眉の変形、ParamEyeLashでまつげの変形を設定します。パラメータの作り方は「Live2Dでキャラクターに表情をつける」で紹介しています。

cubism-jsの設定

前回の記事「cubism-jsによる視線追従」に簡単なゲーム要素を加える形で作成していきます。

1. index.htmlの編集

wwwrootフォルダの index.html を編集して、作成するページをリストに追加します。

index.html
<!-- Menu Area -->
<li data-src="pixiadd3.html"  onclick="onLinkClick(this);" class=""> Add Data 3 </li>

ページの追加

2. pixiadd3.htmlの作成

examplesフォルダのpixibasic.htmlをコピーして、名前を pixiadd3.html (index.htmlのdata-src=””に対応した名前) とします。
Include exampleの部分に作成するファイル名を入力します。

pixiadd3.html
<!-- Include example -->
<script src="../js/pixi3seconds.js"></script>

今回はUIを作成するので、下記コードを追加します。

pixiadd3.html
<!-- Include Pixi-ui.-->
<script src="https://cdn.rawgit.com/pixijs/pixi-ui/bceac364/bin/pixi-ui.min.js"></script>

3. pixi3seconds.jsの作成

今回追加するゲーム要素は「ぴったり3秒を目指す」というものです。
3秒を目指した時に生じる誤差に応じてキャラクターにパラメータを設定します。

前回作成した pixilookatmouse2.js をコピーして、名前を pixi3seconds.js とします。
テクスチャや組込み用ファイル等は前回のものをそのまま使用します。

UIの作成-1

3秒を測定するための「スタートボタン」と「ストップボタン」を作成します。

pixi3seconds.js
var createPIXIUI = function () {
  var uiStage = new PIXI.UI.Stage(1280, 720);
    app.stage.addChild(uiStage);
  var uiContainer = new PIXI.UI.Container("30%", "100%");
    uiStage.addChild(uiContainer);
  var cb_button = PIXI.Texture.fromImage("../assets/UI/grey_button01.png");
  var textStyle = { fill: ['#000000', '#000000'], fontSize: 24, fontFamily: 'Calibri', fontWeight: 'bold'};

  var start = new PIXI.Sprite(cb_button);
    start.x = 40;
    start.y = 80;
    start.interactive = true;
    start.buttonMode = true;
    app.stage.addChild(start);
  var textStart = new PIXI.Text('Start', textStyle);
    textStart.x = 110;
    textStart.y = 90;
    app.stage.addChild(textStart);

  var stop = new PIXI.Sprite(cb_button);
    stop.x = 40;
    stop.y = 510;
    stop.interactive = true;
    stop.buttonMode = true;
    app.stage.addChild(stop);
  var textStop = new PIXI.Text('Stop', textStyle);
    textStop.x = 115;
    textStop.y = 520;
    app.stage.addChild(textStop);
};
createPIXIUI();

各ボタンに new PIXI.Sprite でテクスチャを設定して、.x .y で位置を設定します。
ボタンのテキスト(Start・Stop)は new PIXI.Text で表示しています。

ボタンの配置

UIの作成-2

同様に target time(3.000) と、スタートボタンを押してからストップボタンを押すまでの時間を表示する result を作成します。

pixi3seconds.js
var textStyleTime = { fill: ['#ffffff', '#ffffff'], fontSize: 68, fontFamily: 'Calibri', fontWeight: 'bold' };
var textStyle2 = { fill: ['#ffffff', '#ffffff'], fontSize: 20, fontFamily: 'Calibri', fontWeight: 'normal' };

var target = new PIXI.UI.Text("3.000", textStyleTime);
  target.top = 230;
  target.left = 60;
  uiContainer.addChild(target);

var target_t = new PIXI.UI.Text("target time", textStyle2);
  target_t.x = 60;
  target_t.y = 215;
  uiContainer.addChild(target_t);

var result = new PIXI.Text('0.000', textStyleTime);
  result.x = 60;
  result.y = 320;
  app.stage.addChild(result);

var result_t = new PIXI.UI.Text("result", textStyle2);
  result_t.x = 60;
  result_t.y = 310;
  uiContainer.addChild(result_t);

textStyleでフォントの色やサイズなどを設定しています。

目標時間とリザルト

時間の取得

ゲーム要素については他に解説しているサイトがあるので省略します。

レイヤーとパラメータの追加

modelにレイヤーを追加します。

pixi3seconds.js
.addAnimatorLayer("lipsync", LIVE2DCUBISMFRAMEWORK.BuiltinAnimationBlenders.OVERRIDE, 1)

追加したレイヤーに「口・眉・まつげ」を動かすパラメータを設定します。

pixi3seconds.js
var onLipsync = function () {
  emptyAnimation.evaluate = function (time, weight, blend, target) {
    var param_mouth_open_y = target.parameters.ids.indexOf("PARAM_MOUTH_OPEN_Y");
    var param_mouth_open_x = target.parameters.ids.indexOf("PARAM_MOUTH_OPEN_X");
    var param_brow_l_form = target.parameters.ids.indexOf("PARAM_BROW_L_FORM");
    var param_brow_r_form = target.parameters.ids.indexOf("PARAM_BROW_R_FORM");
    var param_eye_lash = target.parameters.ids.indexOf("PARAM_EYE_LASH");
    if (param_mouth_open_y < 0) {
        param_mouth_open_y = model.parameters.ids.indexOf("ParamMouthOpenY");
    }
    if (param_mouth_open_x < 0) {
        param_mouth_open_x = model.parameters.ids.indexOf("ParamMouthOpenX");
    }
    if (param_brow_l_form < 0) {
        param_brow_l_form = model.parameters.ids.indexOf("ParamBrowLForm");
    }
    if (param_brow_r_form < 0) {
        param_brow_r_form = model.parameters.ids.indexOf("ParamBrowRForm");
    }
    if (param_eye_lash < 0) {
        param_eye_lash = model.parameters.ids.indexOf("ParamEyeLash");
    }
    if (param_mouth_open_y >= 0) {
        target.parameters.values[param_mouth_open_y] =
            blend(target.parameters.values[param_mouth_open_y], param_my, 0, weight);
    }
    if (param_mouth_open_x >= 0) {
        target.parameters.values[param_mouth_open_x] =
            blend(target.parameters.values[param_mouth_open_x], param_mx, 0, weight);
    }
    if (param_brow_l_form >= 0) {
        target.parameters.values[param_brow_l_form] =
            blend(target.parameters.values[param_brow_l_form], param_l, 0, weight);
    }
    if (param_brow_r_form >= 0) {
        target.parameters.values[param_brow_r_form] =
            blend(target.parameters.values[param_brow_r_form], param_r, 0, weight);
    }
    if (param_eye_lash >= 0) {
        target.parameters.values[param_eye_lash] =
            blend(target.parameters.values[param_eye_lash], param_e, 0, weight);
    }
  };
  model.animator.getLayer("lipsync").play(emptyAnimation);
};

パラメータ( param_my, param_mx, param_l, param_r, param_e )の初期値は事前に設定しておく必要があります。

止めた時間とパラメータの連携

スタートをクリックしてからストップをクリックするまでの時間(resultTime)から、target time(3.000)を引いて、目標時間との誤差(rema)を算出します。

絶対値(Math.abs)を使って、誤差が0.1秒未満の時、 誤差が0.5秒未満の時、誤差が0.5秒以上の時の「口・眉・まつげ」のパラメータを設定しています。

pixi3seconds.js
if (Math.abs(rema) < 0.1) {
  param_my = -1;
  param_mx = 1;
  param_l = 0;
  param_r = 0;
  param_e = 0.6;
  onLipsync();
} else if (Math.abs(rema) < 0.5) {
  param_my = 0.3;
  param_mx = -0.7;
  param_l = 0.5;
  param_r = 0.5;
  param_e = 0.2;
  onLipsync();
} else {
  param_my = -0.3;
  param_mx = -1;
  param_l = 0;
  param_r = 0;
  param_e = 0;
  onLipsync();
}

スタートボタンをクリックした時の動作

キャラクターの表情をリセットするために、スタートボタンをクリックした際のイベントにも、パラメータを設定します。

pixi3seconds.js
start.on('pointerdown', function () {
  this.alpha = 0.7;
  param_my = -1;
  param_mx = 0.5;
  param_l = -0.2;
  param_r = -0.2;
  param_e = 0;
  onLipsync();
});

this.alpha でボタンをクリックした時の透明度を設定しています。

Cubism 3 JS Sample

今回の作業で作成したサンプルのキャプチャを載せます。resultTimeに応じてキャラクターの表情が変化します。
ブラウザはEdgeで動作を確認しています。

クリックでGIFアニメーションが再生されます
sample_gif

タイトルとURLをコピーしました