HSPポータル
サイトマップ お問い合わせ


HSPTV!掲示板


未解決 解決 停止 削除要請

2012
0103
Buzzwordネットワーク対戦型シューティングゲームの同期についてです。13解決


Buzzword

リンク

2012/1/3(Tue) 09:07:20|NO.44011

現在、gDPを使って対戦型のシューティングゲームを作っています。
そこで一つ悩んでいるのが弾座標の同期です。

ホストが弾の移動処理をして、その結果をクライアントに座標データとして送り同期すると
していたのですが、それだと送信内容が多いのでゲームが重くなります;

現段階では、上記の方法で同期をとっていますが、弾の移動間隔をなるべく大きくして送信する回数を
減らしています。

ただその場合だとゆっくりな弾が作れないのでどうしようものかと考えています。

そして一つ考えたのですが、発射したということとだけを送信して後の移動処理は各々ホスト・クライアントに
任せるようにしようと考えました。ですが、FPSの違いにより座標に誤差がでます;

60FPSをなるべく高精度で固定する方法はないでしょうか?

現段階では 
#include "tmanage3.as"
asleep double(1000/60)

のようにしています。  これだと何も弾を発射していないときはFPS60ですが、処理が重たくなると50〜60の間で変動します。
それをなるべく小さくしたいです。 

どのような意見でもいいのでお待ちしております。



この記事に返信する


HK2

リンク

2012/1/3(Tue) 10:53:10|NO.44013

クライアントサーバーモデルの場合、
同期は済んでいるものとして、
まず、クライアントはサーバーに操作情報を送信。

サーバーは、操作情報をすべてのクライアントから受信したときに、
すべてのクライアントにすべてのクライアントの操作情報を送信。

クライアントは、この情報を受信したときに、操作に対応する処理を行う。
この処理を終えた後、次の操作情報を送信。

このように繰り返してみるのはどうでしょうか。
この問題は、
通信と処理に時間がかかるクライアントの速度に合わせるので、遅くなりやすいです。
通信速度の問題はあきらめるとして、
処理速度の問題は、描画処理と内部処理を分けて、内部処理だけ正確にして、遅いマシンは描画処理を省略する。
という感じで対策をかけるのはどうでしょうか。

ピアツーピアの場合はサーバー役の内部でサーバー部とクライアント部を分ければ
C/Sと同じようにできるはずです。


私は同期を必要とする通信のプログラムを組んだことがないので、
これでできるかどうかわかりません。



てれてれ

リンク

2012/1/3(Tue) 13:52:45|NO.44018

試しては無いのであまり参考にならないかもしれませんが、

球を発射した方を送信側と考え、
x = [ゲームを開始してからの時間]とし([x]はGetTickCountなどで取得)、
球を発射した瞬間の [x] を受信側へと送信し、
受信側は、[x] から経過した時間を元に球の座標を決定する。
ping値を元に [x] を補正すれば、更に高精度な座標を求められるかと思います。



てれてれ

リンク

2012/1/3(Tue) 15:42:03|NO.44027

先ほど言ったことをテストしてみました。
S/Cが扱い易かったので、今回はpcbnet2を利用させて頂きました。

私用によりあまり時間が無かったため、
説明は手抜きで、見づらかったり無駄な部分があるかもしれませんがどうかご容赦ください。


[pcbnet2]
http://www.sharkpp.net/hsp/plugin/pcbnet2.html


クライアント用

