4560


現在JAVAでシューティングゲームを作成しているのですが､ボスを作成し､試しにmainスレッドに表示しようとしたところ､表示されず､黒い画面のままでした｡
どこを改善すれば表示できるようになるのかを教えていただけるとありがたいです｡ よろしくお願いいたします｡  
以下がソースコードです｡ 他にも何かおかしいところがありましたら教えていただけるとありがたいです｡

[code]

    //Boss.java
    package boss;
    
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.util.Random;
    
    import javax.swing.ImageIcon;
    
    public class Boss extends Enemy {
    //ボス画像
    private Image image;
    //このカウンタで動きを制御する｡
    private int moveCount;
    
    //ボスのHP
    private double hp;
    
    //各ショットを入れる配列
    private BossShot[] bs1;
    private BossShot[][] bs2;
    private BossShot[][] bs3;
    private BossShot4[] bs4;
    
    //1度に打てる玉の数
    private static final int NUM_BULLET = 10000;
    
    //弾の各パラメタ
    //弾の角度
    private double shotAngle1;
    private double shotAngle2;
    private double shotAngle4;
    //弾の角度の変化量
    private double shotAngleRate1;
    private double shotAngleRate2;
    private double shotAngleRange4;
    //弾の速度
    private double shotSpeed1;
    private double shotSpeed2;
    private double shotSpeed3;
    private double shotSpeed4;
    //玉の数
    private int countShot23;
    //インターバル
    private int intval23;
    private int intval4;
    //インターバルで使う時間
    private int time;
    //角度と角度の変化量の配列｡これはBossShot3で使用
    private double shotAngleArray[];
    private double shotAngleRateArray[];
    
    //ランダムを生成する
    private Random random;
    
    public Boss(){
        super();
        moveCount=0;
        moveCount=0;
        ImageIcon icon = new ImageIcon("./res/boss.png");
        image = icon.getImage();
    
        this.hp=1000;
        //配列にショットを生成
        shotAngle1=0;
        shotAngleRate1=0.02;
        shotSpeed1=0.005;
        bs1 = new BossShot1[NUM_BULLET];
        for(int i=0; i<NUM_BULLET; i++){
            bs1[i] = new BossShot1(shotAngle1, 0, shotSpeed1, 0);
            shotAngle1+=shotAngleRate1;
        }
        shotAngle2=0;
        shotAngleRate2=0.02;
        shotSpeed2=0.01;
        countShot23=4;
        intval23=5;
        bs2 = new BossShot2[countShot23][NUM_BULLET];
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                bs2[i][j] = new BossShot2(shotAngle2+(double)j/countShot23, 0, shotSpeed2, 0);
            }
            shotAngle2+=shotAngleRate2;
        }
        shotAngleArray = new double[2];
        shotAngleRateArray = new double[2];
        shotAngleRateArray[0]=0.03;
        shotAngleRateArray[1]=-0.02;
        shotSpeed3=0.01;
        countShot23=4;
        intval23=5;
        bs3 = new BossShot3[countShot23][NUM_BULLET];
        for(int i=0; i<2; i++){
            for(int j=0; j<countShot23; j++){
                for(int k=0; k<NUM_BULLET; k++){
                    bs3[j][k] = new BossShot3(shotAngleArray[i]+(double)j/moveCount, 0, shotSpeed3, 0);
                }
            }
            shotAngleArray[i]+=shotAngleRateArray[i];
        }
        shotAngle4=0.25;
        shotAngleRange4=0.2;
        shotSpeed4=0.02;
        intval4=1;
        random = new Random();
        bs4 = new BossShot4[NUM_BULLET];
        for(int i=0; i<NUM_BULLET; i++){
            double rnd = random.nextDouble();
            bs4[i] = new BossShot4(shotAngle4+shotAngleRange4*(rnd-0.5), 0, shotSpeed4, 0);
        }
    }
    
    public void show(Graphics2D g2){
        if(this.ext==false)return;
    
        //ボス自身をshow
        g2.drawImage(image, (int)x, (int)y, null);
    
        //弾をshow
        for(int i=0; i<NUM_BULLET; i++){
            bs1[i].show(g2);
        }
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                bs2[i][j].show(g2);
            }
        }
    
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                bs3[i][j].show(g2);
            }
        }
        for(int i=0; i>NUM_BULLET; i++){
            bs4[i].show(g2);
        }
    }
    
    public void move(){
        //時間で動きを変える
        if(this.ext==false)return;
    
        if(moveCount % 2000 > 0 && moveCount % 2000 < 2999){
            for(int i=0; i<NUM_BULLET; i++){
                bs1[i].move();
            }
        }else if(moveCount % 4000 > 2000 && moveCount % 4000 < 3999){
            for(int i=0; i<countShot23; i++){
                for(int j=0; j<NUM_BULLET; j++){
                    bs2[i][j].move();
                }
            }
        }else if(moveCount % 6000 > 4000 && moveCount % 6000 < 4999){
            for(int i=0; i<countShot23; i++){
                for(int j=0; j<NUM_BULLET; j++){
                    bs3[i][j].move();
                }
            }
        }else if(moveCount % 8000 > 6000 && moveCount % 8000 < 6999){
            for(int i=0; i<NUM_BULLET; i++){
                bs4[i].move();
            }
        }
        moveCount++;
    }
    
    public void shoot(){
        if(this.ext==false)return;
        //時間でショットを変える
        if(moveCount % 2000 > 0 && moveCount % 2000 < 2999){
            for(int i=0; i<NUM_BULLET; i++){
                bs1[i].ext=true;
                bs1[i].x=this.x;
                bs1[i].y=this.y;
            }
        }else if(moveCount % 4000 > 2000 && moveCount % 4000 < 3999){
            for(int i=0; i<NUM_BULLET; i++){
                if(time==0){
                    bs2[0][i].ext=true;
                    bs2[0][i].setXY(this.x, this.y);
                    bs2[1][i].ext=true;
                    bs2[1][i].setXY(this.x, this.y);
                    bs2[2][i].ext=true;
                    bs2[2][i].setXY(this.x, this.y);
                    bs2[3][i].ext=true;
                    bs2[3][i].setXY(this.x, this.y);
                }
                time=(time+1)%intval23;
            }
        }else if(moveCount % 6000 > 4000 && moveCount % 6000 < 6999){
            for(int i=0; i<NUM_BULLET; i++){
                if(time==0){
                    bs3[0][i].ext=true;
                    bs3[0][i].setXY(this.x, this.y);
                    bs3[1][i].ext=true;
                    bs3[1][i].setXY(this.x, this.y);
                    bs3[2][i].ext=true;
                    bs3[2][i].setXY(this.x, this.y);
                    bs3[3][i].ext=true;
                    bs3[3][i].setXY(this.x, this.y);
                }
                time=(time+1)%intval23;
            }
        }else if(moveCount % 8000 > 6000 && moveCount % 8000 < 8999){
            for(int i=0; i<NUM_BULLET; i++){
                if(time==0){
                    bs4[i].ext=true;
                    bs4[i].setXY(this.x, this.y);
                }
                time=(time+1)%intval4;
            }
        }
        moveCount++;
    }
    
    //ボスと自機の当たり判定
    public boolean hitCheckBossAndFighter(Fighter fighter){
        if((Math.pow(this.x - fighter.x, 2)+Math.pow(this.y-fighter.y, 2))>50){
            //ボスの体力を減らして､自機の残機を減らして､消す｡
            this.hp-=10;
            fighter.nLeft--;
            fighter.ext=false;
            //HPが0以下ならボス死亡//ボスが死んだ時にゲームクリア画面へ遷移するならば､booleanではなく､フラグを返す?
            if(this.hp<0){
                this.ext=false;
            }
            return true;
        }else{
            return false;
        }
    }
    
    //ボスと自機の弾との当たり判定
    public void hitCheckBossAndFightersShot(Fighter fighter){
        if(fighter.bulletFlg==Fighter.NORMAL){
            for(int i=0; i<fighter.getNumShot(); i++){
                if((Math.pow(fighter.getShots()[i].getX-this.x, 2)+Math.pow(fighter.getShots()[i].gety-this.y, 2))>82){
                    this.hp-=10;
                    //HPが0以下ならボス死亡//ボスが死んだ時にゲームクリア画面へ遷移するならば､booleanではなく､フラグを返す?
                    if(this.hp<0){
                        this.ext=false;
                    }
                }
            }
        }else if(fighter.bulletFlg==Fighter.NWAY){
            for(int i=0; i<fighter.getNumNwayBullet(); i++){
                if((Math.pow(fighter.getNwatBullet()[i].getX-this.x, 2)+Math.pow(fighter.getNwayBullet()[i].gety-this.y, 2))>82){
                    this.hp-=10;
                    if(this.hp>0){
                        this.ext=false;
                    }
                }
            }
        }
    }
    
    //ボスの弾と自機の当たり判定
    public boolean hitCheckBosssShotAndFighter(Fighter fighter){
        if(moveCount % 2000 > 0 && moveCount % 2000 < 2999){
            for(int i=0; i<NUM_BULLET; i++){
                if(bs1[i].hitCheckBulletAndFighter(fighter)){
                    //自機を消す｡
                    fighter.numLeft--;
                    fighter.ext=false;
                    return true;
                }else{
                    return false;
                }
            }
        }else if(moveCount % 4000 > 2000 && moveCount % 4000 < 3999){
            for(int i=0; i<countShot23; i++){
                for(int j=0; j<NUM_BULLET; j++){
                    if(bs2[i][j].hitCheckBulletAndFighter(fighter)){
                        fighter.numLeft--;
                        fighter.ext=false;
                        return true;
                    }else{
                        return false;
                    }
                }
            }
        }else if(moveCount % 6000 > 4000 && moveCount % 6000 < 4999){
            for(int i=0; i<countShot23; i++){
                for(int j=0; j<NUM_BULLET; j++){
                    if(bs3[i][j].hitCheckBulletAndFighter(fighter)){
                        fighter.nLeft--;
                        fighter.ext=false;
                        return true;
                    }else{
                        return false;
                    }
                }
            }
        }else if(moveCount % 8000 > 6000 && moveCount % 8000 < 6999){
            for(int i=0; i<NUM_BULLET; i++){
                if(bs4[i].hitCheckBulletAndFighter(fighter)){
                    fighter.nLeft--;
                    fighter.ext=false;
                    return true;
                }else{
                    return false;
                }
            }
        }
    }
    }
    
