Demo 16

Demo16 はパレットの使い方を説明している。中でも特に注目していただきたいのは、パレットアニメーションに関する部分である。すでに Demo16 を実行してみた人は分かると思うが、このデモでは市販のゲームソフトにもなかなか見られないような派手なエフェクトを使っている。しかもこのテクニックにはマシンパワーは必要無く、描画自体には単純にビットマップの背景を描くのと同じ時間しかかからないのである。使い方次第では見る人を「あっ」と驚かせることも出来る魔法のメソッド、CyclePalette の使い方を見てみよう。

procedure TForm1.FormActivate(Sender: TObject);
var
   x: integer;
begin
   //Make a SIN table to work with
   for x :=0 to 359 do
   begin
      //first calculate a real angle and save it in an array
      //Note: the * 90 after (Pi/180) is the radius -- change this
      //      value to change the size of the wave BUT you
      //      shouldn't go over 100 because of the clipping done
      //      to draw the gfx to the screen. Change it to 150 to
      //      see what I'm talking about.

      //JSin one is used for the visual plasma movement
      JSin[x] := Round(Sin(x *(Pi/180))*70);
      //XSin is used to calculate the x wave pattern
      XSin[x] := Round(Sin(x *(Pi/180))*77);
      //YSin is used to calculate the y wave pattern
      YSin[x] := Round(Sin(x *(Pi/180))*63);
   end;
   //When we blit from the BIG surface, we want the X & Y values to start off at
   //different points so that the don't visually follow the same path. Setting
   //this value to 90 will make the screen folow a circle pattern
   //or we could have computed with 'cos' to get the same affect.
   SinY := 90;
end;

先ほど時間がかからないといったが、それは描画のみを見た場合である。実際には描画に時間をかけなくて済むように下準備を行っているのだが、これはそのうちの1つで、サインテーブルを作っている所である。Delphi の三角関数を求めるルーチンは十分に早いので、通常はそのまま使っても問題は無いが、ゲームのように少しでも高速化したい場合にはこのような方法は良く用いられる。最後の SinY 変数は Cos を求めるのに使っていて、Sin と Cos は90度だけ位相がずれていることを利用している。

procedure TForm1.Screen1Initialize(Sender: TObject);

    :
    :

   Screen1.Palette.GetEntries(0,0,256,@Pal[0]);      //copy palette

今回のデモプログラムには DGCStarField コンポーネントが2つ使われているが、新しいことは何もしていないので触れないことにする。

さて、上のソースに含まれている新しいメソッドの GetEntries は、本当は今回のプログラムには必要無い物である。もし DGCScreen に ImageLibrary が設定されていると、DGCScreen の初期化時にパレットとして ImageLibrary のパレットがロードされるようなので、このようにすることによって Pal に DGCScreen のパレットを読み込む事が出来る。しかし、このデモプログラムでは使用する色はすべてこの後にプログラム中で作るので関係ないのだ。こんな事も出来ますよといった紹介の意味だろうか? なお、Pal 変数は次のように宣言されている。

  //The palette used for the color cycling
  Pal : T256PaletteEntry;

更に T256PaletteEntry は bmputil の中で次のように定義されている。

  //T256Palette Entry
  T256PaletteEntry = array[0..255] of TPaletteEntry;

パレットに RGB の情報をセットするには次のようにする。ここでは、赤色のグラデーションをパレットエントリの0〜81に対して与えている。その後、Pal を DGCScreen に渡すためには、SetPalette メソッドを呼び出せばよい。

   //Setting up a false palette with distinct colors -- using this so that
   //the Windows GDI doesn't try and match colors when drawing the plasma later
   For ColorIndex :=0 to 81 do
   begin
        Pal[ColorIndex].peRed   := ColorIndex*3;
        Pal[ColorIndex].peGreen := 0;
        Pal[ColorIndex].peBlue  := 0;
   end;

   Screen1.SetPalette(Pal);                 //set the new palette

ここで作ったパレットは偽のパレット(false palette)である。実際にここで作られた色は実行時には現れないが、だからと言ってこの部分をちょっと書き換えただけでもプログラムは思ったように動かない。いったい何のために偽のパレットが必要なのかは、もう少し後で説明することにして。とりあえずここまでの作業をまとめると次のようになる。

  1:すでに登録されているパレット全てを GetEntries を使って T256PaletteEntry に読み出す
  2:256 色のパレットの中の任意の色を変更する。
  3:SetPalette で新しくなったパレットを登録し直す。
これがプログラム中でパレットを設定する一つ目の方法である。もう一つの方法は、以前のデモプログラムでも登場しているが、部分的にパレットを変更する方法である。

   //Color of stars...
   For Loop := 0 to 4 do
   begin
     WorkPal[Loop].peRed   := 95 + (Loop * 40);
     WorkPal[Loop].peGreen := 95 + (Loop * 40);
     WorkPal[Loop].peBlue  := 95 + (Loop * 40);
   end;

   Screen1.Palette.SetEntries(0,240,5,@WorkPal); //Set the colors for the stars

DGCStarField がパレットエントリ中の 240 から 244 までを使用するので、ここでその部分に星の色を指定している所である。必要な色を作り終わったら、SetEntries メソッドでパレットを書き換えている。

