WebSocketとは、WebサーバとWebブラウザの間で双方向通信できるようにする技術仕様のことです。これを使えばネット対戦ゲームをつくることが可能になります。
ネット対戦ゲームをつくる場合、各プレイヤーの位置や状態がわかるようにしておかないとなりません。WebSocketを使えばこれができそうです。ではさっそくやってみましょう。
アクセスすると矩形が座標(10, 10)に表示されます。そして方向キーをおすと移動します。複数のアクセスがあると矩形も複数表示されます。ブラウザを閉じるとしばらくしたのち消えます。動作確認はこちらから ⇒ https://test2.lets-csharp.com/test
いまからつくるのはページにアクセスすると矩形が表示され、上下左右の方向キーをおすとその位置が変化するという簡単なものです。これを応用すればネット対戦ゲームもつくることができそうです。
まずapp.jsというファイルをつくりますが、同じ階層にフォルダとつくりそのなかに(フォルダ名).htmlというhtmlファイルをつくります。実際にこれを https://lets-csharp.com/XXXX で公開しようとするとうまくいきません。そこでサブドメインを使います。
まずindex/index.htmlですが、これはTestというページへのリンクを表示するだけです。
index/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>websocket-Test</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> </head> <body> <div class="container"> <p><a href="./test">Test</a></p> </div> </body> </html> |
404.htmlはtest.html以外のページにアクセスされた場合に「404 not found」を表示させるためのものです。
404/404.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>404 not found</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>404 not found</h1> <p><a href="./">トップページに戻る</a></p> </div> </body> </html> |
今回の主役はtest.htmlです。server_to_client_objectsイベント・データ(後述)を受信したときに矩形を表示させるとともに接続しているユーザーのIDと矩形の座標を表示させます。
test/test.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>websocket-chat</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <!-- C01. Socket.IOクライアントライブラリの読込み --> <script type="text/javascript" src="./socket.io/socket.io.js"></script> </head> <body> <div class="container"> <h1>WebSocket-Test</h1> <p>=====状態=====</p> <div id="objects"></div> <canvas id = "can"></canvas> </div> <script> let can =document.getElementById("can"); can.setAttribute( "width" , 800 ); can.setAttribute( "height" , 500 ); let con = can.getContext("2d"); let key_test = io('/key-test'); //server_to_client_objectsイベント・データを受信したときの処理 key_test.on("server_to_client_objects", function(data){ con.clearRect(0, 0, can.clientWidth, can.clientHeight); // 接続しているユーザーのIDと矩形の座標を表示させるとともに矩形を表示させる $("#objects").text(''); data.value.forEach(object => { let str = `X = ${object.X} Y = ${object.Y} ID = ${object.Id}<br>\n`; $("#objects").append(str); con.fillStyle=object.Color; con.fillRect(object.X, object.Y, 50, 50); }); }); // はじめてアクセスされたらオブジェクトを追加する処理ができるようにする key_test.emit("client_to_server_first", {value : ''}); // キーがおされたり離されたらオブジェクトの状態を変化させる document.onkeydown = function(e){ key_test.emit("client_to_server_keydown", {value : e.keyCode}); } document.onkeyup = function(e){ key_test.emit("client_to_server_keyup", {value : e.keyCode}); } </script> </body> </html> |
app.jsをつくります。まずnpmでsocket.ioをインストールします。
1 2 |
npm init -y npm install socket.io |
つぎにObjectクラスをつくります。IDと座標、色、上下左右に移動できるかどうかが設定できるようにプロパティを定義します。
app.js
1 2 3 4 5 6 7 8 9 10 11 12 |
class TestObject{ constructor(){ this.Id = ''; this.X = 0; this.Y = 0; this.Color = "#000000"; this.IsMoveLeft = false; this.IsMoveTop = false; this.IsMoveRight = false; this.IsMoveDown = false; } } |
アプリケーションが開始されたらmain関数が実行されるようにします。その前に必要なモジュールを読み込みます。そのあとmain関数を定義します。
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// S01. 必要なモジュールを読み込む let http = require('http'); let fs = require('fs'); let url = require('url'); let port = 65007; // ポート競合の場合は値を変更 function main(){ // S02. HTTPサーバを生成する let server = http.createServer(function(req, res) { CreateServerCallback(req, res); }).listen(port); const socket_io = require('socket.io')(server, { cors: { origin: '*', } }); // S03. HTTPサーバにソケットをひも付ける LinkSocketToHttpServer(socket_io); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
// S02. HTTPサーバを生成のコールバック関数 function CreateServerCallback(req, res){ const url_parts = url.parse(req.url); const pathname = url_parts.pathname; if(pathname == '/' || pathname == '/index.html') CreateServerCallback_TopPage(req, res); else if(pathname == '/test') CreateServerCallback_Text(req, res); else CreateServerCallback_404(req, res); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function CreateServerCallback_TopPage(req, res){ res.writeHead(200, {'Content-Type' : 'text/html'}); res.end(fs.readFileSync(__dirname + '/index/index.html', 'utf-8')); } function CreateServerCallback_404(req, res){ res.writeHead(200, {'Content-Type' : 'text/html'}); res.end(fs.readFileSync(__dirname + '/404/404.html', 'utf-8')); } function CreateServerCallback_Text(req, res){ res.writeHead(200, {'Content-Type' : 'text/html'}); res.end(fs.readFileSync(__dirname + '/test/test.html', 'utf-8')); } |
main関数が実行されたらHTTPサーバにソケットをひも付ける処理がおこなわれます。LinkSocketToHttpServer関数として新しい関数を作成していますが、これは将来、他のページを追加していくことも考えられるからです。
そしてLinkSocketToHttpServer_Text関数が実行されたらTestというクラスのインスタンスを生成しています。いまから作成するテストページに関する実装はTestクラスのなかで完結させてしまいます。複数のページをつくる場合、グローバル変数や関数が多くなるのでクラスをつかって変数や関数を閉じ込めてしまおうというわけです。
1 2 3 4 5 6 7 8 |
function LinkSocketToHttpServer(socket_io){ // ページを追加するたび追加していく。いまはひとつだけ LinkSocketToHttpServer_Text(socket_io); } function LinkSocketToHttpServer_Text(socket_io){ new Test(socket_io); } |
Testクラスのコンストラクタでやることはconnectionイベントを受信すること、client_to_server_keydownイベントとclient_to_server_keyupイベントに対してデータを送信すること、タイマーをセットして一定時間おきにserver_to_client_objectsイベントを発生させることです。
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// S04. connectionイベントを受信したときの処理 class Test{ static Objects = []; constructor(socket_io){ let key_test = socket_io.of('/key-test').on('connection', function (socket){ socket.on('client_to_server_first', (data) =>Test.ClientToServerFirst(socket)); socket.on('client_to_server_keydown', (data) => Test.ClientToServerKeydown(socket, data)); socket.on('client_to_server_keyup', (data) => Test.ClientToServerKeyup(socket, data, Test.Objects)); socket.on('disconnect', () => Test.Disconnect(socket)); }); setInterval(() => Test.Interval(key_test), 1000/60); } } |
client_to_server_firstイベントを受信したときの処理を示します。ここでは新しいオブジェクトを生成してObjectの配列 objectsに追加しています。矩形の色は乱数で決定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// S04. ユーザーがアクセスしたときの処理 class Test{ static ClientToServerFirst(socket){ let id = socket.id; // 乱数を使って矩形の色を生成する let r = ( '00' + Math.floor(Math.random()*256)).slice( -2 ); let g = ( '00' + Math.floor(Math.random()*256) ).slice( -2 ); let b = ( '00' + Math.floor(Math.random()*256) ).slice( -2 ); let color = '#' + r + g + b; // 新しいオブジェクトをリストに追加する // 最初に出現させる矩形の座標は(10, 10)とする let object = new TestObject(); object.Id = id; object.X = 10; object.Y = 10; object.Color = color; Test.Objects.push(object); } } |
client_to_server_keydownイベントを受信したときの処理を示します。キーが押されたらその方向のTestObject.IsMove~フラグをセットし、離されたらフラグをクリアします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// S05. client_to_server_keydownイベントとclient_to_server_keyupイベントの処理 class Test{ static ClientToServerKeydown(socket, data){ // IDが同じオブジェクトを探す let id = socket.id; let object = Test.Objects.find(x => x.Id == id); if(object == null) return; // IDが同じオブジェクトが見つかったらフラグをセット if(data.value == 37) // ←キー object.IsMoveLeft = true; if(data.value == 38) // ↑キー object.IsMoveTop = true; if(data.value == 39) // →ー object.IsMoveRight = true; if(data.value == 40) // ↓キー object.IsMoveDown = true; } static ClientToServerKeyup(socket, data){ let id = socket.id; let object = Test.Objects.find(x => x.Id == id); if(object == null) return; if(data.value == 37) object.IsMoveLeft = false; if(data.value == 38) object.IsMoveTop = false; if(data.value == 39) object.IsMoveRight = false; if(data.value == 40) object.IsMoveDown = false; } } |
ユーザーが離脱したらdisconnectイベントが発生するので、対応するオブジェクトを取り除きます。
1 2 3 4 5 6 7 |
// S06. disconnectイベントが発生したら対応するオブジェクトを取り除く class Test{ static Disconnect(socket){ let id = socket.id; Test.Objects = Test.Objects.filter(object => object.Id != id); } } |
1000/60ミリ秒ごとにInterval関数が呼び出されます。このときはTestObject.IsMove~フラグをみて座標を変更させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// S07. タイマーでserver_to_client_objectsイベントが発生させる class Test{ static Interval(key_test){ Test.Objects.forEach(object =>{ if(object.IsMoveLeft) object.X += -2; if(object.IsMoveTop) object.Y += -2; if(object.IsMoveRight) object.X += 2; if(object.IsMoveDown) object.Y += 2; }); key_test.emit('server_to_client_objects', {value : Test.Objects}); } } |
あとは実行するだけです。
1 2 |
let port = 65007; main(); |
レンタルサーバーなので実行したら公開したいディレクトリに.htaccessを設置することで無理やり表示させています。
.htaccess
1 2 3 4 |
<IfModule mod_rewrite.c> RewriteEngine On RewriteRule ^(.*)$ http://localhost:65007/$1 [P,L] </IfModule> |
動作確認はこちらから ⇒ https://test2.lets-csharp.com/test