Unityでクリックした場所にマーカーを置く|hit.pointとhit.normalを実際に使ってみよう
前回は、RaycastHit の中に入っている情報として、
hit.collider
hit.point
hit.distance
hit.normal
を確認しました。
特に、
hit.point
は、
Rayが実際に当たった場所
を表し、
hit.normal
は、
Rayが当たった面の向き
を表していました。
今回は、この2つを実際に使って、
マウスでクリックした場所にマーカーを置き、そのマーカーを面の向きに合わせる
という処理を作ってみましょう。
- 1. 今回作るもの
- 2. まずは位置だけ考える
- 3. Prefabをスクリプトから参照する
- 4. hit.pointにマーカーを生成する
- 5. クリックした場所に置けた
- 6. しかし向きがおかしい
- 7. hit.normalをもう一度確認
- 8. Quaternion.LookRotation()を使う
- 9. 位置と向きを両方指定する
- 10. 完成コード
- 11. hit.pointとhit.normalの役割がはっきりする
- 12. 面から少し浮かせた方がよい場合もある
- 13. normal方向へ少しずらす
- 14. なぜnormalを足すと外側へ動くのか
- 15. 床をクリックした場合
- 16. 壁をクリックした場合
- 17. Prefabの向きには注意
- 18. なぜマーカーが90度ずれることがある?
- 19. この実習で何が分かった?
- 20. ここまでの記事をつなげてみる
- 21. まとめ
今回作るもの
たとえばCubeをクリックしたとします。
すると、
↑ hit.normal
│
┌────●────┐
│ Marker│
│ Cube │
└─────────┘
↑
hit.point
というように、
クリックした場所にマーカーを表示
します。
さらに、床だけではなく、
床
壁
斜面
Cubeの側面
などをクリックしても、
その面の向きに沿ってマーカーが配置される
ようにします。

