Help us understand the problem. What is going on with this article?

Unityでパチスロ風アプリを作ってみる

More than 1 year has passed since last update.

はじめに

この記事はリンク情報システムの2018年アドベントカレンダーのリレー記事です。
engineer.hanzomon のグループメンバによってリレーされます。
(リンク情報システムのFacebookはこちらから)

クリスマスも差し迫ってきた年末、いかがお過ごしでしょうか。
22日目の記事は、@hunyuが担当いたします。よろしくお願いします。

なぜこのお題にしたのか

ワシぁ…勝ちたいんや…。
自分で勝てるスペック考えて作れば勝てる。なんて簡単なんだぁ…(気づき)

もうちょい真面目な理由

以下の理由です。

  • もともと、Unityに興味があったため。
  • スロットは(私が悲しみを抱えた分だけ)詳しいので、それっぽいものであれば作りやすいと思ったため。

Unityについては、インストールして1年くらい放置していたし、いい勉強の機会になるかなと思い、挑戦してみた次第です。

開発環境

Unity 2018.2.17f1
放置していたものは古いバージョンだったので、適当な最新バージョンにしました。

Unityについて軽くお勉強

開発するにあたり、諸々の使い方を独学にて勉強。
なるほど、よくわからん。

機能が豊富すぎて逆によくわからないのと、ゲーム業界の用語?が多い印象。

まあ、作っていけばなんとかなるか…。ということで、開発開始しました。

スロットの機能や動作を整理

一般的なスロットに備わっている機能や動作を、ざっくりと洗い出すと以下のようなものがあります。
対応の難易度は、想像で勝手に降ってみました。
ついでにどこまで対応可能かなというのも考えてみました。(青字のものが今回の対応対象)


  • 難易度:低
    • 乱数の抽選処理
    • 乱数をもとにした小役判定処理
    • べットおよび小役をもとにしたメダル枚数増減処理
  • 難易度:中
    • リール回転処理
    • リール停止処理(1リール)
    • ウェイト処理
    • 小役カウンター実装
    • 状態保存機能(セーブ機能)の実装
    • 数段階の設定による小役判定処理の変更処理
    • 特定小役によるボーナス抽選処理の実装
    • ボーナスゲームの実装
    • 高確率の実装
  • 難易度:高
    • RT、ARTの実装
    • リール停止処理(3リール)
    • リーチ目の実装
    • 3リール時の押し順毎のリール停止目制御処理
    • ビタ押し処理
    • モードの実装
  • 難易度:EX
    • 癖になる音楽・SEの作成
    • カッコ可愛い画像・キャラの作成
    • 斬新で面白いゲームシステムの考案

…全てを実装するには、道のりは険しく、そして長く…。
(つーか音楽とか画像とかはもはや個人では厳しいのでは…)

急ぎでぱぱっとまとめたので、この程度しかないですが他にも(細かい話も含めて)沢山あると思います。

今回は期間が短いので、難しいものは無理と判断し、比較的簡単な部分だけ作ることにしました。

今回作るスロットの仕様

こんな感じで作ります。

  • リール数は「1」とします。ライン数も「1」とします。
  • べットと始動(以降スピンボタン)は1ボタンに集約します。ボタン押下によってリールが動作します。
  • スピンボタン押下によって、0~999の1000個の乱数から1個を抽選します。
  • 抽選結果の乱数から、小役を判定します。判定条件はLotteryCreator.csのGetKoyakuメソッドのコメント参照のこと。
  • ストップボタン押下によって、リールを停止します。
  • 1ゲームあたり、メダルを3枚消費するものとします。(1リール1ラインなのにね)
  • 小役成立時の配当は、「スロット用の画像作成」を参照のこと。

スロット用の画像作成

スロットといえばやはりスロットの「図柄」ですね。
とりあえず、こいつがないと始まらない…ということで自前で作成しました。
今回は、以下の8種類用意しました。

ブランク ブランク。はずれ。きらい。たまに好き。
リプレイ リプレイ。よく擬似ボーナスの的になる小役。払い出し3枚役扱い。
チェリー チェリー。2つ股や3つ股のものが多い。4枚役。
ベル ベル。たまにコインとか別のものだったりする。10枚役。
スイカ スイカ。実機なら種まで見えます(謎の自慢)。15枚役。
バー バー(REG)。天井BAR単発を思い出せ。150枚役扱い。
赤7 赤7(BIG)。なぜか青や他の色に負けている事が多い。350枚役扱い。
青7 青7(BIG)。君は強く、そして美しい。711枚役扱い。

これらの画像を、縦並びにしたリールが↓。
青7

で、このリールに↓の画像をかぶせて…
青7

真ん中の穴からぐるぐる回るリールをチラ見できるようにして、してスロット風に動かそう!…という魂胆です。

処理の実装

では、早速実装。
作成するスクリプトは以下の3つです。

  • ReelController
    • リール制御用のスクリプト。リールの画像にアタッチする。
  • LotteryCreator
    • 抽選結果生成用のスクリプト。スロットにおける心臓。
  • CanvasController
    • 画面に表示するメダル枚数を管理するスクリプト。テキストエリアにアタッチする。

