質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

1928閲覧

重力の掛け方や地面接地判定に関して。

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2020/01/18 19:04

編集2020/01/18 19:06

前提・実現したいこと

こちらの動画で、
キャラクターコントローラの実装を勉強しているのですが、処理として意図が分からないものがあるため、
ご教示お願いします。
なお、動画では、ジャンプコードも実装していますが、そこに辿り着くまでに分からない点が多いので、
ジャンプコードに関しては今回考えないです。

試したこと

ジャンプコードは考えない、かつ、重力のログや地面接地判定の状態を調べる為、下記コードで試しました。

C#

1 [SerializeField] 2 float speed = 12f; 3 float gravity = -9.81f; 4 [SerializeField] 5 Transform groundCheck; 6 [SerializeField] 7 float groundDistance = 0.4f; 8 Vector3 velocity; 9 10 bool isGrounded; 11 12 // Start is called before the first frame update 13 void Start() 14 { 15 16 } 17 18 // Update is called once per frame 19 void Update() 20 { 21 float x = Input.GetAxis("Horizontal"); 22 float z = Input.GetAxis("Vertical"); 23 24 isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance); 25 26 if(isGrounded && velocity.y < 0){ 27 velocity.y = -2f; 28 } 29 30 Vector3 move = transform.right * x + transform.forward * z; 31 32 controller.Move(move * speed * Time.deltaTime); 33 34 velocity.y += gravity * Time.deltaTime; 35 36 controller.Move(velocity * Time.deltaTime); 37 38 Debug.Log(velocity.y); 39 40 } 41 42 void OnDrawGizmos(){ 43 Gizmos.DrawWireSphere(groundCheck.position, groundDistance); 44 }

構造は以下です。
Cubeをキャラクターと見立てて、Cubeにスクリプトやキャラクターコントローラをアタッチしています。
イメージ説明

・質問1。

C#

1 [SerializeField] 2 float groundDistance = 0.4f;

調整値だとは思いますが、これを0.4fとした場合、
groundCheckオブジェクトを下記のような位置にセットするのが、正しい設定ということですか?
(CheckSphereがプレイヤー自身に接触しない、かつ、地面として判定する場所にgroundCheckオブジェクトをセットするということでしょうか?)

イメージ説明

・質問2。

C#

1controller.Move(velocity * Time.deltaTime);

検索して重力加速度の公式がv=gtであることがわかったので、
上記コードで重力をかけるのはわかったのですが、動画では、上記コードを書く前に、
y=1/2gt^2の式を提示しているのですが(16:44辺り)、y=1/2gt^2はなぜ動画で提示されていますか?
(コードで使っている様子はなかったので。)

・質問3。
下記はどういう意図のコードでしょうか?
地面に設置してたら、重力加速度が無限に大きくなり続けるのを防ぐためにリセットしているかと思うのですが(そもそもその解釈で合っていますか?)、
まず、velocity.y < 0 ということを条件の1つとしていますが、
float gravity = -9.81f
で、
velocity.y += gravity * Time.deltaTime;
としているので、基本的にvelocity.y < 0 の状態になっているかと思うのですが、
velocity.y < 0 をチェックに入れる必要はありますか?

C#

1 if(isGrounded && velocity.y < 0){ 2 velocity.y = -2f; 3 }

あと、最初、動画では、

C#

1 if(isGrounded && velocity.y < 0){ 2 velocity.y = 0; 3 }

としていましたが、最終的にvelocity.y = -2f; にした理由は何ですか?
両方のパターンでゲームを実行して見てみたのですが、動きとしての違いがわかりませんでした。
ログでは、地面接地時にvelocity.yが0付近に留まっているか、-2付近に留まっているかの数の違いだけあることはわかりました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tatamyiwathy

2020/01/18 19:38

質問2については velocity.y += gravity * Time.deltaTime; で 時間を掛けてます。 そして velocity * Time.deltaTime で 速度にさらに時間を掛けて1/2gt^2のt^2に相当することを説明しています。
退会済みユーザー

退会済みユーザー

2020/01/19 05:08

ご回答ありがとうございます。 なるほど、t^2の相当だったのですね。 ありがとうございます。
guest

