Unityを使用してプレーヤーの動きを3Dオブジェクトの表面に制限するにはどうすればよいですか?


16

マリオギャラクシーやジオメトリウォーズ3に似たエフェクトを作成しようとしています。プレイヤーが「惑星」の重力を歩き回ると、重力のようにオブジェクトの端から落ちません。一方向に修正されました。

ここに画像の説明を入力してください
(ソース:gameskinny.com

ジオメトリ戦争3

私は、重力を持たなければならないオブジェクトが他の剛体を引き寄せるアプローチを使用して、探しているものに近いものを実装することができましたが、Unityの組み込みの物理エンジンを使用して、AddForceなどで動きを適用し、運動を正しく感じることができませんでした。プレイヤーがオブジェクトの表面から飛び始めない限り、プレイヤーを十分に速く動かすことができず、これに対応するために加えられた力と重力の良いバランスを見つけることができませんでした。私の現在の実装は、ここで見つかっものの適応です

ソリューションがおそらく物理学を使用して、プレイヤーがサーフェスを離れる場合にオブジェクトに接地するようにしますが、プレイヤーが接地されたら、プレイヤーをサーフェスにスナップして物理学をオフにし、他の方法でプレイヤーを制御しますが、私には本当にわかりません。

プレーヤーをオブジェクトの表面にスナップするには、どのようなアプローチをとるべきですか?ソリューションは(2Dではなく)3D空間で動作し、Unityの無料バージョンを使用して実装できる必要があることに注意してください。



壁の上を歩くことを探すことすら考えていませんでした。これらが役立つかどうかを確認します。
スパルタンドーナツ

1
この質問が特にUnityで3Dでこれを行うことに関するものである場合は、編集してより明確にする必要があります。(その場合、既存のものと完全に重複することはありません。)
アンコ

それも私の一般的な感覚です-そのソリューションを3Dに適応させて回答として投稿できるかどうかを確認します(または、他の誰かが私を打ち負かすことができるなら、私も大丈夫です)。質問をより明確にするために更新してみます。
スパルタンドーナツ

潜在的に役立つ:youtube.com/watch
v

回答:


7

私は必要に応じて、主にパズルの表面をスナップするためのこのブログ投稿の助けを借りて、プレイヤーの動きとカメラに関する独自のアイデアを思いつきました。

プレーヤーをオブジェクトの表面にスナップする

基本的なセットアップは、大きな球体(世界)と小さな球体(プレイヤー)で構成され、どちらにも球体コライダーが接続されています。

行われた作業の大部分は、次の2つの方法で行われました。

private void UpdatePlayerTransform(Vector3 movementDirection)
{                
    RaycastHit hitInfo;

    if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
    {
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
        Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);

        transform.rotation = finalRotation;
        transform.position = hitInfo.point + hitInfo.normal * .5f;
    }
}

private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
    Vector3 newPosition = transform.position;
    Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);        

    if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
    {
        return true;
    }

    return false;
}

Vector3 movementDirectionパラメーターは、このフレームでプレーヤーを移動する方向であり、この例では比較的単純になりますが、このベクトルで計算する方向は、私が最初に理解するのが少し面倒でした。それについては後で詳しく説明しますが、プレーヤーがこのフレームを移動する方向の正規化されたベクトルであることに注意してください。

最初に行うことは、スクリプトのパブリックLayerMaskプロパティであるWorldLayerMaskを使用して、プレイヤーダウンベクトル(-transform.up)に向けられた架空の未来の位置から発生するレイがワールドにヒットするかどうかを確認することです。より複雑な衝突や複数のレイヤーが必要な場合は、独自のレイヤーマスクを作成する必要があります。レイキャストが何かに成功した場合、hitInfoを使用して法線とヒットポイントを取得し、オブジェクト上にあるはずのプレーヤーの新しい位置と回転を計算します。プレーヤーの位置のオフセットは、問題のプレーヤーオブジェクトのサイズと起源に応じて必要になる場合があります。