まずは位置だけ考える
最初は向きのことを考えず、
hit.point
だけ使ってみましょう。
前回のRaycast処理は、次のようになっていました。
Ray ray =
Camera.main.ScreenPointToRay(
Mouse.current.position.value
);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
}
ここで、
hit.point
には、Rayが当たった位置が入っています。
したがって、この場所にGameObjectを生成すれば、
クリックした場所にオブジェクトを置く
ことができます。
マーカーPrefabを用意する
今回は、マーカーとして小さなオブジェクトをPrefabにしておきます。
たとえば、
Marker
というGameObjectを作ります。
初心者向けの確認実習なら、
小さなSphere
小さなCube
薄い円形オブジェクト
などでも構いません。
重要なのは、
クリックした位置が目で確認できること
です。
作成したMarkerをProjectウィンドウへドラッグしてPrefabにしておきましょう。
Prefabをスクリプトから参照する
スクリプトに、
[SerializeField]
GameObject markerPrefab;
を追加します。
using UnityEngine;
using UnityEngine.InputSystem;
public class HitMarker : MonoBehaviour
{
[SerializeField]
GameObject markerPrefab;
void Update()
{
}
}
Inspectorで、
Marker Prefab
の欄へ、先ほど作ったPrefabを設定します。
hit.pointにマーカーを生成する
Raycastが成功したところで、
Instantiate()
を使います。
まずは向きを考えず、
Instantiate(
markerPrefab,
hit.point,
Quaternion.identity
);
としてみましょう。
全体では、
using UnityEngine;
using UnityEngine.InputSystem;
public class HitMarker : MonoBehaviour
{
[SerializeField]
GameObject markerPrefab;
void Update()
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
Ray ray =
Camera.main.ScreenPointToRay(
Mouse.current.position.value
);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Instantiate(
markerPrefab,
hit.point,
Quaternion.identity
);
}
}
}
}
となります。
クリックした場所に置けた
ここで重要なのが、
hit.point
です。
Instantiate(
markerPrefab,
hit.point,
Quaternion.identity
);
なので、
Markerの位置
=
Rayが当たった位置
となっています。
つまり、
マウスクリック
↓
Ray
↓
Colliderに当たる
↓
hit.point
↓
そこへMarkerを生成
という流れです。
これで hit.point の意味が、かなり実感しやすくなります。
しかし向きがおかしい
床だけをクリックするなら、これでも問題がないように見えるかもしれません。
しかし、壁をクリックするとどうでしょう。
たとえば薄いマーカーを使っている場合、
壁
│
│ ─ Marker
│
のように、マーカーの向きが壁と合わない可能性があります。
なぜなら、
Quaternion.identity
としているからです。
これは、
回転させない
という指定です。
クリックした面がどちらを向いているかは考慮していません。
そこで、
hit.normal
を使います。
hit.normalをもう一度確認
hit.normal は、
Rayが当たった面から外側へ垂直に伸びる方向
です。
床なら、
↑ normal
│
────────────
壁なら、
│
│────→ normal
│
斜面なら、
↗ normal
/
─────/
のようになります。
つまり、
hit.normal
を使えば、
その面がどちらを向いているか
が分かります。
Quaternion.LookRotation()を使う
方向から回転を作りたいとき、
Quaternion.LookRotation()
を使うことができます。
たとえば、
Quaternion rotation =
Quaternion.LookRotation(hit.normal);
とします。
Quaternion.LookRotation() は、指定した forward 方向へローカルZ軸を向ける回転を作ります。
つまり、
Quaternion.LookRotation(hit.normal)
なら、
MarkerのZ方向
↓
hit.normal
となります。
位置と向きを両方指定する
これで、
位置 → hit.point
向き → hit.normal
という2つの情報がそろいました。
そこで、
Quaternion rotation =
Quaternion.LookRotation(hit.normal);
Instantiate(
markerPrefab,
hit.point,
rotation
);
とします。
これで、
クリックした位置へ、面の方向に合わせてMarkerを生成
できます。
完成コード
using UnityEngine;
using UnityEngine.InputSystem;
public class HitMarker : MonoBehaviour
{
[SerializeField]
GameObject markerPrefab;
void Update()
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
Ray ray =
Camera.main.ScreenPointToRay(
Mouse.current.position.value
);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Quaternion rotation =
Quaternion.LookRotation(
hit.normal
);
Instantiate(
markerPrefab,
hit.point,
rotation
);
}
}
}
}
処理の流れを分解すると、
① マウスクリック
② ScreenPointToRay()
Rayを作る
③ Physics.Raycast()
Colliderに当たったか調べる
④ hit.point
当たった位置を取得
⑤ hit.normal
当たった面の向きを取得
⑥ Quaternion.LookRotation()
normalから回転を作る
⑦ Instantiate()
Markerを生成
となります。

