HSP-TECH 第9弾

Network Game 基礎知識/サーバプログラム

2001.3.31 Written & Programed by Smith
Written Last Fix 2003.5.30

今回からいよいよネットワークゲームの製作に入ります。
とは言え、ネットワークシステムの知識が全くないままいきなりプログラムを組んでしまっては、 必ず後で投げ出してしまうことになるか、はたまた今後のHSP-TECHを理解できないまま、ただただ 提供されるサンプルプログラムを実行しては指をくわえて眺めているだけがオチでしょう。
 オリジナルのネットワークゲームを製作したいと思うのなら、必ず基礎はしっかりと理解しておくべきです。 なぜなら、ゲーム一つ製作するのでさえ簡単ではないのに、それがネットワークという広大な、そして複雑な 世界の中で複数のPlayerを結びつけ、そして楽しさを共有できるものにまで作り上げようというのですから、 そこにたどり着くまでには数多くの難解をクリアしなければならないのです。
 しかし、それは基礎をしっかりと理解、身につけておくことでいくらでも和らぎます。 ま、前置きはこの程度で早速簡単な範囲から理解していきましょう。

ネットワークゲームの基礎構築に入る前に、どのようなネットワークシステムのタイプがあるのかを知り、 そしてどれを採用してその基盤の元で製作を進めて行くかを決定します。
 一般的なネットワークシステムには、大きく分けて二つのタイプがあり、どちらも利点と欠点がありますので それを十分理解したうえで採用を考えましょう。


     ・クライアント/サーバー型 ---- 処理を要求する側と要求された処理を実行し回答する側が明確
     ・ピアツーピア型 ------------- 平等に機能を備えたプログラム同士が同じレベルで処理を行う

 クライアント/サーバー型(以下C/S)とピアツーピア型(以下P2P)、聞いたことがあるような無いような・・・。  簡単な説明をつけてみましたが、これは処理を行うプログラムレベルでの話しであって、これだけではなんだかいまひとつ イメージが沸いてきませんね。
 それでは、この二つのタイプを簡単な図でネットワーク構成の違いを表してみることにします。以下がC/SとP2P、 それぞれのネットワーク構成概略図です。

△ タイプ別のネットワーク構成概略図を見る △

 この概略図は、違いをわかりやすくする為に描いた図であり、これが実際のネットワークそのものの配線図などでは無いことに 注意してください。
 さて、図を見る事で違いがずいぶんと判明してきましたね。P2Pでは、1対多という関係の集合に対し、C/Sでは1対1の関係と それをまとめる1対多の関係であることがまずわかります。これは後にネットワークゲームのプログラムを組む上で非常に重要 なことです。また、C/Sにはサーバーが存在するのに対し、P2Pには存在しません。厳密には、ネットワークゲームにおいてP2P でもC/Sでもホストと呼ばれる役目が必ず必要になってきます。

 では、これら二つの構成を有名な既存ネットワーク(オンライン)ゲームで例えてみましょう。


     ・C/Sの代表例 ----- Ultima Online、Ever Quest、DIABLO IIなど
     ・P2Pの代表例 ----- DIABLO、STAR CRAFT、Age of EMPIREなど
    *同じC/S、同じP2Pでも、ゲームによって違いはあります。

これらのゲームをPlayしたことがある人は、Playした感覚である程度違いが理解できるかもしれません。 そうではなく、まだ1度もネットワークゲームをPlayしたことがない人は、ちょっと感覚で理解するのは厳しいかもしれません。
また、DirectPlayにはロビーというアーキテクチャーが用意されており、これを組み込んだゲームとそうではないゲームでは、 全体の構成としてもまた違ったものになってきます。これについてはHSP-TECHでは触れませんので、興味がある方は自力で調べて見て下さい。
 さて、C/SとP2P、違いがわかってきたような気がしますが、もう少しだけ突っ込んで仕組みの違いを理解してみましょう。


     ・C/S --- クライアント(つまりPlayer側)は、サーバとだけデータのやりとりをすれば良い
      --- サーバは、全てのクライアントからの要求に答え、情報を集中的に管理する
      --- クライアントソフトとゲームサーバソフトの二種類のプログラムが必要である

     ・P2P --- 全てがクライアントであり、自分の情報は自分で管理し他のクライアント全てに情報を伝える
      --- サーバが存在しなくてもホスト役が存在すれば、ネットワークゲームとして稼働できる
      --- クライアントソフトは、全ての機能を兼ね備えた一つのプログラムである

     *ここでいうプログラムとは、ソフトウエアとしてのプログラムの集合も含む

 これでだいぶそれぞれの違いを理解することができましたね。それでは、HSP-TECHで取り組むタイプを決めにかかります。 C/Sは、役割が明確でそれぞれの役目ごとにプログラムすれば良いから簡単そうに思えます。 一方、P2Pは一つの完結するそれだけでゲームとして動作するプログラムを完成させなければなりませんので大変そうです。
 しかし、今のこの言葉だけで判断してはいけません。C/Sは、別個に開発するだけに実は思った以上にアルゴリズムがむつかしく、 負荷が集中するゲームサーバ側にきめ細やかな神経質すぎるほどの負荷軽減の技術を駆使しなければなりません。 また、ゲームサーバとして稼働するハードウエア(PC)を専任で稼働させなければなりません。