#include "pcbnet2.as" #uselib "kernel32.dll" #cfunc GetTickCount "GetTickCount" int //クリックで球発射 IP = "127.0.0.1" : port = 24654 : size = 24 ;接続先情報 dim recv,size : dim send,size;受信データ,送信データ tcpopen sock,IP,port;サーバーに接続 repeat tcpiscon sock;接続確認 if stat = 1 : break if stat = 2 : end await 10 loop repeat tcprecv recv,0,size,sock;必要な情報を受信 if stat != 0 : break await 10 loop cid = recv(0) : TIME_S = recv(1);cid:接続ID TIME_S:ゲームが開始されてからの時間 title "同期テスト [ID:"+cid+"]" randomize 3510 bmax = 100;最大球数 pmax = 100;最大接続人数 bsize = 10 : bsp = 0.3;球の大きさ、速度 dim bf,bmax,pmax;球が発射された時間(同期用) ddim bx,bmax,pmax,2;球のx座標 ddim by,bmax,pmax,2;球のy座標 ddim br,bmax,pmax;球の角度 dim bid,pmax;球のID dim con,pmax;接続情報 dim bc,pmax,3;球の色 p = 1 : con(cid) = 1;自分のIDを接続済みに設定 repeat pmax bc(cnt,0) = rnd(128)+128 : bc(cnt,1) = rnd(128)+128 : bc(cnt,2) = rnd(128)+128;球の色を設定 loop randomize onclick gosub *発射 TIME_MS = GetTickCount()-TIME_S TIME_SS = GetTickCount() repeat gosub *受信 gosub *処理 gosub *描画 await 10 loop *受信 tcprecv recv,0,size,sock;球が発射されたかどうか受信 if stat != 0{ if con(recv(0)) = 0 : con(recv(0)) = 1 bx(recv(1),recv(0)) = double(recv(2)) : by(recv(1),recv(0)) = double(recv(3));球の座標 br(recv(1),recv(0)) = double(recv(4));球の角度 bf(recv(1),recv(0)) = recv(5);球を同期 goto *受信 } return *処理 TIME_N = GetTickCount();同期用 repeat pmax if con(cnt) == 1{;接続されているか cn1 = cnt repeat bmax if bf(cnt,cn1) != 0 {;球が発射されているか bx(cnt,cn1) = bx(cnt,cn1)+cos(br(cnt,cn1))*(TIME_N-TIME_SS+TIME_S-bf(cnt,cn1))*bsp;球を移動 by(cnt,cn1) = by(cnt,cn1)+sin(br(cnt,cn1))*(TIME_N-TIME_SS+TIME_S-bf(cnt,cn1))*bsp;球を移動 bf(cnt,cn1) = TIME_N-TIME_SS+TIME_S;同期用 } loop } loop return *描画 redraw 0 color : boxf repeat pmax if con(cnt) == 1{;接続されているか cn1 = cnt color bc(cnt,0),bc(cnt,1),bc(cnt,2) repeat bmax if bf(cnt,cn1) != 0 : circle bx(cnt,cn1)-bsize,by(cnt,cn1)-bsize,bx(cnt,cn1)+bsize,by(cnt,cn1)+bsize,0;描画 loop } loop redraw 1 return *発射 bf(bid(cid),cid) = TIME_N-TIME_SS+TIME_S : bx(bid(cid),cid) = double(mousex) : by(bid(cid),cid) = double(mousey);球の情報を設定 r = rnd(65536) : br(bid(cid),cid) = double(r);球の角度 send = cid , bid(cid) , mousex , mousey , r , bf(bid(cid),cid);送信データ tcpsend send,0,size,sock;送信 bid(cid)++ if bid(cid) == bmax : bid(cid) = 0 return



サーバー用

#include "pcbnet2.as" #uselib "kernel32.dll" #cfunc GetTickCount "GetTickCount" int screen 0,120,320,8 gsel 0,2 title "Server" port = 24654 tcpmake mysock,port;サーバーをオープン if stat != 0 : dialog "ERROR" : end;開けなかったら終了 pmax=100 : size = 24 dim con,pmax;接続情報 dim sock,pmax;ソケット dim recv,size/4;受信データ dim send,size/4;送信データ notesel log log = "START !\nOPEN [Port:"+port+"]";ログ mesbox log,640,480,0;ログ TIME_S = GetTickCount();同期用 repeat TIME_N = GetTickCount()-TIME_S;ゲームが開始されてからの時間 tcpwait mysock;接続要求がきているか if stat == 1 { repeat pmax if con(cnt) = 0{ con(cnt) = 1;接続済みフラグ tcpaccept sock(cnt),mysock;接続を受け入れる send = cnt,TIME_N;IDとゲームが開始されてからの時間を送信 tcpsend send,0,size,sock(cnt);送信 noteadd "CONNECT [ID:"+cnt+"]",0 : objprm 0,log;ログ書き換え break } loop } repeat pmax if con(cnt) == 1{;接続されているか tcprecv recv,0,size,sock(cnt);データ受信 if stat != 0{;受信されていたら recvid = cnt repeat pmax if con(cnt) == 1 and recvid != cnt : tcpsend recv,0,size,sock(cnt);受信したデータを他のクライアントに送信 loop } tcpfail sock(cnt);接続が切れた場合 if stat != 0 : con(cnt) = 0 : noteadd "DISCONNECT [ID:"+cnt+"]",0 : objprm 0,log;接続フラグを修正し、ログ書き換え } loop await 10 loop



てれてれ

リンク

2012/1/3(Tue) 15:44:50|NO.44030

必ずサーバー用のスクリプトを実行してからクライアント用のスクリプトを起動してください。



Buzz

リンク

2012/1/4(Wed) 22:02:34|NO.44060

一つ思いついたのですが、現在の時刻を基準に処理するようにすれば最初に発射されたということを
伝えるときに出る誤差以外、ないことになるのでいいんじゃないかと思いつきました。

たとえば、ホストに10000ミリ秒に発射しました。
そのことをクライアントに送信して受信されたのが10010ミリ秒でした。

ホストは10000ミリ秒に発射されたことを基準に10ミリ秒経つごとに移動処理をする
クライアントは10010ミリ秒に発射されたことを基準に10ミリ秒経つごとに移動処理をする。

このようにすればわずかな誤差で、済むんじゃないかと思いました。

HSPでミリ秒単位に時刻習得できますが、もっと短い マイクロ単位で習得したいのですがそのようなことは可能ですか?



てれてれ

リンク

