質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.92%

JAVA repaintでの再描画について

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 5,601

nyu

score 7

前提・実現したいこと

現在、swingを使ってシューティングゲームを作ろうとしているのですが、わからないところがあります。

発生している問題

①,プログラムを実行すると、図形がプログラム通り(敵は自動、戦闘機はキー入力によって)移動するのですが、再描画前の図形が残ったままの状態になります。
イメージ説明
②,①の状態のまま最小化を行うと再描画前の図形が消えて期待した描画が行われます。(また①の状態に戻る)イメージ説明
エラー文は出ません。
この問題の解決方法はあるのでしょうか。
回答宜しくお願いいたします。

package shooting;

import javax.swing.JFrame;

public class Main {
    public static void main(String[] args) {
        JFrame obj = new JFrame();
        Gameplay gamePlay = new Gameplay();
        obj.setBounds(10, 10, 500, 1000);
        obj.setTitle("Shooting Game");
        obj.setResizable(false);
        obj.setVisible(true);
        obj.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        obj.add(gamePlay);
    }
}
package shooting;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Gameplay extends JPanel implements KeyListener, ActionListener{
    private Timer timer;
    private int enemyposX = 100;
    private int fighterposX = 240;
    private int delay = 8;
    private int enemyXdir = 5;
    private int count = 0;
    BufferedImage imgfighter;

    public Gameplay(){
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        timer = new Timer(delay, this);
        timer.start();
    }
    public void paint(Graphics g){

        //enemy
        g.setColor(Color.red);
        g.fillRect(enemyposX, 100, 50, 50);
        //Fighter
//        g.setColor(Color.blue);
//        g.fillRect(fighterposX, 800, 40, 40);
        File file = new File("D:\\ShootingGame\\src\\shooting\\nc98625.png");
        try{
            imgfighter = ImageIO.read(file);
        } catch (Exception e){
        }
        g.drawImage(imgfighter, fighterposX, 800, this);

        g.dispose();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        moveEnemy();
        repaint();
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_RIGHT){
            if(fighterposX >=  460){
                fighterposX = 460;
            } else {
                moveRight();
            }
        }
        if(e.getKeyCode() == KeyEvent.VK_LEFT){
            if(fighterposX <= 0){
                fighterposX = 0;
            } else {
                moveLeft();
            }
        }
        if(e.getKeyCode() == KeyEvent.VK_SPACE){
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }
    public void moveRight(){
        fighterposX += 10;
    }
    public void moveLeft(){
        fighterposX -= 10;
    }
    public void moveEnemy(){
        count ++;
        if(count == 10){
            if(enemyposX > 445 || enemyposX < 0){
                enemyXdir = -enemyXdir;
                enemyposX += enemyXdir;
            }
            if(enemyposX <= 445 || enemyposX >= 0){
                enemyposX += enemyXdir;
            }
            count = 0;
        }
    }
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

umyuさんがコメントされているSwing Painting Guidelinesに参考になる情報が載っています。umyuさんがコメントされている「paintComponentをオーバーライドすべき」という点も大事な点ではありますが、本件についてのポイントはopaqueプロパティーの方です。

opaqueがtrueだと、それは「このコンポーネント上への再描画(paintComponent)では全てのピクセルを描画しますよ」ということを意味し、swingのランタイムは再描画に先立って背景のクリアは必要はないと判断します。
逆にfalseだと、「このコンポーネントは前景として必要な部分のピクセルのみ再描画します」ということを意味するためswingのランタイムは再描画に先立って背景を自動的にクリアしてくれます。

さて、JPanelのデフォルトのopaqueはtrueです。つまりJPanel#paint(それはpaintBorderやpaintChildrenやpaintComponentなどを呼び出しますが)の結果として境界内の全てのピクセルが描画するという考え方のため事前に背景をクリアしないということになります(※)。

一方、JComponentのデフォルトのopaqueはfalseです。こちらは子供のないコンポーネントなので「背景以外のみを描画したい」ことが多く、再描画の際には背景を自動的にクリアしてくれた方が都合がよいためfalseになっていると言えましょう。

※備考:昔のswingではデフォルトでダブルバッファリングしてなかった(オフスクリーンバッファを持ってはいなかった)ため、描画の度に背景をクリアすることをやってましたが現在ではダブルバッファリングが標準で有効になっている(少なくともWindowsでは)ため、JPanelのような子供コンポーネントを持つコンテナにとっては再描画の度に背景をクリアする必要はないため最適化の観点からopaqueをtrueにしているのだろうと思います。

以上より質問者さんの問題の解決には2つ(細かく言えば3つ)の戦略があります。

  • GameplayクラスをJPanelの派生のままにする
    setOpaque(false)をコンストラクターで呼び出しすか、またはpaintComponentの先頭で背景色により全ての領域を塗りつぶして(fillRectして)ください。
  • GameplayクラスをJComponentの派生にする
    基底クラスを変更するだけで期待する動作になります。

現時点ではGameplayクラスには子供を何も配置していないので上記の3種類のいずれでもOKと思います。ただ将来何か子供コンポーネントを配置しようと考えているならJPanelの派生とする戦略で進めるとよいでしょう。


蛇足:本件と本質的に関係ない点ですが実装上の問題点になり得る点をコメントします。

  • setVisible(true)の後にswingコンポーネントにアクセスするのは止めましょう
    setVisible(true)は最後に行うべきです。理由の話は長くなるのでこちらの質問を参照ください。

  • イメージファイルのロード契機
    本件では飛行機のアイコンのような小さなイメージですので実行開始時点でGameplayのインスタンス生成時に読み込んでフィールドへ覚えておく方が一般的だと思います。再描画時に毎回読み込むのは性能的によろしくないと思います。

  • イメージファイルのロードの仕方
    絶対パスで読み込んでますが、このイメージがプログラムに属するものならプロジェクトを別の場所へ移動した際やjarに纏めて配布するといった場合にコードを書き替える必要が生じます。以下のようにするとよいと思います。Gameplay.javaと同じ場所にイメージファイルを置いておけばIDE上で実行する場合でもjarへまとめてからjava -jar xxx.jarで実行してもコードを書き替える必要はなくなりますので。

Gameplay() { // コンストラクター
  ...
  try {
    InputStream is = getClass().getResourceAsStream("nc98625.png")) {
    imgfighter = ImageIO.read(is);
  } catch (IOException ex) {
    throw new RuntimeException(ex);
  }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/10 19:56

    とてもわかりやすい説明、ありがとうございます。

    キャンセル

checkベストアンサー

+1

Swing Painting Guidelines (英語)

SwingはpaintメソッドをOverrideするのではなく、代わりにpaintComponentをOverrideしてください。

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        //enemy
        g.setColor(Color.red);
        g.fillRect(enemyposX, 100, 50, 50);
        //Fighter
//        g.setColor(Color.blue);
//        g.fillRect(fighterposX, 800, 40, 40);
File file = new File("D:\\ShootingGame\\src\\shooting\\nc98625.png");
        try{
            imgfighter = ImageIO.read(file);
        } catch (Exception ex){
            System.out.println(ex);
        }
        g.drawImage(imgfighter, fighterposX, 800, this);
        // disposeは不要なのでコメントアウト
        // g.dispose();
    }

ここからは余談です。
Enemyデータと表示(Gameplay)でクラスを2つに分割した方が、Gameplayクラスで持つフィールド変数を少なくすることができます。
1, private Timer timer; の直後の行に以下の1行を追加

private final Enemy enemy = new Enemy();


2, public void paintComponent(Graphics g) メソッド内のg.fillRect(enemyposX, 100, 50, 50);を変更

g.fillRect(enemy.getPosX(), 100, 50, 50);


3, public void actionPerformed(ActionEvent e) メソッド内のmoveEnemy();を変更

enemy.move();


4, Enemyクラスを追加。

class Enemy {

    private int enemyposX = 100;
    private int enemyXdir = 5;
    private int count = 0;
    int getPosX() {
        return enemyposX;
    }
    void move() {
        count++;
        if (count == 10) {
            if (enemyposX > 445 || enemyposX < 0) {
                enemyXdir = -enemyXdir;
                enemyposX += enemyXdir;
            }
            if (enemyposX <= 445 || enemyposX >= 0) {
                enemyposX += enemyXdir;
            }
            count = 0;
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/10 18:52

    解決できました。ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る