ではP2Pはどうでしょうか、同じソフトウエアを持っている仲間を見つけさえすればすぐにゲームPlayできそうですし、 自分がホスト役をすれば一人でもPlayできそうです。ただし、先の関係図からして小人数な規模が限界に思えそれでは少々物足りない もので終わってしまうかもしれません。
 HSP-TECHでは、果敢にも開発がむつかしいと思えるC/S型に挑戦することにします。理由は、拡張性の高さと、HSP+Pluginによる ポテンシャルの高さを確かめるためです。

 さあ、そうと決まれば早速プログラミングに取りかかりますか。
おっと、その前に通信を実現するためのソフトウエアを紹介します。


■ Microsoftが開発した、複雑なProtocolを気にせずネットワークゲーム開発を可能にさせたソフトウエア
  DirectPlay API (IDirectPlay4 COM Object Interface)

■ HSPでDirectPlayによる通信を実現させるプラグイン
  AMdplay:AkihiroM氏による開発で見事、HSPでもネットワークゲーム開発を可能にさせたHSP用ソフトウエア

<AMdplay最終版の入手>
 AMdplayは、AMdplayダウンロードページより入手してください。

 それと、このようなすばらしいプラグインを開発され、さらにフリーウエアとして利用させて頂けること、
 さらに再配布の許可を頂き、AkihiroMさんにこの場をお借りし御礼申し上げます。ありがとうございます。

では、もうAMdplayは入手しましたね?ではいつものごとく、準備から念入りにいきましょうか。

 今回はゲームサーバプログラムの初期処理を組み、クライアントがログオンできるかどうかまでの プログラム作成です。


#include "AMdplayD.as"
#include "m_AMdplaySupport.as"
#define PlayerMAX 15
#define Entry_Play 1
#define m_ec m_AMdplayErrorCheck
#define EM_SETSEL	$b1
#define EM_SCROLLCARET	$b7
#define EM_GETLINECOUNT	$ba
#define EM_REPLACESEL	$c2

 最初の#includeでAMdplayが使えるようになり、次の#include "m_AMdplaySupport.as"ではデバッグ用のScriptを 結合させてますが、実行形式(*.exe)にするときは"m_AMdplaySupport.as"をインクルードする必要はありません。
 定数定義しているPlayerMAXは、その名の通りこのゲームサーバで立ち上げるセッションの最大Player数です。 セッションとは、例えがむつかしいですが、ネットワーク上にロジカルな会議室を作るようなイメージで、この会議室 の定員数がこれでいうPlayerMAXで定義した値となります。
そして次の定義であるEntry_Playは、受信したクライアントからの要求を定義しておくためのもので、後にでてくる データタイプの値を理解しやすいようにEntry_Playとして定義したまでです。
 これらの詳細説明は、もう必要ありませんね。さっさと次にいきますか。
 次のScriptでは、初期処理の一部としてApplication名の設定とサーバログ表示部などの作成をしてます。

	;<<<<<<<<<<	Initialize	>>>>>>>>>>
	screen 0,640,480,1,0,0
	AppName="ARPG sample"
	VerDate="Network Version 2.00 2001/03/27"
	GameName=""+AppName+" "+VerDate
	title ""+GameName
	sdim logmsg,32000,1
	mesbox logmsg,640,480,1
	msg=""
 次にPlayerキャラクタについての情報をいつもながら宣言(確保)しておきます。HSP-TECH講座8で組み込んだ それとは一部違います。それは、ネットワークゲーム=マルチプレイヤーでは設定した参加可能Player分のPlayer キャラクタ情報をサーバでは常に保持しておくということです。
 今回ここまでのPlayer情報は必要ありませんが、後の講座でいづれ組み込むことになりますし、HSP-TECH8ですでに この型の原型を作ってますからここは素直にそれを採用します。

	;-----	Player情報の構造体	-----
	dim player_data,PlayerMAX,64
	repeat PlayerMAX
		dup mx.cnt,player_data.cnt.0	;MAP座標系x座標
		dup my.cnt,player_data.cnt.1	;MAP座標系y座標
		dup ph.cnt,player_data.cnt.2	;キャラの向き
		dup pa.cnt,player_data.cnt.3	;キャラのアニメーション番号
		dup pc.cnt,player_data.cnt.4	;キャラのアニメーションカウント
		dup mf.cnt,player_data.cnt.5	;移動中フラグ
		dup mc.cnt,player_data.cnt.6	;移動中のカウント
		dup mp.cnt,player_data.cnt.7	;移動中1step分の増分
		dup xv.cnt,player_data.cnt.8	;移動中の一時座標x増分
		dup yv.cnt,player_data.cnt.9	;移動中の一時座標y増分
		dup ps.cnt,player_data.cnt.10	;キャラクタ種別番号
		dup pid.cnt,player_data.cnt.11	;DirectPlayID(DPID)
		dup pno.cnt,player_data.cnt.12	;PlayerNo.
	loop
	sdim player_name,256,PlayerMAX
	;-----				-----

