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に取り付けます。

↑のように経過時間を感覚時間で割った秒数に応じて現在の角度(angle)からdirection * limitAngleの角度の間でスムーズに補間する角度を計算しangleに代入します。

ロープの角度が限界角度の絶対値との差が1度より下になったら向きを反転させています。

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

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

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

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

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

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

Unityでキャラクターがジャンプ出来るようにする
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メソッドでは、キャラクターの状態を変更し、AnimatorControllerのアニメーションパラメータの設定や移動値の初期化等を行っています。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

手をロープに合わせる

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

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

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

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

IKに関しては

Unityの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の角度をキャラクターの向きに合わせて回転させる処理だと思ってください。

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

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)にし、ロープが動いている方向にキャラクターを動かすことにします。

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

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

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

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

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

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

キャラクターに移動値を加える方向はロープを離した時の方向なのでそれをxDirectionに保持しておきます。

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

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

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

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

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

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

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

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

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

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

ロープを動かすMoveRopeスクリプトの改造

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

defaultLimitAngleはロープの限界の角度のデフォルト値で、limitAngleは現在のロープの限界角度です。

キャラクターがロープを離した時はこのlimitAngleの値を段々小さくすることでロープの動きを弱めていきます。

その弱めていくスピードがundoSpeedになります。

moveFlagがtrueの時はキャラクターがロープを掴んでいる時でロープを動かします。

ロープを離した時はlimitAngleの値を徐々に小さくし、ロープの動きを弱めます。

SetDirectionメソッドはキャラクターが飛び乗った方向を指定する時に使用します。

SetMoveFlagはmoveFlagのオンオフをするメソッドでそれ用の設定変更を行います。

directionをロープを離した時にも使用すると後で困りますので・・・・別のフィールドに保持しておきます。

ロープとキャラクターが接触した時のCatchTheRopeスクリプトの改造

次にCatchTheRopeに追記します。

キャラクターの状態がロープを掴んでいる時やロープを離した後地面に接地していないときは何もしないようにします。

それ以外でロープに接触したらMoveRopeスクリプトのSetMoveFlagの引数にtrueを渡してロープが動き出すようにします。

ロープの動かす方向を設定するRopeAreaスクリプトの作成

次にロープにキャラクターが接触した向きによってロープを動かす方法を決めるので、検知エリアを左右に作成しそこに侵入した時にMoveRopeスクリプトのSetDirectionメソッドを呼び出し方向を設定することにします。

ジャンプエリアのRightAreaとLeftAreaを作成

↑のようにRightAreaとLeftAreaという名前の空のゲームオブジェクトを作成し、BoxColliderを取り付けIs Triggerにチェックを入れて検知範囲とします。

RopeAreaスクリプトを取り付ける

RightAreaとLeftAreaにはRopeAreaスクリプトを取り付けます。

Playerタグを設定したキャラクターが検知範囲に入ったら、そのゲームオブジェクトの名前でロープを動かす方向の設定をしています。

実際の検知エリアの領域は

RightAreaとLeftAreaの実際の領域

↑のような感じに作成しました。

検知エリアを作るのではなくキャラクターがロープのエリアのどこに接触したかで判断し動かす向きを設定する事も出来そうですが、難しいので今回は検知エリアで方向を決めてしまいます。

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

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

↑のようになりました。

ロープをHingeJointとRigidbodyを使って動かす

2019/07/17に追記した内容になります。

ここまででキャラクターがロープに飛んで掴み離すという機能は出来ましたが、ロープを動かすのにスクリプトで細かく制御していて分かり辛くなっています。

そこでロープにはHingeJointとRigidbodyを取り付け、キャラクターがロープと接触した時にロープに力を加えて動かすだけでロープ自体の動きをスクリプトで動かさないようにしてみます。

まずはロープに上る階段とロープを1セットにして名前をJointRopePrefabとし、Assetsエリア内にドラッグ&ドロップをします。

HingeJointとRigidbodyで動かすロープの階層

子要素にあるどちらの階段から侵入したか検知しているRightAreaとLeftAreaは使わないので削除します(上の画像では削除済みです)。

RopeBaseに設定していたMoveRopeとCatchTheRopeスクリプトを削除します。

RopeBaseのTagに新しくRopeを作成し設定しておきます。

