LoginSignup
3
1

More than 1 year has passed since last update.

RTAイベント向け簡易背景システムをつくったよ

Last updated at Posted at 2021-12-04

今回のレギュレーション

  • この記事で得られる知識
  • そもそもRTAとは?
  • 作ったものは?
  • なぜ今回の形になったのか?
  • 当日の運営について
  • 完走した感想

この記事で得られる知識

  • サーバがない中で構築する動的背景の作成
  • NodeCGでやろう!と思える心
  • レガシーできたないソースは読みづらいという見本。

そもそもRTAとは?

RTAとはReal Time Attackの略なのですが、
簡単に言うとゲームの早解きのことです。
どんな手を使ってもいいから何人かの人たちが自分の得意なゲームをいかに早くクリアするかを突き詰めるといったもので
世界ではGames Done Quickというアメリカで行われるイベントが一番有名だったりします。
日本でもRTA in Japanというイベントが年に2回開かれており、
今年も12月26日から6日間、24時間開催される予定となっています。
https://nlab.itmedia.co.jp/nl/articles/2012/28/news054.html
こちらの記事を見ていただけると多分その様子がよく分かるかと思います。
わからなくなるかもしれませんが、でも実際にこういった物理的な手を使ってでもクリアしたら正義です。
これらのイベントは主にTwitchで行われ、イベント中に得たチアー(投げ銭)やサブスクは
全て国境なき医師団のような慈善事業団体へ寄付されます。(RTA in Japanでは2000万円ほどあつめ、それを募金しています)
皆さん何卒よろしくお願いいたします(私は運営ボランティアでの参加予定)

さて、今回このシステムを利用したのは個人が運営する同様のRTAイベントで、
1ゲーム辺り数時間かかる長めのゲームのRTAイベントです。

作ったものは?

ゲームを実況する際に表示するこのようなものを作成しました。
image.png
走者(プレイをする人)の情報や、解説者の情報、次の走者と走るタイトルの情報、
あと、実際に現在走っているゲームのタイトル、カテゴリ、EST(クリアまでどのぐらいかかるか?の目安)を表示しているのですが、
こちらのデザインとプログラムを担当し、作成いたしました。
今回は2回目の開催となったのですが、1回目がこちら(私はこの時作成には関わってないです)

固定の画像を背景として表示させていたため、突然なにかが変更になったときに対応が取りづらいため、
こちらを今回動的に変更できるようにしました。

今回のがこちら。

こちらなんですが本来はNodeCGを使えばもっと楽ができたのですが、
今回サーバがなかったためそちらを即断念。(後から別の運営担当が自前サーバを用意してくれたんですが…)
最初は静的なHTMLを走者に配り、OBSという配信ソフトでよみこんでもらうことで
こちらを実現しておりました。
で、走者情報や次の走者情報などは、スプレッドシートからデータをとってきて、
運営その読み込み対象番号を変える事で切り替わっていくようにしていました。

さて、ここからが実際のソースと内容になります。
image.png
一部黒塗りさせていただきましたが、このように走者の一覧がスプレッドシートに書かれているので、
こちらをAppsScriptで読み込むように変更しました

function loading() {
    var ss = SpreadsheetApp.openById("XXXXXXXXXXXXXXXXXXXXXXXXX");

    var listss = ss.getSheetByName("スケジュール");

    var nowList = listss.getRange("K2").getValue();
    let i = 0;
    while(listss.getRange("A4").offset(i , 0).getValues() != ""){
      var nowRow = listss.getRange("K4").offset(i , 0).getValue();
      if(nowRow == nowList){
        return listss.getRange(4 + i ,3 , 1 ,19 ).getValues();
      }
      i++;
    }
    return "Final";
}

function doGet(e) {
  return ContentService.createTextOutput(loading()).setMimeType(ContentService.MimeType.TEXT);
}

もう少しスマートな書き方があったかとはおもうのですが、
とりあえず自分しかみないソースだし適当で良いか というのが上記のような惨状です。
現在の走者番号がK2のセルに入っているのでそれを取得し、
K2に設定されている番号と同じものを探して見つかったらその行を取得して、行の内容全てを返してあげるというものです。
このGASを直接叩くとこんな感じの内容が帰ってきます。

塩化コボルト,天外魔境ZERO,Any%(with Turbo/Auto Fire),コマンド選択制RPG,Sat Dec 30 1899 14:26:01 GMT+0900 (日本標準時),5:40:00,Sat Dec 30 1899 09:13:59 GMT+0900 (日本標準時),Sat Dec 30 1899 14:41:00 GMT+0900 (日本標準時),2,塩化コボルト,XXXXX,twitch,,,,Ever Oasis 精霊とタネビトの蜃気楼,Any%,ショーダイ,

あとはこれを本体のHTMLの方で受け取って、分解して入れ込んでいく作業。…といった感じです。