次に、AMdplayに対する初期設定を組んでみます。
AMdplayでは、接続(今回はサーバ起動)の方法が2種類用意されており、一つはDialogによってその都度 セッション名やPlayer名、接続先を指定できるタイプと、今回採用した自前接続と呼ばれるプログラムで個々に セッション確立に必要なプロセス(手続き)を組むタイプです。
 P2Pの場合は、あらかじめAMdplayで用意されたセッション確立のためのDialogタイプを採用するほうが簡単に 実現できるのでお勧めですが、P2Pでも自前接続でオリジナルのインターフェイスをゲームに組み込むのもまた ムードがあって良いです。

	;------------------------------------------------------------------
	;通信初期設定(DirectX/Play)
	;------------------------------------------------------------------
	MP_Name="ARPG-Server"	;稼働させるゲームサーバ名(実体はPlayerName)
	BootSes="ARPG"		;ゲームサーバで開くセッション名
	Dest_ADDR="127.0.0.1"	;ゲームサーバのIPアドレス
	Port=""			;ポート番号
	dim Players,1:Players=0	;参加Player数のカウント用変数

	;/////	DirectPlayの初期化
	AMdplayInitialize GameName : m_ec

	;/////	ゲームサーバ名を設定
	AMdplaySetPlayerName MP_Name : m_ec

	;/////	コネクションをTCP/IPで設定
	AMdplaySelectSpecifiedConnection Dest_ADDR,Port,AMDPLAY_CONNECT_TCPIP : m_ec
	if Port=="":Port="****"

 IPアドレスが127.0.0.1というのは、LocalHost(自分自身のPC)のことです、覚えておきましょう。 また、使用するネットワークポートはDirectPlayに任せ、通信ProtocolはTCP/IPで初期化しておくことにします。 TCP/IPを採用することでInternetを利用したネットワークゲームとして稼働することを視野に入れたためです。
 次の設定は、通信の性質を設定するものでかなり重要です。AMdplayのリファレンスもきちんと熟読しましょう。


	;/////	ゲームサーバの通信稼働設定
	Server_flag =     DPSESSION_CLIENTSERVER		; C/Sセッションを指定
	Server_flag = Server_flag | DPSESSION_KEEPALIVE		; 異常プレイヤー自動削除を指定
	Server_flag = Server_flag | DPSESSION_OPTIMIZELATENCY	; 遅延最小最適化を指定
	Server_flag = Server_flag | DPSESSION_NODATAMESSAGES	; データ変更通知無しを指定
	Server_flag = Server_flag | DPSESSION_NOMESSAGEID	; 通信にメッセージIDを用いないを指定
	Server_flag = Server_flag | DPSESSION_DIRECTPLAYPROTOCOL; DirectPlayプロトコルを指定

 当然ながらC/S型を構築しますのでDPSESSION_CLIENTSERVERを指定し、その他の設定事項はデータ転送の 品質や保証よりも速度を重視した組み合わせになってます。Action系の場合、このようにデータが相手に届く 確実性よりも応答性を重視して選択するべきです。これはActionゆえにレイテンシ=待ち時間の対処と言えます。 このことから、同期による通信よりも非同期による通信のほうがAction系には向いており、 Action系の場合、応答性が良いほどPlay感はぐっと良いものに感じます。
 ただしこのことは、後の通信アルゴリズムでゲームPlay途中に転送データがロストしてもゲームとして成り立つ 程度を考慮してプログラミングすることを念頭においての選択です。
 さて、次は一気に先に説明したセッション開設と受信バッファ登録などを組みます。

	;/////	セッションを開く
	AMdplayHostSession BootSes ,Server_flag : m_ec

	;/////	開いたセッションの最大同時接続数を設定
	AMdplaySetSession 1, PlayerMAX : m_ec

	;/////	受信バッファを登録
	sdim netbuf, 1024,1
	AMdplaySetReceiveBuffer netbuf,1024:m_ec
	dup bufmes, netbuf			; 文字列データ受信用バッファ
	dup bufnum, netbuf:int bufnum		; 数値データ受信用バッファ

	;/////	受信情報を格納する変数を登録
	dim info,6 : AMdplaySetReceiveInfo info:m_ec

	;/////	帰り値変数登録
	dim ret_stat,64
	AMdplaySetResultVariable ret_stat

 AMdplaySetResultVariable、そして次で説明するAMdplayGetPointerについては、 AMdplayリファレンスを読んでのとおり、やや難解な仕掛けです。私自身、この部分の概念を理解するのに数回読み直しました。  これに関しては、リファレンスをまず読み、その後Internetで検索してCのポインタに関する解説を熟読してください。
 次は、データ送信時の送信データをどのような性質で転送するのかの詳細設定です。


	;/////	送信ディティールの初期化
	dim send_flg, 5
	AMdplayGetPointer send_flg
	pflg = ret_stat
		dup Set_DataProperty,send_flg.0
		dup Set_DataType,send_flg.1
		dup Set_SendType,send_flg.2
		dup Set_Priority,send_flg.3
		dup Set_TimeOut,send_flg.4	

	;/////	送信ディティールの初期設定
	Set_DataProperty = AMDPLAY_SEND_ARRAY_AUTO		; 送信オプション= 簡易圧縮/自動
	Set_DataType = 0					; データタイプ	= デフォルト:0
	Set_SendType = DPSEND_ASYNC | DPSEND_NOSENDCOMPLETEMSG	; 送信フラグ	= 非同期送信/送達確認なし
	Set_Priority = Normal_Priority				; 優先度	= ノーマルプライオリティ:256
	Set_TimeOut = 0						; タイムアウト	= なし:0

