第10回:背景変更エフェクト(2) モザイクアウト・イン



背景変更エフェクトのもう一つの例として、モザイクアウト、モザイクインを解説します。
モザイクアウトとは、画面を幾つかのブロックに分けて各ブロックを特定の1色で
塗りつぶすという処理を、ブロックのサイズを徐々に大きくしながら行っていく
エフェクトのことです。

先に実際の画面をお見せしましょう。
図10-1は通常の画面、図10-2と図10-3は、図10-1から徐々に
モザイクアウトをかけている状態です。
ここで図10-2のブロックサイズは20x15ピクセル、
図10-3のブロックサイズは80x60ピクセルです。

図10-1:モザイクアウト処理前 図10-2:モザイクアウト処理中
(ブロックサイズ:20×15ピクセル)
図10-3:モザイクアウト処理中
(ブロックサイズ:80×60ピクセル)

モザイクアウトの処理の流れは、以下の通りです。
  1. ブロックの幅の初期値を設定する
  2. ブロックの高さを算出する
  3. ブロックのサイズから、表画面内のブロックの数を取得する
  4. 各ブロックについて、ブロック左上のピクセルのRGB値を取得し、その色でブロックを塗りつぶす
  5. 表画面を再描画する
  6. ブロックの幅を二倍にする
1.を行った後、2.から6.をブロックの幅が表画面の幅を超えるまで繰り返します。

モザイクインは、モザイクアウトと逆の処理です。流れを以下に示します。
  1. 裏画面に切り替える背景画像をロードする
  2. ブロックの幅を表画面の幅に設定する
  3. 裏画面を表画面に転送する
  4. ブロックの高さを算出する
  5. ブロックのサイズから、表画面内のブロックの数を取得する
  6. 各ブロックについて、ブロック左上のピクセルのRGB値を取得し、その色でブロックを塗りつぶす
  7. 表画面を再描画する
  8. ブロックの幅を半分にする
1.と2.を行った後、3.から8.をブロックの幅がある値以下になるまで繰り返します。

算出するブロックの高さは、ブロックの幅の3/4とします。
これは、表画面の縦横の比率が4:3となっているため、ブロックの縦横の比率も4:3とすると
表画面を分けてできるブロックの数が縦方向と横方向で同じになるからです。
モザイクアウトの初期ブロック幅と、モザイクインの終了条件となる最終ブロック幅は、
ここでは20ピクセルとします。
つまり、最小のブロックは20×15ピクセル、その時のブロック数は、縦横共に32個ずつとなります。

それではコードを書いていきましょう。
前回BlackOutの手続きを書いた、effectユニットのTEffectクラスのメンバ関数として
MosaicOutとMosaicInを追加します。

            
type
   TEffect = class
   public
      procedure BlackOut(img : TImage);
      procedure BlackIn(img : TImage; backbmp : TBitmap);
            
            
      procedure MosaicOut(img : TImage);
      procedure MosaicIn(img : TImage; backbmp : TBitmap);
            
            
   end;
            

それぞれの処理内容を書きます(リスト25)

(リスト25)
            
//背景のモザイクアウト
procedure TEffect.MosaicOut(img : TImage);
var
   //引数imgの幅
   img_w : Integer;
   //塗りつぶすブロックの幅および高さ
   rect_w,rect_h : Integer;
   //塗りつぶすブロックの縦または横方向の数
   rect_num : Integer;
   //ループカウンタ
   x,y : Integer;
begin
   //引数imgの幅を取得
   img_w := img.Picture.Bitmap.Width;

   //ブロックの幅の初期値を設定
   rect_w := 20;

   //ブロックの幅がimgの幅以下である限り繰り返す
   while rect_w <= img_w do
   begin

      //ブロックの高さを算出
      rect_h := rect_w * 3 div 4;

      //塗りつぶすブロックの数を取得
      //縦、横方向で同じ数となる
      rect_num := img_w div rect_w;

      //横方向のブロックについて繰り返す
      for y := 0 to rect_num - 1 do
         //縦方向のブロックについて繰り返す
         for x := 0 to rect_num - 1 do
         begin
            //ブロックの一番左上のピクセルの色を得て、
            //imgの塗りつぶし色に設定する
            img.Picture.Bitmap.Canvas.Brush.Color :=
               GetPixel(img.Picture.Bitmap.Canvas.Handle,
                        rect_w*x,rect_h*y);

            //ブロックを塗りつぶす
            img.Picture.Bitmap.Canvas.FillRect(
               Rect(rect_w*x,rect_h*y,
                    rect_w*(x+1),rect_h*(y+1)));
         end;

      //画面全体の処理が終わったら、再描画する
      img.Repaint;

      //処理が速すぎる為、ウェイトを置く
      Sleep(50);

      //ブロックの幅を2倍にする
      rect_w := rect_w * 2;
   end;
end;

//背景のモザイクイン
//裏画面(backbmp)から背景を転送した後モザイクをかける
procedure TEffect.MosaicIn(img : TImage; backbmp : TBitmap);
var
   //引数imgの幅
   img_w : Integer;
   //塗りつぶすブロックの幅および高さ
   rect_w,rect_h : Integer;
   //塗りつぶすブロックの縦または横方向の数
   rect_num : Integer;
   //ループカウンタ
   x,y : Integer;