最後に、これは実際にテストされただけであり、球などの単純なオブジェクトでのみうまく機能する可能性があります。私のソリューションのベースとなったブログ記事が示唆しているように、複数のレイキャストを実行し、それらをあなたの位置と回転に対して平均化して、より複雑な地形を移動するときに、より良い遷移を得ることができます。この時点では考えもしなかった落とし穴もあるかもしれません。

カメラと動き

プレイヤーがオブジェクトの表面にくっついたら、次に取り組むべきタスクは動きでした。私はもともとプレイヤーに対する動きから始めていましたが、球の極で問題が発生し始めたため、方向が突然変わり、プレイヤーは極を通過させずに何度も何度も方向を変えました。私がやったことは、カメラに対するプレイヤーの動きを作ることでした。

私のニーズに合ったのは、プレーヤーの位置のみに基づいて、プレーヤーに厳密に追従するカメラを持つことでした。その結果、カメラが技術的に回転していても、上に押すと、常に画面の上部に向かって、下部に向かって下に、というようにプレイヤーが移動しました。

これを行うために、ターゲットオブジェクトがプレーヤーであるカメラで次のコマンドが実行されました。

private void FixedUpdate()
{
    // Calculate and set camera position
    Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
    this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);

    // Calculate and set camera rotation
    Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
    this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}

最後に、プレーヤーを移動するために、メインカメラの変換を活用して、コントロールを上下に移動します。そして、ここでUpdatePlayerTransformを呼び出して、位置をワールドオブジェクトにスナップさせます。

void Update () 
{        
    Vector3 movementDirection = Vector3.zero;
    if (Input.GetAxisRaw("Vertical") > 0)
    {
        movementDirection += cameraTransform.up;
    }
    else if (Input.GetAxisRaw("Vertical") < 0)
    {
        movementDirection += -cameraTransform.up;
    }

    if (Input.GetAxisRaw("Horizontal") > 0)
    {
        movementDirection += cameraTransform.right;
    }
    else if (Input.GetAxisRaw("Horizontal") < 0)
    {
        movementDirection += -cameraTransform.right;
    }

    movementDirection.Normalize();

    UpdatePlayerTransform(movementDirection);
}

より興味深いカメラを実装しますが、コントロールをここにあるものとほぼ同じにするには、レンダリングされていないカメラ、または動きのベースとなる別のダミーオブジェクトを簡単に実装し、より興味深いカメラを使用してゲームは次のようになります。これにより、コントロールを壊さずにオブジェクトを移動する際に、カメラの切り替えがうまくなります。


3

このアイデアはうまくいくと思います:

プラネットメッシュのCPU側のコピーを保持します。メッシュの頂点を持つことは、惑星上の各ポイントに法線ベクトルがあることも意味します。次に、すべてのエンティティの重力を完全に無効にし、代わりに法線ベクトルとまったく反対の方向に力を加えます。

さて、どの点に基づいて、惑星のその法線ベクトルを計算する必要がありますか?

最も簡単な答え(これは大丈夫でしょう)は、ニュートンの方法と同様に近似することです:オブジェクトが最初に出現するとき、惑星上のすべての初期位置を知っています。その初期位置を使用して、各オブジェクトのupベクトルを決定します。明らかに重力は反対方向(に向かってdown)になります。次のフレームでは、重力を適用する前に、オブジェクトの新しい位置から古いdownベクトルに向かって光線を投射します。その光線と惑星との交点を、upベクトルを決定するための新しい参照として使用します。光線が何も当たらないというまれなケースは、何かがひどく間違っていることを意味し、オブジェクトを前のフレームの位置に戻す必要があります。

また、この方法を使用すると、プレーヤーの原点が惑星から遠くなるほど、近似が悪くなることに注意してください。したがって、各プレイヤーの足の周りのどこかを起点として使用することをお勧めします。私は推測していますが、足を原点として使用すると、プレーヤーの取り扱いとナビゲーションが容易になると思います。


