Unityでクリックした場所に弾痕を残す|RaycastとDestroyを使ってみよう
前回は、
hit.point
と、
hit.normal
を使って、
クリックした場所にマーカーを置き、その面の向きに合わせる
ところまで作りました。
今回は、その仕組みを少しゲームらしくしてみます。
今回作るのは、
クリックした場所に弾痕を残し、3秒後に自動的に消す
という処理です。
- 1. 今回の完成イメージ
- 2. 前回のコードを確認する
- 3. MarkerをBulletHoleに変える
- 4. まず弾痕を生成する
- 5. でも弾痕が増え続ける
- 6. Destroy()を使う
- 7. でも、どのGameObjectをDestroyする?
- 8. Instantiateした結果を変数に入れる
- 9. prefabと生成物は別物
- 10. 生成した弾痕を3秒後に削除する
- 11. 完成コード
- 12. コードを順番に追ってみよう
- 13. Raycastで壁を調べる
- 14. 弾痕を置く位置を決める
- 15. 弾痕の向きを決める
- 16. Instantiate()の戻り値に注目
- 17. 「作る」と「その後どうするか」
- 18. Destroy(bulletHole, 3f)の3fとは?
- 19. 3秒をフィールドにするとさらに良い
- 20. ここまでで学んだものを整理する
- 21. 最初の1行からかなり発展した
- 22. まとめ
今回の完成イメージ
たとえば壁をクリックすると、
Camera
●
\
\ Ray
\
× ← hit.point
[●] 弾痕
│
│ 壁
│
クリックした場所に弾痕が表示されます。
さらに、
クリック
↓
弾痕を生成
↓
3秒間表示
↓
弾痕を削除
という動きにします。
前回のコードを確認する
前回は、次のようなコードでした。
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
);
}
この中では、
hit.point
で、
弾痕を置く位置
を決めています。
そして、
hit.normal
で、
弾痕をどちらへ向けるか
を決めています。RaycastHit.point はRayがColliderに当たったワールド座標、normal は当たった面の法線です。

MarkerをBulletHoleに変える
今回は、前回のMarkerを
BulletHole
という弾痕Prefabに置き換えて考えます。
たとえば、
BulletHole
└ 弾痕画像
のようなPrefabを作っておきます。

スクリプト側では、
[SerializeField]
GameObject bulletHolePrefab;
とします。
まず弾痕を生成する
Rayが当たったら、
Instantiate(
bulletHolePrefab,
position,
rotation
);
とすれば弾痕を生成できます。
コードは、
if (Physics.Raycast(ray, out hit))
{
Vector3 position =
hit.point
+ hit.normal * 0.01f;
Quaternion rotation =
Quaternion.LookRotation(
hit.normal
);
Instantiate(
bulletHolePrefab,
position,
rotation
);
}
となります。
これで、
クリックした面に弾痕を貼る
ところまでは前回とほぼ同じです。
でも弾痕が増え続ける
このままゲームを続けると、
●
●
●
●
●
●
●
●
と、クリックするたびに弾痕が増えていきます。
小規模な実習ではすぐ問題にならなくても、
100個
500個
1000個
と生成し続ける設計は、あまりよくありません。
そこで今回は、
生成した弾痕を3秒後に消す
ようにしてみます。
Destroy()を使う
Unityには、
Destroy()
があります。
たとえば、
Destroy(gameObject);
とすると、指定したGameObjectを削除できます。
さらに、
Destroy(gameObject, 3f);
と書くこともできます。
この第2引数は、
何秒後に削除するか
です。
したがって、
Destroy(gameObject, 3f);
なら、
このGameObjectを3秒後に削除してください
という意味になります。Unity 6では Destroy(Object obj, float t) の t が削除までの遅延時間です。
でも、どのGameObjectをDestroyする?
ここで問題があります。
先ほどは、
Instantiate(
bulletHolePrefab,
position,
rotation
);
とだけ書きました。
これでは、
今生成した弾痕
を後から指定するための変数がありません。
そこで、
Instantiate() の戻り値を受け取ります。
Instantiateした結果を変数に入れる
次のように書きます。
GameObject bulletHole =
Instantiate(
bulletHolePrefab,
position,
rotation
);
これで、
bulletHole
という変数が、
今回生成された弾痕GameObject
を指します。
イメージとしては、
bulletHolePrefab
↓
Instantiate()
↓
新しい弾痕を生成
↓
bulletHole
です。
prefabと生成物は別物
ここは重要です。
bulletHolePrefab
は、
これから生成するときの元になるPrefab
です。
一方、
bulletHole
は、
実際にシーンへ生成されたGameObject
です。
つまり、
bulletHolePrefab
↓ 設計図
Instantiate()
↓
bulletHole
↓ 実際に作られたもの
という関係です。
生成した弾痕を3秒後に削除する
生成したGameObjectを変数で受け取れたので、
Destroy(
bulletHole,
3f
);
と書けます。
まとめると、
GameObject bulletHole =
Instantiate(
bulletHolePrefab,
position,
rotation
);
Destroy(
bulletHole,
3f
);
です。
これで、
Instantiate
↓
弾痕を生成
↓
bulletHole変数で覚える
↓
Destroy(bulletHole, 3f)
↓
3秒後に削除
となります。