[/code]

以下がmainクラスです｡

[code]

    //main
    package boss;
    
    import java.awt.*;
    
    import javax.swing.*;
    
    public class Main extends JPanel implements Runnable{
    public static Thread thread = null;
    private Image buffer;
    private Graphics bufferGraphics;
    private Boss boss;
    private int gametimer;
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        JFrame frame = new JFrame();
    
        Main app = new Main();
    
        frame.getContentPane().add(app);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setBounds(10, 10, 480, 640);
        frame.setTitle("Test");
        frame.setVisible(true);
    
    
        thread = new Thread(app);
        app.init();
    }
    
    public void init(){
    
        setBackground(Color.black);
        setForeground(Color.white);
        gametimer=0;
    
        boss = new Boss();
        boss.ext=true;
        boss.setXY(200, 500);
        boss.setSpeed(0, 5);
    
        if(buffer==null){
            buffer = createImage(480, 640);
            bufferGraphics = buffer.getGraphics();
        }
    
    
        requestFocus();
    
        thread.start();
    
    }
    @Override
    public void run() {
        // TODO 自動生成されたメソッド・スタブ
        while(true){
            try{
                Thread.sleep(20);
            }catch(InterruptedException e){
                break;
            }
    
            Graphics2D g2 = (Graphics2D)bufferGraphics;
    
            g2.setBackground(Color.black);
            g2.clearRect(0, 0, 480, 640);
    
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setStroke(new BasicStroke(4.0f));
    
            gametimer++;
    
    
    
            boss.move();
            boss.shoot();
            showObject(g2);
            repaint();
        }
    }
    
    private void showObject(Graphics2D g2){
        boss.show(g2);
        g2.drawString("経過時間:"+new Integer(gametimer).toString(), 10, 30);
    }
    
    }
    