回答1

0

ベストアンサー

質問1について
この辺groundMaskを定義していますが、ここに適切なレイヤーマスクをセットして(この辺CheckSphereを行うようにすれば、プレイヤーには反応させずに地面だけに対して当たり判定を行うことが可能なはずです。ですのでgroundCheckオブジェクトをわざわざプレイヤーと重ならないように配置する必要はないかと思います。

質問2について
tatamyiwathyさんのおっしゃるとおり、自由落下の式に適合させるにはvelocity.y += gravity * Time.deltaTime;の部分だけでなくcontroller.Move(velocity * Time.deltaTime);の部分でもTime.deltaTimeをかけないとつじつまが合わないことを説明したかったんじゃないかと思います。

質問3について
速度が無限に大きくなり続けるのを防ぐためというのは適切な解釈だと思います。条件に関してですが、今後ジャンプを実装すれば(この辺velocity.yがプラスになるケースも出てくるはずです。もし地上にいる(isGroundedtrue)ときに上向きに速度を与えてジャンプさせようとしても、速度の条件がないと次回のUpdateでせっかく与えた速度が書き換えられてしまって跳び上がれなくなるでしょう。
velocity.y = -2f;については動画制作者の方が経験的に決めたようです。わずかに下向きの速度を持たせると、物理シミュレーションエンジンにとっては物体が地面に軽く押しつけられている状態に見えるはずで、その方がいい感じの挙動になるということでしょうかね?
想像してみますと、水平移動中に地面の微妙な起伏にぶつかったり、そうでなくてもシミュレーション上の微妙な誤差によってちょっと上向きの速度を持つことはありそうなように思います。ですがvelocity.yをわずかに下向きにしてあれば、CheckSphereの範囲内であれば次回のシミュレーションステップで再び地面に押しつけられるので、動きが安定しそうな気がします。

投稿2020/01/18 22:16

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/01/19 06:24

ご回答ありがとうございます。 質問1について。 なるほど、groundMaskを定義すればプレイヤーにCheckSphereが接触していても大丈夫なのですね。 各ステージオブジェクトに逐一、地面レイヤーを設定するのは面倒かなと思って、レイヤー判定しないコードで試していたのですが、 動画をよく見たら、(空の?)Enviromentオブジェクトの子オブジェクトに全てのステージオブジェクトを格納して、 Enviromentオブジェクトに地面レイヤーを設定すれば、その全ての子オブジェクトも地面レイヤーのオブジェクトして扱うことができるのですね。 質問2について。 1/2gt^2のt^2に相当させるためだったのですね。 この際、1/2にも対応させるために、 controller.Move(0.5f * velocity * Time.deltaTime); とすればよいのかなとも思ったのですが、 これだと、velocity.xやvelocity.yにも0.5fが掛かってしまい、都合が悪くなるので、 1/2を掛けることは妥協している(無視している)ということでしょうか? 質問3について。 速度が無限に大きくなり続けるのを防ぐためというご教示ありがとうございます。 なるほど、ジャンプ時の考慮のためだったのですね。 「水平移動中に地面の微妙な起伏にぶつかったり、シミュレーション上の微妙な誤差によってちょっと上向きの速度を持つことがありそう」という発想はしてませんでした、勉強になります、ありがとうございます。
Bongo

2020/01/19 10:31 編集

「この際、1/2にも対応させるために」...の処置は不要ではないでしょうか。 位置を時間で微分すると速度に、速度を時間で微分すると加速度になりますが、これは実際に1/2gt^2をtで微分するとgtとなって速度の式になり、gtをtで微分するとgとなって加速度の式(等加速度運動なので定数値ですが...)になることから確認できるかと思います。 逆に加速度を時間で積分すると速度が、速度を時間で積分すると位置が得られますが、積分の学習の際にたとえば http://www.isc.meiji.ac.jp/~mvdl/ex2/joho1/ch07/index.html の図のように「関数が作る図形を短冊の集合体で近似して、短冊の面積の総和を表す式を作り、短冊の幅を無限に細くしていく...」みたいなことをやった経験はありますでしょうか? このときの一つの短冊の面積はシンプルに「短冊の高さ(その時点における関数の値)×短冊幅」で、わざわざ2で割ることはしなかったと思います(もしかすると(その時点における関数の値+次の時点の関数の値)÷2×短冊幅...みたいに台形で近似したかもしれませんが、それでも単に2で割ったのとは異なる式になるでしょう)。 「velocity.y += gravity * Time.deltaTime;」とか「controller.Move(velocity * Time.deltaTime);」の部分は、短冊幅Time.deltaTimeで短冊の面積をどんどん積算することによって現時点の速度、あるいは現時点の位置を近似的に求めていると考えることができます。 これらもやはり毎回の加算量はその時点の加速度(gravity)×短冊幅(Time.deltaTime)、あるいはその時点の速度(velocity)×短冊幅(Time.deltaTime)とすればよく、2で割る必要はないかと思います。
退会済みユーザー

退会済みユーザー

2020/01/19 15:15

ご回答ありがとうございます。 v = gt y = 1/2gt^2 の2つの式から、 y=1/2・gt・t y=1/2・v・t y=1/2vt になるのかと思い、1/2を掛けるという発想になったのですが、 考えてみれば、contoller.Moveの引数に与えるものは、 controllerの位置を指定するものではないので、controller.Moveに1/2vtを与えるべきではないということですか? controller.Moveを調べてみたら、引数には「絶対値の移動デルタ」を指定するみたいですが、 ここで、「位置を時間で微分すると速度になる」ことを思い出して、 式にすると、「v = Δx/Δt」なので、 このΔxが、「絶対値の移動デルタ」に該当するということでしょうか? そして、Δx=v・Δt (速度を時間で積分すると位置になる) より、controller.Moveの引数には、v・Δt、 つまり、velocity * Time.deltaTime を指定するということでしょうか? 短冊の面積の総和、高校時代に学習しましたがうろ覚えでした。 積分の公式に当てはめて、積分計算できることで満足していました。 積分の公式もほぼ忘れていましたが、vをtで積分すると、vtになることは覚えていました。
Bongo

2020/01/19 21:26

はい、妥当な解釈だと思います。 積分によって得られたy = 1/2gt^2の形の式は「初期位置・初速度(この式ではそれらの項はどちらも0ですが)と現在の時刻から現在の位置をダイレクトに求める」といった用途に適していますが、今回のように「前の瞬間の位置・速度から現在の位置・速度を求める」というスタイルには向かないように思います。 ご参考にされた動画では式の左辺が「Δy」と表示されていますが、これは「前の瞬間からの差分位置」ではなく「初期時刻からの差分位置」と解釈するべきでしょう。 ご質問者さんのコメントの式での「Δx」がリファレンスでの「絶対値の移動デルタ」に相当するという解釈も適切だと思います。 リファレンスの訳語ですが、この文脈で「絶対値」というのはちょっと不自然な感じがしますね...英語版だと「absolute movement deltas」となっていますが、多分この「absolute」は絶対空間(https://ja.wikipedia.org/wiki/%E7%B5%B6%E5%AF%BE%E6%99%82%E9%96%93%E3%81%A8%E7%B5%B6%E5%AF%BE%E7%A9%BA%E9%96%93 )のようなニュアンスだと考え、要するにワールド座標系での位置の差分を意味するものと解釈した方がよさそうな気がします。
退会済みユーザー

退会済みユーザー

2020/01/21 04:07 編集

ご回答ありがとうございます。 絶対値のニュアンスに関しても教えていただきありがとうございます。 ご教示いただいたおかげで理解できたので、後は暗記しようと思うのですが、 以下の認識で間違ってないですか? CharacterController.Moveメソッドの引数に与えられるものは、以下の2通り。 ・移動デルタそのもの。  移動デルタ(移動距離のベクトル)をVector3 stepとすると、  CharacterController.Move(step); ・速度・時間。  速度を時間で積分すると位置が得られるので、移動デルタは速度を時間で積分したもの。  速度ベクトルをvelocityとすると、  CharacterController.Move(velocity * Time.deltaTime);  ※時間で積分するので、Updateなど時間が積み重なるメソッドの中で使用することが条件。 ちなみに最初、以下の過ちを犯すところでした。 加速度についても考えられるのかなと思い、 ・加速度を時間で積分すると速度が得られ、速度を時間で積分すると位置が得られので、  加速度ベクトルをaccelerationとすると、  加速度を時間で積分して速度を得る → acceleration * Time.deltaTime  速度を時間で積分して位置を得る → 1/2 * acceleration * Time.deltaTime * Time.deltaTime  例の1/2の形ができてしまう?  なぜだろう、と考えたのですが、  時間で積分できる条件というのが、Updateなど時間が積み重なるメソッドの中ということに気づき、  加速度の場合、時間で2回積分するということは、  言うならば、Updateの中でUpdateするようなこと(?)なので、  普通のUpdateで、  CharacterController.Move(1/2 * acceleration * Time.deltaTime * Time.deltaTime);  とするのは、間違い  と考えました。  加速度の考え方に関しても、こちらの認識で合っていますか?
Bongo

2020/01/21 12:53

CharacterController.Moveについてはそのようなとらえ方でよさそうです。まあ2通りのいずれのシチュエーションであっても、結局のところ「現在の位置を基準として相対的な移動ベクトルを与える」ということなので同じだという考え方もできるでしょうが、ご質問者さんのようにシチュエーションに応じたイメージでとらえるというのも思考の整理に有効だと思います。 加速度の件についてもおおむねいいんじゃないでしょうか。 CharacterController.Move(1/2 * acceleration * Time.deltaTime * Time.deltaTime);についてですが、この式そのものだとうまくいかないでしょうが、加速度の項だけじゃなく速度の項も組み込んでやれば大丈夫だと思います。 つまり「現在の位置を初期位置、現在の速度を初速度」と考えてx = x0 + v0t + 1/2at^2の形に当てはめると... CharacterController.Move(velocity * Time.deltaTime + 0.5f * acceleration * Time.deltaTime * Time.deltaTime); velocity += acceleration * Time.deltaTime; とすることができるはずです(速度の更新を位置の更新よりも後...つまりMoveの時点では更新前の速度を使うことにご注意ください)。 これをさらに掛け算の回数を節約するよう変形して... var deltaVelocity = acceleration * Time.deltaTime; CharacterController.Move((velocity + 0.5f * deltaVelocity) * Time.deltaTime); velocity += deltaVelocity; としてもいいでしょう。この形は最初の... velocity += acceleration * Time.deltaTime; CharacterController.Move(velocity * Time.deltaTime); と比べると式がやや複雑になって計算量がちょっとだけ増えるものの、正確さは向上するはずです(それどころか、今回のような加速度が一定の運動なら、計算誤差を無視すれば理想値ときれいに合うと思います)。 式をさらに変形して... var deltaVelocity = acceleration * Time.deltaTime; var nextVelocity = velocity + deltaVelocity; CharacterController.Move((velocity + nextVelocity) * 0.5f * Time.deltaTime); velocity = nextVelocity; としてやると、式の形が以前のコメントでちょこっと申し上げた「台形による近似」の形になっていることを見て取れますでしょうか? 自由落下の場合、位置を求めるために積分したい速度の式というのがv = gtという風に一次式になっているので、グラフを描くと直線になるはずです。 ですのでグラフが作る三角形の面積を短冊の面積の和として求めようとすると、長方形だと斜辺がガタガタの階段になってしまいますが、台形ならてっぺんが斜めになっていますので三角形とぴったり一致する面積が得られるというわけですね。
退会済みユーザー

退会済みユーザー

2020/01/21 15:43

ご回答ありがとうございます。 ゲーム開発は趣味ですが、趣味だからこそちゃんと理解したい、そして理解した後は公式のように覚えて(シチュエーションに応じたイメージで捉えて)活用したいという姿勢でゲーム開発を楽しんでいます。そして、いつもBongo様には理解の過程から思考の整理部分まで詳細にご教示くださり、本当にありがとうございます。大変感謝しています。 加速度のご教示ありがとうございます。 なるほど、等加速度運動の位置の公式を使えば、そのようなコードで実装できるのですね。 式変形もとても勉強になります。 式から、台形の近似の形になっていることも理解できました。 全ての疑問点が解決しました。 ご教示くださり、本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問