メイン

2012年08月16日

あらためてAIMSでSTGを作る

ikafont.jpg
以前グラディウスっぽいゲームを作るという企画があって
こんな感じのフォントも作ってたりしたんですが、
色々あってほかのゲームを作ることにしました。

冬コミにも申し込んだし新しい企画のほうの制作日誌でもつけようかと思います。

自機のプログラムは大体できていて
ステージスクリプトを読み込んでそれに従って敵が出たりするとこまで動いています。

ステージクリア時のリザルト画面をどう実現するべきなのか悩んでいるところです。
ステージシーンの中でやるべきなのか?
別のシーンをステージと平行動作させるのがいいのか。

あとステージごとに別のシーンにするほうがいいんだろうな・・・。

2011年02月15日

AIMSでグラディウスっぽいSTGを作る(11)

list1.gif
drawGraphicList()を使うことでグラフィックチップを帯状に変形させて表示することができます。
この画像のような誘導レーザーっぽいものを表現するのに向いた機能です。

しかしながら当たり判定矩形の配置方法が通常のグラフィックチップとは違うようなので調べてみました。

通常のチップはアクターの回転や拡大に応じて当たり判定の大きさや位置も変化します。
ただし45度回転させても矩形がひし形になったりはしません。(詳細はAIMS本 P.67)

帯ポリゴンに関しても基本的にはそれに従っているのですが、
原点の位置や初期角度の違いが影響して、おかしく見えるようです。

list2.gif
帯ポリゴンに使用する画像は縦に分割されるため、縦長にしておく必要があります(1参照)。
これに当たり判定矩形を設定するには2の位置にするのが自然でしょう。
ところがそのつもりで矩形を設定してゲーム内で表示すると3の位置に当たり判定が発生します。

3の状態は元画像と同じ方向を向いているので回転していないように見えますが、
実はアクター、帯ポリゴンともに270度回転させた状態です。
回転していない場合は4のように右向きになります。

なお、この時のアクターの原点は5のあたりにあります。

ということで、矩形を帯ポリゴンの中に設定したい場合は、
見た目よりも左にしなければならないようです。
具体的に何ドットなのかまではまだはっきりわかっていません。

2011年02月02日

AIMSでグラディウスっぽいSTGを作る(10)

もうAIMSならではのコーディングが必要なところが少なくなってきました。
とりあえず以前やったマップチップとの当たり判定についてです。

以前のものはマップチップ番号が0か、それ以外かで判定していました。
これを例えばチップ番号16番以下は当たり判定なしとすれば
すり抜けられる背景を実現できますが、当たり判定の境界となるチップ番号を
固定にするとチップ作成の自由度が減りますし
可変としても、やはりすり抜け可能なチップを左に集める必要がありますし、
値をいくつにしたかという情報が必要になります。

と、いうことでこれらを解決する手段の一つとして
当たり判定情報を別のマップデータとして用意する方法を試してみます。

sample5.jpg
AIMSで用意されているマップエディタはレイヤーを作成することができ
それぞれに別のマップチップ画像を割り当てられます。

sample6.jpg
これで見た目と当たり判定の情報を完全に分離することができます。
もちろんデータ量は2倍になるわけですが気にしないことにします。


このようなレイヤーを持ったデータをfmf形式で出力すると
lua側では地形データが mapchip.data[0] に、当たり判定データが mapchip.data[1] に
格納されます。

当たり判定データも mapAllocate() で lua に渡してもいいのですが
表示には使わないにもかかわらずレイヤーをひとつ占有してしまうので
オススメできません。
そのまま配列として扱うのがいいでしょう。

以前は mapRead() でチップ番号を読み取っていましたが
そのかわりにインデックスを自分で計算して参照位置を決めます。
mapRead(x, y)

mapchip.data[1][x + y * mapchip.mapWidth]

2011年01月28日

AIMSでグラディウスっぽいSTGを作る(9)

sample4.jpg
今回はゲームオーバーの作り方です。
これはゲーム次第で全然違ってくるところですから、ひとつの例ということで。

最後の自機が死んだら、爆風が消えた後静止して
「GAME OVER」の文字を出すという仕様にします。

