😈

「豆ぶつけたら刀で斬る」制作ふり返りゆる日記

2021/02/28に公開

はじめまして。たいぷいと申します。
これが初投稿になります。

このたび、「豆ぶつけたら刀で斬る」というゲームを制作しました。

https://unityroom.com/games/ayameheaven

ゲームの内容は、
曲に合わせて飛んでくる豆をクリックで斬る、
○ズム天国のようなカジュアルなリズムゲームです。
1分ほどでプレイでき、PCからプレイすることを想定しています。

今回開発にあたって、Unityを使用しました。

このゲームに登場するキャラクターは、ホロライブ所属のVTuber、百鬼あやめさんです。
https://www.hololive.tv/portfolio/items/nakiri-ayame
https://twitter.com/nakiriayame
YouTubeのvideoIDが不正ですhttps://www.youtube.com/channel/UC7fk0CB07ly8oSl0aqKkqFg

このゲームを作る過程で、自分の中でいろいろな「新しい経験」をするきっかけになったので、
今回は、このゲームの制作過程のようなものを、ゆるくお話できればと思います。
ゲーム作ってみたいけどさっぱり…という方などのお役に立てれば幸いです。

最初からこの記事を書くことを想定していたわけではないため、かなりざっくりとした書き方になります。
作っていた時のことを思い出しながら、今書いています。

2/3

始まりはこの日でした。
きっかけは、青眼昴さんのこちらのツイートでした。

https://twitter.com/subaru_aome/status/1356587084052488199

見て最初に思ったのは「かわいいい!!」でした。

こちらのツイートにも元ネタがあり、

https://twitter.com/nakiriayame/status/1356392574697627648

こちらのツイートも見ていたので、
このツイートだけでこれを作るのはすごいな~とも思っていました。

しばらくはそんな感情だったのですが、ふと、
「これリ○ム天国みたいな感じにしたら面白いかも?」と思い、
しばらく妄想していました。

普段はこんなことはあまり思うことはなく、思っていても一過性で終わってしまうのですが、
今回はなんだかやってみたい感じ、いけそうな感じがしていました。

「ミニゲームだし、とりあえず作ってみるかあ」くらいのノリで、開発を始めました。

2/4

開発にあたって、まずはタスクの整理、クラス図の作成、譜面、譜面エディターについて考えることにしました。

私は過去にもいくつかゲームを作ろうとしていたことがあるのですが、計画性というものがあまりなく、いきなり手を動かし始めた結果、完成せずといったことがほとんどだったので、
今回はなるべく考えてから開発に入ろうと思いました。

ちなみに、プロジェクトのコードネームは「ayameheaven(あやめ天国)」です。
当初はゲームタイトルも「あやめ天国」にする予定でした。

タスク管理

まずはタスク管理。
タスク管理ツールはAsanaを使用しました。
仕事で使用しているので、プライベートでも使うとより理解が深まるかなと思い、Asanaにしました。

セクションを「1.0.0」(初回リリースという意味で)、「進行中」、「完了」に分け(後に「IceBox」(いつかやりたいこと)も追加)、現時点でパッと思いつく、完成までに必要なことを追加していきました。

クラス図

続いてクラス図。
クラス図とは、語弊を恐れずに言うと、
「プログラムを図にしてみる」といった感じです(正しい説明は各自調べていただければと)。
ぶっつけ本番でプログラムを書いていくのも悪くはないと思いますが、
後戻りが多くなったり、複雑になってくると(自分が書いたコードでも)だんだん理解が難しくなってくるので、先にクラス図を書いておくと、実装がスムーズだと思います。
クラス図を書くことで、プログラミングにおいてかなり難しい(と思う)変数/関数の命名等も事前に考えられるので、やっておいて損はないと思います。

私はVisual Studio Code + PlantUMLで書きました。

ただ、私はとにかくやる前に深く考えることが苦手なので、途中までしか書いていません…。

クラス図は最終的にこのような感じになりました(完成してからソースコードを元に図を書きました)。

