Mapchip の境界に沿って動かす

Mapchip の境界に沿ってキャラクタを動かします。
チラツキを抑えるために DoubleBuffer を使います。

下のリンクをクリックすると、このページで作成したアプレットが実行されます。
Mapchip の境界に沿って動かす

前田稔の超初心者のプログラム入門

プログラムの作成

  1. メモ帳などでタイプして rpgdouble.java の名前で保存して下さい。
    Applet はサーバーにアップロードすることが前提なので、ファイル名やクラス名を小文字で統一しています。
    //★ DoubleBuffer で境界に沿って動かす    前田 稔
    //   appletviewer rpgdouble.htm
    import java.applet.*;
    import java.awt.*;
    import java.io.*;
    import javax.imageio.ImageIO;
    import java.net.URL;
    import java.awt.event.*;
    import java.util.*;
    
    public class rpgdouble extends Applet implements Runnable, KeyListener
    {   card        obj;
        card        cat;
        int         key_t[] = { 0,0,0,0 };      // UP, RIGHT, DOWN, LEFT
        int         x= 64, y= 64, dir= 0, num= 0;
        long        waitTime;
        // Double Buffer
        Dimension   size;
        Image       back= null;
        Graphics    buffer;
    
        // 20, 24,  16, 16,  Mapの幅と高さ, Mapchipの幅と高さ
        int     TT[][] =
    { { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 40, 8, 9, 8, 9, 41, 2, 2, 2, 2, 52, 53, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 12, 6, 4, 4, 4, 43, 2, 2, 2, 2, 72, 73, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 32, 26, 56, 57, 4, 64, 65, 66, 41, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 12, 28, 76, 77, 4, 4, 4, 4, 15, 2, 2, 2, 52, 53, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 62, 30, 30, 50, 51, 4, 4, 4, 35, 2, 2, 2, 72, 73, 2, 2, 2, 52, 53, 2, 2, 2 }, 
      { 2, 2, 2, 2, 2, 2, 71, 4, 4, 4, 15, 2, 2, 2, 2, 2, 2, 2, 2, 72, 73, 2, 2, 2 }, 
      { 2, 2, 2, 2, 2, 2, 12, 4, 58, 59, 35, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 2, 40, 66, 66, 67, 4, 78, 79, 64, 65, 8, 9, 8, 9, 9, 41, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 2, 12, 25, 6, 7, 4, 4, 5, 24, 25, 4, 4, 4, 4, 4, 43, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 2, 12, 28, 26, 27, 4, 58, 59, 5, 6, 7, 58, 59, 4, 4, 43, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 2, 62, 30, 50, 51, 24, 78, 79, 25, 26, 27, 78, 79, 4, 4, 64, 9, 9, 41, 2, 2, 2 }, 
      { 2, 2, 2, 2, 2, 2, 71, 4, 4, 4, 4, 4, 48, 49, 30, 50, 51, 56, 57, 4, 35, 2, 2, 2 }, 
      { 2, 54, 55, 2, 2, 2, 42, 4, 4, 56, 57, 4, 68, 69, 2, 2, 71, 76, 77, 4, 43, 2, 2, 2 }, 
      { 2, 74, 75, 2, 2, 2, 62, 50, 51, 76, 77, 4, 43, 2, 2, 2, 62, 30, 30, 31, 63, 2, 2, 2 }, 
      { 2, 2, 2, 2, 2, 2, 2, 2, 71, 4, 4, 4, 43, 2, 54, 55, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 2, 2, 54, 55, 2, 2, 62, 30, 30, 31, 63, 2, 74, 75, 2, 2, 2, 2, 2, 2, 2, 2 }, 
      { 2, 2, 2, 2, 74, 75, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 54, 55, 2, 2 }, 
      { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 74, 75, 2, 2 }, 
      { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 } };
    
        // Initialize
        public void init()
        {   obj = new card(getCodeBase().toString() + "chip.gif",16,16);
            cat = new card(getCodeBase().toString() + "chr47.gif",32,32);
            setBackground(Color.gray);
            size = getSize();
            back= createImage(size.width, size.height);
            if (back==null) System.out.print("createImage Error");
            waitTime= System.currentTimeMillis()+100;
            addKeyListener(this);
            Thread t = new Thread(this);
            t.start();
        }
    
        // Message Loop
        public void run()
        {   {   while(true)
                {   if (System.currentTimeMillis()>waitTime)
                    {   waitTime= System.currentTimeMillis()+50;
                        if (action())   repaint();
                    }
                }
            }
        }
    
        // キャラクタの移動
        public boolean action()
        {   int i;
            // MAP の境界を調べる
            i= 4;
            if ((x%16!=0) && (dir==1 || dir==3))    i= dir;
            if ((y%16!=0) && (dir==2 || dir==0))    i= dir;
            // キーが押されているか
            if (i>3)
            {   for(i=0; i<4; i++)
                {   if (key_t[i]==1)    break;  }
            }
            if (i>3)    return false;
            dir= i;
            num^= 1;    // 二枚の画像を切り替える
            switch(dir)
            {   case 0: y-= 4;  break;  // UP
                case 1: x+= 4;  break;  // RIGHT
                case 2: y+= 4;  break;  // DOWN
                case 3: x-= 4;  break;  // LEFT
            }
            return true;
        }
    
        // Paint Method
        public void paint(Graphics g)
        {   if (back==null)     return;
            buffer= back.getGraphics();
            if (buffer==null)   return;
            size = getSize();
            buffer.setColor(getBackground());
            buffer.fillRect(0, 0, size.width, size.height);
            for(int i=0; i<20; i++)
                for(int j=0; j<24; j++)
                    obj.View(buffer,TT[i][j], j*16+20,i*16+20);
            cat.View(buffer,dir*2+num,x,y);
            g.drawImage(back,0,0,this);
        }
    
        // KeyEvent Listener
        public void keyPressed(KeyEvent e)
        {   switch(e.getKeyCode( ))
            {   case KeyEvent.VK_UP :   key_t[0]= 1;  break;
                case KeyEvent.VK_RIGHT: key_t[1]= 1;  break;
                case KeyEvent.VK_DOWN : key_t[2]= 1;  break;
                case KeyEvent.VK_LEFT : key_t[3]= 1;  break;
            }
        }
        public void keyReleased(KeyEvent e)
        {   switch(e.getKeyCode( ))
            {   case KeyEvent.VK_UP :   key_t[0]= 0;  break;
                case KeyEvent.VK_RIGHT: key_t[1]= 0;  break;
                case KeyEvent.VK_DOWN : key_t[2]= 0;  break;
                case KeyEvent.VK_LEFT : key_t[3]= 0;  break;
            }
        }
        public void keyTyped(KeyEvent e) { }
    }
    
    //★ 画像を切り分ける class card は Java Applet カードゲームと同じです。
    class card extends JApplet
    {   private Image   Img;            // CARD Image
          ・・・
    }
    
  2. Java Applet を起動する HTML ファイルです。
    <html>
      <body>
        <h3>DoubleBuffer で境界に沿って動かす</h3>
        <applet code="rpgdouble.class" width="440" height="400">
        </applet>
      </body>
    </html>
    
  3. プロジェクトのフォルダーに MapChip の画像ファイル(chip.gif)とネコの画像(chr47.gif)を格納して下さい。
    chip.gif は 32*32 の Sprite が2段10列に並んだ画像ですが、今回は 16*16 のサイズに設定して4段20列で処理します。
    インターネットブラウザを起動して HTML ファイルから実行して下さい。
    Applet のウインドウをクリックして、上下左右の矢印キーを押すとキャラクタの画像がアニメーションしながら動きます。
    キーを離すと背景画像の32(16)ドット境界の位置まで移動して止まります。

