BG のスクロール



BG(Back Ground)Class を作成して、矢印キーでドット単位にスクロールします。

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

プログラムの作成

  1. Rpg_04.java に組み込まれていた MAP Class をレベルアップして、ファイルを独立した BG Object Class です。
    BG.java の名前で保存して BG.class を作成して下さい。
    //★ BG Object Class    前田 稔
    import java.awt.*;
    import javax.swing.*;
    import java.io.*;
    import javax.imageio.ImageIO;
    import java.util.StringTokenizer;
    
    class BG extends JFrame
    {   String  Imgfile;        // image file
        Image   Img;            // Image Object
        Dimension   size= new Dimension(32,32); // Sprite Size
        Dimension   num= new Dimension(1,1);    // Sprite 並び数
        int     TT[][];         // MAP Table
        int     AT[];           // Chip 属性
        Dimension   Tn= new Dimension(1,1);     // TT[H][W] Size
        int         Tnum;       // H*W
        Rectangle   win= new Rectangle(0,0,800,600);
        Point   center= new Point(128,128);
        Point   scrollMax= new Point();
    
        //※ Work Area
        BufferedReader  br;     // Reader
        String          str;    // Input Buffer
        StringTokenizer token;  // トークン Object
    
        // Constructor
        BG(String fname)
        {   int     i,wk;
            File    file = new File(fname);
            if (checkBeforeReadfile(file)==false)
            {   System.out.println("Input File Open Error");
                return;
            }
            try
            {   br = new BufferedReader(new FileReader(file));
                // Check File ID
                LineRead();
                if ("//Map".equals(str.substring(0,5))==false)
                {   System.out.println("Map File Error");
                    return;
                }
                // Set Image file
                LineRead();
                Imgfile= (str.substring(2,str.length()));
                // Set Xn,Yn,Width,Height
                LineRead();
                token = new StringTokenizer(str, " ,;\t", false);
                token.nextToken();
                Tn.width= Val();
                Tn.height= Val();
                size.width= Val();
                size.height= Val();
                //画像ロード
                File infile = new File(Imgfile);
                Img = loadImage(infile);
                num.width= Img.getWidth(null)/size.width;
                num.height= Img.getHeight(null)/size.height;
                if (num.width<1 || num.height<1)
                {   System.out.println("Image File Error" + Imgfile);
                    return;
                }
                //TT[H][W] に MapData を格納
                TT= new int[Tn.height][Tn.width];
                Tnum= Tn.height*Tn.width;
                NextRead();
                for(i=0; i<Tnum; i++)
                {   wk= Val();
                    TT[i/Tn.width][i%Tn.width]= wk;
                    if (wk==-1) break;
                }
                br.close();
            }
            catch(FileNotFoundException e)
            {   System.out.println(e);  }
            catch(IOException e)
            {   System.out.println(e);  }
        }
    
        // 一次元配列 AT[] にデータをロード
        public void LoadAT(String fname)
        {   int ATnum,i,wk;
            File    file = new File(fname);
            if (checkBeforeReadfile(file)==false)
            {   System.out.println("Input File Open Error");
                return;
            }
            try
            {   br = new BufferedReader(new FileReader(file));
                //AT[] に 属性 Data を格納
                ATnum= num.height*num.width;
                AT= new int[ATnum];
                NextRead();
                for(i=0; i<ATnum; i++)
                {   wk= Val();
                    AT[i]= wk;
                    if (wk==-1) break;
                }
                br.close();
            }
            catch(FileNotFoundException e)
            {   System.out.println(e);  }
            catch(IOException e)
            {   System.out.println(e);  }
        }
    
        // n 番目の Mapchip を描画
        private void View(Graphics g, int n, int dx, int dy)
        {   int sx, sy;
            if (n >= Tnum)
            {   System.out.println("Sprite Number Error" + n);
                return;
            }
            sx = (n%num.width) * size.width;
            sy = (n/num.width) * size.height;
            if (Img != null)
            {   g.drawImage(Img,dx,dy,dx+size.width,dy+size.height,
                            sx,sy,sx+size.width,sy+size.height,this);
            }
        }
        // Mapchip のオフセットを指定
        private void View(Graphics g, int n, int dx, int dy, int w, int h, int xoff, int yoff)
        {   int sx, sy;
            if (n >= Tnum)
            {   System.out.println("Sprite Number Error" + n);
                return;
            }
            sx = (n%num.width) * size.width;
            sy = (n/num.width) * size.height;
            if (Img != null)
            {   g.drawImage(Img,dx,dy,dx+w,dy+h,sx+xoff,sy+yoff,sx+xoff+w,sy+yoff+h,this);
            }
        }
    
        // TT[][] の MAP を矩形(win)に描画
        public void MapView(Graphics g)
        {   int x,y,xn,yn;
            if (Img==null)  return;
            xn= (win.width)/size.width;     //幅のループ回数
            yn= (win.height)/size.height;   //高さループ回数
            for(y=0; y<yn; y++)
            {   for(x=0; x<xn; x++)
                {   View(g,TT[y][x],x*size.width+win.x+10,y*size.height+win.y+30);  }
            }
        }
    
        // TT[][] を矩形(win)に描画(DOT Scroll)
        public void MapView(Graphics g, int xoff, int yoff)
        {
            int i,yn,yi,yp,yl;
            if (Img==null)  return;
            yn= (win.height)/size.height;   //高さループ回数
            yi= yoff/size.height;           //TT[Y][] 開始IDX
            yp= win.y+30;                   //Y座標
            yl= size.height-(yoff%size.height);   //開始セルのドット長
            MapLine(g,1,xoff,yi,yp,yl);     //先頭行
            yp+= yl;
            yl= size.height;
            for(i=1; i<yn; i++)
            {   MapLine(g,0,xoff,yi+i,yp,yl);
                yp+= yl;
                yl= size.height;
            }
            yl= yoff%size.height;
            if (yl!=0)  MapLine(g,0,xoff,yi+yn,yp,yl);
        }
        public void MapView(Graphics g, Point off)
        {   MapView(g,off.x, off.y);
        }
    
        // Map の One Line を描画
        // flg:1=First, 0=First以外, xoff:Xoffset, TT[y][], yp:座標, yl:長
        private void MapLine(Graphics g, int flg, int xoff, int y, int yp, int yl)
        {   int i,xn,xi,xp,xl;
            xn= (win.width)/size.width; //幅のループ回数
            xi= xoff/size.width;        //TT[y+yi][x+xi]開始IDX
            xp= win.x+10;               //X座標
            xl= size.width-(xoff%size.width); //開始セルのドット長
            // xl(yl): 左(上)端では セルの offset になる
            if (flg==0)     View(g,TT[y][xi],xp,yp,xl,yl,size.width-xl,0);
            else            View(g,TT[y][xi],xp,yp,xl,yl,size.width-xl,size.height-yl);
            xp+= xl;
            xl= size.width;
            for(i=1; i<xn; i++)
            {   switch(flg)
                {   case 0: //First以外
                        View(g,TT[y][xi+i],xp,yp,xl,yl,0,0);
                        break;
                    case 1: //First Line
                        View(g,TT[y][xi+i],xp,yp,xl,yl,size.width-xl,size.height-yl);
                        break;
                }
                xp+= xl;
                xl= size.width;
            }
            xl= xoff%size.width;
            //右端セルは左端セルの残り分
            if (xl!=0)
            {   if (flg==0) View(g,TT[y][xi+xn],xp,yp,xl,yl,0,0);
                else        View(g,TT[y][xi+xn],xp,yp,xl,yl,0,size.height-yl);
            }
        }
    
        // Window Rect の設定
        public void SetRect(int x, int y, int w, int h)
        {   int wk;
            win.setBounds(x,y,w,h);
            // 中央座標の計算
            center.x= ((w/2)/size.width)*size.width;
            center.y= ((h/2)/size.height)*size.height;
            // Scroll の最大値の計算
            wk= (Tn.width*size.width)-w;
            scrollMax.x= wk;
            wk= (Tn.height*size.height)-h;
            scrollMax.y= wk;
        }
        public void SetRect(Rectangle rect)
        {   SetRect(rect.x, rect.y, rect.width, rect.height);
        }
    
        // TT[Y][X] の X-Index を取得
        public int GetX(int pt, int xoff)
        {   int wk;
            wk= (pt-win.x+xoff)/size.width;
            if (wk<0 || wk>=Tn.width) wk= -1;
            return wk;
        }
    
        // TT[Tn.height][Tn.width] の Y-Index を取得
        public int GetY(int pt, int yoff)
        {   int wk;
            wk= (pt-win.y+yoff)/size.height;
            if (wk<0 || wk>=Tn.height) wk= -1;
            return wk;
        }
    
        // Mapchip の属性を取得
        public int GetAtt(int xp, int yp, int xoff, int yoff)
        {   int wx,wy;
            wx= GetX(xp,xoff);
            wy= GetY(yp,yoff);
            if (wx<0 || wy<0)
            {   System.out.println("GetAtt 座標エラー");
                return -1;
            }
            return AT[TT[wy][wx]];
        }
    
        // 方向 dir の Mapchip の属性を取得
        public int GetAtt(int dir, int xp, int yp, int xoff, int yoff)
        {   int wx,wy;
            wx= GetX(xp,xoff);
            wy= GetY(yp,yoff);
            if (wx<0 || wy<0)   System.out.println("GetAtt 座標エラー");
            switch(dir)
            {   case 0: wy--;  break;
                case 1: wx++;  break;
                case 2: wy++;  break;
                case 3: wx--;  break;
            }
            if (wx<0 || wx>=Tn.width)  return -1;
            if (wy<0 || wy>=Tn.height) return -1;
            return AT[TT[wy][wx]];
        }
        public int GetAtt(Point pos, Point off)
        {   return GetAtt(pos.x, pos.y, off.x, off.y);
        }
        public int GetAtt(int dir, Point pos, Point off)
        {   return GetAtt(dir, pos.x, pos.y, off.x, off.y);
        }
    
        // Read Line(次の行)
        private boolean LineRead()
        {
            try
            {   str = br.readLine();  }
            catch(IOException e)
            {   System.out.println(e);  }
            if (str == null)
            {   System.out.println("End of file");
                return false;
            }
            return true;
        }
    
        // Read Next Line(/をスキップ, Token を設定)
        private boolean NextRead()
        {
            while(LineRead())
            {   if (str.charAt(0)!='/')
                {   token = new StringTokenizer(str, " ,;\t", false);
                    return true;
                }
            }
            return false;
        }
    
        // Next Val(Buf から次の値を取得)
        private int Val()
        {   if (token.hasMoreTokens()==false)
                if (NextRead()==false)  return -1;
            return Integer.parseInt(token.nextToken());
        }
    
        // File Open Check
        private boolean checkBeforeReadfile(File file)
        {   if (file.exists())
            {   if (file.isFile() && file.canRead())
                {   return true;  }
            }
            return false;
        }
    
        // Load Image
        private static Image loadImage(File f)
        {   try
            {   Image img = ImageIO.read(f);
                return img;
            }
            catch (IOException e)
            {   throw new RuntimeException(e);  }
        }
    }
    
  2. 矢印キーで BG をスクロールする Rpg_05.java です。
    BG.class と同じフォルダーに格納して下さい。
    //★ 矢印キーで BG のスクロール    前田 稔
    import java.awt.*;
    import javax.swing.*;
    import java.awt.event.*;
    
    public class Rpg_05 extends JFrame implements KeyListener
    {   BG      bg;
        int     xoff= 0, yoff= 0;
        Dimension   size;
        Image       back;
        Graphics    buffer;
    
        // Main
        public static void main(String args[])
        {   new Rpg_05();
        }
    
        // Constructor
        Rpg_05()
        {   super("BG Scroll");
            bg = new BG("BgMap4.txt");
            bg.SetRect(50,50,600,400);
            addKeyListener(this);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setBackground(Color.gray);
            setSize(800,600);
            setVisible(true);
            size = getSize();
            back= createImage(size.width, size.height);
            if (back==null) System.out.print("createImage Error");
        }
    
        // Paint
        public void paint(Graphics g)
        {   if (back==null)     return;
            buffer= back.getGraphics();
            if (buffer==null)   return;
            bg.MapView(buffer,xoff,yoff);
            g.drawImage(back,0,0,this);
        }
    
        // KeyEvent Listener
        public void keyPressed(KeyEvent e)
        {   switch(e.getKeyCode( ))
            {   case KeyEvent.VK_UP:
                    if (yoff>0)     yoff--;
                    break;
                case KeyEvent.VK_DOWN:
                    if (yoff<bg.scrollMax.y) yoff++;
                    break;
                case KeyEvent.VK_RIGHT:
                    if (xoff<bg.scrollMax.x) xoff++;
                    break;
                case KeyEvent.VK_LEFT:
                    if (xoff>0)      xoff--;
                    break;
            }
            repaint();
        }
        public void keyReleased(KeyEvent e) { }
        public void keyTyped(KeyEvent e) { }
    }
    
  3. プロジェクトのフォルダーに MapEditor で出力した TEXT 形式のファイル(BgMap4.txt)と MapChip の画像ファイル(chip.gif)を格納して下さい。
    "BgMap4.txt" は MapEditor を使ってウインドウの四倍程度の背景画像を作成して下さい。
    MapEditor は「超初心者のプログラム入門/Game Program & 各種 Tool/Map Editor」から提供しています。
    上下左右の矢印キーで背景画像がドット単位にスクロールできたら完成です。