(はじめてクラス図を書ききった)
Mainがかなり大きなクラスになってしまいましたね…。
今回は小規模なので許してください…。

譜面

譜面に関しては、今回はリズ○天国がモチーフなので、かっちりとした譜面は必要ないと思い、ぼんやりとこんな感じかな…と頭で考える程度でした。

譜面エディター

音ゲーに譜面エディターは不可欠だと思いますが、そもそもの音ゲー部分がゆるめなので、必要かな?と思いましたが、やはり耳で聞いてそのタイミングを直接書いていくのはさすがにつらいな…と思い、このゲームならこんなエディターがいいかな?というのは頭で考えていました。
(遊んでいて見えることはありませんが、実は譜面エディターも作成してます!後でその話もします)


今思うと、このやり方でリリースまで持っていけたので、「事前に(ある程度)設計をしてから制作した」という新しい経験になったと思います。

楽曲

譜面や譜面エディターを考えていると、「そういや何の曲にしよう?」とふと思いました。
リズム○国がモチーフなら、歌でなくともフリーの音楽でいいかなと思いましたが、せっかく百鬼あやめさんに関連するゲームにするなら、何かあやかりたいなと思っていたところ、そういえば「どーっちどっちの歌」があった!と思い、使用させていただくことにしました。

2/5

たぶん2/4から開発は始めていたと思います。ですが、この日から譜面エディターの開発に入っていたとのメモ書きがありました。

このゲームの一番重要なポイントは、豆を斬るところだよね!ということで、
文字通り豆を斬る部分の作成から始めようと思ったのですが、
まず譜面がどういう構成で、ノーツをどう定義するかというところが決まっていないと作りようがないので、まずは譜面を作るエディターからという流れになったんだと思います。

ちなみに、譜面エディターはこのような感じです。

譜面エディターには大きく分けて3つの要素があり、

  • タイムライン(画面上部)
  • オフセット(タイムラインの下)
  • シークバー(画面下部)

で構成されています。

シークバー

まず最初にシークバーを作成しました。
シークバーでは、曲および譜面の再生位置を任意の場所に指定できます。

ノーツが流れる系の音ゲーを作る上で必ず考えなければいけないことは、ノーツをどうやって流すか?音ズレしないためには?といった部分だと思います。
今回それに対するアプローチとして、

  1. ノーツを動かすのではなく、ノーツの親を動かす
  2. 動かす際は、曲の再生時間をもとに動かす

方法をとりました。
これにより、ノーツが流れる速度がだんだん曲のスピードと乖離するといった現象がなくなります。
この方法を取っていたので、シークバーによる再生位置の指定は容易に実装できました。

ただ、このやり方だと、

  • ノーツの動きがぎこちなくなる場合がある(曲の再生時間がそのままノーツの位置に反映されるので)
  • 速度が可変するノーツに対応できない

という問題がありますが、このゲームではこれらは許容することにしました。

今回、エディット画面に再生/停止ボタンは置きませんでした。
その代わり、スペースキーで再生/停止を行うようにしました。
画面がシンプルになり、操作もしやすいので今回はこのスタイルにしました。
また、再生時間の表示もあえて省きました。

加えて、再生の際は、停止した場所に関係なく前回再生した位置から再生されるようにしました。
これは、前回の音ゲーのプロジェクトで譜面エディターを作った際のフィードバックによるものです。
実際、ある部分の譜面をこだわりたい時は、同じ箇所を繰り返し再生したいと感じると思いますので、理にかなっているのかなと思います。

当初はエディターはおしゃれにしようかなと思っていましたが、ユーザーに見えない部分であり、このためにアセットを導入するのもなぁ、リソースも増えるし…ということで、妥協してUnity標準のままです。

譜面エディターは、専用のGameObjectを用意して、そのオブジェクトがアクティブなら、エディットモードで起動するようにしました。

タイムライン

続いてタイムラインの作成に入りました。
タイムラインに関しては、私の好きなゲームにosu!という音ゲーがありまして、
そちらの譜面エディット画面を参考にさせていただきました。

