この手のゲームでは、時間の管理が重要なポインツだ。
自機や敵を一定間隔で動かさないと動きがぎこちなくなるからである。
この一定間隔を取り出すために割り込み処理を利用する。リアルタイムクロックを利用するものもあれば、画面の垂直同期を利用するものもある。一般的には画面の垂直同期を利用するものが多い。
これは、垂直同期というのがハードウエアが画面の内容をメモリからディスプレイに転送したタイミングであるからである。メモリからディスプレイへの転送中にメモリの内容を書き換えてしまうと表示内容が乱れてしまう。ソレを避けるには転送中にはメモリの内容は書き換えないようにしなければならない。実際には転送が終わったと同時にメモリの内容を変更することで、
間に合うことを祈っている。
しかしハードウエアの性能によりどうしても同間隔で動かすことができないことがある。このとき発生するのがコマ落ち、または処理落ちだ。
この手のゲームに限らず、コンピュータのシステム上は入力と出力がある。
特訓'99系のゲームだと、入力はレバー入力、出力は画面描画、という形で行われる。
コマ落ちというのは途中の画面が抜けてしまう現象で、
途中でテレポーテーションしたかのような演出
入力 | 入力 | 入力 | 入力 | 入力 |
処理 | 処理 | 処理 | 処理 | 処理 |
↓ | 蓄積 | 蓄積 | 蓄積 | ↓ |
出力 | 出力 |
実際には描画中に垂直同期割り込みが発生してしまった場合に発生する。この時に今行っている描画を優先し、入力内容はバッファリングしておく。
次の描画時に、バッファリングした内容も加算して描画する。
処理落ちというのは、あたかも自分が
加速装置を使ってしまったかのような演出
基本的に、これらの事象は性能が充分であれば発生しない。これらが発生するゲームは性能に対してデザインが大きすぎるという、致命的なバグである。
パソコンでゲームを作る場合、専用機と違って動作環境を特定できない。そのためどうしてもある環境では処理落ちが発生するなどの現象からは逃げることができない。
そのため、動作環境としてCPUを指定したり、ビデオカードを指定することがある。
よくある、CPU Pentium 200MHz以上推奨、とかいうのがソレだ。こうでもしないと処理速度を保証できないし、それでもビデオカードが遅かったりするとどうしようもない。
パソコンの場合は処理落ちが発生しても性能に対してデザインが大きすぎる、とは一概に言えないかもしれない。
「そういえばもう3秒たったよ」
コレじゃゲームには使えない
メッセージタイマとは、一定時間が経過した後にOSがウインドウに対して通知する仕組みである。
これは手紙みたいなもので、プログラムが自分からメッセージが届いていないか確認しないと届いていることが解らない。
つまり、届いてもその時にテレビに夢中だったりしてメッセージの確認をしていないと無駄になる。
また、そもそもが一定時間が経過した後に届くというだけでソレがいつ届くかという保証が無い。なので、入力のタイムアウトなどには有効だがリアルタイムゲームの時間基準とするには不向きである。
BKEYのタイムアウト機能はコレを使用している。
メッセージタイマと同じAPIで生成するが、こちらはウインドウに通知を送るのではなくて指定した関数を実行するパターンである。
そのため、メッセージの確認をしなくても、届いたことに気づいた時点で動く。
しかしやっぱり気づかないと動かない。
精度的にはかなりのものがあるのでかなり使える。
コレもコールバックタイマのように指定した関数が勝手に動く。
ちょー優先的なスレッドを作り出し、その中で時間待ちをして時間が来たら指定した関数を呼び出す、というような動作をしているため、指定した関数はアプリケーションの優先レベルではなく、OS並み(か、それ以上)の優先レベルで動作する。
そのため、このタイマで時間のかかる処理を動かすと、その間はPC全体が重くなる。
あと、よくわからないがときどき精度が不安定になる。
が、何しろ重いというのが致命的。
あと、タイマのコールバックからでは特定の関数を呼び出してはいけないことになっている。これが意外と不便で細かな制御を行う際に苦労する。
Windowsのプログラムでは、メッセージループというものを用意して、そこでメッセージを受け取り、配送する、という仕組みをもっている。
これがないと、先程のメッセージタイマや、コールバックタイマは使用できない。というのもこれらはメッセージベースで動作するタイマだからである。メッセージを受信する仕組みが無いとこれらのタイマーは動作しない。
しかし、メッセージを受信して、そのメッセージを処理している間はメッセージを受信できない。つまり、キー入力メッセージを受け取って其の処理を行っている間はタイマーの処理はできないし、その逆もできない。この事もメッセージタイマの精度を下げる要因にもなっている。
仕方がないので別の手段も考える。
要は、独立して動いていればいいわけだ。
ありがちなのがコレ、時間稼ぎにループを使うやり方だ。
for(i=0; i<1000; i++);とか書いて時間稼ぎしてごまかす。
for(i=0; i<1000; i++)Sleep(0);というところか。
for(dwTime=GetTickCount()+10; GetTickCount()<dwTime; Sleep(0));とか。
Windows 9x/NTなどの32ビットベースのWindowsではスレッドという単位でCPUを割り当てる。一つのプログラム(=プロセス)は複数のスレッドを生成することができる。
時間計測用のスレッドを用意することも一つの手段である。
ちなみに、瞑想修行では別スレッドを生成してその中でマルチメディアタイマを使用している。
これは、アプリケーションのメインスレッド内でマルチメディアタイマを使用すると、そっちに制御が奪われてしまい、キー入力と描画が追従しないという問題が発生したためである。このため、マルチメディアタイマから呼び出される関数は、タイマをセットしたスレッドのコンテキストで動作しているのだと誤解していたのだが、今回この特訓'99を開発している時にそうではないことが発覚してしまった。
こうなると、なぜ瞑想修行はタイマが不安定になったのか理由が推測できない。
とにかく、ウインドウを所有し、メッセージループを処理するスレッドでタイマを使うとタイマとメッセージループが干渉し精度を確保できない。
もっともマルチスレッドにしてしまえばタイマを使って定期的に関数を実行するよりも
という手段もある。while(1){ OnTimer(); Sleep(interval); }
コレも謎なのだが、試してみた感じ、GetMessage()APIはスレッドを停止させないようだ。
メッセージを受信するまで制御を返さない、というのはドキュメントに書いてある通りだがスレッドまで止めてくれるわけではないようで、スレッドの優先順位をあげてGetMessage()を呼び出すと他のスレッドの実行を疎外してしまう。
OS内部に入ってからCPUを消費するので、システムモニターなどでは測れないというのが面白い。
そのため、GetMessage()を呼び出す時には優先順位を標準にしておかないと挙動が怪しくなる。
ちなみにWaitMessage()はスレッドを停止させるようだ。
WaitMessage()を使わなくてもSleep(0);とでもしておくとかなり改善される。
処理落ちやコマ落ちについては基本的に、Windowsでは回避できない。
といいうのも、動作しているプログラムがゲームだけでは無いからだ。
DOS環境のように、他に動いているプログラムが無ければ自分のことだけに注意してデザインしプログラミングすることで回避できるが、Windowsのような環境だと自分だけがCPUを使えるわけではない。処理は間に合っているが、OSがCPUを割り当ててくれないために処理できない、ということが発生する。
32ビットのWindowsはCPUをスレッドに対して割り当ててくる。
スレッドにはそれぞれ優先順位があり、優先的なスレッドがあればソレを優先して実行し、それでも時間があまればその下位のスレッドを実行する。時間があまらなければ下位のスレッドは実行されない。
さっきのループで時間経過を待つ、という処理を実行した場合、これが優先スレッドだった場合には悲劇である。
これだけが実行されてしまい、ひたすらに時間が経過するのを待つことは待つのだが待ったあとの処理が動作しないことがある。while(play){ /* 時間が経過するまで待つ */ for(dwTime=GetTickCount()+10; GetTickCount()<dwTime; Sleep(0)); /* メインウインドウに時間経過を通知 */ PostMessage(MainWindow, WM_COMMAND, ADVANCE, 0); }
実行するスレッドの切替というのも厄介で、次にどれを動かすか指定する方法は無い。また、どれが動くかも判らない。
さきも挙げたように、優先順位の高いスレッドで時間のかかる処理をすると他のスレッドは身動きできない。
そしてマルチメディアタイマというのが最高優先順位で動作するスレッドとして実装されている(WIndows 95の場合)。そのため、マルチメディアタイマのコールバックで処理を行うとプログラム本体が動作できなくなってしまう。具体的には、
Sleep()で一定時間待つ、という手法も先程挙げたが、これもマルチメディアタイマ程度の精度しか持たない。
Sleep()を使用した場合、その時間は休止することが保証されるが、その時間が経過したら即時に復旧するとは限らない。というか保証されていないし期待できない。
メッセージタイマと同じように、時間が経過したことだけが保証される。
一定時間経過後に動きたいのであれば、現状では
この形が一番精度があるようだ。for(dwTime=GetTickCount()+interval; GetTickCount()<dwTime; Sleep(0));
ちなみに、特訓'99 1999-03-03バージョンにて
コールバック | マルチメディアタイマ | スレッド内でSleep | スレッド内でループ | ||
活動時間 | 53.808 | 16.768 | 17.600 | 17.440 | |
実時間 | 209.457 | 16.783 | 28.026 | 17.452 | |
精度 | 25.69% | 99.91% | 62.80% | 99.93% |
比率が低いほど速度が遅いと考えていただいてかまわないと思う。実際、コールバックタイマ版は非常に動作が遅くかわすのが容易であった。
非常に遅い。また、タイマーメッセージががんがん届いてしまうせいか、キー入力メッセージを処理しきれない。そのためポーズが効かない。
動作はスムーズ。しかし、タイマーのスレッドがちょー優先スレッドなためか16ms間隔ではメインスレッドのCPUサイクルを奪われてしまい動作できない。そのためポーズが効かない。
動作は割とスムーズ。ときどきもたついているような感じがするが気のせいと言われれば信じてしまいそうになる。
数値では6割程度の精度しか出ていないが体感的には8割位は出ていたような気がする。
人間の間隔なんて当てにならないものだ。
動作はスムーズ。
ただしスレッドの優先順位の設定を誤るととんでもないことになる。
というわけで、今まで別スレッドで
Copyright © 1999,2001 びい