【Unityゲーム開発】 指定した目の出るスロットマシンを実装する方法 【備忘録】

この記事では、スロットマシンの基本的な機能を実装する手順を解説します。

紹介する実装方法は、Unityのチュートリアルレベルの知識があることが前提にしています。すべての設定項目等を細かく伝えているチュートリアル的な記事ではない点をご留意下さい。

目次

プロジェクトの設定

ここでは、以下のクラスの構成でスロットを作成していきます。

Unityエディターバージョン:2020.3.30f1以上

  • ReelGenerator.cs: スロットのリールを生成するクラス
  • ReelController.cs: リールの回転や停止を制御するクラス
  • ReelSymbol.cs: スロットの絵柄に数字を付与するクラス
  • Reel Symbol.cs: スロットのシンボル数値を設定するクラス
  • SlotDisplay.cs: カメラを使ってスロットを表示するクラス
  • MainManager.cs: ゲームのメインロジックを管理するクラス

また、開発の際に以下のサイトを参考にさせて頂きました。

リールの生成と管理

リールはReelGenerator.csで生成されます。このクラスは、スロットマシンに必要なリールを作成し、リールの位置や回転を制御します。

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

public class ReelGenerator: MonoBehaviour
{

    public enum ReelPositon
    {
        left,
        center,
        right
    }


    public const int RealCount = 90;

    private GameObject _real;
    private GameObject[] _imgobj; //絵柄のプレハブを格納(計9種)
    GameObject[] tmp_obj;//リールの配列(プレハブの種類が入る)

    private Transform[] img_pos = new Transform[RealCount];//リールの柄の位置

    const float offsetX = -20f;

    private void Awake()
    {
        tmp_obj = new GameObject[RealCount];
    }

    public void GenerateReel( ReelPositon reelpos , GameObject[] imgobj)//リール生成関数本体
    {
        _imgobj = imgobj;

        float posX = 0;  //リールのTransform
        switch (reelpos)
        {
            case ReelGenerator.ReelPositon.left:
                posX = -5f + offsetX;
                break;
            case ReelGenerator.ReelPositon.center:
                posX = 0.0f + offsetX;
                break;

            case ReelGenerator.ReelPositon.right:
                posX = 5f + offsetX;
                break;
        }


        for (int i = 0; i < RealCount; i++)
        {
            Vector3 pos = new Vector3(posX, -5.5f + (5.5f * i), 0.0f);//プレハブ位置の決定
            int tmp = i % imgobj.Length; // 順番に画像を選択

            //プレハブからtempの絵柄idのGameObjectを生成
            tmp_obj[i] = (GameObject)Instantiate(imgobj[tmp]); 

            //リールのオブジェクトを親にする
            tmp_obj[i].transform.SetParent(transform, false);

            //プレハブのtransformを取得
            img_pos[i] = tmp_obj[i].GetComponent<Transform>();

            //プレハブ位置の代入
            img_pos[i].localPosition = pos;

            // レイヤーをSlotLayerに設定
            tmp_obj[i].layer = LayerMask.NameToLayer("SlotLayer");

        }
    }
    public void DestroyReel()
    { //リールをデストロイしてリセットする関数
        GameObject[] reels = GameObject.FindGameObjectsWithTag("reel"); //reelタグが付いているオブジェクトをすべて取得
        foreach (GameObject i in reels)//各reelsのオブジェクトに対して以下を行う
        {
            Destroy(i);//reelsの各オブジェクトを破壊する
        }
    }
}

このメソッドは、リールに表示する画像オブジェクト(スロットのシンボル)を配列として受け取り、それを基にリールを生成します。

リールの回転と停止

リールの回転はReelController.csで制御されます。

このクラスでは、リールを回転させ、特定のシンボルが中央に来たときにリールを停止させます。

using System.Collections;
using UnityEngine;


public class ReelController: MonoBehaviour
{
    /// <summary>
    /// 回転をリセットするY座標
    /// </summary>
    private const float resetY = -445.5f;

    /// <summary>
    /// ゲッターのプレハブ
    /// </summary>
    //public GameObject GetterPrefab; // ゲッターのプレハブ

    public GameObject[] ReelImages; //絵柄のプレハブを格納(計9種)
    public GameObject ReelPrefab; // リールのプレハブ

    private GameObject Reel; // 左のリールを取得
    private GameObject Reel2; // 中央のリールを取得
    private GameObject Reel3; // 右のリールを取得

    Vector3 initialpos; // 左のリールの初期位置
    Vector3 initialpos2; // 中央のリールの初期位置
    Vector3 initialpos3; // 右のリールの初期位置

    float speed1; // 左のリールの回転速度
    float speed2; // 中央のリールの回転速度
    float speed3; // 右のリールの回転速度

