「空島大乱闘」のオンラインの技術的なこと その4

伝説のマニアです♪U・ω・U

前回は、キー操作の同期化について概要を説明しました。今回は、キャラクターイベントの同期化について説明したいと思います。

【連載記事一覧】
「空島大乱闘」のオンラインの技術的なこと その3
「空島大乱闘」のオンラインの技術的なこと その2
「空島大乱闘」のオンラインの技術的なこと その1



【イベント同期化の重要性】
イベント処理は、出来る限り正確に同期化する
前回説明した、キー操作の同期化処理については、ある程度妥協した形で同期化する方法を取っていました。各プレイヤーから見た、他のプレイヤーの見た目が一致しない事が頻繁に起こるというものです。

しかし、キー操作と違って、キャラクターのイベント処理は重要な要素である事が多いため、出来る限り正確に同期化したい、と思いました。

ここで言うキャラクターのイベントとは、「プレイヤーが攻撃した」とか「プレイヤーがダメージを受けた」、「プレイヤーが死んだ」という処理の事です。



【イベントとして送信する情報】
空島大乱闘では、イベント情報として、以下の情報を他のプレイヤーへ送信しています。

・攻撃した・・・・・・(当たっていなくても)パンチ等を行った
・武器を取った・・・・キャラ1の落ちている武器を拾う処理(※武器取得の流れを後述します)
・武器を落とした・・・キャラ1の持っている武器を落とす処理
・必殺技を出した・・・キャラ2の(当たっていなくても)必殺技を行った
・ダメージを受けた・・敵から攻撃されて、ダメージを受けた(一瞬怯む動作)
・吹っ飛ばされた・・・敵から攻撃されて、ぶっ飛ばされる処理
・落ちた・・・・・・・空島から落下した
・ダウンした・・・・・ぶっ飛ばされた後にダウンした(必ずダウンするようになっています)
・死んだ・・・・・・・ヒットポイントが0となり、死んでしまった
・コインを取った・・・落ちているコインを取得した

イベント情報パケットについて
上記のイベントが発生した場合は、そのイベントを同期化するため、イベント情報パケットを送信する事になります。空島大乱闘では、イベント情報パケットは二種類あります。

一つ目は、プレイヤーの操作から発生したイベントで、「攻撃した」や「必殺技を出した」の処理になります。そのさいに送っている情報は、以下の通りです。

・プレイヤーID・・・・・ゲームサーバーより割り振られたID
・プレイヤー位置情報・・プレイヤーのワールド座標位置値
・イベントタイプ・・・・「攻撃した」「必殺技を出した」等の種類

このパケットを受信した端末では、「プレイヤー位置情報」へプレイヤーを強制移動し、「イベントタイプ」で指定されたイベントを必ず行います。

イベントを行ったプレイヤーがどの位置にいようとも、強制的にプレイヤーの位置を移動させ、今プレイヤーがどのような状況であったとしても、強制的にイベントタイプで指定された動作を行うようにしています(一部例外はありますが、稀なケースなのでここでは省きます)。

キー操作の場合だと、プレイヤーの位置はトゥイーンにより移動させていましたが、イベント処理は強制移動となっています。この辺は悩み所でしたが(ぶっちゃけどっちでもいいのですが)、キー操作よりも重要ということでこのようにしました。

二つ目は、プレイヤーが他から受けるイベントで、「ダメージを受けた」や「死んだ」、「コインを取得した」の処理になります。その際に送っている情報は、以下の通りです。

・プレイヤーID・・・・・ゲームサーバーより割り振られたID
・プレイヤー位置情報・・プレイヤーのワールド座標位置値
・イベントタイプ・・・・「ダメージを受けた」や「死んだ」等の種類
・パラメーター1・・・・4バイトの浮動小数点数
・パラメーター2・・・・2バイトの整数型

二つ目のイベントも、強制位置移動と、強制イベント処理を行っていることは、一つ目と変わりありません。

ただ、「どのくらい吹っ飛ばされたか」や「どのコインを取得したのか」は、イベントタイプ情報だけでは分かりません。なので、パラメーターを追加して汎用的に使うことで、情報を同期しています。例えば、以下のような情報を送っています。

