2013年07月24日

キャラクターの操作

ゲームのキャラクターを実装するときに以下のことに気をつけていると後々楽になります

【実装1】キャラクターのアニメーション部分と操作部分を分離しておく(別々のクラスにまとめる)

 操作部分を複数用意して、目的に応じて差し替えることができるようにします。
 例えば敵キャラだったら、同じキャラで異なる行動パターンを適用したり(難易度別とか)、
 イベントシーンにおけるキャラ劇が実装しやすくなったり、必要に応じてパッド入力による操作、スクリプトによる操作に変えることができます
 
 
【実装2】プレイヤーキャラだけでなく、敵キャラもパッドで操作できるようにしておく
 敵キャラを自分で好きなように動かせるので、新しい敵を作ったときなどの動作確認がすごく楽になります。
 プレイヤーが敵キャラに変身するなどの行動も面白いかもしれません。
 その敵にしかない特殊行動なんかは、通常のゲームプレイでは使わないボタンに割り当てる必要がありますが。
 これは【実装1】による恩恵の一つです。
 
 
【実装3】敵キャラが自動行動するとき、行動を直接指定するのではなく、擬似パッド入力を送るようにしておく
 たとえば敵を移動→攻撃させたいとき、「移動アニメに変更」、「攻撃アニメに変更」というふうに動きを直接指定するのではなく、「パッド左押す」「パッド左離す」「攻撃ボタン押す」「攻撃ボタン離す」のようにダミーのパッド操作を送信します。その命令を受け取る敵キャラは、それが実際のパッドから入力されたものなのか、それともCOM操作による入力なのかは区別できません(区別する必要がない)。
 これは【実装2】による恩恵の一つです
 
 また、普段プレイヤーが使っているキャラをCOMが操作するときに、人間のプレイヤーにはできないような行動がとれなくなります。たとえば、必ずアクションA→アクションBの順番で出る技があったとして、アクションを直接指定すると、アクションAを経由せずにいきなりアクションBを出すことも可能ですが、パッドの擬似入力なら、人間が操作したときと同じ条件で操作することになります。人間の代わりにロボットがパッドのボタンを押している、という考え方です。パッドを介して操作している限り、COMには人間と同じ操作しかできません(入力の速さや正確さは別にして)。逆に言えば人間が操作するときと同じ動きしかできない、ということですが、これをメリットと見るかデメリットと見るかは状況次第です。格闘ゲームで対戦相手をCOMが操作している場合なんかには有効です。


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

2013年07月17日

フォント描画とか

どうやら GetGlyphMetrics では空白文字の横幅は取れないみたいですね。
この関数の引数に半角スペースや全角スペースを指定すると、なぜかエラーになってしまいます。

というわけで、GetGlyphMetrics を呼ぶ前に、調査対象が空白文字だった場合の処理をしなくてはなりません。

ところで可変ピッチフォントのスペース幅っていくつなんでしょう?
探してみたら、参考になりそうなものがリコーのサイトにありました。

「MSゴシック」、「MS明朝」についてのお話
http://www.ricoh.co.jp/font/related_info/knowledge/fontknowledge01.html

どうやら「MS Pゴシック」の場合、半角スペースは「MS ゴシック」文字幅の 1/3 に、全角スペースは 2/3 になっているみたいです。じゃあプロポーショナル版しか存在しないフォントのスペース幅はどうしようか?と思ったのですが、emという単位が存在するぐらいだし、大文字 M の横幅を基準に考えて良いのではないかという結論に達しました。

emとは
http://www.sophia-it.com/content/em

しかし文字をただ並べるだけなのにこんなに手間がかかるとは…。その点、固定ピッチフォントは楽ですね。
posted by JUNOSOFT at 23:51| Comment(0) | プログラミング

2013年07月14日

ロードのタイミング

ゲームデータのロードっていつやってますか?
必要なデータの量が増えてくると、ゲーム開始時のロード時間が長くなりますよね。
というわけで、自分がゲームを作るときに使うロード方法をまとめてみました。


■一括ロード

ゲーム起動時に全ステージ、全てのデータを読み込む
・一度ロードしてしまえば、その後一切ロードする必要がなくなるためゲーム中はすごく快適。
・ゲームの起動時間が長くなる
・最終ステージのデータなど、結局使われないであろうデータも一緒にロードしてしまう
・データに不備があってロード不可能な場合、ゲーム起動時に発見できる