完成コード
using UnityEngine;
using UnityEngine.InputSystem;
public class BulletHoleShooter : MonoBehaviour
{
[SerializeField]
GameObject bulletHolePrefab;
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
);
GameObject bulletHole =
Instantiate(
bulletHolePrefab,
position,
rotation
);
Destroy(
bulletHole,
3f
);
}
}
}
}
コードを順番に追ってみよう
まず、
if (Mouse.current.leftButton.wasPressedThisFrame)
で、
クリックされた?
を調べます。
次に、
Ray ray =
Camera.main.ScreenPointToRay(
Mouse.current.position.value
);
で、
Camera
↓
マウス位置
↓
Ray
を作ります。
Raycastで壁を調べる
次に、
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
で、
Ray
↓
Collider
↓
当たった?
を調べます。
当たった場合、hit の中に、
hit.point
hit.normal
hit.collider
hit.distance
などの情報が入ります。
弾痕を置く位置を決める
Vector3 position =
hit.point
+ hit.normal * 0.01f;
です。
基本位置は、
hit.point
です。
しかし、そのままでは壁と弾痕の面が重なる可能性があります。
そこで、
hit.normal * 0.01f
を足して、
表面からほんの少し外側
へ移動しています。
弾痕の向きを決める
次に、
Quaternion rotation =
Quaternion.LookRotation(
hit.normal
);
です。
Quaternion.LookRotation() は、指定されたforward方向へローカルZ軸を合わせる回転を作ります。
つまり、
BulletHoleのZ方向
↓
hit.normal
となります。
そのため、
床なら上向き
壁なら横向き
斜面なら斜め
と、クリックした面に合わせることができます。

Instantiate()の戻り値に注目
今回の新しいポイントはここです。
GameObject bulletHole =
Instantiate(
bulletHolePrefab,
position,
rotation
);
以前は、
Instantiate(...);
だけでした。
今回は、
GameObject bulletHole =
を付けています。
なぜでしょう。
その直後に、
Destroy(bulletHole, 3f);
をしたいからです。
「作る」と「その後どうするか」
この考え方はUnityで非常によく出てきます。
たとえば、
GameObject enemy =
Instantiate(enemyPrefab);
なら、
生成した敵を、
enemy
で扱えます。
同じように、
GameObject bullet =
Instantiate(bulletPrefab);
なら、
生成した弾を、
bullet
で扱えます。
今回も、
GameObject bulletHole =
Instantiate(...);
としておけば、
生成した弾痕に対して、
削除する
移動する
回転させる
色を変える
Componentを取得する
といった処理を続けられます。
Destroy(bulletHole, 3f)の3fとは?
Destroy(bulletHole, 3f);
の、
3f
は、
3秒
です。
したがって、
0秒
↓
Instantiate
弾痕が出る
1秒
↓
まだある
2秒
↓
まだある
3秒
↓
Destroy
消える
という流れになります。
実際には Destroy は指定時間後に削除を予約し、Unityの更新処理との関係で実際の破棄タイミングが管理されます。
初心者の段階では、
3秒後に削除される
と理解しておけば十分です。
3秒をフィールドにするとさらに良い
ここまで理解できたら、
3f
を直接書くのではなく、
[SerializeField]
float lifeTime = 3f;
としてもよいでしょう。
そして、
Destroy(
bulletHole,
lifeTime
);
とします。
全体では、
using UnityEngine;
using UnityEngine.InputSystem;
public class BulletHoleShooter : MonoBehaviour
{
[SerializeField]
GameObject bulletHolePrefab;
[SerializeField]
float lifeTime = 3f;
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
);
GameObject bulletHole =
Instantiate(
bulletHolePrefab,
position,
rotation
);
Destroy(
bulletHole,
lifeTime
);
}
}
}
}
これならInspectorから、
Life Time
1
3
5
10
などに変更できます。
ここまでで学んだものを整理する
この小さな実習には、かなり多くのUnityの基本が入っています。
Mouse
↓
クリックを検出
Camera.main
↓
メインカメラを取得
ScreenPointToRay()
↓
Rayを作る
Physics.Raycast()
↓
Colliderに当たったか判定
RaycastHit
↓
当たった情報を取得
hit.point
↓
位置
hit.normal
↓
向き
Instantiate()
↓
Prefabを生成
Destroy()
↓
生成したGameObjectを削除
となります。
最初の1行からかなり発展した
このシリーズの最初は、
Ray ray =
Camera.main.ScreenPointToRay(
Mouse.current.position.value
);
という1行でした。
そこから、
Camera.mainとは?
↓
ScreenPointToRayとは?
↓
Physics.Raycastとは?
↓
RaycastHitとは?
↓
hit.pointとは?
↓
hit.normalとは?
↓
Instantiateとは?
↓
Destroyとは?
と知識がつながってきました。
一つのゲームらしい処理を作りながら、
それぞれのコードが何のために存在するのか
を理解できます。
まとめ
今回の重要な部分は、
GameObject bulletHole =
Instantiate(
bulletHolePrefab,
position,
rotation
);
Destroy(
bulletHole,
3f
);
です。
Instantiate() で、
弾痕を作る
だけではなく、
GameObject bulletHole =
として、生成したGameObjectを受け取っています。
そして、
Destroy(bulletHole, 3f);
で、
その弾痕を3秒後に削除
しています。
一言で整理すると、
Instantiate
↓
作る
戻り値
↓
作ったものを覚える
Destroy
↓
消す
です。
そして前回までの内容と合わせれば、
hit.point
↓
どこに作る?
hit.normal
↓
どちらを向ける?
Instantiate
↓
弾痕を作る
Destroy
↓
一定時間後に消す
となります。
これでRaycastの学習が、単なる当たり判定から、
実際のゲームで見かける処理
へ一段進みました。
【シリーズ】Raycastで弾痕を作りながら学ぶ(4/9)
← 前の記事:hit.pointとhit.normalでマーカーを置く | シリーズ目次 | 次の記事:弾痕を壁の子にする(SetParent) →











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