→ダメージを受けて、吹っ飛ばされた場合
・プレイヤーID・・・・・ゲームサーバーより割り振られたID
・プレイヤー位置情報・・吹っ飛ばされた時のプレイヤーのワールド座標位置値
・イベントタイプ・・・・吹っ飛ばされた
・パラメーター1・・・・吹っ飛ぶキャラのrigidbody2D.velocityへ入れる値(x軸のみです)
・パラメーター2・・・・攻撃主(敵キャラ)のID

→コインを取得した場合
・プレイヤーID・・・・・ゲームサーバーより割り振られたID
・プレイヤー位置情報・・コイン取得時のプレイヤーのワールド座標位置値
・イベントタイプ・・・・コインを取った
・パラメーター1・・・・未使用
・パラメーター2・・・・コインのID

※因みに、なぜ攻撃主のIDが必要かというと、攻撃主から発する効果音を鳴らす必要があるからです。敵キャラによって、殴られた時の効果音が違うので、「どの敵キャラに殴られたか」という情報が必要になっています。

ダメージ処理について
上記のパケット情報を見てみると、どれだけのダメージを受けたか、という情報を送っていないことに気づきます。そうです、プレイヤーのダメージ値情報は同期化していないのです。

空等大乱闘では、協力プレイであるというのと、簡易的なダメージ処理システム(FPSでよくあるような、ダメージを一度受けても、一定時間ダメージを受けなければ回復するというもの)であるため、今自分のヒットポイントがどれだけ残っているか、というのは重要ですが、他のプレイヤーのヒットポイントが残りどれだけであるか、というのはあまり重要ではありません。

なので、プレイヤーが死んだのか、死んでいないのか、が分かればいいのです。むしろ、他のプレイヤーのクラスのメンバー変数として、ヒットポイント値を保持してもいませんw

実装としては、「is_death」という死んだか死んでいないかどうかの判定用の変数(bool)だけを用意し、「プレイヤーが死んだ」というイベントを受け取るとフラグをonにしているだけ、ということになります。

協力プレイということや、簡易なダメージシステムにより、この実装方法が成り立っているかと思います。これが対戦型や、複雑なダメージシステムだと成り立たないかもしれません。



【武器取得処理】
空島大乱闘では、上述のように、キャラクターイベント処理は、結構単純な情報で同期化されていることが分かると思います。

しかし、武器取得イベントについては単純にはいきません。

落ちている武器を取得出来るのは、自分だけではなく、他のプレイヤーも取得することが出来るため、武器取得イベントが被る事が考えられるからです。

例えば、プレイヤーAとプレイヤーBが、落ちている同じ武器を同時に取得するイベントを発生させた場合に、お互いに自分の端末では取得成功しているが、相手方(Aから見るとB、Bから見るとA)の武器取得情報をまだ受信していない場合です。

この場合、落ちている武器を自分が取ったはずなのに、他のプレイヤーも同じ武器を取った、というイベントを受ける事となってしまいます。


これを回避するため、空島大乱闘では、ゲームサーバーを介して以下の処理手順により同期化を行っています。

→武器取得処理が成功するまでの一連の流れ
(1)プレイヤーより、武器取得リクエストをゲームサーバーへ送信する
(2)ゲームサーバーより、武器取得リクエストが成功した、というパケットをプレイヤーに送信する
(3)プレイヤーは、武器取得成功を受信すると、実際に武器を取得する処理に入る
(4)プレイヤーより、完全に武器取得処理が成功したら、終わった事をサーバーへ送信する

通常通りうまくいけば、上の処理で武器取得は成功します。しかし、以下の場合だと武器取得処理は失敗します。

・パターン1
(1)〜(3)の間に、武器取得リクエストを送信したプレイヤーが敵からの攻撃を受ける等して失敗した場合
  →この場合、(4)で武器取得が失敗した事をサーバーへ伝える

・パターン2
(1)の処理が他のプレイヤーと被った場合
  →この場合、サーバー側へ最初にリクエストパケットを到達させたプレイヤーが勝ちですw
  →サーバーから、武器取得成功パケットと、武器取得失敗パケットを、それぞれプレイヤーへ送信します

※上記パターンは代表例で、実際のプログラムはもっと複雑です

基本的には、(1)でリクエストを受けたサーバーは、当該武器をロックし、以後他のリクエストが来てもこれを受け付けません。その後、最終的に(4)で成功が来ると、そのままロック、(4)で失敗が来ると、再度ロックを解除し受付できるようにしています。



