ライツアウト風

My Lights Out!(3 X 3)


チュートリアル作成日

2020年9月3日

 

ゲームのルール

9つのライトが3×3のマスに並んでいる。プレイヤーは9つすべてのライトを消すことができればクリアとなる。ただし、ライトは、押されると、自身と上下左右のライトの明滅が反転する仕組みとなっている。点いてるライトを押すと、自身は消えるが、上下左右のライトにも影響を与える。

 

制作環境

Unity 2019.4.6f1

C#

 

完成予想画像

遊び方

プレイヤーがライトをタップすると、そのライトおよび周囲のライトが明滅する。すべてのライトを消すことができればクリア。ライトの初期設定はランダムとなっている。

 


プロジェクトの新規作成

プロジェクトの新規作成

2D。

名前、保存場所は自由。

2分程度でプロジェクトが作成される。

 

AndroidへSwitch Platform

<Unity>--<File>--<Build Settings>

Androidを選択して、<Switch Platform>をクリック。2分程度で処理が終わる。

 

処理が終わったら、右上Xボタンで<Build Settings>を閉じる。

 

画面設定を16:9 Portraitに。

<Game>ウィンドウにて、サイズを<16:9 Portrait>にする。

 

 

<Scene>の名前を変更する。

<Project>--<Scene>フォルダの<SampleScene>で右クリック。名前を<Game>に変更。

ポップアップが出るので、<Reload>をクリック。

 

3つのフォルダを作る。

<Project>--<Assets>で右クリック、<Create>--<Folder>をクリック。名前を<Images>にする。

同じ要領で、<Prefabs>と<Scripts>フォルダも作成。

 


Lightオブジェクトの作成

このゲームのメインとなるLightオブジェクトを作成する。

Lightオブジェクトは、プレイヤーのタップを受けつけ、自身の明滅を反転するとともに、上下左右の4つのライトの明滅を反転させる。

 

オブジェクトを作る。

<Project>--<Images>で右クリック、<Create>--<Sprites>--<Hexagon>を選択。名前はそのままでよい。

<Project>--<Assets>--<Images>の<Hexagon>を、<Hierarchy>ウィンドウにドラッグアンドドロップ。

 

<Hierarchy>--<Hexagon>を選択。

<Inspector>ウィンドウにて、名前を<Light>に変更。

<Inspector>--<Add Component>--<Physics2D>--<Polygon Collider 2D>を選択。

 

 

スクリプトを記述する。

<Project>--<Scripts>フォルダ内にて右クリック、<Create>--<C# Script>を選択。

名前を<LightOnOff>に。(違う名前で作成してしまった場合、一度削除して、また作ってください)

今作った<LightOnOff>を<Hierarchy>--<Light>にアタッチ。

<LightOnOff>をダブルクリックして、Visual Studioを開く。

 

 

このスクリプトでやりたいことは、

1.ライトをタップするとライトが点いたり消えたりする。

2.そのライトの上下左右のライトも点いたり消えたりする。

主にこの2点である。

 

ライトが消えてる状態を0、点いてる状態を1とする。int型の変数isOffをpublicで定義する。

 

using UnityEngine;

public class LightOnOff : MonoBehaviour

{

    public int isOff;

}

 

-----------------------------

ライトがタップされたときの処理を作る。

publicなTurnOnLight関数を作成する。

 

public void TurnOnLight()

    {

        //ライトが点いていたら、消す。

        if (isOff == 1)

        {

            isOff = 0;

            GetComponent<SpriteRenderer>().color = new Color(0.5f, 0.5f, 0.5f);

        }

        //ライトが消えていたら、点ける。

        else if (isOff == 0)

        {

            isOff = 1;

            GetComponent<SpriteRenderer>().color = new Color(1, 1, 0);

        }

    }

-------------------------

色の(0.5f,0.5f,0.5f)は灰色、(1,1,0)は黄色である。

二つ目のifはelse ifにしておく。

 

別に、OnMouseDown関数を作成し、TurnOnLight関数を実行するよう記述する。

 

private void OnMouseDown()

    {

        TurnOnLight();

    }

 

-----------------------

Unityの機能で、OnMouseDown関数をコライダーを持つオブジェクトにつけておくと、タップされたときに働く関数となる。

 

試しに、Start関数に

 

private void Start()

    {

        isOff = 0;

    }

--------------

 

と記述してテストプレイしてみよう。

うまくいきましたか?

 