begin
   //引数imgの幅を取得
   img_w := img.Picture.Bitmap.Width;

   //ブロックの幅の初期値を設定
   rect_w := img_w;

   //ブロックの幅が20ピクセル以上である限り繰り返す
   while rect_w >= 20 do
   begin
      //裏画面から表画面に背景を転送
      img.Picture.Assign(backbmp);

      //ブロックの高さを算出
      rect_h := rect_w * 3 div 4;

      //塗りつぶすブロックの数を取得
      //縦、横方向で同じ数となる
      rect_num := img_w div rect_w;

      //横方向のブロックについて繰り返す
      for y:=0 to rect_num-1 do

         //縦方向のブロックについて繰り返す
         for x:=0 to rect_num-1 do
         begin
            //ブロックの一番左上のピクセルの色を得て、
            //imgの塗りつぶし色に設定する
            img.Picture.Bitmap.Canvas.Brush.Color :=
               GetPixel(img.Picture.Bitmap.Canvas.Handle,
                        rect_w*x,rect_h*y);

            //ブロックを塗りつぶす
            img.Picture.Bitmap.Canvas.FillRect(
               Rect(rect_w*x,rect_h*y,
                    rect_w*(x+1),rect_h*(y+1)));
         end;

      //画面全体の処理が終わったら、再描画する
      img.Repaint;

      //処理が速すぎる為、ウェイトを置く
      Sleep(50);

      //ブロックの幅を半分にする
      rect_w := rect_w div 2;
   end;

   //最後に裏画面から表画面に背景を転送
   img.Picture.Assign(backbmp);
   //再描画する
   img.Repaint;
end;
            

処理の流れは先程解説した通りです。
ブロックの塗りつぶし色は、Win32APIのGetPixel関数を使用して
ブロック左上のRGB値を取得し、設定しています。
ブロックの塗りつぶし処理はTCanvasクラスの手続きFillRectで行います。
また、モザイクインのループは、ブロックの幅が20ピクセル未満になったら終了するため、
モザイクの最小ブロックが残ったままとなってしまうので、
これを回避するために、ループを抜けた後に一度裏画面を表画面に転送する処理を記述しています。

ブラックアウト→ブラックインと同様に、モザイクアウト→モザイクインも
スクリプトで指定された時に呼び出せるようにしましょう。
スクリプトの解析処理は既に実装しているので、解析結果として呼び出される手続き
ChangeBackGroundの一部を以下のように書き換えます。


            
//背景CGを変更する手続き
//BGNumber : 背景CGの番号
//EffectType : 変更エフェクト
procedure TMainForm.ChangeBackGround(EffectType : string; BGNumber : Integer);
var
   //TEffectクラスのインスタンス変数
   ef : TEffect;
begin
   //背景CGの番号を設定する
   CurBack := BGNumber;
   //指定された番号の背景CGをロードする
   BackBmp.LoadFromFile(Format('..\\..\\data\\bg%.2d.bmp',[CurBack]));

   //TEffectクラスの実体を生成する
   ef := TEffect.Create;
   try
      //EffectTypeによる分岐
      //'black'の場合、ブラックアウト→ブラックイン
      if EffectType = 'black' then
      begin
         ef.BlackOut(PrimImage);
         ef.BlackIn(PrimImage,BackBmp);
            
            
      end
      //'mosaic'の場合、モザイクアウト→モザイクイン
      else if EffectType = 'mosaic' then
      begin
         ef.MosaicOut(PrimImage);
         ef.MosaicIn(PrimImage,BackBmp);
            
            
      end;
   finally
      //オブジェクトを解放する
      ef.Free;
   end;

   //立ち絵は消去されているためフラグを降ろす
   SetChara := False
end;
            

これでシナリオファイルのスクリプト部分に"changeBG(mosaic,n)"と記述されていれば、
そのページでモザイクアウト→モザイクインのエフェクトが発生するようになります。
そのようにして実験しても良いのですが、ここでは他の箇所で試してみましょう。

実は、セーブデータをロードしてセーブした時の画面に戻る際に、
まだ何もエフェクトを付けていません。
このときにモザイクアウト→モザイクインのエフェクトを掛けてみることにします。

ポップアップメニューの「ロード」をクリックした時に発生するイベント
MnuLoadClickの一部を書き換えます。

            
//「ロード」メニューが押された時のイベント
procedure TMainForm.MnuLoadClick(Sender: TObject);
(中略)
   ChoiceNum := loaddata.ChoiceNum;

   //セーブした状態を復元する
            
            
   //背景CGの変更
   ChangeBackGround('mosaic',CurBack);
            
            
   SetChara := loaddata.SetChara;
   if SetChara then DrawChara;
   DrawMessageWindow;
   PutText(TextPoint);
   CDPlay(CurTrack);
   if phase = choice then ChoiceInit;

end;
            

変数CurBackにはセーブデータに入っていた「セーブした時の背景CGの番号」が
入っているため、その番号の背景CGを読み込んで
モザイクアウト→モザイクイン処理を行います。

では実行してみましょう。
適当な箇所でマウスを右クリックして現れるポップアップメニューから「セーブ」を選択し、
現在の状況をセーブします。
その後同じくポップアップメニューから「ロード」を選択すると、
画面にモザイクアウトのエフェクトが掛かり、セーブ時の画面が
モザイクの状態から表示されます。


第11回へ
アドベンチャーゲーム設計論トップへ戻る
開発分室へ戻る
トップページへ戻る