【コイン取得処理】
武器取得処理についてはそれなりにちゃんと同期化していますが、コイン取得処理は、テキトーです。

ぶっちゃけ、コイン取得はイベント被りが可能となっていますw

プレイヤーAとプレイヤーBで同じコインを同時に取得した場合、両方ともコインを取得した事になりますw

この辺は、特にコインの取得処理が被っても問題はない、ということで妥協しています♪U・ω・U



【まとめ】
今回は、キャラクターイベントの同期化について説明していきましたが、言葉だけで説明しているため、ちょっと(結構)分かりにくいかもしれません。

私に図とか絵を描くセンスがあれば、それを駆使しまくって説明したいと思うところですが、あいにく皆無です(日本語もアレですが)。

。。。とりあえず、キャラクターのイベントは基本的に重要なのでしっかり同期化していますが、(コイン取得処理のような)重要ではない処理は同期化をある程度妥協していく、という方法で実装しています!!
テーマ: Unityゲーム制作 | ジャンル: コンピュータ

「空島大乱闘」のオンラインの技術的なこと その3

伝説のマニアです♪U・ω・U


今回は、空島大乱闘のオンライン技術のうち、キー操作の同期化について説明していきたいと思います。

空島大乱闘では、十字キー(8方向)を使用しています。十字キーの実装方法については記事を分けましたので、下記を参照してください。

UnityのuGUIで十字キーを実装する



【キー操作同期化】
自分のプレイヤーはちゃんと操作したい
空島大乱闘では、自分のプレイヤー、他人のプレイヤー、敵キャラクターが出てきます。この中で、まず一番重要なのは、自分が操作しているプレイヤーです。

連載記事の「その2」で述べたように、空島大乱闘は、完全に各ユーザーを同期化することは妥協しています。しかし、せめて自分が操作するキャラクターは、自分が操作した通りに動いてほしいものだと思います。

よって、基本的に、空島大乱闘では、自分が操作するキャラクターは、自分が操作した通りに動いてくれます。

そして、同期化の方法として、自分が操作したこと(touch_began、touch_move等)を、他のプレイヤーに送る事によって、他のプレイヤーへ自分がどう動いたかを伝えています。

キー操作同期化だけではなく、空島大乱闘の実装にあたり全体的に意識していたのは、ユーザーが行った操作・結果についてはなるべくその通り同期しようという考え方です。

オンラインゲームでラグるのは当たり前ですが、一番ユーザーがイラっとするのが、「自分はこう操作したはずなのに、違う動きになった」「敵を殴ったはずなのに、殴った事にならなかった」というものです。なので、出来るだけユーザーが行った操作は尊重してその通りに同期化しよう、というのが基本方針でした。

キー操作のパケットについて
次に、キー操作時に、どういった情報を送るか説明します。空島大乱闘では、キー操作情報として、以下の情報を送っています。

・プレイヤーID・・・・・ゲームサーバーより割り振られたID
・プレイヤー位置情報・・プレイヤーのワールド座標位置値
・インプットタイプ・・・touch_began、touch_move等の種類
・キーステータス・・・・キーのステータス(上を押している、右を押している等)
・走ってるか否か・・・・プレイヤーが歩いているか、または走っているか

上記の情報を他のプレイヤーに送り、他のプレイヤーはこれを受けたら、その通りに送信したプレイヤーを動かしていきます。

送られてきたパケットの処理手順について
送信されてきたパケットを受けた後の、処理手順は以下の通りです。

(1)プレイヤーIDより、該当するプレイヤーを取得する
(2)プレイヤー位置情報を確認して、プレイヤーの位置がズレていた場合、位置を補正する
(3)キーステータスを、該当するプレイヤーへ適用する(インプットタイプによって処理は変わる)
(4)走っているか否かを確認して、該当するプレイヤーへ適用する

上記の流れでキー操作パケットは処理されています。

この内、(2)の位置補正については、トゥイーンで補正しています。ちょっとズレていた場合には、少しずつそのズレを修正するというものです。ただ、大幅にズレていた場合には、強制的にプレイヤー位置情報に従って、その位置へ移動させます(この場合、プレイヤーが瞬間移動しているように見えますw)。

