Unityでロープに捕まって崖から崖へ渡る機能の作成

今回は、崖から崖へといった高い所から高い所へとジャンプでは渡れない場所があった時、ロープに捕まって渡れるような機能を作成してみたいと思います。

ゴッドオブウォーやゴッドオブウォー2やゴッドオブウォー3にあるような鎖を掴んで飛ぶようなやつですね。

それ以外のゲームが思いつきませんでした・・・・(^_^;)

キャラクターにジャンプ機能を搭載していれば穴がある場所も飛ぶことは出来ますが、ジャンプでは到達出来ない部分に行く為に上からロープや鎖のようなものがぶら下がっており、それを使ってブランコの要領?で飛び越えられるような機能です。

スポンサーリンク

ロープを掴んで崖を渡る機能の作成

それではロープを掴んで崖を渡る機能を作成していきましょう。

崖とロープを含んだステージの作成

まずは、崖とロープのゲームオブジェクトを配置していきます。

サンプルなので崖はCubeで作成し、ロープはCylinderで作成します。

ヒエラルキーで右クリック→Create Emptyで空のゲームオブジェクトを作成し、名前をStageとします。

Positionは(0, 0, 0)、Rotationを(0, 0, 0)、Scaleを(1, 1, 1)とします。

機能が完成したら位置を変更しても問題はありません。

Stageの子要素にCubeで崖を作成します。

Cubeで崖の作成

↑のようにStageの子要素にCubeで作成します。

Cubeで作成した崖のサンプル

↑が作成した崖のサンプルです。

崖はただの土台として作成しているだけなので、今回の機能とはあまり関係がありません。

崖以外の地面はPlaneで作成し、ワールド空間に作成します。

キャラクターはStandardAssetsのEthanを配置し、AnimatorControllerの設定をしておきます。

キャラクター操作スクリプトは、後で作りますのでおいておきます。

次にロープを作成します。

Stageの子要素に右クリック→3D Object→Cylinderを作成し名前をRopeとし、Scaleを(0.1, 1.5, 0.1)とします。

RopeのCapsule ColliderのIs Triggerにチェックを入れ、物理的に当たらないようにしておきます。

その後Ropeの子要素にCreate Emptyで空のゲームオブジェクトを作成し、名前をRopeBaseとします。

RopeBaseの位置を調整しRopeの上に来るようにします。

Ropeの子要素にRopeBaseを作成

RopeBaseはRopeを回転させる時のBaseの位置になるもので、後でRopeをRopeBaseの子要素にします。

RopeBaseの位置をRopeの上に移動させる

RopeBaseを選択した状態で位置をRopeの上になるように調整します。

調整が終了したらRopeBaseをドラッグしStageの上でドロップします。

するとRopeとの親子関係が解除されるので、次はRopeをドラッグしRopeBaseの上でドロップします。

これでRopeBaseが親、Ropeが子になりました。

なぜこんな面倒くさい事をしたかというと、RopeBaseの位置をRopeの上に合わせる為です。

RopeBaseとRopeの親子関係を逆にしたので、RopeBaseのScaleを(1, 1, 1)、Ropeを(0.1, 1.5, 0.1)に変更します。

ここまできたらRopeBaseの位置を調整し、崖と崖の間に移動させます。

崖と崖の間にロープを配置

崖やロープにマテリアルを設定し、見た目を変えてみてもいいですね。

これで崖とロープが完成しました。

ロープを動かす

ロープが完成したので、ロープを左右に動かすようにしましょう。

Z軸の向きを確認

RopeBaseの前方は↑のように青軸の向きとなっています。

つまりロープを左右に振るにはこの青軸を中心に回転させればいい事になります。

新しくMoveRopeスクリプトを作成し、RopeBaseに取り付けます。

↑のようにMathf.Sinを使って-1~1の間の数値をUnity実行からの経過時間で変化させて取得し、それに90をかける事で-90~90度Z軸を回転させる事が出来ます。

ロープが左右に振られるようになった

ベースのキャラクター操作スクリプト

ロープに捕まり移動するキャラクターを作る前に、とりあえず移動とジャンプが出来ないといけません。

そこでベースとなるスクリプトをまずは作成します。

スクリプトの詳細については

Unityで3Dキャラクターモデルを配置し、キャラクターをCharacterControllerの機能を使って移動させるようなプログラミングをしてみます。

Unityのゲームでキャラクターがジャンプ出来るようにする機能を作成します。

を参照してください。

今回はこのスクリプトに処理を追加していきます。

さてどれから作成しましょうか・・・・(-_-)

キャラクターをロープに張りつかせる

キャラクターがロープにジャンプした時に、キャラクターがロープと一緒に動くようにしなければいけません。