静止させる方法はAIMS本の5-3「複数シーンの並列動作と一時停止」が
とても参考になります。

setSceneFreeze()でシーンを停止させることができます。
これによってそのシーンに属するアクターやスレッドも止まる
(画面表示だけは行われる)ので、停止させるのは簡単です。

停止を再開させるためには並列動作するシーンが必要になるので
setSceneFreeze()の前にaddScene()で gameoverシーンを起動します。

gameoverシーンではcreateTextActor()で文字表示アクターを作成します。
ここで作成したアクターはsetSceneFreeze()の影響を受けずに動作するようです。
OnStepではボタンが押されるまで待ちます。

ボタンが押されたらフリーズを解除し、changeScene()でフリーズを解除したシーンを
タイトル画面のシーンに切り替えます。
そして自分自身をcloseScene()で終了させます。
ここではフリーズしているシーンを操作する必要があるので
グローバル変数などでシーンIDを保持しておかなければなりません。

function game_OnStep()
  if 自機が死んだ then
    setSceneFreeze(V.scene, true)
    addScene("gameover")
  end
end

function gameover_OnStart()
createTextActor(G.font, "GAME OVER", 320, 240,
LAYER_INDICATOR, 255, 255, 255, 255, ALIGN_CENTER)
end

function gameover_OnStep()
if getJoyPressCount(0, BUTTON_TRIG1) == 1 then
setSceneFreeze(V.scene, false)
changeScene(V.scene, "title")
closeScene()
end
end


2011年01月26日

AIMSでグラディウスっぽいSTGを作る(8)

そろそろ自機が死ぬようにしたいのですが
そのためにはシーンをちゃんと作らないといけません。

普通はゲームオーバーになったらタイトル画面に戻ります。
タイトル画面は独立したシーンとして作るのが普通です。
よってシーンを作る必要が出てくるわけです。

公式サンプルのSUiCA32ではサークルロゴ表示をひとつのシーンとして用意し
そこでデータの読み込みをしています。
読み込み自体はローダースレッド内で行い、メインスレッドはそれが終わるのを待ちます。

時間がかかるような大きなファイルの読み込みをメインスレッドで行うと
AIMSが応答なしになってしまうので、この形が推奨されています。

SUiCA32では"LOADING"の文字を表示するためにPXFというアクタークラスが使われていますが、
AIMS1.40にはcreateTextActor()で同等の機能を持ったアクターが使えるので
そっちを使うと楽です。

createTextActor()の引数を見ると、一つのアクターで表示できる文字列は
固定のように思えるかもしれません。
しかし、setActorString()という関数が用意されているのでこれを使えば
いつでも好きなだけ書き換えが可能です。

実際、スコアの表示にも使っていますし。こんな感じで。


function score_OnStep()
setActorString(string.format('1P %8d HI %8d', getScore(), getHiScore()))
end

そんなわけでテキストアクターを使うこと以外は
SUiCA32そのままでいいと思います。

2011年01月16日

AIMSでグラディウスっぽいSTGを作る(7)

sample3.jpg

適当に地形を作って、ミサイルがそれに沿って進むようになりました。

画像ではミサイルが地面から離れて見えますが
これはマップチップ番号が0以外の場合は全て当たり判定を持つようにしているためです。
チップのほとんどが透明であっても判定があるので。

ミサイルの挙動は、
1.落下中かつ、ひとつ下のチップに判定があるなら、水平移動に遷移
2.水平移動中かつ、ひとつ下のチップに判定がないなら、落下中に遷移
となっていて、これをミサイルのOnStep()に記述します。

ミサイルは段差を乗り越えたりはしないので
水平移動中に前方にある地形と接触したら消えなければいけませんが、
これはマップチップ側の当たり判定ロジックがやってくれます。


マップチップが動かない場合(スクロールを除く)は、ここまでで十分なはずです。
が、ミサイルが上を滑っていくような敵(グラIIのホッピングモアイとか)や
背景が動く場合(グラII最終面カニ前のせり上がる床など)には対応できません。

このへんも何とかしたいところです。

