ブログの女の子を作る #36 パーティクルとコライダーの風魔法を当てる【後編】【Unity】
前回の記事で、風魔法を作ってコントローラから撃てるようになりました。
後編となる今回は、モーションと表情を作って、風魔法が当たった時のプログラムを作ります。
目次
(1) Blenderの「シェイプキー」で表情を作る
まずは、風魔法がキャラクターに当たった時の表情を「Blenderのシェイプキー」で作ります。
以前の記事で、まばたきとジト目を作りました。
今回は、困ったような恥ずかしいような表情にしてみます。
頂点を移動して表情を作っていく
体や目などのオブジェクトを修正して、表情を作っていきます。
まずは、眉毛の両サイドを上げます。
次に、視線を下げます。
顎部分の頂点を選択して、首の根本辺りを軸にして回転すると、
口が開きました!
別のオブジェクトで歯を作る
とりあえず作ってみましたが、口の形がおかしいのと歯が見えないのが違和感あります。
口の形は「頂点の移動」でいけそうですが、歯は「頂点を追加」する必要があります。
ですが、参考動画でも言われていたように、シェイプキーを作ってる時は「頂点を追加・削除」するとおかしくなってしまいます。
回避策として、いまシェイプキーを作っているオブジェクト(今回は「体」)に頂点を増やすのではなく、別のオブジェクトを新規に作成し、歯のようなものを作ることにしました。
やはり歯があった方が良い感じです。これまでずっと口を閉じてたので気づきませんでしたが、口を開けると表情が豊かになりますね。
涙をシェイプキーで作る
ついでに、漫画的な「涙」も白いオブジェクトで作ってみました。
最初から作るのが面倒だったので、目のハイライトのオブジェクトを複製し、涙の形になるように変形してみました。
上目遣いも作る
最後に上目遣いのシェイプキーも作りました。いろいろ作っておくと、シェイプキーを組み合わせることでいろんな表情が作れそうですね。
これで表情のシェイプキーができました!
(2) 体のテクスチャで「頬染めバージョン」を作る
次は、表情を変えた時の「頬染めのテクスチャ」を作ります。
テクスチャの切り替えもシェイプキーで制御できればよいのですが、Unityのシェイプキーでは「メッシュの頂点を動かす」だけのようですね。
「通常時」と「頬染め時」の2つのテクスチャをスクリプトで切り替えることにしました。
「頬染め時」のテクスチャを作る、と言ってもブラシで明るめの色を付けるだけです。
このテクスチャファイルをUnityに読み込み、マテリアルとして登録しておきます。既に「体」という名前のマテリアルを使ってますので、「体2」というマテリアル名にしました。
Unityにエクスポートしておく
こんな表情になりました。
これで、Blender側の作業は完了です!
FBXファイルにエクスポートして、Unityで取り込みなおしておきましょう。
(3) Unityのアセット「VeryAnimation」でモーションを作る
表情ができたので、風魔法がキャラクターに当たった時の「アクション(モーション)」を作ります。
モーションと言っても、作るのは「一つのポーズ」だけ。フレーム毎にたくさんのポーズを作る必要はありません。モーションとモーション間の動きは、自動的にUnityが補間してくれます。とても便利ですね。
モーションを作るのは、以前しゃがむポーズを修正する時にも使った「Very Animation」アセットを使用します。
このアセットのおかげで、かなりモーション作成が簡単になりましたよ。
「驚いて少し手を上げたポーズ」を作る
ボーン間の関節部分を操作して、「驚いて少し手を上げたポーズ」を作ります。
こんな感じですね。
このままでは単に手を上げてるだけにしか見えませんね。
Very Animationは、モーションと同時に「シェイプキー(Unityではブレンドシェイプ)」を操作することもできます。
これを使って、先ほど作成したシェイプキーをオンにしてみます。
表情が出てきました!
やはり表情が付くと印象が違います。
少し下から見るとこんな感じ。
「困ったような恥ずかしいような表情」になった気がします。
「Animator Controller」に追加する
作ったモーションはこれまでと同じように、「Animator Controller」に追加しておきます。
これでモーションの準備ができました。
(4) キャラクターに「当たり判定用のコライダー」を付ける
モーションは、風魔法がキャラクターに当たった時に実行したいです。
風魔法がキャラクターに当たったことを検出するには、「風魔法のUnityコライダー」が「キャラクターのUnityコライダー」と接触しているか?で判定できます。
「風魔法の当たり判定用コライダー」は子オブジェクトにアタッチする
その当たり判定用として、キャラクターの腰辺りに「UnityのCapsule Collider」を設置しておきます。
ここで大事なのが、コライダーをアタッチするオブジェクトです。キャラクターのオブジェクトに直接付けるのではなく、「子オブジェクト(Chara_MagicObject)」に付けてます。
理由は、コライダー同士が接触した時、1つのオブジェクトに複数のコライダーが付いていると、どのオブジェクトに当たったかわからないからです。1つのオブジェクトには1つのコライダーだけが付いた状態にする必要があります。
更に、そのオブジェクトに「専用のタグ(CharaMagic)」を付けることで、スクリプト側でオブジェクトを判別しやすくしています。
「カメラとキャラクターが近づいたことを検知するコライダー」も付けておく
今回作成するスクリプトでは、以下の2つのイベントを「それぞれのコライダーとタグ」で区別しています。
- 風魔法が当たったことを「Chara_MagicObject にアタッチしたコライダー」と「CharaMagic というタグ」で検知する。
- カメラとキャラクターが当たった(近づいた)ことを「Chara_AreaObject にアタッチしたコライダー」と「CharaArea というタグ」で検知する。
2つ目のコライダーはこんな感じになってます。
この大きな球の範囲内にカメラが入ると、接触したことが検知されるわけですね。
IsTriggerをオンにする
コライダーの「IsTrigger をオン」にしておくと、コライダー同士がぶつからずにすり抜けてくれた上で、接触した時に「検知」できるようになります。オンにしておきましょう。
(5) 「風魔法が当たったらモーションして表情を変える」スクリプトを作る
ここまでの作業で、「風魔法」と「キャラクターのモーション+表情+テクスチャ+当たり判定用のコライダー」ができました。
あとはスクリプトを使って、「風魔法がキャラクターに当たった時に、モーションして表情とテクスチャを変える」ことができればOKですね。
早速組み込んでいきましょう。
「体」オブジェクトがRayを無視するための Ignore Raycast 設定
ここで、設定を2つ追加しておきましょう。
今の設定のままでは、前回の記事で作った「風魔法のRayCast」がキャラクターの「体のコライダー」にも当たるようになってます。そういう使い方をする場合もありますが、今回は直接当たって欲しくないので、キャラクターのInspectorの「Layer」を Default から「Ignore Raycast」に変更しました。
こうすることで、キャラクターにRayが当たらないようになります。Rayを無視する方法はその他にもあるようですが、細かな制御が必要なければ、この方法が一番お手軽ですね。
「体」マテリアルを変更するための設定
2つ目の設定です。
「モーションして表情を変えた」時、体のマテリアルを「頬染め」用のマテリアルに変更するのですが、ここでは下準備として、スクリプトから使えるように設定をしておきます。
InitCharacter.cs は以前の記事で作成した、「キャラクターを作成するスクリプト」でしたね。
[InitCharacter.cs]
/// <summary> /// キャラクターの初期設定クラス /// </summary> public class InitCharacter : MonoBehaviour { [SerializeField] private Transform baseChara; [SerializeField] private Material[] pantsMaterials; [SerializeField] private Material[] ribbonMaterials; [SerializeField] public Material[] karadaMaterials; [SerializeField] private enum AgentFlg { on, off } [SerializeField] private AgentFlg agentFlg;
18,19行目で、体用のマテリアルを配列で複数持てるよう定義しています。
あとは、InspectorにマテリアルをD&Dすれば設定OKです。
これでマテリアルをスクリプトから簡単に使えるようになりました。
風魔法に検知用スクリプト「WindMagic.cs」をアタッチする
風魔法に、キャラクターと接触したことを検知するスクリプト「WindMagic.cs」を作成し、アタッチします。
[WindMagic.cs]
using UnityEngine; namespace Amaotolog { /// <summary> /// 風魔法のクラス /// </summary> public class WindMagic : MonoBehaviour { private void OnTriggerEnter(Collider other) { if (other.CompareTag("CharaMagic")) { // キャラクターのモーション変更 other.GetComponentInParent<AgentMotion>().MotionSkirt(); } } } }
Unityコライダー同士が接触すると、10行目の OnTriggerEnterメソッド が呼ばれます。
12から16行目では、接触したのが「キャラクター側に配置されている”風魔法との当たり判定用Unityコライダー”」だった場合のみ、「キャラクターのモーションを変更するメソッド(MotionSkirt)」を呼んでます。
ここではメソッドを呼ぶだけで、それ以外の処理は書いてません。キャラクターの処理は、キャラクター側のスクリプトで処理した方が役割分担ができて良いですね。
「MotionSkirt」メソッドで体のマテリアルを変更してモーション実行する
まずは、マテリアルの下準備用の処理を実行しておきます。
[AgentMotion.cs]
public float ikWeight; // 視線制御IKのウェイト値 private InitCharacter scriptInitCharacter; // InitCharacterクラス void Start() { agent = GetComponent<NavMeshAgent>(); animator = GetComponent<Animator>(); agent.speed = Define.INIT_SPEED; CreateLotMotionsList(); // 抽選リストを作成 SetDestByLot(); // 最初の目的地を設定 // InitCharacterクラスを取得 scriptInitCharacter = GameObject.Find("InitCharacter").GetComponent<InitCharacter>(); }
少し前の設定で、「体」マテリアルを変更するために「InitCharacter.cs」にマテリアルを登録しましたが、別のスクリプトなので今のままでは使えません。そこで、28行目でスクリプト自体を変数定義して、39,40行目で「InitCharacter.cs」を変数に登録しています。
[AgentMotion.cs]
// モーション public void MotionSkirt() { // 実行中のモーション情報取得 AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); if (stateInfo.IsName("Walk")) { // 頬染め(体のマテリアル変更) transform.Find(Define.KARADA).gameObject.GetComponent<Renderer>().sharedMaterial = scriptInitCharacter.karadaMaterials[1]; SetAnimNavOn(MotionIndex.Skirt1); } }
そして、「MotionSkirt」メソッドで、体のマテリアル変更とモーション実行を制御します。
328行目で、現在のモーションが「歩きモーション中」かどうかを判定しています。
歩きモーション中だった場合、331行目で「体のマテリアル」を変更して、333行目でVery Animationで作成した「モーション」を実行しています。MotionIndex.Skirt1 は手を上げるモーションです。
モーションが終わったらマテリアルを元に戻す
[AgentMotion.cs]
void Update() { // 目的地に近づいた場合 if (!agent.pathPending && agent.remainingDistance < 0.5f) { // モーション実行と次の目的地を設定 SetMotionAndNextDest(); } // モーションが終わった場合 AnimatorTransitionInfo aniTraInfo = animator.GetAnimatorTransitionInfo(0); if (aniTraInfo.IsUserName("ToWalk")) { agent.updatePosition = true; agent.updateRotation = true; agent.speed = Define.INIT_SPEED; // 次の目的地をゆっくり向く IEnumerator mukuTugi = MukuTugi(); StartCoroutine(mukuTugi); if (aniTraInfo.nameHash == Animator.StringToHash("Ladder -> Walk")) { // はしごの場合、ゆっくり降りる IEnumerator oriruHashigo = OriruHashigo(); StartCoroutine(oriruHashigo); } else if (aniTraInfo.nameHash == Animator.StringToHash("Skirt1 -> Walk") || aniTraInfo.nameHash == Animator.StringToHash("Skirt2 -> Walk")) { // 体のマテリアルを元に戻す transform.Find(Define.KARADA).gameObject.GetComponent<Renderer>().sharedMaterial = scriptInitCharacter.karadaMaterials[0]; agent.updatePosition = true; } else { agent.updatePosition = true; } // 歩くモーションを設定 animator.SetInteger(Define.MOTION_PARAM, (int)Define.MotionIndex.Walk); } }
70行目で「モーション」が終わったことを判定し、74行目で元のマテリアルに変更します。
これでメインの実装は完了です!
(6) 近づくとこちらを見る
残りの実装として、カメラが近づくと「キャラクターがカメラの方を見る」ようにします。
アニメーションIKを設定する
過去の記事で使った処理を参考にします。
[AgentMotion.cs]
/// <summary> /// キャラの移動とモーションクラス /// </summary> [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Animator))] public class AgentMotion : MonoBehaviour { [SerializeField] private Transform[] motionPoints; private int destPoint = 0; List<int> lotPointsList = new List<int>(); private NavMeshAgent agent; private Animator animator; [SerializeField] private Transform lookTarget; // 視線先のターゲットオブジェクト public float ikWeight; // 視線制御IKのウェイト値 private InitCharacter scriptInitCharacter; // InitCharacterクラス
25行目で「視線先のターゲットオブジェクト(今回はMeta Questのカメラ)」の変数を、27行目で「IKの強さ(ウェイト値)」を定義してます。
[AgentMotion.cs]
// アニメーションIKの設定 private void OnAnimatorIK() { // 視線制御のIK animator.SetLookAtWeight(ikWeight); animator.SetLookAtPosition(lookTarget.position); }
そして、「OnAnimatorIK」メソッド内、91行目で「IKの強さ」を指定するのですが、ここの値(ikWeight)の大きさで「どれだけカメラを向くか?」を制御できます。0 はまったくこちらを向かない、0.5 は半分くらい向く、1 の時は完全にこちらを向く、という感じですね。
あとは、この値をプログラムで制御すればよいですね。
近づいたらこちらを向いて、遠ざかったら向かない
制御の仕組みは、「風魔法が当たったらキャラクターがモーション実行する」のとほとんど同じです。
[TransformMover.cs]
// カメラがキャラクターの視線検出範囲に入った場合 private void OnTriggerEnter(Collider other) { if (other.CompareTag("CharaArea")) { // カメラの方を向く other.GetComponentInParent<AgentMotion>().CameraMukuOn(); } } // カメラがキャラクターの視線検出範囲から出た場合 private void OnTriggerExit(Collider other) { if (other.CompareTag("CharaArea")) { // カメラの方を向くのを止める other.GetComponentInParent<AgentMotion>().CameraMukuOff(); } }
「キャラクターの範囲検知用のUnityコライダー」と「カメラのUnityコライダー」が接触した時(近づいた時)、58行目の OnTriggerEnterメソッド が実行され、接触しなくなった時(離れた時)、68行目の OnTriggerExitメソッド が実行されます。
それぞれのメソッドの中では、「other.CompareTag(“CharaArea”)」でコライダーが当たったものが「CharaAreaというタグ」かどうかを判定してます。「CharaAreaというタグ」の場合、「キャラクターの範囲検知用のUnityコライダー」が検知した、ということになります。
あとは、63行目の CameraMukuOnメソッド でこちらを向かせ、73行目の CameraMukuOffメソッド でこちらを向かないようになります。
ゆっくりとこっちを向く
ここまでの実装で、こちらを向いてくれるようになったんですが、検出範囲内に入った瞬間に超すばやくこちらを向くので少し怖いです。。。
コルーチンを使ってゆっくりとこっちを向くようにしましょう。
[Define.cs]
/// <summary> /// 定義値クラス /// </summary> public static class Define { public static readonly float INIT_SPEED = 0.7f; // 初期速度 public static readonly string MOTION_PARAM = "motion"; // モーションパラメータ名 public static readonly float LOOK_IK_WEIGHT = 0.7f; // 視線制御IKのウェイト値
まずは変数定義です。10行目でウェイト値を定数宣言してます。ウェイト値の最大は 1.0f なんですが、0.7fくらいがちょうど良さそうなので、この値にしてます。
[AgentMotion.cs]
// 視線制御IKをゆっくりオンにする public void CameraMukuOn() { if (ikWeight > 0f) { return; } ikWeight = 0f; IEnumerator changeIkWeightOn = ChangeIkWeightOn(); StartCoroutine(changeIkWeightOn); } private IEnumerator ChangeIkWeightOn() { while (ikWeight <= Define.LOOK_IK_WEIGHT) { ikWeight += 0.05f; yield return null; } } // 視線制御IKをゆっくりオフにする public void CameraMukuOff() { if (ikWeight < Define.LOOK_IK_WEIGHT) { return; } ikWeight = Define.LOOK_IK_WEIGHT; IEnumerator changeIkWeightOff = ChangeIkWeightOff(); StartCoroutine(changeIkWeightOff); } private IEnumerator ChangeIkWeightOff() { while (ikWeight > 0f) { ikWeight -= 0.05f; yield return null; } }
106から110行目で「0.0f → 0.7fまで、0.05fずつ増やす」、124から128行目で「0.7f → 0.0fまで、0.05fずつ減らす」処理になってます。ウェイト値をゆっくり変化させることで、キャラクターがカメラの方を向く速度もゆっくりになります。
解像度を上げてジャギーを取る
これもおまけの処理なんですが、Oculus Linkの状態でもジャギーが気になってきたので、設定を変えました。
[InitCharacter.cs]
void Start() { // 解像度を上げる(ジャギーを無くす) UnityEngine.XR.XRSettings.eyeTextureResolutionScale = 1.5f; if (agentFlg == AgentFlg.on) { CreateLotPantsList(); // 抽選リストを作成 // キャラクター生成 SetChara1(); SetChara2(); SetChara3(); SetChara4(); SetChara5();
以前の記事でやっていた方法ですね。
解像度が上がるので負荷も上がりますが、ジャギーが無くなって見た目がかなりきれいになりますよ。
これですべての作業が完了しました!!
まとめ
パーティクルとコライダーの風魔法を使ったプログラムができました。
キャラクターのリアクションがあったり、近づくとこちらを向くようになったので、臨場感が増した気がしますよ。
Unityの世界でできることが増えるのは楽しいですね。
コメント
トラックバックは利用できません。
コメント (0)
この記事へのコメントはありません。