■シーン単位でロード

タイトル、ゲームステージ、ステージクリア、ゲームオーバーなど、ゲームをシーン単位で区切って、各シーンの開始時に必要なデータをロードする
・たぶん一番お手軽で一般的な方法
・シーンの変わり目ごとにロード処理が入るが、シーンの変わり目なので多少ロードによる遅延があっても許される?
・でもデータが多い場合はやっぱり「Loading...」の画面が必要になる



■データを使う時にロード

ゲーム開始時、ステージ開始時にまとめてロードするのではなく、とあるデータが必要になったときにその場でロードする
一度ロードしたら破棄しないで次のためにとっておく。例えば会話のためのデータは、会話が始まるその瞬間までロードしないし、
キャラクターのデータは、そのキャラクターが登場するその瞬間までロードしない
・ゲームの開始時、ステージの開始時にロード画面を設けたりする必要がない
・ゲーム起動中に行ったデータファイルへの変更が即座に反映されるので、ゲームの開発中、デバッグ中に便利
・キャラクターの出現時、音楽を鳴らすとき、なにかの画像を表示するタイミングでちょくちょくロードによる遅延が発生する
・データにエラーがあってロード不可能な場合、そのことに気づくのが遅れる。例えば最終面のボスデータに不備があってロード不可能だった場合、そのボスが登場するまでロードエラーには気づくことができない



■データを使いそうな時ロード

データをロードする場面に近づいたら、実際にその場面に出くわす前に、ロード専用スレッドでデータを読み込んでおく。
ゲームの起動から終了まで、待ち時間が発生することはほとんどない。

例えば…

・マップセレクト画面で、プレイヤーがカーソルを「マップ1」に合わせたら、その時点でマップ1のデータをロードし始める。その後プレイヤーがカーソルをマップ2に移動してしまった場合は、マップ1データを破棄してマップ2をロードし始める。このとき順番を工夫して、マップを最初に選ばせて、つぎにプレイヤーの装備や難易度などの選択画面を置いておけば、その間にマップデータをロードできる。

・マップ1をプレイ中に、マップ2への移動地点が近づいたら、その時点でマップ2のデータを読み始めてしまう。
予想に反してプレイヤーがマップ2に移動せず引き返してしまった場合は、データを捨てるもよし、そのまま保持し続けるもよし。

・ボスとのイベント場所に近づいたら、その時点でボスのデータを読み始める。



自分は「一括ロード」、「シーン単位でのロード」、「データを使う時にロード」を状況に応じて使っていますが、
「データを使いそうなときにロード」だけは大変面倒くさそうなので実装したことがありません…

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

2013年07月11日

HSP

最近、「プログラムって何?」と聞かれることが何回かあり、適当に説明したりしてたんですが、こういうのは実際にいじらせてみるのが一番ですよね。
ところが定番の C も Java もとても初心者&学習向けとは思えないし、HELLO WORLD しても文字が出るだけなので全く面白みがありません。
GLUTなんかと組み合わせてみても少しハードルが高いだろうし、そもそも開発環境のインストールの時点で疲れそうです。

覚えやすくて、インストールが簡単で、シンプルなIDEが一緒についてきて、プログラムを新規作成しようとしたら最初にファイルの保存先を選ぶなんてことやる必要がなくて、簡単なコードでウィンドウ出したり図形が描けたりする都合のよいスクリプトは?と考えたときに、ふとHSPのことを思い出しました。

よくHSPの名前を聞くことはあったのですが、「初心者むけの簡単スクリプト言語!!」みたいなイメージがあったので、特にチェックすることもなくスルーしてました。
そういえば小学生向けの本なんかも出てたなーと思い、とりあえずダウンロードして軽くいじってみたのですが、いやー、至れり尽くせりでびっくりしました。
テキストエディタはそこそこ使いやすいし、ヘルプも充実。入力支援もあるし、ちょっと作ってみよう→書く→実行が本当に気軽にできます。プログラム部品を配置していくだけでよい HSP Peas とか、もうゴメンナサイって感じ。
次からはこれを使って教えてみようと思います。
posted by JUNOSOFT at 04:07| Comment(0) | プログラミング

