5
4

More than 5 years have passed since last update.

RTA in JapanのNodeCGを改造してみる

Posted at

はじめに

NodeCGは動かせるようになったけど、どうやって改造したらいいんだ…
という方向けに、RTA in Japanのレイアウトを各自のイベント用に改造するための方法を記載します。

この記事のゴール

  • 各自のイベント用のレイアウトを作成する。

用意するもの

用意するもの(必須)

用意するもの(オプション)

Microsoft製のエディタ。(以降VSCodeと記載)
Windows版、Mac版があり、比較的軽くてオススメです。
エディタの設定をプロジェクトに含めてgitにコミットしておくことで、複数人で設定を共有することができます。

エディタで開く

VSCodeを起動し、rtainjapan-layoutを開きます。
スクリーンショット 2019-04-17 22.05.07.png

フォルダを開いたらターミナルを表示します。
以降のコマンドは、VSCode上で実行します。

スクリーンショット 2019-04-17 22.23.39.png

VSCode上でターミナルを開くとこんな感じのウインドウになります。
スクリーンショット 2019-04-17 22.25.20.png

Gitフォルダを削除

そのままだとRTA in Japanのgitととの差分が表示され続けて面倒なので削除してしまいましょう。
プロジェクトの一番上にある.gitというフォルダを削除します。

(必要な人のみ)レイアウト名を変える

レイアウトのrtainjapanの名称を自分のイベント名に変えてみましょう。
以降はmyrta-layoutsに変えた前提で記載していきます。
変更箇所は以下の通りです。

  • ファイル名、フォルダ名
cfg/rtainjapan-layouts.json
rtainjapan-layouts
  • ファイルの中身
rtainjapan-layouts/package.json
scripts/postinstall.ts
package.json

レイアウトの名前を変えたらもう一度インストールする必要があります。
最上位のpackage.jsonがあるフォルダで以下を実行します。

yarn

ビルドと起動

最上位のpackage.jsonがあるフォルダで以下を実行します。

yarn dev

ターミナルをもう一つ立ち上げ、以下を実行します。

yarn start

配信レイアウト編集

RTA in Japanの配信レイアウトはReactと呼ばれるJavaScriptのフレームワークで構成されています。初学者がいきなりReactを理解するのは非常に困難であるため、この記事では、以下を目標とします。

  1. 背景を変える
  2. ゲームの表示位置を変える
  3. ゲーム情報、走者情報の位置を変える

新規レイアウトの作成

配信レイアウトのソースは以下に格納されています。
myrta-layouts/src/graphics/views
サンプルとして、hd1.tsxを複製し、新しいレイアウトを作成してみましょう。
同フォルダにnewLayout.tsxとしてコピーします。

レイアウト内のpackage.jsongraphicsの配列があるので、以下を追記します。

package.json
            {
                "file": "newLayout.html",
                "width": 1920,
                "height": 1080
            }

package.jsonに新しいレイアウトを追加したら、再ビルドします。
既にビルドを起動していた場合は、Ctrl+Cなどで停止してから実行してください。
なお、package.jsonを弄らない限り、以降このビルドコマンドを打つ必要はありません。

yarn dev
yarn start

追加したレイアウトが正しく認識されていれば、以下にアクセスした際に、追加したレイアウトが表示されているはずです。
http://localhost:9090/dashboard/#graphics
スクリーンショット 2019-04-17 23.12.13.png

位置調整

本題のレイアウトいじりです。

まずは、コピーしたレイアウトとHTMLがきちんと結びつくようにします。
Reactのおまじないみたいなものだと思って良いです。

newLayout.tsx(一番下あたり)
ReactDom.render(<App />, document.getElementById('newLayout'));

上記を保存すると自動的に再ビルドが行われます。
以下にアクセスして、コピー元のHD1と同じレイアウトが表示されればOKです。
http://localhost:9090/bundles/myrta-layouts/graphics/newLayout.html

Reactはコンポーネント呼ばれる部品で構成されています。
コンポーネントの説明は省きますが、<table>とか<div>のようなタグを拡張して自分で機能を付加できるすごいタグだと思えば良いです。(有識者に怒られる解説)