また、(3)のキーステータスを、該当するプレイヤーへ適用すると、例えば、「右を押している」というステータスが適用された場合、そのプレイヤーは次のキー操作パケットを受信しない限り右へ移動し続けます。空島大乱闘でたまに、右にずっと移動していたプレイヤーが急にパッと(瞬間移動して)左に移動し始める場合がありますが、これは、「右を押している」というパケットを受信した後に、「左を押している」というパケットの受信が遅れたため起こる現象です。

あとは、走っているか否かを確認して、「右を押していて走っている」のであれば、右に走る処理を行い、「左を押して歩いている」のであれば、左に歩く処理を行う、という感じです。

パケットを送るタイミング
そしてキー操作の同期化で一番悩んだのが、キー操作パケットをどのくらいの頻度で、いつ送ればいいのか、という事です。頻度を上げれば同期化の精度は上がりますが、通信の負担が増えます。また頻度を下げ過ぎると、通信の負担は減りますが、頻繁に瞬間移動してしまうことになるでしょう。

空島大乱闘はどうしているかというと、かなり頻度を落としていますw

空島大乱闘で、キー操作パケットを送るタイミングは、以下の場合だけです。

・touch_beganが呼ばれた時
・touch_moveにより、キーステータスが変わった時
・touch_canceledが呼ばれた時
・touch_endedが呼ばれた時

連載記事の「その2」で述べたように、空島大乱闘はリレーサーバーを使っているため、あまり通信負担をかけることができません。なので、同期化の精度を落としてでも、負担を少なくしたいと思ったからです。

協力プレイであるため、何とかなりますが、これが対戦型となると、もっと精度を上げる必要があるかと思われます。



【他のキー操作同期化方法】
空島大乱闘では、上述のように、かなり頻度を落として同期化を行っていますが、他の方法として考えられるものを紹介していきます。

空島大乱闘をベースに頻度を上げる
単純に、空島大乱闘の実装方法にプラスして、定期的にキー操作パケットを送る、という方法が考えられます。この場合は、例えば、上述の送るタイミングに加えて、1フレームに数回、キー操作情報を送ります。

この場合は、インプットタイプ、キーステータス、走っているか否か、が変わらないのであれば、以下の情報送信だけで十分です。

・プレイヤーID
・プレイヤー位置情報

それでも、プレイヤーIDが2バイト、プレイヤー位置情報が8バイトなので、パケットの長さとしては全体で16バイトとなります。もちろんTCPヘッダ等を含めるともっと大きくなります(※パケットの細かい作成方法については連載の中で説明する予定です)。

プレイヤーの位置情報だけを送る
また、よくある実装方法として、プレイヤーの位置情報だけを送り(もちろん走っているか否か等の情報も送りますが)、それに合わせていく方法があります。この方法の場合は、位置情報を受けた側はトゥイーン等によりその位置へ移動させていくような処理を行います。

プレイヤーが目指す位置だけを送る
アクションゲームからは離れてしまいますが、この方法は、MMOG等でよく使われていた方法です。自分が行きたい位置をクリックするとその位置へプレイヤーが走っていく、というもので、その位置だけを他のプレイヤーへ送る方法です。
MMOGの場合は、リアルタイムアクションが難しいため(今となってはアクションMMOもあるようですがw)、出来るだけ通信を節約するため、この方法がよく使われているようです。



【まとめ】
まとめ。。。まとめというか、なんかもうアレです。同期化方法については悩みまくりですw

同期化方法はいくらでも考えられますが、ゲームシステム、想定される接続人数、通信環境、想定するクライアント端末、サーバースペック等色々と考慮して最善策を考えなければなりません。。。U・ω・U

。。。。そして私の場合は個人開発であるため、想定される接続人数なんて超少ないのです。しかし、急に増える(嬉しい)可能性も考えてスケーラビリティに作る必要があります(※この辺については連載の中で紹介していきます)。なので、必要なサーバー台数等の計算もやりにくいのですw

そして、結局色々と悩んだ挙句、最終的には「勘」という事に落ち着くのでした♪U・ω・U
テーマ: Unityゲーム制作 | ジャンル: コンピュータ

UnityのuGUIで十字キーを実装する

伝説のマニアです♪U・ω・U


今回は、UnityのuGUIを使って、十字キーを実装する方法を紹介したいと思います!
(Android、iPhoneを想定しています)