2012/1/4(Wed) 22:12:40|NO.44062

それに近いことを実際にやってみせたのにスルーですか\(^o^)/

>HSPでミリ秒単位に時刻習得できますが、もっと短い マイクロ単位で習得したいのですがそのようなことは可能ですか?
マイクロ単位で取得する方法があっても、awaitの最小単位がミリ単位である以上取得することに意味を成さないと思うのですが・・・



Buzz

リンク

2012/1/5(Thu) 00:15:39|NO.44091

申し訳ないです; 一番最初読んでて理解できてなかったもので;

てれてれさんのソース実行してみましたが なかなかいいですね。

まだソースを詳しく見てないですがこれをgDPで試してみようと思います。

解決しましたら、解決にチェックいれます。



HK2

リンク

2012/1/5(Thu) 00:17:05|NO.44092

ミリ単位と書かれていますが、環境によっては5ms単位や、10ms単位だったりします。

送信時間と受信時間の差はバタフライ効果でとんでもない違いが生まれるかもしれません。
球をどのように移動させるのかわかりませんが、ダメージ計算をそれぞれのコンピュータで行っている場合、
わずかな範囲の誤差で当たった当たらなかったで、撃破された撃破されなかったに影響を与えると思います。

>マイクロ単位で取得する方法があっても、awaitの最小単位がミリ単位である以上取得することに意味を成さないと思うのですが・・・
awaitを少しだけ早めに設定して、
awaitを抜けたらビジーウェイトを使って細かい時間を調整する方法があります。



てれてれ

リンク

2012/1/5(Thu) 01:47:56|NO.44093

>申し訳ないです; 一番最初読んでて理解できてなかったもので;
読みづらかったようで大変申し訳無いです。

分からなかった箇所を教えて頂ければ追って説明させて頂きます。


>awaitを少しだけ早めに設定して、
>awaitを抜けたらビジーウェイトを使って細かい時間を調整する方法があります。
確かにそういう方法もありますが、
今回の場合、ビジーウェイトを使っていない今でさえ、
処理量が大きくなるとFPSが安定しないようですので、
メインループにビジーウェイトなんて挟んだら、
FPSの安定性が下がってしまい、あまり現実的では無いかと思います。



HK2

リンク

2012/1/5(Thu) 08:04:30|NO.44095

ビジーウェイトはFPSの安定性に悪影響を与えるのはなぜですか。
処理が追いついて、早くビジーウェイトに突入すれば、ビジーウェイトは適切な時間まで待ち、
処理が遅れ、遅れてビジーウェイトに突入すれば、ビジーウェイトは待つことなく次へ進めるはずです。

今回の場合はFPSが安定していない(処理が遅れてる)ので、
ビジーウェイトの使用に意味がなく、現実的でないことは確かなようです。
そのあたりを考えていませんでした。


あと、読み返して思ったのですが、
どうやらBuzzさんがマイクロ秒単位で時刻を取得しようとしたのは
待ち時間を厳密にするためでないようです。
早とちりしてすみません。



てれてれ

リンク

2012/1/5(Thu) 12:09:51|NO.44100

処理が遅れている状態でビジーウェイトに突入するようなことが連続してしまうと、
負荷が大きくなりすぎてしまうのは目に見えています。

さらに、今回の場合、時間を取得することが目的ですので、
ビジーウェイト中であっても、(その方法があるのかどうかを考えずに)
毎ループ、マイクロ秒というものの取得を続けなければなりません。

マイクロ秒単位を取得するようなビジーウェイトですので、
1度でも「通常のウェイト」を入れたら、大きく時間がずれてしまいます。
しかし、常にビジーウェイトを続けるわけにも行かないので(負荷の大きさを考慮して)、
どこかでビジーウェイトを抜け、「通常のウェイト」を行う必要があります。
その「通常のウェイト」とは、どんなに早くても、HSPで設定されているms単位が最速です。

マイクロ秒単位の取得においてms秒の休憩はあまりにも大きすぎます。
ただでさえゲームの動作に関わる処理が間に合っていないのに、
ビジーウェイトを使って時間を取得し、
FPSに安定性をもたせることまで同時に行うとなると、
それは非常に困難なのではないかと思い、あのような発言を致しました。



HK2

リンク

2012/1/7(Sat) 13:12:32|NO.44156

(awaitよりwaitのほうが扱いやすいかもしれませんが)
ビジーウェイトはawaitで時間経過を待った後、微調整に使うものとして書いたつもりです。
これでawaitの分解能を超えて待ち時間を調整できるので、
マイクロ秒単位の取得も意味がないことはないということで発言しました。



Buzz

リンク

2012/1/15(Sun) 07:11:32|NO.44287

みなさん、いろいろな意見ありがとうございました。

あれからいろいろ試行錯誤した結果、それらしくなったので解決にチェック入れておきます。

また新しく質問しておりますので、よかったらお返答くれたら助かります。



ONION software Copyright 1997-2023(c) All rights reserved.