送信ディティールは、あらかじめ想定する何パターンかを初期処理で作っておくと後が楽になりそうです。 HSP-TECHでは、先で述べた通り応答性を重視したデータ転送になるように構成しました。もちろん、これがベストではなく、 ネットワークゲームにおいては特にInternet上での稼働はとにかくテストを重ね、何人ものいろいろな環境からの参加で どのようなものがベストかを検証していくことが非常に大切です。
 ネットワークゲームの開発では、こういった部分にどれだけ時間をかけるかでゲームが活きるかあるいは死ぬか、 それほど重要な問題です。場合によっては、1年間をこれに費やすことにさえなるかもしれません。しかしこれを怠っては、 おそらく散々なモノが出来上がってしまうでしょう。そのようなものに誰が貴重な時間を裂いてPlayしてくれるのか。
 さて、話が熱くならないうちに次で通信の初期設定は最後です。と言ってもこれは確認の為の情報なので、むつかしいことは なにもありませんね。

	;/////	稼働させたゲームサーバのユニークIDを取得
	AMdplayGetMyID Server_ID

	;/////	稼働させたゲームサーバ情報表示	/////
	msg = "Game Server Name : "+MP_Name:gosub *logput
	msg = "Game Server ADDR : "+Dest_ADDR+" / Port : "+Port:gosub *logput
	msg = "Boot Session     : "+BootSes:gosub *logput
	msg = "Game Server  ID  : "+Server_ID:gosub *logput

	;------------------------------------------------------------------

 これでゲームサーバ側の通信準備は、とりあえずOKです。長いですがゆっくりとAMdplayリファレンスと併せて理解して下さい。
 また、logputサブルーチンはmesboxで作った情報表示です。ゲームサーバは、クライアントソフトとは違いグラフィカルな表現は 別段必要ありません。ゲームサーバの稼働状況が掴めれば、あとはデザインに時間を割く必要はありません。
 logputサブルーチンですが、これはAMdplayに付属されているサンプル内から拝借した情報表示用のルーチン名を除いてそのままです。 説明は致しません。
 次はようやく処理として活躍する受信処理です。


*Recieve_Check
	;/////	受信チェック	/////
	result = 0 : AMdplayReceive result
	if result!1 : return

	;/////	データタイプの判断	/////
	if info.0 == Entry_Play : goto *Entry_Player
return

 と言っても、今回はまだクライアントからの接続要求に答えるだけのモノなので簡単です。