hit.pointとhit.normalの役割がはっきりする
今回のコードで、2つの役割は明確です。
hit.point
は、
どこに置くか
です。
一方、
hit.normal
は、
どちらを向けるか
です。
つまり、
hit.point
↓
Position
hit.normal
↓
Rotation
と考えることができます。
面から少し浮かせた方がよい場合もある
マーカーを、
hit.point
ぴったりに置くと、マーカーと元の面がほぼ同じ位置になる場合があります。
たとえば、薄い板や画像を壁に貼るようなMarkerの場合、
壁の表面
Markerの表面
が重なってしまうことがあります。
すると、描画上ちらついて見えることがあります。
これは一般に、
Z-fighting
と呼ばれる現象です。
normal方向へ少しずらす
そこで、
hit.point + hit.normal * 0.01f
のようにして、
面から少し外側へずらすことができます。
Vector3 position =
hit.point + hit.normal * 0.01f;
これは、
hit.point
↓
●────────→ hit.normal
↑
当たった位置
0.01だけ外側へ移動
という意味です。
修正版
Vector3 position =
hit.point + hit.normal * 0.01f;
Quaternion rotation =
Quaternion.LookRotation(hit.normal);
Instantiate(
markerPrefab,
position,
rotation
);
全体では、
using UnityEngine;
using UnityEngine.InputSystem;
public class HitMarker : MonoBehaviour
{
[SerializeField]
GameObject markerPrefab;
void Update()
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
Ray ray =
Camera.main.ScreenPointToRay(
Mouse.current.position.value
);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Vector3 position =
hit.point
+ hit.normal * 0.01f;
Quaternion rotation =
Quaternion.LookRotation(
hit.normal
);
Instantiate(
markerPrefab,
position,
rotation
);
}
}
}
}
となります。
なぜnormalを足すと外側へ動くのか
ここも大切なところです。
hit.normal は、
面の外側を向く方向
でした。
そこで、
hit.normal * 0.01f
とすると、
その方向へ0.01
という移動量になります。
さらに、
hit.point + hit.normal * 0.01f
とすることで、
当たった場所
+
面の外側へほんの少し
という位置を作っています。
これは Vector3 の学習にもつながります。
床をクリックした場合
水平な床なら、normalはおおよそ、
(0, 1, 0)
です。
したがって、
hit.normal * 0.01f
は、
(0, 0.01f, 0)
になります。
つまり、
床から少し上へ
Markerを移動します。
壁をクリックした場合
たとえば壁のnormalが、
(1, 0, 0)
なら、
hit.normal * 0.01f
は、
(0.01f, 0, 0)
です。
つまり、
壁から少し外側へ
Markerを移動します。
コードを変えなくても、normalによって方向が自動的に変わります。
ここが hit.normal の便利なところです。
Prefabの向きには注意
ここは実習で非常に重要です。
Quaternion.LookRotation(hit.normal)
では、Markerの
ローカルZ軸
を hit.normal の方向へ向けます。
したがって、Marker Prefabそのものが、
Z方向が表側
になるように作られていると分かりやすくなります。
Prefabの作り方によっては、
X軸を面に向けたい
Y軸を面に向けたい
ということもあります。
その場合は、追加の回転が必要です。
なぜマーカーが90度ずれることがある?
コードが間違っていなくても、
Markerが横を向く
Markerが90度ずれる
ということがあります。
これは、
Prefabのどのローカル軸を「表」として作ったか
が関係しています。
たとえば、
Quaternion.LookRotation(hit.normal)
はZ軸をnormalに合わせます。
しかし、Prefab側が、
Y軸方向を表
として作られていれば、期待した向きにはなりません。
この場合、
コードだけでなくPrefabのローカル座標軸を見る
ことが大切です。
これはUnityを学ぶうえで重要な考え方です。
この実習で何が分かった?
これまで RaycastHit を、
Raycastの結果が入っている
と説明してきました。
今回実際に使ってみると、
hit.point
は、
オブジェクトを配置できる具体的な位置
であり、
hit.normal
は、
オブジェクトの向きを決められる具体的な方向
だということが分かります。
つまり、
Raycast
↓
RaycastHit
↓
3D空間の情報を取得
↓
その情報を使って別のGameObjectを配置
という流れです。
ここまでの記事をつなげてみる
これまでの流れを整理すると、
Camera.main
↓
メインカメラを取得
ScreenPointToRay()
↓
マウス位置からRayを作る
Physics.Raycast()
↓
RayがColliderに当たったか調べる
RaycastHit
↓
当たった情報を取得
hit.point
↓
当たった位置
hit.normal
↓
当たった面の方向
Instantiate()
↓
その場所にMarkerを作る
となりました。
最初は、
Camera.main.ScreenPointToRay(...)
という1行から始まりました。
しかし少しずつ分解すると、
画面上のマウス操作と3D空間のGameObjectがどのようにつながっているのか
が見えてきます。
まとめ
今回の一番重要なポイントは、
hit.point = どこ?
hit.normal = どっち向き?
です。
位置は、
Vector3 position =
hit.point;
で取得できます。
面の向きは、
hit.normal
で取得できます。
そのnormalから、
Quaternion rotation =
Quaternion.LookRotation(
hit.normal
);
と回転を作れます。
そして、
Instantiate(
markerPrefab,
position,
rotation
);
とすることで、
クリックした場所に、その面の向きに合わせたMarkerを配置
できます。
さらに、
hit.point
+ hit.normal * 0.01f
とすれば、
面から少し浮かせることもできます。
今回の実習によって、
hit.point
hit.normal
は単に覚えるためのプロパティではなく、
3D空間で「位置」と「向き」を扱うための実用的な情報
だということが分かります。










ディスカッション
コメント一覧
まだ、コメントがありません