ダークモード
第5回 弾を撃つ
この回でつくるもの:Z キーかスペースキーを押すと、自機の機首から光る弾がまっすぐ右へ飛んでいくようにします。押しっぱなしで連射もできます。
この回で学ぶこと
- 光るマテリアル(Emission)を作る
- プレハブ(Prefab)を作って、スクリプトからたくさん出す(
Instantiate) - 決まった時間がたったらオブジェクトを消す(
Destroy) - Input Actions に新しいキー(バインド)を追加する
if文と「タイマー」で連射の間隔を決める
前回までのふりかえり
前回は、自機が画面の外へはみ出さないようにして、上下に動くと機体が傾くようにしました。 これで PlayerController は完成です。Player の中身は Player > Bank > Model と Player > Muzzle になっています。 今回は Muzzle(機首の先の目印)から弾を出します。
1. 弾を作る
1-1. 球(Sphere)を置く
- Hierarchy ウィンドウの何もないところを右クリックします。
- 3D Object > Sphere を選びます。
- できた
Sphereを選んだまま、名前をPlayerBulletに変えます(F2 キーか、ゆっくり2回クリック)。 - Inspector の Transform で Position を (0, 0, 0) にします。
- Rotation が (0, 0, 0) になっていることを確かめます。
- Scale を (0.6, 0.15, 0.15) にします。横に細長い弾になります。
1-2. 当たり判定をいったん外す
Sphere には最初から Sphere Collider(当たり判定の形)が付いています。当たり判定は第8回でまとめて設定するので、今は外しておきます。
- Hierarchy で
PlayerBulletを選びます。 - Inspector の Sphere Collider の右はしにある ⋮(縦の点3つ)をクリックします。
- Remove Component を選びます。
先生へ
第8回で Rigidbody と一緒に Sphere Collider(Is Trigger ON)を付け直します。今のうちに外しておくと、第8回の手順が「付ける」だけで済み、説明がそろいます。
1-3. 光るマテリアルを作る
マテリアルは「物の表面の色や質感」を決めるアセットです。弾を光らせてみましょう。
- Project ウィンドウで
Assets/_Game/Materialsフォルダを開きます。 - 何もないところを右クリックして、Create > Material を選びます。
- 名前を
Mat_PlayerBulletにします。 Mat_PlayerBulletを選び、Inspector の Shader が Universal Render Pipeline/Lit になっていることを確かめます。- Surface Inputs の Base Map の右にある色の四角をクリックし、黄色を選びます。
- 少し下にある Emission(発光)のチェックボックスをオンにします。
- Emission Map の右にある色の四角をクリックします。
- 出てきたカラーピッカー(色を選ぶ窓)で黄色を選び、下の Intensity(明るさ)を 2 くらいにします。
- カラーピッカーを閉じます。
- Project の
Mat_PlayerBulletを、Scene ビューのPlayerBulletにドラッグ&ドロップします。
ヒント
Emission は「自分で光っているように見せる」設定です。今はただ明るい色に見えるだけですが、第19回で Bloom(光がにじむ効果)を入れると、ぼわっと光って見えるようになります。
ここで Ctrl+S(Macは Cmd+S)を押してシーンを保存しましょう。
2. Bullet スクリプト
2-1. スクリプトを作る
- Project ウィンドウで
Assets/_Game/Scriptsフォルダを開きます。 - 何もないところを右クリックして、Create > Scripting > MonoBehaviour Script を選びます。
- 名前を
Bulletにして Enter を押します(ファイル名とクラス名は同じにします)。 Bulletをダブルクリックしてエディタで開きます。
2-2. コードを書く
中身をすべて消して、次のコードを書きます。
csharp
using UnityEngine;
// 自機の弾
public class Bullet : MonoBehaviour
{
public float speed = 15f; // 速さ
public float lifeTime = 2f; // 何秒で消えるか
void Start()
{
Destroy(gameObject, lifeTime);
}
void Update()
{
// 自分の向いている「右」へ進む(回転させると斜めに飛ぶ)
transform.Translate(Vector3.right * speed * Time.deltaTime);
}
}書けたら Ctrl+S(Macは Cmd+S)でファイルを保存して、Unity にもどります。
2-3. コードの解説
| 行 | 意味 |
|---|---|
public float speed = 15f; | 弾の速さ。1秒に15進みます |
public float lifeTime = 2f; | 弾が生まれてから消えるまでの秒数 |
Destroy(gameObject, lifeTime); | 「自分自身(gameObject)を、lifeTime 秒後に消して」というお願い |
transform.Translate(...) | 今いる場所から、指定した分だけ動かす |
Vector3.right | 右向き (1, 0, 0) の矢印 |
Destroyは、オブジェクトを消す命令です。2つ目に数字を書くと「その秒数がたってから消す」になります。- 弾を消さないと、画面の外へ飛んでいった弾がいつまでも残り、ゲームがだんだん重くなります。だから2秒で消しています。
Translateは、何も書かないと 自分の向き を基準に動きます。今は回転が (0, 0, 0) なので右へ飛びます。後の回で弾をななめに回転させて撃つときに、この性質が役に立ちます。
2-4. 弾に付けて試す
- Hierarchy で
PlayerBulletを選びます。 - Inspector の一番下の Add Component をクリックします。
- 検索欄に
Bulletと入力し、出てきた Bullet をクリックします。 - ▶ Play ボタンを押します。
弾が右へ飛んでいき、2秒ほどで Hierarchy から消えればOKです。 確認できたら、もう一度 ▶ を押して Play を止めます。
3. プレハブにする
3-1. プレハブとは
弾は、撃つたびに新しく作ります。そのために「弾の設計図」を作っておきます。これを プレハブ(Prefab) といいます。 プレハブがあれば、スクリプトから何個でも同じ弾を作れます。
3-2. プレハブを作る
- Project ウィンドウで
Assets/_Game/Prefabsフォルダを開きます。 - Hierarchy の
PlayerBulletを、Project ウィンドウのPrefabsフォルダの中へドラッグ&ドロップします。 - Project に青い箱のアイコンの
PlayerBulletができたことを確かめます。 - Hierarchy の
PlayerBulletの文字も青くなっていることを確かめます。
3-3. シーンの弾を消す
設計図ができたので、シーンに置いてある弾は消してしまいます。
- Hierarchy の
PlayerBulletを選びます。 - Delete キー(Macは Cmd+Delete)を押して消します。
- Project の
PrefabsフォルダにPlayerBulletが残っていることを確かめます。 - Ctrl+S(Macは Cmd+S)でシーンを保存します。
よくあるまちがい
Project の方の PlayerBullet を消してしまうと、設計図がなくなってしまいます。消すのは Hierarchy の方 だけです。
4. Attack に Z と Space を追加する
4-1. Attack アクションを見る
テンプレートに入っている InputSystem_Actions には、最初から Attack(攻撃)というアクションがあります。でも、キーボードではマウスの左クリックや Enter キーくらいしか登録されていません。 シューティングでよく使う Z キーと Space キーを追加しましょう。
- Project ウィンドウで
Assetsフォルダを開きます。 InputSystem_Actionsをダブルクリックします。- Input Actions の編集ウィンドウが開きます。
- 左の Action Maps で Player を選びます。
- まん中の Actions の中に Attack があることを確かめます。
4-2. Z キーを追加する
- Actions の Attack の右にある + ボタンをクリックします(マウスを乗せると出てきます)。
- 出てきたメニューから Add Binding を選びます。
- Attack の下に
<No Binding>という行が増え、選ばれた状態になります。 - 右側の Binding Properties の Path の欄をクリックします。
- 出てきた小さな窓の左上にある Listen ボタンをクリックします。
- キーボードの Z キーを押します。
- 候補に出てきた Z [Keyboard] をクリックします。
- Path が
Z [Keyboard]になったことを確かめます。 - その下の Use in control scheme で Keyboard&Mouse にチェックを入れます。
4-3. Space キーを追加する
- もう一度 Attack の右の + をクリックし、Add Binding を選びます。
- 新しい
<No Binding>を選んだまま、Path の欄をクリックします。 - Listen をクリックして、Space キーを押します。
- Space [Keyboard] をクリックします。
- Use in control scheme で Keyboard&Mouse にチェックを入れます。
4-4. 保存する
- ウィンドウの上の方にある Save Asset ボタンをクリックします。
- Auto-Save にチェックが入っている場合は、自動で保存されるのでボタンを押さなくても大丈夫です。
- 編集ウィンドウを閉じます。
注意
保存しないで閉じると、追加したキーが消えてしまいます。閉じるときに「保存しますか?」と聞かれたら Save を選びましょう。
ヒント
Space キーは、最初から Jump(ジャンプ)アクションにも登録されています。このゲームでは Jump を使わないので、気にしなくて大丈夫です。
5. PlayerShooter スクリプト
5-1. スクリプトを作る
- Project ウィンドウで
Assets/_Game/Scriptsフォルダを開きます。 - 右クリックして Create > Scripting > MonoBehaviour Script を選びます。
- 名前を
PlayerShooterにして Enter を押します。 - ダブルクリックしてエディタで開きます。
5-2. コードを書く
中身をすべて消して、次のコードを書きます。
csharp
using UnityEngine;
using UnityEngine.InputSystem;
// 弾を撃つスクリプト
public class PlayerShooter : MonoBehaviour
{
public GameObject bulletPrefab; // ふつうの弾
public Transform muzzle; // 弾が出る場所
public float fireInterval = 0.15f; // 連射の間隔(秒)
float timer;
InputAction attackAction;
void Start()
{
attackAction = InputSystem.actions.FindAction("Attack");
}
void Update()
{
timer -= Time.deltaTime;
if (attackAction.IsPressed() && timer <= 0f)
{
Shoot();
timer = fireInterval;
}
}
void Shoot()
{
Instantiate(bulletPrefab, muzzle.position, Quaternion.identity);
}
}書けたら保存して Unity にもどります。Console ウィンドウに赤いエラーが出ていないか確かめましょう。
5-3. コードの解説
| 行 | 意味 |
|---|---|
public GameObject bulletPrefab; | 撃つ弾のプレハブを入れる箱 |
public Transform muzzle; | 弾が出る場所(Muzzle)を入れる箱 |
public float fireInterval = 0.15f; | 次の弾が撃てるまでの時間(秒) |
float timer; | 「あと何秒で撃てるか」を数えるタイマー |
InputSystem.actions.FindAction("Attack") | 第3回の Move と同じように、Attack アクションを探してくる |
attackAction.IsPressed() | Attack のキーが 押されている間 ずっと true(はい)になる |
Shoot(); | 下に書いた Shoot の中身を実行する |
Instantiate(...) | プレハブから新しいオブジェクトを作る |
if 文のおさらい
csharp
if (attackAction.IsPressed() && timer <= 0f)
{
// ここは条件が「はい」のときだけ実行される
}if (条件)は「もし〜なら」という意味です。条件が正しいときだけ{ }の中が実行されます。&&は「かつ」という意味です。「キーが押されている」かつ「タイマーが 0 以下」のときだけ撃ちます。<=は「以下」です。
タイマーのしくみ
timer -= Time.deltaTime;で、毎フレーム、たった時間だけタイマーを減らします。- 撃ったら
timer = fireInterval;で、タイマーを 0.15 にもどします。 - 0.15 秒たつとタイマーが 0 以下になり、また撃てるようになります。
こうすると、押しっぱなしでも 1秒に約6〜7発の「ほどよい連射」になります。タイマーがないと、毎フレーム(1秒に60回以上)弾が出てしまいます。
関数(メソッド)を自分で作る
csharp
void Shoot()
{
Instantiate(bulletPrefab, muzzle.position, Quaternion.identity);
}StartやUpdateと同じように、自分で名前を付けた「命令のまとまり」を作れます。これを 関数(メソッド)といいます。Shoot();と書くと、Shootの中身が実行されます。- 「撃つ」処理を1か所にまとめておくと、後の回で音を鳴らしたり、弾の種類を増やしたりするときに、ここだけを直せば済みます。
Instantiate の3つの材料
| 材料 | ここでは | 意味 |
|---|---|---|
| 1つ目 | bulletPrefab | 何を作るか(プレハブ) |
| 2つ目 | muzzle.position | どこに作るか(Muzzle の位置) |
| 3つ目 | Quaternion.identity | どの向きで作るか(「回転なし」という意味) |
6. Inspector で Bullet Prefab と Muzzle をつなぐ
6-1. PlayerShooter を Player に付ける
- Hierarchy で
Player(いちばん上の親)を選びます。 - Inspector の一番下の Add Component をクリックします。
PlayerShooterと入力して、Player Shooter をクリックします。
6-2. プレハブと Muzzle を入れる
Playerを選んだまま、Inspector の Player Shooter を見ます。- Project ウィンドウの
PrefabsフォルダにあるPlayerBulletを、Bullet Prefab の欄へドラッグ&ドロップします。 - Hierarchy の
Playerの左の ▶ を開き、子のMuzzleを表示します。 - Hierarchy の
Muzzleを、Inspector の Muzzle の欄へドラッグ&ドロップします。 - Fire Interval が 0.15 になっていることを確かめます。
- Ctrl+S(Macは Cmd+S)でシーンを保存します。
注意
Muzzle をドラッグするときに、Hierarchy で Muzzle をクリックしてしまうと Inspector の表示が Muzzle に切りかわってしまいます。Player を選んだまま、Muzzle を 押したまま 引っぱってきましょう。
6-3. 動作確認
▶ Play ボタンを押して、次のようになればOKです。
- Z キーか Space キーを押すと、機首の先から弾が右へ飛ぶ
- 押しっぱなしにすると、一定のリズムで連射される
- 自機を動かしながら撃っても、弾は自機の今いる場所から出る
- 飛んでいった弾は、2秒ほどで Hierarchy から消える(
PlayerBullet(Clone)が増えたり減ったりする)
ヒント
Play 中に Inspector で Fire Interval や、プレハブの Speed を変えると、すぐに結果を試せます。ただし Play を止めると、シーン上の値は元にもどります。気に入った値はメモしておき、Play を止めてから入れ直しましょう。
チャレンジ課題
- Fire Interval を 0.05 や 0.5 に変えて、連射の気持ちよさがどう変わるか試してみよう
PlayerBulletプレハブの Speed や Lifetime を変えてみよう(プレハブは Project でダブルクリックすると編集できます)- マテリアルの色を水色や赤に変えて、自分好みの弾にしてみよう
Muzzleの位置を動かして、機首の先ぴったりから弾が出るように調整してみよう
つまずきポイント
| こんなときは | ここを確認 |
|---|---|
| キーを押しても弾が出ない | Input Actions で Z / Space を追加したあと Save Asset したか。Player に PlayerShooter が付いているか |
Console に UnassignedReferenceException と出る | PlayerShooter の Bullet Prefab と Muzzle の欄が空になっていないか |
| 弾が自機の真ん中や変な場所から出る | Muzzle の位置。Muzzle の Position の Z が 0 になっているか |
| 弾が飛ばずにその場に止まる | PlayerBullet プレハブに Bullet が付いているか。Speed が 0 になっていないか |
| 弾がたて長や斜めになる | PlayerBullet プレハブの Rotation が (0, 0, 0)、Scale が (0.6, 0.15, 0.15) か |
| 弾が消えずにどんどん増える | Bullet の Start に Destroy(gameObject, lifeTime); を書いたか |
まとめ
- 何度も作るものは プレハブ にして、
Instantiateでスクリプトから作る - いらなくなったものは
Destroyで消す。秒数を付けると時間がたってから消える ifと&&で「キーが押されていて、しかも時間がたっていたら」という条件が書ける- タイマーを使うと、連射の間隔を決められる