Unityを始めて2週間の人が作った謎ゲー!

みなさん、こんにちは。とげら@Togeratです!

Unityはじめて18日目。
あのクソゲーから2週間ちょいたったけれど、成長のほどを見せるられるでしょうか。ドキドキ。

とげらはあのあと、北村愛美さん著作の「Unityの教科書」で勉強をはじめまして、いまは2Dゲームを作るチャプターまで終わらせたというとこです。この本は教えかたがほんとうまいですな~。飲み込みが悪いとげらでもサクサク進められて・・まぁ、その副作用として「いまなら何でも作れそうな気がする!」なんてナゾの万能感がわきあがってしまったわけですw

せっかく万能感があるうちに、オリジナル一本いってみますか!

ゲーム「ねぼすけを起こせ!」

まずは完成した作品をおひろめ~。タイトルは「ねぼすけを起こせ!」

ストーリー

あるところに「ねぼすけ」がいました。彼はシャボン玉を浮かべながら気持ちよく眠っていて、いつまでたっても起きません。

目覚まし時計であるあなたは、この子を起こさなければならないでしょう。あなたの武器は「音」です。画面をドラッグして音を飛ばし、シャボン玉に当ててみましょう。

シャボン玉が割れると「ねぼすけゲージ」が下がります。ゲージがゼロになるとあなたの勝ちです。小さいのを狙うほどたくさん削れるようですよ。それではグッドラック!!

↓の画像をクリックするとゲームが始まるので、みなさんもよかったら遊んでってくださいな^^

この作品の見どころポイント

どうかな、楽しんでもらえましたか?

個人的な見どころは、いろいろあるけど3つあげるなら、「スワイプによる音とばし」「ランダムなシャボン玉」「ねぼすけゲージ」ですね。

見どころ①「スワイプによる音とばし」

目覚まし時計の上に配置した「音」のオブジェクトには「Rigidbodyコンポーネント」をアタッチし、物理法則に従うようにしてあります。これを動かすための動力の強さと向きは、ユーザーが画面をマウスでドラッグする距離と方向で決まります。直感的ですね(そしてUnityの教科書の影響うけまくってますね!)

ドラッグの開始を「A地点」ドラッグの終わりを「B地点」としてそれぞれ座標をうけとり、x方向とy方向にそれぞれ移動した距離を計算します。それをそのままx方向とy方向にかかる動力とします。

ちなみに飛ばした「音」オブジェクトは画面外に出たタイミング破壊されます。それと同時に新たな「音」オブジェクトが目覚まし時計の上に出現します。「音」には数の限りを設定してないので、永遠に撃ち出せる弾となります。

見どころ②「ランダムなシャボン玉」

シャボン玉の挙動がいちばん苦労しましたねぇ。でも、いちばんお気に入りでもある部分です^^

シャボン玉のオブジェクトは、それのみだと一定速度で上に向かって移動するだけの存在です。そのオブジェクトをもとにオリジナル体を作り、それのクローンが次々と生成されるようします(※もとになるオリジナル体を「プレハブ」、クローンを「インスタンス」といいます)

シャボン玉には大中小の3つのサイズがあって、どの大きさのシャボン玉が生成されるかは完全ランダムです。ついでに生成される位置も一定範囲内でランダムにしています。こうすることで機械的な印象になることを防ぎます。

見どころ③「ねぼすけゲージ」

縦長のねぼすけゲージには、画像の満ち欠けを設定するための「Fill」機能を使います。満ち欠け方式は「Vertical(垂直)」「Bottom(下へ)」で、上から下に削られていく感じになります。

実際にゲージが削られるメソッドは、シャボン玉と音が衝突した「OnTriggerEnter」のなかで呼び出されるようにしてます。

この作品の課題点

ここまでは見どころを書いてきましたが、もちろん課題点もたくさんあります。目を背けたいですが・・逃げちゃダメだ・・。たくさんある課題点の中から3つあげるとするなら「プレイヤーの負けが存在しない」「サウンドがない」「画像(スプライト)のサイズ設計ミス」ですね。

課題点①「プレイヤーの負けが存在しない」

このゲームにはプレイヤーの負けが存在しないんですよね。時間切れもなければ、残機がなくなるってこともありません。負けがないゲームはプレイヤーへのプレッシャーがないぶん、クリアしたときの達成感があまり得られないんですよね。もったいない限り。

制限時間とカウントダウン、そして残機の管理・・それらはヒヨッコのとげらにとってまだ技術的にむずかしいものです。でもそういう要素こそゲームの花形ですからね。鼻歌気分でサクッと組み込んでいけるようにこれから精進していきまーす!

課題点②「サウンドがない」

このゲームには、BGMや効果音がないことにお気づきでしょうか。はっはっは、そんなものなくてもゲームはできるのですよ! ・・すみません、本音は入れたかったんだけどできなかったんです。

「音を飛ばす」というコンセプトなので、音はぜったいあったほうが良いですよね。音を飛ばすときのアラーム音や、シャボン玉がはじける音の音声素材などは実はちゃんと用意してたんです・・。だけど悲しいかな、それをプログラムにする技術がなかったのですよおおおお><

ぐやじーーーー!ヽ(`Д´)ノ

音声があるとゲームのリアリティは段違いになりますからね。音声の扱いについてももっと勉強していきたい、大きな課題です。

課題点③「画像(スプライト)のサイズ設計ミス」

ゲームで使われてるキャラはとげらがデザインしたんですが、最初はどのくらいのピクセル数でつくればいいか判断つかなかったんですよね。それで適当に作って Unity の画面に配置したら、予想外に小さくてガッカリ・・。

仕方なくスプライトのスケールをいじって、画面に合うように無理くり調整しました。でもこんなその場しのぎなことを続けていたら、大きなデータを扱うようになったときにぜったいに破綻します。破綻しないまでも混乱します。混乱するととげら死にます。

調べたところ、Unityでの1単位(1ユニット)が何ピクセルかは、画像のインスペクターの「Pixels per Unit」というプロパティで設定されてるんですね。ビューの1マスが1ユニットなので、このくらいのサイズがほしいなーと思うマス数を数えて「Pixels per Unit」をかけ算したら、どのくらいのピクセル数でつくればいいかわかってきますね! これからは事前にちゃんと計算してから作りはじめることにしましょう。

相方に遊んでもらった感想

相方を呼んできて、遊んでもらいました^^ どうどう?

相方「キャラは可愛いね!」

相方「むむ・・音符ぜんぜんとばせない・・」

相方「ギャー、とばしすぎて画面のそと行っちゃった!!」

相方「やっとクリアした・・難易度たかすぎるよこれー!涙」

うむむ、アクションゲームが苦手な相方にとっては難易度がたかすぎた模様。こんどは RPG みたいなものも企画してみますか・・。まぁでも、途中で不具合になることもなく、ちゃんと遊べるものにはなっていたみたいですね。一歩前進!

ファイル構成とスクリプト

みなさん、ここまで見ていただいて、どうもありがとう^^

最後にこのゲームのヒエラルキーウィンドウの構成と、スクリプトなどをご紹介して終わりますね。スクリプトは、長くなるので開閉ボタンから見られるようにしてあります!

Start Scene:スタート画面

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;  // シーン移動に使う

public class StartDirector : MonoBehaviour
{
    void Update()
    {
        // 画面をクリックしたら処理を実行
        if(Input.GetMouseButtonDown(0))
        {
            // GameSceneに移動する
            SceneManager.LoadScene("GameScene");
        }
    }
}

GameScene:ゲーム画面

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BubbleController : MonoBehaviour
{
    // シャボン玉の速度
    float flowSpeed = 0.01f;

    void Update()
    {
        // シャボン玉が移動
        this.transform.Translate(0, this.flowSpeed, 0);

        // シャボン玉が画面外に出たときに実行
        if(this.transform.position.y > 6.0f)
        {
            // シャボン玉がデストロイ!
            Destroy(this.gameObject);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BubbleGenerator : MonoBehaviour
{
    // 3種のシャボン玉のプレハブを入れる
    public GameObject bubble1Prefab;
    public GameObject bubble2Prefab;
    public GameObject bubble3Prefab;

    // シャボン玉を生み出す間隔
    float span = 1.0f;

    // 時間を蓄積するカウンター
    float delta = 0;

    // Update is called once per frame
    void Update()
    {
        // タイマーに時間を蓄積する
        this.delta += Time.deltaTime;

        // カウンターが、spanの数値を超えたら処理発動
        if(this.delta > this.span)
        {
            // カウンターの蓄積をゼロにする
            this.delta = 0;

            // これから作るインスタンスをいれておく変数を用意
            GameObject pop;

            // ランダムで0~2の中から整数を取り出す
            int bubbleKey = Random.Range(0, 3);

            // ↑の値により、作られるインスタンスを分岐
            if(bubbleKey == 0)
            {
                pop = Instantiate(bubble1Prefab) as GameObject;
            }
            else if(bubbleKey == 1)
            {
                pop = Instantiate(bubble2Prefab) as GameObject;
            }
            else
            {
                pop = Instantiate(bubble3Prefab) as GameObject;
            }

            // 出現時のx座標を5.0f~7.0fのランダムにする
            float px = Random.Range(5.0f, 6.0f);

            // 作ったインスタンスの初期位置
            pop.transform.position = new Vector3(px, -2.3f, 0);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NoteController : MonoBehaviour
{
    // 自分のリジッドボディコンポーネントを取得する変数
    Rigidbody2D rigid2D;

    // 自分にかかるチカラを取得する変数
    float throwForceX;
    float throwForceY;

    // スワイプを始める座標と終わる座標をを取得する変数
    Vector2 startPos;
    Vector2 endPos;

    void Start()
    {
        // 自分のリジッドボディコンポーネントを取得
        this.rigid2D = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        // 音符がy軸方向に移動していないときに処理を実行
        if(this.rigid2D.velocity.y == 0)
        {
            // マウスがクリックされたときに処理を実行
            if(Input.GetMouseButtonDown(0))
            {
                // スワイプを開始した座標を取得
                this.startPos = Input.mousePosition;
            }
            // マウスクリックが離れたときに処理を実行
            else if(Input.GetMouseButtonUp(0))
            {
                // スワイプが離れた座標を取得
                this.endPos = Input.mousePosition;

                // マウスをクリックしてから離すまでの距離を取得
                this.throwForceX = (this.endPos.x - this.startPos.x) * 3.0f;
                this.throwForceY = (this.endPos.y - this.startPos.y) * 3.0f;

                // かかるチカラの最大値を決める(X軸は500、Y軸は1000とする)
                if(this.throwForceX > 500) this.throwForceX = 500.0f;
                if(this.throwForceY > 1500) this.throwForceY = 1500.0f;

                // リジッドボディにチカラを与えて移動させる
                this.rigid2D.AddForce(transform.up * this.throwForceY +
                transform.right * this.throwForceX);
            }
        }

    // 自分がシャボン玉に衝突したとき処理を実行
    void OnTriggerEnter2D(Collider2D other)
    {
        // ヒットしたシャボン玉のオブジェクトを消滅させる
        Destroy(other.gameObject);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NoteGenerator : MonoBehaviour
{
    // 音符のプレハブを入れる
    public GameObject notePrefab;

    // 音符を新たに生成するメソッド(外部呼び出し用のためpublicにする)
    public void notePop()
    {
        GameObject notePop = Instantiate(notePrefab) as GameObject;
        notePop.transform.position = new Vector3(-6.5f, -0.7f, 0);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UIを使うため宣言
using UnityEngine.SceneManagement; // シーン移動に使うため宣言

public class GameDirector : MonoBehaviour
{
    // ねぼすけゲージのオブジェクトを入れる変数を用意
    GameObject hpGauge;

    void Start()
    {
        // ねぼすけゲージのオブジェクトを入れる
        this.hpGauge = GameObject.Find("hpGauge1");
    }

    void Update()
    {
        // ねぼすけゲージが0になったら処理を実行
        if(this.hpGauge.GetComponent<Image>().fillAmount == 0)
        {
            // ClearSceneに移動する
            SceneManager.LoadScene("ClearScene");
        }
    }

    // ねぼすけゲージを減らすメソッド
    public void DecreaseHP(float damage)
    {
        // ねぼすけゲージの画像のFillをへらす
        this.hpGauge.GetComponent<Image>().fillAmount -= damage;
    }
}

ClearScene:クリア画面

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // シーン移動に使うため宣言

public class ClearDirector : MonoBehaviour
{
    void Update()
    {
        // 画面をクリックしたら処理を実行
        if(Input.GetMouseButtonDown(0))
        {
            // GameSceneに移動する
            SceneManager.LoadScene("GameScene");
        }
    }
}

以上! とげらでした。

グッドユニティ~^^ノ

タイトルとURLをコピーしました