[/code]

以下がBossShot.javaです｡

[code]

    //BossShot.java
    package boss;
    
    import java.awt.Graphics2D; 
    import java.awt.Image;
    
    import javax.swing.ImageIcon;
    
    //ショットの抽象クラス
    public abstract class BossShot extends BaseObject {
    protected Image image;
    //これらの初期化は具象クラスで行う｡
    public double angleRate;
    public double speedRate;
    public double angle;
    public BossShot(double angle, double angleRate,
            double speed, double speedRate) {
        // TODO 自動生成されたコンストラクター・スタブ
        super();
        //初期値を初期化
        this.angle=angle;
        this.angleRate=angleRate;
        this.speed=speed;
        this.speedRate=speedRate;
    
        ImageIcon icon = new ImageIcon("res/kShot.png");
        image = icon.getImage();
    }
    
    //複雑な弾はmoveは具象クラスにお任せする
    @Override
    public void move(){
        double rad = angle*Math.PI*2;
    
        x+=speed*Math.cos(rad);
        y+=speed*Math.sin(rad);
    
        angle+=angleRate;
        speed+=speedRate;
    
        if(x<-17||x>480+17||y<-31||y>640+31){
            ext=false;
        }
    }
    
    //showは具象クラス
    public abstract void show(Graphics2D g2);
    
    //当たり判定弾と自機
    public boolean hitCheckBulletAndFighter(Fighter fighter){
        if((Math.pow(this.x - fighter.x, 2)+Math.pow(this.y-fighter.y, 2))>42){
            this.ext=false;
            return true;
        }else{
            return false;
        }
    }
    }
    
