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

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

新規登録して質問してみよう
ただいま回答率
85.47%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Q&A

解決済

1回答

4644閲覧

指定した座標に向けて放物線を描くように移動させたい

kentyann

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

0グッド

0クリップ

投稿2022/07/23 10:15

実現したいこと

Unityで現在地からクリックした地点まで放物線を描くように動かしたいです。
また、移動速度は調整できるようにしたいです。

試したこと①

初めにSlerp関数を使ってみましたが、孤の方向を操作できず、孤が横に向いてしまい放物線になりません。

恥ずかしながら線形補完の数学的な理屈はふんわりとしか理解できてません…
また機能(関数)としても使うのは初めてです。
知らないだけで引数などで孤の方向を指定できるのであれば話は早いのですが…

cs

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Throw : MonoBehaviour 6{ 7 /* 8 * 1.マウスクリック 9 * 2.クリックされた座標を取得 10 * 3.取得した座標にSleap関数で移動 11 */ 12 13 //落下地点(クリックした座標) 14 Vector3 ThrowPoint = new Vector3(0, 0, 0); 15 16 //投げるスピード 17 public float speed = 10; 18 19 //投げるフラグ 20 bool Throwing = false; 21 22 float distance_two = 0; 23 24 private void Update() 25 { 26 print(Input.mousePosition); 27 print("world;"+Camera.main.ScreenToWorldPoint(Input.mousePosition)); 28 _Throw(); 29 } 30 31 void _Throw() 32 { 33 34 //左クリック時のマウス座標を取得 35 if (Input.GetMouseButtonDown(0)) 36 //&& !Throwing) 37 { 38 //スクリーン座標からワールド座標に変換してから代入 39 var MousePos = Input.mousePosition; 40 var ScreenPos = new Vector3(MousePos.x, MousePos.y, 40f); 41 ThrowPoint = Camera.main.ScreenToWorldPoint(ScreenPos); 42 ThrowPoint.y = 1; 43 44 print(ThrowPoint); 45 46 //二点間の距離を代入(スピード調整に使う) 47 distance_two = Vector3.Distance(transform.position, ThrowPoint); 48 49 Throwing = true; 50 51 } 52 53 if (Throwing) 54 { 55 // 現在の位置 56 float present_Location = (Time.time * speed) / distance_two; 57 58 // オブジェクトの移動 59 transform.position = Vector3.Slerp(transform.position, ThrowPoint, present_Location); 60 } 61 } 62} 63

試したこと②

次にRigidbodyのAddForceで物理的に目的地まで角度をつけて発射するやり方を見つけたのですが速度をコントロールする方法わかりません。

using System.Collections; using System.Collections.Generic; using UnityEngine; public class Throw2 : MonoBehaviour { /* * 1.マウスクリック * 2.クリックされた座標を取得 * 3.取得した座標にSleap関数で飛ばす */ //落下地点(クリックした座標) Vector3 ThrowPoint = new Vector3(0, 0, 0); // 射出角度 public float ThrowingAngle = 60; private void Update() { _Throw(); } void _Throw() { //左クリック時のマウス座標を取得 if (Input.GetMouseButtonDown(0)) { //スクリーン座標からワールド座標に変換してから代入 var MousePos = Input.mousePosition; var ScreenPos = new Vector3(MousePos.x, MousePos.y, 40f); ThrowPoint = Camera.main.ScreenToWorldPoint(ScreenPos); ThrowPoint.y = 1; Throwing(); } } private void Throwing() { // 射出速度を算出 Vector3 velocity = CalculateVelocity(transform.position, ThrowPoint, ThrowingAngle); // 射出 Rigidbody rid = this.GetComponent<Rigidbody>(); rid.AddForce(velocity * rid.mass, ForceMode.Impulse); } private Vector3 CalculateVelocity(Vector3 pointA, Vector3 pointB, float angle) { // 射出角をラジアンに変換 float rad = angle * Mathf.PI / 180; // 水平方向の距離x float x = Vector2.Distance(new Vector2(pointA.x, pointA.z), new Vector2(pointB.x, pointB.z)); // 垂直方向の距離y float y = pointA.y - pointB.y; // 斜方投射の公式を初速度について解く float speed = Mathf.Sqrt(-Physics.gravity.y * Mathf.Pow(x, 2) / (2 * Mathf.Pow(Mathf.Cos(rad), 2) * (x * Mathf.Tan(rad) + y))); if (float.IsNaN(speed)) { // 条件を満たす初速を算出できなければVector3.zeroを返す return Vector3.zero; } else { return (new Vector3(pointB.x - pointA.x, x * Mathf.Tan(rad), pointB.z - pointA.z).normalized * speed); } } }

こちらのサイト(https://qiita.com/_udonba/items/a71e11c8dd039171f86c)
を参考にしました(発射機構ほぼコピってます)

追記

  • 後々調整しやすそうなので、できればSlerp関数で実現したいと考えています。

  • teratailを使うのは初めてなので、使い方や書き方に至らぬ点があるかもしれませんがご容赦ください。

もしそういった点があれば教えていただけると幸いです。

  • C#、Unityについても初心者なので細かく教えていただけるとありがたいです。

補足情報(FW/ツールのバージョンなど)

Unity:2021.3.5.f1

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

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

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

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

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

guest

回答1

0

ベストアンサー

Slerpの挙動となりますと、たとえば「【Unity】Vector3.Lerp/Slerpの使い方と内部挙動 | ねこじゃらシティ」だとかの解説がイメージを掴むのにご参考になるでしょうかね?

Slerpを使うとしたら、下記のような感じでいかがでしょう。円弧に沿った曲線ですので放物線とは言えないかもしれませんが、見た目は悪くないんじゃないかと思います。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Throw : MonoBehaviour 6{ 7 /* 8 * 1.マウスクリック 9 * 2.クリックされた座標を取得 10 * 3.取得した座標にSleap関数で移動 11 */ 12 13 //投げるスピード 14 public float speed = 10; 15 16 //軌道の円弧の中心角 17 [Range(0.0f, 180.0f)] public float arcAngle = 60.0f; 18 19 //地上にいるときのY座標 20 public float baseHeight = 1.0f; 21 22 //投げるフラグ 23 bool Throwing = false; 24 25 Vector3 FromVector; 26 Vector3 ToVector; 27 Vector3 Pivot; 28 float Travel; 29 30 void Update() 31 { 32 _Throw(); 33 } 34 35 void _Throw() 36 { 37 //左クリック時のマウス座標を取得 38 if (Input.GetMouseButtonDown(0)) 39 { 40 //マウスポインタの位置を指すレイを作成 41 var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); 42 43 //地上を表す平面を作成 44 var basePlane = new Plane(Vector3.up, -baseHeight); 45 46 if (basePlane.Raycast(mouseRay, out var enter)) 47 { 48 //レイと平面の交差があれば、その地点を目的地とする 49 var toPoint = mouseRay.GetPoint(enter); 50 51 //現在の位置を地上の高さに合わせ、その地点を出発地とする 52 var fromPoint = transform.position; 53 fromPoint.y = baseHeight; 54 55 //軌道の円弧の角度の半分のタンジェントを求める 56 var tangentOfHalfAngle = Mathf.Tan(Mathf.Deg2Rad * arcAngle * 0.5f); 57 58 //出発地と目的地の中間点を求め... 59 var midPoint = (fromPoint + toPoint) * 0.5f; 60 61 //出発地から中間点までの距離(弦の長さの半分)を求める 62 var chordHalfLength = Vector3.Distance(fromPoint, midPoint); 63 64 //円弧の中心はこうなる 65 Pivot = midPoint; 66 Pivot.y -= chordHalfLength / tangentOfHalfAngle; 67 68 //中心から出発地、中心から目的地へのベクトルを求めておく 69 FromVector = fromPoint - Pivot; 70 ToVector = toPoint - Pivot; 71 72 //移動量を0.0にリセットしておく 73 Travel = 0.0f; 74 75 Throwing = true; 76 print($"{fromPoint} -> {toPoint}"); 77 } 78 } 79 80 if (Throwing) 81 { 82 //出発地からの移動量を求め... 83 Travel += speed * Time.deltaTime; 84 85 //円弧の長さで割って、円弧上を進行した割合を求める 86 var t = Travel / (FromVector.magnitude * Mathf.Deg2Rad * arcAngle); 87 print(t); 88 89 if (t < 1.0f) 90 { 91 //FromVectorとToVectorを進行割合で補間し、Pivotを足して現在の位置とする 92 transform.position = Vector3.Slerp(FromVector, ToVector, t) + Pivot; 93 } 94 else 95 { 96 //tが1.0に到達したら移動終了とする 97 transform.position = ToVector + Pivot; 98 Throwing = false; 99 } 100 } 101 } 102}

図1

また、AddForce案で「速度をコントロールする方法がわからない」とおっしゃるのは、もしかしてオブジェクトの速度が残っているせいで目標からそれたり、着地後に余力で滑ったりしてしまう現象を指しているのでしょうか。
でしたら、下記のようにAddForceの代わりにvelocityを直接操作したり、必要に応じてconstraintsを設定して動きを拘束してやればどうでしょう。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Throw2 : MonoBehaviour 6{ 7 /* 8 * 1.マウスクリック 9 * 2.クリックされた座標を取得 10 * 3.取得した座標にSleap関数で飛ばす 11 */ 12 13 //落下地点(クリックした座標) 14 Vector3 ThrowPoint = new Vector3(0, 0, 0); 15 16 // 射出角度 17 public float ThrowingAngle = 60; 18 19 //地上にいるときのY座標 20 public float baseHeight = 1.0f; 21 22 void Update() 23 { 24 _Throw(); 25 } 26 27 void _Throw() 28 { 29 30 //左クリック時のマウス座標を取得 31 if (Input.GetMouseButtonDown(0)) 32 { 33 //マウスポインタの位置を指すレイを作成 34 var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); 35 36 //地上を表す平面を作成 37 var basePlane = new Plane(Vector3.up, -baseHeight); 38 39 if (basePlane.Raycast(mouseRay, out var enter)) 40 { 41 //レイと平面の交差があれば、その地点を目的地とする 42 ThrowPoint = mouseRay.GetPoint(enter); 43 Throwing(); 44 } 45 } 46 } 47 48 void Throwing() 49 { 50 // 射出速度を算出 51 Vector3 velocity = CalculateVelocity(transform.position, ThrowPoint, ThrowingAngle); 52 53 // 射出 54 Rigidbody rid = this.GetComponent<Rigidbody>(); 55 56 //FreezePositionをオフにして... 57 rid.constraints &= ~RigidbodyConstraints.FreezePosition; 58 59 //AddForceを使った場合、射出時点のオブジェクトの速度に対して 60 //射出速度が加算されるため、目標からそれる可能性がある 61 //そこで、AddForceの代わりにvelocityを直接書き換えることで 62 //現在の速度の影響を消してしまうことにした 63 rid.velocity = velocity; 64 } 65 66 void OnCollisionEnter(Collision collision) 67 { 68 //他の物体と接触したらFreezePositionをオンにする 69 Rigidbody rid = this.GetComponent<Rigidbody>(); 70 rid.constraints |= RigidbodyConstraints.FreezePosition; 71 } 72 73 Vector3 CalculateVelocity(Vector3 pointA, Vector3 pointB, float angle) 74 { 75 // 射出角をラジアンに変換 76 float rad = angle * Mathf.PI / 180; 77 78 // 水平方向の距離x 79 float x = Vector2.Distance(new Vector2(pointA.x, pointA.z), new Vector2(pointB.x, pointB.z)); 80 81 // 垂直方向の距離y 82 float y = pointA.y - pointB.y; 83 84 // 斜方投射の公式を初速度について解く 85 float speed = Mathf.Sqrt(-Physics.gravity.y * Mathf.Pow(x, 2) / (2 * Mathf.Pow(Mathf.Cos(rad), 2) * (x * Mathf.Tan(rad) + y))); 86 87 if (float.IsNaN(speed)) 88 { 89 // 条件を満たす初速を算出できなければVector3.zeroを返す 90 return Vector3.zero; 91 } 92 else 93 { 94 return (new Vector3(pointB.x - pointA.x, x * Mathf.Tan(rad), pointB.z - pointA.z).normalized * speed); 95 } 96 } 97}

図2

追記

すみません、「速度をコントロールする方法がわからない」とおっしゃる意味を誤解してしまったかもしれません。
もう1パターン、下記のような形を作ってみましたがいかがでしょうか。

C#

1using UnityEngine; 2 3public class Throw3 : MonoBehaviour 4{ 5 //投げる水平スピード 6 public float speed = 10; 7 8 //射出角度 9 [Range(0.0f, 90.0f)] public float throwingAngle = 60.0f; 10 11 //地上にいるときのY座標 12 public float baseHeight = 1.0f; 13 14 //投げるフラグ 15 bool Throwing; 16 17 Vector3 FromPoint; 18 Vector3 ToPoint; 19 float Travel; 20 21 void Update() 22 { 23 _Throw(); 24 } 25 26 void _Throw() 27 { 28 //左クリック時のマウス座標を取得 29 if (Input.GetMouseButtonDown(0)) 30 { 31 //マウスポインタの位置を指すレイを作成 32 var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); 33 34 //地上を表す平面を作成 35 var basePlane = new Plane(Vector3.up, -baseHeight); 36 if (basePlane.Raycast(mouseRay, out var enter)) 37 { 38 //レイと平面の交差があれば、その地点を目的地とする 39 ToPoint = mouseRay.GetPoint(enter); 40 41 //現在の位置を地上の高さに合わせ、その地点を出発地とする 42 FromPoint = transform.position; 43 FromPoint.y = baseHeight; 44 45 //移動量を0.0にリセットしておく 46 Travel = 0.0f; 47 Throwing = true; 48 49 print($"{FromPoint} -> {ToPoint}"); 50 } 51 } 52 53 if (Throwing) 54 { 55 //出発地からの水平移動量を求め... 56 Travel += speed * Time.deltaTime; 57 58 //出発地と目的地の距離を求め... 59 var distance = Vector3.Distance(FromPoint, ToPoint); 60 61 //進行割合を求め... 62 var t = Travel / distance; 63 64 if (t < 1.0f) 65 { 66 //tが0.5(つまり中間地点)からどれだけ離れているかを求める 67 //中間地点で0.0、出発地や目的地で1.0となるような値にする 68 var d = Mathf.Abs(t - 0.5f) * 2.0f; 69 70 //現在の水平位置を決め... 71 var p = Vector3.Lerp(FromPoint, ToPoint, t); 72 73 //高さを二次関数の曲線に沿って調整し... 74 p.y += Mathf.Tan(Mathf.Deg2Rad * this.throwingAngle) * 0.25f * distance * (1.0f - (d * d)); 75 76 //位置を設定する 77 transform.position = p; 78 } 79 else 80 { 81 //tが1.0に到達したら移動終了とする 82 transform.position = ToPoint; 83 Throwing = false; 84 } 85 } 86 } 87}

図3

投稿2022/07/23 22:06

編集2022/07/23 23:08
Bongo

総合スコア10807

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

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

kentyann

2022/07/24 09:27

非常に丁寧な回答ありがとうございます。 手元で試したところ「追記」の形が目的に完璧に合致していましたので、まだお手本の内容を全て理解できてはいませんがベストアンサーに選ばせていただきました。 また質問した点以外にも私が実現したかった機能を実装した100点以上の回答が返ってきて驚いています。 特にレイを使って目的地の座標を取得する方法は、ちょうど悩んでいた「カメラの角度によって正しくワールド座標に変換できない」問題の解決でき、目から鱗でした。 しばらくは頂いた3つのお手本の内容を勉強してみたいと思います。 こういった質問をネット上でするのは初めてでしたが、最初からこんな満点を超える丁寧な回答をいただけて感動しています。 改めてありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問