【Socket.IO】PC50台を無線でつなげてアートを作ってみた

この記事は [福島高専 Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nitfc) 25日目（最終日）の記事です。

# 1. はじめに
こんにちは、高専4年のAkkunlabです。主にプログラミングや電子工作などをしています。

[去年のAdvent Calendar](https://qiita.com/advent-calendar/2020/nitfc)では「[ちょっとすごいと思わせるデジタルアートを作ってみた](https://qiita.com/Akkunlab/items/581f17a90f35b1879adb)」というタイトルで、**インタラクティブなLEDアート**について記事を書きました。

今回はその進化版として**50台のPCを使用したデジタルアート**を制作したので、記事にしていきたいと思います。
読みにくいところがあるかもしれませんが、最後まで読んでいただけたら幸いです。

# 目次
[1. はじめに](#1-はじめに)
[2. 制作したアート](#2-制作したアート)
[3. システム構成](#3-システム構成)
[4. Server側プログラム](#4-server側プログラム)
[5. Client側プログラム](#5-client側プログラム)
[6. 完成した作品](#6-完成した作品)
[7. 開発環境](#7-開発環境)
[8. おわりに](#8-おわりに)
[9. 参考](#9-参考)

# 2. 制作したアート
今回制作したものは、複数台のPCやスマホを制御することで一つの大きな**ディスプレイ**として表現できるアートです。

この作品は、Web上で動作するアプリケーションであり、簡単にディスプレイを**追加・制御**することができます。
有線接続でマルチディスプレイをする感覚と同じように、**複製**したり、**拡張**することができます。（今のところは、一部の映像のみ共有できます）

従来のマルチディスプレイと異なるのは、無線で接続しているということです。この通信には**Socket.IO**を使用しています。
無線接続のため、少し遅延が生じる場合があります。そのときは、制御画面よりデバイス別に制御することで全体のズレを小さくすることが可能です。

下の写真が、完成したアートです。
![Art (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/857712/2aacf874-c3c2-4160-06d0-374b9e207445.png)
![Art (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/857712/429216aa-18f6-9f0d-7120-dfae42cd690c.png)
![Art (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/857712/4e1b739d-3512-6f97-6e5a-8ae70168d81a.png)

# 3. システム構成
今回のアートを作るにあたって、システムの構成は下の図のようにします。

① **制御PC**から**Server**に表示命令を送信。
② **Server**から**各デバイス**へ表示命令を送信。

表示命令を送信する前に、あらかじめ全てのデバイス時刻を同期させます。表示命令には「表示を実行する時刻」が含まれており、その時刻になると各デバイスで表示するようにしています。

![Art (4).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/857712/07fdd9de-7653-f286-fb2e-6144e2a55cb8.jpeg)

また、以下のように同じページで表示（入室）と制御（管理）を簡単に切り替えられるようにしています。
![Art (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/857712/d5c1089a-c12d-f811-4f00-5ba05d5e9f0b.png)

# 4. Server側プログラム
今回使用したServer側プログラムの一部です。Server側には**Node.js**を使用しており、ServerとClient間の通信には**Socket.IO**を使用しています。
はじめに、入室処理です。

```javascript:server.js
  // 入室
  socket.on('join', data => {
    eventName = data.name;
    const user = new User(socket.id, data.displayName); // インスタンスを作成

    if (!eventsList[eventName]) eventsList[eventName] = {}; // Objectがない場合はObjectを作成
    if (eventsList[eventName].managerId) {
      events.to(eventsList[eventName].managerId).emit('mDataAdd', user); // managerにデータを送信
    }

    eventsList[eventName][user.myId] = user; // userを追加
  });

  // 管理者入室
  socket.on('mJoin', data => {
    eventName = data.name;
    const user = new User(socket.id, data.displayName); // インスタンスを作成

    if (!eventsList[eventName]) eventsList[eventName] = {}; // Objectがない場合はObjectを作成

    events.to(socket.id).emit('mData', eventsList[eventName]); // managerにデータを送信

    eventsList[eventName][user.myId] = user; // userを追加
    eventsList[eventName].managerId = user.myId; // managerを追加
  });
```

表示側の入室イベントを受信すると、Userインスタンスを作成します。Userクラスにはid、名前、作成日のプロパティがあります。
また、入室する部屋を管理する`eventsList`に`user`を追加します。

管理側の入室では、表示側と同様にUserインスタンスを作成します。そして、`eventsList`に管理者として追加します。

次に、制御側の表示命令及び管理についての処理です。

```javascript:server.js
  // 管理システム変更
  socket.on('MSChange', data => {
    
    if (data.id === 'ms_change_unit_data') {
      eventsList[eventName][data.value.id].myName = data.value.number; // 名前を更新
      events.to(data.value.id).emit('MSChange', data); // 送信
    } else {
      data.time = Date.now() + 5e3;
      socket.broadcast.emit('MSChange', data); // 送信
    }
  });
```

管理システムの変更イベントを受信すると、その種類によってデータを表示側に送信します。

最後に、退出処理です。

```javascript:server.js
  // 退出
  socket.on('disconnect', () => {
    if (!eventsList[eventName]) return; // 入室0人の場合
    if (!(socket.id in eventsList[eventName])) return; // 未入室の場合
    
    // managerの場合は削除
    if (eventsList[eventName].managerId) {

      events.to(eventsList[eventName].managerId).emit('mDataDelete', eventsList[eventName][socket.id]); // managerにデータを送信

      if (eventsList[eventName].managerId === socket.id) {
        delete eventsList[eventName].managerId;
      }
    }

    delete eventsList[eventName][socket.id]; // userを削除
  });
```

退出イベントを受信すると、`eventsList`からuserを削除し、管理者に送信します。

# 5. Client側プログラム
今回使用したClient側プログラムの一部です。Client側は表示側、管理側どちらも一つのプログラムで動作しています。
また、ちょっとした演出は[**Three.js**](https://threejs.org/)を使用しています。Three.jsを使用したプログラムは以前にいくつか作成していますので[こちら](https://github.com/Akkunlab/three.js-project)をご覧ください。

主なイベント処理です。

```javascript:server.js
// 管理者Data
socket.on('mData', data => {
  mData = data;
  objects.createUnits(mData); // データを表示
});

// 管理システム変更
socket.on('MSChange', data => {
  // 動画を表示・非表示
});
```

`mData`イベントを受信すると、データを更新し管理画面を再構築します。
`MSChange`イベントを受信すると、その内容に従って動画を表示・非表示させます。

より詳しく知りたい方は、コードをGithubにあげていますので[こちら](https://github.com/Akkunlab/YouSync)をご覧ください。

# 6. 完成した作品
完成した管理（制御）システムです。
各デバイスが登録した名前が一覧で表示されています。**クリック**することで個別に制御することが可能です。
上の水色のボタンは共有する動画を読み込むものです。
![Art (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/857712/2c832843-16fb-02c4-b3d9-394b652e4d9a.png)

完成したアートの動画です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">先日、ディジタルアートイベントを開催しました！<br><br>PC約50台を同時に使用したアートです。過去最高の作品になったと思います🎄<br><br>参加していただいた皆様、本当にありがとうございました！！ <a href="https://t.co/GUrzj6Yxlq">pic.twitter.com/GUrzj6Yxlq</a></p>&mdash; あつし (@atsushi_nit) <a href="https://twitter.com/atsushi_nit/status/1474721540126408711?ref_src=twsrc%5Etfw">December 25, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 7. 開発環境
- Windows10
- Socket.IO 4.0.0
- Three.js r131

# 8. おわりに
今回は、**複数ディスプレイ**を使用したアートを制作しました。
準備を手伝っていただいた皆様、本当にありがとうございました！
**過去最高**のデジタルアート作品を完成させることができました！！

最後まで読んでいただき、ありがとうございました！

# 9. 参考
- [Socket.IO開発時に役立つツール4選とroom、namespaceライブラリの使い方](https://atmarkit.itmedia.co.jp/ait/articles/1607/01/news027.html)
- [第1回　NTPとWindows時刻同期サービス](https://atmarkit.itmedia.co.jp/ait/articles/1203/15/news121.html)
- [最新版で学ぶThree.js入門
手軽にWebGLを扱える3Dライブラリ](https://ics.media/entry/14771/)