2011年01月13日

AIMSでグラディウスっぽいSTGを作る(6)

今回は公式で配布されている SUiCA32 を改造して、地形との当たり判定を実装します。

AIMSにはマップチップの描画機能が用意されているので表示はそれを使いますが、
当たり判定は完全に自前でやることになります。

editor.jpg
マップデータはAIMS付属のMapEditorを使って作成します。
チップのサイズは16*16、マップのサイズは64*64にして、
こんな感じに適当に木を生やしてみました。
(マップチップ画像はマップエディタPlatinumに同梱のサンプルを使っています)

fmf形式でマップデータを出力したら適当なディレクトリに
マップチップ画像と一緒に放り込んでおきます。


次はスクリプトに手を入れます。
ちょうどいい感じにレイヤー9が使われていないのでこれを使います。

#game_common.lua
LAYER_MAPCHIP = 9

マップチップの表示は専用のアクターで行います。
gameシーンの開始時に生成するので game_OnStart()内でcreateActor()を実行します。
このアクターは通常の画像表示をしないので透明のチップハンドル(G.common.clear)を渡しています。

#game.lua
A.mapchip = createActor(G.common.clear, 0, 0, LAYER_MAPCHIP, 'mapchip')

アクターの実体はとりあえずplayer.lua内に定義します。
本当は別ファイルにしたほうがいいんですけど。

#player.lua
function mapchip_OnStart()
G.mapchip = loadGraphic('gfx/empire-3.bmp', 0, 117, 117)
local t = loadFMF('gfx/test.fmf')
mapAllocate(LAYER_MAPCHIP, t.mapWidth, t.mapHeight, G.mapchip, 16, 16, 0, t.data[0])
mapSetWrapMode(LAYER_MAPCHIP, OUTSIDE_NONE, OUTSIDE_WRAP)
mapSetVisible(LAYER_MAPCHIP, true)
setLayerScroll(LAYER_MAPCHIP, 0, 0)
end

function mapchip_OnStep()
local x, y = getLayerScrollX(LAYER_MAPCHIP), getLayerScrollY(LAYER_MAPCHIP)
y = y - 1
setLayerScroll(LAYER_MAPCHIP, x, y)

for index, id in pairs(iLayerActors(LAYER_EBULLET)) do
ex, ey = mapGetWrappedPos(LAYER_MAPCHIP,
bit.arshift(getX(id), 4),
bit.arshift(getY(id) + y, 4))
if mapRead(LAYER_MAPCHIP, ex, ey) ~= 0 then
vanish(id)
end
end
end

function mapchip_OnVanish()
end

suica.jpg
これで、木がスクロールし、敵弾が木に当たると消滅するようになりました。
問題は色々ありますが。
最大の問題は当たり判定の大きさです。
敵弾の中心が当たり判定のあるチップ(16*16ドット)の内部に入った時に接触と見なすため
敵弾がもっと大きい判定を持っている場合に困ってしまいます。

また mapRead() ~= 0 と書いてあるとおり、チップ0番以外は全て当たり判定を持つと
見なすようになっているので、当たり判定を持たない背景が実現できません。

これらも追々解決しなければいけません。

2011年01月11日

AIMSでグラディウスっぽいSTGを作る(5)

敵キャラが出現して、それを破壊できるようになりました。
が、絵面はあまり変わらないのでSSはなし。

このあたりの処理はAIMSならではというところはあまりありません。

ショットやミサイルのOnStepでgetHitLayerを呼んで
当たっている敵の体力を減らしつつ自分はvanishで消滅。
敵のOnStepで体力が0になったらvanishを実行するだけです。
体力の値はVARスロットのどれかをそれ用に割り当てます。


敵のOnVanishでは cause==KILL_ORDERED だったら
爆発エフェクトのアクターを呼び、スコアをカウントアップするようにします。

これでかなりゲームらしさが出てきます。
自機はあいかわらず無敵のままなんですけどね。
自機が死ぬ処理を実装するにはシーンにも手を入れないといけないので
なるべく後回しにしておきたいところです。

2011年01月08日

AIMSでグラディウスっぽいSTGを作る(4)