RopeBaseにインスペクタのAdd ComponentからHinge Jointを取り付けると一緒にRigidbodyも取り付けられます。

RigidbodyのUse Gravityにチェックを入れて重力を働かせ、Dragを0.1にして空気抵抗を加えます。

Hinge JointのUse Limitにチェックを入れMinに―90、Maxに90を設定しロープが回転しすぎないようにします。

RopeBaseのインスペクタの新しい設定

RopeBaseに新しくRopeScriptを作成し取り付けます。

RopeScriptはCatchTheRopeスクリプトに記述していた内容を一部移しました。

またインスペクタで設定していた手の位置等はStartメソッドで階層下から設定することにします。

oldAngleはUpdateメソッドが呼ばれる度にロープのRotationのZの値を入れます。

AlignDirectionOfIKメソッドではキャラクターがロープに飛び乗った時にIKの角度をキャラクターの向きに合わせて変更する為の処理です。

Swingメソッドはキャラクターがロープに捕まっている時にブランコを漕ぐのと同じようにロープに力を加えた時に呼び出す処理です。

引数ではキャラクターの前方方向と漕ぐ力を受け取ります。

角度の計算ではロープの下向きが0でX軸の正の方向が0~90、負の方向が270~360になるので、-90から90の間に補正しています。

ロープに力を加える時はキャラクターの前方方向とロープの前方方向(この記事ではX軸の方に動く)からVector3.Dotで内積を求め0より大きければキャラクターの横より前方、0より小さければキャラクターの横より後方になります。

つまりキャラクターの前方方向とロープの前方からキャラクターがどっちからロープに捕まっているかを計算しています。

その向きに合わせてロープを漕げる角度の範囲を指定しロープに力を加えています。

文章では分かり辛いので画像で説明します。

内積が0以上の時は下のようにキャラクターが右を向いていて、赤い四角で囲まれている部分で上から下にロープが回転している時がロープを漕げる場所になり、

内積が0より大きい時のロープを漕げるとき

内積が0より小さい時は下のようにキャラクターが左を向いていて、赤い四角で囲まれている部分で上から下にロープが回転している時にロープを漕げます。

内積が0より下の時でロープを漕げるとき

ロープが上から下へと回転している時に前回のロープのZの角度と今のロープの角度を調べています。

次に主人公の操作スクリプトとロープに接触した時のスクリプトを新しく作ります。

主人公操作スクリプトはJumpToRopeChara2でロープに接触した時のスクリプトはCatchTheRope2という名前にします(名前はちゃんとした名前を付けてください)。

主人公にこの二つのスクリプトを取り付けます。

HingeJointとRigidbodyのロープに捕まるキャラクターのインスペクタ

JumpToRopeChara2スクリプトはJumpToRopeCharaスクリプトとほとんど同じです。

キャラクターの状態がロープを掴んでいる時にEnterキー(return)を押したらRopeScriptのSwingメソッドでロープを漕ぎます。

SetStateメソッドでキャラクターがロープを掴んだ状態に変更する時にCharacterControllerを無効化し、ロープを離した時に有効にする処理を記述しています。

CharacterControllerのコライダが邪魔してロープに影響を与えてしまう為です。

SetRopeDataでRopeScriptを保持します。

GetSwingPowerメソッドでロープを漕ぐ力を返します。

次にCatchTheRope2スクリプトを見ていきます。

OnControllerColliderHitはCharacterControllerで移動中に他のコライダと接触した時に呼ばれます(動いていない時は呼ばれないので別途それ用の処理が必要かもしれません)。

キャラクターがRopeという名前のタグを設定されたコライダと接触したらキャラクターの親をロープにします。

キャラクター操作スクリプトのSetStateで状態と接触したロープのRopeScriptを引数として渡します。

RopeScriptと同じゲームオブジェクトに設定されているRigidbodyにAddForceメソッドで力を加えます。

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

上のようになります。

ロープにHinge JointとRigidbodyを付けるだけで振り子運動をさせることが出来ますし、

ロープ自体をスクリプトを使って動かすのではなくキャラクターが接触した時とロープを漕いだ時だけロープのRigidbodyに力を加えるだけなので結構楽ですね。

ロープを漕ぐ時にアニメーションも変化させてアニメーションに合わせてロープに力を加えるとより良くなりそうですね。

終わりに

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

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

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

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

タイトルとURLをコピーしました