レイドボス「naichi」登場!というゲームを作りました。

概要

第10回目開催、おめでとうございます!
unityroomさんにはお世話になっていて、リスペクトを表現したく
いつかnaichiさんをラスボスとしたゲームを作りたいなー考えていました。

そして、今回ついに作ることができました。
レイドボス「naichi」登場!

レイドボス「naichi」登場! | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

燃え尽き症候群となったunityroom運営者を
みんなでクリックして褒めて、やる気を出してもらうゲームです。

企画する

以下2つの条件を満たすものを考えていました。

  1. naichiさんがラスボス
  2. unityroomの人ならすごく盛り上がれる何か

実在する1人の人をラスボスにしている時点で、楽しめる人が限られる。
ならいっそもうunityroomに全寄せしてしまおう!と考えました。

で、プラットフォーム系のサービスはそこで活動しているクリエイターさんありき、だと思ったので
じゃあnaichiさんだけじゃなくて他の開発者様にも登場してもらおう!と。

それから開発期間1週間の制限を合わせ、考えついたのが
プレイヤー協力型のクリッカーゲーム「レイドボス「naichi」登場!」です。
「unity1weekに挑戦している」というゆるい繋がりで結ばれたプレイヤー同士で遊んだら
面白いんじゃね?と予想してました。

交渉する

今回の製作で一番難しかったところです。

勝手に名前を使うのはご法度、なので
登場して欲しい開発者様にコンタクトをとりました。
面と向かって話したこともあったこともない人に
コンタクト時何伝えようか悩んだんですが、とにかく「意味が伝わるか?」を色々考え
最終的に4点を意識するようにしました。

  • 自己紹介 (unityroomで動いている人間です)
  • こっちがやりたいこと (ゲームのOPで開発者を登場させたい)
  • ご協力いただく範囲 (Twitterアイコンとこちらで考えたセリフの使用許可)
  • ご協力いただいた際のリターン (クレジットにお名前表示、あれば拡散したいもののURLなど)

コンタクトとった6名の開発者様からは初回の返信で、 「全面OK、またはセリフだけ変えればOK」
でしたので、こちらの意図はうまく伝わったのかなと思います。

リターンについては、こちらが提示できるものが少なく...
この条件でご協力いただいた開発者様に感謝申し上げます。

それから、unityroom開発者の中で誰に依頼するか...なのですが
界隈で活躍されている方の中で、

  • 自分がリツイートとかいいねとかちょいちょいしている人
  • TLをみて、コラボ系の話乗ってくれそうと判断した人
  • TLをみて、すごく忙しくなさそうな人
  • ツイッターアイコンが顔写真じゃない人

であれば、快く承諾してくださるかなーと思ってお声がけしました。
OKをいただいた6名の開発者様の他にも何名か依頼しようとしていたのですが、
DMが解放されていなかったり、話しかけて良いかわからず不安だったりで
最終的にコンタクト取らなかった方もいらっしゃいます。

今回は穏便に行きましたが、1歩間違えると大炎上およびunityroom出禁のリスクがある、
というのを感じてて、非常にドキドキしてました。

まとめ

次回開催時、開発者の名前を使いたい方はぜひ自分にお声がけください。welcomeです。
チーム開発もお待ちしてます(チームで開発したことないですが...)
そして、unityroomに感謝!

おまけの「開発する」

全部かくと大変なので、工夫したところを絞って書きます。

シーン全体で参照したいデータについて

小規模個人開発で使える、シングルトンでデータを持たせる形にしています。
どんなシングルトンかというと。。。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using CoffeeR.Model;

public class ServiceLocator : SingletonMonoBehaviour<ServiceLocator> {

    /// <summary>
    /// シーン毎に初期化されるコンテナ
    /// </summary>
    List<object> sceneContainer;

    override protected void Awake ()
    {
        if (this != Instance)
        {
            Destroy(this.gameObject);
            Debug.Log(
                typeof(ServiceLocator) +
                " は既に他のGameObjectにアタッチされているため、コンポーネントを破棄しました." +
                " アタッチされているGameObjectは " + Instance.gameObject.name + " です.");
            return;
        }

        Initialize();

        DontDestroyOnLoad(this.gameObject);
    }

    void Start () {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnSceneLoaded( Scene scene, LoadSceneMode mode ){
        Initialize();
    }

    void Initialize() {
        sceneContainer = new List<object>();

        var gameState = new GameState();
        var score = new Score();
        var tooltip = new ToolTipModel();
        var todo = new Todo();

        sceneContainer.Add(gameState);
        sceneContainer.Add(score);
        sceneContainer.Add(tooltip);
        sceneContainer.Add(todo);
    }

    public T Get<T>() {

        for(int i = 0; i < sceneContainer.Count; i++){
            if(typeof(T) == sceneContainer[i].GetType()){
                return (T)sceneContainer[i];
            }
        }
        
        throw new System.Exception(typeof(T) + "のインスタンスを取得できませんでした。");
    }
}

シーン全体で使う変数はクラス化、インスタンスを「sceneContainer」に入れてます。
使うときはこんな感じで、Get()だけ使えばよし。

var score = ServiceLocator.Instance.Get<Score>();
var gameState = ServiceLocator.Instance.Get<GameState>();
ボタンのアニメーションについて

押せるボタンについては、アニメーションの処理が記述されたコンポーネントをアタッチしてます。

using UnityEngine;
using UnityEngine.EventSystems;

public class GeneralUIReaction : MonoBehaviour , IPointerEnterHandler, IPointerClickHandler, IPointerExitHandler{

    public void OnPointerClick(PointerEventData eventData){
        // ここにクリックした時のアニメーション処理
    }

    public void OnPointerEnter( PointerEventData eventData ){
        // ここにマウスポインターが入った時のアニメーション処理
    }

    public void OnPointerExit(PointerEventData eventData){
        // ここにマウスポインターが離れた時のアニメーション処理
    }
}

ゲーム上で押せるボタンはどれなのか、プレイヤーに早く学習させたかったので
どの押せるボタンにも、上記のコンポーネントをアタッチしました。

リアルタイムっぽくクリック数を集計する

クリック数はニフクラのモバイルバックエンドから取ってきてます。
で、10秒おきに各プレイヤーのクリック数をsumするという、
日頃DB触っている人にとっては激おこ案件になる恐ろしいことをしています。

// ※UniRxというライブラリを使ってます。

//  10秒おきに、
Observable.Interval(System.TimeSpan.FromSeconds(10))
.Subscribe(_ => {
        // サーバーにある、自身以外のプレイヤー全員のスコアを通信して取得
    Observable.FromCoroutineValue<int>(NCMBClient.LoadOtherPlayersScore)
    .Subscribe(otherScore => {
                 // 通信して取得したデータを、変数に入れておくよ
        score.OtherPlayersScore.Value = otherScore;
        score.OtherPlayersScoreOfLoadTiming = otherScore;
    }).AddTo(this);
}).AddTo(this);