●Top ●Diary (old) ●Products ●Documents ●Donation ●Link

Delphiのすすめ

2001/04/21 改定
次のようなことを考えて改定中です。
  • グローバル変数は極力使わない
  • プログラム内になぞの定数値(マジックナンバー)を直接書き込まない
  • 変数、関数などには意味のある名前を付ける
  • 機能単位で適当にファイルを分ける
  • publicなフィールドは作らない
もぐら叩きの全ソースコードをおきました。

Windowsでプログラムをしてみたいと思いませんか
でもなんか難しそう、そんな人にはDelphiがお勧めです。
他にも、VisualC++(以後VC),VisualBasic(以後VB)なんか有るけどどれもちょっと問題あり。
C++は今はもう、どうにも複雑になりすぎててどう考えても入門用の言語なんかじゃない。しかも、実は文法を知ってても慣習のたぐいでの制約もなんかごちゃごちゃとある。
VBはVBで、なんかやたら実行速度遅いし、大体なんでオブジェクト指向といいつつ継承一つできないのはなんで?(なんか、次のバージョンではできるらしいけどね)
それにVBは簡単といわれてるけど、それは最初のうちだけで、だんだん制約多すぎて逆に面倒くさくなるみたい。

それに比べてDelphiはすばらしい、GUIだけでアプリの見た目はたいていできるし、言語使用も整理されてて覚えやすいし、Windowsでできることはたいていできる(まあドライバは書けないみたいだけど、普通やらないでしょ)、それに作ったアプリの実行速度もC++のたぐいに引けを取らないし、コンパイルも中間コード作ってるはずのVBより早いんじゃないだろうか。(Basicからきた人は、最初は厳しい型チェックがうざいかも知れないけど、ちょっと慣れれば全然問題なし。バグも減るしね。)
まっ、簡単に言うとVBとVCのいいとこどり。
追加
このへんは、部誌の他の記事との絡みです。まあ、極論というか。
それぞれいい所もありますが、Delphiもいいですよ。


まあそういうことでみんなDelphiを使うと結うことになったのでDelphi講座をはじめます。
ここでは、簡単に文法をチェックしてから、ミニゲームを作るところまで行こうと思います。

代入   a := 10; 変数に、値を代入する。
条件 if n = 0 then
  begin
    a := 1;
    b := 1;
  end
else
  a := 2;
式がtrueだったら、thenの次の文を実行し、falseだったらelseの次の文を実行する。
elseの直前の文には;がつかないことに注意。
条件 case n of
  0..2 : a := 1;
  3,4 : a := 3;
  5..10: a := 2;
else
  a := 0;
end
式の値で、下のリストに分岐する。
ループ文 for i:=0 to 10 do
begin
  a := a + i;
  b := b * 2;
end;
カウンタの値を初期値から終値まで1ずつ増やしながら、 その次の文を実行する。
ループ文 while not (i mod 10) do
begin
  i := i + 3;
  n := n + 1;
end;
式がtrueのあいだ次の文を実行する。
ループ文 repeat
  i := i * 2;
  a := a - 1;
  b := i * a;
until b <= 15;
untilの次の式がtrueの間repeatからuntilまでを実行する。
ジャンプ label Label1;
begin
  for i := 0 to 10 do
    for j := 0 to 120 do
      for k := 0 to 120 do
        if i * j - k = 5 then
          goto Label1;
  Label1;
end;
ラベルまで無条件に飛びます。一般にgoto文はプログラムの流れが分かりにくなるので、使わない。多重ループから抜けるときには、使ってもよいかも。

さぁまずDelphiで使われているObjectPascalの文法の説明からはじめますが、全部説明するのは紙面の都合でつらいし、実用的でないと思うのでBasicかC/C++なんかの言語をかじった事があるとして話を進めます。(なんか上で書いたこととちょっと矛盾してるような気もするけどまぁそんな細かいことは気にしないように。)