中身はこんな感じ。

ReelController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Assets.LotteryCreator;

// リール周りの動きをまとめたクラス
public class ReelController : MonoBehaviour
{
    // 共通変数
    private bool SpinButtonFlg;     // spinボタン用フラグ
    private bool StopButtonFlg;     // stopボタン用フラグ

    private float TargetPointA;     // 停止位置A
    private float TargetPointB;     // 停止位置B ※A以上B未満の位置でリール停止

    LotteryCreator lottery = new LotteryCreator();


    // スプライト生成時に動作する処理
    void Start()
    {
        SpinButtonFlg = false;
        StopButtonFlg = false;
        TargetPointA = 0f;
        TargetPointB = 0f;
    }

    // スプライト生成後、常に監視して動作し続ける処理
    void Update()
    {
        // stopボタンが押されたら(先にチェック)
        if (StopButtonFlg &&
            (transform.position.y <= TargetPointA && transform.position.y > TargetPointB)
           )
        {
            SpinButtonFlg = false;
            StopButtonFlg = false;

            // win枚数反映処理
            GameObject.Find("Canvas").GetComponent<CanvasController>().WinMedal();
        }

        // spinボタンが押されたら
        if (SpinButtonFlg)
        {
            // リール回転処理
            transform.Translate(0, -0.6425f, 0);

            // リール位置が一定位置を超えたら先頭に戻る
            if (transform.position.y < -10.25f)
            {
                transform.position = new Vector3(0, 10.2f, 0);
            }
        }

    }

    // 画面上に配置しているボタンを押したときの処理
    void OnGUI()
    {
        // spinボタン
        if (GUI.Button(new Rect(10, 320, 100, 30), "bet and spin"))
        {

            // bet枚数反映処理
            GameObject.Find("Canvas").GetComponent<CanvasController>().InsertMedal();

            // リール停止位置AとBを取得
            TargetPointA = lottery.LotteryKoyakuPoint();
            TargetPointB = TargetPointA - 0.6425f;

            SpinButtonFlg = true;

        }

        // stopボタン
        if (GUI.Button(new Rect(385, 320, 100, 30), "stop"))
        {
            StopButtonFlg = true;
        }
    }

}
LotteryCreator.cs
using UnityEngine;

namespace Assets.LotteryCreator
{
    // 抽選関連の処理をまとめたクラス
    public class LotteryCreator
    {

        // 共通変数
        private int MaxLottery;     // spinボタン用フラグ

        // 小役位置返却メソッド
        public float LotteryKoyakuPoint()
        {
            // 抽選
            var lottery = Lottery();

            // 抽選結果から小役を選択
            int koyaku = GetKoyaku(lottery);

            // 小役からwin枚数判別処理
            GameObject.Find("Canvas").GetComponent<CanvasController>().GetMedal(koyaku);

            // 抽選結果をもとに小役判定、位置をretrun
            return KoyakuPoint(koyaku);
        }

        // 抽選処理(ここの偏り一つで世界が変わる)
        private int Lottery()
        {
            var random = new System.Random();

            MaxLottery = 1000;

            // 0~999まで(MaxLottery個)の結果をreturn
            return random.Next(MaxLottery);

        }

        // 抽選結果から小役振り分け(ここ次第で勝ち負けが大きく変わる)
        // 【memo】
        // それぞれの小役に対応する番号と対応する抽選結果の範囲は以下の通り。
        // 0:BR 0~769 (約1.3分の1)
        // 1:リプ 770~894(8分の1)
        // 2:チェ 945~984(20分の1)
        // 3:ベル 945~984(25分の1)
        // 4:スイカ   985~992(125分の1)
        // 5:バー 993~996(250分の1)
        // 6:赤7 997~998(500分の1)
        // 7:青7 999     (1000分の1)
        private int GetKoyaku(int lottery)
        {
            if (lottery >= 0 && lottery <= 769) return 0;
            else if (lottery >= 770 && lottery <= 894) return 1;
            else if (lottery >= 895 && lottery <= 944) return 2;
            else if (lottery >= 945 && lottery <= 984) return 3;
            else if (lottery >= 985 && lottery <= 992) return 4;
            else if (lottery >= 993 && lottery <= 996) return 5;
            else if (lottery >= 997 && lottery <= 998) return 6;
            else if (lottery <= 999) return 7;
            else return 0;
        }

        // 抽選結果をもとに小役判別して小役位置を返すメソッド
        //
        // 【memo】
        // それぞれの小役に対応する番号と位置は以下の通り。
        // 0:BR -9.50f(本来は-10.20f、リール周期の都合上、-9.50f)
        // 1:リプ 2.57
        // 2:チェ 7.71
        // 3:ベル 0.00
        // 4:スイカ  -2.57
        // 5:バー 5.14
        // 6:赤7 -7.71
        // 7:青7 -5.14
        private float KoyakuPoint(int koyaku)
        {

            // リール位置返却
            switch (koyaku)
            {
                case 0: return -9.50f;
                case 1: return 2.57f;
                case 2: return 7.71f;
                case 3: return 0.00f;
                case 4: return -2.57f;
                case 5: return 5.14f;
                case 6: return -7.71f;
                case 7: return -5.14f;
                default: return -9.50f;
            }
        }

    }
}
ReelController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