そこでロープにキャラクターを検知するエリアを作成し、キャラクターが侵入した時にロープに張りつかせるようにしましょう。

ロープに張りつかせるというのは難しい感じがしますが、実は簡単です。

キャラクターをロープの子要素に配置してしまえば、ロープと一緒に動くキャラクターになります。

まずはRopeBaseにAdd ComponentからPhysics→Box Colliderを選択し取りつけます。

Is Triggerにチェックを入れ物理的に当たらないようにします。

ロープのキャラクター検知エリア

↑のような感じのキャラクター検知エリアとなるように、Box ColliderのCenterとSizeを調整します。

RopeBaseには、CatchTheRopeというキャラクターが侵入した事を検知するスクリプトを作成し取りつけます。

OnTriggerEnterメソッドでキャラクターが侵入してきた事を検知し、自身が保持しているキャラクターの状態がState.catchRopeでなければ、

キャラクターの親要素をこのスクリプトを設置しているRopeBaseにします。

その後、キャラクター操作スクリプトのSetStateメソッドを呼び出し、キャラクターの状態を変更しています。

SetStateはまだ定義していないのでこれから定義します。

処理の順番の関係で、キャラクターをロープの子要素にしてからSetStateでキャラクターの状態を変更する必要があります。

キャラクターがロープを掴んだ時は、キャラクターの移動や重力は働かせたくないので、キャラクター操作スクリプトを変更します。

以下のスクリプトは追加部分だけ記述しています。

キャラクターの状態を表すStateを宣言し、Startメソッド内でState.normalを設定します。

Updateメソッドでは、キャラクターの状態がState.normalの時だけ実行するように変更します。

SetStateメソッドでは、キャラクターの状態を変更し、Animatorのアニメーションパラメータの設定や移動値の初期化等を行っています。

これでキャラクターがロープの検知エリアに侵入したら、キャラクターがロープの子要素になりキーボードでの移動やジャンプ、重力が働かなくなります。

それでは実行してみましょう。

キャラクターが接触するとロープと共に動く

↑のように、キャラクターがロープと接触するとロープの子要素となり一緒に動いています。

キャラクターがロープの子要素になっている

ヒエラルキーを確認するとRopeBaseの子要素にキャラクターがいますね。

キャラクターの位置と角度を合わせる

先ほどキャラクターをロープの子要素にする事が出来ましたが、キャラクターの角度や位置がロープに合っていません。

キャラクターの到達点をRopeBaseの子要素に作成し、ロープを掴んだ状態の時は徐々に移動させるようにします。

また、キャラクターの角度はY座標だけを回転させロープの角度と合わせます。

まずはRopeBaseの子要素に到達点であるArrivalPointを作ります。

RopeBaseの子要素に空オブジェクトを作成し、名前をArrivalPointとします。

ロープの子要素に到達点を作成

↑のように子要素にArrivalPointが作成されました。

到達点はキャラクターの足の位置

ArrivalPointを移動させキャラクターをロープの子要素にした時に、キャラクターの足元が来る位置にArrivalPointを合わせます。

次にJumpToRopeスクリプトに追加していきます。

ロープを掴んでいる状態の時で、キャラクター位置が到達点に達していない時は、キャラクターの位置を少しづつ到達点に移動させます。

speedToRopeはインスペクタで設定出来るようにし、到達点までのスピードを設定します。

到達点に達したらmoveFlagをfalseにし、到達点移動処理の実行を止めます。

次にCatchTheRopeスクリプトを修正します。

CatchTheRopeスクリプトでは、ArrivalPointをインスペクタで設定出来るようにし、そのローカル位置を返すメソッドを定義します。

OnTriggerEnterメソッドでは、キャラクター操作スクリプトにCatchTheRopeスクリプトの参照を渡しています。

これで処理の修正が終わりました。

CatchTheRopeスクリプトのarrivalPointにArrivalPointゲームオブジェクトを設定し、JumpToRopeスクリプトのspeedToRopeに1を設定し確認してみます。

speedToRopeに1を設定した理由は到達点に至る過程を確認する為です。

本来であれば、もう少し数値を大きくし到達点に早く移動出来るようにします。

それでは実行してみましょう。

キャラクターが徐々に到達点に移動する

試しに横向きでジャンプしてみました。

ロープに接触すると、段々と到達点(ArrivalPoint)に移動していくのがわかると思います。

これで、キャラクターの位置と角度をロープと合わせる事が出来ました。

手をロープに合わせる

ここまでで、キャラクターをロープに合わせて動かす事が出来ました。

しかし、キャラクターは棒立ちのままなのでキャラクターがロープを掴むようにしてみます。

これにはIKを使って手を所定の位置に設定するようにします。

IKを使うと本来のアニメーションとは別に手の位置や角度、肘の方向を所定の位置に固定する事が出来ます。

