はじめに
皆さんこんにちは!
Life is Tech!#1 Advent Calendar 2020 9日目の記事を担当いたします。きゃみです!
よかったら最後まで見ていってくださいね〜。
OSCの魅力を伝えたい
さて皆さん、突然ですがOSCって知っていますか?
OSCはOpen Sound Controlの略で、電子楽器やコンピューターなどの機器において音楽演奏データをリアルタイムで共有するための通信プロトコルのことです。(出典: Wikipedia)
本来の用途が音楽演奏データの転送というだけで、実際は転送データの柔軟性は結構高く、Wi-Fiなどの屋内のネットワークを用いていろいろなデータを転送できます。
そんなOSC通信ですが、
困ったことに、
記事が全然ないんですよ!
もう本当にない。そもそも知名度が全くない。
全然Hello, Worldを教えてくれないんですよね。
しかもLife is Tech!メディアアートコースでは最近カリキュラムが変わってp5.jsを使うようになりました。
openFrameworksとか、TouchDesignerはサーバーサイドの知識がなくても簡単にできるんですけど、jsは自分でサーバー立ててsocketの設定してゴニョゴニョ...
ってな感じでとてもバックエンドを触ったことのない自分にはハードルが高かったわけです。
でもOSCってめちゃくちゃ楽しいんですよ!
自宅のWi-Fiを使うだけで、めちゃくちゃ低遅延でPC同士あるいはPCとスマホ等他の端末をつなげて遊べるってめちゃくちゃワクワクしませんか?しませんか。僕はします。
なんとかしてp5.jsでもOSCを使えるようにしたい...。
そしてみんなにOSCの魅力を伝えたい...。
と、そんな思いのもと、簡単にゲーム(的なもの)を作りながら、セットアップから順番にまとめていこうと思います。
右がコントローラーによる操作で動いている様子、左はそのデータを受け取って位置情報や勝ち負け判定を行っている様子
0. プロジェクトの準備
何はともあれ、プロジェクトがないと始まりません。
p5.jsには公式サイトにWeb Editerというサービスがあってサイト上でコードをかけるんですが、今回はsketch.js以外のJavaScriptファイルを扱ったりするのでローカルにダウンロードした方がいいかなと思います。
設定済みのp5.jsのフォルダを作成
いろいろなセットアップの方法があると思いますが、Web Editerでプロジェクトを作ってから直接ダウンロードするのが一番確実で早いのかなと思います。
適当にプロジェクトを作ってDownloadしましょう。
ファイルを別フォルダにまとめる
詳しくは言わないですが、後々ターミナルをいじる時に困るので以下のような感じでファイルを以下のようにまとめておきましょう。(publicの階層にいる他のファイル・フォルダは後ほど出来上がっていきますので、今はなくても気にしないでください。)
これでプロジェクトの準備は完了です。
次はサーバーサイドの環境設定をしていきましょう。
1. サーバーの構築
ここらへんからターミナルが絡んできます。nodeとかnpmとか使うんですが、正直僕はサーバーの知識は皆無なので細かいことはよくわかりません。(断言)
ですが、そんな自分でもできたので、自分なりに細かく説明していきます。
npmとnodeについて
この先ターミナルの中でコマンドを打っていくわけですが、もしnpmやnodeコマンドをinstallしてない場合はそのコマンドを使えるようにするところから始めましょう。
Homebrewが入っている人はbrew経由でダウンロードできるかと思います。
入ってない人はこの辺を見ながらbrewのinstallをしてください。
server.jsファイルを作成
ものの試しに、server.jsという名前のファイルを作成して、以下のコードを実行していきましょう。
console.log('My server is running!');
絵に描いたようなHello, Worldですね。これを実行するためにはターミナルを開いて以下のようにします。
$ cd プロジェクトファイルのパス
$ node server.js
> My server is running!
nodeはJavaScriptを実行するためのコードです。多分。
このserver.jsのなかにサーバー立ち上げ用のコードを書いて、立ち上げるときはnodeで実行するという感じになります。
npmを初期化
package.jsonファイルを作成
$ npm init
とかいて、npmの初期設定に入ります。npmはnodeのpackage managerだそうです。
すると以下文が順番に出てくると思いますので、一つずつ入力していきましょう。
$ package name: (ps4_osc) ps4_osc ##なんでもいい
$ version: (1.0.0) ##Enter押すだけ
$ description: sockets for advent calendar ##なんでもいい
$ entry point: (server.js) ##ここは絶対にserver.js
$ test command: ##Enter押すだけ
$ git repository: ##Enter押すだけ
$ keywords: sockets p5.js node ##なんでもいい
$ author: Kosuke Murakami ##自分の名前
$ license: (ISC) ##Enter押すだけ
ここまでくるとこれでいいですか?的な文が出てくるので、Enterを押します。
これが適切に完了されると、package.jsonというファイルが出来上がるかと思います。
これでこのプロジェクトのinformationが詰まったJSONファイルが出来上がりました。
パッケージをインポート
サーバーを立てるには以下の二つのパッケージが必要なので、npmを使ってインストールしましょう。
$ npm install express --save
$ npm install socket.io --save
expressは通信のポート管理に使って、socket.ioはアプリ間のデータのやりとりに使います。多分。
これで環境構築は完了です。次から本格的にコードを書いていきます。
1.5. OSCの通信の流れ
書いていきますと言いながらOSC通信の流れについて話します。
そんなん知ってるぜ!って人はスッと飛ばしてください。
サーバーサイドを書いたことがある人はイメージしやすいのでしょうか?僕は今までライブラリに頼りながらOSCを使ってたので、いざ自分で通信周りを書くとなるとこの流れを理解するのに苦労しました...。
OSCに必要なもの
そもそもOSC通信をするにあたって、絶対に必要になるものが二つあります。それは、
- IPアドレス
- ポート番号
です。
基本的にIPアドレスは一意に定めないといけませんが、ポート番号は任意でいいことになっています。
それについても含めて、以下で説明していきます。
IPアドレス
IPアドレスとは、そのパソコンがつながっているネットワークに対して割り当てられている固有の番号、いわば住所みたいなものです。
皆さんの中には、お家にポケットWi-Fiやルーターがある人がいるかと思います。
そんな人たちの中に、ふと、
なんでこのWi-Fiは一つしかないのに、複数の人がつなげるんだろう?
と、疑問に思ったことがある人、いるんじゃないでしょうか。
それは、同じWi-FiでもルーターやモデムがWi-FiのIPアドレス、すなわち住所をいろんな場所に振り分け直してくれているからなのです。
通常一つのネットワークには一つのIPアドレスしか使えないので、家族でお家のWi-Fiを使えたり、友達同士でポケットWi-Fiが使えるのは、その機器がIPアドレスを振り直してくれているからということになります。
逆に言えば、振り分けの上限に達するとそれ以上はWi-Fiに繋げなくなるということです。
ポート番号
さっきのIPアドレスが住所だとすると、ポート番号は道路にあたります。
ポート番号によって指定された道路を通って、IPアドレスによって指定された住所まで届ける。そういった流れになっています。
この住所に届けてください!とお願いされることはあっても、この道順でよろしくお願いします!って言われることはないですよね?
なので基本的にはポート番号は任意でいいことになってます。
ただし、ポート番号は一つのアプリケーションの通信に対して一つしか使えないので、一般的に今回のような自分でポート番号を指定するような場合は、他のアプリケーションとのバッティングを避けるためにできるだけ大きい数を使った方が良いという慣習があります。
送受信の流れ
OSC通信の流れは以下のようになっています。
これでだいたいわかると思います。
わからない人はもうちょっとバックエンドに通じてる人に聞いてみてください←
2. server.jsを記述
全貌から書いていきます。
var express = require('express');
// ポートをopen
var app = express();
var server = app.listen(3000);
// publicフォルダを使っていくよってこと?
app.use(express.static('public'));
console.log("My socket server is running");
var socket = require('socket.io');
var io = socket(server);
io.sockets.on('connection', newConnection);
// OSCでつながった時に呼ばれる関数
function newConnection(socket) {
console.log('new connection: ' + socket.id)
// padsやmatchという名前のdataを受信
// 受信したら第二引数の関数がcallbackする
socket.on('pads', controllerMsg);
socket.on('match', matchMsg);
// padsを受信した時に発動
function controllerMsg(data) {
socket.broadcast.emit('pads', data);
}
// matchを受信した時に発動
function matchMsg(data) {
socket.broadcast.emit('match', data);
}
}
受信側のポートを開く
// ポートをopen
var app = express();
var server = app.listen(3000);
ここでポート番号3000のポートを開いています。
受信した時の関数を定義する
// padsを受信した時に発動
function controllerMsg(data) {
socket.broadcast.emit('pads', data);
}
// matchを受信した時に発動
function matchMsg(data) {
socket.broadcast.emit('match', data);
}
あとで説明しますが、sketch.js (p5.jsにおける描画するためのファイル) 内でいろいろな条件下のもと、emitでそれに応じたdataを送信します。その際server.jsでdataを受け取り、受信側にdataを送り直すので、そのための記述です。
サーバー側は、コード量的にはそうでもなかったですね。
実際、server.jsは受け取ったdataを受信側に送り返すだけなので、あまり記述量は多くならないのかなと思います。
次はsketch.jsについて記述していくのですが、送受信を含めた大体の処理がsketch.jsに記述されるのでめちゃくちゃ長くなります。それゆえ全部は説明できないので、OSCに関わる部分だけ説明していこうかなと思います。
3. sketch.jsを記述
setup
function setup() {
...
// IPアドレスに接続
// localhostでもいいが、自分のPC内でしかやりとりできない
// 他のPCと接続する際には、自分か相手か、どちらかのIPアドレスに合わせる必要がある
// また、この時server.jsで設定したポート番号に合わせる
socket = io.connect('http://hogehoge:3000');
// socket = io.connect('http://localhost:3000');
// 受信用のsocketをopen
// 第一引数の名前のデータを受け取ったとき、第二引数の関数がcallbackされる
socket.on('pads', newOperated);
socket.on('match', result);
...
}
主にsetupでOSCの設定を書いていきます。
io.connectでIPアドレスとポート番号を指定して、socket.onでどんな名前のデータを受け取った時にどんな関数を実行するのかの設定をしてあげています。
今回は「相手がコントローラーを操作した」というデータと、「相手が負けた」というデータを取得してそれぞれ関数を定義しています。
コントローラーを操作
// コントローラーを操作した時に発動する関数
// 発動と同時に通信相手に'pads'dataを送信
function controllerOperated(pads) {
var but = [];
for (var i = 0; i < pads.buttons.length; i++) {
var val = pads.buttons[i];
var pressed = val == 1.0;
if (typeof(val) == "object") {
pressed = val.pressed;
val = val.value;
}
but[i] = val;
}
// PS4の○ボタンは配列の1番目
// ゆえに以下のif文で○ボタンをおしたかどうか検知
if (but[1] == 1) {
velocity += lift;
}
...
var axes = pads.axes;
...
let data = {
x: dogPosition.x,
y: dogPosition.y,
}
socket.emit('pads', data);
}
こちらはコントローラーから操作した時に、相手側のPCにデータを送るための関数です。
socket.emitでどんな名前のどんなデータを送るかを設定しています。
いつも通りキーボードからの入力をとってくるだけじゃ味気ないので、試しにPS4のコントローラーとp5を接続してみましょう。
PS4のコントローラーをp5.jsで使えるようにするのはとても簡単です。
var pads = navigator.getGamepads ? navigator.getGamepads() :
(navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
と書くだけです。今回はこれによって取得したコントローラーのデータを引数として、この関数に渡しています。特にライブラリとかいらないんですよね。便利便利。
PCとコントローラーを接続するには、コントローラーのPSボタンとSHAREボタンを同時に長押しして、PC側からBluetooth設定でWireless Controllerを接続してください。
ただし、近くにPS4があるとそっちに引っ張られてしまう可能性があります。その時は直接有線でつないであげましょう。
これでPS4のコントローラーをPCにつなぐことができました。
p5.jsにPS4のコントローラー繋いだこの新しいデバイスのことを、僕はPS5と呼ぶことにします。
…
……
………
(ちなみにSwitchでもできるみたいです。)
敵の接触判定
// 自分がハチと接触した時に発動する関数
// 発動と同時に通信相手に'match'dataを送信
function youLose() {
isLose = true;
let data = {
match: "is Lose"
};
socket.emit('match', data);
}
これはゲームっぽくしたかったので、イラスト屋さんから拝借したハチに当たり判定をつけて衝突したらこっち側負けましたよ、っていうデータを送るための関数です。
本質的にはさっきの関数と変わりません。でもコードが短いのでもしかしたらこっちの方がわかりやすいかもしれませんね。
データの受け渡し
// 通信相手のコンピューターがコントローラーを操作した時に発動する関数
function newOperated(data) {
rabbitPosition = data;
}
// 通信相手のコンピューターがハチと接触した時に発動する関数
function result(data) {
if (data.match == "is Lose") {
isWin = true;
}
}
この二つの関数はsetupの時にも出てきましたね。
データを受け取った時に発動する関数です。
newOperatedの方では、向こうのPCから受けとった画像の座標を受け取って、対戦相手の画像をdrawで表示しています。
この関数の中で画像を表示するのではなく、draw関数内で表示してあげるのがポイントです。
この関数は逐次的に呼ばれるので、表示のコードを書いても一瞬しか表示されません。
resultでは対戦結果の処理をしています。
そもそもmatchというデータでは"is Lose"しか送らないので、このif文は冗長かもしれませんが念のため書いています。
isWinがtrueになると勝ちの画像が、isLoseがtrueになると負けの画像が表示される仕組みになっています。
draw関数
function draw() {
...
// PS4のコントローラーから操作を取得するための文
var pads = navigator.getGamepads ? navigator.getGamepads() :
(navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
pads = pads[0];
if (pads) {
controllerOperated(pads);
}
...
// 負け判定を受け取ったら負け画像を表示
if (isLose == true) {
fill(0, 100);
noStroke();
rectMode(CENTER);
rect(dogPosition.x+100,0,width,height);
image(loseImg,dogPosition.x+100,0,height,height);
}
// 勝ち判定を受け取ったら勝ち画像を表示
if (isWin == true) {
fill(0, 100);
noStroke();
rectMode(CENTER);
rect(dogPosition.x+100,0,width,height);
image(winImg,dogPosition.x+100,0,height,height);
}
}
drawは今までの関数で受け取ったデータをもとに表示するだけです。
相手側のコード
相手側のコードはdogとrabbitを入れ替えるだけなので割愛します。
出来上がったものがこちら
ここまで書けたあなたは、ターミナルで
$ node server.js
と実行してから、localhostにつないでみましょう。自分の書いたコードが広がっているのではないでしょうか?
僕は今回、素材は全部イラスト屋さんから拝借しました。
右のPCはコントローラーに接続されていて、左のPCは右側から画像の位置勝ち負け判定のデータを受け取っているのでwin or loseもリアルタイムに判定できます。
最後に
今回はp5.jsにOSC通信を使って、簡単な通信ゲームを作りました。
今回はPCとPCをつないで行きましたが、OSCは通信プロトコルに過ぎないのでiPhoneとPCを繋ぐこともできます。難しい知識なく異なるデバイスのデータがやりとりできるってすごくワクワクしませんか?しませんか。僕はします。
PCとiPhoneをOSCでつないで作った別の作品もこちらにありますので、よかったら見てってください。
TouchDesignerでガラスを透過したようなステルス迷彩を作りたい!
明日はバズの天才、マッチョさんが記事を書いてくれるそうです。
よかったらそちらもご覧ください。
少しでもOSCの魅力が伝われば嬉しいです!最後まで見ていただいてありがとうございました!
コード
1P側のコード
https://github.com/kyamisuke/adCal_2020
参考文献
Introduction to Node: The Coding Train チャンネル
p5.jsでPS4とSwitchのコントローラ(Joy-Con)を接続してブラウザ上で操作する