sample2.jpg
ぱっと見では前回とあんまり変わりませんが、カプセルが出現するようになり
それを取ってグラディウス式のパワーアップが出来るようになりました。

さすがにパワーアップの制御ロジックはAIMSですることではないので
それなりにめんどうです。

カプセルを取る、つまりカプセルとの当たり判定はAIMSの出番です。
当たり判定矩形はチップ1枚ごとに設定する必要があるので、
アニメーションするアクターの場合は全てのチップに矩形を設定します。

当たり判定の実行は二つの関数が用意してあり、
isHitActor で一対一、getHitLayer でレイヤーに含まれる全てのアクターとの判定を行えます。
シューティングのような不特定多数のアクターが出現する場合は getHitLayer がいいでしょう。

自機アクターの onStep 内で getHitLayer を実行します。
当然カプセルの属するレイヤーは自機や自弾とは別にしておきます。
実行後、接触しているものがあればアクターハンドルの入ったテーブルが返るので

capsules = getHitLayer(LAYER_ITEM)
if #(capsules) > 0 then
for index, id in pair(capsules) do
vanish(id)
end
end

とすれば接触したカプセルを全て消滅させることが出来ます。

2011年01月07日

AIMSでグラディウスっぽいSTGを作る(3)

sample1.jpg
それらしく動くものを作ってみました。
敵は出ないので当たり判定ロジックは入っていませんし
パワーアップも最初からフル装備で固定です。

この程度なら簡単に作れてしまいます。

これを作っていてAIMSのアニメーションの仕様を理解できました。
AIMSのチップハンドル(cutGraphicなどで作成する)は
個別に「自分の表示フレーム数」「次に表示するチップハンドル」という情報を持っています。

ここに値を設定するには setGraphicAnime/setGraphicAnimeLoop/setGraphicAnimeSequence を使います。

アニメ情報を設定したチップハンドルをcreateActorに指定してアクターを生成すると
自動的にアニメーションが行われます。

setGraphicAnimeLoop({G.pat[1], G.pat[2], G.pat[3]}, 5, 1)
としてアニメ情報を設定し
createActor(G.pat[1], 0, 0, 1)
このようにアクターを生成すると、 1 > 2 > 3 > 1 の順にアニメーションがループします。

createActor(G.pat[2], 0, 0, 1)
であれば 2 > 3 > 1 > 2 の順にループします。

1 > 2 > 3 > 2 > 1 の順にループさせたい場合は
setGraphicAnimeLoop に渡すテーブルを {G.pat[1], G.pat[2], G.pat[3], G.pat[2]} にしたくなりますが
これではダメです。
2の次に表示するチップが「2 > 3」「2 > 1」の二種類存在することになってしまいます。
次に表示するチップは1種類しか指定できませんからどちらか一方しか有効になりません。

実際には「2 > 3」の設定が「2 > 1」で上書きされるので 1 > 2 > 1 というアニメになってしまいます。

ではどうするかというと、cloneGraphic を使って 2 のコピーを作って
それを4としてループを作成すればいいのです。

G.pat[4] = cloneGraphic(G.pat[2])
setGraphicAnimeLoop({G.pat[1], G.pat[2], G.pat[3], G.pat[4]}, 5, 1)

これで期待通りのアニメーションになります。

2011年01月06日

AIMSでグラディウスっぽいSTGを作る(2)

AIMSでは動くものは基本的に全てアクターです。それとは別にシーンというものもありますが。

そしてアクターには必ずひとつ以上の画像と結びつきます。
ここが少し特殊なところですね。
例えば編隊を組んだ敵を全滅させるとボーナス取得、とかいうのの実現には
編隊自体を表すアクターが必要になってきますが、
表示すべき画像が存在しないこのアクターについても画像が必要です。

そういったアクターのために1*1ドットの透明な画像をダミーとして
用意するのが良さそうです。


アクターを動かすには前述のとおり画像(グラフィックハンドル)を用意して
createActorで生成したあと、addMoverで大まかな動作を指定するだけという、簡単なのがいいですね。
画面外に出たら消滅というのも自動で行わせることができますし
画像を複数登録しておけばアニメーションも簡単です。

