前回作った3D的当てゲームに、新しい遊びを少しずつ追加します。コードを書いたらすぐに動かし、思ったとおりになるか確かめながら進めましょう。
前回のゲームを確認しよう
Unity 6.3 LTS(6000.3.24f1)で前回のプロジェクトを開き、再生してみましょう。
- マウスで視点を動かせる
- 画面中央の照準で的を狙える
- 左クリックで的だけを消せる
- 右クリックまたはホイール押し込みで視点を戻せる
前回の手順を見直す
前回の記事「3D的当てゲームを作ろう」へ戻り、止まったSTEPを確かめよう。
スコアを画面に出そう
的をいくつ当てたか分かるように、まずは画面に「SCORE: 0」と表示します。このSTEPではまだ数字を増やしません。
Canvasの中にUI → Text – TextMeshProを作ります。初めて使うときにTMP Importerが開いたら、上段の「Import TMP Essentials」を押します。
作った文字の名前を「ScoreText」にし、文字を「SCORE: 0」に変えます。画面の左上へ置き、読みやすい大きさと色にしましょう。
背景が明るい場所と暗い場所のどちらでも読める色や大きさになっているかな?
ヒントを見る|文字が見えないとき
ScoreTextがCanvasの子になっているか確認しよう。Rect Transformの位置と、文字の色も見直してみよう。
Gameビューの左上に「SCORE: 0」を表示しよう。照準と重ならず、読みやすければ成功です。
的を当てたらスコアを増やそう
画面に出したScoreTextをShootCheckから動かします。的を1個消すたびに、スコアを1増やしましょう。
ShootCheck.csの先頭にusing TMPro;を追加し、クラス内に次の変数を用意します。
public TMP_Text scoreText;
int score = 0;
scoreTextは画面の文字、scoreは現在の得点を覚えます。
既存のStart()の中で、最初の表示を0にします。
scoreText.text = "SCORE: " + score;
Targetに当たったときの処理を次のようにします。
if (hit.collider.gameObject.CompareTag("Target"))
{
score++;
scoreText.text = "SCORE: " + score;
Destroy(hit.collider.gameObject);
}
score++で得点を1増やし、新しい数字をScoreTextへ表示します。
Unityへ戻ったら、Main CameraのShoot CheckにあるScore Textへ、HierarchyのScoreTextをドラッグします。
Score TextがNoneのままだと、ゲーム中に文字を変更できません。
床や台を撃ったときにスコアが増えないのは、コードのどの条件が働いているからかな?
ヒントを見る|数字が増えないとき
Main CameraのScore Text欄と、SphereのTarget Tagを確認しよう。Consoleに赤いエラーが出ていないかも見てみよう。
的を2個撃って「SCORE: 2」にしよう。次にCubeを撃ち、数字が増えないことも確かめよう。
全部当てたらCLEARにしよう
ゲーム開始時にTargetの数を数え、的を消すたびに残りを1減らします。残りが0になったら「CLEAR!」と表示しましょう。
クラス内に残りの的を覚える変数を追加します。
int remainingTargets;
既存のStart()内で、Target Tagが付いた的を数えます。
remainingTargets =
GameObject.FindGameObjectsWithTag("Target").Length;
的を増やしたときも、再生開始時の個数を自動で数えます。
Targetに当たったとき、スコアと残りの数を変えます。
if (hit.collider.gameObject.CompareTag("Target"))
{
score++;
remainingTargets--;
if (remainingTargets == 0)
{
scoreText.text = "CLEAR! SCORE: " + score;
}
else
{
scoreText.text = "SCORE: " + score;
}
Destroy(hit.collider.gameObject);
}
4個の的を消したのにスコアが8になりました。前のscore++を残したまま、新しい処理にも同じ行を追加していたため、1回で2点増えていました。
ヒントを見る|点数が倍になるとき
ShootCheck.csでscore++;を検索し、Targetを消す処理の中に1回だけあるか確認しよう。
すべての的を消してCLEARを出そう。表示されたスコアが、置いた的の数と同じか確認しよう。
動く的を作ろう
赤い的は止めたまま、青い的だけを左右に動かします。動く的は、タイミングを考えて撃つ必要があります。
MovingTarget.csを作り、次のコードを書きます。
using UnityEngine;
public class MovingTarget : MonoBehaviour
{
public float moveDistance = 2f;
public float moveSpeed = 2f;
Vector3 startPosition;
void Start()
{
startPosition = transform.position;
}
void Update()
{
float offset =
Mathf.Sin(Time.time * moveSpeed) * moveDistance;
transform.position =
startPosition + new Vector3(0f, 0f, offset);
}
}
最初の場所を覚え、時間に合わせてZ方向の位置を行ったり来たりさせます。この射撃場では、Z方向がGameビューの左右に見えます。
Moving Targetを青いSphereだけに付け、Move DistanceとMove Speedをまず2にします。
Move Speedを速くすると難しさはどう変わるかな? Move Distanceを大きくすると、狙う範囲はどうなるかな?
ヒントを見る|違う方向へ動くとき
このコードはZ方向へ動かします。Sceneビューの青い矢印と、カメラから見た左右の向きを確認しよう。
青い的だけを左右へ動かし、狙って消そう。スコアが1増えることも確認しよう。
動く的をボーナスにしよう
動いて狙いにくい青い的だけを3点にします。赤い的は、これまでどおり1点です。
MovingTargetに、青い的の点数を追加します。
public int scoreValue = 3;
ShootCheckにあったscore++;を、次の処理へ置き換えます。
MovingTarget movingTarget =
hit.collider.GetComponent<MovingTarget>();
if (movingTarget != null)
{
score += movingTarget.scoreValue;
}
else
{
score++;
}
GetComponentで、当たった的にMovingTargetが付いているか調べます。付いている青い的はScore Valueの点数、付いていない赤い的は1点を加えます。
動く的の3点はちょうどよいかな? 速さや動く距離を変えたら、何点にすると遊びやすいか試してみよう。
ヒントを見る|青い的が1点のままのとき
青いSphereにMoving Targetが付いているか、Score Valueが3になっているか確認しよう。古いscore++;が条件の前に残っていないかも調べよう。
青い的を先に撃って3点、次に赤い的を撃って4点になることを確かめよう。
制限時間を表示しよう
30秒から減るタイマーを作ります。まずは時間を表示し、0になったあとの動きは次に考えます。
Canvas内のScoreTextを複製してTimerTextにします。ShootCheckには次の変数を追加します。
public TMP_Text timerText;
public float timeLimit = 30f;
float timeRemaining;
Start()で、残り時間を30秒にします。
timeRemaining = timeLimit;
timerText.text =
"TIME: " + Mathf.CeilToInt(timeRemaining);
Update()の最初で、前のフレームから進んだ時間を引きます。
timeRemaining -= Time.deltaTime;
if (timeRemaining < 0f)
{
timeRemaining = 0f;
}
timerText.text =
"TIME: " + Mathf.CeilToInt(timeRemaining);
ScoreTextを複製したため、TimerTextも左上に置かれています。次はTimerTextを右上へ移し、情報を読みやすくします。
CLEARしたあとも時間は減り続けます。0秒になっても撃てるままでよいかな? ゲーム終了時に止めたいものを考えてみよう。
ヒントを見る|時間が表示されないとき
Shoot CheckのTimer Text欄が空になっていないか確認しよう。Consoleに赤いエラーがある場合は、まず一番上のエラーを読んでみよう。
30秒から時間を減らしながら、時間内に4個の的を倒そう。
ここまでのコードを整理しよう
機能が増えて、コードも長くなりました。ここで3本のスクリプトの仕事を分け、現在の形を確認します。
ShootCheckの処理の順番
- ゲームが終わっているか調べる
- 残り時間を減らす
- 左クリックされたか調べる
- 画面中央からRayを飛ばす
- 当たったものにTarget Tagがあるか調べる
- 的の種類に合わせて得点を増やす
- 残りの的が0ならCLEARにする
- 命中した的を消す
ShootCheck.cs|射撃ゲームの進行
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class ShootCheck : MonoBehaviour
{
Camera cameraComponent;
public TMP_Text scoreText;
public TMP_Text timerText;
public float timeLimit = 30f;
int score = 0;
int remainingTargets;
float timeRemaining;
bool gameEnded;
void Start()
{
cameraComponent = GetComponent<Camera>();
scoreText.text = "SCORE: " + score;
remainingTargets =
GameObject.FindGameObjectsWithTag("Target").Length;
timeRemaining = timeLimit;
timerText.text =
"TIME: " + Mathf.CeilToInt(timeRemaining);
}
void Update()
{
if (gameEnded)
{
return;
}
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0f)
{
timeRemaining = 0f;
timerText.text = "TIME: 0";
scoreText.text = "TIME UP!\nSCORE: " + score;
gameEnded = true;
return;
}
timerText.text =
"TIME: " + Mathf.CeilToInt(timeRemaining);
if (Mouse.current == null ||
!Mouse.current.leftButton.wasPressedThisFrame)
{
return;
}
Ray ray = cameraComponent.ViewportPointToRay(
new Vector3(0.5f, 0.5f, 0f));
if (!Physics.Raycast(ray, out RaycastHit hit))
{
return;
}
Debug.Log(hit.collider.gameObject.name);
if (!hit.collider.CompareTag("Target"))
{
return;
}
MovingTarget movingTarget =
hit.collider.GetComponent<MovingTarget>();
if (movingTarget != null)
{
score += movingTarget.scoreValue;
}
else
{
score++;
}
remainingTargets--;
if (remainingTargets == 0)
{
gameEnded = true;
scoreText.text = "CLEAR!\nSCORE: " + score;
}
else
{
scoreText.text = "SCORE: " + score;
}
Destroy(hit.collider.gameObject);
}
}
MouseLook.cs|視点を動かす
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseLook : MonoBehaviour
{
public float sensitivity = 0.1f;
float yaw;
float pitch;
float startYaw;
float startPitch;
void Start()
{
yaw = transform.eulerAngles.y;
pitch = transform.eulerAngles.x;
startYaw = yaw;
startPitch = pitch;
}
void Update()
{
if (Mouse.current == null) return;
Vector2 move = Mouse.current.delta.ReadValue();
yaw += move.x * sensitivity;
pitch = Mathf.Clamp(
pitch - move.y * sensitivity, -80f, 80f);
transform.rotation = Quaternion.Euler(pitch, yaw, 0f);
if (Mouse.current.rightButton.wasPressedThisFrame ||
Mouse.current.middleButton.wasPressedThisFrame)
{
yaw = startYaw;
pitch = startPitch;
transform.rotation = Quaternion.Euler(pitch, yaw, 0f);
return;
}
}
}
MovingTarget.cs|青い的を動かす
using UnityEngine;
public class MovingTarget : MonoBehaviour
{
public float moveDistance = 2f;
public float moveSpeed = 2f;
public int scoreValue = 3;
Vector3 startPosition;
void Start()
{
startPosition = transform.position;
}
void Update()
{
float offset =
Mathf.Sin(Time.time * moveSpeed) * moveDistance;
transform.position =
startPosition + new Vector3(0f, 0f, offset);
}
}
ヒントを見る|エラーが出たとき
まず波かっこの組み合わせを確認します。次にTMP_Textの間に余分な記号がないか、hitをRaycastより前で使っていないかを見てみよう。
3本のスクリプトを見比べ、それぞれが担当している仕事を1つずつ説明してみよう。

コメント