退職者を送る技術 - Twilio と Socket.IO で作る電話マルチプレイシステムの小ネタ

この記事は [DeNA Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dena) の12/19(木)の記事です。

![taisyoku_hanataba_young_man.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5542/45e71a7b-0592-414d-ca52-d21ab3409952.png)

会社で代々行われている文化である、非テック縛りの、謎 LT 大会のタイミングがちょうど退職者の最終出社日にあたったため、寄せ書きがわりに「不特定多数のマルチプレイゲームシステム」を開発しました。
（LT 会ではテックな話はせず、ここですることにしました）

開発したシステムはクイズとシューティングゲームの二種類ですが、表示が違うだけで技術は同じです。

なお今回ご紹介する内容は忘年会や新年会、または春の歓送迎会など、イベント向きのシステムであったりもしますので、この記事を小ネタとしてストックとしてご活用いただけたらとなんとなく嬉しいです。:relaxed:

# 使用したライブラリ・技術 :computer:

お手軽な構成です。

- Pixi.js (神ウェブグラフィックスライブラリ)
- Socket.IO (リアルタイムマルチユーザー通信)
- Express.js (ウェブアプリケーションフレームワーク)
- pm2 (プロセス管理)
- nginx
- Cent OS
- Twilio (電話 API の SaaS)

この中でややユニークなものが電話 API の Twilio ですので、今回はその紹介をいたします。

# 電話 API Twilio :telephone: 

