2007年12月15日土曜日

例外処理について

例外処理は難しい。

しかし、例外をうまく処理すれば、エラーやバグの原因をすばやく突き止められるはず。

例外はただcatchしておけばよいっていうものでは無く、catchしたあとで誰に宛ててエラーメッセージを表示するかを考慮する必要がありますね。

Missileフレームワークの場合、例外は以下3つに分類されます。
1.ユーザーに宛てる例外
2.スクリプターに宛てる例外
3.プログラマーに宛てる例外

それぞれの名宛人の定義は、
 1は、Missileフレームワークで作られたゲームをプレイしている人
 2は、Missileスクリプトを記述する人
 3は、Missileフレームワークをプログラミングしている人
です。

1は、ユーザーのシステム環境が原因で発生するエラーが主です。
例えば、DirectXのバージョンが古いとか、メモリが足りないなどが考えられます。

2は、スクリプトの記述ミスで発生するエラーです。
例えば、スクリプト中で存在しないファイルを指定した場合などが考えられます。

3は、Missileフレームワークのバグで発生するエラーです。
ぬるぽとかそれ系です。

ApplicationExceptionを継承して、UserExceptionとScriptRuntimeExceptionを用意します。

1は、キャッチして、UserExceptionをthrowします。例外メッセージには、ユーザーが読んで理解できるようなことを入れておきます。

2は、キャッチして、ScriptRuntimeExceptionをthrowします。例外メッセージには、スクリプトのバグの原因を突き止められるような内容を入れておきます。

3は、キャッチしないようにします。その方がVisualStudioでデバックしやすいからです。


そして、UserExceptionとScriptRuntimeExceptionをメイン画面のフォームでキャッチして、例外メッセージをダイアログボックスに表示します。そしてプログラムを終了させます。


こんな感じでいいかと思います。

2007年12月11日火曜日

スコアアタックについて

Missileスクリプトの仕様策定の段階であったのですがスクリプト上簡単にリプレイを作成できるようになっています。
しかし、このリプレイ。
多少の欠点があります。

それは現状バージョンの差異を特定出来ないことです。

リプレイデータはゲーム中でプレイヤーがどのようにキャラを動かしたか、それがMISSILEスクリプトのバーチャルマシン上でどのように反映されるかを保存します。

そのため、スクリプト変更に伴う敵の動きの変更があった場合その動きの差異を拾えきれない事があります。

スクリプト、アップローダ用のスコアを特定する方法はすでに内部で決まってはいるのですが他の同人STGでも過去のリプレイがまともに動く例は少ないようです。
(当たり前といえば当たり前なのですが)

しかし、これを解決したところでステージ構成が違うバージョンでのスコアアタックに意味があるのか。
おそらくは意味がない場合がほとんどなんでしょう。

2007年12月7日金曜日

Missileスクリプトの優位性について2

つづきです。

Misiileスクリプトを使えば、同じことを以下のように記述できます。
main()
{
texture("enemy1.png");
x = 100;
y = 100;

for (;;)
{
vx = 2;
frame(100);
vy = 2;
frame(100);
vx = -2;
frame(100);
vy = -2;
frame(100);
}
}
かなり簡単になりました。
frame命令は、Missileスクリプトの最大の特徴だと思います。
frame命令が実行されるとスクリプトの実行を一時停止して、指定されたフレーム数の間、スクリプトの呼び出し側に処理を返します。
指定されたフレーム数が経過すると、一時停止した所から処理を再開します。

C言語とかの関数の場合は、処理を一時停止して、呼び出し側に戻るっていうことはできません。また、ローカル変数は関数を抜けると開放されてしまうので、キャラクタのコンテキストを関数の外側で保持しておく必要がありました。

Missileスクリプトでは、frame命令で処理を「一時停止」して、その間に他のスクリプトの処理や描画処理などを並列的に実行します。
この仕組みにより、キャラクタのコンテキストをローカル変数に置くことが出来るようになります。また、カウンタなどで時間軸を分割して移動パターンを記述する必要がなくなります。

結果として、直感的に分かりやすいコードになっていると思います。

以下は余談です。
処理を一時停止して、呼び出し側に戻って、その後処理を再開できるルーチンのことを、「コルーチン」と言います。
Lua5.0とかC#2.0にはコルーチンがあります。
でも、C#のコルーチンは、列挙子を記述するための専用構文になっていて汎用的には使えないのが残念です(yield文)。

Missileスクリプトの優位性について

Missileスクリプトを使って、ゲームを作る場合の良い点を書いておこうと思います。

Missileスクリプトを使うと、敵の移動パターンを分かりやすく記述できます。これが最大の利点かつ唯一の利点かと思いますw

既存のプログラミング言語でSTGを作った場合の、「よくありそうな」例を以下に挙げます。
C#っぽい言語で書いておきます。

まず、STGに登場するキャラクタの基底クラスを作ります。
    public class Character
{
public void Move()
{
}

public void Draw(Graphics graphics)
{
}
}

敵クラスとか自機クラスとか得点表示クラスは、全てCharacterクラスから派生させるわけです。

キャラクタ基底クラスを継承して、敵1クラスを作ります。この敵の動き方は、
  右に1フレーム当り2ドットの速さで100フレームの間、移動
  次に、下に1フレーム当り2ドットの速さで100フレームの間、移動
  次に、左に1フレーム当り2ドットの速さで100フレームの間、移動
  次に、上に1フレーム当り2ドットの速さで100フレームの間、移動
とします。
つまり、正方形の辺を時計回りにたどるように動きます。
    public class Enemy1 : Character
{
private Texture texture;
private int x;
private int y;
private int count;

public Enemy1(Texture texture, int x, int y)
{
this.texture = texture;
this.x = x;
this.y = y;
this.count = 0;
}

public new void Move()
{
if (count < 100)
{
x += 2;
}
else if (count < 200)
{
y += 2;
}
else if (count < 300)
{
x -= 2;
}
else if (count < 400)
{
y -= 2;
}

count++;
if (count >= 400)
{
count = 0;
}
}

public new void Draw(Graphics graphics)
{
graphics.Sprite.Draw2D(texture, x, y);
}
}

そして、最後はメインループです。
        public void Main()
{
Graphics graphics = new Graphics();
Texture texture = TextureLoader.FromFile("enemy1.png");

LinkedList characters = new LinkedList();
characters.AddLast(new Enemy1(texture, 100, 200));

for (; ; )
{
for (int i = 0; i < characters.Count; i++)
{
characters[i].Move();
}

for (int i = 0; i < characters.Count; i++)
{
characters[i].Draw(graphics);
}
}
}

多分、OOPっぽくSTGを作ると、たいていこんな構造になると思います。

この例では、いまいちな点が2点ほどあります。
1.Enemy1.Move()ではカウンタの値で分岐して行動パターンを記述しているので、直感的に分かりづらいコードになっていること。
2.インスタンスはメインルーチン内で管理しなければならないこと。

移動パターンを関数に記述したとき、ローカル変数は関数を抜けると開放されるので、位置などの変数は、関数の外で保持してやる必要があります。

上の例のように、クラスを使える言語で書いた場合は、位置などの変数はメンバ変数にします。また、純粋なCで書いた場合は、キャラクタの情報を入れる構造体を作って、それを参照渡しする形になると思います。
上の例では位置x,yは、敵1クラスのメンバ変数になっています。
Enemy1クラスのインスタンスはMain内で生成されています。そのため、Main内で各キャラクタのインスタンスを管理する必要があります。

テクスチャの指定も、Main側から行う形になっています。1つのテクスチャを複数のキャラクタで使う場合がある(というよりは、STGでは同じキャラクタが何個も生成されるので、そのたびにテクスチャをロードするのは無駄がありすぎる)ので、Enemy1クラス内でテクスチャをロードして使うことが出来ないためです。本来ならば、テクスチャの指定といった、キャラクタごとに固有の処理は、各キャラクタクラス内で完結して記述できたほうが良いのは当然です。

ローカル変数だけを使って、行動パターンを記述できないだろうか。
また、各キャラクタに関する処理は、そのキャラクタのソース内で完結して記述できないだろうか。
そういう発想を原点にして、作り始めたのがMissileスクリプトです。

つづく

2007年12月4日火曜日

google-code-prettifyの導入を試験的に行ってみました。

内部で行っていることなので、閲覧者の方には解りづらいかもしれませんが、これで各種言語のコードが解りやすくなると思います。
missileには対応してないので、その辺はご勘弁を。

とりあえず、こんな感じです。

public void Play(bool loop_flag)
{
loopflag = loop_flag;
try
{
audiodata = new Audio(filename);
audiodata.Play();
audiodata.Ending += new System.EventHandler(this.Loop);
}
catch (Exception e)
{
Console.WriteLine("Not FileSetting", e);
}
}


と、言うわけでスタッフの方への業務連絡でした。

Missileスクリプトエンジン環境下のリソース管理について

Missileスクリプトでは、オーディオデータやグラフィックデータなどのリソースを管理する手間が非常に少なくなる予定です。

普通の環境だと、リソースをファイルからロードしてインスタンス化し、それを実際に使用して、最後に破棄する、という手順が必要になります。

なぜ、ロード、使う、破棄、の3ステップを踏まなければならないかといえば、メモリとハードディスクの特性の違いによるものだと私は考えます。

一般に、メモリは小容量・高速・揮発性で、ハードディスクは大容量・低速・不揮発性という特性を持っています。プログラマはこれらの違いを意識してプログラムを書く必要があります。

もしも、ハードディスク並みに大容量かつ電源を切っても内容が消えないメモリがあり、PCの記憶装置はメインメモリだけということになったら、上記の点に関してプログラマはとても楽をできるはずです。ディスクからロードしたりする手順を踏まなくとも、すべてのリソースに最速でアクセスできるわけです。

Missileスクリプトエンジンでは、擬似的にそのような環境を実現することを目標にしています。スクリプトの記述者は、明示的にリソースをロードしたり破棄したりするコードを書く必要がありません。
それは、Missileスクリプトエンジンにリソースを自動的にキャッシュする仕組みがある為です。