公式scriptをちょっといじってカッコイイアクションポーズを複数回決めちゃう方法!
はじめに
アバターに
アクションポーズをとらせることが
できたらカッコイイですよね
そしてさらに、複数のヒーロポーズを
決めることができたら
気分はかなり盛り上がります
この記事はメタバースで
スーパーヒーローになりたい皆様に向けて
発信しています(笑)
1 アクションポーズ動画
2 アクションポーズの作成
動画のアクションポーズは
『VRM Posing Desktop』という
アプリで作成しています
有料アプリですので興味がある方は調べてみて
3 アクションポーズの入手
Adobe 『Mixamo』から遊んでみたい
アクションポーズを入手することができます
Adobe 『Mixamo』からの
アクションポーズの入手は
こちらにも少し記載があります
4 インタラクトアイテムの準備
Blenderなどでモデリングしたアイテムを
Unityにアップします(Cubeでも大丈夫)
この『鬼蟲【チェイサー】』には
『追跡機能』と『効果音機能』をつけたいので
『Rigidbody』と『Movable Item』
『Item Audio Set List』の
コンポーネントを付与しています
ここからが本題です
『Humanoid Animation List』の
コンポーネントを付与して
複数のアクションポーズを登録します
そして、そのアクションポーズ毎に
任意のID『Anim1~4』を割り振ります
最後に
『Scriptable』のコンポーネントを付与します
5 script
【1】公式script
この記事にある公式さんのscriptをベースにします
ここからアクションポーズを
アニメーションという言葉に置き換えます
【2】アニメーション定義の設定
公式さんのscriptでは
アニメーションとその長さを
値を一度入れたら変更できない
『const』で定義しているので
グローバルスコープで使用できる変数
『var』に置き換えます
//アニメーション定義【cluster公式script】
//const anim = $.humanoidAnimation("dancing");//下記と差し替え
var anim = $.humanoidAnimation("Anim1");//むさし差し替え
//const len = anim.getLength();//下記と差し替え
var len = 0;//むさし差し替え
const interval = 0.1;
const inOutInterval = 0.5;
定義系は、かせーさんの記事を
参考にするとわかりやすいです
【3】スイッチの設定
公式さんのscriptは
アニメーションを1回再生するタイプなので
それを複数回、
順番に再生するscript『switch』を加えます
$.onInteract(playerHandle => {
//アニメーション指示【cluster公式script】
if (!!$.state.playerHandle) {
$.state.playerHandle.setHumanoidPose(null);
$.state.playerHandle = null;
}
$.state.playerHandle = playerHandle;
$.state.enterTime = 0;
$.state.nextAnimationTime = 0;
$.state.intervalWait = 0;
//スイッチ【きえらさんのscript】
$.state.count++;
switch($.state.count ) {
case 1:
anim = $.humanoidAnimation("Anim2");//むさし追加
len = anim.getLength();//むさし追加
break;
case 2:
anim = $.humanoidAnimation("Anim3");//むさし追加
len = anim.getLength();//むさし追加
break;
case 3:
anim = $.humanoidAnimation("Anim4");//むさし追加
len = anim.getLength();//むさし追加
break;
default:
anim = $.humanoidAnimation("Anim1");//むさし追加
len = anim.getLength();//むさし追加
$.state.clicked = false;
$.state.count = 0;
}
});
スイッチする毎に
アイテムに仕込んだ『Anim1~4』の
アニメーションとその長さを
取得して、順番に再生指示をします
『case 1』に『Anim1』ではなく『Anim2』を
『default』の後に『Anim1』を
記述しているのは
『default』を一番最初に通り
『case 1』『case 2』『case 3』を経て
再び『default』に行く順番だからです
『switch』文は、以前、きえらさんに教えてもらいました
また、鬼蟲【チェイサー】には、
きえらさんの『時間管理』scriptの力をお借りして
『クワガタムシ』の角を動かしています
本当に感謝です
【4】全体のscript
全体のscriptはこんな感じです
//アニメーション定義【cluster公式script】
//const anim = $.humanoidAnimation("dancing");//下記と差し替え
var anim = $.humanoidAnimation("Anim1");//むさし差し替え
//const len = anim.getLength();//下記と差し替え
var len = 0;//むさし差し替え
const interval = 0.1;
const inOutInterval = 0.5;
$.onInteract(playerHandle => {
//アニメーション指示【cluster公式script】
if (!!$.state.playerHandle) {
$.state.playerHandle.setHumanoidPose(null);
$.state.playerHandle = null;
}
$.state.playerHandle = playerHandle;
$.state.enterTime = 0;
$.state.nextAnimationTime = 0;
$.state.intervalWait = 0;
//スイッチ【きえらさんのscript】
$.state.count++;
switch($.state.count) {
case 1:
anim = $.humanoidAnimation("Anim2");//むさし追加
len = anim.getLength();//むさし追加
break;
case 2:
anim = $.humanoidAnimation("Anim3");//むさし追加
len = anim.getLength();//むさし追加
break;
case 3:
anim = $.humanoidAnimation("Anim4");//むさし追加
len = anim.getLength();//むさし追加
break;
default:
anim = $.humanoidAnimation("Anim1");//むさし追加
len = anim.getLength();//むさし追加
$.state.clicked = false;
$.state.count = 0;
}
});
$.onUpdate((dt,playerHandle) => {
//アニメーション指示【cluster公式script】
if (!$.state.playerHandle) {
return;
}
// 最初のポーズの場合
if ($.state.enterTime === 0) {
const opt = {};
opt.transitionSeconds = inOutInterval;
$.state.playerHandle.setHumanoidPose(anim.getSample($.state.nextAnimationTime), opt);
$.state.enterTime = $.state.enterTime + dt;
return;
}
// 最初のポーズに遷移中の場合
if ($.state.enterTime < inOutInterval) {
$.state.enterTime = $.state.enterTime + dt;
return;
}
// 最後のポーズの場合
if ($.state.nextAnimationTime > len) {
const opt = {};
opt.timeoutSeconds = 0.0;
opt.timeoutTransitionSeconds = inOutInterval;
$.state.playerHandle.setHumanoidPose(anim.getSample(len), opt);
$.state.playerHandle = null;
return;
}
// 遷移中
if ($.state.intervalWait < interval) {
$.state.intervalWait = $.state.intervalWait + dt;
return;
}
const opt = {};
opt.transitionSeconds = interval;
$.state.playerHandle.setHumanoidPose(anim.getSample($.state.nextAnimationTime), opt);
$.state.nextAnimationTime = $.state.nextAnimationTime + interval;
$.state.intervalWait = 0;
});
6 付録
鬼蟲【チェイサー】をBOOTHで配布しています
この鬼蟲【チェイサー】には
vinsさんの『プレイヤー追跡』scriptを
搭載しています
鬼蟲【チェイサー】が
どこにでもついてきてくれてどこででも
カッコイイアクションポーズを決められるのは
vinsさんのおかげなのです
本当に感謝です
【参考】付録のscript
//追跡定義【vinsさんのscript】
const force = 7;
const maxDistance = 30;
const minDistance = 3;
const rad2deg = (rad) => (rad * 180) / Math.PI;
//アニメーション定義【】
//アニメーション定義【cluster公式script】
const interval = 0.1;
const inOutInterval = 0.5;
var len = 0;//むさし追加
var anim = $.humanoidAnimation("Anim1");//むさし追加
//クワガタの角の定義【きえらさんのscript】
const obj1 = $.subNode("lefthone");
const obj2 = $.subNode("righthone");//むさし追加
const intervalhone = 3;//むさし修正+hone
const speed1 = 1;
const speed2 = 2;//むさし追加
const v3 = new Vector3(0,1,0);
const angle1 = 5;
const angle2 = -5;//むさし追加
//効果音定義【cluster公式script】
const se = $.audio("Audio1");
$.onInteract(playerHandle => {
//アニメーション指示【cluster公式script】
if (!!$.state.playerHandle) {
$.state.playerHandle.setHumanoidPose(null);
$.state.playerHandle = null;
}
$.state.playerHandle = playerHandle;
$.state.enterTime = 0;
$.state.nextAnimationTime = 0;
$.state.intervalWait = 0;
//効果音指示【cluster公式 script】
se.play();
playerHandle.setGravity(-1.635);//むさし追加
//スイッチ【きえらさんのscript】
$.state.count++;
switch($.state.count) {
case 1:
anim = $.humanoidAnimation("Anim2");//むさし追加
len = anim.getLength();//むさし追加
break;
case 2:
anim = $.humanoidAnimation("Anim3");//むさし追加
len = anim.getLength();//むさし追加
break;
case 3:
anim = $.humanoidAnimation("Anim4");//むさし追加
len = anim.getLength();//むさし追加
break;
default:
anim = $.humanoidAnimation("Anim1");//むさし追加
len = anim.getLength();//むさし追加
$.state.clicked = false;
$.state.count = 0;
}
});
$.onUpdate((dt,playerHandle) => {
//角ふり指示【きえらさんのscript】
if(!$.state.init) {
$.state.time1 = 0;
$.state.rotate = 1;
$.state.speed = speed1;
$.state.init = true;
}
let time1 = $.state.time1 + dt;
$.state.time1 = time1;
obj1.setRotation(
new Quaternion().setFromAxisAngle(
v3,
(time1 % intervalhone / intervalhone) * angle1 * $.state.speed * $.state.rotate
)
);
//角ふり指示【むさし追加】
obj2.setRotation(
new Quaternion().setFromAxisAngle(
v3,
(time1 % intervalhone / intervalhone) * angle2 * $.state.speed * $.state.rotate
)
);
//角もどし指示【きえらさんのscript】
if (interval < time1) {
$.state.time1 -= intervalhone;
$.state.rotate = -$.state.rotate;
$.state.speed = ($.state.speed == speed1) ? speed2 : speed1;
}
//追跡指示【vinsさんのscript】
let position = $.getPosition();
let players = $.getPlayersNear(position, maxDistance);
let targetPlayer = null;
let targetDistance = Infinity;
// それぞれのプレイヤーとの距離を計算し、最も近いものを探す
players.forEach((player) => {
let playerPosition = player.getPosition();
let distance = playerPosition.clone().sub(position).length();
if (distance < targetDistance) {
targetDistance = distance;
targetPlayer = player;
}
});
// 最も近いプレイヤーのPlayerHandleを保存
$.state.targetPlayer = targetPlayer;
let player = $.state.targetPlayer;
if (player == null) return;
// 追跡中のプレイヤーがログアウトなどでいなくなっていた場合
if (player.exists() == false) return;
// プレイヤーがいる方向の角度を計算してY軸回転
let direction = player.getPosition().clone().sub(position);
let angle = rad2deg(Math.atan2(direction.x, direction.z)); // atan2は弧度法での角度を返すため、度数法に変換する
let rotation = new Quaternion().setFromEulerAngles(new Vector3(0, angle, 0));
$.setRotation(rotation);
// ここからが違う
// プレイヤーと一定より離れているときに、正面方向に前進
if (direction.length() > minDistance) {
direction.normalize().multiplyScalar(force * dt);
$.setPosition(position.add(direction));
}
//アニメーション指示【cluster公式script】
if (!$.state.playerHandle) {
return;
}
// 最初のポーズの場合
if ($.state.enterTime === 0) {
const opt = {};
opt.transitionSeconds = inOutInterval;
$.state.playerHandle.setHumanoidPose(anim.getSample($.state.nextAnimationTime), opt);
$.state.enterTime = $.state.enterTime + dt;
return;
}
// 最初のポーズに遷移中の場合
if ($.state.enterTime < inOutInterval) {
$.state.enterTime = $.state.enterTime + dt;
return;
}
// 最後のポーズの場合
if ($.state.nextAnimationTime > len) {
const opt = {};
opt.timeoutSeconds = 0.0;
opt.timeoutTransitionSeconds = inOutInterval;
$.state.playerHandle.setHumanoidPose(anim.getSample(len), opt);
$.state.playerHandle = null;
return;
}
// 遷移中
if ($.state.intervalWait < interval) {
$.state.intervalWait = $.state.intervalWait + dt;
return;
}
const opt = {};
opt.transitionSeconds = interval;
$.state.playerHandle.setHumanoidPose(anim.getSample($.state.nextAnimationTime), opt);
$.state.nextAnimationTime = $.state.nextAnimationTime + interval;
$.state.intervalWait = 0;
});
7 おわりに
この記事は
clusterのプロダクトマネージャー
Smithさんの呼びかけに応じて
クリエイター同士のscriptの共有を図り
創造を加速するために執筆いたしました
script原案者の
きえらさん、cluster公式さん、
vinsさんに感謝しながら
当該scriptやアイテムをご活用いただければ幸いです