[Twilio](https://www.twilio.com/) は電話番号を購入できて、その電話番号からフックしてプログラムを実行させたり、返答を返すなどができるサービスです。導入が非常に簡単で、[ケーススタディ](https://customers.twilio.com)も豊富に用意されていて、様々なサービスに活用できそうと感じます。

今回はなんとなくサーバーサイドで JavaScript (Express.js) を使用していますが、Ruby / PHP / Go / C# / Python などにも対応しています。

# マルチプレイ部分の実装 :pencil2: 

電話番号を Twilio のコンソールで購入し、以下の要領で実装できます。

## Twilio の実装

### ① POST の受け口を作ります。

好きな言語、フレームワークなどで POST 先を作ります。

```
npm i --save express
```

```javascript:server.js
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000

app.post('/voice', (request, response) => {}

http.listen(PORT, console.log(`App listening on port ${PORT}.`))
```

### ② Twilio のコンソール上で POST 先を指定します。

[Active Numbers](https://www.twilio.com/console/phone-numbers/incoming) ページ → 購入した電話番号の詳細ページ を表示して、`A CALL COMES IN` に ① の POST 先を指定します。

![capture - A CALL COMES IN](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5542/aa0927e4-9a86-2394-5f41-18eee0d6f5cb.png)

モザイクの中には https なドメインが入ってます。(http でもいけた)

### ③ Twilio SDK を使用して必要な機能を実装します。

```
npm i --save twilio
```

`VoiceResponse.prototype.say()` メソッドを使用すると、指定した文字列の音声を鳴らすことができます。日本語も対応しています。

```javascript:server.js
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000

app.use(express.json()) |
app.use(express.urlencoded({ extended: true }))
const VoiceResponse = require('twilio').twiml.VoiceResponse

app.post('/voice', (request, response) => {
  const twiml = new VoiceResponse()
  twiml.say({ language: 'ja-JP', voice: 'Polly.Mizuki' }, '入力してください。')

  // gather(request)
  
  response.type('text/xml')
  response.send(twiml.toString())
}

http.listen(PORT, console.log(`App listening on port ${PORT}.`))
```

`VoiceResponse.prototype.gather()` メソッドを使用すると、キーパッドの入力を受け付けることができます。

```javascript:server.js
function gather(request) {
  if (request.body.Digits) {
    console.log(request.body.Digits)
  }
  const gatherNode = twiml.gather({ numDigits: 1 })
  gatherNode.pause({ length: 10 })
  twiml.redirect('/voice')
}
```

これで指定の電話番号に電話をかけると、音声が流れ、キーパッドを入力を受け付け、サーバーログで値が受け取れるようになりました。

## Socket.IO の実装

あとはフロントエンドに反映させるだけです。
リアルタイムなマルチプレイアプリにするので、[Socket.IO](https://socket.io) を使用します。

### ④ キーパッドの値を Socket.IO に流します。

Socket.IO を使って、gather 時に `emit` するようにします。

```terminal
npm i --save socket.io
```

```javascript:server.js
const http = require('http').Server(app)
const io = require('socket.io')(http)
```

```javascript:server.js
if (request.body.Digits) {
  io.emit('message', request.body.Digits)
}
```

### ⑤ HTML 側で Socket.IO を受けるようにします。

HTML、JavaScript などフロントエンドの静的ファイルを読めるようにして、

```javascript:server.js
app.use(express.static(__dirname + '/htdocs'))
```

[CDN - Socket.IO](https://cdnjs.com/libraries/socket.io) のライブラリを使用して、

```html:index.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
```

`emit` された値をフロントエンド側で受け取ります。

```javascript:index.js
var socket = io()
socket.on('message', msg => {
  console.log(msg)
}
```

あとは受け取った値をこねくり回して完成です。

![shooting.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5542/77cb08db-2652-9ed6-5de7-0c73686ab778.gif)

デバッグ機能も用意して、ボスキャラからも必殺の連続攻撃こと会場の盛り上がり次第で「最後の最後で理不尽なことをするボスキャラこと退職者」な裏機能もつけて、イベントの時間を迎え、真っ白に燃え尽きました。

![bomb.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5542/8a1426b8-a684-2673-4138-d5b5599fd730.gif)

図でまとめるとこんな感じです。

![stacks.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5542/99f6fc69-90fd-0959-2894-8bde542f3f7e.png)

会場にいるみんなで同じ電話番号に電話して、自機の移動・攻撃をキーパッドで操作する仕組みです。QR コードで Web ページを開くのと違って、音声がプレイヤーにフィードバックされるので、音声が面白さに繋げられるとよりアイデアの精度が高くなりそうだなと感じました。

# 小話 :speak_no_evil: 

おじさんになると話が長くなるものです。(元々長い)

## なぜシューティングか

シューティングというか、退職者が「Undertale」というゲームが好きなので、「Undertale」みのある何かにしようとしました。

## なぜ手書き絵か

その日の前後ぐらいに Clip Studio Paint (iPhone 版) というアプリが出たというニュースが Twitter で賑わっていたのと、深夜に漫画を読みながらこのシステムを作っていて、漫画テイストにしたくなったためです。電車で立っててもアセットを作ることができるので、すごいです。ただ、手書きじゃなくてドット絵の方が Undertale らしさが出そうなので反省です。

## 元ネタ

今回のアイデアは先日参加した JavaScript 開発者カンファレンス「JSConf」にて出会った Twilio 社の Samuel Agnew さんの発表「[PLAYING POKÉMON TOGETHER WITH NODE.JS](https://jsconf.jp/2019/talk/samuel-agnew)」をヒントにし、技術調査、社内へのフィードバックを兼ねてつくりました。

## ついでに今年の振り返り

春に参加した「WWDC19」でもマルチプレイの AR ゲームがアクティビティにあったり(当日書いたレポート: [day1](https://design.dena.com/report/wwdc19-day1/), [2](https://design.dena.com/report/wwdc19-day2/), [3](https://design.dena.com/report/wwdc19-day3/), [4](https://design.dena.com/report/wwdc19-day4/), [5](https://design.dena.com/report/wwdc19-day5/))、別の社内勉強会でも[双方向性のあるシステム](https://qiita.com/kengo92i/items/b1362ce1ab14180ff4bd)が導入されたり、昨日の [Frokan x UIT #2](https://frokan.connpass.com/event/155128/) でも LINE 社の方の提供によるマルチユーザーシステムが入ったり、会社のサービスでもマルチプレイ・双方向性の仕組みは多く提供されていたり。何かと今年はマルチプレイ・双方向性が気になった一年でした。

また、勉強会やカンファレンスは、新しい技術やエンジニアとしての考え方を知るだけでなく、アイデアや体験も学ぶことができることに気づいた 2019 年でした。

# 謝辞 :pray:

退職者本人に事前に申し伝え、素材としての利用を快く承諾していただいたおかげと、退職者本人のキャラのおかげで大いに盛り上がった会でした。本人の弁で、「僕 CC0 なので大丈夫ですよあはははは」とのことで、ありがとうございました。大変お疲れ様でした。

それでは、良いお年を。