Access Violation
VCのデバッグモードで動作テストして問題なかったのに
直接起動するとAccess Violationで落ちるというのはなぜだろう・・・?
しかも落ちるのがnewでメモリを確保している箇所全般なのだから
ますますわかりません。
エラーになるところをコメントアウトすると、その次にnewしているところで落ちるという・・・。
VCのデバッグモードで動作テストして問題なかったのに
直接起動するとAccess Violationで落ちるというのはなぜだろう・・・?
しかも落ちるのがnewでメモリを確保している箇所全般なのだから
ますますわかりません。
エラーになるところをコメントアウトすると、その次にnewしているところで落ちるという・・・。
30FPS描画は試してみたんですけど、劇的な効果が出ている感じはないような、あるような・・・。
ただ、それ以前の問題として30FPSの動きのガタガタ加減は許せないです。
やっぱり60FPSは死守したい。
プログラム的には実用レベルに近づいてきたので、もうそろそろネットワーク越しのテストが出来そうな予感です。
そういえばおかみさんの必殺技とかの名前が仮称のまんまだったなぁ。どうしよう・・・。
真紀子フックはまだしも、SAの「ハイバーベロシティ」とか、いくらなんでもこれはマズイっす。
っていうか、これの元ネタがわかる人っているんでしょうか。
前からわかっていたことではありますが対戦時のキャラの表示が重いのが
ネット対戦でネックになっている感じですね・・・。
他のゲームではどうやってパレット違いのキャラを表示しているのでしょうか?
VRAMの消費量を考えるとリアルタイムでテクスチャ展開するしかないような気がするんですが・・・重いんですよね。
描画だけ30FPSに落とすっていう後ろ向きな方法もあるにはありますが、まあ最後の手段ということで。
先日のエントリーに書いたことは、かなり間違ってました・・・。
実際にはディレイ値の分だけ受信と送信はずれるので、受信待ちの時間は十分にあります。
とりあえずコードはそのままでリリースモードでテストしてみたらディレイ1でほぼ常時60fpsでることが確認できました。まあ、LAN対戦でのテストなのでまだ安心は出来ませんけど。
とりあえずUDPで通信が可能になりました。
が、無茶苦茶重い・・・。TCP版より重い気が・・・。
で、ちょっと考えました。
キーデータを送信した直後に、相手からのキーデータの受信待ちをしているのが間違いの元ですね。
1フレーム内における処理の順序が
1.Sleep()
2.キーデータ送信
3.キーデータ受信
4.キャラを動かす
5.画面描画
ってなっているんですけど、これだと2と3の間の時間はゼロに等しいので、いかにUDPといえども間に合いっこありません。
1.キーデータ送信
2.Sleep()
3.キーデータ受信
4.キャラを動かす
5.画面描画
こうすれば、データの受信自体はスレッドでやっているので、Sleepしている間にデータが届いている可能性が高くなり、受信待ちで処理落ちする可能性も低くなります。
この考えに到達してからth075casterのソースを見ると、受信と送信でロジックが分かれているのも理解できます。目からウロコですね。
今までずっと気が付かなかったのですが、1フレームで2回キーの入力待ちをしてました・・・。
おそらくこれを修正するだけでTCP版通信対戦の速度はかなりマトモになると思います。
TCP版がやたらと遅いのはコーディングがヘボいからだとばかり思ってたんですけど、このバグのせいだったんですね。
といっても、せっかく途中まで作ったUDP版を捨てるのももったいないのでUDP版の作成は続けますけど。
なんか、ここ数日の日記がmixiに反映されないな・・・。
それはそれとして。th075caster のソースの解析を始めました。
UDPのパケットロスにどう対応しているのか非常に気になっていたんですが、なんという逆転の発想・・・。
1フレームに付き、10~20回パケットを送信してどれかが届けばそれで良いということらしいです。
つまり再送なんて最初からする気がないわけです。
空きバッファを探すところを読み間違えていました。
あー、恥ずかしい。
光-ADSLの対戦でFPSが30程度しか出ないという結果が出ているそうです。
ううっ、光-光なら時々ラグが発生はするものの60FPS出ていたので
ここまで遅くなるとは思ってもみませんでした。
何か根本的な対策を立てないといけませんね・・・。
やっぱりUDPを使わないとダメなんでしょうかね。
何はともあれ、テストしていただいた方には感謝します。
入力をバッファに溜めて、数フレームおきにまとめて送受信するようなプログラムにしてみたところかなりの速度向上がありました。
毎フレーム送信のときはLANでも50FPS程度しか出なくて、あきらかに処理落ちしているのがわかるレベルだったんですが、
1フレームおきに送信するようにしたら60FPSを若干下回る程度まで出るようになりました。これくらいになるとほぼ違和感無く操作が出来ますね。
リリースモードでコンパイルすれば常時60FPSで安定するかも・・・?
って、LANで60FPSが出ても意味ないんですけどね。
光対光で60FPS出るようでないと。
でも、実用レベルまでもうちょっとって感じですね。
頑張ろー!
とりあえずLAN対戦が不完全ながらも動くようになったので試してみました。
そしたら、リリースモードでコンパイルしていないためかどうかわかりませんが
50fps程度しか出ませんでした。
LANで60fps出ないのか・・・。
リリースモードでコンパイルして多少改善したとしても
ディレイ通信モードを用意しないとネットワーク越しの対戦は無理っぽいですね。
他のネット対戦ゲームではディレイを設定できるらしいということは
以前から知っていたんですけど、それを使ってどうやって通信するのかが謎のままでした。
が、某ゲームのマニュアルを読んで、ようやくわかりました。
ディレイの数値の分だけまとめてデータを送受信するんですね。
単位時間当たりの通信回数を減らすことで、スムーズな通信を行うことができる、と。
とはいえ、ディレイを大きくしすぎると、実際の入力からゲームに反映されるまでの時間が大きくなるので遊びにくくなります。
まあ、ほどほどに値を設定しないとダメなわけですね。
やー、目からウロコな感じです。
とりあえず、今はディレイのことなんて考えずに作っていたので、
すぐに実装するわけには行きませんが、よく検討していきたいと思います。
非同期ソケットによる接続がやっとうまく行ったようです。
何だか苦労したな・・・。
基本的には WSAEventSelect() を使って、タイムアウトつきで WSAWaitForMultipleEvents() を呼び出すというアルゴリズムです。
サーバ側
1.socket()
2.bind()
3.WSAEventSelect()
4.listen()
5.WSAWaitForMultipleEvents()
6.accept()
1.から順に実行しますが、5.でイベントがなかったら再度5.でイベント待ちをします。
クライアント側
1.socket()
2.WSAEventSelect()
3.connect()
4.WSAWaitForMultipeEvents()
3.で接続できたらそこで終了。出来なかったら4.で待ってイベントがあったら終了。なかったら3.に戻ります。
これで上手く動いているみたいです。
コネクション確立までは非同期ソケットで処理しようと思ったら、なんかやたらとめんどくさいですね。
WSAEWOULDBLOCK が出たときに、それが解消されるまで待つ必要があるんですけど、解消されたかどうかをどうやって調べれば良いのかが、まだよくわかってません。
ウィンドウメッセージを使うことは出来ないし。
select() で書き込み可能になったかどうかを調べれば良いのかな。
bind() や listen() で WSAEWOULDBLOCKが返ってくる可能性は考えなくて良いのだろうか・・・。
MSDNの戻り値には書かれてないから考えなくて良いのかもしれないけど、なんか不安ですね。
テストプログラムで実際にインターネット越しの通信テストをしてみました。
TCPだと30fpsくらいしか出ないのかなーと思ってましたが、ほとんど常時60fps出ますね。
時々ラグが発生してましたけど。
ゲーム中にラグが発生した場合にどれくらい気になるかは・・・実際にゲームに組み込んでみないことにはわからないかもしれませんね。
とにかく、今回のテストは成功だったので、アルファ版の開発に着手しようと思います。
もしかしたら11月の早い段階で公開できるかも?
サーバ、クライアントともにパケットを送信したあと相手からパケットが来るのを待つようにしたら、秒60フレーム出るようになりました!
でも、しばらくすると秒17フレームで安定してしまいます。何故じゃー!!
パケットが来るのを待つロジックは
while (queue->IsEmpty()) {};
っていう無限ループにしてあったんですけど、やっぱりこれがまずかった様子。
while (queue->IsEmpty()) {Sleep(0)};
↑このようにスリープを入れたら秒60フレームで安定するようになりました。
CPU使用率も100%になってないし、これでよかったのかな。
通信に必要な機能を最低限実装したクラスが出来たのでテストしてみました。
・受信は別スレッド
・クライアントはパケットが到着したらパケットを返信
・サーバはパケットの送信後に同一フレーム内でパケット受信を待つ
・クライアントとサーバはひとつのPCで動作
こんな条件でテストしてみたんですけど・・・無茶苦茶重い・・・。
秒10フレーム程度しか出ません。
このまま実装してもゲームにならないですね。
まだ、当初想定していたアルゴリズムとは違うので、TCPでももっと速く出来るはずだと思いますけど、それにしてももう少しマシなスピードで動くと思っていたのでショックです。
TCP通信だけを行うサンプルは動いてくれたので、今はTCPパケットを格納するキュークラスを作成中です。
マルチスレッドで動作させることを考えるとクリティカルセクションを使う必要がありますね。
使ったことないけど、きっとmutexとほとんど同じだろう・・・。
ネットワークプログラミングの続き。
サーバ側は accept()で接続を待ち受けるんですが、この関数にはタイムアウトがありません。
なのでひたすら接続待ちをし続けることになってしまいます。中断することが出来ません。
UNIXならばシグナルを送ることでタイムアウトさせることが出来るんですが、Windowsで同じことを出来るんでしょうか・・・?
色々調べてたら、accept()を使わず select() で read 出来るかどうかを調べて接続とみなす方法があるみたいです。select()ならタイムアウト時間を自由に設定できますからね。
それにしても、こんな初歩的で重要なことなのに Web上では解決方法がろくに見つからないのは一体・・・。
体験版も公開したので、さっそくネットワーク対応に着手です。
プログラム的には検証用のスケルトンとDLL読み込み部分を作っただけですが。
まずはTCP版を作る予定ではありますが、UDP版について非常に有用な助言をもらったのでこっちも夢ではないような気がしてきました。
UDPだとパケロスの可能性があるため、再送要求を出す必要が出てきます。
しかし、その再送要求もロストする可能性があり、再送要求の再送要求って感じに泥沼化することもあるわけです。
再送要求のような重要なパケットはTCPで送り、スピードが求められるキー入力等はUDPで送るということにすれば、一挙に解決ですよ。
確か、ひとつのポートにTCPとUDPの両方をバインドすることも出来たはずです。
これでかなり前進したような気がします。
「無敵格闘娘」でぐぐって見に来てる人が急に増えたなと思ったら、某なりた氏が雑記でコメントしてたからだったんですね。
「今後に期待」と書かれていて、ちょっと嬉しいです。
さて、ネット対戦の実装について、試行錯誤してみたいと思います。
前提条件としてはこんな感じでしょう。
・Winsockを使う
・UDPを使う
・やり取りする情報は基本的にキー入力のみ
・パケロスは認めない
UDPを使うということはパケットの送受信に信頼性が無く、再送処理なども自前でやる必要があります。
基本的な通信の流れは次のようになると思います
1.クライアント(2p側)がキー情報をサーバ(1p側)に送信
2.サーバがキー情報を受け取って1p側のキー情報をクライアントに送信
3.ゲームループを処理して1.に戻る
パケットロスが無ければこれだけでいいんですが、そうはいきません。
対処法その1
受信側は.データを受け取ったら受領シグナルを返す。
送信側に一定時間経っても受領シグナルが来なければ再送する。
対処法その2
受信側に一定時間経ってもキー情報が送られてこなかったら再送シグナルを送る
こういった処理が必要になるはずです。
また、送受信を行なうロジックはスレッドを立ててそっちでやる必要があります。
送受信の待ち時間が発生する以上、メインのスレッドで待つわけにはいきませんからね。
対処法1は受領シグナルが来なかった場合、受領シグナルがロストした可能性もあるのであまり良い方法ではないかもしれません。
対処法2で実装するべきか・・・?でも再送シグナルの到着が遅延したときに困るなぁ。
受領、再送シグナルの両方を使うといいのかな。
うーん、いきなり詰まったな・・・。
最近は同人ゲームでもネットワーク対戦に対応したものが増えてきてますね。
私はそっち系のプログラミングの知識が無いので指をくわえてるだけだったんですけど、やっぱり勉強しておく必要があるかなと思い始めました。
というわけで、早速ぐぐる。
うーむ・・・あまり有益な情報が見当たりませんね。
DirectPlay を使うことになるのかな。
通信の方法自体は調べればすぐわかりそうなのでいいとして
問題はどうやって同期するか、ですね。
キー入力情報だけ送受信してても同期できるのかな・・・。
問題はラグと取りこぼしをどうやってフォローするか。
ここがわからないんですよねぇ。
まあ、おいおい調べていきます。