はじめに
JunShimuraさんの「[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で ::(1)ステージ配置~(6)ブロックを手作業で並べる)」をよりゲームらしく改造する。
作成する記事は下記の通りです。
- スコアの表示
- ゲームマネージャークラスを作る(この記事)
- ブロックをスクリプトで配置
- 音を鳴らす
ゲームマネージャークラスとは
ゲームの進行に関する情報を管理します。
例えば、「タイトル -> ゲームスタート -> (ミスしたら)ゲームオーバー -> タイトル」 といった進行となります。
クラス名は人によってマネージャー(Manager)ではなくコントローラー(Controller)かも知れません。
ゲームマネージャークラスを作成する前に幾つか準備が必要となります。
順々にこなしてください。
タイトルの表示
スコアの表示はScene上はカメラの影響で傾いて表示されますが、実際のGame上は垂直に表示されます。
テキストの追加
スコア表示の時とは違い、今回は間にパネル(Panel)を挟んでみます。パネルを挟むとゲーム画面全体に"もや"がかかったような表示になります。※靄(もや)とは霧(きり)よりも薄いものを指す。
参照:【Unity開発】uGUIのPanelの使い方(非表示の方法等)【ひよこエッセンス】
Hierarchyの下にある[Create]>[UI]>[Canvas]を選択して下さい。次に[Create]>[UI]>[Panel]を選択して下さい。同様に[Create]>[UI]>[Text]を選択して下さい。
今のままだとPanel
とText
が同一階層になっているので、Text
をPanel
の子階層にするため、Text
をドラッグ&ドロップでPanel
に重ねてください。
Canvasの設定変更(Title GUI)
Canvasの[Inspector]タブにて名前を「Title GUI」にします。CanvasコンポーネントにあるRender Mode
をScreen Space - Camera
に変更します。次にRender Camera
のところにある○をクリックし、開いたリスト画面からMain Camera(Camera)
を選択します。
このままだと、タイトルが壁の下に表示されてしまうので、カメラとGUIとの距離であるPlane Distance
を18
に変更します。
※Render Mode
はScreen Space - Camera
ではなくデフォルトのScreen Space - Overlay
のままでもいいです。Screen Space - Overlay
はゲームで使われる他のオブジェクトの描画が終わったあとにGUIが描画されます。つまりGUIが絶対に最前面になります。本来はこれが望ましいです。その場合、Scene上ではGUIが別の位置に配置されますが、Game上では重なって表示されます。CanvasをクリックすればScene上のGUI位置に移動するので、慣れればこちらを使う方がいいかも知れません。
タイトルの作成(Title)
今回、タイトル部分は「Game Start」と「Game Clear」と「Game Over」の3つをスクリプトで文字と色を書き換えて使いまわします。
Title GUI配下のTextの[Inspector]タブにて名前を「Title」にします。
[Rect Transform]と[Scale]を下図のように変更します。今回は目立つように文字の大きさを3倍にしておきます。
次にText
をGame Start
と入力します。Font Style
をBold And Italic
、Font Size
を48
、Alignment
を水平方向は中央寄せ/垂直方向は中央揃えにして、Color
を緑(0,255,0)にします。
タイトルに影やアウトラインを装飾する
そのままの文字だとそっけないので、影(Shadow)とアウトライン(Outline)のエフェクトを追加して装飾します。
参照:UnityのGUIの基本的な作り方 uGUIのPanel、Button、Text、Imageの使い方
Title
の[Inspector]タブの一番下にある[Add Compornent]ボタンをクリックして、UI>Effects>Shadowを追加します。同様にUI>Effects>Outlineを追加します。
影の大きさを変更したいので、Shadow(Script)
のEffect Distance
のX
を4
にします。
シーン表示が変になったら
シーンの向きや拡大・縮小などの変更はアンドゥ(CTRL+Z)やリドゥ(CTRL+Y)が効かないので、元に戻す方法が分からなくなる時があります。
その場合、Sceneビュータブの右クリックメニューの[Add Tab]-> [Scene]で、新たなSceneビューを作成します。問題の起こっているSceneビューは右クリックメニューの [Close Tab] で閉じてしまいましょう。
参照:【Unity】シーン表示上のシーンの向きに戸惑わない方法
シーンの保存
現在、シーン名は初期値の「Untitled」になったままなので、ここらへんで名前を付けて保存しましょう。
メニューの[File]>[Save Scenes]を選択すると、保存ダイアログ画面が表示されるので「main」で保存してください。
ラケットの位置をずらす
現在のラケットの位置が高めなので、少し下げましょう。
Position
のZ
を-6.0
から-8.5
に変更します。
ボールの位置をずらす
現在のボールの位置が高めなので、ラケットの乗せた感じにするように下げましょう。
Position
のZ
を0.0
から-7.5
に変更します。
ブロックのタグを追加
ブロックを色で分けるように今後はしていきたいのですが、その前にどの色でもブロックと判定されるように、タグを追加します。
Blockの[Inspector]タブのTagのところでUntagged
をクリックして出るプルダウンから、Add Tag...
を選びます。タグ&レイヤー画面(Tag & Layers)のTagの一覧の左にある+
をクリックしBlock
を追加します。
※名前を間違えたら-
をクリックします。この際に(Removed)
となりますが、プロジェクトをリロードした時に一覧から削除されるので気にせず、+
をクリックして再追加してください。
タグ&レイヤー画面を閉じる方法が見当たらないのでHierarchy
のBlock
を再選択します。[Inspector]タブのTagのプルダウンを選ぶと、Block
が現れるので選択します。
※ブロックが複数ある場合、全てのTagをBlock
に変更してください。(複数選択で一気に変更可能)
スクリプトの作成
先に空のゲームオブジェクトを作成します。
Hierarchyの下にある[Create]>[Create Empty]を選択して下さい。
名前をManager
とします。Position
はXYZを0
にしておきます。
Manager
の[Inspector]タブの一番下にある[Add Compornent]ボタンをクリックして、Scriptアセットを作成、追加します。ファイル形式はcsを選びます。
ファイル名をManager
とします。そうすると、Assetsの中に「Manager.cs」が出来ています。
※先頭は英大文字です。間違えた場合、ファイル名と中身のクラス名の両方を修正してください。
Manger.csを編集する
Asset内のManger
スクリプト(C#のアイコン)をダブルクリックすると、エディタが開きます。
優先的に開かれるエディタは、多くの場合はMonoDevelopというソフトが起動されます。
少し長いのでコピー&ペーストで入力してしまいましょう。
※MonoDevelopでペーストが効かないに時はMonoDevelopを閉じ直してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Manager : MonoBehaviour {
// ゲームステート
public enum GameState{
Opening,
Playing,
Clear,
Over
}
// 現在のゲーム進行状態
public GameState currentState = GameState.Opening;
// パネル
private GameObject panel;
// タイトル
private GameObject title;
// ラケット
private GameObject racket;
// ボール
private GameObject ball;
// テキスト
private Text text;
// ステージ
private int stage = 1;
// Use this for initialization
void Start () {
// Panelゲームオブジェクトを検索し取得する
panel = GameObject.Find("Panel");
// Titleゲームオブジェクトを検索し取得する
title = GameObject.Find("Title");
// ラケットゲームオブジェクトを検索し取得する
racket = GameObject.Find("Racket");
// ボールゲームオブジェクトを検索し取得する
ball = GameObject.Find("Ball");
// テキスト
text = title.GetComponent<Text> ();
// オープニング
GameOpening ();
}
// Update is called once per frame
void Update () {
// ゲーム中ではなく、Spaceキーが押されたらtrueを返す。
if(currentState == GameState.Opening && Input.GetKeyDown (KeyCode.Space)) {
dispatch (GameState.Playing);
}
if (currentState == GameState.Playing) {
// ゲームクリアーの判定
if (GameObject.FindGameObjectsWithTag ("Block").Length == 0) {
dispatch (GameState.Clear);
}
}
}
// 状態による振り分け処理
public void dispatch (GameState state){
GameState oldState = currentState;
currentState = state;
switch (state) {
case GameState.Opening:
GameOpening ();
break;
case GameState.Playing:
GameStart ();
break;
case GameState.Clear :
GameClear ();
break;
case GameState.Over:
if (oldState == GameState.Playing) {
GameOver ();
}
break;
}
}
// オープニング処理
void GameOpening ()
{
currentState = GameState.Opening;
// ボールの動作停止
Time.timeScale = 0;
// タイトル名のセット
SetTitle ("Game Start", Color.green);
// ラケットの初期位置をセット
racket.transform.position = new Vector3 (0.0f, 0.0f, -8.5f);
// ボールの初期位置をセット
ball.transform.position = new Vector3 (0.0f, 0.0f, -7.5f);
}
// ゲームスタート処理
void GameStart ()
{
// パネル非活性化
panel.SetActive (false);
// ボールの動作開始
Time.timeScale = 1.0f;
// ボールの初期化
FindObjectOfType<Ball> ().Init ();
}
// ゲームクリアー処理
void GameClear ()
{
stage++;
// タイトル名のセット
SetTitle ("Game Clear", Color.yellow);
// 3秒後にオープニング処理を呼び出す
Invoke("GameOpening", 3f);
}
// ゲームオーバー処理
void GameOver ()
{
// タイトル名のセット
SetTitle ("Game Over", Color.red);
// ステージ初期値
stage = 1;
// ハイスコアの保存
FindObjectOfType<Score> ().Save();
// 3秒後にオープニング処理を呼び出す
Invoke("GameOpening", 3f);
}
// オープニング処理
void SetTitle (string message, Color color)
{
// タイトル名のセット
text.text = message;
text.color = color;
// パネル活性化
panel.SetActive (true);
}
}
少し説明します。
-
Start
処理で使用するオブジェクトを予め格納しておくことで、都度オブジェクト検索するのを防いでいます。 - ブロックのタグ検索をして何も見つからない場合にゲームクリアーと判定しています。
-
Time.timeScale = 0
として、ボールの動きを停止しています。 -
dispatch
処理はpublic扱いにして他スクリプトから呼べるようにしています。 - タイトル表示有無はPanelを活性/非活性で実現させています。
Ball.csを編集する
一番下にある壁にボールが当たったら、ミスとしたとしてゲームオーバーにします。
ボールの衝突判定処理(OnCollisionEnter
)を追加します。
また、ゲームスタート時にボールの加速値を初期化したいため、Start
にある処理をManager
スクリプトから呼べるようpublicを付けたInit
処理を作成し、処理内部を移動させます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour {
private float speed = 20.0f; //これを追加
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnCollisionEnter(Collision collision) {
//衝突判定
if (collision.gameObject.name == "BottomWall") {
// ゲームオーバー処理を呼ぶ
FindObjectOfType<Manager>().dispatch(Manager.GameState.Over);
}
}
public void Init(){
// 加速値を初期化
Rigidbody rd = this.GetComponent<Rigidbody> ();
rd.velocity = Vector3.zero;
rd.AddForce(
(transform.forward + transform.right) * speed,
ForceMode.VelocityChange);
}
}
実装を確認する
エディタ画面中央の実行ボタンを押してみましょう。
オープニング画面でスペースキーで押すとゲームスタートとなります。
ボールが一番下の壁に当たるとゲームオーバーになり、3秒後にオープニング画面に自動で戻ります。また、ブロックが全て無くなるとゲームクリアーになり、これも3秒後にオープニング画面に自動で戻ります。
すぐ気が付くと思いますが、実はブロックの初期化処理が出来ていません。
ブロックをスクリプトで配置する上でPrefab(プレハブ)を使いたいのですが、説明が長くなるので次回にします。
学んだこと
- Panelを使うことで背景に"もや"がかかるように表示できること
- 空のゲームオブジェクトを作成して管理するスクリプトを使うこと
- タグを使うことで複数種類があっても判定がタグ1つで済むこと
- マネージャクラスにて進行状況により処理を振り分けること