今回、ノーツ(豆)は秒数ではなく小節で指定するようにしました。
私は以前にも音ゲーを作ろうとしたことがあり、その時は秒数指定だったのですが、
BMS等のメジャーな音ゲーでは小節で指定しているらしく、小節にすることにより、曲のテンポが変わることがあってもより柔軟に対応できるとのことで、今回は小節を用いることにしました(これは新しい試み!)。

タイムラインには小節ごとに白い縦線が表示されるようにしてあります。

タイムライン上で左クリックするとノーツを作成し、
右クリックするとノーツが消せます。

ノーツ作成時は、基本的に決まったパターンで置きたかったので、0.5小節ごとにスナップされるようにしました。

ノーツのドラッグ&ドロップも実装しようか迷いましたが、同じ位置に置いて消してがすぐにできるので、作るのが面倒だったので作りませんでした。

また、右ドラッグで表示位置(再生位置)を調整できます。
シークバーでもできるのですが、触っていてここでも動かしたいなと思ったので追加しました。

譜面作成の時は、小節ごとにテンポ用のSEが流れるようにしていました(リリース時には削除しています)。

オフセット

そして、譜面作成の際の頭出しが気になったので、オフセットとして譜面全体をずらせるようにもしました。
-1~1のスライダー調整で、実際には±1小節分の位置調整ができるようにしました。

エディット時も豆は斬れるようにしていますが、その度に豆を生成/削除するのはあまりよくないかなと思い、豆は表示/非表示で制御するようにしました。

譜面を作りながら譜面エディターもアップデートしていたので、2~3日かけました。

2/7

大体譜面ができてきたあたりで、そろそろ”斬りたい”なぁ、と思ったので、判定処理の実装に入りました。
本格的な音ゲーなら判定は細かく分けたほうがよいと思いますが、
今回はあくまでカジュアルなので、「ちょうど(just)」と「ずれてる(miss)」の2つだけにしました。

2/8

ノーツ位置の指定がうまくいっていなかったようなので、小節処理を見直していました。

譜面管理は、csvかjsonに書き出すのがおそらくメジャーで、私もそうしようかなと思っていましたが、今回は(1曲だけだし)そこまでしなくていいかな~と思ったので、ScriptableObjectを使用することにしました。
ScriptableObjectとは、Unity内でのみ使用できる仕組みで、簡単に言うと、自分で好きな型を定義して情報を保持しておける仕組みのようなものです。
今回はこれを使って、

  • BPM
  • オフセット
  • 各ノーツの位置

を保存することにしました。
前回はcsvでしたので、これも初の試みとなりました。
Unity特化ということもあり、結構簡単に実装はできたのですが、どうも値が保存されていない気がする…
調べてみると、ScriptableObjectに動的に値を保存する際は、

EditorUtility.SetDirty();

というメソッドを呼んであげる必要があったみたいです。初めて知った…。
そして、ビルドの際に

using UnityEditor;

を含めるとエラーが発生するので、

#if UNITY_EDITOR
#endif

でくくる必要があることも学びました…。

譜面データはこんな感じです。

ノーツは全部で95個になりました。
どうもノーツの位置がキリのいい数値にならなかったのですが、まぁ自分で入力するわけじゃないし、目に見える箇所でもないからいいか…でも気になる…。

2/9

ここで、このゲームにおけるメディア(音楽/動画)の管理をもうちょっと意識しようという気になりました。

このゲームにおける音楽は、

  • トラック(楽曲)
  • SE(斬る時の音、リザルトの効果音)
  • ボイス(リザルト)
  • BGM(リザルト)

動画は、

  • 斬る時の演出

です。

音楽に関して、一番気になるのは、斬った時のSEですね。
この再生に遅延は許されないので、ADX2 LEの導入が当初からずっと頭にありました(ADX2 LEを導入するとほぼ確実に遅延をなくせる)。
ただ、PCだと遅延は(ほぼ)発生しないので、無理に導入する必要もないのかなぁと思い、導入はしませんでした。
ただそうなると、Unity Audioを使用することになるので、それはそれで別の配慮は必要だよなと思いました。