IKに関しては

UnityのIKを使ってキャラクターが物を運んでいるアニメーションを作成していきます

等を参考にしてください。

まずはRopeBaseの子要素にIKで使う右手、左手、右ひじ、左ひじ交互に見て(謎)

の位置や角度を表す空のゲームオブジェクトを作ります。

親はSphereで作成し、MeshRendererのチェックを外します。

親のゲームオブジェクトも空のゲームオブジェクトで作成してもいいんですが、位置等を確認する時にSphereの方が便利かと思ってそうしました。

親の名前をIKとし、その子要素に右手の位置や角度を設定するRightHand、左手のLeftHand、右ひじの方向を表すRightElbow、左ひじの方向を表すLeftElbowの空のゲームオブジェクトを作成します。

位置や角度は、スクリプトでIKを有効にしてから調整しますのでおいておいてください。

IKを使うには、AnimatorのベースとなるレイヤーのIK Passにチェックを入れます。

AnimatorのIK Passにチェックを入れIKを有効にする

これでAnimatorを設定しているゲームオブジェクトのスクリプトで、OnAnimatorIKやOnAnimatorMove等のメソッドが呼ばれるようになります。

IKはこれらのメソッドの中で設定します。

まずは、CatchTheRopeスクリプトにIK関連の処理を追記します(追記処理だけ載せています)。

親のIKや右手、左手のゲームオブジェクトのTransformをインスペクタで設定出来るようにしておきます。

大元のIKゲームオブジェクトは、キャラクターがロープを掴んだ時の向きによって回転させるので、初期位置をStartメソッドで入れています。

キャラクターがロープを掴んだ時に、IKのローカル角度にキャラクターの右手方向のローカル方向の角度をかけた値を再度IKのローカル角度に代入しています。

ここら辺は難しいので、とりあえず元のIKの角度をキャラクターの向きに合わせて回転させる処理だと思ってください。

次にキャラクター操作スクリプトJumpToRopeに処理を追記します。

IK用のウエイト変数を用意し、ロープを掴んでいる間は0→1へとウエイトを足していきます。

OnAnimatorIKメソッド内でウエイトを設定し、右手や左手の位置や角度をRightHandやLeftHandの位置や角度に設定しています。

ウエイトはどれだけその位置や角度にするかの値で、0だとIKが働かず、1だと完全にその位置や角度になります。

つまり、徐々にIKを働かせる事によって、手の位置を少しづつRightHandやLeftHandに近づけているわけです。

徐々に手の位置を動かさなくてもいい場合は、ウエイトを1にするだけです。

それでは実行してみましょう。

↑のようにキャラクターの方向に合わせてIKゲームオブジェクトが回転し、手の位置が合うようになっています。

途中ジャンプしてもロープに接触出来てないですが・・・まぁ気にしないでください・・・・。

Is Triggerにチェックを入れるのを忘れていただけです・・・・(;一_一)

先にすでにRightHandやLeftHandの位置や角度を調整した状態を紹介しましたが、通常であれば手の位置や角度がおかしなところを向いているはずです。

IK用のゲームオブジェクトの位置や角度を調整します。

右側の崖から左向きに飛んだ時に、右手の位置や角度、左手の位置や角度が合うように調整してください。

調整の仕方はUnityを実行し、キャラクターを右側のがけから左にむかせロープに接触させます。

実行中のままRightHandの位置や角度を調整すると、キャラクターの右手も動きますので、RightHandのインスペクタの歯車をクリックしCopy Componentをします。

Unityの実行を停止したあと、RightHandの歯車からPaste Component Valuesを選択し値をペーストします。

面倒ではありますがRightHand、LeftHand、RightElbow、LeftElbowを一つ一つ実行、停止してベストな位置と角度を探します。

これでロープを掴む処理が完成しました。

ロープを離した時の処理を追加する

ここまででロープを掴んで手の位置や角度を合わせる事まで出来ました。

しかし、キャラクターがロープを掴んだまま離す事が出来ません。

そこで、Jumpキーを押した時にロープを離すようにしたいと思います。

ただ、ロープを離した時にキャラクターの状態をState.normalにするだけでは、ロープの振りの力をキャラクターの移動値に反映出来ません。

そこで、ロープを離してから地面に触れるまではロープを離した状態(State.releaseRope)にし、ロープからキャラクターの向きを算出しそちらに移動するようにします。

例えば、左から右へロープが移動している場合は左から右へ移動値を加えます。

右から左へロープが移動している場合は左へ移動値を加えます。

その為、今ロープが右向きに移動しているのか?左向きに移動しているのか?を知らなくてはいけません。

いやぁ・・・大変ですね・・・・(-_-)