それだけでは済まない場合になって初めて、アクタークラスを用意することになります。
クラスといってもオブジェクト指向的なアレではなくて
(クラス名)_OnXXX というファンクションを三つ用意するだけ。

継承とかが使えないのはそれはそれで物足りないところもありますが
そのへんは割り切ってしまいましょう。
共通部分をファンクションにくくりだして呼び出せばいいだけです。
(Lua自体はクラスが実現可能な仕組みをもっていますがAIMSには不向きなのです)


ということで今回は終わり。

2011年01月03日

AIMSでグラディウスっぽいSTGを作る(1)

去年は色々あって創作活動をほとんど何もしていなかったのですが
今年はもう少しがんばろうと思います。
去年の問題は今なお継続中ですがそれを理由にしていると何もできなくなるので・・・。

と、いうことで去年末に知ったAIMSで色々やってみるつもりです。
AIMSというのはD.N.A.softwaresさんが開発したアクション系2Dゲーム開発に特化したゲームエンジンです。
ベースの言語はLuaなので、覚えやすくて高速だしコンパイルも不要です。
背景や地上敵が存在しないような縦STGならば驚くほど簡単に作れそうなところが魅力的ですね。

ネットで検索してみると、これを使ってゲームを制作しているという話はほとんど引っかからないし
サンプルも本家で公開しているものしか見つからないので、
同じようにAIMSでゲーム制作をしたい人のためにも情報を発信していけたらなぁと思います。

題材はタイトルにもあるように「グラディウスっぽいSTG」です。
グラディウスといったらオプションとかマップチップを使った背景とか
そういう要素が必須になってくるので、AIMSの使い方を覚えるのにも丁度いいかなぁ・・・。

2010年05月31日

ウィンドウの一部をDirectXで描画するには・・・

仕事の都合でもうしばらくの間、活動が滞っています。
やはり同人活動はリアルの生活に安定が保証されていればこそ出来るものですし・・・。

それはそれとして、最近はテクスチャのアニメーションをチェックするための
ツールを作ろうかと思って色々と調べているところです。

Windowsでのゲームプログラミングはそれなりに長いことやってきてはいますが
ウィンドウアプリケーションの作成については、未だ素人同然なので苦労しています。

で、タイトルのところで詰まっているわけです。
DirectXを使わないならビットマップをどうにかして出来そうなんですが
DirectXだとウィンドウ全体に対して描画する方法しかわからず・・・。

私が今も愛用しているDirectXライブラリ(Luna)もゲームに特化しているので
そういったツール作成用途は難しそうです。
しかしLunaで使われているテクスチャファイルフォーマットを利用する関係で
切り離せないのです。

どうしたものか・・・。

2008年06月05日

Access Violation

VCのデバッグモードで動作テストして問題なかったのに
直接起動するとAccess Violationで落ちるというのはなぜだろう・・・?

しかも落ちるのがnewでメモリを確保している箇所全般なのだから
ますますわかりません。
エラーになるところをコメントアウトすると、その次にnewしているところで落ちるという・・・。

2007年03月01日

ネットワークプログラミング(4)

30FPS描画は試してみたんですけど、劇的な効果が出ている感じはないような、あるような・・・。
ただ、それ以前の問題として30FPSの動きのガタガタ加減は許せないです。
やっぱり60FPSは死守したい。

プログラム的には実用レベルに近づいてきたので、もうそろそろネットワーク越しのテストが出来そうな予感です。


そういえばおかみさんの必殺技とかの名前が仮称のまんまだったなぁ。どうしよう・・・。
真紀子フックはまだしも、SAの「ハイバーベロシティ」とか、いくらなんでもこれはマズイっす。
っていうか、これの元ネタがわかる人っているんでしょうか。

2007年02月26日

ネットワークプログラミング(3)

前からわかっていたことではありますが対戦時のキャラの表示が重いのが
ネット対戦でネックになっている感じですね・・・。

他のゲームではどうやってパレット違いのキャラを表示しているのでしょうか?