プログラムの説明

  1. 背景画像(BG) をドット単位にスクロールする Rpg_05.java です。
    bg は BG Object Class です。
    xoff, yoff はドット単位のスクロール値です。
        public class Rpg_05 extends JFrame implements KeyListener
        {   BG      bg;
            int     xoff= 0, yoff= 0;
            Dimension   size;
            Image       back;
            Graphics    buffer;
        
  2. Main の Constructor でゲームウインドウ(背景画像を描画する矩形領域)を設定します。
        // Constructor
        Rpg_05()
        {   super("BG Scroll");
            bg = new BG("BgMap4.txt");
            bg.SetRect(50,50,600,400);
            addKeyListener(this);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setBackground(Color.gray);
            setSize(800,600);
            setVisible(true);
            size = getSize();
            back= createImage(size.width, size.height);
            if (back==null) System.out.print("createImage Error");
        }
        
  3. paint() メソッドでは xoff,yoff を指定して描画します。
        // Paint
        public void paint(Graphics g)
        {   if (back==null)     return;
            buffer= back.getGraphics();
            if (buffer==null)   return;
            bg.MapView(buffer,xoff,yoff);
            g.drawImage(back,0,0,this);
        }
        
  4. KeyEvent Listener で矢印キーを検出してスクロール値を更新します。
    ScrollMax.y,ScrollMax.x はスクロールの最大値です。
        // KeyEvent Listener
        public void keyPressed(KeyEvent e)
        {   switch(e.getKeyCode( ))
            {   case KeyEvent.VK_UP:
                    if (yoff>0)     yoff--;
                    break;
                case KeyEvent.VK_DOWN:
                    if (yoff<bg.scrollMax.y) yoff++;
                    break;
                case KeyEvent.VK_RIGHT:
                    if (xoff<bg.scrollMax.x) xoff++;
                    break;
                case KeyEvent.VK_LEFT:
                    if (xoff>0)      xoff--;
                    break;
            }
            repaint();
        }
        

BG Object Class の説明

  1. RPG ゲームで最も難しいのが「ドット単位に BG(背景)をスクロール」する処理です。
    一枚の画像なら簡単なのですが、マップチップを組み合わせた背景画像となると一苦労です。
    一枚画像のスクロールは 背景画像のスクロール を参照して下さい。
    前回までの説明は 出力ファイルを使う を参照して下さい。
  2. BG Object Class は Rpg_04.java に組み込まれていた MAP Class をレベルアップして、ファイルを独立した Class です。
    ちょっとした RPG ゲームにも使えるようにレベルアップした関係で、多少難しくなっています。
    AT[] は MapChip の属性データを入力する配列です。
    win は Game Window の矩形領域です。
    center は Game Window の中央座標ですが、MapChip に合わせて端数を切り捨てるので、やや左上になります。
    scrollMax はスクロールの最大値で、この値を超えると配列の範囲外を参照する危険性があります。
    スクロールの最小値はゼロなので、特に領域は設けていません。
        class BG extends JFrame
        {   String  Imgfile;        // image file
            Image   Img;            // Image Object
            Dimension   size= new Dimension(32,32); // Sprite Size
            Dimension   num= new Dimension(1,1);    // Sprite 並び数
            int     TT[][];         // MAP Table
            int     AT[];           // Chip 属性
            Dimension   Tn= new Dimension(1,1);     // TT[H][W] Size
            int         Tnum;       // H*W
            Rectangle   win= new Rectangle(0,0,800,600);
            Point   center= new Point(128,128);
            Point   scrollMax= new Point();
        
  3. TEXT FILE の入力とトークンを切り出す領域です。
    java.util.StringTokenizer; を import して下さい。
    str は一行分の入力バッファです。
        //※ Work Area
        BufferedReader  br;     // Reader
        String          str;    // Input Buffer
        StringTokenizer token;  // トークン Object
        
  4. AT[] に属性データを入力するメソッドです。
    Text File を配列に格納するプログラムは Text File を二次元配列(int)に格納 を参照して下さい。
    属性データの説明は Mapchip を並べて背景画像を表示 を参照して下さい。
        // 一次元配列 AT[] にデータをロード
        public void LoadAT(String fname)
        {   int ATnum,i,wk;
            File    file = new File(fname);
            if (checkBeforeReadfile(file)==false)
            {   System.out.println("Input File Open Error");
                return;
            }
            try
            {   br = new BufferedReader(new FileReader(file));
                //AT[] に 属性 Data を格納
                ATnum= num.height*num.width;
                AT= new int[ATnum];
                NextRead();
                for(i=0; i<ATnum; i++)
                {   wk= Val();
                    AT[i]= wk;
                    if (wk==-1) break;
                }
                br.close();
            }
            catch(FileNotFoundException e)
            {   System.out.println(e);  }
            catch(IOException e)
            {   System.out.println(e);  }
        }
        
  5. オフセットを指定して、一個のセルを描画するメソッドです。
    dx,dy が座標で、w,h がサイズで、xoff,yoff がセルのオフセットです。
        // Mapchip のオフセットを指定
        private void View(Graphics g, int n, int dx, int dy, int w, int h, int xoff, int yoff)
        {   int sx, sy;
            if (n >= Tnum)
            {   System.out.println("Sprite Number Error" + n);
                return;
            }
            sx = (n%num.width) * size.width;
            sy = (n/num.width) * size.height;
            if (Img != null)
            {   g.drawImage(Img,dx,dy,dx+w,dy+h,sx+xoff,sy+yoff,sx+xoff+w,sy+yoff+h,this);
            }
        }
        
  6. X,Yのドット単位のオフセットを指定して背景を描画するメソッドです。
        // TT[][] を矩形(win)に描画(DOT Scroll)
        public void MapView(Graphics g, int xoff, int yoff)
        {
            int i,yn,yi,yp,yl;
            if (Img==null)  return;
            yn= (win.height)/size.height;   //高さループ回数
            yi= yoff/size.height;           //TT[Y][] 開始IDX
            yp= win.y+30;                   //Y座標
            yl= size.height-(yoff%size.height);   //開始セルのドット長
            MapLine(g,1,xoff,yi,yp,yl);     //先頭行
            yp+= yl;
            yl= size.height;
            for(i=1; i<yn; i++)
            {   MapLine(g,0,xoff,yi+i,yp,yl);
                yp+= yl;
                yl= size.height;
            }
            yl= yoff%size.height;
            if (yl!=0)  MapLine(g,0,xoff,yi+yn,yp,yl);
        }
        
  7. オフセットを指定して、一行分のセルを描画するメソッドです。
    flg==1 のときは先頭行で flg==0 のときは先頭以外の行です。
    先頭行では、Y軸方向のオフセットを指定してセルを描画しなければなりません。
    xoff は行の先頭セルのXオフセットです。
    行の先頭では、X軸方向のオフセットを指定してセルを描画しなければなりません。
    y は TT[y][] のインデックスで、yp はY座標で、yl はセルの高さです。
        // Map の One Line を描画
        // flg:1=First, 0=First以外, xoff:Xoffset, TT[y][], yp:座標, yl:長
        private void MapLine(Graphics g, int flg, int xoff, int y, int yp, int yl)
        {   int i,xn,xi,xp,xl;
            xn= (win.width)/size.width; //幅のループ回数
            xi= xoff/size.width;        //TT[y+yi][x+xi]開始IDX
            xp= win.x+10;               //X座標
            xl= size.width-(xoff%size.width); //開始セルのドット長
            // xl(yl): 左(上)端では セルの offset になる
            if (flg==0)     View(g,TT[y][xi],xp,yp,xl,yl,size.width-xl,0);
            else            View(g,TT[y][xi],xp,yp,xl,yl,size.width-xl,size.height-yl);
            xp+= xl;
            xl= size.width;
            for(i=1; i<xn; i++)
            {   switch(flg)
                {   case 0: //First以外
                        View(g,TT[y][xi+i],xp,yp,xl,yl,0,0);
                        break;
                    case 1: //First Line
                        View(g,TT[y][xi+i],xp,yp,xl,yl,size.width-xl,size.height-yl);
                        break;
                }
                xp+= xl;
                xl= size.width;
            }
            xl= xoff%size.width;
            //右端セルは左端セルの残り分
            if (xl!=0)
            {   if (flg==0) View(g,TT[y][xi+xn],xp,yp,xl,yl,0,0);
                else        View(g,TT[y][xi+xn],xp,yp,xl,yl,0,size.height-yl);
            }
        }
        
  8. ゲームウインドウを設定するメソッドです。
    背景画像を描画する矩形領域を設定することが出来ます。
        // Window Rect の設定
        public void SetRect(int x, int y, int w, int h)
        {   int wk;
            win.setBounds(x,y,w,h);
            // 中央座標の計算
            center.x= ((w/2)/size.width)*size.width;
            center.y= ((h/2)/size.height)*size.height;
            // Scroll の最大値の計算
            wk= (Tn.width*size.width)-w;
            //scrollMax.x= (wk/size.width)*size.width;
            scrollMax.x= wk;
            //scrollMax.x= (wk/size.width)*size.width+size.width-1;
            wk= (Tn.height*size.height)-h;
            //scrollMax.y= (wk/size.height)*size.height;
            scrollMax.y= wk;
            //scrollMax.y= (wk/size.height)*size.height+size.height-1;
        }
        
  9. カーソルの座標から TT[][] のインデックスを取得するメソッドです。
    GetX() で横軸方向のインデックスを取得します。
    GetY() で縦軸方向のインデックスを取得します。
        // TT[Y][X] の X-Index を取得
        public int GetX(int pt, int xoff)
        {   int wk;
            wk= (pt-win.x+xoff)/size.width;
            if (wk<0 || wk>=Tn.width) wk= -1;
            return wk;
        }
        // TT[Tn.height][Tn.width] の Y-Index を取得
        public int GetY(int pt, int yoff)
        {   int wk;
            wk= (pt-win.y+yoff)/size.height;
            if (wk<0 || wk>=Tn.height) wk= -1;
            return wk;
        }
        
  10. カーソルの座標から属性を取得するメソッドです。
        // Mapchip の属性を取得
        public int GetAtt(int xp, int yp, int xoff, int yoff)
        {   int wx,wy;
            wx= GetX(xp,xoff);
            wy= GetY(yp,yoff);
            if (wx<0 || wy<0)
            {   System.out.println("GetAtt 座標エラー");
                return -1;
            }
            return AT[TT[wy][wx]];
        }
        
  11. キャラクタが向いている方向の属性を取得するメソッドです。
        // 方向 dir の Mapchip の属性を取得
        public int GetAtt(int dir, int xp, int yp, int xoff, int yoff)
        {   int wx,wy;
            wx= GetX(xp,xoff);
            wy= GetY(yp,yoff);
            if (wx<0 || wy<0)   System.out.println("GetAtt 座標エラー");
            switch(dir)
            {   case 0: wy--;  break;
                case 1: wx++;  break;
                case 2: wy++;  break;
                case 3: wx--;  break;
            }
            if (wx<0 || wx>=Tn.width)  return -1;
            if (wy<0 || wy>=Tn.height) return -1;
            return AT[TT[wy][wx]];
        }
        

超初心者のプログラム入門(Java2)