レベル3

目次

レベル-1
入門以前の知識の紹介。
レベル0
コンポーネントの使用。
レベル1
とことんShowMessage。
レベル2
じゃんけん。
レベル3
キャラの表示と十字移動。
レベル4
敵をつくってみよう。
レベル5
DelphiXを利用してみよう。
発展
発展的入門知識(矛盾?)。
ショートカットキー
ショートカットキーの紹介。
テクニック
さまざまなテクニックの紹介。

いままで作ったプログラムは、ShowMessage位の手続きしか使わないファミコンとかのゲームとは程遠いものでした。そろそろみんなゲームプログラミングしたいと思っている頃だと思うので、ゲームプログラミングを始めましょう。

とりあえず最初はキャラを敵から逃がすだけの「避けゲー」をつくります。新しいコンポーネントとかが次々と出てくるので注意してください。

TImageコンポーネントを利用してキャラを表示する

ゲームを作るためにはキャラを表示させる必要があります。キャラが表示されなかったら風のリグレット(←暇があったらぐぐってみよう)になってしまいます。

で、キャラを表示させる一番簡単な方法がTImageを使うことです。TImageを使うと画像を読み込むだけでキャラの表示ができます。(これ、実は他言語だと可也難しい作業なんですよね…)

TImageはコンポーネントパレットの「Additional」の左から六番目にある絵のマークです。これを今までと同じように貼り付ければ、四角形が表示されるかと思います。

そして貼り付けた「Image1」のPictureプロパティのところをダブルクリックしてください。

「画像の設定」というものが表示されるので、「読み込み」を押して適当な画像を読み込んでからOKボタンを押せば画像が表示されます。

画像を読み込んだ後は、Image1のAutosizeプロパティをTrueにすればWidthが画像の横幅に、Heightが画像の縦幅に設定されます。

これからこのキャラは「自機」として話をすすめるので、わかりやすくするためにnameプロパティを「ziki」にしておいてください。

TTimerコンポーネントを使ってキャラに動きをつける

あなたは何故アニメにおいてキャラが動いて見えるのか知っていますか?あれは、少しずつキャラを微妙に動かした静止画を連続して見せているからキャラが動いて見えるのです。

ゲームにおいてキャラが動いて見えるのも同じです。毎ループ毎にキャラの位置を少しずつ動かしているのです。

さて、ちゃんとアニメーションするためには「このImageをこれだけ動かす」という命令を一秒間に何回も実行する必要があるわけですが、今まで「何もしなくても常に命令の実行を繰り返す」というイベントはありませんでした(OnClickイベントはクリックした時にしか実行されなかったし、OnCreateイベントは起動時にしか実行されませんでしたね)。

ここでTTimerの出番となります。TTimerのOnTimerイベントを利用すればキャラに動きをつけることができるのです。

TTimerの使い方(微妙にややこしい)

コンポーネントパレットの「System」から時計の形をしたものを貼り付けてください。位置はどうでもいいです。

次に、貼り付けたTimer1のIntervalプロパティを50あたりにしてください。このIntervalプロパティは「1秒を1000とするとどれくらいの間隔で実行するか」というものです。50だと0.05秒に一回実行されます。ちなみに、子の作業を忘れるとキャラの動きが異常に遅くなります。

いじったら、OnTimerイベントのところをダブルクリックして

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ziki.top:=ziki.top + 5;
end;

こう書いて実行してください。絵はどのように動いたでしょうか。動かない人は多分Timer1のIntervalプロパティをいじるのを忘れています。

この「Top」というプロパティは、zikiの上端のy座標を表しています。ちなみに左端を表すプロパティは「Left」です。

ここで、ある程度数学の知識を持った人ならば、少し疑問に思うと思います。なぜなら数学における座標はy座標が増えるほど上に行くからです。

これは、プログラミングの際使われる座標の種類が数学のとは違う「スクリーン座標系」であるからです。コンピュータは描画を画面の上端から行うため、y座標の向きが逆になるのです。

さて、下に動かしたところで今度は右に動かしてみましょう。

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ziki.left:=ziki.left + 5;
end;

十字キーでキャラを移動させられるようにする

タイトルのまんまです。構想を練ってみると

if <上キーが押されている> then ziki.Top:=ziki.Top - 5;
if <下キーが押されている> then ziki.Top:=ziki.Top + 5;
if <左キーが押されている> then ziki.left:=ziki.left - 5;
if <右キーが押されている> then ziki.left:=ziki.left + 5;