    bool stopflag1 = false; // 左のリールが停止しているか
    bool stopflag2 = false; // 中央のリールが停止しているか
    bool stopflag3 = false; // 右のリールが停止しているか

    bool allflag = true; // 全てのリールが停止しているかのフラグ

    ReelGenerator reelGenerator1; // 左のリールの生成器
    ReelGenerator reelGenerator2; // 中央のリールの生成器
    ReelGenerator reelGenerator3; // 右のリールの生成器


    private int stopSymbol1; // 左のリールを停止する絵柄の番号
    private int stopSymbol2; // 中央のリールを停止する絵柄の番号
    private int stopSymbol3; // 右のリールを停止する絵柄の番号


    private const float smallReelScale = 0.5f; // 小さいリールのスケール

    private void Awake()
    {
        init(); // 初期化
    }


    /// <summary>
    /// 開始時に実行する
    /// </summary>
    public void init()
    {
        //リールの作成

        //左のリール
        Reel = Instantiate(ReelPrefab, Vector3.zero, Quaternion.identity);
        Reel.name = "LeftReel";
        var geneLeftReel = Reel.GetComponent<ReelGenerator>();
        geneLeftReel.GenerateReel(ReelGenerator.ReelPositon.left, ReelImages);

        //真ん中のリール
        Reel2 = Instantiate(ReelPrefab, Vector3.zero, Quaternion.identity);
        Reel2.name = "CenterReel";
        var geneCenterReel = Reel2.GetComponent<ReelGenerator>();
        geneCenterReel.GenerateReel(ReelGenerator.ReelPositon.center, ReelImages);

        //右のリール
        Reel3 = Instantiate(ReelPrefab, Vector3.zero, Quaternion.identity);
        Reel3.name = "RightReel";
        var geneRightReel = Reel3.GetComponent<ReelGenerator>();
        geneRightReel.GenerateReel(ReelGenerator.ReelPositon.right, ReelImages);

        //初期位置を取得しておく
        initialpos = this.Reel.transform.position; 
        initialpos2 = this.Reel2.transform.position;
        initialpos3 = this.Reel3.transform.position;

        // 各リールの生成器を取得
        reelGenerator1 = GameObject.Find("LeftReel").GetComponent<ReelGenerator>();
        reelGenerator2 = GameObject.Find("CenterReel").GetComponent<ReelGenerator>();
        reelGenerator3 = GameObject.Find("RightReel").GetComponent<ReelGenerator>();
    }



    public void StartReel()//リールを再生成して回し始める関数
    {
        DestroyGetter();//リールを回す時に絵柄ID取得プレファブを破壊する。

        speed1 = -1.3f; //リールの回転スピードの代入
        speed2 = -1.3f;
        speed3 = -1.3f;

        stopflag1 = false; //ボタンを押していない状態にリセット
        stopflag2 = false;
        stopflag3 = false;

        allflag = false;
    }



    public IEnumerator StopReel(int stopReelNum, int symbol)
    {
        switch (stopReelNum)
        {
            case 1:
                stopReel1(symbol); //リールを止める関数を実行
                break;
            case 2:
                stopReel2(symbol); //リールを止める関数を実行
                break;
            case 3:
                stopReel3(symbol); //リールを止める関数を実行
                break;
        }

        yield break;
    }
    public void ReelUpdate() //ゲームが始まったら
    {
        this.Reel.transform.Translate(0, speed1, 0); //リールをy方向(下)に動かす
        this.Reel2.transform.Translate(0, speed2, 0);
        this.Reel3.transform.Translate(0, speed3, 0);


        if (Reel.transform.position.y < resetY) //リールが一番下に来たら
        {
            this.Reel.transform.position = initialpos; //初期位置に戻す
        }
        if (Reel2.transform.position.y < resetY)
        {
            this.Reel2.transform.position = initialpos2;
        }
        if (Reel3.transform.position.y < resetY)
        {
            this.Reel3.transform.position = initialpos3;
        }

        // 停止フラグが立っている場合、指定された絵柄でリールを止める
        if (stopflag1 && ShouldStopReel(Reel, stopSymbol1) && speed1 != 0)
        {
            speed1 = 0;

        }
        if (stopflag2 && ShouldStopReel(Reel2, stopSymbol2) && speed2 != 0)
        {
            speed2 = 0;
        }
        if (stopflag3 && ShouldStopReel(Reel3, stopSymbol3) && speed3 != 0)
        {
            speed3 = 0;
        }

        // 全てのリールが停止した場合
        if (speed1 == 0 && speed2 == 0 && speed3 == 0 && allflag != true)
        {
            allflag = true;
        }

    }