プログラムの説明

  1. このプログラムは Mapchip を並べて背景画像を表示 の続きです。
    KeyListener で直接キーを検出したのでは、タイムラグが生じます。
    ゲームでは FPS(frames per second) を一定にして描画します。
    普通に描画したのでは画面の「チラツキ」が大きくてゲームになりません。
    これらの説明は RPGゲームガイド を参照して下さい。
  2. RPG(Role Playing Game)の背景画像は、マップチップと呼ばれるタイル状の画像を組み合わせて描画する方法が良く使われます。
    マップチップは32(16)ドットで構成されるので、キャラクタはこの境界に沿って移動するのが好都合です。
    矢印キーでキャラクタを16ドットの境界に沿って動かしてみましょう。
    card obj; は BG を描画する card object class の領域です。
    card cat; はキャラクタを描画する card object class の領域です。
    key_t[] はキャラクタを矢印キーで操作するための領域です。
    x, y はキャラクタの座標です。
    dir は進む方向(後,右,前,左)で、num は二枚の画像を切り替えながらキャラクタをアニメーションする領域です。
    size, back, buffer は描画の「チラツキ」を抑えるための Back Buffer です。
    TT[][] は Mapchip の並び情報(Index 番号)を配列で定義しています。
        public class rpgdouble extends Applet implements Runnable, KeyListener
        {   card        obj;
            card        cat;
            int         key_t[] = { 0,0,0,0 };      // UP, RIGHT, DOWN, LEFT
            int         x= 64, y= 64, dir= 0, num= 0;
            long        waitTime;
            // Double Buffer
            Dimension   size;
            Image       back= null;
            Graphics    buffer;
            int     TT[][] =
        { { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 
              ・・・
          { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 } };
        
  3. init() はアプレットの初期化を行うメソッドです。
    obj = new card() で BG の card class をインスタンス化します。
    16, 16 はマップチップ一枚分の幅と高さです。
    cat = new card() でキャラクタの card class をインスタンス化します。
    32, 32 は Sprite(ネコ)一枚分のサイズです。
    Java Applet の動作環境では、ローカルフォルダーはセキュリティで保護されています。
    一般的に画像は、アプレットのディレクトリ(CodeBase または DocumentBase)から入力します。
    getCodeBase() で URL を取得して、画像ファイルの名前と結合してパラメータで渡します。
    背景色を gray に設定しています。
    「チラツキ」を抑えるために Back Buffer を作成します。
    矢印キーを検出するために KeyListener を設定します。
    スレッドを設定して t.start() で起動します。
        // Initialize
        public void init()
        {   obj = new card(getCodeBase().toString() + "chip.gif",16,16);
            cat = new card(getCodeBase().toString() + "chr47.gif",32,32);
            setBackground(Color.gray);
            size = getSize();
            back= createImage(size.width, size.height);
            if (back==null) System.out.print("createImage Error");
            waitTime= System.currentTimeMillis()+100;
            addKeyListener(this);
            Thread t = new Thread(this);
            t.start();
        }
        
  4. Runnable から action() メソッドを呼び出して、座標が更新されていると repaint() で描画します。
        // Message Loop
        public void run()
        {   {   while(true)
                {   if (System.currentTimeMillis()>waitTime)
                    {   waitTime= System.currentTimeMillis()+50;
                        if (action())   repaint();
                    }
                }
            }
        }
        
  5. キーを検出してキャラクタを操作する action() メソッドです。
    キーを離したあとも x%16!=0 または y%16!=0 でマップの境界になるまでキャラクタを進めます。
        // キャラクタの移動
        public boolean action()
        {   int i;
            // MAP の境界を調べる
            i= 4;
            if ((x%16!=0) && (dir==1 || dir==3))    i= dir;
            if ((y%16!=0) && (dir==2 || dir==0))    i= dir;
            // キーが押されているか
            if (i>3)
            {   for(i=0; i<4; i++)
                {   if (key_t[i]==1)    break;  }
            }
            if (i>3)    return false;
            dir= i;
            num^= 1;    // 二枚の画像を切り替える
            switch(dir)
            {   case 0: y-= 4;  break;  // UP
                case 1: x+= 4;  break;  // RIGHT
                case 2: y+= 4;  break;  // DOWN
                case 3: x-= 4;  break;  // LEFT
            }
            return true;
        }
        
  6. paint() メソッドでは、BackBuffer の準備が整っていることを確認して描画します。
    BackBuffer にイメージを作成してから一挙に Front Buffer に転送します。
        // Paint Method
        public void paint(Graphics g)
        {   if (back==null)     return;
            buffer= back.getGraphics();
            if (buffer==null)   return;
            size = getSize();
            buffer.setColor(getBackground());
            buffer.fillRect(0, 0, size.width, size.height);
            for(int i=0; i<20; i++)
                for(int j=0; j<24; j++)
                    obj.View(buffer,TT[i][j], j*16+20,i*16+20);
            cat.View(buffer,dir*2+num,x,y);
            g.drawImage(back,0,0,this);
        }
        

Java Game Program