こうなります。これを実装するためには、各キーが押されているかどうか調べる必要があるのですが、どうすればいいのでしょうか。

キーの状態を調べるにはGetKeyState関数を使います。引数として仮想キーコードというのを渡すと、キーの状態を調べてくれます。キーが押されていれば戻り値は0以下になります。GetKeyState関数を使って構想を実現してみると

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if GetKeyState(VK_Up) < 0    then ziki.Top :=ziki.Top  - 5;
  if GetKeyState(VK_Down) < 0  then ziki.Top :=ziki.Top  + 5;
  if GetKeyState(VK_Left) < 0  then ziki.left:=ziki.left - 5;
  if GetKeyState(VK_Right) < 0 then ziki.left:=ziki.left + 5;
end;

VK_Downとか何?ってひとはヘルプで「仮想キーコード」と調べればなんとなくわかると思います。

なんかGetKeyStateとか長くて覚えられないよ!って人は使う時この文を写せばOKでしょう。

実行すれば、絵が動きます…うまく行けば。

画像のブレを無くす

今までのプログラムを実行すると気付くでしょうが、移動するたびに画像がぶれてしまいます。なぜ画像がブレてしまうのでしょうか? 理由として

というのがあります。とりあえず、この二つの問題を解決しましょう。

画像の座標を直接いじるのをやめる

実は、画像の座標を直接いじるのをこれからやる方法で修正するといろいろ便利になります。

…とまあ、100利あって1害なしって感じなのです。どのようにするのかというと

  1. 移動量を変数Dx,Dyに代入
  2. Top:=Top + Trunc(Dy);などとする。(このTruncは切捨てをする関数で、引数として小数の変数を渡すと切り捨てられた整数を返します)
  3. DyをFrac関数で小数部のみにする。(Frac関数は実数の小数部を返す関数です)

なんか新しい関数が結構出てきて良くわからないと思いますが、実際書いてみると「あー…そういうことか」ってな感じになるのでまずは写して実行してみてください。

{$R *.dfm}
var
  Dx,Dy:Extended;
  
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //移動量を予測する。
  if GetKeyState(VK_Up) < 0    then Dy:=Dy - 5;
  if GetKeyState(VK_Down) < 0  then Dy:=Dy + 5;
  if GetKeyState(VK_Left) < 0  then Dx:=Dx - 5;
  if GetKeyState(VK_Right) < 0 then Dx:=Dx + 5;
  
  //移動予測量を切り捨てて代入(Ziki.TopとかはInteger型のため)
  Ziki.Top :=Ziki.Top + Trunc(Dy);
  Ziki.Left:=Ziki.Left+ Trunc(Dx);
  
  //Dx,Dyを小数部分のみにする。これを忘れると速度がありえないくらい上昇しちゃいます
  Dx:=Frac(Dx);
  Dy:=Frac(Dy);
end;

ちなみに、運動予測量の代入とかの部分は最後に書いたほうがいいです。OnTimerの中間らへんとかに書くとあまりよくないです。

これでちょっと画像のブレが緩和されます…が、まだ画像のブレがあると思います。完全に画像のブレを無くすため、最後の「ダブルバッファリングをしていない」という問題を解決しましょう。

ダブルバッファリングを行う

ダブルバッファリングとは

メモリ上に表示されない「裏」の画面のようなものを作ってその上にいろいろ描画を行い、その「裏」の画面を最終的に「表」の画面にコピーする画像のブレを無くすための技法です。

なんで画像がぶれるの?

実は画像の表示はとても手間のかかる作業で、OnTimer部で画像を移動させる速度なんかよりずっと遅いのです。そのため、まだ書き途中でも表示されてしまう時がでてしまい、画像のブレが生じるのです。

画像のブレを防ぐためにはどうすればいいの?

なんだかんだいって、プレイヤーに完全に描画しきった画面だけ表示するようにすればブレがなくなるわけですよね?

ダブルバッファリングで表示されない画面をつくり、その上にまず完全に描画しきります。その完成した「裏」の画面を「表」の画面にコピーすれば、プレイヤーは完成した画面だけを見ることができるのです。

どうやってダブルバッファリングするの?

ここまでいろいろと難しい御託を並べてきましたが、実はたったForm1のOnCreateイベントに一行追加するだけでできちゃいます。

procedure TForm1.FormCreate(Sender: TObject);
begin
  DoubleBuffered:=True;