予定変更、簡単なプログラムの作成を通して文法もいっしょにやりましょう。
#あっ、それからDelphiのバージョンは一応5.0で話を進めるけど多分2.0以降なら問題ないはず。
追加
Delphi2.0では、TImageのTransparentプロパティがないようです。

さあ、Delphiを起動します。ロゴの後に統合開発環境が出てきて、 下のような画面になるはず。
Form1とか書いてあるウィンドウをフォームと呼んで、これはそのままソフトを実行したときのウィンドウになる。

まずは、基本のハローワールドかな。
上のほうにある、Standardの中からButtonを選んでフォームにはりつけます。

ここで試しに緑の三角形になっている実行ボタンを押してみると、ボタンの1個ついたウィンドウが出てくるでしょ。ボタンを押しても何もおこんないけど最大化も最小化もできるし、終了することもできるでしょ。コード(プログラム)一行も書いてないけどソフト作れたでしょ(まあ何の役にも立たないけど)。
さて、さっきのウィンドウを閉じたらフォームに置いたボタンをダブルクリックしてみて。
procedure TForm1.Button1Click(Sender: TObject);
begin

end;
と表示されたでしょ。
ここを
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('Hello World!');
end;
と変えて実行してみましょ。
ボタンを押すと、Hello World!と書かれたメッセージボックスが表示されるよね。

Delphiでは、こんな感じで、マウスでボタンなんかのコンポーネント(リストとかメニューとかラジオボックスとかいろいろ)をおいて、そこで何か起こったときにどういう処理を書くことで成り立っている。
ここでやったのは、ボタンが押されたときにShowMessageを呼ぶようににすること。オブジェクトインスペクタのButton1:TButtonのイベントのところを見てみると、OnClickのところにButton1Clickと書かれているはず。これはButton1でOnClickイベントが起こったときに、Button1Click手続きを呼ぶようしてる。
ShowMessageというのは、試してみたならわかったと思うけど引数で渡した文字列とOKのついたメッセージボックスを表示する手続き。
あとはそうそう、Pascalでは文字列は、シングルクォーテーションでくくります。

procedureというのは、手続きの意味でfunctionは関数なんだけど、これはほかのプログラミング言語をやってた人ならわかるよね(Cのvoidを返す関数が手続き)。

Delphiで何か作るとき基本になるのは、ここでやったように何かが起こった(ここではボタンが押された)時に何かをする(ここではメッセージボックスを表示する)という考え方が基本になっている。これを、イベントドリブン式のプログラムという。

何かこれだけじゃなにがなんだか分からないかなぁ。
まぁ、いくつか例を見てなんとなくわかればそれでOKということで、ちょっともぐら叩きを作ってみましょ。(いまどきそんなん誰もやらないとか言わないように)
全ソースを追加しました。

まずは、Delphiの得意なユーザーインターフェイスから作っていきましょう。
こんな感じの画面にしようかな。

使っているコンポーネントは、TButton,TLabel,TPanelで下のように配置。

それぞれのNameをオブジェクトインスペクタで、GamePanel、StartButton、TimeLimitLabel、ScoreLabel、ScoreValueLabelに変えます。 GamePanelの GamePanel の文字がうざいから、Caption欄にある文字列を消す。
それから、StartButtonのCaptionをStartに変える。
TimeLimitLabel,ScoreLabel,ScoreValueLabelのCaptionもそれぞれ適当に変えといてね。

ここで、登場するもぐらのクラスを作っておこう。
まず、
メニューからファイル新規作成〜新規作成〜ユニットでユニットを作ってMole.pasに名前を変えて保存。
type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Label1: TLabel;
    Label2: TLabel;
となっているところに、
type
  TMole = class(TObject)
  private
  protected
    FX,FY: Integer; //モグラの位置
    FWidth,FHeight: Integer;//モグラの大きさ
    FDead: Boolean;
    FBirthTime: Integer; //生成された時間
    FCurrentTime: Integer; //最終更新時間
    FDeathTime: Integer; //叩かれた時間
    procedure OnClick(Sender: TObject);
  public
    FImage: array[0..1]of TImage;//モグラの絵
    constructor Create(AOwner: TComponent;Parent: TWinControl; x,y: integer;birth: integer);
    destructor Destroy;
    procedure Update(time: Integer);
  end;

  TForm1 = class(TForm)