RTA in Japanのレイアウトは以下のコンポーネントで構成されています。

  • StyledContainer
    • 背景画像と、ゲーム表示、カメラ表示エリアを担当するCSS
    • xxxContainer系はそれぞれのCSS
  • RunnerContainer
    • 走者情報のCSS
  • CommentatorContainer
    • 解説者のCSS
  • GameContainer
    • ゲーム情報のCSS
  • TimerContainer
    • タイマーのCSS
  • RtaijRunner
    • 走者情報
  • RtaijGame
    • ゲーム情報
  • StyledRuler
    • ゲーム情報とタイマーの境界線
  • RtaijTimer
    • タイマー情報
  • RtaijOverlay
    • 左上のロゴ
    • ツイート表示
    • 画面下の背景をちょっと暗くするやつ

背景画像の差し替え

myrta-layouts/src/graphics/images/background.pngを置き換えるか、以下を好きな画像のパスにします。

newLayout.tsx
import background from '../images/background.png';

ゲーム、カメラ表示領域の変更

RTA in Japanのレイアウトでは、StyledContainerのCSS、clip-pathによって、背景を切り抜くことでゲーム画面表示領域を表現しています。

newLayout.tsx
const StyledContainer = styled(Container)`
    background-image: url(${background});
    clip-path: polygon(
        0px 0px,
        15px 0px,
        15px 741px,
        15px 1065px,

上記のような座標を手打ちするのは至難の業であるため、まずはツールでざっくりレイアウトを作りましょう。
clip path generatorを使います。

スクリーンショット 2019-04-18 0.08.34.png

まずは背景画像のファイルを、画面にドラッグ&ドロップします。
スクリーンショット 2019-04-18 0.10.55.png
解像度が高くて表示域が画面からはみ出してしまいましたが問題ありません。
ブラウザの表示を縮小しましょう。

左上のカクカクっとしたアイコン、Polygonal lasso Selectionを選択し、画面に表示する領域を囲んでいきます。クイックスやダンシングアイをやってる気分になれますね。良い子は後者でググってはいけない。
なお、線を引き終える時はダブルクリックです。

以下の例は、カメラ無し、右上にゲーム、という表示領域を作っている様子です。
CSSのパラメータが画面左下のテキストボックスに書かれているのがわかると思いますので、後は微調整をすればOKです。

control.gif

上記手順で生成したCSSをレイアウトに適用し、保存してみましょう。

newLayout.tsx
    clip-path: polygon(
以下略

保存した後、ブラウザでレイアウトを表示すると、見事(?)適当に引いた線が反映されました。
スクリーンショット 2019-04-18 0.41.30.png

ゲーム情報やタイマー情報の位置を変える

対応するxxxxContainerのCSSを変えるだけでOKです。
なお、GamerContainerTimerContainerは共通CSSとしてinfoStyleが定義されています。
チョチョイとCSSをいじってみましょう。

const infoStyle = css`
    position: absolute;
    z-index: 10;
    top: 200px;
    height: 210px;
    display: grid;
`;

const GameContainer = styled.div`
    ${infoStyle};
    left: 0px;
    width: 765px;
`;

const TimerContainer = styled.div`
    ${infoStyle};
    left: 0px;
    top: 300px;
    width: 372px;
`;

スクリーンショット 2019-04-18 0.51.43.png
・・・はい!というわけで表示の移動ができました。各々好みの位置に変えましょう。

コンポーネント作り

あんまり細かいことには触れないので、頑張って読み解いてください。以下メモ書き。

  • コンポーネントはmyrta-layouts/src/graphics/componentsに格納されている
  • rtaij-game.tsxなどは、NodeCGが持っているnodecg変数から自身に必要な値を取り出し、表示用の共通コンポーネントbase-info.tsxに渡している
  • 走者情報はnameplateのコンポーネントを使っている
  • nodecgに変化があった時のイベント登録はcomponents直下の.tsxたちが全部請け負っている
  • nodecgが持ってる変数の情報はmyrta-layouts/src/replicants/index.tsに定義されているReplicantNameと、それに対応する定義。

雑に実装しちゃう

細けぇことはいいんだよ、俺は俺のタイマー表示したいんだよ的な人向けの参考資料。
最低限の要素だけで構成しています。

newLayout.tsx
import '../styles/common.css';

import React from 'react';
import ReactDom from 'react-dom';
import styled from 'styled-components';
import {Container} from '../components/lib/styled';
import background from '../images/background.png';
import {CurrentRun, ReplicantName as R, Timer} from '../../replicants';

const StyledContainer = styled(Container)`
    background-image: url(${background});