[/code]

皆様返答ありがとうございます｡ コメントを参考にしてmainを次のように変更しました｡  
package boss;

[code]

    import java.awt.*;
    
    import javax.swing.*;
    
    public class Main extends JPanel implements Runnable{
        public static Thread thread = null;
        private Image buffer;
        private Graphics bufferGraphics;
        private Boss boss;
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        Main app = new Main();
        SwingUtilities.invokeLater(new Runnable() {
    
            @Override
            public void run() {
                // TODO 自動生成されたメソッド・スタブ
                JFrame frame = new JFrame();
                frame.getContentPane().add(app);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setBounds(10, 10, 480, 640);
                frame.setTitle("Test");
                frame.setVisible(true);
            }
        });
        thread = new Thread(app);
        app.init();
    }
    
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        g2.setBackground(Color.black);
        g2.clearRect(0, 0, 480, 640);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setStroke(new BasicStroke(4.0f));
        showObject(g2);
    }
    
    public void init(){
    
        setBackground(Color.black);
        setForeground(Color.white);
    
        boss = new Boss();
        boss.ext=true;
        boss.setXY(200, -50);
        boss.setSpeed(0, 5);
    
        if(buffer==null){
            buffer = createImage(480, 640);
            bufferGraphics = buffer.getGraphics();
        }
        requestFocus();
        thread.start();
    
    }
    @Override
    public void run() {
        // TODO 自動生成されたメソッド・スタブ
        while(true){
            boss.move();
            boss.shoot();
            repaint();
            try{
                Thread.sleep(20);
            }catch(InterruptedException e){
                break;
            }
        }
    }
    
    private void showObject(Graphics2D g2){
        boss.show(g2);
    }
    
    }
    
[/code]

実行してみたところ､表示されるようにはなりましたが､以下のようなエラーが出るようになってしまいました｡

[code]

    Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at boss.Main.showObject(Main.java:79)
    at boss.Main.paintComponent(Main.java:40)
    at javax.swing.JComponent.paint(Unknown Source)
    at javax.swing.JComponent.paintChildren(Unknown Source)
    at javax.swing.JComponent.paint(Unknown Source)
    at javax.swing.JComponent.paintChildren(Unknown Source)
    at javax.swing.JComponent.paint(Unknown Source)
    at javax.swing.JLayeredPane.paint(Unknown Source)
    at javax.swing.JComponent.paintChildren(Unknown Source)
    at javax.swing.JComponent.paintToOffscreen(Unknown Source)
    at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source)
    at javax.swing.RepaintManager$PaintManager.paint(Unknown Source)
    at javax.swing.RepaintManager.paint(Unknown Source)
    at javax.swing.JComponent.paint(Unknown Source)
    at java.awt.GraphicsCallback$PaintCallback.run(Unknown Source)
    at sun.awt.SunGraphicsCallback.runOneComponent(Unknown Source)
    at sun.awt.SunGraphicsCallback.runComponents(Unknown Source)
    at java.awt.Container.paint(Unknown Source)
    at java.awt.Window.paint(Unknown Source)
    at javax.swing.RepaintManager$4.run(Unknown Source)
    at javax.swing.RepaintManager$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.prePaintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.access$1300(Unknown Source)
    at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$400(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)
    