と付け加える。
JavaとかC++を知っている人ならなんとなくわかるよね、このオブジェクトの持つ変数としてFX,FY,FWidth,FHeight,FBirthTime,FCurrentTime,FDeathTimeをInteger型、つまり整数型の変数、FDeadをBoolean型、つまり論理型として宣言している。
さらにこのオブジェクトのクリックされたときのイベントハンドラとして使うOnClickメソッド、そしてコンストラクタとデストラクタ、さらにUpdateメソッドを宣言する。
#Delphiでは最初のtypeってところから下で、型の宣言をすして、implementationの下で実体を定義するようになってる。

そして、
var
  Form1: TForm1;

implementation

となってるところを、 var
  Form1: TForm1;
  Score: Integer;

implementation

と書きかえるんだけど、ここはグローバル変数を宣言するところで得点に使う変数を整数型で宣言しとく。(グローバル変数の多様は一般的にバグの宝庫になりかねないのであんましよくないみたいだけど)
そんで、次にもぐらクラスの実装に入りましょ、
まずコンストラクタ、
constructor TMole.Create(AOwner: TComponent;Parent: TWinControl; x,y: integer;birth: integer);
var
  i: integer;
  bmp: TBitmap;
begin
  FX := x;//位置
  FY := y;
  FBirthTime := birth;//画面に出てくる時間
  FDead := false;//叩かれた?
  bmp := TBitmap.Create;//画像読み込みに使うビットマップのインスタンスを作成。
  for i := 0 to 1 do
  begin
    FImage[i] := TImage.Create(AOwner);//Ownerが開放されるときに自分も開放される。
    bmp.LoadFromFile('Mole'+IntToStr(i)+'.bmp');//ビットマップファイルを読み込む
    FImage[i].Picture.Assign(bmp);//ビットマップからFImageにコピー
    FImage[i].Enabled := false;//イベントを受け取らない。
    FImage[i].Visible := false;//見えない。
    FImage[i].Left := FX;//X座標
    FImage[i].Top := FY;//Y座標
    FImage[i].AutoSize := true;//ビットマップの大きさにあわせる。
    FImage[i].Parent := Parent;//親コントロールを設定。(これを作るときにこれを設定し忘れて???だったし。)
    FImage[i].Transparent := true;//ビットマップの背景を透明にする
  end;
  FImage[0].OnClick := OnClick;//クリックされたときに、OnClickメソッドが呼ばれるように設定しとく。
end;
フォームデザイナでポンポン置いていくとこの辺の設定は楽なんだけど、今回は実行時に作成してるので結構面倒に見えるかもね。

次はデストラクタ、
destructor TMole.Destroy;
var
  i: integer;
begin
  for i := 0 to 1 do
  begin
    FImage[i].Free;
  end;
end;
Freeというのは、オブジェクトがnil(Cとかで言うところのNULL)でないときにデストラクタを呼び出すもので、直接Destroyを呼び出すよりも安全。

でクリックされたときのイベントハンドラ
procedure TMole.OnClick(Sender: TObject);
begin
  //叩かれたら、死んじゃう。
  if not FDead then
  begin
    FDeathTime := FCurrentTime;
    FDead := true;
    //表示する絵を切り替える
    FImage[0].Enabled := false;
    FImage[0].Visible := false;
    FImage[1].Enabled := true;
    FImage[1].Visible := true;
    Score := Score+100;//得点100点
  end;
end;