なお、uGUIの基本的な使い方は割愛したいと思います。基礎的なuGUIの知識があることを前提で紹介していきます。



【十字キー用のゲームオブジェクトを用意する】
画像を用意する
今回作る十字キーでは、斜めの移動も含めて、8方向移動できるように実装していきたいと思いますので、それぞれの方向に対応した下のような9枚の画像を用意してください。

crosskey.png



UIのImageを追加する
「Unityのメニュー → GameObject → UI → Image」より、十字キーの元となるImageオブジェクトを作ります。


Event Triggerを追加する
追加したImageの「Add Component」より、「Event Trigger」を追加します。その後、「Add New Event Type」より下のようにイベントタイプを追加していきます。これを設定することで、用意したImageがタップされた場合に呼ばれるイベントを制御することができます。

20150611201643.png


・Pointer Enter(BaseEventData)
 タップした時に呼ばれるイベントです。
・Drag(BaseEventData)
 タップしたままドラッグした時に呼ばれるイベントです。
・Cancel(BaseEventData)
 タップをキャンセルした時に呼ばれるイベントです。
・Pointer Exit(BaseEventData)
 タップを離した時に呼ばれるイベントです。

上記用意したイベントタイプへ、イベント時に呼んでほしい関数を設定していきます。上の画像だと、「Pointer Enter」イベントが発生した時に、「CrossKeyScript.touch_began」が呼ばれるように設定されています(※関数の中身については後述します)。



【十字キー用のスクリプトを作る】
次に、十字キー用のスクリプトを作っていきます。UnityはC#やJavaScriptが使えますが、私はC#を使ったので、C#のコードを紹介していきたいと思います。
作成したスクリプトは、「Add Component」より当該十字キーへ追加します。

C#


ソースコードを簡単に解説していきます。

十字キーの画像を設定する
上で用意した9枚の画像を、それぞれ9行目〜17行目のpublic Spriteへぶっ込んでいきます(Inspector上でドラッグ&ドロップ)。

クラス内の変数について
20行目〜31行目の列挙型は、十字キーのステータスの一覧です。何も押していない状態(NONE)と、8方向のステータスが列挙されています。
そして、32行目のkstatusに、その時の十字キーのステータスが入ります。

35行目のis_touchは、十字キーがタップ中か否か、の判定用の変数です。タップ中であればtrue、タップ中でなければfalseが入ります。

36行目のfinger_idは、タップ中のtouch ID(PointerEventData.pointerId)が入ります。

クラス内の関数について
・touch_began関数
53行目のtouch_began関数内では、最初にタップが開始された時の処理を書いていきます。
まず、タップ情報を取得するため、引数としてBaseEventDataが指定されています。Event Triggerより関数が呼ばれたさいに、タップ情報がBaseEventDataに格納されるような仕組みになっています。

今回はタップ情報から、タップされた座標等を取得したいので、55行目でBaseEventDataをPointerEventDataへ変換しています。

57行目のタップ中かどうかの判定については、マルチタップが発生した場合に2本目のタップを無効にするための判定です。この中でtouch IDを取得することにより、以降呼ばれるtouch_moveやtouch_endedで1本目のタップだけを取り扱うように制御しています(60行目〜62行目)。

取得したPointerEventDataのposition値は、Screen-pointとなっているため、これを65行目でworld-pointに変換しています。今回取り扱う十字キーのImageのposition値はScreen-pointとなるため、これに合わせる必要があるからです。

そして最後に、十字キーのposition(十字キーの真ん中のx軸、y軸の値)とタップ座標の差分をとって、key_action関数に処理を渡しています。

Constants.TOUCH_MOVEについては気にしないでくださいw 特に今回は使っていません。

・touch_move関数
74行目のtouch_move関数は、タップした後、ドラッグ操作によりタップ座業が変化した時に呼ばれる関数です。十字キーは押しっぱなしでグリグリやることが多いため、この関数は重要になります。
関数の中身については、touch_began関数とほぼ同じですが、78行目の判定で、「タッチ中であり且つtouch_began関数で取得したtouch IDと同じIDしか処理しないように制御しています。

・touch_canceled関数
90行目のtouch_canceled関数では、途中でタップがキャンセルされた場合に呼ばれる関数です。
この関数ではタップが終了するため、reset関数を呼んでいます。