VRAMの消費量を考えるとリアルタイムでテクスチャ展開するしかないような気がするんですが・・・重いんですよね。
描画だけ30FPSに落とすっていう後ろ向きな方法もあるにはありますが、まあ最後の手段ということで。

2007年02月25日

ネットワークプログラミング(2)

先日のエントリーに書いたことは、かなり間違ってました・・・。
実際にはディレイ値の分だけ受信と送信はずれるので、受信待ちの時間は十分にあります。

とりあえずコードはそのままでリリースモードでテストしてみたらディレイ1でほぼ常時60fpsでることが確認できました。まあ、LAN対戦でのテストなのでまだ安心は出来ませんけど。

2007年02月23日

ネットワークプログラミング

とりあえずUDPで通信が可能になりました。
が、無茶苦茶重い・・・。TCP版より重い気が・・・。

で、ちょっと考えました。

キーデータを送信した直後に、相手からのキーデータの受信待ちをしているのが間違いの元ですね。
1フレーム内における処理の順序が

1.Sleep()
2.キーデータ送信
3.キーデータ受信
4.キャラを動かす
5.画面描画

ってなっているんですけど、これだと2と3の間の時間はゼロに等しいので、いかにUDPといえども間に合いっこありません。

1.キーデータ送信
2.Sleep()
3.キーデータ受信
4.キャラを動かす
5.画面描画

こうすれば、データの受信自体はスレッドでやっているので、Sleepしている間にデータが届いている可能性が高くなり、受信待ちで処理落ちする可能性も低くなります。

この考えに到達してからth075casterのソースを見ると、受信と送信でロジックが分かれているのも理解できます。目からウロコですね。

2007年02月16日

なんという凡ミス・・・

今までずっと気が付かなかったのですが、1フレームで2回キーの入力待ちをしてました・・・。

おそらくこれを修正するだけでTCP版通信対戦の速度はかなりマトモになると思います。
TCP版がやたらと遅いのはコーディングがヘボいからだとばかり思ってたんですけど、このバグのせいだったんですね。

といっても、せっかく途中まで作ったUDP版を捨てるのももったいないのでUDP版の作成は続けますけど。

2006年11月20日

ネットワークプログラミング (11)

なんか、ここ数日の日記がmixiに反映されないな・・・。
それはそれとして。th075caster のソースの解析を始めました。

UDPのパケットロスにどう対応しているのか非常に気になっていたんですが、なんという逆転の発想・・・。
1フレームに付き、10~20回パケットを送信してどれかが届けばそれで良いということらしいです。
つまり再送なんて最初からする気がないわけです。

空きバッファを探すところを読み間違えていました。
あー、恥ずかしい。

2006年11月19日

ネットワークプログラミング (10)

光-ADSLの対戦でFPSが30程度しか出ないという結果が出ているそうです。

ううっ、光-光なら時々ラグが発生はするものの60FPS出ていたので
ここまで遅くなるとは思ってもみませんでした。

何か根本的な対策を立てないといけませんね・・・。
やっぱりUDPを使わないとダメなんでしょうかね。


何はともあれ、テストしていただいた方には感謝します。

2006年11月16日

ネットワークプログラミング(9)

入力をバッファに溜めて、数フレームおきにまとめて送受信するようなプログラムにしてみたところかなりの速度向上がありました。

毎フレーム送信のときはLANでも50FPS程度しか出なくて、あきらかに処理落ちしているのがわかるレベルだったんですが、
1フレームおきに送信するようにしたら60FPSを若干下回る程度まで出るようになりました。これくらいになるとほぼ違和感無く操作が出来ますね。
リリースモードでコンパイルすれば常時60FPSで安定するかも・・・?

って、LANで60FPSが出ても意味ないんですけどね。
光対光で60FPS出るようでないと。

でも、実用レベルまでもうちょっとって感じですね。
頑張ろー!

2006年11月09日

ネットワークプログラミング(8)

とりあえずLAN対戦が不完全ながらも動くようになったので試してみました。

そしたら、リリースモードでコンパイルしていないためかどうかわかりませんが
50fps程度しか出ませんでした。
LANで60fps出ないのか・・・。

リリースモードでコンパイルして多少改善したとしても
ディレイ通信モードを用意しないとネットワーク越しの対戦は無理っぽいですね。

2006年11月07日

ネットワークプログラミング(7)

他のネット対戦ゲームではディレイを設定できるらしいということは
以前から知っていたんですけど、それを使ってどうやって通信するのかが謎のままでした。

が、某ゲームのマニュアルを読んで、ようやくわかりました。
ディレイの数値の分だけまとめてデータを送受信するんですね。

単位時間当たりの通信回数を減らすことで、スムーズな通信を行うことができる、と。

とはいえ、ディレイを大きくしすぎると、実際の入力からゲームに反映されるまでの時間が大きくなるので遊びにくくなります。
まあ、ほどほどに値を設定しないとダメなわけですね。


やー、目からウロコな感じです。
とりあえず、今はディレイのことなんて考えずに作っていたので、
すぐに実装するわけには行きませんが、よく検討していきたいと思います。

2006年10月27日

ネットワークプログラミング(6)

非同期ソケットによる接続がやっとうまく行ったようです。
何だか苦労したな・・・。

基本的には 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.に戻ります。


これで上手く動いているみたいです。

2006年10月25日

WSAEWOULDBLOCK

コネクション確立までは非同期ソケットで処理しようと思ったら、なんかやたらとめんどくさいですね。
WSAEWOULDBLOCK が出たときに、それが解消されるまで待つ必要があるんですけど、解消されたかどうかをどうやって調べれば良いのかが、まだよくわかってません。

ウィンドウメッセージを使うことは出来ないし。
select() で書き込み可能になったかどうかを調べれば良いのかな。


bind() や listen() で WSAEWOULDBLOCKが返ってくる可能性は考えなくて良いのだろうか・・・。
MSDNの戻り値には書かれてないから考えなくて良いのかもしれないけど、なんか不安ですね。

ネットワークプログラミング(5)

テストプログラムで実際にインターネット越しの通信テストをしてみました。

TCPだと30fpsくらいしか出ないのかなーと思ってましたが、ほとんど常時60fps出ますね。
時々ラグが発生してましたけど。
ゲーム中にラグが発生した場合にどれくらい気になるかは・・・実際にゲームに組み込んでみないことにはわからないかもしれませんね。

とにかく、今回のテストは成功だったので、アルファ版の開発に着手しようと思います。
もしかしたら11月の早い段階で公開できるかも?

2006年10月21日

ネットワークプログラミング(4)

サーバ、クライアントともにパケットを送信したあと相手からパケットが来るのを待つようにしたら、秒60フレーム出るようになりました!
でも、しばらくすると秒17フレームで安定してしまいます。何故じゃー!!

パケットが来るのを待つロジックは

while (queue->IsEmpty()) {};

っていう無限ループにしてあったんですけど、やっぱりこれがまずかった様子。

while (queue->IsEmpty()) {Sleep(0)};

↑このようにスリープを入れたら秒60フレームで安定するようになりました。
CPU使用率も100%になってないし、これでよかったのかな。

2006年10月20日

ネットワークプログラミング(3)

通信に必要な機能を最低限実装したクラスが出来たのでテストしてみました。

・受信は別スレッド
・クライアントはパケットが到着したらパケットを返信
・サーバはパケットの送信後に同一フレーム内でパケット受信を待つ
・クライアントとサーバはひとつのPCで動作

こんな条件でテストしてみたんですけど・・・無茶苦茶重い・・・。
秒10フレーム程度しか出ません。
このまま実装してもゲームにならないですね。


まだ、当初想定していたアルゴリズムとは違うので、TCPでももっと速く出来るはずだと思いますけど、それにしてももう少しマシなスピードで動くと思っていたのでショックです。

2006年10月19日

ネットワークプログラミング(2)

TCP通信だけを行うサンプルは動いてくれたので、今はTCPパケットを格納するキュークラスを作成中です。

マルチスレッドで動作させることを考えるとクリティカルセクションを使う必要がありますね。
使ったことないけど、きっとmutexとほとんど同じだろう・・・。

2006年10月16日

Accept

ネットワークプログラミングの続き。

サーバ側は accept()で接続を待ち受けるんですが、この関数にはタイムアウトがありません。
なのでひたすら接続待ちをし続けることになってしまいます。中断することが出来ません。

UNIXならばシグナルを送ることでタイムアウトさせることが出来るんですが、Windowsで同じことを出来るんでしょうか・・・?


色々調べてたら、accept()を使わず select() で read 出来るかどうかを調べて接続とみなす方法があるみたいです。select()ならタイムアウト時間を自由に設定できますからね。

それにしても、こんな初歩的で重要なことなのに Web上では解決方法がろくに見つからないのは一体・・・。

2006年10月11日

ネットワークプログラミング

体験版も公開したので、さっそくネットワーク対応に着手です。
プログラム的には検証用のスケルトンとDLL読み込み部分を作っただけですが。

まずはTCP版を作る予定ではありますが、UDP版について非常に有用な助言をもらったのでこっちも夢ではないような気がしてきました。

UDPだとパケロスの可能性があるため、再送要求を出す必要が出てきます。
しかし、その再送要求もロストする可能性があり、再送要求の再送要求って感じに泥沼化することもあるわけです。

再送要求のような重要なパケットはTCPで送り、スピードが求められるキー入力等はUDPで送るということにすれば、一挙に解決ですよ。
確か、ひとつのポートにTCPとUDPの両方をバインドすることも出来たはずです。

これでかなり前進したような気がします。

2006年09月13日

試行錯誤してみる

「無敵格闘娘」でぐぐって見に来てる人が急に増えたなと思ったら、某なりた氏が雑記でコメントしてたからだったんですね。
「今後に期待」と書かれていて、ちょっと嬉しいです。

さて、ネット対戦の実装について、試行錯誤してみたいと思います。
前提条件としてはこんな感じでしょう。
・Winsockを使う
・UDPを使う
・やり取りする情報は基本的にキー入力のみ
・パケロスは認めない

UDPを使うということはパケットの送受信に信頼性が無く、再送処理なども自前でやる必要があります。
基本的な通信の流れは次のようになると思います

1.クライアント(2p側)がキー情報をサーバ(1p側)に送信
2.サーバがキー情報を受け取って1p側のキー情報をクライアントに送信
3.ゲームループを処理して1.に戻る

パケットロスが無ければこれだけでいいんですが、そうはいきません。

対処法その1
受信側は.データを受け取ったら受領シグナルを返す。
送信側に一定時間経っても受領シグナルが来なければ再送する。

対処法その2
受信側に一定時間経ってもキー情報が送られてこなかったら再送シグナルを送る

こういった処理が必要になるはずです。

また、送受信を行なうロジックはスレッドを立ててそっちでやる必要があります。
送受信の待ち時間が発生する以上、メインのスレッドで待つわけにはいきませんからね。


対処法1は受領シグナルが来なかった場合、受領シグナルがロストした可能性もあるのであまり良い方法ではないかもしれません。
対処法2で実装するべきか・・・?でも再送シグナルの到着が遅延したときに困るなぁ。
受領、再送シグナルの両方を使うといいのかな。

うーん、いきなり詰まったな・・・。

2006年07月11日

通信対戦花盛り

最近は同人ゲームでもネットワーク対戦に対応したものが増えてきてますね。

私はそっち系のプログラミングの知識が無いので指をくわえてるだけだったんですけど、やっぱり勉強しておく必要があるかなと思い始めました。

というわけで、早速ぐぐる。

うーむ・・・あまり有益な情報が見当たりませんね。
DirectPlay を使うことになるのかな。

通信の方法自体は調べればすぐわかりそうなのでいいとして
問題はどうやって同期するか、ですね。
キー入力情報だけ送受信してても同期できるのかな・・・。


  1. クライアントがキー入力を送信
  2. サーバが受信
  3. クライアントに受け取ったクライアントの入力とサーバの入力を送信

基本はこうかな。

問題はラグと取りこぼしをどうやってフォローするか。
ここがわからないんですよねぇ。

まあ、おいおい調べていきます。