goo blog サービス終了のお知らせ 

Hack and Play

プログラミングやCG、ゲーム、コンピュータのネタを投稿していくブログ。不定期更新。

表情ジェネレータについて(使い方編)

2014年01月11日 | CG関連

先日じゃんけんゲーム http://www.unitygames.jp/game/ug7000830 を作った際に、表情をつけるのが大変だなーと思い、ふと

自動で表情生成できたらおもしろんじゃね?

という欲が出て、作ることを計画しました。ということで、できました。

できたものは、

 http://www.unitygames.jp/game/ug7000851

にあります。
この記事では使い方について解説をしたいと思います


1.初期画面について

まず、作りたい表情のキーワードを入力します。あらかじめ入れられているキーワードは「かわいい」、「なごむ」、「悲しい」、「怒ってる」が入っていますが、キーボード入力から変更が可能です。好きな表情のキーワードを入れてください。
「表情パラメータ」変更のチェックボックスについては、記事の最後に解説します

入れ終わったら、次へを押します。

 

2.表情のキーワードごとの評価と表情の再生成


 
次の画面では、いろいろメニューが出てきます。「次の表情」、「前の表情」ボタンで生成された表情を行き来できます。その右側の「再生成」ボタンは表情を作り直すことができます。
画面中央の「パラメータ表示」のチェックボックスを押すと、作られた表情の各モーフのウェイト値(0から1の範囲)を見ることができます。
キーワードの右側のスライドを動かして、この表情がキーワードにあっているかを評価します。度合いで示し、右側(プラス側)に行くほど、その表情はキーワードとマッチしている、左側(マイナス側)はぜんぜんあっていない場合になり、中央値(0付近)はどちらとも取れる、もしくは分からないときにここにあわせます。
続けて「次の表情」を押して評価をしていきます。
 
また、「3.すべて評価したら・・・」の下に「計算!」、「表情削除」「表情追加」ボタンがあります。
「表情追加」ボタンを押すと、現在の表情数の上限が5増え、新たに表情が追加されます。
「表情削除」ボタンを押すと、表情数が5減りますが、初期の表情数よりも少なくなることはありません。
 
 
3.すべての表情が評価終わったら

そして、「計算!」ボタンを押すことにより、次の画面に映ります。
この画面では、キーワードごとの評価結果から推定した表情を生成します。
各キーワードのボタンを押して、スライダを動かすと、表情が変わります。
その下のテキストボックスは各モーフのウェイト値を参照しています。
評価した表情が少ないと、値が収束されず、変な表情になりやすいと思います。
その場合は「前に戻る」ボタンをおして、前の画面に戻り、表情数を増やして評価し、再度計算しなおしてみてください。
表情数を増やして評価をすると、データが収束し、

 
表情もそれっぽいのができあがります。
他にも、

 
といった表情を、各キーワードに対して評価をしておくと、生成することができます。
いろいろ試してみてください!
 
 
4.自分で変化させるモーフを指定する。

最初の画面で、「表情パラメータ変更」のチェックボックスがあったと思いますが、このチェックボックスをオンにして、テキストボックス内の設定を書き換えることで、自分でモーフの設定を変更することができます。
※入力時はテキストフィールドで改行が反映されないので、いったんメモ帳などからコピペするのをお勧めします。
 
 
実際のパラメータを抜粋すると・・・
 
0-1: 困る;
0-1: にこり;
0-1: 怒り;
・・・
 
という風になっていると思います。
これは以下のような構成になっています。
[モーフのウェイト値設定]: [モーフ名];
[モーフのウェイト値設定]: [モーフ名1], [モーフ名2];
 
という風になっており、コロン(:)とセミコロン(;)で識別しています。これを忘れると正常に認識できないので注意してください。また、パラメータによっては複数モーフをしていする場合があり、その場合はカンマ(,)で区切ります。
モーフのウェイト値設定のタイプは合わせて7種類あり、以下のようになります
 
  • "*0,5"など  =  モーフを固定数(0.5)のにする
  • 0-1または0.1-1.0  =  モーフのウェイトを0~1のランダムな数に設定する
  • "0.1-0.8"など  =  モーフのウェイトを前の数字(0.1)から後ろの数字(0.8)までの間で生成する
  • a-b  =  ランダムな数字r(0~1)に対し、前のモーフはrの値、後ろのモーフは(1-r)の値をウェイトに設定する。(書き方は、a-b: [モーフ1], [モーフ2]; となる) 
  • a-0-b  =  ランダムな数字r(-1~1)に対し一方のモーフは+側の数値、一方のモーフは-側の数値を参照する。(一方の値が割り振られたら、もう一方は常に0になる)
  • "[0.2-0.7]"など  =  0~1のランダムな数を生成し、前の数字未満(0.2)になった場合は、すべて0になる、また後ろの数字(0.7)よりも大きい場合はすべて1になる
  • a^b  =  モーフ間のウェイトの合計値を1未満にする。(計算式は[各モーフのランダム値]/[モーフのランダム値の合計]x[0~1のランダムな数])、また、2つ以上のモーフを使うことができ、3つの場合の記述は"a^b^c: [モーフ1], [モーフ2], [モーフ3];"、4つの場合は"a^b^c^d: [モーフ1], [モーフ2], [モーフ3], [モーフ4];"となる