動画に関して言うと、ここでは斬る時のムービーになるわけですが、再生方法に少し悩みました。

元々が動画なので、動画として再生するのが普通だと思ったのですが、Unity標準のVideoPlayerを使ったところ、なんかスムーズに再生されないな、といった印象を受けました。
動画自体も短いので、テクスチャを高速で切り替えて動画のように見せる手法を取りました。

私はUnityで何かを作るたびに、どの制作でも共通で使えるユーティリティークラスを作成/拡張しているのですが、
これを機に、メディア再生用の共通インターフェースを作成しようと思いました。

音楽なら、Unity AudioでもADX2 LEでも同じ感覚で使えるように、
動画ならVideoPlayerでもテクスチャアニメーションでも共通で使用できるように、
それぞれISoundPlayer、IVideoPlayerを作成しました(これも新しい試み)。

ISoundPlayer.cs
namespace Utility
{
    public interface ISoundPlayer
    {
        //========================================
        // Properties.
        //========================================

        float Length { get; }

        float PlayTime { get; }

        bool IsPlaying { get; }

        //========================================
        // Methods.
        //========================================

        void Play(float playTime = 0.0f, System.Action callback = null);

        void Stop();
    }
}
IVideoPlayer.cs
namespace Utility
{
    public interface IVideoPlayer
    {
        //========================================
        // Properties.
        //========================================

        bool IsPlaying { get; }

        float Ratio { get; }

        //========================================
        // Methods.
        //========================================

        void Play(System.Action callback = null);
    }
}

ユーティリティーですが、今回は必要最小限の実装になっています。

2/11

ノーツを処理した場合の判定をもっとちゃんと取ろうと思い、方法を色々考えていました。

当初はノーツ自身が判定する形式にしようと思っていたのですが、それだと譜面に存在するすべてのノーツが常に判定を意識していないといけなくなるため、今回はノーツを叩いた時に、判定ラインの近くにいるノーツを判定する仕組みにしました。

良いか悪いかは分かりませんが、今回は判定ラインとノーツの座標との距離を取って、その距離によって判定を行うようにしました。

また、判定エリアは視覚的に分かりやすくするため、変更が容易にできるように、GameObjectとして置くようにしました。

2/12

この日くらいから、リザルトに本格的に着手し始めました。

リザルトは、ゲームの評価ごとに分けることにしました。
評価は判定justの割合のみで決定され、
S:100%
A:95%以上
B:80%以上
C:80%未満
の4種類用意しました。

リザルトはシンプルがいいなぁ、でもお嬢らしさ(?)は出したいなぁ、ということで、
評価によって百鬼さんの絵とボイスを鳴らすことにしました。

なので、ここ2、3日は、ひたすら百鬼さんの切り抜きを漁っていました。
幸いにも、

10分かわ余チャージ

という素晴らしい動画があるので、基本的にはこちらを参考にしようと思っていましたが、もうちょっとこんなボイスもほしいな、こういう画像がほしいなと思い、気がつけば一日中百鬼さんの動画を漁っていました。
(楽しかったと同時に、制作で一番つらかったところと言っても過言ではない笑)

結果的に、

評価 パターン BGM ボイス
S 3種類 2種類 2種類
A 2種類 - 6種類
B 2種類 - 3種類
C 2種類 - 30種類
がランダムで出るようにしました。
(お嬢のポンボイスがたくさん…)

2/14

リザルトも終わったところで、そろそろリリースのことを考え始めます。

リリースに関しては、
ミニゲームなので、誰でも気軽に遊べるのがいいなぁ
プラットフォームにはこだわらず、どんな環境でも遊べるといいなぁ
となると、ブラウザで遊べるのがいいなぁ
それならWebGLでビルドかなぁ
といったことをざっくりと考えていました。

それならビルドしてサイトに上げて…といきたいですが、
公開する以上、たくさんの方に遊んでもらうことになる(といいな)ので、
テストおよびデバッグは入念にしておいた方がいいなぁ、ということで、
デバッグに役立つ機能をいくつか作りました。

オートプレイ機能