・touch_ended関数
100行目のtouch_ended関数でも、touch_canceled関数と同じようにreset関数を呼んでいます。

・reset関数
reset関数では、変数の初期化を行っています。因みに、finger_idを-1で初期化している理由としては、touch IDが(マルチタップ時)タップされた順番によって、1、2、3、という風に振られるため、-1に設定していると、他のタップと被る事がないためです。

・key_action関数
key_action関数は、十字キー処理のメインとなる関数です。touch_began関数とtouch_move関数から呼ばれてきます。引数としては、key_type(今回は使いません)、x軸値、y軸値となっています。軸の値は、十字キーの真ん中から見たx、yの位置になります。これを使い、今十字キーの上が押されているのか、または右が押されているのかを判定します。

中学か高校の頃にやったようなやらなかったような数学の二次関数のアレですw

例えば、右を押している、と判定したい場合、下記の不等式が両方真となる場合、「右」となります。

不等式1:y < 1/2 * x
不等式2:y > -1/2 * x

これをC#のコードで書くと、134行目のようになります。

このように8方向すべて判定していきます。そして、各対応するsprite、kstatusを設定すれば終わりです。

作成した関数をEvent Triggerへ設定する
上の方で作成したEvent Triggerへ、作成した関数を設定していきます。下記の通り設定します。

・Pointer Enter(BaseEventData) → touch_began関数
・Drag(BaseEventData) → touch_move関数
・Cancel(BaseEventData) → touch_canceled関数
・Pointer Exit(BaseEventData) → touch_ended関数

十字キーの利用方法について
以上で十字キーの基本的な実装は完了します。これで、ユーザーのタップによって、キーのステータスが変化し、今ユーザーがどの位置をタップしているか判定出来るようになります。

この十字キーの利用方法としては、例えばゲームのプレイヤーのスクリプトから、この十字キーのステータスを参照し、ステータスの種類によって右に移動したり左に移動したりするプログラムを書けば、プレイヤーが動いてくれると思います。

また、ダブルタップを判定したい場合は、touch_began時にタップ時間を取得して、2回目のタップの時間との差分をとれば判定できるかと思います。

また、ホールドを判定したい場合も、touch_began時に時間を取得して、Update関数により一定時間後ホールドしている、と判定すれば実装できるかと思います。

こういった操作の種類については、ゲームの種類によって色々と考えられるかと思います。

Sponsored Links
テーマ: Unityゲーム制作 | ジャンル: コンピュータ

「空島大乱闘」のオンラインの技術的なこと その2

伝説のマニアです♪U・ω・U


今回は、空島大乱闘のオンライン技術のうち、全体的な構成を説明していきたいと思います!



【通信の種類を選択する】
まず、ネットワーク通信を行う上で、通信種類を決める必要があります。知識のある方はご存知かと思いますが、通信の種類は大きく分けて、TCP通信、UDP通信があります。オンラインゲームであれば、TCP通信もUDP通信もよく使われます。

どの通信種類を選ぶかは、ゲームシステムによって決定しなければなりません。RPGのアイテム取得処理等のような、通信ミスが許されないものについてはTCP通信を使う必要がありますし、FPSのような通信速度を求められるゲームではUDP通信が使われていると思われます。

また、UDP通信に信頼性を付加した通信方法もオンラインゲームではよく使われるようです。この場合、パケットロス時の再送やパケット順番制御を自前でプログラミングする必要があります。探せば、そういったミドルウェアもあるかもしれません。

。。。そして、オンラインアクションゲームの空島大乱闘は何かと言いますと。。。


全部TCP通信ですww


通常、オンラインアクションゲームであれば、可能な限りUDP通信が使われると思います。前回触れたNAT問題があるため、端末が常にUDP通信を使える環境にあるとは限りません。

そのため、通信種類を決定する上で、「このゲームはTCPだ!」と決めつけるのではなく、まず端末がUDPが使えるかどうかチェックする、使えないのであればTCP、、、といった工夫が使われていると思うのです。

しかしながらぶっちゃけめんどくさい、空島大乱闘はTCP通信で作っています。この辺は、個人開発という言い訳で妥協していますw

後々は端末毎に通信種類を制御できるような仕組みを作りたいと思っているところです。。。U・ω・U



【通信の方式を選択する】
オンラインゲームの通信方式は、大きく分けて、「完全同期型」と「非同期型」の二種類があります。