使用できるモーフ名は
 
眉系: 真面目、困る、にこり、怒り、上、下、前、眉頭左、眉頭右
 
目系:まばたき、笑い、ウィンク、ウィンク右、ウィンク2、ウィンク2右、なごみ、はぅ、びっくり、じとめ、キリッ、(はちゅ目、はちゅ目縦潰れ、はちゅ目横潰れ、)、星目、はぁと、瞳小、瞳縦潰れ、光下、(恐ろしい子!)、ハイライト消し、映り込み消
 
口系:あ、い、う、え、お、あ2、ん、(▲)、^、(□、ワ)、ω、(ω□)、にやり、にやり2、にっこり、ぺろっ、てへぺろ、てへぺろ2、口角上げ、口角下げ、口横広げ、歯無し上、歯無し下
 
他:(涙)、輪郭、照れ、がーん、みっぱい、(メガネ)、(LightUp、LightOff、LightBlink、LightBS)、青ざめる、(髪影消、照れ消、AL未使用)
 
などがあります。カッコつきのモーフに関しては、差し替え系や効果が分からないものなので注意が必要です。
たとえば、はちゅ目にしたい場合は、モーフのウェイトは0か1にしたいので、"[0.4999-0.5001]: はちゅ目;" とすればできますが、他の目系にウェイトをかけてると、へんな線が出たりするので、取り扱いが難かしいと思います。
 
また、口系や目系は複数のウェイトをかけると、顔面が崩れやすいので、a^bのパラメータをかけるか、0.0-0.5など、最大値を抑えるなどの工夫がいるかと思います。
 

UnityのPlaymakerとC#による状態変移の制御について

2014年01月03日 | CG関連

UnityのAssetStoreで販売している、Playmakerを購入しました。

http://www.hutonggames.com/index.html

このPlaymakerは、スクリプトを一切書かなくても、Unityでゲームロジックが組めるということを売りにしていますが、自分の場合は、普段C#でスクリプトを組んでいるので、そのメリットがちょっと見えづらいという点がありました。

いろいろいじってた結果、Playmakerはゲームロジックのひとつである、状態変移に特化しており、その代わりに非同期処理や複数条件での分岐処理(n-stateを含む)を書くのが難しいことが分かりました。

またC#側ではメリットデメリットが逆転している(状態変移を書くと、Update関数内が変移チェック変数でごちゃごちゃする)ので、これらを使い分けると面白いのではないかと考えたわけです。

 

というわけで、自分の勉強もかねて、C#とPlaymaker両方を使って、状態変移の制御を行う簡単なやり方について考えてみました。
内容の理解度に関しては、一通りPlaymakerを触っている人が対象になります。
アルゴリズムについてですが、

  1. PlaymakerからGameObject(Prefab)のインスタンスを作成する。(1秒ごとに生成するループ)
  2. 生成されたGameObjectは一定時間(ランダム性あり)が来たら自己消滅する。その際にPlaymaker側に消滅したことを知らせる(カウンタを増やす)
  3. Playmaker側は一定数の消滅が来たら、生成ループから外れて、自己消滅する

という構成になっています。

まずはPlaymaker側の設定です。適当な空のGameObjectをHierarchyに追加し名前を"CubeControllerFSM"にし、PlaymakerのFSMを作ります。また、ついでにもうひとつ、"CubeOrigin"という名前の空のGameObjectを作っておきます。
シーンに初めてFSMを作った際の名前は、"FSM"になっていますが、それ以降は変わってきますので、この辺はスクリプトを制御する際に他のFSMと混同しないよう、FSMの名前は意識しておいたほうが良いと思います。

一つ目のActionはCreate Objectになります。このActionは実行時にGameObject(Prefab可)のインスタンスを作成する機能を持っています。Game Objectの箇所には生成したいGameObjectを、Spawn Pointには生成する原点となるGameObjectを設定します。
Store ObjectにはPlaymakerのVariablesの変数と紐付けできますが、増減するものに対しては使いづらいので、今回は無視します。
スクリーンショットでは既にCubeというGameObjectがつけられていますが、後でそのPrefabについて説明します。