ノーツが曲とずれているか、私に絶対的なリズム感があれば、それがすぐに分かるのですが、そうでもないので、ノーツを自動で叩いてくれるようにしました。

評価を指定して、その評価になるように自動でノーツを叩く仕組みを作りました。

エージングテスト

製造業やソフトウェア開発の仕事をされている方には馴染みあるかもしれませんが、「エージングテスト」というものがあります。
機器やソフトを長時間稼働させ続けて、問題がないかを検証するというものなのですが、これもやりたいなぁと思っていたので、オートプレイのついでにゲーム自体を自動でプレイし続ける仕組みを作りました。

エージングテストはあくまでテストであり、本制作には関係ないので、なるべくゲームに関わるコードに干渉したくないなと思っていました。
それに加えて、他のどんなプロジェクトでも簡単に導入できるものを目指しました。
その結果、

  • 自動で入力してほしいクラスに、IAgingを実装する
  • IAgingを実装したクラスは入力してほしいタイミングになったらIAgingのプロパティのフラグを立てる
  • AgingTesterは、最初にIAgingを実装しているクラスを登録し、フラグが立ったら入力を実行する

仕組みにしました。

IAging.cs
namespace Utility
{
    public interface IAging
    {
        //========================================
        // Properties.
        //========================================

        /// クリック可能なタイミングかどうか.
        bool Clickable { get; }

        /// プレイの区切りになったかどうか.
        bool ShouldLap { get; }

        //========================================
        // Methods.
        //========================================

        /// <summary>
        /// エージングテスト前のセットアップ.
        /// </summary>
        void SetupForAging();

        /// <summary>
        /// エージングテストによるクリック.
        /// </summary>
        void OnClickedByAging();

        /// <summary>
        /// エージングテストによるクリック処理.
        /// </summary>
        System.Collections.IEnumerator CoClickedByAging();

        /// <summary>
        /// クリック可能フラグをリセットする.
        /// </summary>
        void ResetClickable();

        /// <summary>
        /// プレイの区切りフラグをリセットする.
        /// </summary>
        void ResetShouldLap();
    }
}
AgingTester.cs
using UnityEngine;

using System.Linq;


namespace Utility
{
    public sealed class AgingTester : MonoBehaviour
    {
        //========================================
        // Fields.
        //========================================

        /// IAgingを実装しているクラスをアタッチしているオブジェクト.
        [SerializeField] private GameObject[] TargetObjects = null;

        [SerializeField] private FPSCounter FPSCounter = null;

        //========================================
        // Properties.
        //========================================

        private IAging[] Targets { get; set; } = null;

        private string LogFileName { get; set; } = "";

        private string LogFilePath { get; set; } = "";

        private int TryCount { get; set; } = 1;

        //========================================
        // Methods.
        //========================================

        //----------------------------------------
        // MonoBehaviour methods.
        //----------------------------------------

        private void Start()
        {
            Setup_();
        }

        private void Update()
        {
            // IAging実装クラスのフラグを見て処理を行う.
            foreach (var target in Targets)
            {
                if (target.Clickable)
                {
                    target.OnClickedByAging();
                    target.ResetClickable();
                }
                if (target.ShouldLap)
                {
                    Lap();
                    target.ResetShouldLap();
                }
            }
        }

        //----------------------------------------
        // Private methods.
        //----------------------------------------

        private void Setup_()
        {
            // IAgingを登録する.
            Targets = TargetObjects
            	.Select(obj => obj.GetComponent<IAging>())
            	.Where(iAging => iAging != null)
            	.ToArray();

            // 初期化を実行する.
            foreach (var target in Targets)
            {
                target.SetupForAging();
            }

            // ログファイルの設定を行う.
            LogFileName = System.DateTime.Now.ToString("yyyyMMddHHmm");
            LogFilePath = IOUtility.ProjectDirectory 
            	+ "/AgingTextLogs/" 
            	+ LogFileName 
            	+ ".txt";
        }

        //----------------------------------------
        // Public methods.
        //----------------------------------------