パレットの準備が出来た所で、いよいよプラズマの下準備に取り掛かる。転送元となるサーフェイスはすでに BigSurface という名前で作成されているので、このサーフェイスに Canvas を使用して書き込みを行う。BigSurface のサイズは 800x600 である。

   //Draw the lines across the screen.
   ColorIndex :=0;                          //init index
   Direction := 1;                          //init direction : 1 for single increase
   With BigSurface.Canvas do                //working with the BIG surface
   begin
     Box.Left :=0;                       //draw lines of color on surface
     Box.Right := 799;
     For Line :=0 to 599 do
     begin
       Inc(ColorIndex, Direction);    //increase color used (by direction amount)
       If ColorIndex > 80 then ColorIndex := 1;  //only used 80 palette entries
       Brush.Color := PaletteRGB(Pal[ColorIndex].peRed,0,0); //Set pen to use color
       Box.Top := Line;
       Box.Bottom := Line+1;
       FillRect(Box);                 //draw the line (box since line uses moveto, etc...)
     end;
     Release;                            //release when done
   end;

あまり難しいことはしていないのですぐ分かると思うが、BigSurface に単なる横縞を描いているだけである。もちろん奇麗なグラデーションであるが、それだけでは何も起こらない。ここで注目するのは、Brush.Color をセットしている部分である。ここで先ほど Pal に指定した赤のグラデーションを使っているが、PaletteRGB によってパレット内のもっとも近い色に変換される。しかし、今回は指定する色が存在することが決まっているので、RGB を使っても問題ない。重要なのは、BigSurface に作るグラデーションが、パレットエントリの1〜80番によって作られることで、偽パレットを使っていたのはこれを確実にするためだったのである。Canvas を使い終わったら、最後の Release も忘れてはいけない。

確実にパレットの1〜80を使ってグラデーションを描いたら、次は本物のパレットを作る。パレットの0番は背景色として使われるので、ここで黒にしている。全て終わったら再び SetEntries を使ってパレットをセットする。これでようやくパレットの準備が全て整った。

   //Set these colors into the palette
   Screen1.Palette.SetEntries(0,0,82,@WorkPal[0]);

再びプラズマの準備に戻って、今度は横縞の BigSurface を「縦方向に波打つ横縞」にする作業をする。それが終わったら今度は、それを更に「横方向に波打つ、縦に波打つ横縞」にするべく作業を開始する。全て終わった状態をいきなり作り出すことも可能だが、そうすると Canvas の pixels プロパティを使うことになり、それではとても遅くなるので使いたくないのだろう。

   //The following is where the plasma is generated

   //This is the vertical sine pattern (up & down shifting)
   SinPos :=0;                                 //init place holder
   For pixel := 0 to 798 do                    //each column
   begin
     box.Left := pixel;                     //column working with
     box.Right := pixel + 1;                //column width of one
     //calculating clipping for blt
     if XSin[SinPos] > 0 then               //positive clipping
     begin
       Top := XSin[SinPos];              //set position for dest
       box.Top := 0;                     //set top source position
       box.Bottom := 599 - XSin[SinPos]; //get the proper amount of pixels
     end
     else                                   //a negative value
     begin
       Top := 0;                          //set top to 0 for top of screen
       box.top := abs(Xsin[SinPos]);      //set top source position
       box.bottom := 599;                 //get the right amount of pixels
     end;

     //do the blit
     BigSurface.BltFast(Pixel,Top,BigSurface,Box,False);
     Inc(SinPos);                           //increse sine index
     If SinPos > 359 then Dec(SinPos,360);  //if out of array, reset
   end;

「横方向に波打つ、縦に波打つ横縞」が完成した時点でプラズマの下準備は終わりである。ここでパレットアニメーションを開始するため、CyclePalette メソッドを呼び出す。引数は、アニメーションに使うパレットエントリの最小値・最大値と、ローテーションするパレットのステップ、フリップに対するパレット更新のスキップを指定する。早くアニメーションしたい場合は Step を大きくすればよく、遅くしたい場合は Skip を大きくすれば良い。

   //Now that the plasma screen is generated, start the color cycling routine.
   //This is a new function to DGC Beta 5
   Screen1.CyclePalette(1,80,1,0);

ここまでの説明を聞いても、どんな画面が出来たのかさっぱり分からない人もいると思うので、実際に出来上がった画面を見てみよう。まずは DGCScreen の DisplayMode プロパティを 800x600x8 に設定し、デモプログラムの一番最後の部分を次のように書きかえる。

   Screen1.Back.BltFast(0,0,BigSurface,Rect(0,0,799,599),False);
//   Screen1.Back.BltFast(0,45,BigSurface,Rect(JSin[SinX]+200,JSin[SinY]+200,JSin[SinX]+520,JSin[SinY]+349),False);
//   Stars1.Update;
//   Stars2.Update;

要するに出来上がった BigSurface を画面いっぱいに表示させてみるのだ。StarField は邪魔になるのでコメントにしている。画面全体に波打つ縞模様が見えると思うが、実際に画面表示する時はここから一部分を切り取って BltFast で転送している。切り取る元の座標が円を描くように移動するため、不規則な動きをしているように見えるのだ。

以上で今回のデモプログラムの説明を終わる。ちょっと分かり難いかと思うが、自分で試してもらえばパレットも自由に使えるようになると思う。

もどる