    // 指定された絵柄で止めるための条件をチェックするメソッド
    private bool ShouldStopReel(GameObject reel, int stopSymbol)
    {
        ReelSymbol[] reelSymbols = reel.GetComponentsInChildren<ReelSymbol>();
        foreach (ReelSymbol reelSymbol in reelSymbols)
        {
            if (reelSymbol.symbolNum == stopSymbol)
            {
                float positionY = reelSymbol.transform.localPosition.y;
                float reelY = reel.transform.localPosition.y;
                float targetY = 0.09f; // 中央にしたい高さ
                if (Mathf.Abs(reelY + positionY - targetY) < 0.2f) // 中央に来るための閾値調整
                {
                    return true;
                }
            }
        }
        return false;
    }

    public bool CheackAllStop()
    {
        bool ret;

        if (speed1 == 0 && speed2 == 0 && speed3 == 0 && allflag == true)//すべてのリールが停止していてallflagがfalseの場合
        {
            ret = true;
        }
        else { ret = false;}

        return ret;

    }

    // 左のリールを停止する
    private void stopReel1(int symbol)
    {
        if (stopflag1 != true)
        {
            speed1 = -0.4f;
        }
        stopSymbol1 = symbol;
        stopflag1 = true;
    }

    // 中央のリールを停止する
    private void stopReel2(int symbol)
    {
        if (stopflag2 != true)
        {
            speed2 = -0.4f;
        }
        stopSymbol2 = symbol;
        stopflag2 = true;
    }

    // 右のリールを停止する
    private void stopReel3(int symbol)
    {
        if (stopflag3 != true)
        {
            speed3 = -0.4f;
        }
        stopSymbol3 = symbol;
        stopflag3 = true;
    }

    public void DestroyGetter()
    { //絵柄ID取得プレファブをデストロイしてリセットする関数
        GameObject[] getters = GameObject.FindGameObjectsWithTag("getter"); //getterタグが付いているオブジェクトをすべて取得
        foreach (GameObject i in getters)//各getterのオブジェクトに対して以下を行う
        {
            Destroy(i);//getterの各オブジェクトを破壊する
        }
    }

    /// <summary>
    /// ランダムな負けシンボル番号を返す。
    /// VerA
    /// </summary>
    /// <returns>ランダムな負けシンボル番号。</returns>
    public int GetRandomLosingSymbolA()
    {
        // はずれシンボルの番号を返すロジック
        int[] losingSymbols = { 1, 2, 3, 4, 5 }; // はずれシンボルの配列
        int randomIndex = Random.Range(0, losingSymbols.Length);
        return losingSymbols[randomIndex];
    }

    /// <summary>
    /// ランダムな負けシンボル番号を返す。
    /// VerB
    /// </summary>
    /// <returns>ランダムな負けシンボル番号。</returns>
    public int GetRandomLosingSymbolB()
    {
        // はずれシンボルの番号を返すロジック
        int[] losingSymbols = { 6, 7, 8, 9 }; // はずれシンボルの配列
        int randomIndex = Random.Range(0, losingSymbols.Length);
        return losingSymbols[randomIndex];
    }

}

リールが下の方まで来たタイミングで元の位置に戻すことで、リールがループしているような表現を再現しています。絵柄が初期の状態と同じタイミングで戻すのがポイントです。

指定したシンボルで止める方法として、ここでは閾値調整によって実現していますが、当たり判定を用いた方法などでも良いかもしれません。

指定した値で止める作りとしているため、最終的に止まった値を取得するといった機能は実装していません。

スロットの表示

SlotDisplay.csを使って、メインカメラとサブカメラを使い分け、スロットの表示位置やサイズを調整します。

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

/// <summary>
/// メインカメラとサブカメラを使ってスロットの表示を設定するクラス
/// </summary>
public class SlotDisplay : MonoBehaviour
{
    public Camera mainCamera;
    public Camera subCamera;
    public Camera miniSlotCamera;

    void Start()
    {
        // サブカメラの初期設定
        subCamera.clearFlags = CameraClearFlags.Depth;
        subCamera.cullingMask = LayerMask.GetMask("SlotLayer");

        // メインカメラの設定からSlotLayerを除外
        mainCamera.cullingMask &= ~LayerMask.GetMask("SlotLayer");

    }


    /// <summary>
    /// スロットの表示位置とサイズを動的に変更する関数
    /// </summary>
    /// <param name="viewportRect"></param>
    public void SetSlotDisplay(Rect viewportRect)
    {
        subCamera.rect = viewportRect;
    }

    //ミニスロットの表示のenableを設定
    public void SetMiniSlotDisplay(bool isEnable)
    {
        miniSlotCamera.enabled = isEnable;
    }

}

