こんにちは、今日はスロットマシーンを作ってみたいと思います。
私はあまりスロットのゲームになじみがないのですが、一人で遊べるウェブゲームを作りたいと思いスロットに至りました。
ダンボール工作で作るのも一手ですが、私はUNITYに興味があるので、プログラミングで作成したいと思います。
まずは、リールを回転して止めるというところから始め、最終的には台の設定等もできるように仕上げたいと思っています。
絵柄は取り急ぎ文字にしておいて、最終的には絵柄の画像をちゃんと作りたいと思っています。
自分としては市販のメーカーのスロットマシーンに負けない出来栄えのスロットマシーンを作りたいと思います。
それでは、早速スロットを作っていきましょう!
後編はこちらから↓
遊べる完成版はこちらから↓
目次
スロットを回してボタンで止める
スロットの作り方の要点としては、短冊状に並べた絵柄を上から下へ流して、回っているリールを再現するという方向で作りたいと思います。
まず最初は一番単純な作り方を考えます。
リールの目を幾つか並べた縦長のファイルを最初から用意して、それを上から下へ動かします。
スロットマシーン筐体とリールの準備
次に、こんな感じに短冊状の絵柄が流れるようにしたいと思います。
スロットマシーンの筐体にリールが被らないように、sortinglayerで筐体が手前に表示されるようにしておきます。
筐体と枠+シェーディングをwakuレイヤーに入れ、一番手前に表示させるようにし、二番目にリールをreelレイヤーにいれ、最後に、一番奥の白い面をdefaultに設定します。
筐体のリール部分は半透明のpngファイルにしておく必要がありますね。
それによって1レイヤー下のリールの目が見えるようにしてあります。
それから、ボタンとレバーは後々動かしたいので別のファイルにしておきます。
スロットマシーンのリールの流し方と止め方
一番単純な考え方でスクリプトを組んでいきたいと思います。
ゲームが始まるとスロットが回り始め、ボタンを押すと任意の場所に止まるというのを実装します。
ピッタリと目の所で止まるというのは後々考えていきたいと思うので、取り急ぎ、止まればいいことにします。
まずはReelControllerというEmptyObjectを作成し、ReelControllerというC#スクリプトをアタッチしておきます。
ReelController では、リールをPublic オブジェクトとして取得し、それに対して速度を下向きに与えてあげます。
一番下までリールが回ったら初期位置にリールを戻し、もう一度流します。
これによってリールが延々と流れる仕組みになっています。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Reelcontroller : MonoBehaviour { public GameObject Reel; //リールを取得 public GameObject Reel2; public GameObject Reel3; Vector3 initialpos; //初期位置 Vector3 initialpos2; Vector3 initialpos3; float speed1; //リールの回転速度 float speed2; float speed3; private void Awake() //ゲーム開始時に { initialpos = this.Reel.transform.position; //初期位置を取得しておく initialpos2 = this.Reel2.transform.position; initialpos3 = this.Reel3.transform.position; speed1 = -0.35f; //リールの回転スピードの代入 speed2 = -0.35f; speed3 = -0.35f; } private void Update() //ゲームが始まったら { 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 < -3.78f) //リールが一番下に来たら { this.Reel.transform.position = initialpos; //初期位置に戻す } if (Reel2.transform.position.y < -3.78f) { this.Reel2.transform.position = initialpos2; } if (Reel3.transform.position.y < -3.78f) { this.Reel3.transform.position = initialpos3; } } public void stopReel() //この関数がボタン1を押すと呼ばれる { speed1 = 0; //リールの回転スピードを0にする } public void stopReel2() { speed2 = 0; } public void stopReel3() { speed3 = 0; } }
それからボタンを押すと速度を0にしてあげる事も出来るようにしてあげたいと思います。
これは別にスクリプトを書いてあげる必要がありますね。
まずはマウスでボタンをクリックした場合にクリックを検知し、リールの回転スピードを0にする関数を呼んであげる事にします。
まずはEventSystemを使った、マウスクリックの検知の仕方を考えます。
EventSystemを使ったスロットマシーンのボタン検知方法
マウスクリックを検知するためにEventSystemを利用します。
まずは、ヒエラルキービューでイベントシステムを追加しましょう。
右クリック>UI>EventSystemで追加できます。
次に、メインカメラにPhysics2DRaycasterをコンポーネントとして追加します。
そして、マウスクリックしたいボタンを選択し、そのゲームオブジェクトにRigidbody2Dと、BoxCollider2Dをコンポーネントに追加します。
この時、Rigidbody2DのGravityScaleは0にしてボタンが落っこちないようにします。
それから、ボタンにマウスクリックが発生した時に呼ばれるC#スクリプトを付けておきます。
C#スクリプトには、public void OnClick()というクリックを検知する関数を書いておきます。
その関数で、reelcontroller下のstopReel()を実行するようにします。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Button3 : MonoBehaviour { public Reelcontroller reelcontroller;//reelcontrollerの使用 // Start is called before the first frame update void Start() { reelcontroller = GameObject.Find("ReelController").GetComponent<Reelcontroller>();//reelcontrollerの取得 } public void OnClick() { reelcontroller.stopReel3();//リールを止める関数を実行 } }
最後に、ボタンのゲームオブジェクトにEventTriggerコンポーネントを追加してあげます。
そして、AddNewEventTypeより、PointerClick を選択します。
+ボタンをクリックして、RuntimeOnlyと書いてある下に、押したいボタンをSceneObjectより選択します。
NoFunction と書いてあるボタンをおして、ボタンに付けたC#スクリプトの名前を選択し、その中のメソッド一覧より、OnClick()を選びます。
これでクリックする設定は全部終わりです。
これを各ボタン3つに全部設定します。
きちんと設定できると、以下のようにプレイできるようになります。
絵柄は取り急ぎです。赤7を表現するために絵柄の中の$$$の色を一つだけ赤にしています。
スロットリールの自動生成・中心でとめる・リプレイの実装
とりあえず、遊べるものができたのですが、問題が4つあります。
- リールの柄の出やすさの調整ができない
- リールの柄がピッタリ真ん中にとまらない
- リプレイのためにレバーを押すのでなく再起動が必要
- 子役判定ができない
この問題を全て解決する方法を考えたいと思いますが、役判定については長くなりそうなので、最初の3点のみ取り急ぎは解決したいと思います。
リールの絵柄の出やすさの調整を行う
まずは、先人の知恵を借りるべく、今回は「スロット 作り方」でググってみると幾つかサイトがありますね!
私が参考にした人気の関連ブログは下記の二つです。
Unityゲーム制作日記:簡単なスロットゲームの作り方(2D) 後編]
上の記事をよく読むとリールの生成方法や止め方に必要な方法が詳しく書いてあります。
早速読み込んで部分的に応用していきたいと思います。
この記事を読むと配列を使用してリールを生成していますね。
まずEmptyObjectを作成し、ReelGeneratorと名前を付けます。
そして、下記のように書いたC#スクリプトをアタッチします。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ReelGenerator1 : MonoBehaviour { public GameObject[] imgobj; //絵柄のプレハブを格納(計7種) GameObject[] tmp_obj = new GameObject[90];//リールの配列(プレハブの種類が入る) Transform[] img_pos = new Transform[90];//リールの柄の位置 Transform pos; //リールのTransform int div0=7;//赤$$$が出るしきい値 int div1=15;//$$$が出るしきい値 int div2=20;//BARが出るしきい値 int div3=40;//bが出るしきい値 int div4=55;//0が出るしきい値 int div5=80;//wが出るしきい値 //%が出るしきい値 void Awake()//ゲーム開始時に { GenerateReel();//リール生成関数を呼び出す } public void GenerateReel()//リール生成関数本体 { pos = GetComponent<Transform>();//リールのTransformはオブジェクト位置とする for (int i = 0; i < 90; i++) { Vector3 pos = new Vector3(0.0f, -1.5f + (1.5f * i), 0.0f);//プレハブ位置の決定 int tmp; int rand = Random.Range(0, 91);//0~91の数字をランダムで生成 if (0 < rand && rand < div0 //もし数字が0~赤$$$のしきい値以内だったら ) { tmp = 0;//赤$$$の絵柄idをtempに代入 } else if (div0 < rand && rand < div1 // もし数字が赤$$$のしきい値~$$$のしきい値以内だったら ) { tmp = 1;//$$$の絵柄idをtempに代入 } else if (div1 < rand && rand < div2 //以下他の絵柄について同様に ) { tmp = 2; } else if (div2 < rand && rand < div3 ) { tmp = 3; } else if (div3 < rand && rand < div4 ) { tmp = 4; } else if (div4 < rand && rand < div5 ) { tmp = 5; } else { tmp = 6; } tmp_obj[i] = (GameObject)Instantiate(imgobj[tmp]); //プレハブからtempの絵柄idのGameObjectを生成 tmp_obj[i].transform.SetParent(transform, false); //リールのオブジェクトを親にする img_pos[i] = tmp_obj[i].GetComponent<Transform>();//プレハブのtransformを取得 img_pos[i].localPosition = pos;//プレハブ位置の代入 } } public void DestroyReel() { //リールをデストロイしてリセットする関数 GameObject[] reels = GameObject.FindGameObjectsWithTag("reel"); //reelタグが付いているオブジェクトをすべて取得 foreach (GameObject i in reels)//各reelsのオブジェクトに対して以下を行う { Destroy(i);//reelsの各オブジェクトを破壊する } } }
まずは、3種類の別々の配列を準備しています。
冒頭の下記の部分になります。
public GameObject[] imgobj; //絵柄のプレハブを格納(計7種) GameObject[] tmp_obj = new GameObject[90];//リールの配列(プレハブの種類が入る) Transform[] img_pos = new Transform[90];//リールの柄の位置
imgobjは絵柄のプレハブを格納するための配列です。
これはpublicにしてあるので、インスペクタービューに絵柄のプレファブをドラッグ&ドロップしてあげます。
スプライトのままだと入らないので、予め一度インスタンス化してプレファブにする必要があります。
元のスプライトを一度シーンビューにドラッグ&ドロップして、それからヒエラルキービューからプロジェクトウィンドウにもう一度ドラッグ&ドロップでプレファブ化できます。
これによって抽選する元データの配列ができることになります。
tmp_objはリールの配列そのもので、各要素にはImgobjの絵柄id(0~6)が抽選の結果入ります。
今回のスクリプトでは90個のリールの配列を作成しています。
長いですよね。目押しをある程度難しくするためにリールを長めに作っています。
img_posはリール絵柄のTransform(位置情報)を格納しています。
これによって親オブジェクトであるReelGeneratorの中に順序良く配列を並べる事ができます。
さて、お題の抽選の方法ですが、まずはランダムに0~90の整数をrandとして取得します。
Vector3 pos = new Vector3(0.0f, -1.5f + (1.5f * i), 0.0f);//プレハブ位置の決定 int tmp; int rand = Random.Range(0, 91);//0~91の数字をランダムで生成
そしてrandがあるしきい値としきい値の間にある場合、絵柄のid をtmpに返すようにします。
例えば、上の記述の通りであれば、randの値が0~5であれば、赤$$$の絵柄が抽選され、idがtmpに返されます。
要するに5/90の確率で赤$$$絵柄が抽選されるということになります。
このしきい値をいじることで抽選の渋さをいじることができるようなシステムになっています。
これで絵柄の出やすさの調整ができるようになりました。
ちなみに、プレハブ位置は1.5f刻みにy方向に並べるようになっています。
int div0=5;//赤$$$が出るしきい値 if (0 < rand && rand < div0 //もし数字が0~赤$$$のしきい値以内だったら ) { tmp = 0;//赤$$$の絵柄idをtempに代入 }
それから、このReelGeneratorには、リールをすべて破壊する関数DestroyReel()が仕込んであります。
これは、リールを再生成する際に古いリールを消してあげないと、重なってしまうからです。
この関数は、後にレバーを押したら呼ばれることにします。
リールの絵柄をピッタリ中心で止める
上の図のようにボタンを押した時にピッタリ真ん中に絵柄が来るようにリールを止めるためには工夫が必要です。
このリールは1.5f刻みで絵柄が配置されているので、中心に絵柄が止まるのは、リールのポジションを1.5fで割った余りの絶対値が一定の値を取る場合になるはずです。
リールが止まる条件として、ボタンが押されている事と、中心に絵柄が来ている事の両方が満たされている場合に絞って考えれば良さそうです。
Reelcontrollerを下記のように書き換えてあげる必要があります。
変更点について説明します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Reelcontroller : MonoBehaviour { public GameObject Reel; //リールを取得 public GameObject Reel2; public GameObject Reel3; Vector3 initialpos; //初期位置 Vector3 initialpos2; Vector3 initialpos3; float speed1; //リールの回転速度 float speed2; float speed3; bool stopflag1 = false;//ボタンが押されたかどうか bool stopflag2 = false; bool stopflag3 = false; ReelGenerator1 reelGenerator1;//ReelGenerator1の宣言 ReelGenerator1 reelGenerator2; ReelGenerator1 reelGenerator3; private void Awake() //ゲーム開始時に { initialpos = this.Reel.transform.position; //初期位置を取得しておく initialpos2 = this.Reel2.transform.position; initialpos3 = this.Reel3.transform.position; reelGenerator1 = GameObject.Find("ReelGenerator").GetComponent<ReelGenerator1>();//ReelGenerator1の取得 reelGenerator2 = GameObject.Find("ReelGenerator (1)").GetComponent<ReelGenerator1>(); reelGenerator3 = GameObject.Find("ReelGenerator (2)").GetComponent<ReelGenerator1>(); } public void StartReel()//リールを再生成して回し始める関数 { reelGenerator1.GenerateReel();//ReelGenerator1のGenerateReel関数を呼ぶ reelGenerator2.GenerateReel(); reelGenerator3.GenerateReel(); speed1 = -0.35f; //リールの回転スピードの代入 speed2 = -0.35f; speed3 = -0.35f; stopflag1 = false; //ボタンを押していない状態にリセット stopflag2 = false; stopflag3 = false; } private void Update() //ゲームが始まったら { 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 < -130) //リールが一番下に来たら { this.Reel.transform.position = initialpos; //初期位置に戻す } if (Reel2.transform.position.y < -130f) { this.Reel2.transform.position = initialpos2; } if (Reel3.transform.position.y < -130f) { this.Reel3.transform.position = initialpos3; } if (stopflag1 && 0.85 <= Mathf.Abs(Reel.transform.position.y % 1.5f) && Mathf.Abs(Reel.transform.position.y % 1.5f) < 0.88) //ボタンが押されていて、かつ、リールを1.5fで割った余りが規定値以内であったら { speed1 = 0;//リールの回転スピードを0にする } if (stopflag2 && 0.85 <= Mathf.Abs(Reel2.transform.position.y % 1.5f) && Mathf.Abs(Reel2.transform.position.y % 1.5f) < 0.88) { speed2 = 0; } if (stopflag3 && 0.85 <= Mathf.Abs(Reel3.transform.position.y % 1.5f) && Mathf.Abs(Reel3.transform.position.y % 1.5f) < 0.88) { speed3 = 0; } } public void stopReel() //この関数がボタン1を押すと呼ばれる { if (stopflag1 != true)//ボタンがまだ押されてない場合 { speed1 = -0.03f;//リールをゆっくりにする } stopflag1 = true; //ボタンを押す } public void stopReel2() { if (stopflag2 != true) { speed2 = -0.03f; } stopflag2 = true; } public void stopReel3() { if (stopflag3 != true) { speed3 = -0.03f; } stopflag3 = true; } }
まず、ボタンが押されたかどうかのbool値を用意しておきます。
これによって、リールを止める条件の1つが決まります。
bool stopflag1 = false;//ボタンが押されたかどうか
それから、ボタンが押されていない状態で、ボタンを押した場合に、リールのスピードを遅くする処理を書いています。
「ボタンが押されていない状態」というのを付け加えている理由は、一度止まったリールが、もう一度ボタンを押すと回り始める事を防ぐためです。
二度押し防止用のフラグですね。
リールを遅くすることで、滑りっぽいギミックを仕込んでいるのと、回り方の精度を上げています。
回り方がゆっくりになることで、回転量に対してupdateが呼ばれるタイミングが多くなるので、より綺麗な位置で止めやすくなります。
public void stopReel() //この関数がボタン1を押すと呼ばれる { if (stopflag1 != true)//ボタンがまだ押されてない場合 { speed1 = -0.03f;//リールをゆっくりにする } stopflag1 = true; //ボタンを押す }
そして、下記に書かれている通り、ゆっくり回っているうちに、リールのポジションを1.5fで割った余りの絶対値が規定値以内になった場合、リールを止めます。
if (stopflag1 && 0.85 <= Mathf.Abs(Reel.transform.position.y % 1.5f) && Mathf.Abs(Reel.transform.position.y % 1.5f) < 0.88) //ボタンが押されていて、かつ、リールを1.5fで割った余りが規定値以内であったら { speed1 = 0;//リールの回転スピードを0にする }
これによって、下のようにきれいにリールが止まるようになりました。
レバーによるリプレイの実装
まず、レバーを押すとレバーが下に動き、ガチャっという音がするようにします。
それから、0.5f秒後にレバーが上に戻るギミックを入れます。
そして、reelgeneratorのリールを全消しするDestroyReel()関数を呼んでリールを一度全部消します。
そのあとに、リールを再生成するreelcontrollerのStartReel()を呼び出し、リールを再生成します。
まとめるとレバーにアタッチするC#スクリプトは、下記のようになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lever : MonoBehaviour { private AudioSource audioSource;//オーディオの宣言 public Reelcontroller reelcontroller;//ReelControllerの宣言 public ReelGenerator1 reelgenerator; //ReelGenerator1の宣言 void Start() { reelcontroller = GameObject.Find("ReelController").GetComponent<Reelcontroller>();//ReelControllerの取得 reelgenerator = GameObject.Find("ReelGenerator").GetComponent<ReelGenerator1>();//ReelGenerator1の取得 } public void OnClick() { audioSource = gameObject.GetComponent<AudioSource>();//オーディオの取得 audioSource.Play();//ガチャっというオーディオの取得 this.transform.Translate(0, -4.33f, 0);//レバーを下に落とす StartCoroutine(LeverCoroutine());//コルーチンを始める(レバーを0,5秒後に戻すため) reelgenerator.DestroyReel();//リールを全消しする関数を呼ぶ reelcontroller.StartReel();//リールを再生成して回す関数を呼ぶ } IEnumerator LeverCoroutine()//コルーチン本体 { yield return new WaitForSeconds(0.5f);//0.5秒待つ this.transform.Translate(0, 4.33f, 0);//レバーを元に戻す } }
レバーにもボタンと同様にEventTriggerの設定をしてあげる必要がありますね。
Rigidbody2DとBoxCollider2Dも忘れずにつけましょう。
リプレイの実装ができました。
スロットの作り方まとめ
今回の記事では、短冊状の絵柄を下に流し、リールの回転を表現して、それをスローダウンしてから揃えるやり方について紹介しました。
まだ子役の判定はできていませんが、結果的に取り急ぎ簡単にプレイできるものができました。
意外と短いスクリプトで、スロットマシーンが出来上がりましたね。
今後は役の判定、台の設定の遷移(ボーナスステージ等)、コインのカウント等を実装していきたいと思います!
完成まで待ち遠しいですね。
また、新たなバージョンができ次第ご紹介したいと思います。
それではまた!