自機から連射

Java Applet で Shooting Game(シューティングゲーム)に挑戦します。
Space キーで MyShip から弾丸を連射します。
自機から連射

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

プログラムの作成

  1. 2012/9 Windows Vista 以降のOSで、Java Applet で swing を使うと何故かキーの取得が出来ません。 (^_^;)
    ホームページに掲載しているキーが効かない Applet を WindowsXP で実行できることを確認しました。
    そんな訳でこのページも swing の使用を止めて AWT で作成したプログラムに組み替えます。
    漢字変換を OFF にして、ウインドウ上でクリックしてから操作して下さい。
  2. メモ帳などでタイプして appletshot1.java の名前で保存して下さい。
    //★ 自機から弾丸を連射    前田 稔
    //   appletviewer appletshot1.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 appletshot1 extends Applet implements Runnable, KeyListener
    {   // MyShip の領域
        int         key_t[] = { 0,0,0,0,0 };    //UP, RIGHT, DOWN, LEFT, SPACE
        Image       ship= null;         //Ship の画像
        int         x= 300, y= 380;     //Ship の座標
        // shot 弾丸の領域
        sprite      dangan= null;       //弾丸の切り分け
        ArrayList<shot>   list;         //弾丸の登録
        shot        shotw= new shot();  //shot のワーク
        long        nowTime,drawTime,shotTime;
        // Double Buffer
        Dimension   size;
        Image       back= null;
        Graphics    buffer;
    
        // Initialize
        public void init()
        {   //画像ロード
            ship = getImage(getDocumentBase(),"ship.png");
            dangan = new sprite(getCodeBase().toString() + "tama4.png",32,32);
            setBackground(Color.gray);
            list = new ArrayList<shot>();
            shotTime= 0;
            size = getSize();
            back= createImage(size.width, size.height);
            if (back==null) System.out.print("createImage Error");
            drawTime= System.currentTimeMillis()+500;
            addKeyListener(this);
            requestFocus();
            Thread t = new Thread(this);
            t.start();
        }
    
        // Message Loop
        public void run()
        {   {   while(true)
                {   nowTime= System.currentTimeMillis();
                    if (drawTime<nowTime)
                    {   drawTime= nowTime+30;
                        action();
                        Update();
                        repaint();
                    }
                }
            }
        }
    
        // Paint
        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);
            if (ship!=null)
            {   buffer.drawImage(ship,x,y,this);  }
            for(int i=0; i<list.size(); i++)    // 弾丸の描画
                dangan.View(buffer,list.get(i).Sp_no,(int)list.get(i).posX,(int)list.get(i).posY);
            g.drawImage(back,0,0,this);
        }
    
        // 座標の更新&弾の削除
        public void Update()
        {   for(int i=0; i<list.size(); i++)
            {   list.get(i).Update();   }
            for(int i=list.size()-1; i>=0; i--)
                if (list.get(i).Sp_no==99)  list.remove(i);
        }
    
        // シップの移動
        public void action()
        {   if (key_t[0]==1)    y=y-4;  // UP
            if (key_t[1]==1)    x=x+4;  // RIGHT
            if (key_t[2]==1)    y=y+4;  // DOWN
            if (key_t[3]==1)    x=x-4;  // LEFT
            if (key_t[4]==1)            // Space で連射
            {   if (shotTime<nowTime)
                {   shotTime= nowTime+200;
                    shotw = new shot();
                    shotw.Set(1,x+20,y,0,-8);
                    list.add(shotw);
                }
            }
        }
    
        // 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;
                case KeyEvent.VK_SPACE: key_t[4]= 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;
                case KeyEvent.VK_SPACE: key_t[4]= 0;  break;
            }
        }
        public void keyTyped(KeyEvent e) { }
    }
    
    //★ 画像を切り分けるクラス
    class sprite extends Applet
    {   private Image   Img;            // Sprite Image
        private int     Width,Height;   // Sprite の幅,高さ
        private int     Wnum,Hnum;      // Sprite 横枚数,縦枚数
        private int     frameNum;       // Wnum*Hnum
        int             Sp_no;          // Sprite の番号
        float           posX, posY;     // Sprite の座標
        float           vectX, vectY;   // Sprite の移動量
        long            chgTime;        // Sprite を切り替えた時刻
        int             Left,Top;       // Window 左,上
        int             Right,Bottom;   // Window 下,右
    
        // Constructor
        sprite(String path, int ws, int hs)
        {   Width= ws;
            Height= hs;
            Wnum=Hnum= 1;
            Sp_no= 99;                  // flag:OFF
            posX=posY= 0f;              //座標
            vectX=vectY= 0f;            //移動量
            chgTime= 0;                 //Sprite の切替
            Left=Top= 0;                //仮の枠(0,0,800,600)
            Right= 800;
            Bottom= 600;
            Img= null;
            try
            {   URL u = new URL(path);
                Img = ImageIO.read(u);
            }
            catch (IOException e)
            {   System.out.println("Image File Load Error");
                return;
            }
            Wnum= Img.getWidth(null)/Width;
            Hnum= Img.getHeight(null)/Height;
            frameNum = Wnum*Hnum;
            if (Wnum<1 || Hnum<1)   System.out.println("Image File Error");
        }
    
        // Sprite View
        public void View(Graphics g, int n, int dx, int dy)
        {   int sx, sy;
            if (n >= frameNum)  return;
            sx = (n % Wnum) * Width;
            sy = (n / Wnum) * Height;
            if (Img != null)
            {   g.drawImage(Img,dx,dy,dx+Width,dy+Height,sx,sy,sx+Width,sy+Height,this);  }
        }
        public void View(Graphics g)
        {   View(g, Sp_no, (int)posX, (int)posY);
        }
    
        // Sprite 番号と座標の設定
        public void Set(int num,float xp,float yp,float xv,float yv)
        {   Sp_no= num;
            posX= xp;
            posY= yp;
            vectX= xv;
            vectY= yv;
        }
    
        // 次の画像に切り替える
        public void Next(long Tim)
        {   if (Sp_no < frameNum)
            {   long  nowTime = System.currentTimeMillis();
                if (chgTime+Tim < nowTime)
                {   chgTime= nowTime;
                    Sp_no++;
                }
            }
            else Sp_no = 99;
        }
        public void Loop(long Tim)
        {   long  nowTime = System.currentTimeMillis();
            if (Sp_no < frameNum && chgTime+Tim < nowTime)
            {   chgTime= nowTime;
                Sp_no = (Sp_no + 1) % frameNum;
            }
        }
    
        // Window Rect の設定
        // Update(), Bound() を使うときは設定する
        public void SetRect(int lt, int tp, int rt, int bm)
        {   Left= lt;
            Top= tp;
            Right= rt;
            Bottom= bm;
        }
    
        // Update method
        // 枠とvectを設定すること
        public void Update()
        {   if (Sp_no==99)  return;
            posX+= vectX;
            posY+= vectY;
            if (posX>Right || posX<Left || posY>Bottom || posY<Top) Sp_no= 99;
        }
    
        // Bound method
        // 枠とvectを設定すること
        public void Bound()
        {   if (Sp_no==99)  return;
            posX+= vectX;
            posY+= vectY;
            if (posX>Right || posX<Left)  vectX= 0-vectX;
            if (posY>Bottom || posY<Top)  vectY= 0-vectY;
        }
    
        //当たり判定
        public boolean Hit(float targetx, float targety, float len)
        {   float   dist,wx,wy;
            if (Sp_no==99)  return false;
            wx = posX - targetx;
            wy = posY - targety;
            dist = (float)Math.sqrt((double)(wx * wx + wy * wy));
            if (dist<len)   return true;
            return false;
        }
    }
    
    //★ 弾丸の座標を管理するクラス
    class shot
    {   float   posX, posY;     //弾の座標
        float   vectX, vectY;   //弾の移動
        int     Sp_no;          //Sprite の番号
    
        // Constructor
        public shot()
        {   posX=posY= 0;
            vectX=vectY= 0;
            Sp_no= 99;          //flag:OFF
        }
    
        // Set method
        public void Set(int num,float xp,float yp,float xv,float yv)
        {   Sp_no= num;
            posX= xp;
            posY= yp;
            vectX= xv;
            vectY= yv;
        }
    
        // Update method
        public void Update()
        {   if (Sp_no==99)  return;
            posX+= vectX;
            posY+= vectY;
            if (posX>800 || posX<0 || posY>600 || posY<0)   Sp_no= 99;
        }
    
        // 円状に弾の移動を設定
        public void Rot(float rt, float len)
        {   vectX = (float)(Math.sin(rt / 180 * 3.14)) * len;
            vectY = (float)(Math.cos(rt / 180 * 3.14)) * len;
        }
    }
    
  3. Java Applet を起動する HTML ファイルです。
    <html>
    <body>
    <h3>弾丸を連射</h3>
      Applet のウインドウをクリックして Space キーを押すと弾丸を連射します。<br>
      <applet code=appletshot1.class width=800 height=600>
      </applet>
    </body>
    </html>
    
  4. インターネットブラウザを起動して HTML ファイルを表示して下さい。
    Applet のウインドウをクリックして Space キーを押すと MyShip から弾丸を連射します。