このメソッドを使用することで、スロットの表示を動的に変更でき、ゲーム画面のUIや他の要素とバランスを取ることができます。

メインカメラにはスロットを表示できないように設定し、サブカメラ、ミニスロットカメラにスロットのみを表示するように設定し、それらを組み合わせることで各オブジェクトをカメラの表示・非表示を切り替えるだけで操作することができるようにしています。

スロット関連Sprite等のLayerを”SlotLayer”に設定し、CameraのCulling Maskから設定を切り替えています。

また、各カメラからのサイズや位置は、Cameraの SizeやViewport Rectから調整することができます。

スロットマシンの操作

スロットマシンの操作はMainManager.csで行います。

ここでは、リールの回転開始、停止、そしてスロットの結果を確認するなどの操作を行います。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using GameConst;
using System.Linq;
using static UnityEngine.EventSystems.EventTrigger;

public class MainManager : MonoBehaviour
{

    #region public 変数

    /// <summary>
    /// メインマネージャーのインスタンス
    /// </summary>
    public static MainManager instance;


    /// <summary>
    /// リールコントローラー
    /// </summary>
    public GameObject reelController;

    #endregion

    #region private 変数

    private GameObject leftReel;
    private GameObject centerReel;
    private GameObject rightReel;

    private ReelController reelC;

    private SlotDisplay slotDisplay;



    #endregion

    #region unityのイベント処理

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void Start()
    {

        //各コンポーネントの取得
        reelC = reelController.GetComponent<ReelController>();
        slotDisplay = GetComponent<SlotDisplay>();

    }

    private void FixedUpdate()
    {
        reelC.ReelUpdate();
    }

    #endregion


    #region デバッグ処理

    public void debug1()
    {
        //リールを回す
        reelC.StartReel();
    }

    public void debug2()
    {
        //リールを止める

        //左のリール
        StartCoroutine(reelC.StopReel(1,1));

        //中央のリール
        StartCoroutine(reelC.StopReel(2, 2));

        //右側のリール
        StartCoroutine(reelC.StopReel(3, 3));

    }

    public void debug3()
    {
        //リールが止めっているか確認する
        bool isAllStop = reelC.CheackAllStop();

        if (isAllStop) {
            //リールが全て止まっている場合
            //スロットの結果を表示する
            Debug.Log("すべて止まっている");
        }
        else
        {
            Debug.Log("すべて止まっていない");
        }

    }

    #endregion
}

デバッグ用のボタンを設置して動作確認ができるようにdebug機能を回転、止める、回転しているかどうかの判定機能3つを用意しています。

数値オブジェクトにシンボルの設定

Reel Symbol.cs数値を設定するSprite画像にその画像がどの数値を意味しているかを設定します。

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

public class ReelSymbol : MonoBehaviour
{
    public int symbolNum;
}

コラム:生成AIを利用した数字の画像素材の生成

素材の画像素材は、ChatGPTのDallE-3で作成したものと公式の画像を組み合わせて利用しています。

現在は画像も制限があるものの無料プランでも作れるようなので作成した際のプロンプトを紹介しておきます。

※生成AIは同じプロンプトでも同じものができるわけではないのでご参考程度に。

A single number 1 designed for a slot game. The number is cute yet intense, with a three-dimensional appearance. It is decorated with cherry blossom motifs, making it stand out prominently.
スロットゲーム用にデザインされたシングルナンバー1。かわいらしくも強烈な、立体感のある数字です。
桜のモチーフがあしらわれ、ひときわ目立つ。
A single number 7 designed for a slot game. The number is cute yet intense, with a three-dimensional appearance and a luxurious design. It is decorated with cherry blossom motifs and additional intricate details to make it stand out prominently. The background is a solid color.
スロットゲーム用にデザインされたシングルナンバー7。立体的で高級感のあるデザイン。桜をモチーフにした装飾や細かなディテールが施され、ひときわ目を引きます。背景は無地です。

また、これらの背景を透過する方法については、調べると色々出てくる思いますのでここでは割愛します。

まとめ

今回の解説では、Unityを使ってスロットマシンを実装する基本的な手順を紹介しました。

スロットマシンのリール生成や回転、停止に関するロジックを学ぶことで、ゲーム開発に必要なスキルを身につけることができます。

さらに、記事内のコラムでは、ChatGPTのDALL-E3を活用して数字の画像素材を生成する方法についても紹介しました。生成AIは、無料プランでも利用できるため、手軽にオリジナルの素材を作成することが可能です。プロンプトの工夫次第で、多様な画像を生成できる点も魅力の一つです。

ただし、同じプロンプトでも毎回同じ結果が得られるわけではないため、生成された画像は参考程度に活用してください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次