• RuntimesUnity
  • コンストレイントを C# スクリプトで動かす方法について

お世話になっております。

[状況]
現在、人型のモデルにコンストレイント「head_handle」を作成し、Spine エディタ上でそれを回転・トランスレートさせることで顔を疑似 3D のように動かすところまで出来ています。
また、このコンストレイントをアニメーションで設定し、首が動くよう C# スクリプトを実行させ、こちらも動作しております。

skeleton.AnimationState.SetAnimation(13, "idol", true);

[質問]
C# スクリプト上で直接コンストレイントの回転・トランスレートを行うためにはどのようなコードを記述すればいいでしょうか?

お手数ですが、よろしくご回答いただけますようお願いします。

  • Misaki님이 이에 답장했습니다.
    Related Discussions
    ...

    fujii こんにちは!

    実現したいことからすると、「コンストレイントを動かしたい」のではなくて「コンストレイントのターゲットボーンを動かしたい」ように思われますが、念のため両方のやり方をご紹介します。


    C#スクリプトでボーンを動かしたい場合>
    ボーンのトランスフォーム(位置、回転、スケールなど)をオーバーライドするにはまずSkeletonの FindBone() メソッドで動かしたいボーンを取得し、そのボーンの各プロパティの値を上書きします。例えばボーンの位置を変更するには以下のようなコードになります。

    Bone bone = skeletonAnimation.Skeleton.FindBone("boneName");
    bone.X = newBoneX;
    bone.Y = newBoneY;

    使用できるボーンのプロパティの一覧についてはAPIリファレンスを参照してください。

    spine-unityランタイムに付属している以下のサンプルシーンの中で、ボーンの位置をオーバーライドする方法が実演されています: Spine Examples/Getting Started/4 Object Oriented Sample

    このシーンではSpineboyの照準マーク(crosshair)ボーンの位置がマウスカーソルの位置に従うようにオーバーライドされています。詳しくは、VIEW SpineboyゲームオブジェクトにアタッチされているサンプルスクリプトSpineboyTargetController.csをご覧ください。

    また、Spine Examples/Scripts/Sample Components/内に、ボーンの位置と回転をオーバーライドするサンプルコンポーネントBoneLocalOverride.csもあります。


    C#スクリプトでコンストレイントのプロパティを変更したい場合>
    まず対応するSkeletonのfind系メソッド(FindIkConstraint()FindPathConstraint()FindTransformConstraint()のいずれか)を使ってコンストレイントを取得してください。それから各プロパティの値を上書きします。

    現在のところコンストレイントのプロパティ変更を実演しているサンプルシーンはありませんが、もし何か不明な点があれば遠慮なくご質問ください。

    Misaki様

    有難うございました! FindBone をオーバーライドする事で望むような動作が得られました。
    ただ、他のアニメーションを適用した瞬間、オーバーライドした値が(おそらく1フレーム)リセットされてしまい、キャラクターが震えてしまいました。
    原因は SetAnimation 後の、次のコードでした。

    skeleton.skeleton.SetToSetupPose();

    なぜこれを差し込んだのか確認したところ、目のアニメーション変移(目開け > 目閉じ)を実行すると

    このように目が残ってしまう問題の解決として、コードを入れたようです。

    なお、SetToSetupPose() はこちらを参考にしてコードを差し込みました。
    http://ja.esotericsoftware.com/spine-unity#%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%9D%E3%83%BC%E3%82%BA%E3%81%B8%E3%81%AE%E3%83%AA%E3%82%BB%E3%83%83%E3%83%88

    現在のまま SetToSetupPose を行うとボーン位置がリセットされる、さりとて SetToSetupPose を消すと上のような開いた目が残ってしまう、という状態です。
    この場合、目のアニメーションの作成方法を見直した方がよろしいでしょうか?

    • 別の質問スレッドを立て直した方がよろしいようであればご指示ください。
    • Misaki님이 이에 답장했습니다.

      fujii FindBoneメソッドでご希望の動作が得られたとのことで良かったです!

      現在のまま SetToSetupPose を行うとボーン位置がリセットされる、さりとて SetToSetupPose を消すと上のような開いた目が残ってしまう、という状態です。
      この場合、目のアニメーションの作成方法を見直した方がよろしいでしょうか?

      SetToSetupPose()はスケルトンの全てをセットアップポーズに戻すためのメソッドですので、もしアタッチメントの表示状態だけをセットアップポーズに戻したいようでしたらSetSlotsToSetupPose()を使用していただいた方が良いかと思います。

      ちなみに、使用されているコンポーネントはSkeletonMecanimコンポーネントでしょうか?もしそうだとしたら、SkeletonMecanimは仕様上、新しいアニメーションをセットしただけでは前に適用されていたアニメーションのタイムラインを自動的にセットアップポーズへリセットすることができないため、各アニメーションの0フレーム目にタイムラインをリセットするためのキーを追加していただく必要があります。今回のケースで言うと、目のアタッチメントの表示状態リセットするために、常にアニメーションの0フレーム目にセットアップポーズでの目の表示状態のキーを記録する必要があります。

      正確には、SkeletonMecanim用の追加パラメーターであるMecanim Translator -> Auto Resettrueに設定することで、アニメーションが終了したときに自動的にスケルトンの状態がセットアップポーズにミックスアウトされるようにも出来ますが、Auto Resetを有効にするとスムーズなトランジションが出来なくなってしまうので、スムーズなトランジションが出来るようにしつつ前のアニメーションの状態が残らないようにしたい場合はアニメーションにリセット用のキーを追加する必要があります。
      詳しくはドキュメントをご覧ください: SkeletonMecanimコンポーネント - 必要になる追加のキー

      SkeletonAnimationSkeletonGraphicコンポーネントを使用している場合は追加のキーが無くても前のアニメーションのタイムラインが残ってしまうことは通常無いはずですが、もしそのどちらかを使用しているにも関わらずこの問題が起きてしまっているようでしたら、アニメーションの設定に関わるコード全体を見せていただけませんか?
      フォーラムでは公開できない場合は、メールにて問題を再現できる最小限のUnityプロジェクトを送っていただければ詳細を確認できます: contact@esotericsoftware.com

      お手数ですがご確認いただけますと幸いです。

      Misaki様

      丁寧な解説ありがとうございます。以下、返答させていただきます。

      ・SetSlotsToSetupPose()

      承知しました。たしかにこちらを適用したところ、ボーン位置はクリアされないようになりました。

      ・SkeletonMecanim コンポーネントを使用しているか

      Unity では SkeletonMecanim コンポーネントではなく、SkeletonAnimation コンポーネントを使用しています。

      Spine Editor で製作されるデータについては別会社のデザイナーが担当しており、私の方ではお答えが難しいのですが、以下、Spine Editor で見たアニメーションデータウィンドウです。

      ・アニメーションの設定に関わるコード全体を見せていただけませんか?

      メール用に最小限のコードを書いていたところ、SetAnimation でも前の「開いた目のゴミ」が残らない(正しい)状態になったので確認したところ、問題が起きている(目のゴミが残る)コードでは SetAnimation の前に以下のコードが記述されていました。

      skeleton.AnimationState.ClearTrack(0);
      skeleton.AnimationState.SetEmptyAnimation(0, 1);
      skeleton.AnimationState.SetAnimation(0, "eye/eye_06", false);

      アニメーションを明示的に止めないと、後の SetAnimation に悪影響が出るのでは……と挟んだコードでしたが、むしろそれが悪手になっていたようです。大変失礼しました。

      深く理解が及んでおらず、色々とご迷惑をおかけしますが、今後ともよろしくお願いいたします。
      本件については「解決済み」とさせていただきます。

      • Misaki님이 이에 답장했습니다.

        fujii ご確認いただきありがとうございます!なるほど、ClearTrack()SetEmptyAnimation()を使用されていたのですね。

        すでに問題は解決されているようなので詳しい説明は不要かもしれませんが、同様の問題に遭遇した方にも向けて以下に補足します。


        まずSetEmptyAnimation()メソッドについて、これは主に、トラックに先にキューされているアニメーションを破棄してセットアップポーズに戻す※ために使用されます。※下位トラックにアニメーションが適用されている場合は下位トラックが優先されます。

        例えば、トラック0にwalk(歩行)アニメーション、トラック1にaim(銃を構える)アニメーションがセットされている状態から、walkは再生したままでaimだけを破棄したい場合、トラック1に対してSetEmptyAnimation()メソッドで空のアニメーションをセットすれば、walkaim の両方でキーが設定されているタイムラインはwalkアニメーションにミックスされ、aimにはあってもwalkにはキーが無いタイムラインはセットアップポーズに対してミックスされることにより、walkアニメーションだけが適用されている時の状態に戻ります。

        ただ、SetEmptyAnimaton()で空のアニメーションをセットする場合、指定したトラックに対してすぐに空のアニメーションをセットするので、先にキューされていたアニメーションが最後まで再生されていなくても中断することになります。もし先にキューされていたアニメーションを最後まで再生しつつ空のアニメーションにミックスアウトするようにしたい場合はAddEmptyAnimation()を使用できます。

        spineAnimationState.SetAnimation(0, "walk", true);
        spineAnimationState.SetAnimation(1, "aim", false);
        spineAnimationState.AddEmptyAnimation(1, 0.2f, 1.0f); //トラック1のaimアニメーションを、1秒のディレイの後に0.2秒かけてセットアップポーズ(または下位トラックのwalk)へミックスアウトさせる

        結果はこのようになります:


        そしてここが今回誤解があった点かと思いますが、SetAnimation()は指定したトラックにアニメーションをセットするだけでなく、ミックス完了後に先にキューされていたアニメーションを破棄します
        よって、例えばトラック0で再生されているwalkアニメーションをrunアニメーションに置き換えるなど、すでにアニメーションが再生されているトラックに対して新しいアニメーションをセットすることはSetAnimation()だけで実現できます。そして空のアニメーションのセットと同じく、もし先にキューされていたアニメーションを最後まで再生しつつ次のアニメーションにミックスアウトしたい場合はAddAnimation()が使用できます。
        これらについては以下の動画が参考になるかもしれません:

        また、SetEmptyAnimation()のもう一つの使い方として、先にSetEmptyAnimation()でトラックに空のアニメーションをセットしておいて、その後AddAnimation()でアニメーションをセットすることで、セットアップポーズからスムーズにアニメーションをミックスインできるようにするという使い方があります。例えば以下のようなコードで、セットアップポーズから2秒かけてwalkアニメーションをミックスインできます:

        spineAnimationState.SetEmptyAnimation(0, 0);
        var trackEntry = spineAnimationState.AddAnimation(0, "walk", true, 0);
        trackEntry.MixDuration = 2.0f;

        結果はこのようになります:

        もし上の例の2行目をAddAnimation()ではなくSetAnimation()にすると、先にセットした空のアニメーションをただちに置き換えてアニメーションを再生してしまうので、空のアニメーションに対するミックスが起こりません。よってSetEmptyAnimation()の次にSetAnimation()を続けると、空のアニメーションをセットした意味がなくなります。


        一方、ClearTrack()ミックスを行わずに指定したトラックの全てのアニメーションを削除し、かつスケルトンの現在のポーズをそのまま残します。なので、ClearTrack()を使ってアニメーションを削除してしまうと、今回のfujiiさんのケースのようにそれまで再生されていたアニメーションのアタッチメントの表示状態が残るなど中途半端な状態になってしまうことがあります。実際、一般的なアニメーションの実装ではClearTrack()を使うことはほとんどありません。


        今回の問題とは直接関係ないことまで含めて説明させていただきましたが、同じ問題に遭遇された方向けの補足ですのでご返信は不要です。ご参考になれば幸いです!

        とてもためになる解説、本当に有難うございます! SetEmptyAnimation() は奥が深そうですね。
        今後使わせていただく際の参考にします!

        • Misaki 님이 이 게시물을 좋아합니다..