今回は、ブロックを壊したらアイテムが出現して、そのアイテムをゲットしたらなにか良いこと(悪いこと)が起こる仕組みを導入します。
- Mac mini (M1, 2020)
- Unity 2022.3.36f1
アイテムの導入
以下の7つのアイテムを作りました。
- ボールが速くなる
- ボールが遅くなる
- ボールが大きくなる
- ボールが増える
- ブロックが止まる
- パドルからミサイルが発射される
- ライフが増える
全部解説するのは大変なので、ボールが速くなるアイテムの実装を解説します。ほかもだいたい同じようなもんです。
アイテムのオブジェクトを作成
ブロックが破壊されたら一定の確率でアイテムが出現して、物理法則に則って落下していくようにします。そのアイテムのプレハブを作ります。
アイテムの形は何でもいいのですが「2D Object > Sprites > Circle」にしました。見た目を変えたいならば、画像を貼り付けたり色を変えたり大きさを変えたりします。
このアイテムは物理法則に則って落下させるので、「Rigidbody 2D」コンポーネントを追加します。そして以下のように設定します。
項目 | 値 | 説明 |
---|---|---|
Body Type | Dynamic | 物理法則に従って動かす |
Mass | 1 | 質量 |
Linear Drag | 0 | オブジェクトが直線運動するときの抵抗 |
Angular Drag | 0 | オブジェクトが回転するときの抵抗 |
Gravity Scale | 0.05 | 重力の強さを調整する係数 |
MassとGravity Scaleの値を変更すれば、アイテムの落ちかたが変わります。
プレイヤーと接触したことを検知したいので「Circle Collider 2D」を追加します。そして「Is Trigger」にチェックを入れます。
「Is Trigger」を有効にすると、他のオブジェクトと衝突しても跳ね返りません。物理的な動作をせずに、衝突したことを検知します。衝突を検知したらアイテムを消して効果を発動するということをスクリプトでやりたいわけです。
アイテムのオブジェクトができたら、これをプレハブ化します。ヒエラルキーのオブジェクトをAssets/Prefabsフォルダにドラッグ&ドロップするだけです。
アイテムの出現と消滅
アイテムの出現
ブロックが破壊されたら一定の確率でアイテムが出現するようにします。BlockController.csスクリプトを修正します。
public class BlockController : MonoBehaviour
{
// ...省略...
public GameObject[] itemPrefabList; // アイテムのプレハブリスト
private float itemDropRate = 0.08f; // アイテム出現確率
// ...省略...
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ball") // 衝突したオブジェクトのタグが Ball なら
{
// ...省略...
// アイテムを出現させる
if (Random.value < itemDropRate)
{
// アイテムをランダムに選択
int randomIndex = Random.Range(0, itemPrefabList.Length);
GameObject itemPrefab = itemPrefabList[randomIndex];
// アイテムを生成
Instantiate(itemPrefab, transform.position, Quaternion.identity);
}
// ...省略...
}
}
}
itemPrefabList
はアイテムのプレハブを入れておく配列です。今のところアイテムは1種類ですが、複数のアイテムをこの配列に入れておいてランダムに選んで出現させます。
itemDropRate
はアイテムの出現確率です。0.08f
ならば8%の確率でアイテムが出現します。
Random.value
は0f〜1.0fのランダムな浮動小数点数を返します。itemDropRate
よりも小さいときに真(true)となりアイテムが出現します。
Random.Range(min, max)
は指定した範囲のランダムな数値を返します。min, maxがfloat型ならば浮動小数点数をint型ならば整数を返します。
で、float型の場合maxは範囲に含まれ、int型の場合maxは範囲に含まれないそうです。例えばRandom.Range(0, 10)
は0〜9を返します。なんでそんなややこしい仕様なんだろうと思いましたが、今回のように配列のインデックスとして使う場合、- 1
しなくていいから便利ですね。
最後にInstantiate()
でプレハブからオブジェクトを生成します。
アイテムの消滅
アイテムがプレイヤーや下の壁に当たったときに消滅するようにします。
スクリプトItemController.csを作成してアイテムのプレハブにアタッチします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemController : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Player") || collision.gameObject.CompareTag("WallBottom"))
{
Destroy(gameObject);
}
}
}
PlayerまたはWallBottomというタグが付いているオブジェクトに当たったらアイテムを消滅させます。
アイテムのコライダーは「Is Trigger」にチェックが入っているので、衝突判定はOnCollisionEnter2D
ではなくOnTriggerEnter2D
を使います。
アイテムの効果発動
ここまでのスクリプトでは、アイテムをゲットしても何も起こりません。アイテムがプレイヤーに当たったら効果を発動させましょう。ItemController.csを修正します。
public class ItemController : MonoBehaviour
{
private float speedChangeDuration = 10f; // 速度変化持続時間
private float speedMultiplierFast = 1.6f; // 速くする場合の倍率
private Color greenColor = new Color(0.5f, 1f, 0.5f, 1f); // 緑
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player") // プレイヤーに衝突したら
{
GameObject ball = GameObject.FindWithTag("Ball"); // ボールオブジェクトを取得
if (ball != null)
{
BallController ballController = ball.GetComponent<BallController>();
if (ballController != null)
{
// ボールを早くする
if (gameObject.CompareTag("ItemFastBall"))
{
StartCoroutine(ChangeBallSpeedRoutine(ballController, speedMultiplierFast, greenColor));
}
// ...他のアイテムの処理...
}
}
Destroy(gameObject); // アイテムを消滅
}
else if (collision.gameObject.tag == "WallBottom")
{
Destroy(gameObject); // アイテムを消滅
}
}
// ボールの速度を変えるコルーチン
private IEnumerator ChangeBallSpeedRoutine(BallController ballController, float speedMultiplier, Color color)
{
ballController.ChangeColor(color); // ボールの色を変更
ballController.ChangeSpeed(speedMultiplier); // ボールの速度を変更
yield return new WaitForSeconds(speedChangeDuration); // 10秒待つ
if (ballController != null && ballController.gameObject != null) // ボールが存在していれば
{
ballController.ChangeSpeed(1f); // 速度を元に戻す
ballController.ChangeColor(whiteColor); // 色を元に戻す
}
}
}
BallController.csを修正します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BallController : MonoBehaviour
{
// ...省略...
private float currentSpeed; // 現在のボール速度
void Start()
{
// ...省略...
currentSpeed = initialSpeed; // 初期速度を現在の速度に設定
}
// ...省略...
// ボールの速度を変更するメソッド
public void ChangeSpeed(float multiplier)
{
currentSpeed = initialSpeed * multiplier; // 現在の速度を変更
rb.velocity = rb.velocity.normalized * currentSpeed; // 新しい速度を適用
}
// ボールの色を変更するメソッド
public void ChangeColor(Color color)
{
spriteRenderer.color = color;
}
}
効果の持続時間を10秒間にするためにコルーチンを使いました。
テストプレイしてみたところ、アイテムをゲットしたらボールの速度が速くなったのですが、10秒経っても元に戻りません。何が間違っているのか2日ほど悩みました。で、原因はItemController.csの25行目でした。ここでアイテムを削除しているのでアタッチしているこのスクリプトも無効になっていたという。当たり前ですね。
この問題を解消するために禁断のシングルトンを使うことにしました。GameManager.csを修正します。
public class GameManager : MonoBehaviour
{
// ...省略...
public static GameManager Instance { get; private set; } // このクラスの唯一のインスタンス 外部から値を取得できるが設定できない
void Awake()
{
if (Instance == null) // Instanceがまだ未設定ならば
{
Instance = this; // Instanceにthisを代入(シングルトンインスタンス作成)
}
else // Instanceがすでに存在するならば
{
Destroy(gameObject); // 現在のオブジェクトを破棄
}
}
// ...省略...
}
ItemController.csを修正します。
public class ItemController : MonoBehaviour
{
// ...省略...
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player") // プレイヤーに衝突したら
{
GameObject ball = GameObject.FindWithTag("Ball"); // ボールオブジェクトを取得
if (ball != null)
{
BallController ballController = ball.GetComponent<BallController>();
if (ballController != null)
{
// ボールを早くする
if (gameObject.CompareTag("ItemFastBall"))
{
GameManager.Instance.StartCoroutine(ChangeBallSpeedRoutine(ballController, speedMultiplierFast, greenColor));
}
// ...他のアイテムの処理...
}
}
Destroy(gameObject); // アイテムを消滅
}
// ...省略...
}
// ...省略...
}
以上です。だいぶややこしくなったな。
こんな感じで他のアイテムも実装していきます。ここにもいろんな試行錯誤の物語があるんだけど、面倒くさいから書きません(暴言)。
スコアランキング
このゲームはunityroomに投稿するので、スコアランキング機能を実装したいんですね。下記のページの通りにやったら、簡単に実装できました。
さいごに
ちゅーわけで、ブロック崩しゲームが完成しました。最初に想定していたよりも要素が盛り沢山になって、楽しいゲームができたと思います。
unityroomで公開しているので、遊んでみてください。
でわでわ
コメント