うまくいったら、Start関数の中身は削除しておく。

ここでうまくいかない場合、<Light>オブジェクトに<Polygon Collider 2D>がついていない可能性が考えられる。

    

 

上下左右のライトを点けたり消したりする関数を作る。

 

ここでの考え方は、タップした<Light>オブジェクトから上下左右にRayを飛ばし、その先にある<Light>オブジェクトの<TurnOnLight>関数を実行させる、というものである。Rayとはレーザー光線のようなものである。

たとえば、自身の上にある<Light>オブジェクトに対してRayを飛ばすときは、以下のように記述する。

 

//上のライト

        RaycastHit2D hitUp = Physics2D.Raycast(transform.position + Vector3.up, Vector2.up, 0.1f);

        if (hitUp.collider!=null)

        {

            hitUp.collider.GetComponent<LightOnOff>().TurnOnLight();

        }

-----------------

やっていることは、

自身の1マス上(transform.position+Vector3.up)から、上方向に向けて(Vector2.up)、0.1fの距離のレーザー光線を飛ばし、

なにかコライダーに当たったならば(if (hitUp.collider != null))、そのコライダーの有する<LightOnOff(Script)>コンポーネントのTurnOnLight関数を実行しなさい、ということである。

注意するところは、2か所の「2D」を忘れないことである。忘れると3D版の別の命令になってしまう。

新しくRay関数を作成し、以下のように記述。

 

void Ray()

    {

        //上のライト

        RaycastHit2D hitUp = Physics2D.Raycast(transform.position + Vector3.up, Vector2.up, 0.1f);

        if (hitUp.collider!=null)

        {

            hitUp.collider.GetComponent<LightOnOff>().TurnOnLight();

        }

        //下のライト

        RaycastHit2D hitDown = Physics2D.Raycast(transform.position + Vector3.down, Vector2.down, 0.1f);

        if (hitDown.collider != null)

        {

            hitDown.collider.GetComponent<LightOnOff>().TurnOnLight();

        }

        //右のライト

        RaycastHit2D hitRight = Physics2D.Raycast(transform.position + Vector3.right, Vector2.right, 0.1f);

        if (hitRight.collider != null)

        {

            hitRight.collider.GetComponent<LightOnOff>().TurnOnLight();

        }

        //左のライト

        RaycastHit2D hitLeft = Physics2D.Raycast(transform.position + Vector3.left, Vector2.left, 0.1f);

        if (hitLeft.collider != null)

        {

            hitLeft.collider.GetComponent<LightOnOff>().TurnOnLight();

        }

    }

------------------

hitUp、hitDown、hitRight、hitLeft、と4つあるので、内容を間違えないように注意する。

とくに、transform.position + Vector3.xxxのところと、if ( hitxxxx.collider ----)のところに注意。

 

先に作ったOnMouseDown関数に、いまのRay関数の実行を記述。これで、押されたライト自身と上下左右のTurnOnLight関数が実行される。

 

private void OnMouseDown()

    {

        TurnOnLight();

        Ray();

    }

-------------

 

Unityに戻る。

<Hierarchy>--<Light>を、<Project>--<Assets>--<Prefabs>にドラッグアンドドロップしてプレハブ化する。

<Hierarchy>--<Light>を削除。

 

 


GameManagerオブジェクトの作成

GameManagerオブジェクトは、ゲーム開始時にライトを9つセットし、またプレイヤーがライトをすべて消したかどうか判定する。

 

 

オブジェクトを作る。

<Hierarchy>にて<Create Empty>を選択し、名前を<GameManager>に変更。

<Transform>--<Position>を(0,0,0)に。

 

 

スクリプトを記述する。

<Project>--<Scripts>内で右クリック、<Create>--<C# Script>を選択。名前を<GamedController>に。

作った<GameController>スクリプトを<Hierarchy>--<GameManager>オブジェクトにアタッチ。

<GameController>をダブルクリックして、Visual Studioへ。

 

 

このスクリプトでやりたいことは、

1.問題の作成

2.クリア判定

主にこの2つである。

 

問題作成の考え方は、点いている<Light>と消えている<Light>をあわせて9つ生成し、3×3のマスに並べる、というものである。

・9つのうち、2つ以上のライトが点いているようにする。

・9つ生成した<Light>をListに入れておき、それをシャッフルする。

・シャッフルした<Light>を3×3のマスに並べる。

 