二つ目のActionはInt Compareです。これはVariables中のint型の変数が、ある一定の数値未満(Less than)、同値(Equal)、それよりも大きい(Greater than)に、処理を分岐する機能です。

ここでは、IsNotOVERとIsOVERというカスタムEventを作っていますが、ラベル付けの役割だけですので、名前は何でも良いです。
IsNotOVERのほうは、WaitのActionに紐付けされ、IsOVERのほうはDestroy Selfに紐付けされています。
今回は変数にNumOfDestroyという変数を作っています。Variablesタブから変数を作っておきましょう。作ったらInteger1にNumOfDestroyをセットしましょう。

三つ目のActionはWaitです。あまり説明することは少ないですが、Waitは結構重要で、他のActionのLogicのうち、*** Changed系のActionがある場合は、これをかまさないとうまく動かないケースがあります。また、今回はPlaymakerが無駄にインスタンスを生成しないように、Waitを入れています。

このActionは一つ目のCreate ObjectのActionと紐付けされており、ループ処理が行われます。

最後のActionは、Destroy Selfになります。C#で言うDestroy(gameObject)を呼ぶのと同じ機能です。これはそのままなので、画像は省略します。

さて、今度はC#のスクリプト作りです。

名前は何でも良いですが、この記事では"FSMListner"というスクリプトにしています。コードは以下のとおりです。スクリプト自体は、上記の2の機能そのものです。

using UnityEngine;
using System.Collections;
using HutongGames.PlayMaker;

public class FSMListner : MonoBehaviour {

    PlayMakerFSM fsm;
    public string PlaymakerObject;
    public string FsmName;
    public string ParamName;

    float lifetime;

    // Use this for initialization
    void Start () {
        fsm = GameObject.Find(PlaymakerObject).GetComponent<PlayMakerFSM>();
        if(fsm == null && fsm.name != FsmName) {
            Debug.Log("FSMListner: " + PlaymakerObject + " is not found!");
        }
        lifetime = 1.0f + Random.value;
        Vector3 vec = new Vector3();
        vec.x = (Random.value - 0.5f) * 2f;
        vec.y = (Random.value - 0.5f) * 2f;
        gameObject.transform.position = vec;
    }
    
    // Update is called once per frame
    void Update () {
        lifetime = lifetime - Time.deltaTime;
        if(lifetime < 0.0f) {
            if(fsm != null) {
                fsm.FsmVariables.GetFsmInt(ParamName).Value += 1;
            }
            Destroy(gameObject);
        }
    }
}

Start時に自己消滅までの時間を設定し(lifetimeがそれです)、生成位置をランダムに設定しています。また、

fsm = GameObject.Find(PlaymakerObject).GetComponent<PlayMakerFSM>();

の行で、PlaymakerFSMクラス(Playmakerの実体のクラス)から、特定のFSMを抽出できます。
Update関数内では、時間経過による処理のプログラムを書いています。Destroyを呼ぶ前の行の、

fsm.FsmVariables.GetFsmInt(ParamName).Value += 1;

で、FSM内のVariablesにアクセスし、値を書き換える(インクリメント)しています。

スクリプトは保存して、Assetにおいておきましょう。

次にCubeのPrefabとHierarchyについてです。

HierarchyにCubeのGameObjectを作成し、そこに"FSMListener"のスクリプトをD&Dし、CubeをAssetに追加し、Prefab化しておきます。一度Prefab化が終わっていれば、HierarchyからCubeを削除しても大丈夫です。
Inspectorの"FSMListener"の項目に、public変数がありますので、それぞれ、

Playmaker Objectには、"CubeControllerFSM"、
Fsm Nameには、"FSM"、
Param Nameには、"NumOfDestroy"

を忘れずに入れておきましょう、すべて文字列になっていますので、手打ちで文字列を
入力してください。

これが終わったら、再度CubeのPrefabを、Playmakerの一つ目のCreate ObjectのGameObjectにセットしておきましょう。

終わったら、実行して、どんな動作をするか確認しましょう。

実行中はCube(Clone)が生成され、消えては生成され、という具合になり、最後はCubeがすべて消え、CubeControllerFSMも消滅するという挙動になると思います。

さて、今回はPlaymakerとC#両方の操作で互いに影響するコードについて解説をしました。
今後もこのように、互いのメリットデメリットを補完することにより、より楽しいUnityコーディングができるようにいろいろ考えていこうと思います。