完全同期型
完全同期型は、その名の通り、端末同士(もしくはサーバー&端末同士)を完全に一致させる通信方式です。ゲームシステムによって、ターン毎、またはフレーム毎にプレイヤー全員の操作が各端末ですべて揃ってから、ゲームの処理を進める、というものです。

この方式では、端末での見た目が完全に一致します。通信速度を求められないターン性のゲームや、速度を求めるが絶対にズレが許されない格闘ゲームではこの方式が選ばれると思われます。

非同期型
非同期型は、端末同士の見た目が一致しない通信方式です。各プレイヤー同士で、見えているゲーム世界が異なる事を許容する、という方式です。

この方式を採用する場合、各プレイヤー・モンスターのイベント、タイミングが一致しない事が起こり得ます。それでも、ゲームにとって重要なイベント(プレイヤー死亡やモンスター死亡等)はちゃんと一致させるように調整します。

ゲームを完全に合わせる事は妥協しているが、全体的にゲームが破綻していなければ良しとするのがこの方式です。

大規模なMMOGやFPSではこの方式が使われていると思われます。


そして空島大乱闘は、非同期型に分類されます。

空島大乱闘では、プレイヤー数は最大4人、モンスターの数は20匹以上となることがあります。この数のキャラクターを全部完全に同期させるのは非常に難しいです(ラグりまくりで大変な事になります♪)。

そこで、完全に合わせる事は妥協しているが、プレイヤーにはあまりそう感じさせない工夫が必要でした。

また、空島大乱闘は、対戦型ではなく協力プレイであるため、あまり厳しい同期化が求められないだろうと判断しました。

これが対戦型となると、また同期化の方法を変更しなければならなくなると思われます。




【クライアント/サーバー型、P2P型について】
次に、ゲームの処理を、サーバー側で行うのか、それともクライアント側で行うのかを決める必要があります。

クライアント/サーバー型
サーバー側でゲーム処理を行う場合、各クライアントからはキー入力イベントをサーバーへ送信し、サーバーからは結果をクライアントへ送信する、という流れになるかと思います。

MMOG等でこの方法が採用されているかと思われます。この方法では、ゲームの処理内容をサーバー側でチェックすることができるため、チート対策がやりやすくなります。

P2P型
P2Pの場合は、各クライアント同士がお互いにキー入力イベントを送りあう事となります。

格闘ゲーム等ではこの方法が採用されているかと思われます。



そして、空島大乱闘では、基本的にP2P型をとっています。

。。。ただし、直接クライアント端末同士で通信するのではなく、一度専用サーバー(リレーサーバーと言います)を通して通信を行っており、擬似的なP2P型と言えます。

理由としては、やはり、NAT問題がめんどくさいをクリアすることが難しいからです。

もし、プレイヤー端末がWiFi環境下にあった場合、ルーターの種類によっては、そのプレイヤーにパケットを送る事が難しくなります。

本当は、ルーターの種類によって、パケットを送る事が可能かどうかをチェックし、動的に通信方法を変える事が理想ではありますが、今回は妥協して、リレーサーバーを通して通信を実現しました。

リレーサーバーとは、クライアントから送信されてきたパケットを、そのまま別のクライアントに転送するだけのサーバーの事を言います。

この方法をとったのも、どのような環境下であったとしても、サーバーへTCPセッションを開始することができない端末はほとんどないだろう。。。と判断したからです(ただし、環境によってはTCPセッションの開始をポート80番に制限しているものもあるようですが、そんなの知りません)。

また、必ずサーバーを通して通信を行う事で、ゲームの中で絶対同期化したい処理(アイテム取得等の被ってはいけない処理)を、簡単に実装可能という事もありました。



【まとめ】
まとめとして、空島大乱闘は、TCP通信を使い、非同期型でリレーサーバーを用いた擬似P2P方式を採用している、ということになります。

リレーサーバーで大丈夫かな。。。と思ったのも、一度スマートフォン端末を使い、サーバー間(東京と沖縄)で通信を行い速度テストを行ったところ、概ねRTTが50msを実現できた事で、「あ、いけるかも」と思ったからですw (もちろん、スマートフォン端末は通信速度が不安定なので、毎回この速さで通信できるとは限りませんが。。。)