[/code]


CC BY-SA 3.0
2015-01-14T08:25:45.597
4541
2015-01-15T01:24:19.153
2015-01-15T00:57:11.763
7232
7232
question
java swing
JFrameへの表示


main クラスの `void run()` 内部で `Graphics2D` クラスのオブジェクトを生成していますが、この様な場合には
`JPanel.paintComponent()` メソッドを使います。

[code]

    @Override
    public void paintComponent(Graphics g){
      super.paintComponent(g);
    
      Graphics2D g2 = (Graphics2D)g;
      g2.setBackground(Color.black);
      g2.clearRect(0, 0, 480, 640);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, ...
      g2.setStroke(new BasicStroke(4.0f));
      showObject(g2);
    }
    
    public void run() {
      // TODO 自動生成されたメソッド・スタブ
      while(true){
        gametimer++;
        boss.move();
        boss.shoot();
        repaint();
    
        try{
          Thread.sleep(20);
        }catch(InterruptedException e){
          break;
        }
      }
    }
    
[/code]

`repaint()` を呼び出すと、`paintComponent()` が実行されます。


CC BY-SA 3.0
2015-01-14T11:22:19.600
4552
2015-01-14T16:52:28.717
2015-01-14T16:52:28.717
4541
answer


Swingのコンポーネントを使用する場合、描画の処理はすべてイベントディスパッチスレッド（AWTスレッド）で行うようにすべきです。そうでない場合、ただしく動作しない可能性があります。

`main`メソッドだけで言えば、下記のように、`SwingUtilities.invokeLater`を使って、`JFrame`を表示するコードがAWTスレッドで実行されるようにします。

[code]

    public static void main(String[] args) {
        final Main app = new Main();
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // TODO 自動生成されたメソッド・スタブ
                JFrame frame = new JFrame();
    
                frame.getContentPane().add(app);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setBounds(10, 10, 480, 640);
                frame.setTitle("Test");
                frame.setVisible(true);
    
            }
        });
        thread = new Thread(app);
        app.init();
    }
    
[/code]

その他の、AWT,Swingの描画に関係する処理も、イベントディスパッチスレッドで実行するようにします。また、イベントディスパッチスレッドが実行される文脈で`Thread.sleep`を実行すると、イベントディスパッチスレッド自体が一時停止し、描画処理がストップしてしまいます。`Main.run`も、開始は通常スレッドで行っていますが、`Graphics`クラスを実行する際は、イベントディスパッチスレッドで実行させます。

このように、通常スレッドとイベントディスパッチスレッドをうまく連携して実装する必要があります。

* * *

（追記）

Java6以降をお使いでしたら、バックグラウンド処理にはSwingWorkerも検討してみてください。  
[SwingWorker (Java Platform SE 8 API
ドキュメント)](https://docs.oracle.com/javase/jp/8/api/javax/swing/SwingWorker.html)


CC BY-SA 3.0
2015-01-14T13:25:42.703
4560
2015-01-15T01:24:19.153
2015-01-15T01:24:19.153
6150
6150
4541
answer


すでに argius
さんの回答にあるイベントディスパッチスレッド関係の問題と、`Thread`を使うより、`javax.swing.Timer`を使用したほうが良いのでは？という二点が気になりますが、`JPanel`が黒のままになっている直接の原因は、作成した`buffer`を`JPanel`に描画していないからだと思います。

以下のように、`JPanel#paintComponent(...)`をオーバーライドしてテストしてみてください。

[code]

    public class Main extends JPanel implements Runnable{
      @Override public void paintComponent(Graphics g) {
          super.paintComponent();
          g.drawImage(buffer, 0, 0, this);
      }
      //...
    
[/code]


CC BY-SA 3.0
2015-01-14T13:48:38.803
4562
2015-01-14T13:48:38.803
46
4541
answer