Unityでインディゲーム道!

プログラム、Unity初心者がインディゲーム制作を目指して日々思うことなどを書き綴ります。

動く足場とプレイヤーが一緒に動くためには!"Rigidbody"による対処法検証!その1

"Rigidbody"を使った動く足場の作り方、それに乗ったプレイヤーと足場を同期させる方法について。現時点での解決法のまとめ!

 

この行ったり来たりする、宙に浮く足場というものは、マリオをはじめたとしたアクションゲーム(海外で言うところの"Platformer")において、なくてはならないものです。早い段階から作りたかったのですが、まともな形で実現できたのは最近になります。改善の余地はあるかと思いますが、とりあえず、経緯含め書き出してみました。

※近いうちに図、コードなどを挿入する予定です。

 

 動く足場の課題

アクションゲームにおいて、動く足場というのは、なくてはならない仕掛けだと思います。上下、左右に往復する足場、これを踏み外せばゲームオーバー!!

よりダイナミックで、スリリングなレベルデザインが可能になるわけですが、その実現には、解決しなければいけない、ふたつの課題があります。 

  • 決まった幅を決まった周期で往復する。

ただ動けばいいわけではありません。コントロールされた反復運動である必要があります。ゲームが詰んでしまいますからね。 

  • 乗ったプレイヤーが、足場と一緒に動く

慣性の法則により、そこに乗ったプレイヤーも、足場と同期して動く必要があります。 

いやそもそも宙に浮く足場って・・・、突っ込みはナシで。

 

 動く足場

まずUnityの場合、ゲームオブジェクトを動かすのに、二つの移動方法があります。

TransformによるものとRigidbodyによるものです。これらを使って、まず上下左右を行ったりきたりする足場を作るわけです。

 

最初に思いついたのは、Transform由来の方法です。例えばVector3.Lerpを使い、あらかじめ設定した2つの地点を行ったり来たりする方法があります。ただしこの方法は、いちいち左右の地点を指定したり、ワンループごとに進行方向をif文などで切り替えないといけないんですよね。(違う方法あったらすいません。)これだけだと少しスマートではないな、と。

 

で思いついたのがMathf.Sinを使う方法です。三角関数なんてすっかり忘れてますが、要はサイン波であり、反復運動なわけです。これを上手く使えれば、シンプルなコードに出来るんでは?と思いました。

  

 Mathf.Sinについて

引数はfloatによるラジアン(radian)になります。ラジアンとは、その半径と同じだけの円周を取ったときの角度です。1ラジアンはおよそ57.3度です。

当然円は360度で一周ですので、つまりおよそ6.283ラジアンをもって、サイン波でいうところの上がって下がって戻ってきたというワン・ループになります。

Mathf.Sin(0.0f) ~ Mathf.Sin(6.283f)までですね。

返り値としては、時間軸毎に0 -> 1 -> 0 -> -1 -> 0

という感じで、float値が帰ってきます。つまりゼロを中心とした反復運動になっているわけです。float "speed"を代入演算子でドンドン足していって、6.283以上になったら、0,0fにリセットして・・・という感じでループさせます。

 

初期位置Vector3 centerとして、そこを基点に、このMathf.Sinによる値を利用して、Transformを計算し、Update()を使い、transform.positionを随時指定していく、という方法になります。

これは見た動きの感じもスムーズですし、かなり満足行く結果になりました。しかし、実際にプレイヤーを操作して、そこに乗せてみると二番目の課題が!

  

 プレイヤーが一緒に動かない!

プレイヤーキャラは、Sphereつまりボールを使っているのですが、実際乗せてみると、足場と同期せずに、そのままだとドンドンずれていきます。

そういう足場である、と言い通せば(仕様です、という魔法の言葉)いいレベルではあるかもしれませんが、やはり不自然ではあります。

 

ググッタ結果、コリジョンにプレイヤーのparent(Transform.SetParentというメソッドもあります。)足場に設定する、という方法を見つけました。それを試しては見たんですけども、動きは鈍くなり、かなり不自然な動作になってしまいました。足場のScaleの影響で乗った瞬間、形がおかしくなったりします。より細かい設定が必要なのかもしれませんが、この方法はあきらめました。

 

ということで、とりあえずこの問題は保留に。

  

 Rigidbodyによる解決

この際、Rigidbodyを使って動かして、プレイヤーのRigidbodyと同期させたらどうなんだろう?と思ったんですが、まず足場が上手く動かせなかったんですよね。

で後日、Rigidbody.MovePositionを使えばいいことに気がつきました。先ほどのMathf.Sinと、このMovePositonを使って足場を動かします。この際、足場のRigidbodyの設定として、rotation.yはfreezeにしてクルクルしないように設定。

 

そして足場のOnCollisionStayに接触したプレイヤーから、Rigidbodyの参照を得るコードを書きます。でそのRigidbody、ここではplayerRbとしてます。

そのplayerRb.positionを足場の移動先のVector3である、movePositionを代入するとどうなるかというと、足場の中央に埋まったボールが、足場と一緒に動きます。これは使えるな、と思いました。それからいろいろ試したんですが、結果はシンプルなコードになりました。 

playerRb.position = movePos + playerRb.collider.transform + transform.position;

こんな感じになりました。どうなんですかね、よくわかんないっす。

movePosというのは足場の動く位置ですね、これはあくまで相対的な位置、足場内部での位置関係なので、プレイヤーと足場のワールド座標も足して、調整してるという感じになると思います。とりあえずこれでひとまずそれっぽくはなりました。

 

ただし、この方法には明らかな問題が見つかっています。Interpolationの設定です。通常、プレイヤーのInterplation設定は、Interpolationに設定し、それ以外のRigidbodyに関しては、オフにすべきだということが推奨されています。

しかし、この方法において、プレイヤのInterpolationがオンになっていると、乗った瞬間からガクガクになります。解決法としては、乗ってOnCollisionEnterが効いている間Interpolationnoneにするという手段をとっています。

  

 ひとまず、まとめ

というわけで結構なゴリ押しで、いろいろ試してみた結果、よくわからんけど上手くいった!という流れなので、これでいいのか?という気もします。

実際、何かしら問題もあるのかもしれませんが、表面的な動作はかなり自然です。まだまだ検証は必要かと思いますが、しかしまともなゲームを作るためのピースはそろってきたように思います!動く足場があれば、実際ゲームとしての幅は、かなり広がりますからね!Unityかなり面白くなってきました!!!