今回は色々なゲーム作りのテクニック(と言えるほどでもない)を使って、一人用のフリーフォールアトラクションを作ってみました。
フリーフォールをゲームに取り込むなんて方はまぁほとんどおられないと思いますので、中で使っているテクニックを他の機能を作る時に使ってみてください。
テクニックと言っても以前から使っている物を使っただけなので、目新しいものがあるわけではありませんが・・・・。
↑のような感じです。
本当は自己満足のおまけ程度にフリーフォールを作ってみましたと動画をアップし記事を作るだけの予定だったんですが、途中からなんやかんや付けたしてそれなりのものが出来たので作り方も公開する事にしました。
この機能を作ると学べる事
今回の一人用フリーフォール機能を作っていくと以下のような事柄のテクニックを学ぶ事が出来ます。
ざっと挙げるとこんな感じですが、ゲームを作る時に非常に使えるテクニックだと思います。
一人用フリーフォール機能の概要
今回作成する一人用フリーフォールの機能の概要を説明します。
とりあえず機能としてはこんな感じになります。
細かい部分に関してはこれから紹介していきます。
座る、立ち上がるアニメーションの作成
まずはフリーフォールに乗る時、降りる時のアニメーションをBlenderで作成します。
Blenderでアニメーションを作成するにはキャラクターのモデルにボーンが設定され、ボーンの動きで動くメッシュが設定されている必要があります。
Blenderでアニメーションを作成するにはボーンを直接動かす事でも作成出来ますが、BlenderのRigifyを設定して作成する場合は
↑の記事を参照して設定してください。
Rigifyを使ってアニメーションを作成するやり方は
↑の記事を参照してください。
アニメーションの細かい部分に関しては上記の記事を見て作り方を参照してもらうとして、わたくしが作ったアニメーションをフレーム毎のポーズで紹介したいと思います。
あらかじめBlenderのアニメーションのフレームレートを60にしてUnityと合わせておきます。
キャラクターモデルが置いてあるレイヤーとは別のレイヤーに椅子の形状のモデルをプリミティブ素材で作成しておきます。
椅子自体はキャラクターが椅子に手をついて座る時の目安として使用するだけなのでサイズを調整するだけで適当に作ります。
レイヤーは↑の画像の下の方にある四角いマス目を選択する事で、そのレイヤーにあるオブジェクトを表示することが出来ます。
複数のレイヤーを選択したい場合は、Shiftキーを押しながらレイヤーを押していきます。
オブジェクトを別のレイヤーに移動したい時は移動元のオブジェクトを右クリックで選択した状態でMキーを押し移動先のレイヤーを押します。
これで準備が出来たので椅子に座るアニメ―ションのフレーム毎のポーズを紹介します。
↑のようなポーズをフレーム毎に設定しました。
次は座っている状態から立ち上がるアニメーションです。
↑のような感じのポーズを設定しました。
細かい所は調整してください。
今回の機能を作るにあたって一つだけ必要になるポーズが椅子に手をかける部分です。
これは椅子の位置と手を付いた時のアニメーションの位置を合わせる為に使用します。
これでアニメーションの作成が完了しました。
フリーフォールアトラクションの舞台の作成
一人用フリーフォールを設置する為の舞台(地面)は単純に3DオブジェクトのCubeやPlaneで作ってもいいんですが、今回は眺めを良くする為にTerrainで地面を作っておきます。
Terrainで地面を作成するやり方は
を参照して作成してみてください。
わたくしは
↑のような感じで作りました。
フリーフォールが高くまで上がる事も考慮して周りの壁を高くしてみました。
周りの景色を楽しめるようにするならTerrainのサイズを大きく作るといいかもしれません。
わたくしの場合は大きく作るとパソコンのスペックの影響でクラッシュする為、WidthとLengthを200にしました。
Terrainには木にコライダを設定しTerrainの機能で木を生やしました。
フリーフォールの作成
次にフリーフォールを作成していきます。
フリーフォールゲームオブジェクトの作成
Blender等でフリーフォールの素材を作った方が綺麗になりますが、今回はUnityのプリミティブ素材を使って椅子と体を固定するバーを作成します。
ヒエラルキー上で右クリック→Create Emptyを選択し名前をFreeFallとします。
FreeFallの子要素にCubeをいくつか組み合わせてScaleを調整し椅子を作ります。
またFreeFallの子要素に空のゲームオブジェクトを作成し、名前をBarとし椅子の背もたれの上の真ん中辺りに移動します。
固定バーもCubeをいくつか組み合わせて作りますが、Barの子要素に配置します。
これはキャラクターが椅子に座ったらBarを回転させBarを下ろすようにする為です。
↑FreeFallの階層で、CubeとCube(1)が椅子を表していてCube(1)が背もたれの部分です。
↑が作成した椅子と固定バーです。
Cube(1)の子要素にあるLeftFootPositionは椅子に座る時の左足の位置、GetOffLeftFootPositionは椅子から立ち上がった時の左足の位置を空のゲームオブジェクトで作ったものです。
↑のように足の位置と角度を作成します。
角度は足が水平になるようにします。そうしないとアニメーションが足の角度に合わせて斜めになってしまいます。
本来であれば手を椅子に付けた時のアニメーション時に、椅子の背もたれに手の位置を合わせたいところですが、手の場合は角度を合わせるのが難しい為に足にしました。
文章を見ているだけだと解り辛いですが、後で試しに手で合わせてみてください。
アニメーションが手の位置と角度に合わせて斜めになってしまうのがわかると思います(もちろん合わせ方によりますが)。
Barの子要素に空のゲームオブジェクトでRightHandとLeftHandを作成し固定バーの右手の位置と左手の位置がくる場所に移動させます。
↑のような位置と角度になります。
LeftFootPosition、GetOffLeftFootPosition、RightHand、LeftHandは後でUnityを実行しながら位置と角度を合わせるのでだいたいの場所に位置していれば問題ありません。
これでフリーフォールのゲームオブジェクト群が出来上がりました。
フリーフォールゲームオブジェクトに取り付けるスクリプトの作成
次にフリーフォールのゲームオブジェクトに取り付けるスクリプトを作成していきます。
を作成します。
キャラクター検知スクリプト
まずはFreeFallにSphereColliderを取り付けIs Triggerのチェックを入れ、キャラクター検知エリアを作ります。
キャラクターを検知するエリアは、フリーフォールの前方辺りに置き後ろの方では検知しないようにします。
これはアニメーションが椅子に手を付く時に、椅子に設定した位置と合わせるので、後ろから合わせると椅子を通過してアニメーションしてしまう為です。
その為、前方から横からしか座る事が出来ないようにしておきます。
キャラクターを検知するスクリプトSearchCharaを作りFreeFallに取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | using UnityEngine; using System.Collections; public class SearchChara : MonoBehaviour { // フリーフォールに乗る時に表示するUI public GameObject infoUI; // 範囲内にキャラがいるかどうか private bool isEnter = false; // FreeFallCharaMove private FreeFallChara freeFallChara; void OnTriggerEnter(Collider col) { if (col.tag == "Player") { infoUI.SetActive (true); isEnter = true; // まだキャラ移動スクリプトが設定されていなければセット if (freeFallChara == null) { freeFallChara = col.GetComponent <FreeFallChara> (); } } } void OnTriggerExit(Collider col) { if (col.tag == "Player") { infoUI.SetActive (false); isEnter = false; } } void Update() { // 指定範囲内にキャラがいる if (isEnter) { // SPACEキーを押したらフリーフォールに乗る if (Input.GetKeyDown ("space")) { if (freeFallChara.GetState () == FreeFallChara.State.normal) { freeFallChara.SetState (FreeFallChara.State.sitDown); } } } } } |
isEnterフラグでキャラクターが範囲内にいるかどうかを判断します。
OnTriggerEnterでキャラクターを検知しキーを押す事を促すUIを表示します。
OnTriggerExitはキャラクターが検知エリアから出た時の処理でUIを非表示にしています。
Updateではキャラクターが範囲内にいる時に、SPACEキーを押したらキャラクター操作スクリプトFreeFallCharaの状態をフリーフォールに座る状態に変更します。
状態を変更する時はキャラクターがノーマル状態の時だけにします。
フリーフォールを管理するスクリプト
次はFreeFallを管理するFreeFallスクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | using UnityEngine; using System.Collections; public class FreeFall : MonoBehaviour { public enum FreeFallState { none, wait, up, down } // フリーフォールの元の位置 private Vector3 basePos; // フリーフォールの状態 private FreeFallState state = FreeFallState.none; // フリーフォールのY座標到達点 public float arrivalPointY; // フリーフォールが上がるスピード public float upSpeed; // フリーフォールが探すスピード public float downSpeed; // フリーフォールが頂上に行ってから落ちるまでの時間 public float waitCount; // フリーフォール操作用ボタンUI public GameObject freeFallButton; // フリーフォールのバーアニメーション public Animator barAnim; // Use this for initialization void Start () { basePos = transform.position; } // Update is called once per frame void Update () { if (state == FreeFallState.up) { transform.position = Vector3.MoveTowards (transform.position, new Vector3 (transform.position.x, arrivalPointY, transform.position.z), upSpeed * Time.deltaTime); if (transform.position.y >= arrivalPointY) { SetState (FreeFallState.wait); } } else if (state == FreeFallState.wait) { StartCoroutine (CountDown ()); } else if (state == FreeFallState.down) { transform.position = Vector3.Lerp (transform.position, basePos, downSpeed * Time.deltaTime); if (Vector3.Distance (transform.position, basePos) <= 0.1f) { transform.position = basePos; SetState (FreeFallState.none); } } } IEnumerator CountDown () { yield return new WaitForSeconds (waitCount); // 待ち時間経過したらフリーフォールを落とす SetState (FreeFallState.down); } public void SetState(FreeFallState state) { this.state = state; if (state == FreeFallState.up) { OnOffButton (false); } else if (state == FreeFallState.none) { OnOffButton (true); } } public FreeFallState GetState() { return state; } // フリーフォール操作用ボタンの表示・非表示 public void OnOffButton(bool isOn) { freeFallButton.SetActive (isOn); } // フリーフォールのバーの開け閉め public void SetBar(bool isClose) { // バーを下ろす if (isClose) { barAnim.SetBool ("Close", true); // バーを上げる } else { barAnim.SetBool ("Close", false); } } // バーの開閉が終わっているかどうか public bool IsMoveBar() { AnimatorStateInfo animInfo = barAnim.GetCurrentAnimatorStateInfo (0); // バーを上げ下ろししている時で再生が終わっていない時は動いているのでtrue、それ以外は動いていないのでfalse if (animInfo.IsName ("Close")) { Debug.Log ("Close"); return animInfo.normalizedTime < 1f; } // 現在再生しているアニメーションがCloseでなければ動いていると見なしtrueを返す return true; } public bool IsOpenBar() { return barAnim.GetCurrentAnimatorStateInfo (0).IsName ("Idle"); } } |
FreeFallスクリプトではフリーフォールが到達する位置、上がっていく速度、下がっていく速度、上に到達してから落ちるまでの秒数等をpublicで宣言し設定値を変更出来るようにします。
フリーフォールをスタートするボタン、フリーフォールから降りるボタンのUIをオン・オフする為UIのゲームオブジェクトを設定出来るようにしています。
またフリーフォールの固定バーは『開く』と『閉じる』というアニメーションを後で作成しますので、FreeFallスクリプトでアニメーションを切り替えられるようにします。
Updateメソッドではフリーフォールの状態によって処理を分岐させ、FreeFallState.up時にはVector3.MoveTowardsで一定スピードで上昇するようにしています。
FreeFallState.wait状態は上に昇りきってからの状態で、指定時間が経過したらFreeFallState.down状態へと移行させます。
指定秒数を計測しているのがCountDownメソッドで、コルーチンを使って指定秒数経過後にフリーフォールの状態をFreeFAllState.downに変更しています。
コルーチンに関しては
を参照してください。
FreeFallState.down状態ではVector3.Lerpを使って元の位置へと移動させていますが、Vector3.MoveTowardsと違って最後はスピードが落ちます。
SetStateメソッドではフリーフォールの状態を変更し、フリーフォールの操作ボタンのオン・オフをします。
オン・オフをするタイミングは状態がFreeFallState.up(上がっていった時)にオフ、FreeFallState.none(フリーフォール操作を何もしていない時)にオンとしています。
OnOffButtonメソッドは受け取った論理値でボタンのゲームオブジェクトをオン・オフしているだけです。
SetBarメソッドは固定バーのアニメーションを操作するメソッドです。
IsMoveBarメソッドは固定バーの開け閉めのアニメーションが再生中かどうかを判断するメソッドで、固定バーのAnimatorのClose状態にいる時でアニメーションの再生が終わっていない時にfalseを返し、それ以外はすべてtrueを返します。
IsOpenBarは固定バーが完全に開いた状態になっているかどうかを判定します。
判定の条件は現在のAnimatorの状態がIdle状態にいるかどうかで判断しています。
これでFreeFallスクリプトの作成が終了しました。
キー押しを促すUIの作成
FreeFallスクリプトでキャラクターが検知エリア内に入った時、出た時にSPACEキーを押す事を促すUIをオン・オフしていますがそのUIを作成します。
ヒエラルキー上で右クリック→UI→Textを選択し、Canvasの名前をInfoUIとします。
↑のようにInfoUIの子要素のTextに文字を入力し色やサイズ、位置を調整します。
これでキー押しを促すUIの作成も終了しました。
フリーフォール操作ボタンの作成
次はフリーフォールに乗った時に操作するボタンUIの作成をします。
ヒエラルキー上で右クリック→UI→Canvasを選択し名前をFreeFallButtonとします。
子要素にUI→Buttonを2つ作成し、名前をStartとCancelとします。
↑のような階層になります。
ボタンの位置等を調整し、左下と右下にそれぞれ表示されるようにします。
次にFreeFallButtonにFreeFallButtonスクリプトを新しく作り取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using UnityEngine; using System.Collections; public class FreeFallButton : MonoBehaviour { public FreeFall freeFall; public FreeFallChara freeFallChara; // フリーフォールを開始 public void StartFreeFall() { if (freeFall.GetState () == FreeFall.FreeFallState.none) { freeFall.SetState (FreeFall.FreeFallState.up); } } // フリーフォールを降りる public void EndFreeFall() { if (freeFall.GetState () == FreeFall.FreeFallState.none) { freeFall.SetBar (false); freeFall.OnOffButton (false); freeFallChara.SetState (FreeFallChara.State.transitionToNormal); } } } |
freeFallはFreeFallスクリプト、freeFallCharaはキャラクター操作スクリプトをインスペクタで設定出来るようにしておきます。
StartFreeFallメソッドは、Startボタンを押した時に実行するメソッドでフリーフォールの状態が何もしていない状態(FreeFallState.none)の時に、フリーフォールを到達点まで上昇させる状態(FreeFallState.up)にしています。
EndFreeFallメソッドはCancelボタンを押した時に実行するメソッドで、何もしていない状態の時にフリーフォールの固定バーを開き、操作用UIを非表示にした後、キャラクターの状態をノーマルへと移行させる状態(State.transitionToNormal)へと変更してます。
StartボタンのインスペクタでOn Click()にFreeFallButtonスクリプトのStartFreeFallメソッド、CancelボタンのインスペクタでOn Click()にFreeFallButtonスクリプトのEndFreeFallメソッドを設定してください。
ボタンを押した時に実行したいメソッドを設定するやり方は
を参照してください。
これでフリーフォール操作ボタンの作成は終了です。
固定バーのアニメーションの作成
固定バーの開閉のアニメーションを作成します。
UnityエディターのWindowからAnimationを選択しAnimationビューを表示します。
↑のようにAnimationビューが表示されるのでFreeFallの子要素のBarを選択しCreateボタンを押します。
Animationビューでのアニメーションの作成は
辺りを参照して作成してみてください。
↑のようにIdle、Close、Openの3つを作成します。
Idleはずっと開いたままの状態、Closeは開いた状態から閉じた状態、Openは閉じた状態から開いた状態へとアニメーションをさせます。
プロパティにPositionも入っていますが・・・、実際にはBarのRotationしか変更していません。
Closeのアニメーションだけ見てみましょう。
↑のように開いた状態から閉じた状態になるようにBarを回転させています。
Openはこの逆を作成するだけです。
固定バーのAnimatorではアニメーションパラメータにBool型のCloseを作成します。
↑のように遷移を作成します。
Idle→CloseはCloseがtrue
Close→OpenはCloseがfalse
それぞれHas Exit Timeのチェックを外しておきます。
Open→Idleは、Has Exit Timeにチェックを入れアニメーションの終了とともにIdleに流れるようにしておきます。
これで固定バーのアニメーションの作成が終了しました。
ここまでで、FreeFallに関する機能の作成が終わりました。
次は、スクリプトを設定したFreeFallゲームオブジェクトのインスペクタでFreeFallButtonやInfoUIを設定します。
↑のように設定しました。
キャラクターの作成
次にフリーフォールに乗るキャラクターを作成していきます。
フリーフォールに乗るまではキャラクターを普通に操作出来るようにし、フリーフォールに乗ったらキャラクターの視点をマウス操作で変更出来るようにします。
キャラクターの設置
今回使用するキャラクターモデルはStandardAssets→Characters→ThirdPersonCharacter→Models→Ethanを使用します。
ヒエラルキー上で右クリック→Create Emptyを選択し空オブジェクトを作成し名前をCharacterとします。
Characterの子要素にEthanをドラッグ&ドロップします。
キャラクター操作スクリプト等は全てCharacterに取り付けEthanには取り付けません。
こうすることで主人公キャラクターの見た目を変更したい時はEthanを削除し別のモデルをCharacterの子要素、CharacterControllerのコライダの調整をするだけで実現する事が出来ます。
CharacterにはCharacterControllerの取り付けとコライダのサイズ調整をします。
ここら辺は
を参照してください。
キャラクター用AnimatorControllerの作成
キャラクターのAnimatorに設定するAnimatorControllerを作成します。
Assetsフォルダ内で右クリック→Create→AnimatorControllerを選択し名前をFreeFallとします。
↑のようにアニメーションパラメータにFloatのSpeed、BoolのSitDownを作成し、状態と遷移を作成します。
Idleには立っているアニメーション、Runには走っているアニメーション、SitDownには座るアニメーション、StandUpには立ち上がるアニメーションを設定します。
Idle、SitDown、StandUpの状態を選択し、
↑のようにFootIKの項目にチェックを入れます。
FootIKにチェックを入れると足元のIKが有効になります。
座った状態で確認すると
のようにFootIKにチェックを入れていないと足が地面にうまく接地出来ていません。
立ちあがる時は
↑のようになります。
IKが有効になっていると足を付いた時にそこに固定され足元がブレないようになります。
詳しくは
を参照してください。
Idle→RunはSpeedが0.1より上
Run→IdleはSpeedが0.1より下
Idle、Run→SitDownはSitDownがtrue
SitDown→StandupはSitDownがfalse
それぞれのHas Exit Timeのチェックを外しておきます。
StandUp→Idleは、Has Exit Timeのチェックを入れアニメーションが終わったらIdleに流れるようにしておきます。
Base Layerの右の歯車をクリックしIK Passにチェックを入れます。
IK Passにチェックを入れるとスクリプトでOnAnimatorIKが呼び出されるようになります。
これでAnimatorControllerの作成が終わったのでCharacterのAnimatorに設定してください。
キャラクター操作スクリプトの作成
次にキャラクター操作スクリプトを作成します。
スクリプトが長いので分割して紹介します。
フィールド等の宣言部
まずはフィールド等の宣言部です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | using UnityEngine; using System.Collections; public class FreeFallChara : MonoBehaviour { // 主人公の状態の列挙型 public enum State { normal, // ノーマル状態 sitDown, // 座ろうとしている状態 transitionToFreeFall, // フリーフォールのバーが降りるまでの状態 freeFall, // フリーフォールに乗っている状態 transitionToNormal // ノーマル状態へ移行している状態 } private Animator animator; private CharacterController cCon; private Vector3 velocity; // 入力値 private Vector3 input; // 移動スピード public float moveSpeed = 3f; // 主人公の状態変数 private State state = State.normal; // 左足をつく場所 public Transform leftFoot; // 指定位置と回転(最終的な位置・回転を調べて設定する) private Vector3 targetPos = new Vector3 (58.625f, -0.285f, 78.547f); private Quaternion targetRot = Quaternion.Euler (357.6664f, 270f, 0f); // 指定位置に移動・回転させるスピード public float destinationSpeed; // フリーフォールスクリプト public FreeFall freeFall; // カメラ切り替えスクリプト public ChangeCamera changeCamera; // フリーフォールから降りる時の左足の位置 public Transform getOffLeftFoot; // カメラの角度変更のスピード public float cameraSpeed; // カメラ public Transform myCamera; // カメラのX軸の角度変化値 private float xRotate = 0f; // カメラのY軸の角度変化値 private float yRotate = 0f; // カメラの初期位置 private Quaternion initCameraRot; // カメラの限界回転値 public float cameraRotateLimit; // 右手のTransform public Transform rightHand; // 左手のTransform public Transform leftHand; // 手のIKに使うウエイト private float handIKWeight = 0f; // IKのウエイトを増やすスピード public float weightUpSpeed; // フリーフォールに乗る時に表示するUI public GameObject infoUI; } |
キャラクターは通常の移動状態やフリーフォールに乗っている状態などで操作の切り替えが出来るようにしてます。
その為、キャラクターの状態を列挙型で作成しておきその都度状態を変更します。
targetPosとtargetRot、destinationSpeedは右手が椅子に触った時のアニメーションで位置と回転を合わせる場合のフィールド値で今回は使用していません。
これらのフィールドを使った処理はコメント化しています。
一部フィールドの説明をしていきます。
moveSpeedはキャラクターが走るスピード
changeCameraはキャラクターがフリーフォールに乗った時にカメラをキャラクター視点のものに切り替えるスクリプトを入れます。
leftFoot、getOffLeftFootはFreeFallに作成したLeftFootとGetOffLeftFootを指定します。
cameraSpeedはフリーフォールに乗っている時のキャラクターの視点の移動スピードを指定します。
myCameraはキャラクター視点のカメラを設定します。
xRotate、yRotateはカメラの視点を変更する時に使用するフィールドです。
initCameraRotはキャラクター視点のカメラの初期の角度を入れておくフィールドです。
cameraRotateLimitはキャラクター視点のカメラが回転出来る限度を指定します。
rightHand、leftHandは固定バーを持つ時の手の位置と角度で、FreeFallに作成したRightHand、LeftHandを指定します。
handIKWeightは手の位置を段々と動かしているように見せる為のウエイトで、0だと何もしない、1だと完全に指定の位置になります。
weightSpeedはIKのウエイトを変更するスピードを指定します。
Startメソッド
Startメソッドでは初期設定等をしています。
1 2 3 4 5 6 7 8 | void Start () { animator = GetComponent<Animator>(); cCon = GetComponent<CharacterController>(); velocity = Vector3.zero; initCameraRot = myCamera.localRotation; } |
コンポーネントの取得と移動値の初期化、カメラの初期角度を保持するという事をしています。
Updateメソッド
Updateメソッドではキャラクターの状態に応じて処理を分けています。
キャラクターの状態事に処理を確認していきましょう。
ノーマル状態
まずは通常の状態について見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | void Update () { // ノーマル状態 if (state == State.normal) { // 地面に接地してる時は初期化 if (cCon.isGrounded) { velocity = Vector3.zero; // 入力値を取得 input = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0.1f && !animator.GetCurrentAnimatorStateInfo (0).IsTag ("Attack")) { animator.SetFloat ("Speed", input.magnitude); transform.LookAt (transform.position + input); velocity += input.normalized * moveSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat ("Speed", 0); } } velocity.y += Physics.gravity.y * Time.deltaTime; cCon.Move (velocity * Time.deltaTime); // 座り終えるまでの状態 |
ノーマル状態では普通にキャラクターの移動処理を記述しているだけなので割愛させて頂きます。
座り終えるまでの状態
フリーフォールに座り終わるまでの状態を見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // 座り終えるまでの状態 } else if (state == State.sitDown) { // 再生しているアニメーションがSitDownの時 if (animator.GetCurrentAnimatorStateInfo (0).IsName ("SitDown")) { // 最初の再生時 if (animator.GetCurrentAnimatorStateInfo (0).normalizedTime <= 1f) { // MatchTargetを使って左足を特定の位置と回転に合わせる animator.MatchTarget (leftFoot.position, leftFoot.rotation, AvatarTarget.LeftFoot, new MatchTargetWeightMask (new Vector3 (1f, 1f, 1f), 1f), 0.100f, 0.360f); } else { /* MatchTargetで回転のウエイトを1にした場合にアバターターゲットの回転とターゲットの回転が合わない場合は最終的な位置・回転を合わせていく必要がある // 最初の再生が終わったら到達位置・回転に徐々に合わせていく transform.position = Vector3.Lerp (transform.position, targetPos, destinationSpeed * Time.deltaTime); transform.rotation = Quaternion.Lerp (transform.rotation, targetRot, destinationSpeed * Time.deltaTime); // 到達位置との距離が小さくなったら到達位置・回転をそのまま代入 if (Vector3.Distance (transform.position, targetPos) <= 0.01f) { transform.position = targetPos; transform.rotation = targetRot; // フリーフォール状態に変更 SetState (State.freeFall); } */ // 回転を修正(今回の場合用の固定値で作り方によっては違う値を入れる必要あり) transform.rotation = Quaternion.Euler (0f, 270f, 0f); // フリーフォール状態に変更 SetState (State.transitionToFreeFall); } } |
状態がState.sitDownの時はキャラクターがフリーフォールに座ろうとしている状態なので、ここでアニメーションの再生位置と椅子の位置とを合わせます。
アニメーションがSitDownを再生している時で、最初の再生の時にanimator.MatchTargetで左足の位置を指定した位置に合わせます。
椅子に座るアニメーションの0.100から0.360の再生位置の時に、指定した位置に合わせるように設定しています。
ここの数値は座るアニメーションで椅子に手を合わせているのが36%辺りなので、10%から36%のアニメーションの間に指定した位置に合わせるようになります。
数値によってはうまく合わない可能性もあるので調整してください。
else以下では右手の位置で、アニメーションを合わせる時の処理を記述しています。
手で合わせた場合は最終的にキャラクターが斜めになってしまう事がある為、キャラクターを椅子と水平に合わせる為に調整しています。
else以下のこれらの処理は今回は使っていないのでコメント化しています。
elseに行く条件は座るアニメーションが終わった時なのでキャラクターの角度をきっかりとした角度に書き換えています。
ここで0, 270, 0と角度を指定していますが、フリーフォールの角度によってはY軸の角度を別の角度にする必要が出てきます。
座り終わったらキャラクターの状態をフリーフォールに乗りこみが完了した状態へと移行する状態に変更します。
フリーフォールへの移行状態
フリーフォールに座ってから固定バーが降りるまでの状態を見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 | // フリーフォールへの遷移の状態 } else if (state == State.transitionToFreeFall) { // フリーフォールのバーが動いていないかどうか、動いていなければカメラの視点、フリーフォールの開始UIを表示 if (!freeFall.IsMoveBar ()) { // カメラをキャラクター視点に替える changeCamera.Change (ChangeCamera.CameraMode.freeFall); // フリーフォール操作ボタンUIを有効にする freeFall.OnOffButton (true); SetState (State.freeFall); } |
フリーフォールに乗りこみ完了までの状態(State.transitionToFreeFall)では、固定バーが動いてなければChangeCameraスクリプトのChangeメソッドを呼び出してキャラクター視点のカメラへと変更し、フリーフォール操作ボタンを有効にして、キャラクター状態をフリーフォール状態(State.freeFall)にしています。
つまり固定バーが閉まった時にカメラを切り替え、フリーフォールの操作が出来るようにしています。
フリーフォールに乗りこみ完了状態
フリーフォールに座って固定バーがしまってフリーフォールを操作出来る状態について見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | } else if(state == State.freeFall) { if (handIKWeight < 1f) { handIKWeight += weightUpSpeed * Time.deltaTime; } if(Input.GetButton("Fire2")) { // 視点の横向きの移動値を設定 yRotate += Input.GetAxis("Mouse X") * cameraSpeed * Time.deltaTime; // 視点の縦向きの移動値を設定 xRotate += -1 * Input.GetAxis("Mouse Y") * cameraSpeed * Time.deltaTime; } |
フリーフォールへの乗りこみが完了したらIKのウエイトを少しづつ増やし、両手をそれぞれ指定した位置と角度に段々と移動するようにします。
またマウスの右ボタンを押している時は、カメラの視点を変更出来るようにしています。
フリーフォールから降りる状態
最後にフリーフォールから降りる状態を見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // フリーフォールから降りる状態 } else if (state == State.transitionToNormal) { // バーが動いていなければ立ち上がるアニメーションに変更 if (freeFall.IsOpenBar ()) { if (animator.GetBool ("SitDown")) { animator.SetBool ("SitDown", false); } else { // 再生しているアニメーションがStandUpの時 if (animator.GetCurrentAnimatorStateInfo (0).IsName ("StandUp")) { // 最初の再生時 if (animator.GetCurrentAnimatorStateInfo (0).normalizedTime <= 1f) { // MatchTargetを使って左足を特定の位置に合わせる animator.MatchTarget (getOffLeftFoot.position, getOffLeftFoot.rotation, AvatarTarget.LeftFoot, new MatchTargetWeightMask (new Vector3 (1f, 1f, 1f), 0f), 0.2f, 0.4f); } // 立ち上がってIdleに遷移したらCharacterController等を有効化しノーマル状態に遷移させる } else if (animator.GetCurrentAnimatorStateInfo (0).IsName ("Idle")) { cCon.enabled = true; transform.SetParent (null); animator.applyRootMotion = false; SetState (State.normal); } } } } |
フリーフォールから降りてノーマル状態へと移行する状態の時は、まず固定バーが完全に開いているかどうかを調べます。
固定バーが開いていたら、アニメーションパラメータのSitDownをfalseにし、立ち上がるアニメーションへと移行させます。
立ち上がる時のアニメーションの20%から40%にかけて左足の位置をGetOffLeftFootの位置に合わせます。
キャラクターがIdle状態へと移行したらCharacterControllerの有効化、親要素の解除、AnimatorのApply Root Motionの無効化をしキャラクターをノーマル状態にします。
いきなりCharacterControllerの有効化、親要素の解除、AnimatorのApply Root Motionの無効化という話が出てきて解り辛いですが、これらはフリーフォールに乗りこむ時に逆の事をしているのでそれを元に戻しているだけです。
CharacterControllerを無効化したのはコライダが椅子にひっかかってうまく動作しない為、キャラクターの親をFreeFallにするのはFreeFallと一緒に移動させる為、
Apply Root Motionにチェックを入れるのはMatchTargetを使用する為に行っています。
これらの処理は次に紹介するSetStateメソッドで行っています。
SetStateメソッド
SetStateメソッドはキャラクターの状態を変更する時の処理で自身のスクリプト、または他のスクリプトから呼び出して状態を変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public void SetState(State state) { this.state = state; if (state == State.normal) { animator.SetFloat ("Speed", 0f); // MatchTargetを使う為applyRootMotionのチェックを入れる } else if (state == State.sitDown) { cCon.enabled = false; animator.applyRootMotion = true; animator.SetBool ("SitDown", true); } else if (state == State.transitionToFreeFall) { // 親をフリーフォールにして一緒に動かす transform.SetParent (freeFall.gameObject.transform); // フリーフォールのバーを下ろす freeFall.SetBar (true); // インフォメーションUIを非アクティブにする infoUI.SetActive (false); } else if(state == State.freeFall) { // フリーフォール状態になったらカメラ位置をリセット myCamera.localRotation = initCameraRot; // カメラ角度の変更値もリセット xRotate = 0f; yRotate = 0f; handIKWeight = 0f; } else if (state == State.transitionToNormal) { changeCamera.Change (ChangeCamera.CameraMode.main); } } |
状態を変更したらそれぞれの状態に応じて処理をしています。
ノーマル状態の時はアニメーションパラメータのSpeedを0にして、フリーフォールに乗った時のSpeedの値を0にしてIdle状態から始まるようにしています。
フリーフォールに座る時の状態ではCharacterControllerの無効化、Apply Root Motionの有効化、アニメーションを座るアニメーションにしています。
フリーフォールに完全に乗りこむまでの状態では、キャラクターの親をFreeFallにしFreeFallスクリプトのSetBarメソッドを呼び出して、フリーフォールの固定バーを下ろしています。
また、キーを押す事を促していたUIを非表示にしています。
フリーフォールに乗りこんだ状態の時は、キャラクター視点のカメラの変更値とIKのウエイトを初期化しています。
フリーフォールを降りてノーマル状態へと移行している状態の時は、カメラをキャラクター視点から通常のカメラへと切り替えています。
GetStateメソッド
GetStateメソッドはキャラクターの現在の状態を返すだけです。
1 2 3 4 5 | public State GetState() { return state; } |
LateUpdateメソッド
LateUpdateメソッドではカメラの角度を変更しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void LateUpdate() { // フリーフォールに乗っている時だけ視点変更出来るようにする if (state == State.freeFall) { // 限界角度を越えないように値を調整 xRotate = Mathf.Clamp (xRotate, -cameraRotateLimit, cameraRotateLimit); yRotate = Mathf.Clamp (yRotate, -cameraRotateLimit, cameraRotateLimit); // 初期のカメラ角度に変更値を足して最終的な角度を計算 var xRot = initCameraRot.x + xRotate; var yRot = initCameraRot.y + yRotate; // カメラ角度の変更 myCamera.localRotation = Quaternion.Euler (xRot, yRot, initCameraRot.z); } } |
キャラクターの状態がState.freeFallの時にカメラの角度変更値を限界角度内に納めて計算し、それをキャラクター視点カメラのローカル角度に入れています。
Mathf.Clampは第1引数の値を第2引数と第3引数の間に収める為の標準メソッドです。
カメラの角度をローカル角度で計算しているのは、カメラはキャラクターの子要素に設置する為で、myCamera.rotationを変更するとキャラクターの向きに関係なくカメラ角度をワールドの角度で変更してしまう為おかしくなります。
OnAnimatorIKメソッド
OnAnimatorIKメソッドは標準のメソッドで、キャラクターに取り付けているAnimatorのIK Passにチェックを入れると呼び出されるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void OnAnimatorIK() { if(state == State.freeFall) { animator.SetLookAtWeight (1f, 0.2f, 1f, 1f, 0.5f); animator.SetLookAtPosition(myCamera.position + myCamera.forward * 100f); animator.SetIKPositionWeight (AvatarIKGoal.RightHand, handIKWeight); animator.SetIKPositionWeight (AvatarIKGoal.LeftHand, handIKWeight); animator.SetIKRotationWeight (AvatarIKGoal.RightHand, handIKWeight); animator.SetIKRotationWeight (AvatarIKGoal.LeftHand, handIKWeight); animator.SetIKPosition (AvatarIKGoal.RightHand, rightHand.position); animator.SetIKPosition (AvatarIKGoal.LeftHand, leftHand.position); animator.SetIKRotation (AvatarIKGoal.RightHand, rightHand.rotation); animator.SetIKRotation (AvatarIKGoal.LeftHand, leftHand.rotation); } } |
OnAnimatorIKでは、キャラクターがフリーフォールに乗りこんでいる状態の時に処理を行っています。
SetLookAtWeightでIKのウエイトを設定し、SetLookAtPositionでキャラクターの視点の先をカメラの前方100mの辺りに向けさせています。
SetLookAtWeightの第1引数が全体のウエイト、第2引数が体のウエイト、第3引数が頭のウエイト、第4引数が目のウエイト、第5引数がキャラクターのモーションの制限です。
今回の場合は第2引数で体の向きをあまり動かさないように0.2f、第5引数で体のモーションの制限を半分の0.5fにして体が変に曲がらないようにしています。
SetIKPositionWeight、SetIKRotationWeightで両手の位置と角度のウエイトを設定し、
SetIKPosition、SetIKRotationで指定した位置と角度に両手を設定しています。
ウエイトはUpdateメソッド内で段々と増やしていますので段々とIKが働くという感じになっています。
これでキャラクター操作スクリプトFreeFallCharaが完成しました。
Unity実行中にフリーフォールに座らせ、RightHand、LeftHand、LeftFootPosition、GetOffLeftFootPositionの位置と角度を調整します。
↑はUnityエディター実行中にLeftHandの位置と回転を調整し、固定バーの部分に左手を合わせるようにしています。
位置と角度が決まったらLeftHandのインスペクタのTransformで歯車のアイコンをクリックし、Copy Componentを選択します。
その後Unityエディターの実行をやめて、LeftHandのインスペクタのTransformの歯車をクリックしPaste Component Valuesを選択します。
これで実行中のTransformの位置と角度を停止した後のTransformに反映する事が出来ます。
詳しいやり方は
を参照してください。
LeftFootPositionは位置とY軸の角度、GetOffLeftFootPositionは位置だけを調整し、角度を変更しないようにします。
角度を変更すると足の角度がその角度に合わせて傾くので全体のアニメーションも傾きます。
LeftFootPosition、GetOffLeftFootPositionの位置は大体の位置を設定し実際に椅子に座らせたり立たせたりして調整していきます。
キャラクター視点カメラ、その他のカメラの作成
フリーフォールに乗った時のキャラクター視点のカメラとキャラクターを斜め前から写すカメラ、フリーフォールを遠目に写すカメラを作成します。
キャラクター視点カメラ
Characterの子要素に右クリック→Cameraを作成し、名前をMyCameraとします。
カメラのField Of Viewを90にして視野角を広げます。
Clipping PlanesのNearを0.01にしてより近い位置から描写できるようにしておきます。
MyCameraをキャラクターの目の位置に移動させます。
IKを使ってキャラクターの体を曲げた時に、カメラの位置も調整しなければいけない為Ethanのボーンの子要素に空オブジェクトでカメラ位置を作ります。
EthanHead1のボーンに合わせてCameraPositionの位置が動きます。
MyCameraにMoveMyCameraという新しいスクリプトを作り取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using UnityEngine; using System.Collections; public class MoveMyCamera : MonoBehaviour { // カメラの位置を置く場所 public Transform cameraPos; void LateUpdate () { // LateUpdateで行わないとIKを使った体の向きとのズレが生じてしまう // カメラ位置をHeadの子要素に作った空オブジェクトの位置に調整 transform.position = cameraPos.position; } } |
インスペクタでCameraPosには先ほど作ったCameraPositionを設定します。
LateUpdateメソッド内でMyCameraの位置を常にCameraPositionの位置に変更しています。
ややこしい感じになっていますが、IKで体を傾けているのとか、カメラ自体の角度を変更していたりするのでこんな感じになったような記憶があります。
もっと簡潔にキャラクター視点カメラの操作が出来るような気もしますが、まぁそこら辺は試してみてください。
キャラクターを斜め前から写すカメラ
次にCharacterの子要素にもうひとつカメラを作成し名前をTheSkyCameraとします。
カメラのインスペクタでAudio Listenerを削除しておきます。
Audio Listenerは音声の耳の役割をするものでゲーム内でひとつあればいいので、メインカメラにすでに取り付けてあるのでTheSkyCameraの方は削除しておきます。
MyCameraのAudio Listenerを削除しなかったのは、こちらはメインカメラと切り替えるカメラなので、どちらか一方にはAudio Listenerが必要になるからです。
↑のように、キャラクターを斜め前から写すような位置と角度に変更します。
Viewport RectでX、Yを0.7、W、Hを0.3にします。
フリーフォールの動きを確認するカメラ
フリーフォールの動きを遠目から確認出来るカメラも作成しておきます。
ヒエラルキー上で右クリック→Cameraを選択し名前をFixedPointCameraとします。
こちらはCharacterの子要素ではなく、親要素を持たないように作ります。
↑のようにCharacterの子要素には置きません。
FixedPointCameraの位置と角度を調整し、
↑のようにフリーフォールを遠目に写すようにします。
カメラのViewport RectでXを0、Yを0.7、W、Hを0.3にします。
カメラ切り替えスクリプト
通常のカメラとキャラクター視点のカメラを切り替えるスクリプトを作成します。
ヒエラルキー上で右クリック→Create Emptyを選択し名前をManagementとします。
ManagementにChangeCameraという新しいスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | using UnityEngine; using System.Collections; public class ChangeCamera : MonoBehaviour { public enum CameraMode { main, freeFall } public GameObject mainCamera; public GameObject freeFallCamera; // カメラ切り替え処理 public void Change (CameraMode mode) { // メインカメラをアクティブにする if(mode == CameraMode.main) { mainCamera.SetActive (true); freeFallCamera.SetActive(false); // フリーフォールカメラ(一人称視点)をアクティブにする } else if(mode == CameraMode.freeFall) { freeFallCamera.SetActive (true); mainCamera.SetActive (false); } } } |
publicなフィールドでメインカメラとキャラクター視点のカメラを選択出来るようにしておきます。
Changeメソッドでは受け取った引数によってメインカメラとキャラクター視点カメラのオン・オフをしています。
フリーフォール機能の確認
これで全ての解説は終わったと思います。
抜けがあるかもしれませんが・・・・。
ちなみにFreeFallCharaのインスペクタの設定は
↑のようになりました。
最初にすでに動画で紹介しているのでまた機能の確認するのも意味はないんですが再度確認してみましょう・・・(^_^;)
MyCamera、FreeFallButton、InfoUIは最初は見えない状態にする為、インスペクタで名前の横のチェックを外しておきます。
最初に紹介した動画との違いを出す為に、もっと高い位置まで昇って落ちるような設定にして確認してみましょう。
最初に紹介した動画では
ArrivalPointYは100
UpSpeedを5
DownSpeedを3
WaitCountを5
としていました。
今回は
ArrivalPointYを1000
UpSpeedを100
DownSpeedを1
WaitCountを3
にします。
また、TheSkyCameraとFixedPointCameraを無効化して、景色をもっと楽しめるようにします。
↑のようになりました。
あんまり臨場感がありませんね・・・・・(^_^;)
周りの景色がもっと広くて素晴らしければ良かったんですが・・・・。
VR(バーチャルリアリティ)に対応すると楽しそうな気もしますが、持っていないので対応も出来ないし確認も出来ません・・・・(T_T)/~~~
ちなみにUnityの実行がカクカクの場合はマッチターゲットがうまく働かず空中椅子をしたり本来の位置とは違うところに座ってしまいます。
直せないのでこのままで・・・・(._.)
いやぁ・・・この記事作成するの大変すぎですよ・・・・(ーー;)
もう長い記事は書きたくない・・・・(;一_一)