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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Swing

SwingはJavaに標準で付属するグラフィック関連のクラスライブラリを指します。

Q&A

解決済

2回答

12228閲覧

JAVA repaintでの再描画について

nyu

総合スコア7

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Swing

SwingはJavaに標準で付属するグラフィック関連のクラスライブラリを指します。

0グッド

0クリップ

投稿2017/05/09 12:43

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

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

java

1package shooting; 2 3import javax.swing.JFrame; 4 5public class Main { 6 public static void main(String[] args) { 7 JFrame obj = new JFrame(); 8 Gameplay gamePlay = new Gameplay(); 9 obj.setBounds(10, 10, 500, 1000); 10 obj.setTitle("Shooting Game"); 11 obj.setResizable(false); 12 obj.setVisible(true); 13 obj.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 14 obj.add(gamePlay); 15 } 16}

java

1package shooting; 2 3import java.awt.Color; 4import java.awt.Graphics; 5import java.awt.event.ActionEvent; 6import java.awt.event.ActionListener; 7import java.awt.event.KeyEvent; 8import java.awt.event.KeyListener; 9import java.awt.image.BufferedImage; 10import java.io.File; 11 12import javax.imageio.ImageIO; 13import javax.swing.JPanel; 14import javax.swing.Timer; 15 16public class Gameplay extends JPanel implements KeyListener, ActionListener{ 17 private Timer timer; 18 private int enemyposX = 100; 19 private int fighterposX = 240; 20 private int delay = 8; 21 private int enemyXdir = 5; 22 private int count = 0; 23 BufferedImage imgfighter; 24 25 public Gameplay(){ 26 addKeyListener(this); 27 setFocusable(true); 28 setFocusTraversalKeysEnabled(false); 29 timer = new Timer(delay, this); 30 timer.start(); 31 } 32 public void paint(Graphics g){ 33 34 //enemy 35 g.setColor(Color.red); 36 g.fillRect(enemyposX, 100, 50, 50); 37 //Fighter 38// g.setColor(Color.blue); 39// g.fillRect(fighterposX, 800, 40, 40); 40 File file = new File("D:\\ShootingGame\\src\\shooting\\nc98625.png"); 41 try{ 42 imgfighter = ImageIO.read(file); 43 } catch (Exception e){ 44 } 45 g.drawImage(imgfighter, fighterposX, 800, this); 46 47 g.dispose(); 48 } 49 50 @Override 51 public void actionPerformed(ActionEvent e) { 52 moveEnemy(); 53 repaint(); 54 } 55 56 @Override 57 public void keyTyped(KeyEvent e) { 58 } 59 60 @Override 61 public void keyPressed(KeyEvent e) { 62 if(e.getKeyCode() == KeyEvent.VK_RIGHT){ 63 if(fighterposX >= 460){ 64 fighterposX = 460; 65 } else { 66 moveRight(); 67 } 68 } 69 if(e.getKeyCode() == KeyEvent.VK_LEFT){ 70 if(fighterposX <= 0){ 71 fighterposX = 0; 72 } else { 73 moveLeft(); 74 } 75 } 76 if(e.getKeyCode() == KeyEvent.VK_SPACE){ 77 } 78 } 79 80 @Override 81 public void keyReleased(KeyEvent e) { 82 83 } 84 public void moveRight(){ 85 fighterposX += 10; 86 } 87 public void moveLeft(){ 88 fighterposX -= 10; 89 } 90 public void moveEnemy(){ 91 count ++; 92 if(count == 10){ 93 if(enemyposX > 445 || enemyposX < 0){ 94 enemyXdir = -enemyXdir; 95 enemyposX += enemyXdir; 96 } 97 if(enemyposX <= 445 || enemyposX >= 0){ 98 enemyposX += enemyXdir; 99 } 100 count = 0; 101 } 102 } 103}

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

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で実行してもコードを書き替える必要はなくなりますので。

java

1Gameplay() { // コンストラクター 2 ... 3 try { 4 InputStream is = getClass().getResourceAsStream("nc98625.png")) { 5 imgfighter = ImageIO.read(is); 6 } catch (IOException ex) { 7 throw new RuntimeException(ex); 8 } 9}

投稿2017/05/10 01:13

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nyu

2017/05/10 10:56

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

0

ベストアンサー

Swing Painting Guidelines (英語)

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

Java

1 @Override 2 public void paintComponent(Graphics g){ 3 super.paintComponent(g); 4 //enemy 5 g.setColor(Color.red); 6 g.fillRect(enemyposX, 100, 50, 50); 7 //Fighter 8// g.setColor(Color.blue); 9// g.fillRect(fighterposX, 800, 40, 40); 10File file = new File("D:\\ShootingGame\\src\\shooting\\nc98625.png"); 11 try{ 12 imgfighter = ImageIO.read(file); 13 } catch (Exception ex){ 14 System.out.println(ex); 15 } 16 g.drawImage(imgfighter, fighterposX, 800, this); 17 // disposeは不要なのでコメントアウト 18 // g.dispose(); 19 }

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

Java

1private final Enemy enemy = new Enemy();

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

Java

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

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

Java

1enemy.move();

4, Enemyクラスを追加。

Java

1class Enemy { 2 3 private int enemyposX = 100; 4 private int enemyXdir = 5; 5 private int count = 0; 6 int getPosX() { 7 return enemyposX; 8 } 9 void move() { 10 count++; 11 if (count == 10) { 12 if (enemyposX > 445 || enemyposX < 0) { 13 enemyXdir = -enemyXdir; 14 enemyposX += enemyXdir; 15 } 16 if (enemyposX <= 445 || enemyposX >= 0) { 17 enemyposX += enemyXdir; 18 } 19 count = 0; 20 } 21 } 22}

投稿2017/05/09 15:58

編集2017/05/09 17:26
umyu

総合スコア5846

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nyu

2017/05/10 09:52

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問