プログラムの説明

  1. key_t[] はシップの操作と弾丸を連射するためのキーの配列です。
    sprite は画像を切り分ける Class です。弾丸の画像は色違いの弾丸が横に4枚並んだものを使用します。
    list は弾丸を登録する ArrayList です。
    Applet で shot を登録するときは shotw= new shot(); で事前に shot Class をインスタンス化して下さい。
    shot は弾丸の座標を管理する shot Object Class です。
    back, buffer は画面の「ちらつき」を抑えるためのダブルバッファです。
        public class appletshot1 extends Applet implements Runnable, KeyListener
        {   // MyShip の領域
            int         key_t[] = { 0,0,0,0,0 };    //UP, RIGHT, DOWN, LEFT, SPACE
            Image       ship= null;         //Ship の画像
            int         x= 300, y= 380;     //Ship の座標
            // shot 弾丸の領域
            sprite      dangan= null;       //弾丸の切り分け
            ArrayList<shot>   list;         //弾丸の登録
            shot        shotw= new shot();  //shot のワーク
            long        nowTime,drawTime,shotTime;
            // Double Buffer
            Dimension   size;
            Image       back= null;
            Graphics    buffer;
        
  2. init() メソッドで初期化を行います。
    getImage() で "ship.png" を入力します。
    "tama4.png" は色違いの弾丸が横に4枚並んだ画像です。
    4枚の画像を切り分けるために sprite class を使います。
    getCodeBase() で URL を取得して、画像ファイル名(tama4.png) と結合してパラメータで渡します。
    32, 32 は Sprite(弾丸)一枚分のサイズです。
        // Initialize
        public void init()
        {   //画像ロード
            ship = getImage(getDocumentBase(),"ship.png");
            dangan = new sprite(getCodeBase().toString() + "tama4.png",32,32);
            setBackground(Color.gray);
            list = new ArrayList<shot>();
            shotTime= 0;
            size = getSize();
            back= createImage(size.width, size.height);
            if (back==null) System.out.print("createImage Error");
            drawTime= System.currentTimeMillis()+500;
            addKeyListener(this);
            requestFocus();
            Thread t = new Thread(this);
            t.start();
        }
        
  3. run() から FPS を30ミリ秒に設定して action(), Update() を呼び出し、repaint() で描画します。
        // Message Loop
        public void run()
        {   {   while(true)
                {   nowTime= System.currentTimeMillis();
                    if (drawTime<nowTime)
                    {   drawTime= nowTime+30;
                        action();
                        Update();
                        repaint();
                    }
                }
            }
        }
        
  4. paint() メソッドで背景色でクリアして、シップと弾丸を描画します。
        // Paint
        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);
            if (ship!=null)
            {   buffer.drawImage(ship,x,y,this);  }
            for(int i=0; i<list.size(); i++)    // 弾丸の描画
                dangan.View(buffer,list.get(i).Sp_no,(int)list.get(i).posX,(int)list.get(i).posY);
            g.drawImage(back,0,0,this);
        }
        
  5. 弾丸の座標の更新とウインドウの外に出た弾丸の削除です。
        // 座標の更新&弾の削除
        public void Update()
        {   for(int i=0; i<list.size(); i++)
            {   list.get(i).Update();   }
            for(int i=list.size()-1; i>=0; i--)
                if (list.get(i).Sp_no==99)  list.remove(i);
        }
        
  6. シップの移動と弾丸の連射です。
        // シップの移動
        public void action()
        {   if (key_t[0]==1)    y=y-4;  // UP
            if (key_t[1]==1)    x=x+4;  // RIGHT
            if (key_t[2]==1)    y=y+4;  // DOWN
            if (key_t[3]==1)    x=x-4;  // LEFT
            if (key_t[4]==1)            // Space で連射
            {   if (shotTime<nowTime)
                {   shotTime= nowTime+200;
                    shotw = new shot();
                    shotw.Set(1,x+20,y,0,-8);
                    list.add(shotw);
                }
            }
        }
        
  7. KeyEvent Listener でキー操作を検出して key_t[] を設定します。
        // 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;
                case KeyEvent.VK_SPACE: key_t[4]= 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;
                case KeyEvent.VK_SPACE: key_t[4]= 0;  break;
            }
        }
        public void keyTyped(KeyEvent e) { }
        
  8. 画像を切り分ける sprite Object Class です。
    Width, Height は Sprite(カード一枚分)の幅と高さです。
    Wnum, Hnum は切り分ける Sprite(カード)の横と縦の枚数です。
    frameNum は切り分ける Sprite(カード)の総枚数です。
    画像描画の詳細は Image(画像)の描画 を参照して下さい。
        //★ 画像を切り分けるクラス
        class sprite extends JApplet
        {   private Image   Img;            // Sprite Image
            private int     Width,Height;   // Sprite の幅,高さ
            private int     Wnum,Hnum;      // Sprite 横枚数,縦枚数
            private int     frameNum;       // Wnum*Hnum
            int             Sp_no;          // Sprite の番号
            float           posX, posY;     // Sprite の座標
            float           vectX, vectY;   // Sprite の移動量
            long            chgTime;        // Sprite を切り替えた時刻
            int             Left,Top;       // Window 左,上
            int             Right,Bottom;   // Window 下,右
        
  9. 弾丸の座標を管理する shot Object Class です。
    posX, posY は弾丸の座標です。
    vectX, vectY は弾丸の移動量です。
    Sp_no は切り分けた Sprite の既定の番号で、99 のとき「描画しない」になります。
    詳細は「超初心者のプログラム入門(Java2)/Game Program/シューティングゲームに挑戦」を参照して下さい。
        //★ 弾丸の座標を管理するクラス
        class shot
        {   float   posX, posY;     //弾の座標
            float   vectX, vectY;   //弾の移動
            int     Sp_no;          //Sprite の番号
        

Java Game Program