最後のメモ:より良い結果を得るために、次のことを行うこともできます。各フレームでのプレーヤーの動きを追跡する(例:を使用current_position - last_position)。次に、movement_vectorオブジェクトの方向の長さupがゼロになるようにクランプします。この新しいベクトルを呼び出しましょうreference_movement。前回の移動reference_pointにより、reference_movementおよびレイトレーシングの原点として、この新しいポイントを使用しています。光線が惑星に衝突した後(および衝突した場合)、reference_pointその衝突点に移動します。最後に、upこの新しいから、新しいベクトルを計算しますreference_pointます。

それを要約するいくつかの擬似コード:

update_up_vector(player:object, planet:mesh)
{
    up_vector        = normalize(planet.up);
    reference_point  = ray_trace (object.old_position, -up_vector, planet);
    movement         = object.position - object.old_position;
    movement_clamped = movement - up_vector * dot (movement, up_vector);

    reference_point  += movement_clamped;
    reference_point  = ray_trace (reference_point, -up_vector, planet);
    player.up        = normal_vector(mesh, reference_point);
}

2

この投稿は役に立つかもしれません。その要点は、キャラクターコントローラーを使用せず、物理エンジンを使用して独自のコントローラーを作成することです。次に、プレーヤーの下で検出された法線を使用して、メッシュの表面に向けます。

ここに素敵な概要がありますテクニックのがあります。「3Dオブジェクトマリオギャラクシーのユニティウォーク」などのWeb検索用語に関するリソースは他にもたくさんあります。

Unity 3.xのデモプロジェクトもあります。これには、歩行エンジンがあり、兵士と犬がいて、シーンの1つにあります。Galaxyスタイルの3Dオブジェクトを歩くデモを行いました。それはルーンビジョンによる移動システムと呼ばれています。


1
一見したところ、デモはうまく機能せず、Unityパッケージにはビルドエラーがあります。この作業を行えるかどうかは確認しますが、より完全な回答があれば幸いです。
スパルタンドーナツ14

2

回転

この質問の答えは、answers.unity3d.com(実際に自分で尋ねました)で確認してください。見積もり:

最初のタスクは、上を定義するベクトルを取得することです。図面から、2つの方法のいずれかでそれを行うことができます。惑星を球体として扱い、(object.position-planet.position)を使用できます。2番目の方法は、Collider.Raycast()を使用し、それが返す「hit.normal」を使用することです。

彼が私に提案したコードは次のとおりです。

var up : Vector3 = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

すべての更新を呼び出して、それを機能させる必要があります。(コードはUnityScriptにあることに注意してください)。C#
の同じコード:

Vector3 up = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

重力

重力については、質問で説明した「惑星物理学手法」を使用できますが、実際には最適化されていません。それはただ思いついたものでした。
重力のために独自のシステムを作成することをお勧めします。

ここでセバスチャンLagueによるYouTubeのチュートリアルが。これは非常に効果的なソリューションです。

編集:統合で、編集 > プロジェクト設定 > 物理に移動し、重力のすべての値を0に設定(またはすべてのオブジェクトからリジッドボディを削除)して、組み込みの重力(プレーヤーをプルダウンする)が競合しないようにしますカスタムソリューション。(消して)


プレイヤーを惑星の中心に引き寄せる(そしてそれに応じて回転させる)ことは、ほぼ球形の惑星ではうまくいきますが、ロケット型のマリオが最初のGIFでジャンプしているように、球形でないオブジェクトでは本当に間違っていると想像できます。
アンコ14

たとえば、非球体の内部でX軸の周りのみに移動するように制限されたキューブを作成し、プレイヤーが移動するときにそれを移動して、プレイヤーの下を常に真っ直ぐにしてから、プレイヤーを引っ張ることができますキューブまで。それを試してみてください。
ダニエルクビスト14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.