時間にあわせて表示非表示を切り替えよう
procedure TMole.Update(time: integer);
begin
  if FDead then
  begin
  //死んでから2秒後に非表示にする。
    if FDeathTime + 2000 < time then
    begin
      FImage[1].Enabled := false;
      FImage[1].Visible := false;
    end;
  end
  else
  begin
    if FBirthTime < time then
    begin
      FImage[0].Enabled := true;
      FImage[0].Visible := true;
    end;
    //2〜4秒表示したら非表示に切り替える。
    if time > FBirthTime + 2000 + Random(2000) then
    begin
      FImage[0].Enabled := false;
      FImage[0].Visible := false;
      FDead := true;
    end;
  end;
  FCurrentTime := time;
end;

次はTForm1の変更
まずは宣言部分
  TForm1 = class(TForm)
    Button1: TButton;//ここから
    Panel1: TPanel;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    procedure FormClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);//ここまではDelphiによるコード
  private
    { Private 宣言 }
    procedure ApplicationIdle(Sender: TObject;var Done: Boolean);//こっから
  public
    { Public 宣言 }
    FMoles: array[0..MOLEMAX]of TMole;
    FBaseTime: integer;
    FImage1 : TImage;
    procedure StartGame;
    procedure EndGame;//ここまでを追加してね
  end;

さて実装は、

ゲームはじめるときに各種の初期化をする
procedure TForm1.StartGame;
var
  i: integer;
begin
  for i := 0 to MOLEMAX do
  begin
    //ここでまずモグラのインスタンスを作る。
    FMoles[i] := TMole.Create(Form1 ,Panel1
      ,Random(Panel1.Width-120),Random(Panel1.Height-120),random(55000));
  end;
  Score := 0;//得点を初期化
  Application.OnIdle := ApplicationIdle;//OnIdleハンドラに、ApplicationIdle手続きを取り付ける。
  FBaseTime := timeGetTime;//開始時間を記録しとく
end;
うえの
Application.OnIdle := ApplicationIdle;
ってのは、アプリケーションが暇なとき(処理すべきメッセージがないとき)にApplicationIdle手続きを呼ぶようにするということ。
これはDelphiのゲームのメイン部分を作るときに使われる方法の一つ

作っといたオブジェクトを開放してOnIdleでやってたループをとめる。
procedure TForm1.EndGame;
var
  i: integer;
begin
  //もぐらのオブジェクトを開放
  for i := 0 to MOLEMAX do
  begin
    FMoles[i].Destroy;
    FMoles[i] := nil;
  end;
  Application.OnIdle := nil;//ループをとめる
  Button1.Enabled := true;//ゲーム開始を可能にする。
end;

ここで無限ループみたいな処理にしてメイン部分を作る
procedure TForm1.ApplicationIdle(Sender: TObject;
var Done: Boolean);
var
  time: integer;
  i: integer;
begin
  time := timeGetTime - FBaseTime;//開始からのミリ秒数
  for i := 0 to MOLEMAX do
  begin
    FMoles[i].Update(time);
  end;
  //タイムオーバーでゲーム終了
  if time > 60000 then
  begin
    ShowMessage('Score:'+IntToStr(Score));
    EndGame;
  end;
  Label3.Caption := 'あと'+IntToStr((60000-time)div 1000)+'秒です';
  Label2.Caption := IntToStr(Score);//整数を文字列に変換
  Done := false;//ここでDoneにfalseを代入することで、この手続きの終了直後にまたこの手続きが呼ばれる。
end;

お手付きをしたときは、得点を減らす。
procedure TForm1.FormClick(Sender: TObject);
begin
  Score := Score - 50;
end;

ボタンが押されたらゲームをはじめる。
procedure TForm1.Button1Click(Sender: TObject);
begin
  StartGame;
  Button1.Enabled := false;
end;

そしてモグラの絵をビットマップファイルで用意して(Mole0.bmp,Mole1.bmp)
さぁ、ここまでやったら実行してみましょう。どう、うまく動きましたぁ?

ここまで、かなに駆け足で説明(?)してきたけど、少しは役に立った?
わかんないこととか、変なとことか会ったら直接聞きに来てみてね。
Contact me <zakki@peppermint.jp>
© 2001-2006 zakki. total:total /yesterday:yesterday /today:today