        public void Lap()
        {
            var text = $"{TryCount}回目\n平均FPS:{FPSCounter.AverageFps}\n";
            IOUtility.WriteToHead(LogFilePath, text);
            TryCount += 1;
        }
    }
}

本当はIAgingを実装しているクラスのみアタッチできるようにしたかったのですが、簡単にはできなさそうなので、GetComponent形式にしました。

テストするクラスでは、入力してほしいタイミングになったら
Clickable = trueしてあげるだけです。

    private void DoClickedForResult_()
    {
        Result.Hide();
        ResetJudgeResults_();
        var color = FadeImage.color;
        color.a = 0.0f;
        FadeImage.color = color;
        GameObjectUtility.SetActive(TitleObject, true);
        CurrentGameState = GameState.Title;
        Clickable = true;
        ShouldLap = true;
    }

今は必要最小限のみの実装なのでクリックだけですが、今後はいろいろな入力に対応していけたらと思っています。

エージングテストするなら、プレイ毎の平均FPSを記録したいよなぁと思い、エージングテスト後にログを出力する仕組みも作りました。

5回目
平均FPS:55

4回目
平均FPS:54

3回目
平均FPS:253

2回目
平均FPS:243

1回目
平均FPS:99

平均FPSがうまく取れなかったのでえらい値になっていますが、こんな感じでテキストファイルが出力されるようにしています。
使用メモリ量やスパイクのタイミングも記載されるとよりよいですね。

その他、フォントファイルを実際に使用する文字のみにしたり、音楽ファイルをほぼすべてmp3にしたりして、プロジェクトの最適化を図りました。

最終的に、プロジェクトのGitHub上のサイズは36.6MBになりました。
(Unityのファイルサイズはキャッシュうんぬんで972MB)

2/16

ここでふと「お?喧嘩する?」のボイスがめちゃくちゃほしくなったので、再び切り抜きを漁っていました。

その他、画像を修正したり(画像は私が手で切り抜いています。雑でごめんなさい)、Unityのアップデートも行いました(2020.2.4→2020.2.5)。

2/17

この日はhololive IDOL PROJECT 1st Live.『Bloom,』を観ていました。
お嬢、最高だった余!!

2/18

実はこの時点まで、判定justでも豆が2つに割れる演出はありませんでした。ただ消えるだけ。
それでもいいかなと思っていたのですが、なんか爽快感がないなあと。斬れた方が気持ちいいかなと思い、斬れた豆が落ちるような演出にしました。

実装は、2つに割れた豆の画像を別途用意し、判定がjustなら、その豆の位置に割れた豆を生成するという単純なやり方です。

今回は斬れ方にこだわりはなく、規則的な割れ方でも問題ないかなと思ったのでこのやり方です。
割れる時の動きは、最初はUnity Animationでやろうかなと思ったのですが、物理的な動きをキーフレームで打つのは面倒大変だなと思ったので、Rigidbody2Dに頼りました。

Rigidbodyは処理的に重いので、動きが決まったらAnimationにする方が本来はいいと思います。

2/19

ここでクレジットに着手します。
各素材のライセンス周りもここで確認しました。

2/20

自分でも結構遊んでいましたが、ボイスによって音量が違うことがかなり気になってきました。
なので、ゲーム内の音量をすべて同じぐらいにしようと思ったのですが、どれくらいの音量に合わせればいいのか…。
DTMをやっている方からするとすぐに解は見つかるのかもしれませんが、音楽ド素人の私には難しかったので、ひとまず「どーっちどっちの歌」の音量に合わせることにしました。

ボイスの切り抜きにはSoundEngine Freeを使っていたので、音量に関してもオートマキシマイズを使おうと思っていたのですが、使ってみた感じ、「○○dBにする」といった処理ではなさそうな感じで、求めていたものとは違うように見えたので、GauDioを使うことにしました。

これでゲーム内の音量はだいたい同じになったのですが、どうも音が大きく感じる…。
結局、AudioMixerを使って、Masterを-16dBしました。
「普通」ってなんだろう…。と迷う一日でした。

