見出し画像

公式scriptをちょっといじってカッコイイアクションポーズを複数回決めちゃう方法!

はじめに

アバターに
アクションポーズをとらせることが
できたらカッコイイですよね
そしてさらに、複数のヒーロポーズを
決めることができたら
気分はかなり盛り上がります
この記事はメタバースで
スーパーヒーローになりたい皆様に向けて
発信しています(笑)

1 アクションポーズ動画

YouTube

2 アクションポーズの作成

動画のアクションポーズは
『VRM Posing Desktop』という
アプリで作成しています
有料アプリですので興味がある方は調べてみて

3 アクションポーズの入手

Adobe 『Mixamo』から遊んでみたい
アクションポーズを入手することができます

Adobe 『Mixamo』からの
アクションポーズの入手は
こちらにも少し記載があります

4 インタラクトアイテムの準備

Blenderなどでモデリングしたアイテムを
Unityにアップします(Cubeでも大丈夫)

鬼蟲

この『鬼蟲【チェイサー】』には
『追跡機能』と『効果音機能』をつけたいので
『Rigidbody』と『Movable Item』
『Item Audio Set List』の
コンポーネントを付与しています

コンポーネント図1

ここからが本題です
『Humanoid Animation List』の
コンポーネントを付与して
複数のアクションポーズを登録します
そして、そのアクションポーズ毎に
任意のID『Anim1~4』を割り振ります

コンポーネント図2

最後に
『Scriptable』のコンポーネントを付与します

コンポーネント図3

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やアイテムをご活用いただければ幸いです