end;

これだけで終わりなんです。実装する事自体はめちゃくちゃ簡単なんですね。ちなみにDoubleBufferedはBoolean型の変数で、Trueは「真」Falseは「偽」を表します。

ちなみに、ダブルバッファリングを行うと「裏」の画面をつくるために使用メモリ領域がめちゃくちゃ上がることにも注意する必要があります。

with文 それは愛

いままで「ziki.top」「ziki.left」などと書き続けてきましたが、これちょっとメンドクサイと思いませんか?

というか「Form1.Top」とかはいじる事がないわけで、「此処から此処までの『Top』『Left』は『Ziki』のものだよ」ということができればかなり楽になるわけです。

この役割をするのがwith文です。

with <親となるもの> do
begin

end;

こうすれば、beginとend;の間のものはある程度省略できるようになるのです。

今までのプログラムを修正してみましょう。

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  with ziki do
  begin
    //beginの後は2文字分インデントしたほうが見易い。
    
    //移動量を予測する。
    if GetKeyState(VK_Up) < 0    then Dy:=Dy - 5;
    if GetKeyState(VK_Down) < 0  then Dy:=Dy + 5;
    if GetKeyState(VK_Left) < 0  then Dx:=Dx - 5;
    if GetKeyState(VK_Right) < 0 then Dx:=Dx + 5;
    
    //移動予測量を切り捨てて代入(Ziki.TopとかはInteger型のため)
    Top :=Top + Trunc(Dy);
    Left:=Left+ Trunc(Dx);
    
    //Dx,Dyを小数部分のみにする。これを忘れると速度がありえないくらい上昇しちゃいます
    Dx:=Frac(Dx);
    Dy:=Frac(Dy);
  end;
end;

「あんま短くなってなくね?てかこれやった方がめんどくさくね?」と思うかもしれませんが、絶対やったほうがいいです。大規模なプログラムだと労力が全然違うし、小規模なものでも「Ziki」を「Mario」などと名称変更する必要あった場合修正が楽ですので。

というかこれから先変えさせるんですけどね…

移動範囲の限定

今までのプログラムを実行すればわかると思いますが、キャラが画面外に飛び出してもそのまま移動し続けてしまう為、ちょっと修正する必要があります。

行き先の座標が画面の範囲外にあったら、動く分を無効にしてやればいいのです。この時、画面の右端に当たった時は必ずキャラが右に動いているということに注意してください。こういう「いまどっちむきに動いてるか」ってのが知れるからDx,Dyを使うと便利なんですよね…

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  with ziki do
  begin
    //移動量を予測する。
    if GetKeyState(VK_Up) < 0    then Dy:=Dy - 5;
    if GetKeyState(VK_Down) < 0  then Dy:=Dy + 5;
    if GetKeyState(VK_Left) < 0  then Dx:=Dx - 5;
    if GetKeyState(VK_Right) < 0 then Dx:=Dx + 5;
    
    (******************こっから******************)
    if (top + Dy < 0) and (Dy < 0) then Dy:=0;
    if (top + Dy > form1.height - height) and (Dy > 0) then Dy:=0;
    if (left + Dx < 0) and (Dx < 0) then Dx:=0;
    if (left + Dx > form1.Width - Width) and (Dx > 0) then Dx:=0;
    (***************ここまでが追加分*************)
    
    //移動予測量を切り捨てて代入(Ziki.TopとかはInteger型のため)
    Top :=Top + Trunc(Dy);
    Left:=Left+ Trunc(Dx);
      
    //Dx,Dyを小数部分のみにする。これを忘れると速度がありえないくらい上昇しちゃいます
    Dx:=Frac(Dx);
    Dy:=Frac(Dy);
  end;
end;

こうすればキャラが画面の外に出るなんてことはなくなります。追加する位置に注意してください(移動量予測と代入との間に書いてください。)

発展

GetKeyState関数はWin32APIの一つです。

APIって?

ソフトウェアを開発する際に使用できる先人が作られた命令や関数の集合のことです。

Win32APIって?

Windows 95/98/Me/NT/2000/XPにてマイクロソフトが提供しているAPIです。これを利用すれば、キーが押されているか押されてないか判定できたり、CDトレイを開閉したり、マウスの位置を取得したりといろいろできます。

ためしにForm1のOnCreateにSwapMouseButton(True);と書いて実行してみると、Win32APIの便利さが体感できるかも?

困ったら再起動してね