2013年07月09日

構文解析とか

大学の授業なんてほとんどその場限りのイベントに過ぎないのですが、唯一実際に役に立ったのが、コンパイラ構成という授業でやった構文解析についてです。それ以前に、 数式を直接入力できる電卓プログラムを作ろうとして挫折したことがあったのですが、まさにその問題の解決法を授業で習ったのです。知ってしまえば理屈は非常に簡単で、その後無事に電卓を完成させました。その後もゲームスクリプトを独自開発するというときに、もう少し本格的な構文解析について調べたのですが、基本はカッコつき四則演算式の解析と変わりません。

というわけで、もしスクリプトを自分で作りたかったり、複雑な数式を直接入力できるような電卓を作ってみたい という型は構文解析でググってくださいね。と。

ちなみにコンパイラのための構文解析では、この本に大変お世話になりました。いわゆるドラゴン・ブックてやつです。
少し高いですが、それに見合った内容ですよ。ちなみにこれは上下巻に分かれています
「コンパイラ 原理・技法・ツール(1)」
「コンパイラ 原理・技法・ツール(2)」
http://www.amazon.co.jp/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E2%80%95%E5%8E%9F%E7%90%86%E3%83%BB%E6%8A%80%E6%B3%95%E3%83%BB%E3%83%84%E3%83%BC%E3%83%AB-Information-Computing-A-V-%E3%82%A8%E3%82%A4%E3%83%9B/dp/478191229X

バイブルといっても良いかも。
posted by JUNOSOFT at 04:36| Comment(0) | プログラミング

2013年07月03日

処理の順序(2/2)

こんにちは。いまだに自動車の不具合と戦っているプログラム担当です。
今回は前回「処理の順序(1/2)」の続きです

1. 全キャラクターの行動パターンを更新。それに合わせてアニメと移動速度を設定

まず最初に、全てのキャラクターの行動を更新します。プレイヤーならばゲームパッドからの入力を元にして、移動するとか、攻撃するとかを決定します。敵ならば敵の出現パターンにしたがって移動速度を決定したり、敵の弾を生成したりします。


2. 全キャラクターの座標を更新+地形との判定による座標補正

全てのキャラクターの行動が決まったら、次に移動処理をします。速度にしたがって座標を更新し、
必要ならば加速度にしたがって速度も更新します。そのとき、地形との接触判定もやっておき、地形と矛盾した場所に移動しないように、座標を補正しておきます。


3. いよいよ攻撃・ダメージ判定ですが、まず最初にプレイヤーの攻撃判定と敵のダメージ判定を処理します。
(これは、「プレイヤーの攻撃が敵に当っていて、かつ敵の攻撃もプレイヤーに当たっている」という状況が同時に発生した場合に、プレイヤーの攻撃を優先するためです。相反する状況が同時に発生した場合は、かならずプレイヤーに有利なほうを選びます。
例えばボスを倒した瞬間に敵の弾に当たり、ステージクリアとゲームオーバーの条件を同時に満たしてしまった場合は、ステージクリアを優先させます)
ここで、プレイヤーの弾、オプションキャラクターなど、敵に対する全ての攻撃判定と、敵側のダメージ判定の接触を調べ、組み合わせをリスト化しておきます。


4. リスト化された全ての接触の組み合わせを見て、攻撃やダメージの処理を行います。なぜわざわざリスト化してから処理するのかといえば、複数の攻撃が同時にヒットした場合の処理を簡単にするためです。ダメージを受けた敵は爆発するか、もしかしたら進行方向が変わるだけかもしれません。ただ、たとえ進行方向が変化したとしても、ここでは速度を再設定するだけにしておきます


5. 今度は敵の攻撃判定と、味方のダメージ判定の処理です。プレイヤーに対する全ての攻撃判定と、プレイヤー側のダメージ判定を調べ、接触していればその組み合わせをリスト化しておきます


6. 5で作成したリストを元にしてプレイヤーのダメージ処理を行います。ちなみにプレイヤーが無敵状態の場合は、5 と 6 をスキップします。ダメージを受けたことによって吹き飛ばされたり、進行方向が変わるかもしれませんが、ここでは速度を設定するだけで座標は変えないでおきます


