• ベストアンサー

Java ボールが動くアニメーション

ボールがウインドウ内を跳ねるプログラムを作っているのですが、ある所でフリーズしてしまいました。 class BallBound implements ActionListener { BallBound() { //ウインドウ作成・アクションリスナ登録・メニューバー作成&取り付けなど } //アニメーション public void byoga() { boolean xvect = true, yvect = true; loop = true; //グローバル変数 while (loop) { repaint(); try { Thread.sleep(10); } catch (InterruptedException e1) { } if (x <= 0 || x+vx >= frame.getWidth()) { xvect = !xvect; } //はみ出たら逆ベクトルへ if (y <= 0 || y+vy >= frame.getHeight()) { yvect = !yvect; } if (xvect) x += xch; else x -= xch; if (yvect) y += ych; else y -= ych; } } public void actionPerformed(ActionEvent e) { if(e.getSource() == start) { if(loop == false) { byoga(); } } if(e.getSource() == stop) { loop = false; } } protected void paintComponent(Graphics g) { g.fillOval(x, y, vx, vy); } } という風にactionPerformed()から、JMenuItem startが押された場合にbyoga()を呼び出しているのですが、startのメニューを押した(選んだ)瞬間にフリーズしてしまってタスクマネージャから強制終了せざるを得ない状態になってしまいます。 しかしコンストラクタの末尾からbyoga()を呼び出してみたりmain()から呼び出したりした場合はフリーズせずに起動できます。 これは一体何故なんでしょうか・・・? どなたか教えてほしいです・・・

質問者が選んだベストアンサー

  • ベストアンサー
  • _ranco_
  • ベストアンサー率58% (126/214)
回答No.2

投稿されたプログラムでは、一つのメインスレッド上でビジネスロジックとGUI操作の両方をやろうとしているため、byoga()メソッドがリターンするまでrepaint()は実行されません。そこで、ビジネスロジックを別スレッドにするとともに、本当はGUI操作をSwingUtilities.invokeLater()を経由する等によりGUIのスレッドに礼儀正しく登録委託する必要があります(下のサンプルプログラムは超単純で問題もないのでそれを省略)。ただし、そういう面倒なことをしたのは初期の話で、最近では、単純なアニメのようなものはjavax.swing.Timer, より複雑な(I/Oがからむような)ビジネスロジックはjavax.swing.SwingWorkerを使って実装するのが定石です。 (参考URLはhttp://を省略。) 下のプログラムは、古典的に、明示的に別スレッドを作る解法です: ---------------------------------------------------- /* save and compile as BallBoundX.java */ import javax.swing.*; import java.awt.*; import java.awt.event.*; public class BallBoundX extends JPanel { int x, y, vx, vy, xch, ych; boolean loop; Thread t; public BallBoundX(){ x = y= 0; vx = vy = 20; xch = ych = 5; setBackground(Color.white); setPreferredSize(new Dimension(500, 500)); } public void start(){ if (t == null){ t = new Thread(new Runnable(){ public void run(){ byoga(); } }); } t.start(); } public void stop(){ loop = false; } public void byoga() { boolean xvect = true, yvect = true; loop = true; //グローバル変数 while (loop) { if (x < 0 || x + vx > getWidth()) { xvect = !xvect; } //はみ出たら逆ベクトルへ if (y < 0 || y + vy > getHeight()) { yvect = !yvect; } if (xvect){ x += xch; } else{ x -= xch; } if (yvect){ y += ych; } else{ y -= ych; } repaint(); try { Thread.sleep(10); } catch (InterruptedException e1) { e1.printStackTrace(); } } t = null; } protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.green); g.fillOval(x, y, vx, vy); } public static void main(String[] args){ JFrame.setDefaultLookAndFeelDecorated(true); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container con = frame.getContentPane(); JMenuBar mb = new JMenuBar(); frame.setJMenuBar(mb); JMenu menu = new JMenu("実行制御"); final JMenuItem start = new JMenuItem("start"); menu.add(start); final JMenuItem stop = new JMenuItem("stop"); menu.add(stop); mb.add(menu); final BallBoundX bbx = new BallBoundX(); con.add(bbx, BorderLayout.CENTER); frame.setBounds(100, 100, 500, 500); frame.setVisible(true); ActionListener ner = new ActionListener(){ public void actionPerformed(ActionEvent e){ if(e.getSource() == start) { bbx.start(); } else if(e.getSource() == stop) { bbx.stop(); } } }; start.addActionListener(ner); stop.addActionListener(ner); } } -------------------------------------------------

参考URL:
java.sun.com/docs/books/tutorial/uiswing/concurrency/, java.sun.com/products/jfc/tsc/articles/threads/threads1.html
kirikirkaz
質問者

お礼

なるほど。 まだJavaを初めて日が浅いのでスレッドなどには触れていませんでした。 このソースをコンパイルした時3つもクラスファイルが出来たことに驚いたぐらいの初心者です(笑 (ちなみにビジネスロジックという言葉も初めて聞きました。。。) どうやらスレッドを使うと別個にクラスファイルが出来るのですね。 それにgetWidth(),getHeight()はJPanelのを使えば微妙にはみ出たりせず済むのですね。 本当はちょっと-50とかして微調整したりしてました。。。 ちょっとまだ完全に理解できていませんが、GUIとビジネスロジックは分けて書く、ということは覚えておこうと思います。 勉強になるソースをありがとうございました。

その他の回答 (2)

  • _ranco_
  • ベストアンサー率58% (126/214)
回答No.3

すいません。急いで書いたプログラムのデバッグです: (1)t.start();はif節の中に入れないといけません。 (2)xvect,yvectはクラスグローバルとし、コンストラクタの中でtrueに初期化すべきです(そうしないと、stop -> 再startのとき、方向が前と違ってしまうことがある)。

kirikirkaz
質問者

お礼

どうもありがとうございます。

回答No.1

while(loop){ } の部分で無限ループになっているからですよ。

kirikirkaz
質問者

お礼

やっぱり無限ループになってしまうんですか・・・ てっきり僕は再描画した後も、アクションリスナがマウス入力を受け付けてくれると思ったのですが・・・ No.2のrancoさんの仰る通り別のスレッドを作る必要があるのですね。 あ、あとフリーズしてしまうのは描画を中止して、再描画した時ですね。 補足する必要ないですがタイミングを書くのを忘れてしまってました。すいません。

関連するQ&A