`;

const currentRunRep = nodecg.Replicant<CurrentRun>(R.CurrentRun);
const timerRep = nodecg.Replicant<Timer>(R.Timer);

/** このコンポーネントへの引数みたいなもの。今回は何も無い */
interface Props {}

/** このコンポーネント内で持つ一時変数みたいなもの */
interface State {
    /** 走者情報 */
    runner: string;
    /** タイマー */
    timer: string;
}

class App extends React.Component<Props, State> {
    // コンポーネント生成時に一回だけ実行するやつ
    constructor(props:Props){
        super(props);
        // タイマーが変わったら実行する関数をセット
        timerRep.on('change', this.timerChangeHandler);
        // 走者情報が変わったら実行する関数をセット
        currentRunRep.on('change', this.currentRunChangeHandler);
        // 初期値セット
        this.state = {
            runner: '',
            timer: ''
        }
    }

    /** 上でセットした、nodecgのタイマーが変わった時に動くやつ。stateにタイマーをセット */
    private readonly timerChangeHandler = (newVal: Timer) => {
        this.setState({timer: newVal.formatted});
    };

    /** 上でセットした、nodecgの走者情報が変わった時に動くやつ。stateに走者の名前をセット */
    private readonly currentRunChangeHandler = (newVal: CurrentRun) => {
        this.setState({runner: newVal.runners[0].name});
    };

    render(){
        return(
            <StyledContainer>
                <div style={{color:'white'}}>
                    <h1>走者:{this.state.runner}</h1>
                </div>
                <div style={{color:'white'}}>
                    <h1>タイム:{this.state.timer}</h1>
                </div>
            </StyledContainer>
    )}
}

ReactDom.render(<App />, document.getElementById('newLayout'));

上記のソースは、こんな感じで表示されます。
簡易的ではあるものの、ここまでできたら後は純粋なHTMLとCSSの知識だけでも作れますね。

control.gif

(オマケ)コンポーネントをちゃんと作る人へ

ReactであればStorybookを使ってコンポーネント作りすることを強く推奨します。
理由は以下の通り。

  • コンポーネント単体で表示確認できる
    • ページ全体を作れていなくてもパーツの確認ができる
    • ダッシュボードの実装と並行してパーツを作れる
    • パーツ単体なのでデザイナーと意思疎通が取れやすい
  • 表示パターンを網羅的に確認できる
    • 例えば機種ごとにアイコンを表示したくなったとして、ダッシュボードからいちいち確認するのは面倒だし、そもそも表示確認を漏らす可能性が高い。

React Storybook入門:コンポーネントカタログがさくさく作れちゃうかもしれないオシャレサンドボックス環境を参考にしてみてください。

(オマケ)レイアウトごとにhtmlファイルをソースとして用意しなくてよい理由

レイアウトごとにhtmlを用意するんじゃないの?と疑問に思った方、鋭い指摘です。
RTA in JapanのNodeCGでは、ベースとなるhtmlを元として、Webpackの中で複製しています。つまりレイアウトを増やす時はReactのJSXを増やすだけで良いのです。

webpack.config.ts
    const files = globby.sync(`./src/${name}/views/*.tsx`);
    for (const file of files) {
        entry[path.basename(file, '.tsx')] = file;
    }

〜〜〜〜〜中略〜〜〜〜〜〜
            ...Object.keys(entry).map(
                (entryName) =>
                    new HtmlPlugin({
                        filename: `${entryName}.html`,
                        chunks: [entryName],
                        title: entryName,
                        template: `webpack/${name}.html`,
                        typekitId: process.env.TYPEKIT_ID,
                    }),
            ),

5
4
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
5
4