main.js
$(function(){
    $.ajax({
        type: 'GET',
        url:'[GASのURL]',

        dataType:"text",
        success: function(data){

            // HTML上の必要な箇所に値を設定します。
            const obj = ["title","cate","est","runName","runID","runIcon","comName","comID","comIcon","nextTitle","nextCategory","nextRunner","nextCommentator"];
            const colNum = [1,2,5,9,10,11,12,13,14,15,16,17,18];
            const iconImage = {
                "youtube":"yt.png",
                "twitch":"tw.png",
                "nico":"nc.png",
                "twitter":"tt.png"
            };
            var textLength = data.split(",");
            $("#cate").text(textLength[0]);

            var colArr = ["comName","comID"];

            for(let i=0;i < obj.length;i++){
                var clname = "#"+ obj[i];
                if(obj[i] == "comName" && textLength[colNum[i]] == "") {
                    $("#commentator").css("display","none");
                    $("#commentator2").css("display","none");
                    $(".freeSpace").css("height","300px");
                }else if(obj[i] == "comName" && textLength[colNum[i]].indexOf("/") < 1){
                    $(".freeSpace").css("height","210px");
                    $("#commentator2").css("display","none");

                }else if(obj[i] == "comName" && textLength[colNum[i]].indexOf("/") >= 1){
                    $(".freeSpace").css("height","130px");
                }

                if(obj[i] == "comIcon" &&  textLength[colNum[i]].indexOf("/") >= 1){
                    var listIcon = textLength[colNum[i]].split("/");
                    for (let index = 0; index < listIcon.length; index++) {
                        const element = listIcon[index];
                        $(clname + (index + 1)).addClass(element);
                    }
                }else if(obj[i] == "comIcon" && textLength[colNum[i]] != "" ){
                        $(clname +  1).addClass( textLength[colNum[i]]);
                }else if(obj[i].match(/Icon/) && textLength[colNum[i]] != ""){
                    $(clname).addClass(textLength[colNum[i]]);
                }else if(obj[i] == "title" && textLength[colNum[i]] != ""){
                    var titleLen = textLength[colNum[i]].replace(/\r?\n/g , "");                    
                    if (titleLen.length > 40 ){
                        $(clname).css("font-size","100%");
                    }
                    $(clname).text(titleLen);  
                }else if(obj[i] == "cate"){
                    if (textLength[colNum[i]].length > 30 ){
                        $(clname).css("font-size","16px");
                    }
                    $(clname).text(textLength[colNum[i]]);
                }else if(obj[i] == "est"){
                    $(clname).text("EST:" + textLength[colNum[i]]);
                }else if(obj[i] == "nextTitle" && textLength[colNum[i]] != ""){
                    if (titleLen.length > 40 ){
                        $(clname).css("font-size","16px");
                    }
                    $(clname).text(textLength[colNum[i]].replace(/\r?\n/g , ""));
                }else if(obj[i] == "nextRunner" && textLength[colNum[i]] != ""){
                    $(clname).text("Runner:" + textLength[colNum[i]]);
                }else if(obj[i] == "nextCommentator" && textLength[colNum[i]] != ""){
                    $(clname).text("Commentator:" + textLength[colNum[i]]);

                }else if(obj[i] == "nextCategory" && textLength[colNum[i]] != ""){
                    if(textLength[colNum[i]].length > 30){
                        $(clname).css("height","60px")
                    }
                    $(clname).text("Category:" + textLength[colNum[i]]);
                }else if(obj[i] == "nextCommentator" && textLength[colNum[i]] == ""){
                    $(clname).remove();
                }else if(colArr.includes(obj[i]) && textLength[colNum[i]] != "" ){
                    var listCom = textLength[colNum[i]].split("/");
                    for (let index = 0; index < listCom.length; index++) {
                        const element = listCom[index];
                        $(clname + (index + 1)).text(listCom[index]); 
                    }

                }else{
                    $(clname).text(textLength[colNum[i]]);    
                }
            }
        }
    });
});

リファクタリングしてないがゆえにここまで汚くなってしまったわけですが、
こうなったのにも理由があり、あとから色々変わっていったのと時間がなく…
自分のレベルの低さに恐れおののいてしまいますね。
もっとクラスを有効に使うべきでしたし、
とりあえず動けばいいやろで作ってしまったのが問題でした。

あと、HTMLの方は割愛しますが、地味に歯車が右へ左へと切り替わりつつ回っているので動画にてご注目いただきたいなというところです。

なぜ今回の形になったのか?

先項でも述べた通り、サーバがない状態で走者にHTMLの塊だけ渡して運営しようとしていたためです。
サーバがない状態でも突然の対応に走者が追われないために作成しました。

当日の運営について

当日はあくまでスプレッドシートをいじりさえすればOKの状態にしていました。
突然なにか変更があった場合もスプレッドシートの情報さえ変更すれば、
走者に影響なく運営はできたので、概ね思った通りの動きができていたと思います。

あと、別途裏でオープニングを作成してほしいと数時間前に言われて
慌てて作成したり…
それと、エンディングも必要と言われるだろうと想定し、
エンドロールとエンディング用画面を作成しました
https://www.twitch.tv/videos/1159875157?t=00h09m15s
前もっていってほしかった…

完走した感想

二度とこんな形で作りたくない。
サーバがあるならNodeCGを使おう。
まじで。

ライブ配信レイアウトを作るNode.jsのフレームワーク
https://qiita.com/Hoishin/items/36dcea6818b0aa9bf1cd

1から学ぶNodeCG#1:NodeCG導入編
https://qiita.com/cma2819/items/e775bd8aa2a2fa911d4c

NodeCGで配信を華やかに
https://qiita.com/secchanu/items/f422f1e101cc85caf40a

RTA in JapanのNodeCGレイアウトを動かしてみる
https://qiita.com/pasta04/items/d676d9c2fb716176f665

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1