7. この時点でのアニメ(行動に適したキャラクタ画像)、座標を使って全てのキャラクターを描画します


--
このような感じで、キャラクター単位で処理するよりも、機能単位で処理したほうがなにかと便利です。
ある特定のキャラクターの状態によって別のキャラクターの行動が変化するなど、キャラクター同士が影響を及ぼしあう場合、処理の順番に気をつけないと思わぬ事故が発生する場合があるので、気をつけましょう。
シューティングやアクションゲームのようなリアルタイムに進行するゲームでは全員が同時に動くのでややこしいですが、
結局はターン制のゲームと同じで、ただ敵のターンと味方のターンが超高速で切り替わっているだけと考えれば、すこしは楽になるかもしれません。
posted by JUNOSOFT at 04:33| Comment(0) | プログラミング

2013年07月01日

処理の順序(1/2)

こんにちは。モンスターエナジーがあれば徹夜もへっちゃら、プログラム担当です

突然ですが自分が最初にゲームを作り始めた頃、わりと悩んでいたのが、1フレームごとにどういう順番でキャラクターの処理をするか、です。

例えばシューティングゲームならば、
・キャラクターを移動させる
・プレイヤーの弾と敵、敵本体とプレイヤー同士の接触判定
・キャラクターの状態によるアニメーションの更新
・描画

などの処理を毎フレーム行うわけですが、どの順番で処理していくのか、結構迷っていました。
また、当然ですがキャラクターは複数存在するため、
キャラクタ1に関する全ての項目を更新→キャラクタ2に関する全ての項目を全更新→ ... とのようにキャラクタ単位で処理していく方法と、
全キャラのアニメだけ更新→全キャラの座標だけ更新→全キャラの接触判定だけ処理→ ... のように機能単位で処理していく方法があります。

自分の結論としては、
1. 全キャラクターの行動パターンを更新。それに合わせてアニメと移動速度を設定
2. 全キャラクターの座標を更新+地形との判定による座標補正
3. 味方の攻撃判定と、敵のダメージ判定の接触を検出
4. 3で検出した全ての接触に対する処理
5. 敵の攻撃判定と、味方のダメージ判定の接触を検出
6. 5で検出した全ての接触に対する処理
7. 描画
の順番で処理するのが一番やりやすかったです。

次回もう少し詳しくお話します
それでは。
posted by JUNOSOFT at 19:51| Comment(0) | プログラミング

2013年06月25日

luaのバイナリ形式

こんにちは。コーヒーを飲みすぎて気分が悪くなったプログラム担当です。

Luaの癖の強い作法が最初はいやだったのですが(配列が1起算とか、否定等号が != でも <> でもなく ~= だとか)だいぶ見慣れてきてストレス無く扱えるようになってきました。

ところで Lua スクリプトをロードするとき luaL_loadstring なんかを使っていると、テキストを解析してコンパイルする時間がかかるため、スクリプトが多くなってくるとロードだけで時間がかかってしまいます。
幸いにも Lua にはコンパイル済みバイナリを書き出す機能があるので、それを使ってキャッシュファイルを作り、二回目以降の起動ではコンパイル済みのスクリプトファイルから直接ロードすることができます。

やり方は簡単で、まずテキスト形式の Lua スクリプトを普通にロードし、それを lua_dump で書き出します。
読み込むときには luaL_loadstring ではなく lua_load を使うと、テキスト形式でもバイナリ形式でもどちらでも読み込んでくれます。

テキストluaからバイナリluaを作るコードはこんな感じです:

static char g_buf[1024];
const char * reader(lua_State *L, FILE *fp, size_t *size) {
 if (feof(fp)) {
  *size = 0;
  return NULL;
 } else {
  *size = fread(g_buf, 1, sizeof(g_buf), fp);
  return g_buf;
 }
}

static int writer(lua_State *ls, const void *data, size_t size, FILE *fp) {
 fwrite(data, size, 1, fp);
 return 0; // no error
}

void make_bin_lua(const char *bin_filename, const char *text_filename) {
 FILE *fin = fopen(lua_filename, "r");
 FILE *fout = fopen(bin_filename, "wb");
 lua_State *ls = luaL_newstate();
 lua_load(ls, (lua_Reader)reader, fin, "", NULL);
 lua_dump(ls, (lua_Writer)writer, fout);
 lua_close(ls);
 fclose(fout);
 fclose(fin);
}

