Flashでゲームキャラを動してみる2。オンラインゲームが作りたい(5)
とりあえず、ソケットサーバーと連携してリアルタイムで複数のキャラクターが動くようになった。
前回色々追加
なんか、バグが出まくってるしソースが酷いことになってるので、一度きちんと設計してやり直したほうが良いのではないのかと思う今日この頃。
とりあえず、動いたので一旦やりなおしする予定。
プロジェクトファイル
今回使用しているドット絵はREFMAP様が配布しているフリー画像素材を使用してます。ゲームからデータを取り出しての2次使用は禁止されています。詳しくはこちらを参考ください。
Flashでゲームキャラを動してみる2。オンラインゲームが作りたい(4)
前回に引き続きキャラクターを動かす。
前回の記事はこっち(そろそろ同一ウィンドウ内でリンクしたい)
今回はちょっと変わってます。ポイントは以下の3つ。
- RPGツクール2000用のキャラクターチップを読込む
- 一枚の画像からキャラクターアニメーションを作成
- キャラクターが止まってるときは、ちゃんと足をそろえる。
RPGツクール2000用のキャラクターチップを読込むってのが特にポイント。
ドット絵描けない自分にとって、フリーでゲーム用素材を提供してくれる職人様は神様です。
でも、ネット上で配布されているフリーのゲーム素材のほとんどがRPGツクール2000ベースで作成されています。
たまに、XP用などもありますが、2000の方が「RPGツクール以外のゲームでも使って良いよ」素材が多いので今回は2000のキャラクターチップからロード出来るように作成しました。
こういう画像を読込みます。
素材はあらかじめ透過色を指定しておいてください。
配布元によっては複数のキャラクターが一枚の画像にまとめられている場合があります。
その場合はフォトショでアクションでも組んでください。
3つのクラスで構成されています。
NetworkGame.as
package { import flash.display.*; import flash.events.*; import flash.utils.*; public class NetworkGame extends Sprite { // プレイヤー private var player:Character; // キーコード private var keyCode:Number; public function NetworkGame() { // プレイヤー作成 player = new Character(); addChild( player.getMovieClip() ); // イベントリスナーの追加 stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownEvent); stage.addEventListener(KeyboardEvent.KEY_UP,keyUpEvent); // タイマーの追加(100m秒間隔、無制限) var timer:Timer = new Timer(100,0); // イベントリスナーの追加(発生時に) timer.addEventListener(TimerEvent.TIMER,timerEvent); // タイマーの開始 timer.start(); } // キーダウンイベント private function keyDownEvent(event:KeyboardEvent):void { // キーコードを代入 keyCode = event.keyCode; } // キーアップイベント private function keyUpEvent(event:KeyboardEvent):void { // 静止アニメーション if (keyCode == 37) { player.stopLeft(); } else if (keyCode == 38) { player.stopUp(); } else if (keyCode == 39) { player.stopRight(); } else if (keyCode == 40) { player.stopDown(); } // キーコードを空にする keyCode = 0; } // タイマーイベント private function timerEvent(event:TimerEvent):void{ // 移動 ←↑→↓ if (keyCode == 37) { player.moveLeft(); } else if (keyCode == 38) { player.moveUp(); } else if (keyCode == 39) { player.moveRight(); } else if (keyCode == 40) { player.moveDown(); } // 攻撃 space if (keyCode == 32) { player.actionAttack(); } } } }
Character.as
package { import flash.display.*; import flash.events.*; import flash.utils.Timer; import mx.core.ButtonAsset; public class Character extends Sprite { // 素材クラスの定義 [Embed(source='images/character/player/0202.png')] private var char0202:Class; // MovieClip private var mc:MovieClip; // 移動スピード private var x_speed:Number;//X座標 private var y_speed:Number;//Y座標 private var reverse_flg:Boolean; // // RPG2kConverter public var converter:RPG2kConverter; //コンストラクタ public function Character() { // 変数の初期化 x_speed = 7;//X座標移動スピード y_speed = 7;//Y座標移動スピード // RPG2k用画像からプレイヤーMCの作成 converter = new RPG2kConverter(char0202); mc = converter.getMovieClip(); // 配置位置の初期化 mc.x = 0; mc.y = 0; } public function moveUp():void { var position:Number = converter.getPosition(); if(position == 12){ if(reverse_flg){ converter.changeMovieClip(13); reverse_flg = false; }else{ converter.changeMovieClip(11); reverse_flg = true; } }else{ converter.changeMovieClip(12); } mc.y -= y_speed; } public function moveRight():void { var position:Number = converter.getPosition(); if(position == 22){ if(reverse_flg){ converter.changeMovieClip(23); reverse_flg = false; }else{ converter.changeMovieClip(21); reverse_flg = true; } }else{ converter.changeMovieClip(22); } mc.x += x_speed; } public function moveDown():void { var position:Number = converter.getPosition(); if(position == 32){ if(reverse_flg){ converter.changeMovieClip(33); reverse_flg = false; }else{ converter.changeMovieClip(31); reverse_flg = true; } }else{ converter.changeMovieClip(32); } mc.y += y_speed; } public function moveLeft():void { var position:Number = converter.getPosition(); if(position == 42){ if(reverse_flg){ converter.changeMovieClip(43); reverse_flg = false; }else{ converter.changeMovieClip(41); reverse_flg = true; } }else{ converter.changeMovieClip(42); } mc.x -= x_speed; } public function stopUp():void{ converter.changeMovieClip(12); } public function stopRight():void{ converter.changeMovieClip(22); } public function stopDown():void{ converter.changeMovieClip(32); } public function stopLeft():void{ converter.changeMovieClip(42); } public function actionAttack():void { trace("攻撃"); } public function getMovieClip():MovieClip{ return mc; } } }
RPG2kConverter.as
package { import flash.display.*; public class RPG2kConverter extends Sprite { private var mc:MovieClip; private var position:Number; public function RPG2kConverter(source:Class){ // 元画像MC生成 var image:MovieClip = new MovieClip(); image.addChild(new source()); image.name = "image"; // マスク生成 var mask:MovieClip = new MovieClip(); mask.graphics.beginFill(0x000000); mask.graphics.drawRect(0,0,24,32); mask.graphics.endFill(); mask.name = "mask_mc"; mask.alpha = 0.5; // 合体!! mc = new MovieClip(); mc.addChild(image); mc.addChild(mask); mc.mask = mask; mc._x = 0; mc._y = 0; //初期画像 mc.getChildByName("image").x = -24; mc.getChildByName("image").y = -64; } public function getMovieClip():MovieClip{ return mc; } public function getPosition():Number{ return position; } public function changeMovieClip(n:Number):void{ // 位置を記憶 position = n; switch (n){ case 11: mc.getChildByName("image").x = 0; mc.getChildByName("image").y = 0; break; case 12: mc.getChildByName("image").x = -24; mc.getChildByName("image").y = 0; break; case 13: mc.getChildByName("image").x = -48; mc.getChildByName("image").y = 0; break; case 21: mc.getChildByName("image").x = 0; mc.getChildByName("image").y = -32; break; case 22: mc.getChildByName("image").x = -24; mc.getChildByName("image").y = -32; break; case 23: mc.getChildByName("image").x = -48; mc.getChildByName("image").y = -32; break; case 31: mc.getChildByName("image").x = 0; mc.getChildByName("image").y = -64; break; case 32: mc.getChildByName("image").x = -24; mc.getChildByName("image").y = -65; break; case 33: mc.getChildByName("image").x = -48; mc.getChildByName("image").y = -64; break; case 41: mc.getChildByName("image").x = 0; mc.getChildByName("image").y = -96; break; case 42: mc.getChildByName("image").x = -24; mc.getChildByName("image").y = -96; break; case 43: mc.getChildByName("image").x = -48; mc.getChildByName("image").y = -96; break; default: mc.getChildByName("image").x = -24; mc.getChildByName("image").y = -66; position = 32; break; } } } }
まったく関係ないけどRPGツクールアドバンスは名作だと思う。
次回はそろそろサーバーと連携。
今回使用しているドット絵はREFMAP様が配布しているフリー画像素材を使用してます。ゲームからデータを取り出しての2次使用は禁止されています。詳しくはこちらを参考ください。
Flashでゲームキャラを動してみる1。オンラインゲームが作りたい(3)
今回はとりあえずFlash上でゲームキャラが動くようにしてみる。
ただ、動くだけ。本当にただ動くだけ。
前回の記事はこっち(どうやったら同一ウィンドウ内でリンクが出来るか知りたい)
とりあえず、読込んだキャラクターが動くだけ。
FlashCS3で書いています。
画像はランケージからnew char(0,0)ってやつ。
ASファイルはドキュメントクラスに設定。
サンプル
package { import flash.display.*; import flash.events.*; public class Game extends Sprite { // プレイヤー private var player_mc:MovieClip; // 移動スピード private var x_speed:Number;//X座標 private var y_speed:Number;//Y座標 //コンストラクタ public function Game() { // 変数の初期化 x_speed = 2;//X座標移動スピード y_speed = 2;//Y座標移動スピード // プレイヤー生成 player_mc = createCharacter( new char(0,0) ); // 配置位置を決定 player_mc.x = 10; player_mc.y = 10; // ステージに配置 addChild(player_mc); // イベントリスナーの登録 stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownEvent); } // キャラクター生成 private function createCharacter(bmpdata:BitmapData) { // ビットマップデータをビットマップオブジェクトにセット var bmp = new Bitmap( bmpdata ); // キャラクターMC生成 var mc:MovieClip = new MovieClip(); // 表示 mc.addChild(bmp); // 戻り return mc; } // keyDownEvent private function keyDownEvent(event:KeyboardEvent) { var code:Number = event.keyCode; // 移動 ←↑→↓ if (code == 37) { moveLeft(); } else if (code == 38) { moveUp(); } else if (code == 39) { moveRight(); } else if (code == 40) { moveDown(); } } private function moveUp() { player_mc.y -= y_speed; } private function moveRight() { player_mc.x += x_speed; } private function moveDown() { player_mc.y += y_speed; } private function moveLeft() { player_mc.x -= x_speed; } } }
ソースのダウンロード
後半明らかにコメント書くのがめんどくさくなってるさ。
でも、このままだとキーを押したときにキャラクターが動く。
「その何がいけないの?」って思うかもしれないけど、それだったら高橋名人が↑を連打したらすっごい勢いでキャラクターが上に移動してしまう。
ゲームの場合、普通は方向キーを押しっぱなしで一定間隔で移動しないといけない。
TimerEventを使って今度は押しっぱなしで移動するように書き換えてみる。
もう、FlashCS3はしんどいのでFlex Builder 2で書く。
サンプル
package { import flash.display.*; import flash.events.*; import flash.utils.Timer; public class Game extends Sprite { // 素材クラスの定義 [Embed(source='01.png')] private var char01:Class; // プレイヤー private var player_mc:MovieClip; // 移動スピード private var x_speed:Number;//X座標 private var y_speed:Number;//Y座標 // キーコード private var keyCode:Number; //コンストラクタ public function Game() { // 変数の初期化 x_speed = 5;//X座標移動スピード y_speed = 5;//Y座標移動スピード // プレイヤー生成 /* // Bitmapを描く場合 var bmpdata:BitmapData = new BitmapData(30,40,true,0xffff0000); player_mc = createCharacter(bmpdata); // CS3でランケージ読みする場合 player_mc = createCharacter( new char0101(0,0) ); */ // Flex Builder 2でEmbedした場合 player_mc = new MovieClip(); player_mc.addChild( new char01() ); // 配置位置を決定 player_mc.x = 10; player_mc.y = 10; // ステージに配置 addChild(player_mc); // イベントリスナーの追加 stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownEvent); stage.addEventListener(KeyboardEvent.KEY_UP,keyUpEvent); // タイマーの追加(20m秒間隔、無制限) var timer:Timer = new Timer(20,0); // イベントリスナーの追加(発生時に) timer.addEventListener(TimerEvent.TIMER,timerEvent); // タイマーの開始 timer.start(); } // キャラクター生成 private function createCharacter(bmpdata:BitmapData):MovieClip { // ビットマップデータをビットマップオブジェクトにセット var bmp:Bitmap = new Bitmap( bmpdata ); // キャラクターMC生成 var mc:MovieClip = new MovieClip(); // 表示 mc.addChild(bmp); // 戻り return mc; } // キーダウンイベント private function keyDownEvent(event:KeyboardEvent):void { // キーコードを代入 keyCode = event.keyCode; } // キーアップイベント private function keyUpEvent(event:KeyboardEvent):void { // キーコードを空にする keyCode = 0; } // タイマーイベント private function timerEvent(event:TimerEvent):void{ // 移動 ←↑→↓ if (keyCode == 37) { moveLeft(); } else if (keyCode == 38) { moveUp(); } else if (keyCode == 39) { moveRight(); } else if (keyCode == 40) { moveDown(); } // 攻撃 space if (keyCode == 32) { actionAttack(); } } private function moveUp():void { player_mc.y -= y_speed; } private function moveRight():void { player_mc.x += x_speed; } private function moveDown():void { player_mc.y += y_speed; } private function moveLeft():void { player_mc.x -= x_speed; } private function actionAttack():void { trace("攻撃"); } } }
プロジェクトファイルのダウンロード
次回は多分今回の続き。
今回使用しているドット絵はREFMAP様が配布しているフリー画像素材を使用してます。ゲームからデータを取り出しての2次使用は禁止されています。詳しくはこちらを参考ください。
PHPでソケットプログラミング。オンラインゲームが作りたい(2)
今回はクライアントを同時接続させてチャットっぽいものを作ってみる。
PHPlet使えよってのは禁句です。
前回の記事はこっち。
socket_select()ってのを使えばやりたい事が出来るみたい。
例によってPHPマニュアルのvardhan ( at ) rogers ( dot ) comさんの投稿を参考にとりあえず作ってみた。
なんだか自分でも理解してるのかどうかわかんないけど、出来た。
function?何それ?食べ物?
<?php // エラーのタイプ error_reporting (E_ALL); // サポートされる全てのエラーと警告 // プログラム開始 echo "プログラムを開始しました。\n"; // スクリプトが実行可能な秒数を無制限に設定 set_time_limit (0); // 出力関数のコールが行われるたびに自動的にフラッシュ操作が行う ob_implicit_flush (); // サーバーのIPアドレス $address = '192.168.24.66'; // 使用するポート $port = 10001; // IPv4インターネットプロトコル $domain = AF_INET; // 時系列的、高信頼性、全二重、接続型のバイトスト リーム(何の事やらさっぱり) $type = SOCK_STREAM; // 最大キュー数(どこで役になってるかよく分からん) $backlog = 5; // ソケットを作成する if (($sock = socket_create ($domain, $type, 0)) < 0) { echo "ソケットの作成に失敗しました。" . socket_strerror ($sock) . "\n"; }else{ echo "ソケットを作成しました。\n"; } // ソケットに名前をバインドする if (($ret = socket_bind ($sock, $address, $port)) < 0) { echo "バインドに失敗しました。" . socket_strerror ($ret) . "\n"; }else{ echo "名前をバインドしました。\n"; } // ソケット上で接続をモニタする。 if (($ret = socket_listen ($sock, $backlog)) < 0) { echo "接続待ちに失敗しました。 " . socket_strerror ($ret) . "\n"; }else{ echo "モニター開始しました。\n"; } // クライアントリスト(配列)を作る $clients = array($sock); // break 2 するまで繰り返し while (true) { // クライアントリストをコピっとく $read = $clients; // ブロックの監視 $write = NULL; // 例外の監視 $except = NULL; // 有効時間 $tv_sec = 3600; // マニュアルには0でOKってあるけど、0にするとループして処理落ちする // ソケットの配列を受け取り、 指定した有効時間の間それらの状態が変化するまで待ちます。 if (socket_select($read, $write, $except, $tv_sec) < 1){ // 最初に戻る continue; } // 新規に接続しようとするクライアントがいる場合 if (in_array($sock, $read)) { // ソケットへの接続を許可する。 if ( ($newsock = socket_accept($sock)) < 0) { echo "接続許可に失敗しました。 " . socket_strerror ($newsock) . "\n"; continue; } // クライアントリスト(配列)に追加する $clients[] = $newsock; // 現在の接続人数 $clientCnt = count($clients) - 1; // 接続者のIPを取得 socket_getpeername($newsock, $ip); // 出力する文字 $msg = "==================================\r\n". "サーバーに接続しました。\r\n" . "終わりたいときは'1'押して。\r\n" . "サーバーを止めたい時は'2'押して。\r\n" . "==================================\r\n"; // クライアントへ文字の出力 socket_write($newsock, $msg, strlen($msg)); // 出力する文字 $msg = "「{$ip}さんが来ましたよ。(今{$clientCnt}人)」\r\n"; // 全てのクライアントにログインを報告 foreach ($clients as $send_sock) { // #4を無視 if ($send_sock == $sock ){ continue; } // クライアントへ文字の出力 socket_write ($send_sock, $msg, strlen ($msg)); } // サーバーに文字の出力 echo $msg; // 発言用のforeachが発動しないようにunset $key = array_search($sock, $read); unset($read[$key]); } // 発言のあるクライアントの数だけループ(奇跡の同時入力でも行われない限り大抵1個) foreach ($read as $read_sock) { // クライアントIP socket_getpeername($read_sock, $read_ip); // ソケットから(最大2048バイトまでの)入力データを読込む $buf = socket_read($read_sock, 2048); // 接続が確認出来ない場合 if (FALSE === $buf) { // クライアントリストから削除 $key = array_search($read_sock, $clients); unset($clients[$key]); // 現在の接続人数 $clientCnt = count($clients) - 1; echo "「{$read_ip}さんが切断されました。(今{$clientCnt}人)」\n"; // 次のクライアントへ continue; } // 空白除去して中身が空っぽだったら次のクライアントへ if (!$buf = trim ($buf)) { continue; } // 1だったらクライアントの終了 if ($buf == '1') { // クライアントリストから削除 $key = array_search($read_sock, $clients); unset($clients[$key]); // ソケットを切断する socket_close ($read_sock); // 現在の接続人数 $clientCnt = count($clients) - 1; // 出力する文字 $msg = "「{$read_ip}さんが帰りました。(今{$clientCnt}人)」\r\n"; // 全てのクライアントにログインを報告 // あれ?どっかで同じ処理みたぞ。 foreach ($clients as $send_sock) { // #4を無視 if ($send_sock == $sock ){ continue; } // クライアントへ文字の出力 socket_write ($send_sock, $msg, strlen ($msg)); } // サーバーに文字の出力 echo $msg; // 次のクライアントへ continue; } // 2だったらソケットサーバーの停止 if ($buf == '2') { // ソケットを切断する socket_close ($read_sock); break 2; } // 全てのクライアントに発言を送信 // あれ・・・また同じ・・・ foreach ($clients as $send_sock) { // #4を無視 if ($send_sock == $sock ){ continue; } // 自分の発言の時は改行コードだけ出力する if($send_sock == $read_sock){ socket_write ($send_sock, "\r\n"); continue; } // 出力する文字 $talkback = "{$read_ip}:{$buf}\r\n"; // クライアントへ文字の出力 socket_write ($send_sock, $talkback, strlen ($talkback)); } // サーバーに文字の出力 echo "{$read_ip}:{$buf}\n"; } } // ソケットを閉じる(サーバーの終了) socket_close ($sock); ?>
なんか、接続してない状態でsocket_read()してもfalseが返ってこないんですよね。
だからクライアントがキー操作で切断した時以外ソケットが残ってしまう。
どうしたものかと今後の課題。
本当はずらずらと書いた後にClass化しようかと思ってたんだけど、めんどくさいから止めた。
PHPでソケットプログラミング。オンラインゲームが作りたい(1)
今回はPHPマニュアルに載ってるソケットサーバーのサンプルを少し書き換えて、コメント付け。
1って付けちゃったよ。
たぶん連載ものになる予定。
「JAVAやRubyで良いじゃん」ってのは禁句です。
人には色々事情があるんです。
で、ソケットプログラミング。
うん、さっぱりわからん。
誰もやらないのか、PHPでソケットプログラミングをする情報が非常に少ないのでPHPマニュアルのサンプルみながら少し書き換えてコメントふってみた。
結構いいかげんだけど、それは適当にメモなんで。
<?php // エラーのタイプ error_reporting (E_ALL); // サポートされる全てのエラーと警告 // スクリプトが実行可能な秒数を無制限に設定 set_time_limit (0); // 出力関数のコールが行われるたびに自動的にフラッシュ操作が行う ob_implicit_flush (); // サーバーのIPアドレス $address = '192.168.24.66'; // 使用するポート $port = 10001; // IPv4インターネットプロトコル $domain = AF_INET; // 時系列的、高信頼性、全二重、接続型のバイトスト リーム(何の事やらさっぱり) $type = SOCK_STREAM; // 最大キュー数 $backlog = 5; // ソケットを作成する if (($sock = socket_create ($domain, $type, 0)) < 0) { echo "ソケットの作成に失敗しました。" . socket_strerror ($sock) . "\n"; } // ソケットに名前をバインドする if (($ret = socket_bind ($sock, $address, $port)) < 0) { echo "バインドに失敗しました。" . socket_strerror ($ret) . "\n"; } // ソケット上で接続をモニタする。 if (($ret = socket_listen ($sock, $backlog)) < 0) { echo "接続待ちに失敗しました。 " . socket_strerror ($ret) . "\n"; } // 接続に失敗するか、break 2 するまで繰り返し do { // ソケットへの接続を許可する。 if (($msgsock = socket_accept($sock)) < 0) { echo "接続許可に失敗しました。 " . socket_strerror ($msgsock) . "\n"; break; } // 出力する文字 $msg = "終わりたいときは'quit' サーバーを止めたい時は'shutdown'"; // クライアントへ文字の出力 socket_write($msgsock, $msg, strlen($msg)); // サーバーに文字の出力 echo "新しいお客さんが来ましたよ。\n"; // ワーハハハ、ずっと俺のターンだ! do { // ソケットから(最大2048バイトまでの)入力データを読込む if (FALSE === ($buf = socket_read ($msgsock, 2048))) { echo "データの読込みに失敗しました。" . socket_strerror ($ret) . "\n"; break 2; } // 空白除去して中身が空っぽだったら何もしない if (!$buf = trim ($buf)) { continue; } // quitだったらクライアントの終了(実際は1文字入力した瞬間反応するんだけど・・・) if ($buf == 'quit') { break; } // shoutdownだったらソケットサーバーの停止 if ($buf == 'shutdown') { // ソケットを切断する socket_close ($msgsock); break 2; } // 出力する文字 $talkback = "お前が打った文字は'$buf'だあああ\n"; // クライアントへ文字の出力 socket_write ($msgsock, $talkback, strlen ($talkback)); // サーバーに文字の出力 echo "お客さんが'$buf'って言ってますよ?\n"; } while (true); // ソケットを切断する socket_close ($msgsock); } while (true); // ソケットを閉じる socket_close ($sock); ?>
テストなんでwindowsで起動。(というかルーター変えてからLinuxの調子が・・・)
IPやパスやポートは適当に書き換えて。
socket_start.bat
@echo off echo ソケットサーバーを起動します。 c:\xampplite\php\php C:\xampplite\htdocs\vh\127.0.0.2\html\test\socket.php
socket_telnet.bat
telnet 192.168.24.66 10001
同時接続どうするんだろう?
多分続く。
リンケージでライブラリの画像読込
Flex2じゃなくて、CS3の話。
いつも、画像読込む時は外部ファイルから読込んでるんだけど、ふとFlaファイル内のライブラリから読込もうと思ったらうまく出来ない。
さっぱり分からんで散々悩んだ挙句下記の方法で解決。
import flash.display.*; addChild( new Bitmap( new hoge(0,0)) );
リンケージした画像のクラス名はhoge
引数にwidth, height指定してやら無いとエラーが出るらしい。
分からなくて、ここでえらい事はまった。
サイズの指定は0,0で良いみたい。
ためしに
import flash.display.*; var piyo = new Bitmap( new hoge(0,0)); trace(piyo.width);
ってやってみたらちゃんとサイズ取得してた。
やっぱりFlex2の方や使い易い気がする。
D&D2 ミスタラ ネームチェッカー携帯版
ついでだから携帯版も作った。land.toのサーバーでZend Frameworkが使えるか試したかったんで無駄にZend Frameworkで作った。無料サーバーで広告が入るんだけど、とくにローカルから移植するときにソースを書き換えたりすることなく移植できたんで今度からどうでも良いコンテンツはここに置こうかと。
PC版もちょっと直した。
携帯版
http://codeweb2.so.land.to/ddnamecheckermb/
PC版
http://codeweb2.so.land.to/ddnamechecker/