AMdplayReceive命令によって受けたデータのinfo.0を確認することでデータタイプを判断します。 データタイプとは、自由に決めることができる処理の識別コードです。自由といってもあまり考えないで使うと 後々非常に痛い目に合いますから、出来れば仕様書作成時に決定しておいてください。このデータタイプが ゲーム内で扱われる通信Protocolの1要素と言えると思います。データタイプ=1ならEntry処理、データタイプ=20なら Playerキャラクタが移動を開始いた、など、実際の通信ではこの値でどんな要求なのか結果なのかといったものを判断 させていくことになります。
 さあ、次はようやく今回の目玉処理です。


*Entry_Player
	;/////	ゲームへの参加要求が届いた	/////
	Players++
	repeat PlayerMAX
		if pid.cnt==bufnum.0:return	;既に存在なら戻る エラー対処はなし
		if pid.cnt!0:continue		;pid.nが空きかどうか、空きじゃんければAMdplay
		pid.cnt=bufnum.0:pno.cnt=cnt:tmp=cnt
		break
	loop
	AMdplayGetPlayerName tmpstr,pid.tmp	;プレイヤー名の取得
	player_name.tmp=tmpstr			;プレイヤー名の保存
	msg = "Player Entry. PID = "+pid.tmp+" : Player No. = "+tmp+" PlayerName = "+player_name.tmp
	gosub *logput:	Set_DataType = 2
	AMdplaySend tmp, 4, pflg, pid.tmp:m_ec	;PlayerNoをクライアントへ返しこれをもってエントリ完了
	msg = "Response:PlayerNo. = "+tmp:gosub *logput
return

 目玉だけにコメントを多く入れましたが、加えて解説すると、クライアントからのエントリ要求に対し、 クライアントが送信してきたDirectPlayID(DPID)をPlayer管理用配列変数に格納し、今度はゲームサーバ独自管理用 のNo.をクライアントに対し発行してあげます。
 それをクライアントが受け取ることにより、サーバ側とクライアント側双方の本当の意味でセッション確立です。
 さて、仕上げはメイン処理のみです、これは今回載せるまでもないくらい簡単です。


*main	;<<<<<<<<<<	メイン処理	>>>>>>>>>>
	await 50
	gosub *Recieve_Check
goto *main

 ご説明するまでもないですね?メインループ中に受信チェックをしているだけです。
今回はまだこんな程度ですが、それでも準備で数多くのことを考えてプログラミングしなければなりません。 正直、これほどまで組んでまでゲームにすらなっておりません、クライアントプログラムすら出来てません。 ここまで読んで、きっと「ダメだ俺には無理だ理解不能」と思ってしまう方もいるでしょう。でもめげないでください。 もう少しがんばれば、小さいながらもC/S型のネットワークゲーム完成が待ってます。それを土台に拡張すれば、 とんでもなく立派なネットワークゲームにも成り得るのです。辛抱強く今後もHSP-TECHで技術を身につけましょう。
 次回はクライアント側のプログラミングです。
それに先だって今回はサーバプログラムとクライアントプログラムのセットをソースでアーカイブしておきました。
 サーバプログラムを一つ起動させたら、クライアントプログラムを順番に複数起動させてみてください。 きちんとエントリされていく様子が、サーバプログラム側、クライアントプログラム側ともにメッセージで出力されます。
 以下、実行時のメッセージ一例です。


********** サーバ側ログ **********
Game Server Name : ARPG-Server
Game Server ADDR : 127.0.0.1 / Port : ****
Boot Session     : ARPG
Game Server  ID  : 1
Player Entry. PID = 29564886 : Player No. = 0 PlayerName = SMITH
Response:PlayerNo. = 0
Player Entry. PID = 29564880 : Player No. = 1 PlayerName = Perry
Response:PlayerNo. = 1
Player Entry. PID = 29564881 : Player No. = 2 PlayerName = Joe
Response:PlayerNo. = 2
Player Entry. PID = 29564882 : Player No. = 3 PlayerName = Aero
Response:PlayerNo. = 3

 最後に、今回のHSP-TECHは、記事を書くだけで9時間もの時間を要しました。
おかげでDI2開発は今日はおやすみですが、その代わりにこのHSP-TECHをきっかけに多くの HSP製ネットワークゲームクリエイターが誕生することを期待致します。
 また、最終的なモノはHSP-TECH第8弾で製作したあのARPGがC/S型で動作できる予定です。
 予定というかなります(自信
 それでは、次回に。

▽ここまでのスクリプトのセットをDLする(5kb)▽

  HSP-TECH第10弾へ

INDEXへ戻る