まずはロープがどちら向きに移動しているかをMoveRopeスクリプトで調べます。

preValueが前の角度、valueが現在の角度、directionが方向で1が右向き、-1が左向きとしています。

前の値より現在の値が大きければ右向きにロープが回転しているので1、それ以外は左向きにロープが回転しているとしています。

次にJampToRopeスクリプトに追記します。

ロープを掴んでいる状態の時にJumpボタンを押したら、キャラクターの状態をState.releaseRopeにします。

キャラクターの状態がState.releaseRopeの時は、キャラクターの角度をロープを掴む前の状態に徐々に変化させます。

MoveRopeのdirectionによって分岐させ、移動値を加える方向を算出してます。

右向きに移動している場合でも、左から真ん中に向いている場合、真ん中から右に向いている場合等4パターンあります。

スクリプトを見ただけだと解り辛いので、実際試しながら方向を変えた方が早いかもしれません。

また、移動値には重力値も加えて下方向に移動させています。

地面に接地したらキャラクターの状態をState.normalにし、通常の移動やジャンプが出来るようにしています。

SetStateメソッドでは、キャラクターがロープを離した時に親子関係を解除したり、ロープを離した時のアニメーションの遷移をさせたりしています。

キャラクターがノーマル状態になったら、強制でキャラクターの角度をロープを掴む前の角度に設定しています。

これで機能が出来ました。

JumpToRopeのインスペクタのspeedToRopeの値を5にして実行してみます。

↑のようにキャラクターがロープを離した時に、ロープの向かっている方向に移動値を加える事が出来ました。

ロープが停止している状態から動かす

ロープが停止している時にキャラクターがロープに触れたら接触した方向へ動き出すようにしてみます。

またキャラクターがロープを離した後は、ロープが自然に停止するようにします。

残念ながらいまいちうまく動作させる事は出来ませんでした・・・・(^_^;)

ロープの動きはMathf.Sinを使って左右にきたら回転スピードを落としていますが、ロープに接触した方向に移動させようと思うとTime.timeでロープを動かしている為、キャラクターの接触時にそちらの方に移動させる処理がうまくいきません。

ロープの動きを単純にメトロノームのようなずっと同じ動きをさせれば力を加えるのももう少し単純に出来るとは思いますが、滑らかな動きを捨てる事が出来ず無理やり作ってみました。

その為、キャラクターがロープを離した時にロープが変な動きをする事があります。

あらかじめご了承ください。

それではまずMoveRopeを改造していきましょう。

Mathf.Sinを使うとうまく出来ないので

↑のように経過時間をdurationで割った割合でangleをsymbol * limitAngleに変更しています。

Mathf.SmoothStepは、第1引数を第2引数にスムーズに変更する為のメソッドです。

これで最初と最後のスピードは遅くなるので、Mathf.Sinと似たような処理にする事が出来ます。

angleがlimitAngleより3小さい値になったら振り子の動きを逆にします。

limitAngleが90だとしたら、振り子が90付近でなかなか動かなかったり、90にいかなかったりするので-3しました。

limitAngleになったら振り子を反対に動かす為、symbolに-1をかけて逆向きにします。

また、startTimeにTime.timeを入れないとどんどん振り子のスピードが上がってしまうので、振り子が逆に動き出したらその時点でのTime.timeを入れます。

moveFlagがfalseの時はキャラクターがロープを離した後のロープの動きの処理です。

ロープの動きが止まる(transform.localEulerAngles.yが0)まで処理を実行します。

paramの値は振り子が到達する角度で、時間経過とともに減らしていく事で振り子が段々と動かなくなるようにしています。

ロープを元に戻す処理ではMathf.Sinを使っているので多少ロープの動きが掴んでいる時と変わります。

SetMoveメソッドではロープを動かすかどうかのフラグのオン・オフをしています。

フラグがオフになった時は、paramに現在のangleを入れ、振り子の最大角度がangleになるようにしています。

また振り子の移動の向きはsymbolで判断しており、paramは絶対値を指定しますのでMathf.Absで絶対値を取得しています。

次にCatchTheRopeに追記します。

キャラクターのX軸がロープの根元の左からロープを掴んだのか、右から掴んだのかでロープのsymbol値を変更しています。

これで機能が完成しました。

実行して確認しましょう。

↑のようになりました。

多少ロープの動きが変になる時がありますが、それなりのものは出来たのではないでしょうか?

終わりに

今回の機能は、前々から作ろう作ろうと思っていた機能です。

もう少し簡単にこの機能を作れると思っていたんですが、結構大変でした・・・・。

かなり苦労したけど、この記事のアクセスも燦々たるものになるんでしょうね・・・・・・・・・・( ノД`)シクシク…

まぁ苦労していない記事など存在していませんが・・・・(+_+)

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます