2013年05月27日

git

修羅場モードです。家で寝たのは何日前だったかな…
あ、この文章はいつどこで書いているんだというツッコミはナシで。

普段はバージョン管理に hg を使っているのですが、mac の xcode で作業すると、プロジェクトの新規作成時には「git リポジトリを作りませんか?」なんて聞かれるし、github なんて流行ってるみたいだし、なにより hg と違ってコミット済みのログの編集が簡単にできるみたいです。これはうれしい。

ひとまず hg のリポジトリを git 形式に変換して、同時運用しながら様子を見てみたいと思います。

前にも書いたような気がしますが、バージョン管理ツールは複数人数による開発や巨大プロジェクトのためだけにあるのではなく、個人が作成する小さなプログラムでも十分に利用価値がありますよ!!


話は変わりますけど、gif の正しい読み方が「ジフ」であると判明したみたいですね。
元々自分の周りではみな「ジフ」と呼んでいたのでいまさら感がありますが、「ギフ」派の間では衝撃が走ったようです。Gimp は「ギンプ」なのに。
ちなみに自分は普段「wav」を「ウェーブ」と呼んでいるのですが、以前会った人が「ワブ」と呼んでいてカルチャーショックを受けたことがあります。

ちなみに一番悩んだのは「ルズハを展開したらバンプがでてきた」という台詞で、lzh と bmp のことだとわかったときには衝撃を受けました。

ではでは。
posted by JUNOSOFT at 22:17| Comment(1) | 雑談

2013年05月23日

FPS

こんにちは。ようやく車検が終わりましたが、自動車税と車検と保険の更新が全部同じ月に重なっているため、
ピンポイントで支出が大変なことになっています…


今日は、FPS値を固定したときの描画タイミングの話です。
ゲームのメインループを回すときに、FPS=60なゲームであれば1秒間に60回更新されるようにうまく調整してやる必要があります。
まっさきに思いつくのは

while (1) {
 Sleep(1000/60);
 DrawGame();
}

みたいなやり方です。が、Sleepは本当に指定した時間だけ待ってくれるわけではないので、あまり使わないほうがいいと。
すると今度は timeGetTime() なんかを使って

DWORD next = timeGetTime();
while (1) {
 if (next <= timeGetTime()) {
  DrawGame();
  next += 1000/60;
 }
}

とやりたくなります。ところがこの 1000/60 がクセ者で、整数同士の計算になるために、
本当だったら1フレームあたり16.6666.. ミリ秒待つ必要があるのに 16 ミリ秒しか待ちません。
16*60=960 なので 60 回更新しても、1秒が経過するまで 40ミリ秒ほど余ることになります。これではダメです。

というわけでココは素直に実数を使って

DWORD next = timeGetTime();
while (1) {
 if (next <= timeGetTime()) {
  DrawGame();
  next += 1000.0/60.0;
 }
}

とするのがよさそうです。
ただこれだとCPUを常に100%MAX使うことになるため、ノートPCなんかではバッテリーの消耗がすごいことになります。
かといって Sleep では指定した時間ぴったり待ってくれるわけではないし…

そこで思いついたのが以下のような方法です。
まずゲーム開始時に、おおよその Sleep 誤差を計測します。

DWORD g_SleepTimeErr;
...

timeBeginPeriod(1); // Sleepの精度上げておく
DWORD time = timeGetTime();
Sleep(1);
g_SleepTimeErr = timeGetTime() - time;
timeEndPeriod(1);


これを何回か繰り返して、平均値を手に入れます。そして、先ほどの更新プログラムに少し手を加えて、
次の更新時刻までの残り時間が sleepTimeErr ミリ秒以上あるときに限って Sleep(1) だけCPUを休ませてしまう、というものです。

timeBeginPeriod(1); // Sleepの精度上げておく
DWORD next = timeGetTime();
while (1) {
 DWORD time = timeGetTime();
 if (next <= time) {
  DrawGame();
  next += 1000.0/60.0;
 } else {
  if (g_SleepTimeErr <= next - time) {
   Sleep(1);
  }
 }
}
timeEndPeriod(1);

これだけでも結構負担が軽くなるので、ノートPCがホッカイロ状態にならずに済みそうですよ?

posted by JUNOSOFT at 08:07| Comment(1) | プログラミング

2013年05月22日

C#foreach

こんにちは。親戚の用事で実家に帰っていたのですが死ぬほど疲れました。その上プログラムでも嵌るという…。
それは C# の foreach だったんですが、

class MyObj {
}

private void button1_Click(object sender, EventArgs e) {
 foreach (MyObj obj in this.Controls) {
 }
}

とかやってもコンパイルが通ってしまうんですね。びっくりしました。
MyObj は Form.Controls の要素とは一切継承関係にないので、当然コンパイルエラーではじかれるものだと思っていたのですが…。
当然期待した動作になるはずもなく、コンパイラエラーにもなっていないのですごく悩みました。
Object という暗黙の共通祖先を持つからOKとか??なんですかね〜
なぞです。

posted by JUNOSOFT at 06:59| Comment(0) | プログラミング

2013年05月16日

Undo と Redo

こんにちは。

ゲームの背景や敵、イベントを配置するために、Visual C# Express で自前のマップエディタを作っていたんですが、やはり Undo Redo がないとイカンということで、履歴機能を実装することにしました。

もともと、このマップエディタではXML形式でファイルを保存するようになっていたため、保存関数に少し手を加えて、

XmlDocument SaveToXml();

というものを作りました。これは現在の状態を表す XML ツリーを構築し、それを返す関数です。
つまり、編集中のマップの状態をファイルではなくメモリ上にセーブできるようにするのです
(このように、ある時点での状態を記録しておくことを「スナップショット」と表現したりします。実際、写真を撮るのと同じようなものです)

また、保存した XmlDocument からそのときの状態を復元できるように

void LoadFromXml(XmlDocument document);

という関数も用意しておきます。次に、これらのスナップショットを時系列順に保存しておくためのスタックを用意します。履歴操作には Undo と Redo があるため、スタックもそれぞれの用途に使えるよう二つ準備します。

Stack mUndoStack = new Stack();
Stack mRedoStack = new Stack();

ユーザーがなにかを変更するときは、かならず変更直前に UpdateHistory() を呼び出して、変更前の状態を Undo スタックに積んでいきます。Undo ボタンが押されたら、現在の状態を Redo に移動し、Undo から元の状態を取得して復元します。逆に Redo ボタンが押されたときは、現在の状態を Undo に積み、Redo から変更後の状態を得ます。


void UpdateHistory() {
 mUndoStack.Push(SaveToXml());
 mRedo.Clear(); // ※1
}

void Undo() {
 mRedoStack.Push(SaveToXml());
 LoadFromXml(mUndoStack.Pop()); // ※2
}

void Redo() {
 mUndoStack.Push(SaveToXml());
 LoadFromXml(mRedoStack.Pop()); // ※2
}

bool CanUndo() { return mUndoStack.Count > 0; }
bool CanRedo() { return mRedoStack.Count > 0; }

※1 Undo を繰り返して状態を戻してからユーザーが作業を再開した場合、Redo に履歴が残っていると変な事になるので、Undo および Redo 以外の操作で状態が変化した場合は必ず Redo を空っぽにしておかなければなりません。過去を変えたら、元の未来には帰れないという事です(これ、うまく管理すれは、バージョン管理ツールのように、一度でも存在した状態ならどの時点にも戻せるようにできるかもしれません。全ての世界線の、好きな時間にに自由に行けると)

※2 さきほど「なにかを変更しようとしたら必ず UpdateHistory() を呼ぶ」と書きましたが、これをそのまま言葉通り実装して、LoadFromXml の中から UpdateHistory を呼んでしまわないように注意してください。スタックの整合性が壊れてしまいます。


これ、やっていることは GoF で言うところの Memento と同じです。実装が非常に簡単なので自分はこの方法を使いましたが、スナップショットの作成コストが高いうえに毎回全ての状態を保存するためにメモリを消費し、あまり賢い方法とはいえません。
変更箇所のみ保存しておくようにするためは、ユーザーが行った編集内容と、そのときの変更を元に戻すための処理をセットにして保存しておく必要があります。それって結局 Command パターンで実装しなおすという事なのでかなり面倒ですが…。


そう考えると、コマンド履歴やらマクロ機能やらをきっちり実装している人はすごいなあと思います。
posted by JUNOSOFT at 18:31| Comment(1) | プログラミング

2013年05月07日

ちらつきと戦う

こんにちは。
GW、もう終わりなんですね…
ま、関係ないんですけどね(涙

ところで最近、マップツールなど描画関係で速度を要求されないもの&GUIが必要なものに関しては C# で作るようにしています。Mono なんて面白そうなのもありますし、そのうち Mac に移植してみようかなと。

C#(というか .NET Framework)でつくったフォーム上で画像なんかを描画する場合、Graphics オブジェクトが操作できると非常に楽なのですが、Paint イベントのタイミングで Graphcis に対して描画すると、画面がかなりちらついてしまうという問題がありました。
Google 先生に聞いてみると、まあ当然のようにダブルバッファリングしろと書いてあります。
Bitmap なんかを用意して、そこに全部描画してから、最後に転送しなさいよと。ところがそれでも症状は治りません。
Form のダブルバッファー設定を有効にするために
 SetStyle(ControlStyles.DoubleBuffer, true);
 SetStyle(ControlStyles.UserPaint, true);
 SetStyle(ControlStyles.AllPaintingInWmPaint, true);
などとやってみてもダメですし、あきらめて DirectX で直接描画していました。

しかし、そんなに高度な画面を描画するわけでもなし、DLLも必要になってしまうし、DirectX なんて使わずに何とか Graphics だけで処理できないものかと試してみた結果、そもそも Paint イベントで処理することがダメなんだという結論に達しました。

画面の再描画要求を出すとき、Invalivate を呼び出す代わりに、Graphics を取得して直接描画しまうのです。

Invalidate()

で Paint イベントを呼んでもらうのではなく、代わりに

Bitmap back = new Bitmap(control.Width, control.Height);
{
 Graphics g = Graphics.FromImage(back);
 MyPaint(g);
 g.Dispose();
}
{
 Graphics g = control.CreateGraphics();
 g.DrawImage(back, new Point(0, 0));
 g.Dispose();
}
back.Dispose();

みたいにする、ということです。よく考えたらツールの画面を DirectX で描画するときも、まさにこんな感じでした。
もちろん実際には、back はリサイズイベント時にだけ再生成するようにして使いまわします。


というわけで、C# でツールを作るうえで一番気になっていた部分が解決したため、これからはなんの迷いもなく作業できそうです (^ ^
posted by JUNOSOFT at 09:50| Comment(0) | プログラミング

2013年05月02日

BASIC

こんにちは。

なんだか急にポケコンが懐かしくなったので、適当なエミュレーターがないものかと App Store を探してみたのですが、これだ!というものはありませんでした。

そこで、ポケコンではなく、単にプログラムが実行できるだけのアプリ、を探してみたところ、Python、Lua、Visual Basic、BASIC が使えるものがいくつか見つかりました。しかし、プログラムの入力を iPhone で行うことを考えると、あまり長い単語は打ちたくないし、できれば記号なども打ちたくありません。そうするとやっぱり BASIC かなということになりまして、アプリをいくつかインストールしてみました。

ところが…

どの BASIC アプリも入力画面のフォント解像度が高すぎます。こんなに綺麗な画面なんて BASIC じゃない!!
ひどいのになるとプロポーショナルフォントで打ち込ませようとしてきます。
黒い画面に緑のビットマップ等幅フォントでプログラムを打ち込む、これがやりたかったのに。

というわけで注目したのは HAND BASIC と BASIC! というアプリです。実際に使ってみましたが、なかなか雰囲気が出てます。


Hand Basic - App Store
https://itunes.apple.com/jp/app/hand-basic-cbm-flavor/id394924289

Basic! - App Store
https://itunes.apple.com/us/app/basic!/id362411238


秋葉原行って探してこようかな…
posted by JUNOSOFT at 11:20| Comment(1) | 雑談