また、画像切り抜きを今までAffinity Photoでやっていたのですが、よりきれいに切り抜けないかなと思ってPhotoshopの無料体験版を試したりもしていました。

それに加え、ADX2 LEも試しました。
ADX2 LEだと遅延はどれだけ防げるかな…と思い、導入して比べました。
やはりADX2 LEは遅延がほぼ0…。
とても素晴らしいですが、PCで遊ぶ分には、Unity Audioでもそんなには気にならないかな…という程度の遅延だったのと(ADX2 LEを体験した後だと差を感じる程度)、今回はなるべく外部アセットは使わないようにしたかったので、やはり導入は見送りました。

本格的な音ゲー制作では必須だと思います。

ゲームのアップロード

そして、いよいよ「ゲームをどこにアップロードするか」を真剣に考えることになります。

考えていた選択肢は

でした。

Unityで作っているのでunityroomがいいかなと思ったのですが、unityroomはスマホに対応していないそうなので、Unity WebGLビルドをアップロードするメジャーなやり方を試していくことにしました。
こちらの記事が、私が知りたかったことが網羅されており、大変参考になりました。

unityroom

unityroomはとてもお手軽でした。
まずTwitterアカウントで登録し、WebGLビルドの成果物のファイルをアップロードするのみ。
ビルド時のCompression FormatをGzipにする必要がありますが、必要な設定はそれくらいです。
使用アセットを登録する箇所もあり、ユーザーフレンドリーです。

Editey

Google Driveをそのままサーバーみたいに使うことができるGoogle Drive上のアプリです。

こちらのサイトがとても分かりやすかったので参考にしていましたが、Publish時にエラーが出てしまい、断念しました。

Netlify

GitHubのプライベートリポジトリをWebGLプレイヤーとして公開できるサービスです。
英語なので翻訳を駆使しつつ、なんとか公開までたどり着けました。

Firebase Hosting

Firebaseを使って、ビルドした成果物をアップロードします。
コマンドを打つ必要があるので多少の環境構築は必要ですが、そこまで難しくはないです。

レンタルサーバー

なんだかんだ自分でサーバー建てるのが一番自由度があっていいだろうということで、これも試しました。

自分で一から建てるのも悪くはないですが、レンタルした方が現実的な気がしたので、おすすめのレンタルサーバーを探し、ConoHa WINGに決めました。
無料お試しがあると良かったのですが、なかったので一番安い(と思われる)ベーシックプランを申し込み。初めてのレンタルサーバーだったのでどきどきしました。
ちょっと勝手が分からず混乱しましたが、ファイルマネージャーからindex.htmlを含むファイルをアップロードすると見れるようになったので、これもアリだなと思いました。

ここまでやっておいてなんですが、Unity WebGLビルドがそもそもモバイル端末に向いてなさそうだな…ということに気づきました。
タブレットならまだしも、スマートフォンだとモバイル用の表示になり、ゲーム画面が引き伸ばされます。
それを防ぐには成果物のコードを書き換えればよいのですが、ゲームをモバイルに完全対応させるには結構手間がかかりそう…と思ったので、ここにきてモバイル端末対応は諦めることにしました…。
そして、unityroomにアップロードすることに決めました。
(ConoHa借りたが結局使わず)
カジュアルミニゲームでモバイル端末遊べないのは結構痛いなと思いましたが、unityroomというプラットフォームに上げられるので遊んでくれる確率は上がるかなと…。

2/21

友人にテストプレイしてもらいました。
当初は判定は表示していなかったのですが、特にノーツが連続で来るところは判定が表示されていた方が分かりやすいかもということで、「良」と「喝」を表示するよう改修しました。
また、斬った時の暗転も最初は真っ暗だったのですが、目がチカチカするので半透明に変更しました。

ゲームも完成、アップロード手続きも完了、もうリリースは目前です。
早まる気持ちを抑え、公開に向けてのテストを進めます。

ここで一つ気になることが。
クレジット画面でリンクを載せたのですが、開こうとすると、ゲーム内のブラウザで開こうとする…。
理想は新しいタブで、しかもエディターでは別タブで開けていたのに…!!