<GameController>スクリプトの冒頭に、以下のように定義。

 

using System.Collections.Generic;

using UnityEngine;

 

public class GameController : MonoBehaviour

{

    public GameObject LightPrefab;

    private List<GameObject> lightList = new List<GameObject>();

-----------------------------

 

問題を作成するMakeQuestion関数を作る。長くなるが、スクリプト内のコメントを参考に理解していただきたい。

 

 void MakeQuestion()

    {

        //2~8のintをRandomに生成。(int型のRandom.Rangeでは、おわりの数(9)は含まれない)

        int r = Random.Range(2, 9);

 

        //r個の点いている<Light>を生成してListに追加

        for (int i = 0; i < r; i++)

        {

            GameObject OnLight = Instantiate(LightPrefab);

            OnLight.GetComponent<LightOnOff>().isOff = 1;

            OnLight.GetComponent<SpriteRenderer>().color=new Color(1, 1, 0);

            lightList.Add(OnLight);

        }

        //9-r個の消えている<Light>を生成してListに追加

        for (int i = 0; i < (9-r); i++)

        {

            GameObject OffLight = Instantiate(LightPrefab);

            OffLight.GetComponent<LightOnOff>().isOff = 0;

            OffLight.GetComponent<SpriteRenderer>().color = new Color(0.5f, 0.5f, 0.5f);

            lightList.Add(OffLight);

        }

        //上記の処理により、lightListには[0]~[8]の9個のGameObjectが入っている。

 

        //Listをシャッフル

        for (int i = 0; i < 9; i++)

        {

            int x = Random.Range(0, 9);

            GameObject temp = lightList[x];

            lightList[x] = lightList[i];

            lightList[i] = temp;

        }

 

        //lightListを3×3に並べる

        for (int i = 0; i < 3; i++)

        {

            for (int j = 0; j < 3; j++)

            {

                lightList[0].transform.position = new Vector2(i, j);

                lightList.RemoveAt(0);

            }

        }

    }

------------------

intの時のRandom.Rangeでは、終端の数字は含まれないので注意。

forの回数に注意。

シャッフルは、「My Hit And Blowチュートリアル」と同じ処理である。

最後の3×3に並べるところでは、lightList[0]を並べては削除していることに注意。結果としてListは空になっている。

 

このMakeQuestion関数を実行するように、Start関数に記述。

 

void Start()

    {

        MakeQuestion();

    }

----------------

 

Unityに戻る。

<Project>--<Prefabs>--<Light>を、

<GameManager>オブジェクトの<GameController(Script)>の<LightPrefab>にアタッチ。

 

テストプレイ。

うまく動きましたか?

 

 

しかし、ライトたちが真ん中に来ていない。

 

これは、<Light>プレハブは(0,0,0)から(2,2,0)の座標に並んでいるが、<Main Camera>の<Position>は(0,0,0)のためである。

そこで、カメラの設定を行う。

 


Main Cameraの設定

<Hierarchy>--<Main Camera>を選択。下記画像を参考に、以下のように設定。

<Position>-->(1,1,-10)

<Clear Flags>--><Solid Color>

<Background>-->黒色(お好きな色)

<Projection>--><Orthographic>

<Size>--> 4

 

テストプレイして、具合を確認。

 


クリア判定

現状ではクリアしても何もなく寂しいので、「CLEAR!」と出るようにしたい。

 

Canvasの設置と設定。

<Hierarchy>にて、<UI>--<Canvas>を選択。

<Inspector>にて下記のように設定。

<Render Mode>--><Screen Space - Camera>

<Render Camera>--><Main Camera>をアタッチ

<Plane Distance>--> 5

<UI Scale Mode>--<Scale With Screen>

<Reference Resolution>--> X720, Y1280

<Screen Match Mode>--><Expand>

 

 

<Hierarchy>--<Canvas>で右クリック、<UI>--<Text>を選択。以下の画像を参考に設定。

<Font>は好きなものがあればそれを使う。

<Horizontal Overflow><Vertical Overflow>--><Overflow>

<Raycast Target>-->チェックマークを外す。(チェックがついていると、タップしたときに<Light>オブジェクトではなく、この<Text>オブジェクトを認識してしまう)

 

 

Visual Studioの<GameController>スクリプトに戻る。

今作った<Text>オブジェクトを使うので、冒頭にて定義。

さらに、Text型を使うためには、using UnityEngine.UI;を記述。

Start関数にて、空っぽのテキストを表示させる。

 

using UnityEngine.UI;

 

public class GameController : MonoBehaviour

{

    public GameObject LightPrefab;

    private List<GameObject> lightList = new List<GameObject>();

    //

    public Text ClearText;

 

    // Start is called before the first frame update

    void Start()

    {

        ClearText.text = "";

        MakeQuestion();

    }

------------------

 

Unityに戻って、<GameManager>の<GameController(Script)>に、<Text>オブジェクトを関連付ける。

 

 

クリアの認識

すべてのライトが消えたときにクリアとなる。

考え方としては、プレイヤーが<Light>オブジェクトをタップするたびに、<isOff>の合計を計算し、それが0であればすべて消えていることになる。

 

Visual Studioの<GameController>に戻る。

プレイヤーが<Light>をタップしたときに働く、AnswerCheck関数を作る。これは<LightOnOff>スクリプトから動かしたいので、publicな関数にしておく。

説明は、スクリプト内のコメントを参考にしてください。

 

 public void AnswerCheck()

    {

        //<LightOnOff>のisOffの合計値を計算するために、変数を用意。

        //はじめに0になるように設定。

        int count = 0;

 

        //ゲーム上の<LightOnOff>スクリプトを探し出し、配列にする処理。

        LightOnOff[] allLights = FindObjectsOfType<LightOnOff>();

 

        //上で作成したallLights配列それぞれのisOffを加算する。

        foreach (var item in allLights)

        {

            count += item.isOff;

        }

 

        //もしcount=0ならば、すべてのライトが消えているのでクリア。

        if (count==0)

        {

            ClearText.text = "CLEAR!";

        }

 

//テストプレイ用に、現在のcountの値を<Console>ビューに表示させる

Debug.Log(count);

    }

-------------------

FindObjectsOfType<LightOnOff>();を使用して、9つの<LightOnOff>を集めている。

FindObjectsと複数形であることに注意。単数形のものもある。

 

 

<LightOnOff>スクリプトへ移動。

<LightOnOff>スクリプトで<GameController>スクリプトを動かしたいので、冒頭に定義。あわせてStart関数に記述。

 

public class LightOnOff : MonoBehaviour

{

    public int isOff;

    private GameController gameControllerCS;

 

    private void Start()

    {

        gameControllerCS = FindObjectOfType<GameController>();

    }

---------------------------

Start関数に記述しているのは、先ほどの単数形。なお、CSはC Sharpの意味。

次に、OnMouseDown関数に、1行追記。

 

    private void OnMouseDown()

    {

        TurnOnLight();

        Ray();

        gameControllerCS.AnswerCheck();

    }

--------------------

ここでAnswerCheck関数を使用するために、publicな関数にしておく必要があった。

 

保存してテストプレイ。

<Console>ウィンドウに、countの値が正しく表示されているだろうか。クリアしたときにメッセージがゲーム画面に表示されるだろうか。

なお、<Console>ウィンドウは、<Unity>--<Window>--<General>の中にある。

 

よさそうであれば、AnswerCheck関数のDebug.Logを削除。

 

 

クリアしたら<Light>を操作できないようにする。

現状では、クリア後もライトを操作できてしまうので、できないようにしたい。

<LightOnOff>スクリプトに、bool型の変数を定義。

 

public class LightOnOff : MonoBehaviour

{

    public int isOff;

    private GameController gameControllerCS;

    public bool isClear;

---------------

 

OnMouseDown関数の冒頭に、isClearがtrueならば働かないように記述。

 

private void OnMouseDown()

    {

        if (isClear)

        {

            return;

        }

        //

        TurnOnLight();

        Ray();

        gameControllerCS.AnswerCheck();

    }

----------------

 

<GameController>スクリプトへ移動。

クリアしたときの処理の中に、すべての<LightOnOff>のisClearをtrueにするように記述。

 

//もしcount=0ならば、すべてのライトが消えているのでクリア。

        if (count==0)

        {

            ClearText.text = "CLEAR!";

            //

            foreach (var item in allLights)

            {

                item.isClear = true;

            }

        }

-----------------

これで、クリアしたらライトを操作できなくなった。

 

 


NewGameボタンの作成

クリアしたとき、またはクリアできなくて新しい問題にチャレンジしたいときのために、NewGameボタンを設置する。

 

Unityに戻る。

 

Buttonオブジェクトの作成

<Hierarchy>--<Canvas>にて右クリック、<UI>--<Button>を選択。

名前を<NewGameBtn>にしておく。

<Inspector>--<Rect Transform>にて以下のように設定。

<PosY>--> -500

<Width>--> 200

<Height>--> 100

 

 

<Hierarchy>--<NewGameBtn>の子オブジェクトになっている<Text>の内容やサイズを変更。

 

<Game>ウィンドウには以下のように表示されている。

 

 

ボタンのスクリプトを書く。

 

<GameController>スクリプトへ移動。

冒頭に、以下の1行を追記。

 

using UnityEngine.SceneManagement;

---------------------

publicなNewGame関数を作成。現在の<Game>シーンに移動するよう記述。

 

public void NewGame()

    {

        SceneManager.LoadScene("Game");

    }

 

-----------------------

 

Unityに戻って、<NewGameBtn>に、いま作成したNewGame関数を関連付ける。

<Hierarchy>--<NewGameBtn>を選択し、<Inspector>--<Button>--<On Click()>の+マークをクリック。

<GameManager>オブジェクトをアタッチして、<GameController>のNewGame関数を関連付ける。

 

 

次に、<Unity>--<File>--<Build Settings>にて、<Add Open Scenes>をクリック。

 

 

 

テストプレイ。

うまく動きましたか?

 

タイトルを入れたりして、見た目を整えよう。

 


実機ビルド(for Android)

スマートフォン端末(Android)の「開発者向けオプション」と「USBデバッグの有効化」を設定する。

以下のサイトを参照。

https://developer.android.com/studio/debug/dev-options?hl=ja

 

スマートフォンとPCを、USBケーブルでつなぐ。(おそらく充電器のコードがUSBケーブル)

 

<Unity>--<File>--<Build Settings>--<Player Settings>をクリック。

 

<CompanyName><ProductName>を適当に決め、下の画像の<Default Orientation>を<Portrait>にする。これでスマートフォンを傾けてもゲーム画面が回転しない。

 

 

 

 

設定したら、右上の×ボタンで<Project Settings>を閉じる。

 

 

スマートフォンとPCがケーブルで接続されていることを確認し、<Build And Run>をクリック。

 

ファイル名を求められるので、「TestSample」などとしておく。

おそらく3分くらいでビルド処理が終了する。

終わったら、右上Xボタンで<Build Settings>を閉じる。

Unityに、ビルドが正常に終了したことを示すメッセージが出ている。

 

USBケーブルを抜いて、スマートフォンでテストプレイしてみる。

 

これで、あなたオリジナルのゲームの完成です!

 

 

より面白くするために、

・効果音を入れてみる。

・5×5マスのゲームにしてみる。

・クリアしたときの演出を派手にする。

・ライトをオンオフだけでなく、3色、4色と増やしてみる。

などなど、いろいろ試してみましょう。

 

 

それでは!

Feel free to Share and Comment!

ホームへ戻る。

コメントをお書きください

コメント: 5
  • #1

    moi (月曜日, 22 3月 2021 23:26)

    こちらも完成しました!ヒットアンドブローよりは簡単にできた気がします。
    次のパズルにも着手します!

  • #2

    Renoboy (火曜日, 23 3月 2021 07:55)

    moiさん、お疲れ様でした。おめでとうございます!
    シャッフルのプログラムはカードゲームなどでも使える便利なものです。
    レイキャストもいろいろ使えます。
    ぜひ、使いこなしてくださいね!

  • #3

    nagisa (土曜日, 19 6月 2021 11:10)

    初めてunityとC#に触れたのでprogramを何行目のどこに追加すればいいのかわかりません。
    「ここかな?」と、予想していれてもエラーばかりです。自分で作ったゲームで遊んでみたいです。
    もしよろしければ、どこにプログラムを追加すればいいのか書いてもらえないでしょうか?
    お願いします。

  • #4

    Renoboy (土曜日, 19 6月 2021 13:12)

    nagisaさん
    ホントのホントの初心者でしたら、ライツアウトではなくて「Unity,C#超入門 数当てゲーム」から始めてみてください。

    がんばろー!

  • #5

    nagisa (水曜日, 23 6月 2021 19:51)

    わかりました。頑張ってみます!

 

 

Profile

string name = "Renoboy";

string message = "《Unity,C#》に詳しくない私による、詳しくない人のためのチュートリアルです。詳しくないので、難しいことは決して教えません。";

 

Appale-Takemuroid