いやー、 Lua っていいですね!

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

2013年06月19日

Androidで3D物理エンジン

こんにちは。先日、某ログハウスの展示場に行ってきたのですが、思ったほど高くなくて驚いたプログラム担当です。金は独身のうちに使っておけ、という家訓が自分の背中を押しているのを感じます。

ところで今度作ろうとしているアプリ、ガチの3D物理演算ライブラリを必要としています。ところが以前使った Box2D は当然2D専用なわけで、今回のプログラムには役に立ちません。良いものがないかと調べてみたところ、Bullet というものを見つけました。ためしにダウンロードしてみたんですが、初めからVC2010向けのプロジェクトファイルが入ってる、ってのがとても良いですね!本体ライブラリのほかに大量のサンプルプロジェクトも入っていて、非常に簡単に試すことができました。

http://bulletphysics.org
http://code.google.com/p/bullet/downloads

ただこれ、結構ファイルが多くて Makefile 書くのが大変そう…。とりあえず Android-NDK で HelloWorld のコンパイルが通るように頑張ってみたいと思います。

うまくいったら、また報告しますね。
posted by JUNOSOFT at 23:25| Comment(0) | プログラミング

2013年06月18日

データのロード

こんばんは。なぜか修羅場が終わりません (涙


今日は、ゲームに使うデータファイルをロードするときの話です。

画像ファイルやアニメファイル、サウンドファイルなどのロードをそのまま素直にやっていると、ファイル数が多くなってきたときに意外とロード時間がかかるようになってきます。
そこであらかじめ、自分のプログラムにとって扱いやすいフォーマットに変換して保存しておき、ゲーム中ではそのファイルを読み取るようにすればロード時間が早くなります。
例えば、画像ファイルはは PNG で管理し、ゲームでは DDS 形式に変換してから使うとします。
(あくまでも例えで、自分は DDS 形式を使ったことはありませんけど)


普段グラフィック担当とやり取りするときはPNGのまま扱い、ゲームに組み込むときにDDSに変換すれば、効率よくロードできます。
ところがこのやり方、ロード時間が早くなるのは良いのですが、DDSへの変換という手間が増えてしまいます。
画像をほんの少し変更したときでも、わざわざDDSに変換してからでないとゲーム中で確認できません。


ならばハイブリッド形式で、PNGとDDSの両方に対応し、DDSがあればそれを使い、なければPNGを使うようにすればよさそうです。


実際、しばらくそのようにしていたのですが、そのうち変換作業自体が面倒になってきました。
PNGのままでも使えるとはいえ、結局どこかのタイミングでDDSに変換する必要があるわけです。
ツールで変換すること自体が面倒です。
そこで、ゲーム自体に変換機能を持たせることにしました。PNGをロードした場合は、自動的にDDSに変換して保存しておく…と。
当然この変換機能が付いているのはデバッグ版だけで、公開版にはありません。(#ifdef _DEBUG で切り分ける)


で、しばらくそのようにやっていたのですが、そのうちさらに面倒になってきました。
PNGとDDSがある場合はDDSを優先してロードするのですが、PNGを更新した場合はDDSも自動更新してほしいなーと。
というわけで、DDSを作るときにPNGのタイムスタンプも同時に記録しておき、以後DDSを使うときには変換元となったPNGのタイムスタンプと、実際に存在するPNGのタイムスタンプを比較し、1秒でも異なっていたらPNGが変更されているとみなしてDDSを再生成します。
そんな面倒な事をしなくても、DDSよりPNGの日付が新しかったらDDSを更新すればよさそうな気もしますが、バックアップからPNGを復元した場合など、必ずしもDDSより新しい日付になるとは限りません)


これは思った以上に快適です。面倒なことは機械に任せて、人間様はどんどんサボりましょう。

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

2013年06月15日

解像度とアスペクト比

こんばんは。疲れ果てたので、小ネタです。

画面のアスペクト比。
フルスクリーンモードの時、画面の縦横比に関係なく、ゲーム本来の縦横比で画面を表示するようにしたかったのですが、簡単かと思ったらそうでもありませんでした。

そんなの簡単、現在のディスプレイ解像度を取得できればあとはやり放題、のはずなのですが、そうではありません。
なぜなら、切り替え可能な解像度は必ずしもディスプレイ本来の解横比とは一致しないからです。
もともと比の異なる解像度にされていた場合、ゲーム側ではそれが本来の解像度であると騙されてしまいます。
自前で正しい縦横比のディスプレイモードに切り替えればよいのですが、そもそも使用中のディスプレイの物理解像度はどうやって取得するのでしょう…?

使用可能な解像度を列挙して、そのなかで一番サイズの大きいもの=そのディスプレイの物理解像度とすればよさそうですが、ところがどっこいノートPCなどでは物理解像度よりも大きな値に設定できたりします。

まあ結局そんなところまで考えてもしょうがない、ということで、ゲーム起動時に設定されていた解像度=そのディスプレイの物理解像度であるという前提で処理するようにしました…。

人間、あきらめが肝心です。

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

2013年06月07日

アクションとか

こんにちは。まだまだ仕事で修羅場が続いております。
例によってこの文章をいつ書いているんだというツッコミは無しの方向で…

今日はプレイヤーのアクション管理とかについてです。

エースオブワンドではさまざまなアクションを取り入れたのですが、基本動作「移動」について少しだけお話します。


・移動属性
プレイヤーは移動に関して、2種類の状態を持っています。
地上状態と、空中状態です。
キャラクターのアニメに使用する絵には、1枚ごとに「地上」か「空中」どちらかの属性を設定してあります。
ポイントは、アニメ単位で地上・空中を設定するのではなく、絵(フレーム)単位で設定することです。
例えばジャンプアニメは、予備動作(ふんばり)→上昇→下降→着地の流れになっています。
ここで、予備動作の絵は「地上」属性にしておきます。
上昇・下降中の絵には「空中」属性に、着地してからの絵は「地上」属性にします。
このように全ての絵を地上または空中に分類し、再生中のプレイヤーの絵がどちらの属性なのかによって移動処理を変えます。


・地上状態
地上状態ではどんなアクションをしても必ず地面にスナップ(吸いつき)し、地面から離れることはできません。
どんな凹凸や坂でも必ず地面に沿って移動します。
地上状態であるにもかかわらず地面が存在しない場合は、自動的に空中状態に移行します。
つまり、キャラクターのアニメを落下状態にするということです。

さらに、どんな地上状態から落下したかによって2つの落下パターンがあります。
走っている状態から落下したら「通常落下」です。
それ以外の状態から落下した場合は「予期しない落下」です。
スライディング中に段差から落ちたり、前進を伴う攻撃中に段差から落ちたりした場合などはすべて「予期しない落下」です。
予期しない落下中は操作不能で左右の速度調整などができず、着地したとたんにバタンと倒れこんでしまいます。


・空中状態
空中状態ではどんな方向にも移動できますが、地面に接触した瞬間に地上状態に戻ります。
地上状態に戻るとは、着地アニメに移行するということです。
また、ある程度の上昇速度で天井に衝突した場合は、頭ぶつけ状態に移行します。
頭ぶつけ状態はダメージ扱い(アニメがダメージ絵になるだけで、シールドは減らない)で落下します。


・動く床
ここから先はエースオブワンドの話ではないのですが、ちょっと付け加えておきます。
ゲームによっては上記に加えて「別のキャラクターの上に乗っている」という状態があります。
上下左右に移動する床とか、押したり壊したり上に乗ったりできる箱とかです。
この場合、プレイヤーは「自分の足元に居るキャラクター(地面キャラ)」と「そのキャラクターからの相対座標」を保持します。
これがNULLなら誰の上にも乗っていないし、非NULLならそのキャラクターの上に乗っている状態です。
誰か(地面キャラ)の上に着地した場合には、そのキャラクターと相対座標をプレイヤーに登録し、リンクさせます。
プレイヤーのアニメが「地上状態」になっている限り、プレイヤーの座標は地面キャラからの相対座標で計算するようにします。
プレイヤーが歩いた場合などは、移動速度を元にして相対座標を変化させます。
「空中状態」になった場合は地面キャラとのリンクを切り、地面キャラとは無関係に移動できるようにします。

こうすることで、地面キャラが移動すると、それに対応してプレイヤーも勝手に移動してくれるようになります。
また、この仕組みをプレイヤーだけでなく箱にも適用させることで、複数の箱が積み重なっている状態とか、
その状態で一番下の箱を動かすとそれに乗っている全ての箱が動くとか、そういうことができるようになります。


・というわけで
もっといろいろ書こうと思っていたのですが、だんだんややこしくなってきました。
アクションゲームはこういうこと考えるだけでも面倒ですね。
もっと単純でうまい方法があるのかもしれませんが、とりあえず自分はこういうふうにやっています、というお話でした。
さて、仕事に戻るとしましょう…。
posted by JUNOSOFT at 13:22| Comment(0) | プログラミング

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年04月22日

データ形式で迷う

こんにちは。
最近 XML の記述が面倒でしかたありません。
データはできる限りテキスト形式で持つようにしたかったので(バージョン管理ツールと相性がよいのと、特別なツールなしに編集できるため)、対応エディターがたくさんあり、読み込みライブラリも充実している XML 形式にしたのですが、あれは人間が書くものじゃないですね…。

確かに HTML で見慣れたのと似たような形式だし、わかりやすいのですが、いかんせん記述量が多すぎます。
データよりもタグの方が多い気がしますし。

そこで今 JSON と YAML に注目しているのですが…。
JSON はコメントが書けないのが致命的です。データの一部をコメントアウトしたり、説明文を書くことがよくあるので。
一方 YAML はコメントが書けるものの、 Python みたいに先頭のスペースに意味があるのが気になります。
Python を書いているとき、ソースを複数行コメントアウトして、しばらくたってからそのコメントを外したときにインデントがずれていたことが何度かあったため、括弧による冗長性なしでインデントのみでデータ構造を示すのがちょっと不安です。

…と思っていたら、どうやら YAML には JSON 形式を含めることができるみたいですね。
となると YAML を、単に「コメントに対応した JSON」として扱うことができるかもしれません。

とりあえず libyaml を持ってきたので、後日試してみることにします。
posted by JUNOSOFT at 23:36| Comment(0) | プログラミング

2013年04月16日

glewをスタティックライブラリとして使う

こんにちは。

最近、スマホを弄っている時間の長さについて考えてみたのですが、若干危機感を抱きました。
自分の場合本当にただの暇つぶしで弄っているだけなので、それに費やしている時間を読書か勉強に割り当てるだけで、かなり充実した人生になりそうな予感がします。

勉強といえば、無駄かな?と思いつつも OpenGL のマニュアル本を買ってしまいました。分厚くて重くて高いヤツです。
で、某アプリで OpenGL + GLSL を使うために GLEW を組み込んでみました。
ところが GLEW の公式サイトから普通にバイナリをダウンロードしてくると、DLL 版しか入っていません。
そのままだとアプリを実行する際に必ず GLEW32.DLL が必要になってしまうため、なんとかならぬものかと思っていました。

とはいえ、悩むまでもなく答えはひとつしかないわけで、 GLEW を動的ではなく静的リンクすればいいと。
そいうわけで公式サイトからソースをダウンロードしてきました。
ありがたいことに VC++2010 用のソリューションが初めから含まれており、それにズバリ glew_static というプロジェクトも入っていました。
それをビルドすれば何の苦労もなくスタティックライブラリ版である glew32s.lib が手に入ります。


ところがこれを組み込んで自作アプリをビルドすると、リンカエラーが発生してしまいます。

「error LNK2001: 外部シンボル "__imp____glewAttachShader" は未解決です。」

これはなんかおかしいです。
GLEW のソースを見てみると、動的・静的の切り替えは GLEW_STATIC の定義の有無で行っていました。つまり #ifdef GLEW_STATIC です。
ところがその切り替えマクロがヘッダファイルに書いてあったのです。

ライブラリビルド時には GLEW_STATIC が定義されているが、アプリをビルドする時には GLEW_STATIC が定義されていない、という状態だと
ライブラリビルド時とアプリのビルド時でヘッダファイルの異なる部分が参照されてしまうという、わりとありがちな事故でした。

つまり、アプリ側で include して利用するときにも GLEW_STATIC を定義しておかないとダメで、

#define GLEW_STATIC
#include

とか書いておけば OK ということです。
ま、ここまでの内容は例の OpenGL マニュアル本と一切関係ないんですが。
あれは手元にあるだけでなんか安心してしまうというダメパターンになってしまいそうです。
posted by JUNOSOFT at 05:50| Comment(0) | プログラミング

2013年04月10日

アプリ内課金キャッシュ

iPad mini が、ミニとはいえ想像以上に見やすい大きさで、かつ非常に軽かったので、
本気で買おうかどうか迷っています。もう2週間ぐらい。
とはいえ冷静になって見てみると、そう悩むほどの値段でもありません。
考えているうちに買ってしまったほうが、時間が無駄にならずにすみそうです。

そう、時間が限られているこの世界、無駄なことはなるべくしたくありません。
プログラムでも、無駄な処理は書かないよう気をつけたいものです。

いま Android アプリ内課金を実装しています。
購入済みアイテムの一覧を得るためには IInAppBillingService.getPurchases を使ってサーバーに問い合わせます。
このメソッドは、どうやら勝手にアイテム情報をキャッシュしておいてくれるみたいです。
オフライン状態で呼んでもエラーを返しません。
最後にサーバに問い合わせたときの返事を、そのまま返してくれました。

購入済アイテムリストをローカルに保存しておき、IInAppBillingService.getPurchases がエラーを返したときはローカルからロードする、という処理を書く必要はないわけです。僕は書いてしまいましたが。

オフライン以外の原因でエラーが発生する場合もあると思うので、まったく無駄な作業だったとは言いませんけど...

まあそんなわけで、今夜あたり iPad mini をポチるかもしれません。
posted by JUNOSOFT at 22:38| Comment(0) | プログラミング

2013年04月01日

非2整数乗テクスチャ

こんにちは。
SimCity5 がやりたいのですが、動画を見ていて面白いと思うよりも先に、こんなの作りたくないなー(プログラム面倒的な意味で)と思ってしまったプログラム担当です。

2の整数乗ではない、中途半端なサイズの画像をテクスチャ化しようとしたとき、まあだいたい余白をつけて2の整数乗になるようにしてしまうと思うのですが、それだと D3DTADDRESS_MIRROR とか D3DTADDRESS_CLAMP の恩恵を受けられなくなるのが地味に嫌でした。
余白が邪魔になって、例えばテクスチャ範囲に (0,0) - (2,2) を指定してリピートさせるとか、そういうことができなくなるんですよね。
そもそも2の整数乗にしないでテクスチャを作るという手もありますが、それをサポートしているビデオカードがどの程度あるのかと。


ともかく、これって、だいたい次のような流れになっていますよね:
1 中途半端な大きさの画像がある。サイズ[w, h]とする
2 余白をつけて2^nサイズにする。サイズ[W, H]する
3 テクスチャ化する
4 縦横比が w:h になるようにサイズを決めて、板ポリゴンを作る。
5 テクスチャ範囲が (0, 0) - (w/W, h/H) になるように指定して、テクスチャをポリゴンに乗せる


この流れを、次のようにしたほうが便利なのでは?と思ったんです:
1 中途半端な大きさの画像がある。サイズ[w, h]とする
2 画像を適当なアルゴリズムで拡大し、2^nサイズにする。サイズ[W, H]とする。当然アスペクト比は崩れるが、余白はない。
3 テクスチャ化する
4 縦横比が w:h になるようにサイズを決めて、板ポリゴンを作る。
5 元画像と同じアスペクト費でテクスチャ全体を描画する。つまり (0, 0) - (1, 1) の範囲をテクスチャとして描画する


2^n 化したときに [W/w, H/h] 倍に引き伸ばしたテクスチャ画像を、[w/W, h/H] 倍にして描画するので、結局アスペクト比は元画像と同じになります。
こうすれば、テクスチャ座標として指定するのは (0,0) - (1,1) の範囲ですから、例えばこれを (0, 0) - (2, 2) とすれば REPEAT なり MIRROR なりで描画することができます。
アスペクト比はポリゴンの座標で決まるので問題ありません。

ただ、1度拡大した画像を縮小するので、元画像からある程度変化してしまう可能性があるのですが、補間なしに設定しておけば拡大時に余計な処理がされなくなるので、意外と大丈夫なのではないかなあと。
posted by JUNOSOFT at 23:09| Comment(0) | プログラミング