調べると、それを実現するにはプラグインが必要とのこと。

https://gamefbb.com/【unity】外部ブラウザでurlを開く/

つら…標準で対応してくれたら…と思いながらも、jslibを作りました。

この実装の後、ビルドをした成果物をunityroomにアップロードして見てみると、
ロード時にRuntimeErrorが出てゲームが遊べなくなってしまいました。
もしかしてjslib入れるとunityroomで遊べなくなる!?と焦っていたら、
どうやらBuild And Runで実行したタブを開いたままアップロードするとこのエラーが出るみたいでした…凡ミス…。

ゲームは一応完成したのですが、お嬢のことをもっと知りたいという気持ちは常にあったので、気づけば百鬼さんのメンバーになっていました。

2/22

テストも紹介文の作成も終わり、いよいよ佳境です。
元ネタにさせていただいたツイート元の青眼昴さん、
「どーっちどっちの歌」を使用させていただいたくみゆき進さんに、ゲームの公開および素材の使用許可をお願いするメールを送りました。
とっっっても緊張しました。時間をかけて送る文章を推敲しました。
どちらか片方からでも断られたらお蔵入りにするつもりでした。

結果、お二方とも快諾してくださり、大変嬉しかったです。

あとは公開するだけだ…!!

2/24

公開のタイミングを探りながら、unityroomの投稿に何のタグをつけようか迷っていた時に、そうだ!先人の方々はどんなゲームを作っているんだろう?と思い、調査も兼ねていくつか遊んでみることにしました。

https://unityroom.com/games/holoaction

https://unityroom.com/games/holohoppingver2

https://plicy.net/GamePlay/108960

「えっ、すごくね…??」
危うく公開を断念するところでした。

2/25

メールにて許可をいただいた後は、公開するタイミングを悩みました。

ミニゲームではあるので、さらっと公開してしまっていいとは思うのですが、
せっかく公開するのであれば、そのタイミングをちょっと意識してみようかなという気になりました。

同じツイートでも時間帯によりインプレッション数が変わると聞いたことがあるので、どのタイミングがいいだろうと悩みました。

実は、一番遊んでほしいのは百鬼あやめさんご本人というより、百鬼組のみなさんかな、と思っていました(ご本人様はちょっと恥ずかしい、、という気もあり)。

ただご本人様にも見てもらえるのであれば、自ずと百鬼組のみなさんにも見てもらえるだろうとも思ったので、
百鬼あやめさんがツイートなりエゴサなりの動きがあった時に公開しようと決めました。
そのタイミングなら、百鬼組のみなさんのサーチにも引っかかるかも…?と思ったので…。

結果はどうだったでしょうか…。

所感

このゲーム制作での一番の目的は、「ゲームを作りきって公開したという実績を作る」ことでした。
なので、ゲームのクオリティはそこそこに、できるだけ早く作って公開するということに重きを置いていました。

個人的には、これはゲームジャムで作れるレベルかなと思っていたので、約1ヶ月もかかってしまったことが、自分自身でとっても情けなく思ってしまいました…。

こんな小さなゲームですが、制作を通して、

  • 事前にある程度設計してからゲームを完成させる
  • クラス図を書ききる
  • 小節ベースで音ゲーを作る
  • インターフェースを用いた実装を行う
  • エージングテストの仕組みを作る
  • GauDioを用いた音量平均化を行う
  • WebGLでビルドする
  • unityroomを使う
  • Editeyを試す
  • Netlifyを試す
  • Firebase Hostingを試す
  • レンタルサーバーを借りる
  • 公開許可のやり取りを行う
  • コンテンツのファンゲームを作る
  • ゲームを作りきって一般公開する
  • 制作ふり返り記事を書いて公開する

等々、自分にとって様々な新しい経験ができました。

次は何を作ろうかな。






ゲーム㊙情報
実は100回に一回の確率で、スタート時に余さんが出てきます!
出会ったアナタはとってもラッキー!!







この記事、たぶんゲームのソースコードより長い。

Discussion