Upgrade to Pro — share decks privately, control downloads, hide ads and more …

node+websocket+enchant.js で 2時間でピグみたいなチャットゲーを作る!

node+websocket+enchant.js で 2時間でピグみたいなチャットゲーを作る!

2012/2/11のNSEG勉強会合宿で行ったハンズオンの内容です。
サーバサイド、クライアントサイド共にjavascriptで、昔の2D RPGのような画面でチャットを行うことができるものを、ハンズオンで作るものです。

SATOH Kiyoshi

March 16, 2022
Tweet

More Decks by SATOH Kiyoshi

Other Decks in Technology

Transcript

  1. Node • サーバサイド javascript • イベントドリブン • シングルスレッド • C10K(1

    万クライアント ) 問題に対応 • javascript は遅そう? → javascript は今や遅くない
  2. websocket • はやりの HTML5 な技術 • http 上で VPN 張るイメージ

    • socket.io というフレームワークが超優れもの → websocket 非対応のクライアント上でも同 じ API で通信できる
  3. 手で入れる場合 自分が Ubuntu-10.04-LTS での例 「 libssl-dev 」と npm のインストールで必要な「 curl

    」を入れとく > sudo aptitude install libssl-dev curl node.js 本体のインストール > wget http://nodejs.org/dist/v0.6.10/node-v0.6.10.tar.gz > tar -zxf node-v0.4.8.tar.gz > cd node-v0.4.8.tar.gz > ./configure > make > sudo make install
  4. Windows の方 僕のノートにアカウントと Cloud9IDE を準備してあります Cloud9IDE とは → node で動く

    javascript で書かれた統合開発環境  ブラウザ上でばしばし動く   Cloud9 という同じ名前で PaaS もあるよ   http://c9.io/
  5. node でチャットを作る node で簡単なチャットを作ることを目標に進めます • まず適当に node とかフォルダ作る • サンプルファイルを落として参照ようとして展開する

    • 出来上がりサンプルは ex? という名前で作ってあります • 同じファイルをどんどん追記修正していく方針でいきます • git とかの利用は各自適当に
  6. コンソールに "Hello World!" を表示 コンソールに表示するにはほんとに 1 行だけ。 これくらいなら include とか

    require とか全く不要 --- ex1.js console.log("Hello World!"); --- enchat.js というファイルをエディタで作ります Cloud9 IDE の人は File->new->JavaScrip file->"enchat.js"
  7. 実行するには > node enchat.js Cloud9 IDE では、上のボタン「 run 」押すと configuration

    のウィンドウが開く。 Name:chat JavaScript file:enchat.js となってりゃ OK なのでこれで「 Run 」押す。 下の「 Output 」に Hello World! が表示される!
  8. http サーバを作って "Hello Wold!" 次は http サーバを作ってブラウザ上に Hello World します。

    基本的には http のライブラリを呼んで設定をしてやるだけ。 --- ex2.js var sys = require( 'sys' ); var HTTP = require( 'http' ); var server = http.createServer( function http_createServer( request, response ) { response.writeHead( 200, { 'content-type': 'text/plain' } ); response.write( "Hello World!" ); response.end(); } ).listen(20005); console.log("Server started."); --- http://192.168.x.x:xxxx を開いてみます。
  9. express を使ってもっとお手軽に express というフレームワークを使うともっと簡単に。 まず npm という node.js のパッケージマネージャを使って「 express

    」 をインストール。 > npm install express これで express がインストールされます。 --- ex3.js var app = require( 'express' ).createServer(); app.get( '/', function( req, res ) { res.send( 'Hello World!' ); }); app.listen(20005); console.log("Server started."); --- こりゃ簡潔!
  10. express で Web サーバを作ってみる express を使うとものすごく簡単に Web サーバを作ることが出来ます。 実は express

    が全部やってくれるので、公開するフォルダをどこにする かを設定するだけ。 --- ex4.js var express = require( 'express' ); var app = express.createServer(); app.configure( function() { app.use( express.static( __dirname + '/public' ) ); }); app.listen( 300x ); console.log( "Server started." ); --- ほぼなんもしてないですね。 "public" というフォルダを作り、その下に適当に "index.html" とかを 作ってアクセスしてみましょう。
  11. websocket でメッセージを送って表示 ここまではつまらなかったですがやっと本題の websocket に入ります。 node では簡単に websocket 等を使ったリアルタイム通信を行うため の

    socket.io というフレームワークが利用できます。 これを使ってクライアントにメッセージを送ってみます。 まずは WebSocket を使うためのパッケージ「 socket.io 」をインストー ルします。 > npm install socket.io
  12. websocket でメッセージを送って表示 (2) 今回はサーバ側とクライアント側どちらも、また最初に開く HTML も必要になります。 html とクライアント用 js は

    public 以下に index.html と chat.js などの名前で作成。 --- ex5.html message: <div id="message"></div> <script type="text/javascript" src="ex5c.js"></script> --- ex5c.js socket.on("connect", function() { socket.emit("message", "Hello server!"); }); socket.on("message", function(text) { $("#message").html(text); }); --- ex5.js io.sockets.on("connection", function(socket) { console.log("connect new client."); socket.emit("message", "Hello client!"); // 繋がったらとりあえず送る // こちらがメッセージを受けた時の処理 socket.on("message", function(text) { console.log("message:" + text); }); }); ---
  13. websocket でメッセージを送って表示 (4) クライアントも似ていますが、接続されることが無いため socket.on("connect", function() { … に接続が出来た時の記述を socket.on("message"

    function(text) { … にサーバと同じく「 message 」というタグでメッセージを受けた時の動きを記述します。 socket.emit("message", text) で「 message 」というタグを付けて text の内容を送信してします。 $("#message").html(text); でクライアントでメッセージを受け取ったら jquery を利用して中身を書き換えています。 ブラウザに「 Hello client! 」が表示されサーバコンソールに「 Hello server! 」が表示さ れます。 また、複数の接続も可能なことも確認してみてください。
  14. broadcast を使って全員と通信 このままだとサーバとクライアントが 1 体 1 でしか通信できないので複 数の接続への送信を行います。 そのためには broadcast

    を使います。 --- ex6.html message: <div id="message"></div> <input id="message_text" type="text"> <input id="message_button" type="button" value="send"> <script type="text/javascript" src="ex6c.js"></script> --- ex6c.js socket.on("message", function(text) { $("#message").html(text); }); $("#message_button").click(function() { var message = $("#message_text").val(); socket.emit("message", message); });
  15. broadcast を使って全員と通信 (2) --- ex6.js io.sockets.on("connection", function(socket) { console.log("connect new

    client."); socket.emit("message", "Hello client!"); // 繋がったらとりあえず送る socket.broadcast.emit("message", "new client login!"); // 全員に知らせる // こちらがメッセージを受けた時の処理 socket.on("message", function(text) { console.log("message:" + text); socket.broadcast.emit("message", text); }); // 切断した時の処理 socket.on("disconnect", function() { console.log("disconnect."); socket.broadcast.emit("message", "client logout."); }); }); --- broadcast はたぶんすぐわかるでしょう。ついでに切断した時の処理と $("#message_button").click(function() { … で jquery を使って送信ボタンを押したときに emit 掛けるように追加しています。 ☆ 出来た人は、自分の発言も表示されるように修正してみましょう。
  16. ログイン時に名前を送って表示 (2) --- ex7.js // 名前を送ってきた時の処理 var login_name = "unknown";

    socket.on("name", function(text) { console.log("name: " + text); login_name = text; socket.broadcast.emit("name", login_name); }); // こちらがメッセージを受けた時の処理 socket.on("message", function(text) { console.log("message:" + text); socket.broadcast.emit("message", login_name + ":" + text); }); --- 「 name 」のタグが付いたときは、クライアントの名前 login_name を変更します。 function の 中 の 変 数 な の で す が 、 connect 毎 に こ の 関 数 は 作 ら れ て 、 中 の login_name は個別に保持されます。 つまりクロージャとして使われています。 ☆ 自分が発言したものも表示されて色分けてわかるようにしたり、切断時に誰が切断し たか表示してみましょう。
  17. enchant.js の使い方 次は enchant.js の RPG デモを改造して簡単に使 い方を覚えます。 • ローカルで開いてデモを動かす

    • 名前やフキダシの表示 • 他のキャラクタの表示 • 他のキャラクタをクリックを起点に移動
  18. まずデモを動かしてみる enchant.js のアーカイブを enchantjs とかに展開してローカルのファイルの enchantjs/examples/rpg/index.html を開いてみます。 キーパッドを押すと動き回れることを確認して下さい。 ゲームメイン部分ソースは enchantjs/examples/rpg/game.js

    で、データ部分が大きいですが、それを除けば 80 行ほどとコンパクトです。 このデモのソース自体の解説についてはこちらが詳しいのでおすすめ enchant.js のサンプルコードを解読する( RPG 編その 1 ) | IDEA*IDEA http://www.ideaxidea.com/archives/2011/04/enchant_rgb_undocumented.html
  19. 例によって Hello World から --- ex8.js var label = new

    Label( "Hello World!" ); label.color = "black"; label.x = 7 * 16 - 8; label.y = 10 * 16; --- stage.addChild(label); --- まず文字列のオブジェクトを作って色や表示位置を指定します。 作ったオブジェクトを表示用のレイヤーに追加します。
  20. 名前とフキダシの表示 addChild した順番に重ね合わせされるため、レイヤーを分ける必要があるの で、 chara_group と message_group 作ります。 CSS で見栄えを修正するためラベルに

    class の属性を追加してやります。 index.html に CSS 追記し角丸半透明でフキダシが表示されるようにします。 --- ex9.html .message{ text-align:center; border-radius:10px; -webkit-border-radius:10px; -moz-border-radius:10px; filter: alpha(opacity=75); -moz-opacity:0.75; opacity:0.75; } .login_name{ text-align:center; }
  21. 名前とフキダシの表示 (2) --- ex9.js var chara_group = new Group(); var

    message_group = new Group(); --- // 名前の表示 player.login_name = new Label( " えぬせぐ " ); player.login_name._element.setAttribute( 'class', 'login_name' ); … 略 // チャット内容の表示 player.message = new Label( "Hello World!" ); player.message._element.setAttribute( 'class', 'message' ); … 略 // キャラクタ表示レイヤーとメッセージ表示レイヤーに追加 chara_group.addChild(player); chara_group.addChild(player.login_name); message_group.addChild(player.message); --- stage.addChild(chara_group); stage.addChild(foregroundMap); stage.addChild(message_group); ---
  22. 他キャラクタの表示 他の人を表示するため、 player の表示部分をそのまま使い、オブジェク トを作ってレイヤーに追加してやります。 イメージを変更するために、 image2.draw の引数を変更して、表示に 使うキャラクタを変えています。 ---

    ex11.js var other_player = new Sprite(32, 32); other_player.x = 7 * 16 - 8; other_player.y = 11 * 16; var image2 = new Surface(96, 128); image2.draw(game.assets['chara0.gif'], 100, 0, 96, 128, 0, 0, 96, 128); other_player.image = image2; // 名前の表示 other_player.login_name = new Label( " はくば " ); … 略 // キャラクタ表示レイヤーとメッセージ表示レイヤーに追加 chara_group.addChild(other_player); chara_group.addChild(other_player.login_name); message_group.addChild(other_player.message); ---
  23. 他キャラクタの移動 (クリックでどっかへ移動) これだけではなにも動きが無くて動かそうとした時ちゃんと動くか不安です。 このキャラクタがクリックされたら移動するようにしてみます。 EventListener に 'touchend' (クリックが離されたら)で動作を追加します。 --- ex12.js

    // キャラクタが押されたら右へ移動していく other_player.addEventListener( 'touchend', function() { this.x += 4; this.login_name.x = this.x - 35; this.message.x = this.x - 30; }); --- キャラをクリックするたびに右へ移動していきます。
  24. メッセージの入力と変更 チャットさせるためには文字入力をさせなければいけません。 enchant.js で文字入力させる場合、 prompt を使わないと無理だそうで す。 なので、フキダシをクリックされたら prompt を出すようにしてみます。

    ついでにフキダシの内容も変更してみましょう。 --- ex13.js // メッセージの入力とフキダシ内容変更 player.message.addEventListener('touchend',function(e) { var message = prompt( 'input message:', 'hi!' ); if ( message != '' ) { player.message.text = message; } }); --- フキダシを押されたら prompt を表示して、中身が空でなければフキダシ の中身を変更します。
  25. 通信部分と enchant の合体 これまで作った node のチャット部分と enchant.js の部分とを合体していきます。 まず public

    に置いて素の enchant デモ動くこと確認します。 enchant.js と plugin/ui.enchant.js と images にある 4 つの画像ファイル apad.png pad.png chara0.gif map1.gif を public にコピーします。 今まで enchant.js で編集していた index.html と game.js を public のフォルダ に入れます。 index.html で enchant.js 等の読み込み先を変更します。 --- ex14.html <script type="text/javascript" src="enchant.js"></script> <script type="text/javascript" src="ui.enchant.js"></script> ---
  26. node の通信部分を enchant.js に組み込む html に socket.io を読み込むための行を追加します。 クライアントの先頭でチャットを作った時の接続部分を組み込みます。 ---

    ex15.html <script type="text/javascript" src="/socket.io/socket.io.js"></script> --- ex15c.js name = prompt("Input name:"); var port = 20005; var socket = io.connect("/", { port: port }); socket.on("connect", function() { socket.emit("name", name); }); --- // 名前の表示 player.login_name = new Label( name ); --- サーバのコンソールに名前が送られてきているのを確認して下さい。
  27. メッセージをサーバに送ることと 他キャラクタへのフキダシ表示テスト フキダシで入力する処理のところに socket.emit も追加します。 また、ソケットに message が来たら other_player のフキダシを修正

    するようにします。 --- ex16c.js // メッセージの入力とフキダシ内容変更 player.message.addEventListener('touchend',function(e) { var message = prompt( 'input message:', 'hi!' ); if ( message != '' ) { player.message.text = message; socket.emit("message", message); } }); --- // サーバからメッセージが来たら他のキャラクタのフキダシに表示 socket.on("message", function(text) { other_player.message.text = text; }); --- サーバが送ってきたメッセージ「 Hello Client! 」が表示されて、こちらか ら送ったメッセージがコンソールに表示されてることを確認して下さい。
  28. name が来たら新たなキャラクターを表示 このままだと、他のユーザキャラクタが 1 体しか表示されないため、名 前が送られてきたら新たなユーザがログインしたと認識して、新たな キャラクターを表示するようにします。 そこで、今まで他のユーザを表示していたところを、 name が来たら動く

    クロージャを作ってくくってやります。 --- ex17c.js // 他のユーザのログイン socket.on("name", function(text) { var login_name = text; … 略 // 名前の表示 other_player.login_name = new Label( login_name ); … 略 }); --- 複数の接続を行うと、それ毎にキャラクターが新しくその名前で表示さ れているのがわかります。 また、メッセージを書くと変更されることも確認して下さい。
  29. ユーザ毎の処理を行えるようにする 今は誰からのメッセージかを考慮せずにそのまま表示するので、すべてのクラ イアントのフキダシがかわってしまいます。そこで、誰からのメッセージなのかを タグに一緒に埋め込んで送って判別出来るようにします。 今まではタグを単に「 message 」としていましたが「 message: 【ログイン 名】」とするようにします。

    --- ex18.js // こちらがメッセージを受けた時の処理 socket.on("message", function(text) { console.log("message:" + login_name + " " + text); socket.broadcast.emit("message:" + login_name, text); }); --- ex18c.js // サーバからこのユーザのメッセージが来たらフキダシに表示 socket.on("message:" + login_name, function(text) { other_player.message.text = text; }); --- ユーザ毎に違うメッセージが表示されていることを確認して下さい。 キャラをクリックして少しずらしてやると見やすいです。
  30. position を送って移動 移動できるように修正し、名前や位置などを player にまとめます。 --- ex19.js var player =

    { login_name : "", x : 0, y : 0, message : "…" }; その他 login_name など関連を player.login_name に修正 --- // こちらがメッセージを受けた時の処理 socket.on("message", function(text) { console.log("message:" + player.login_name + " " + text); player.message = text; socket.broadcast.emit("message:" + player.login_name, text); }); --- // 移動処理 socket.on("position", function(pos) { // console.log("position:" + player.login_name + " " + text); player.x = pos.x; player.y = pos.y; socket.broadcast.emit("position:" + player.login_name, pos); });
  31. position を送って移動 (2) --- ex19c.js socket.emit("position", { x : this.x,

    y : this.y }); --- // サーバからこのユーザの移動が来たら移動させる socket.on("position:" + login_name, function(pos) { other_player.x = pos.x; other_player.y = pos.y; other_player.message.x = other_player.x - 30; other_player.message.y = other_player.y - 16; other_player.login_name.x = other_player.x - 35; other_player.login_name.y = other_player.y + 32; }); ※ この部分はもう削除しとく // キャラクタが押されたら右へ移動していく --- ☆ 余裕のある人は、他キャラクターの向き変更もしてみましょう。   direction 送って frame を direction にあわせて変更します。
  32. ログアウト時の処理 接続が切れてもキャラクタが残るので、ログアウト時に削除しましょう。 --- ex20.js // 切断した時の処理 socket.on("disconnect", function() { console.log("disconnect:"

    + player.login_name); socket.broadcast.emit("disconnect:" + player.login_name); }); --- ex20c.js // 切断が送られてきたら表示とオブジェクトの消去 socket.on("disconnect:" + login_name, function() { // レイヤーから削除 chara_group.removeChild(other_player); chara_group.removeChild(other_player.login_name); message_group.removeChild(other_player.message); delete other_player; }); --- removeChild を使ってレイヤーからの削除を行います。 ☆ ログイン時に名前以外に位置やメッセージ情報も送ってみよう。
  33. 早く出来た人・後の時間用ネタ XSS 対策   node-validate でサーバ側  フキダシ壊れるからクライアント側でもタグ落とす login_name 重複の対応 名前とフキダシの表示リファクタリング

      Chara クラスに機能追加 Chara.prototype = new Sprite(); ログイン時に名前だけじゃなく場所とフキダシも送る  ちゃんとやるには deferred 使う ( DB から読み出す場合とかも) ゲーム化(ゾンビ感染ゲーム、雪合戦、 NPC を捕まえる、など)
  34. Special Thanks • @shi3z_bot enchant.js でメッセージ入力するにはどうしたらよいか困って いた時に的確なアドバイスをいただきました。 enchant.js というすばらしいフレームワークを提供いただいて いること自体にも感謝です。

    • @hkoba ログイン時に全ユーザ情報を送る処理について、クロージャ自 体をハッシュに突っ込もうとして出来なくて困ってた時にアドバ イスいただき、助かりました。 • @KojiSaito ハンズオンで早く終わった人用になにか追加の課題を用意し ておいたら、というアドバイスいただき、使わせていただきまし た。