『Spark AR Studio』で作ろう!⑤
皆さん、こんにちは!
GWは楽しく過ごせましたでしょうか?
私はそれなりに楽しかったです。
では特にネタも思い浮かばないので、さっそく本題に入っていきましょう!
前回に引き続き「Script」と「Patch Editor」の連携をやっていきます。
今回は、それらを使用して何か作っていきます!「V59」を使用しております。
まずはシーンを用意します。
Part3と同じく「Plane Tracker」を使用します。「Plane Tracker」の解説はPart3でやりましたので、ここでは割愛させていただきます。
公式のサンプルから「FBX File」をお借りしました。アニメーションを追加で作っております。
マテリアルの「Shader Type」は「Flat」にしましたので「Light」は削除してます。「Flat」にすると、ライティングの影響を受けません。
「Patch Editor」にアニメーションデータを追加していきます。この「FBX File」は3つのアニメーションデータを持っています。
「Patch Editor」に3つともドラッグアンドドロップして追加します。
続いて、「FBX File」を選択し、「Inspector」パネルの「Animation」の左にある「○」をクリックして「Patch Editor」に追加します。
「Patch Editor」の何もないスペースを「右クリック」もしくは「ダブルクリック」するとメニューが表示されますので、その中から「Animation Player」を選択し、「ダブルクリック」するか、「Insert」ボタンをクリックしてパッチを追加します。同様に「Loop Animation」パッチも追加します。
「box_loop」(オレンジのパッチ)を「Animation Player」の「Animation Asset」に、「Loop Animation」の「Progress」を「Animation Player」の「Progress」に、「Animation Player」の「Animation」を「teddy_comp」(水色のパッチ)に繋ぎます。
文字にするとややこしいですね。下の画像の通りに繋ぎます。
オブジェクトがループアニメーションをするようになりました!
2秒でループしてほしいので、「Loop Animation」の「Duration」(アニメーションの長さ)は「2」にしております。
残り2つのアニメーションも同様に実装していきます。
新たに、「Animation Player」×2、「Loop Animation」「Animation」のパッチを追加します。追加したパッチを下の画像のように繋ぎます。
「open」はループしてほしくないアニメーションなので「Animation」パッチを使用、「teddy_loop」はループしてほしいので「Loop Animation」パッチを使用します。
それぞれ適切な「Duration」を入力します。
「Duration」を確認したい時は、「Assets」タブの確認したいアニメーションを選択し、「Inspector」パネルで「Length」を見れば確認できます。
パッチに違う値を入力して、アニメーションを遅くしたり早くしたりといったこともできます。
3つのアニメーションをうまいこと切り替えていきたいと思いますが、アニメーションを再生させるには、「teddy_comp」(水色のパッチ)に繋がないといけません。ですが、もう「box_loop」の「Animation Player」パッチが繋がっているので、今のままでは他のパッチを繋げることができません。
そこで「Option Picker」パッチを使用します。「Option Picker」は、「Option」番号(上から0~4の5つ)に基づいて、選択した「Type」のデータを出力するパッチです。
「Option Picker」を右クリックすると「Type」を選べますので、「Animation Data」を選択します。
パッチを繋ぎ直します。それぞれの「Animation Player」パッチの「Animation」を「Option Picker」に繋ぎます。「Option Picker」を「teddy_comp」(水色のパッチ)に繋ぎます。
今は「Option」が「0」になっていますので、0番に繋がっている「box_loop」のアニメーションがループ再生されています。ここが「1」になると「open」アニメーションが、「2」になると「teddy_loop」アニメーションが再生されるようになり、アニメーションの切り替えが実装できます。
「Option Picker」を制御する為に「Option Switch」を追加します。「Option Switch」は、0~4のインデックスを使用して、最大5つの状態を制御できます。デフォルトでは「0」を出力します。
例えば下の画像は、
- 口を開けると「Option Switch」の「Set to 0」がオンになる。
- 「Option Switch」がインデックス「0」を出力。
- 「Option Picker」が「0」を受け取る。
- 「Option」番号「0」に入力されたデータを出力。
という流れです。逆に口を閉じると「Set to 1」がオンになり、「Option」番号「1」に入力されたデータを出力します。
この例では「Option Picker」の「Type」を「Number」にしておりますので、単純に「0」「1」の値を出力しています。
こんな感じで本題の方も作っていきます。こちらは画面タップで制御したいと思います。画面タップはスクリプトで実装します。
「Assets」タブの横にある「+ボタン」をクリックして、「Create New Script」を選択し、「script」を追加します。
ここでやっと「Script」と「Patch Editor」の連携が入ります。
「Script」を選択し、「Inspector」パネルにある「From Script」の「+」をクリック、「Trigger」を選択して変数を追加します。再度「+」をクリックして「Trigger」を選択し、もう一つ変数を追加します。名前を「open_anim」「reset_anim」に変更します。
「Actions」の横にある「+」をクリック、「Read variables from script」を選択するか、「Script」を「Patch Editor」にドラッグアンドドロップするかして、パッチを作成します。
続いて、「Inspector」パネル、「To Script」の「+」をクリック、「Scalar」を選択します。名前の左にある「○」をクリックして、パッチを作成します。名前を「anim_counter」に変更します。
パッチを下の画像の通りに繋いでいきます。
「script」を開きます。既に色々書かれてますが、ややこしいので全部消して、新たに下記を入力します。
const Reactive = require('Reactive'); //Reactiveモジュール
const Touch = require('TouchGestures'); //TouchGesturesモジュール
const Patches = require("Patches"); //Patchesモジュール
//パッチエディターのスカラー値を取得
var anim_counter = Patches.getScalarValue("anim_counter");
//画面タップ
Touch.onTap().subscribe(function () {
//anim_counterが0の時
if(anim_counter.pinLastValue() == 0){
//パッチエディターに送信
Patches.setPulseValue("open_anim", Reactive.once());
}
//anim_counterが2の時
else if(anim_counter.pinLastValue() == 2){
//パッチエディターに送信
Patches.setPulseValue("reset_anim", Reactive.once());
}
});
実行してみます。
分かりにくいですが、ちゃんと画面タップでアニメーションが切り替わっています。PCで実行しているからマウスクリックですが。。。
順に解説していくと、
- スタート時は「Option Picker」の「Option」が「0」なので、0番に繋がっている「box_loop」アニメーションがループ再生されています。「anim_counter」の「Value」も「0」です。
- 画面をタップすると、スクリプトの
Touch.onTap().subscribe(function () {
が実行されます。
- 「anim_counter」の「Value」は「0」なので、以下が実行されます。
if(anim_counter.pinLastValue() == 0){ //パッチエディターに送信 Patches.setPulseValue("open_anim", Reactive.once()); }
- 「open_anim」が「Patch Editor」に通知され、「Option Switch」の「Set to 1」がオンになり、「1」を出力します。
- 「Option Picker」に「1」が入力され、「Option」が「1」になり、「open」アニメーションが再生されます。「anim_counter」の「Value」も「1」になります。
- 「open」アニメーションが終了すると、「Animation」パッチの「Completed」がオンになり、「Option Switch」の「Set to2」がオンになり、「2」を出力します。
- 「Option Picker」に「2」が入力され、「Option」が「2」になり、「teddy_loop」アニメーションがループ再生されます。「anim_counter」の「Value」も「2」になります。
- もう一度タップすると、「anim_counter」の「Value」は「2」になっているので、以下が実行されます。
else if(anim_counter.pinLastValue() == 2){ //パッチエディターに送信 Patches.setPulseValue("reset_anim", Reactive.once()); }
- 「reset_anim」が「Patch Editor」に通知され、「Option Switch」の「Set to 0」がオンになり、「0」を出力します。
- 「Option Picker」に「0」が入力され、「Option」が「0」になり、「box_loop」アニメーションがループ再生されます。「anim_counter」の「Value」も「0」になり、スタート時に戻ります。
とまあ、こんな感じです。
「Loop Animation」パッチの「Reset」は、その名の通り、オンになるとアニメーションがリセットされ、始めから再生してくれます。ですので、再生されるタイミングで「Reset」し、前のアニメーションと自然に繋がるようにしています。
「Animation」パッチは「Loop Animation」と違って、「Play」がオンにならないとアニメーションが再生されません。また、一度再生された後に「Reset」しないと、最後のフレームで止まったままになりますので、毎回、再生されるタイミングで「Reset」します。
「Completed」は、 アニメーションが完了したときにトリガーがオンになります。
「open」アニメーションが再生されている時は、画面タップで切り替えたくなかったので、「anim_counter」の「Value」を「1」にすることで回避、再生終了後に「Completed」がオンになり次に進みます。
画面タップやパン、ピンチ等を実装する時は、メニューバーの「Project」→「Edit Properties」を選択し、「Capabilities」タブにある、「Touch Gestures」の追加したい項目のチェックボックスをオンにするのを忘れないで下さい。
よくチェックするのを忘れて、「はっ?なんで動かないの?コード間違ってないでしょ!」とかになって、時間を浪費します。。。
最後にもう少し手を加えて実機で確認します。
パーティクルを足したのと、移動、回転、拡大縮小ができるようにしました。
以下、スクリプトの全文です。「Patch Editor」の方は変わっておりません。
const Reactive = require('Reactive'); //Reactiveモジュール
const Touch = require('TouchGestures'); //TouchGesturesモジュール
const Patches = require("Patches"); //Patchesモジュール
const Scene = require('Scene'); //Sceneモジュール
const Time = require('Time'); //Timeモジュール
const planeTracker = Scene.root.find("planeTracker"); //移動用
const obj_rotate = Scene.root.find("obj_rotate"); //回転、拡大縮小用
//パッチエディターのスカラー値を取得
var anim_counter = Patches.getScalarValue("anim_counter");
//パーティクル
var emitter_list = [];
for(var i = 0; i <= 2; i++){
emitter_list.push(Scene.root.find("emitter" +i));
emitter_list[i].birthrate = 0;
}
//タイマー
var Timer
//画面タップ
Touch.onTap().subscribe(function () {
//anim_counterが0の時
if(anim_counter.pinLastValue() == 0){
//パッチエディターに送信
Patches.setPulseValue("open_anim", Reactive.once());
//パーティクル放出
for(var i = 0; i <= 2; i++){
emitter_list[i].birthrate = 30;
}
//0.75秒後パーティクル停止
Timer = Time.setTimeout(function (t) {
for(var i = 0; i <= 2; i++){
emitter_list[i].birthrate = 0;
}
}, 750);
}
//anim_counterが2の時
else if(anim_counter.pinLastValue() == 2){
//パッチエディターに送信
Patches.setPulseValue("reset_anim", Reactive.once());
}
});
//パンで移動
Touch.onPan(planeTracker).subscribe(function(gesture) {
planeTracker.trackPoint(gesture.location, gesture.state);
});
//ピンチで拡大縮小
Touch.onPinch().subscribe(function(gesture) {
var lastScaleX = obj_rotate.transform.scaleX.pinLastValue();
obj_rotate.transform.scaleX = gesture.scale.mul(lastScaleX);
var lastScaleY = obj_rotate.transform.scaleY.pinLastValue();
obj_rotate.transform.scaleY = gesture.scale.mul(lastScaleY);
var lastScaleZ = obj_rotate.transform.scaleZ.pinLastValue();
obj_rotate.transform.scaleZ = gesture.scale.mul(lastScaleZ);
});
//回転、2本の指で回す
Touch.onRotate(obj_rotate).subscribe(function(gesture) {
var lastRotationY = obj_rotate.transform.rotationY.pinLastValue();
obj_rotate.transform.rotationY = gesture.rotation.mul(-1).add(lastRotationY);
});
今回は以上となります!
説明不足の所もあると思いますが、ご勘弁を!
ともあれ、何とか一か月以内に更新できました!
次回の更新も・・・一か月以内に・・・・・・無理かも。。。
(Cole!Cole!クリエイターU)