次回は、十字キーの実装とキー入力同期の方法を紹介していきたいと思います。
テーマ: Unityゲーム制作 | ジャンル: コンピュータ

「空島大乱闘」のオンラインの技術的なこと その1

こんばんは、伝説のマニアです♪U・ω・U

今回から数回に分けて、リアルタイムオンラインアクションゲーム(空島大乱闘)を作る上で工夫した点などを連載していきたいと思います!



【このゲームを作りたいと思った動機】
空島大乱闘は、MO型のアクションゲームです。複数のユーザー同士で、一つのゲームルームみたいなものを作り、その中でゲームを楽しむ、というものです。

このゲームを作りたいと思ったきっかけが、ファミコンで流行った「ダウンタウン熱血行進曲 それゆけ大運動会」みたいなものを、オンラインで遊べたら面白そうだなぁ〜って思ったことです。思いっきり世代なんですよねw

また、PCゲームの「アラド戦記」からも大きい影響を受けています。オンラインゲームにハマった経験から、ネットワークプログラミングが好きになり、自前でオンラインゲームが作りたい!って思うようになったのです。



【スマートフォンという端末】
空島大乱闘の一番の魅力は、マルチプレイにあります。シングルプレイも用意していますが、他のユーザーと一緒に協力して敵を倒していくことに面白さを感じると思うのです。

しかし、スマホでオンラインゲームを作るのは、いくつかのハードルを越えなければなりませんでした。

避けられないラグの問題
ここ数年で端末のスペックが大幅に上がり、通信速度も高速化してきています。それでも、PCと比べると、通信が不安定だったり、処理落ちなどが頻繁に起こります。不安定な端末・環境でも、ゲームを遊べるレベルに持っていく、というのが全体的な課題でした。

NAT問題
オンラインゲームを作るのであれば、必ず頭を悩ませるのがNAT越えについてです。特にスマホはどこへでも持ち運びが可能であり、通信環境がコロコロ変わります。できるだけ多様な環境で接続できるように工夫する必要があります。

不向きなジャンル
「くにおくん」のような十字キーでキャラクターを操作するタイプのアクションゲームがそもそもスマートフォン向きではない、という事もありました。まずスマートフォンにはボタンがないので、スクリーン上にボタンを配置しなければなりません。そうすると、どうしても指で隠れて見えない部分が出てきてしまうのです。この点を、ユーザーが気にならないように工夫する必要がありました。



【開発に使用したツール等】
クライアント端末
想定する端末は、人気のiPhoneとAndroidにしました。この二つでスマートフォンユーザーの大半を占めるため、問題ないと思われます。

配置するサーバー
使用したサーバーは、ubuntu 12.04です。特にこだわりがあるわけではないのですが、私はネットワークプログラミングを書く上で、主にBSDsocketを使用するため、Linuxであればとりあえずおkですw

普段の開発については、自宅で仮想OS(VirtualBox)でサーバーを動かし、本番環境ではVPSサーバーをレンタルしました。私は沖縄に住んでいますが、サーバーは東京に配置しています。理由は特にないのですが、単純に東京の方が人口が多いだろう。。。と思ったからですw

ゲーム開発について
私は、ゲーム開発についてはあまり詳しくないため、簡単にゲームが作れるツールを探しました。スマートフォン向けのゲーム(2D)ですと、cocos2d-xかUnityが人気のようです。
どのツールで開発しようか悩みましたが、基本無料、簡単にGUIでゲームが実装できる、C/C++(BSDsocketが使いたいだけ)が使える、という点でUnityを選びました。

使用した言語について
Unityでは、C#スクリプトが使えるため、これを利用しました。ぶっちゃけ、C#はよくわかりませんが、ノリで作りましたw ただ、通信部分の処理はC++で記述していたため、UnityのC#からC++のコードを呼び出す形(ライブラリ化してから)で実装しています。

元々得意な言語はC言語、C++なので、サーバー側ではC++で実装しています。

また、サーバー側や、クライアントの通信処理はeclipseで作っています。



【今後について】
できる限り詳細にソースコードを開示して説明していきたいと思っています。また、頻繁に更新したいとも思っていますが、プログラミングにも夢中なのでアレですがw

このブログを見て、「自分もオンラインゲーム作りに挑戦したい!」と思っていただけたら幸いです!!
テーマ: Unityゲーム制作 | ジャンル: コンピュータ