// メダル枚数表示に関する処理をまとめたクラス
public class CanvasController : MonoBehaviour {

    int medalSwap = 0;      // Medal枚数退避エリア(一旦こっちにいれる)
    int medal = 0;          // Medal枚数退避エリア(表示するのはこっち)
    GameObject medalText;

    // 初期化
    void Start () {
        this.medalText = GameObject.Find("Medal");
    }

    // 常時動作
    void Update () {
        medalText.GetComponent<Text>().text = "MEDAL: " + medal.ToString("D1");
    }

    // bet枚数反映処理
    public void InsertMedal()
    {
        this.medalSwap -= 3;
        this.medal = this.medalSwap;
    }

    // 抽選結果をもとにwin枚数を返すメソッド
    //
    // 【memo】
    // それぞれの小役に対応する番号とwin枚数は以下の通り。
    // 0:BR 0
    // 1:リプ 3
    // 2:チェ 4
    // 3:ベル 10
    // 4:スイカ  15
    // 5:バー 150
    // 6:赤7 350
    // 7:青7 711
    public void GetMedal(int koyaku)
    {
        switch (koyaku)
        {
            case 0:  this.medalSwap +=   0; break;
            case 1:  this.medalSwap +=   3; break;
            case 2:  this.medalSwap +=   4; break; 
            case 3:  this.medalSwap +=  10; break; 
            case 4:  this.medalSwap +=  15; break; 
            case 5:  this.medalSwap += 150; break; 
            case 6:  this.medalSwap += 350; break; 
            case 7:  this.medalSwap += 711; break; 
            default: this.medalSwap +=   0; break; 
        }
    }

    // medalSwapに入れたwin枚数含む現時点でのMedal枚数をmedalに反映する処理
    public void WinMedal()
    {
        this.medal = this.medalSwap;
    }
}

遊んでみる

できたらあとは“遊戯”っちまうだけ…!

実行!

slot.gif

おお!うごいた!リールが滑って小役をバシッと止めています!
ちなみに、gifだとわかりにくいけど、結構ヌルヌル動いています!いい感じ!

禁断の遊び

では…始めようか…

LotteryCreator.cs
        // 抽選結果から小役振り分け(ここ次第で勝ち負けが大きく変わる)
        // 【memo】
        // それぞれの小役に対応する番号と対応する抽選結果の範囲は以下の通り。
        // 0:BR 0~769 (約1.3分の1)
        // 1:リプ 770~894(8分の1)
        // 2:チェ 945~984(20分の1)
        // 3:ベル 945~984(25分の1)
        // 4:スイカ   985~992(125分の1)
        // 5:バー 993~996(250分の1)
        // 6:赤7 997~998(500分の1)
        // 7:青7 999     (1000分の1)
        private int GetKoyaku(int lottery)
        {
//            if (lottery >= 0 && lottery <= 769) return 0;
//            else if (lottery >= 770 && lottery <= 894) return 1;
//            else if (lottery >= 895 && lottery <= 944) return 2;
//            else if (lottery >= 945 && lottery <= 984) return 3;
//            else if (lottery >= 985 && lottery <= 992) return 4;
//            else if (lottery >= 993 && lottery <= 996) return 5;
//            else if (lottery >= 997 && lottery <= 998) return 6;
//            else if (lottery <= 999) return 7;
            if      (lottery >=   0 && lottery <= 249) return 1;
            else if (lottery >= 250 && lottery <= 499) return 5;
            else if (lottery >= 500 && lottery <= 749) return 6;
            else if (lottery >= 750 && lottery <= 999) return 7;
            else return 0;
        }

実行!!!!!!!

manmai.gif
クッソチョロすぎwww
40秒で万枚wwwwww
時給90万枚wwwwwwwwww

虚しい

まとめ

スロットに関して

作った本人が言うのもアレですが、このスロット、面白くないです。
理由は明確。

  • 音がない
  • 絵が汚い
  • 目押し要素がない

やっぱこの3つは大切ですね。
今度ちゃんと作る機会があったらやっぱこれらはキチンと作らないといけないな、と感じました。

Unityに関して

Unityからは、無限の可能性を感じました。
…単純に私が知らない機能がもっといっぱいある!って話なのかもしれませんが。
作り手の知識や、シナリオ次第で本当にどんなものでもできてしまう底の深さを感じました。

何より、とりあえずやってみようで本当に出来ちゃうあたり、包容力も抜群ですね。
私が適当に考えた仕様でスロットができたくらいですし。すごいツール。

もっと勉強したくなりました。

さいごに

思ったよりさっくり終わった感はありますが、気づけば深夜までやっていたりしていました。
やっぱ開発が好きなんだな、と改めて実感いたしました。
誘ってくれた@dgkzさん、ありがとうございました。

明日の担当は、@i-ohbaさんです!お楽しみに!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした