Javaでゲームを作る入門 @ うぃき

Swing+AWTの混同した利用について

最終更新:

takejava

- view
だれでも歓迎! 編集
「ゲーム製作におけるSwingとAWT」を混同して利用する方法について検討するページです。
※特にゲームループを独自に実装している人向けのページです
この辺の問題はゲーム作りでなくても、一度はぶつかるのではかと思います

  • 問題、現象
例えば、ちょっとした処理をボタンでやりたいって気軽に思って、じゃぁボタンクラスを使ってみようかってことになります。しかし、今までフレームのグラフィックスを利用してゲームループでゴリゴリ描画していて、いざ、その上にSwingのボタンをのっけてみたけれど、なんかちらちらして上手く描画されません。でも、ボタンを設置した辺りをクリックしたらボタンは押せてるみたいなんですよね・・・、なんて現象が発生してしまいます。

  • 一発で解決する方法
これを解消する方法は、「SwingとAWTを一緒にしない」です。
間違いないです。解決です。お疲れ様でした。
      • では納得いきませんよね。
納得いかない方は以下をお読みください。すこしは理解の手助けになるかと思います。

  • 一般的にはどうなの?
一般的に(といいますか、ネットで調べている限りですが)、SwingとAWTを一緒にしてはいけないっていう表現がよくみられますが、私の経験上でも、実際よくありませんでしたし、SwingとAWTは一緒くたにすべきものではないんだなっていうことが分かってきました。
ただ、絶対だめ、というわけでもありません。上手くやるためには、ものすごい勉強してSwingを理解した上でないとかなり危険だということです。

  • アクティブレンダリングとSwingとAWTの相性が悪いワケ
わたしも、ソースレベルで理解しているわけではないので、事細かに説明することは不可能です。
しかし、ゲームプログラミングを勉強している上で必要なことはわかっています。
まず、「SwingとAWTでは描画の同期がとられない」ということなんです。
例えば、AWTがそれぞれスレッドを持っていて、アクティブレンダリングをしているとします。
そして、そのAWT同士を重ね合わせるとどうでしょうか?
これは問題なく描画されます。
(Swingについては処理的に可能だとは思いますが、数々の問題が発生し打ちのめされるでしょう)
こうイメージすると分かるかと思います。
「AWTは各パーツが窓である」と。
窓って前後関係がしっかり保障されていますよね?
例えば、さまざまなプレーヤーを実行して重ね合わせても、一番後ろにある窓の映像が手前に映る、なんてことは滅多に無いと思います(あるとすれば、悪意のあるソフト、もしくは設定が悪い、最悪Windowsを疑うレベルになると思います)。

じゃぁ、Swingはなんなんだ?っていう話になるかと思います。
Swingは、窓の世界に閉じられたパーツの集まりって言ったらわかるでしょうか?
タブン、分からないかと思います(読み返して自分でもわからなかったです)。
いろんな言い方をしてみます。
「Swingはソフトウェアコンポーネントだ」
「SwingはOSに依存しない(ように)パーツを設置できたりするライブラリだ」
「Swingは独立しているものだ」
「Swingはメインのスレッドとは別のスレッド(しかもシングル)で動いている(ちなみにEventDispathThread:EDTというスレッドです。検索すると色々分かります)」
「そもそもSwingとAWTは比較するレベルのものではない」
「ゲーム画面上にオリジナルのかっこいいボタンをつくることと、Swingでボタンをつくることは同義ととらえていい」

と、勝手に、思うがままに、書き連ねてみましたがどうでしょうか?少しはイメージが変わったでしょうか。ここまで読んで、まだJava=Swingみたいなイメージを持たれている方は、一旦リセットしたほうがいいかもしれません。

つまりは、「AWTとSwingは別スレッドで動いている」というところが決定的な理由になるかと思います。そして「SwingはAWTのグラフィック領域を間借りしている」ということです。

なので、AWTにてゲームループの中でグラフィックをゴリゴリ描画しているその上に、Swingをいくらのっけても、速攻で上書きされてしまうためにちらつきが発生してしまいます。あくまで、画面にでてくる絵がおかしくなっているだけなので、ボタンが押せる、言い換えれば、マウスやキーボードなどの入力は効く、と言う状態になります。

この辺がなんとなく理解したならば、解決方法として、AWTとSwingのスレッド間の同期をとればよいのでは?というものが浮かんでくるかと思います。ぜひ、やってみてください。そして、もし単純に解決できたというのであれば、ご連絡ください。わたしの能力では到底無理なので。というか仕事で上司にやれと言われない限りは絶対その方法はとりたくないです。

よって、先ほど申し上げた通り「SwingとAWTを一緒にしない」が私の解答です。
ボタンを実装したくば、己でゲームループ内で効くように実装しろ!ということです。
解決にはなっていないと思いますが、紆余曲折あってこの結果に至ったので自分では納得しています。

  • ゲームループでは何に対して描画してる?
フレームでしょうか?キャンバスでしょうか?パネルでしょうか?
Javaだとこの辺からGraphicsを取得して、描画処理をするかと思います。
まず、第一段階の切り分けとして、
フレーム(Frameクラス、JFrameクラス)とキャンバス(Canvasクラス)はAWT、パネル(JPanelクラス)はSwingです(JFrameはWindowクラスを継承しているもので、OSに依存するのでAWTとは言えないようです)。

最初、何も知らない私は、とりあえず、フレームにパネルを乗っけてそのパネルのGraphicsを参照していました。知らずにAWTとSwingを混同させていて、今思えば恐いことをしていました。決して悪いとは言いませんが、もし、それ以外にSwingを利用しないのであればやめたほうがいいでしょう。Swingを噛ましているという時点で余分な処理が増えるようなものです。やっぱりリソースは大切にしたいですよね。

じゃあ、ゴリゴリループ描画もしたいし、Swingも使いたいときはどうすれば?いいの。
条件付きですが、すっきり解決する方法があります。
条件とは「重ね合わせない」、「フレームに対するアクティブレンダーはしない」です。
そうした条件のもとであれば、「ゴリゴリループ描画するCanvasとSwingで棲み分ける」ことで解決することができます。


つまり、Swing同士、AWT同士はもちろん描画の同期は保障されるのですが、
SwingとAWTでは全く同期の保障はとられないと思ったほうがいいです。
まず、AWTは重量コンポーネントという意味不明な日本語で訳されたものであるということです。
ネイティブつまり、OS(プラットフォーム)に依存しているということです。
例えば、Frameクラスです。



import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferStrategy;
import java.text.DecimalFormat;
 
import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.JWindow;
 
/*
 * ※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
 * createBufferStrategy();関連のエラーについて
 *
 * ■createBufferStrategyメソッド呼び出しタイミングでエラーがでる件について
 * 「Component must have a valid peer」 と、エラーがえらそうに言っているように、
 * createBufferStrategy()メソッドを呼び出した時点で、
 * 「peerが確定してない(生成できていない)」ことが原因のようだ(peer確定問題と言うようだ)
 *
 * エラーの例1)
 * Exception in thread "main" java.lang.IllegalStateException: Component must have a valid peer
 * at java.awt.Component$FlipBufferStrategy.createBuffers(Component.java:3981)
 * at java.awt.Component$FlipBufferStrategy.<init>(Component.java:3955)
 * at java.awt.Component$FlipSubRegionBufferStrategy.<init>(Component.java:4478)
 * at java.awt.Component.createBufferStrategy(Component.java:3832)
 * at java.awt.Canvas.createBufferStrategy(Canvas.java:194)
 * at java.awt.Component.createBufferStrategy(Component.java:3755)
 * at java.awt.Canvas.createBufferStrategy(Canvas.java:169)
 * at Main.<init>(Main.java:80)
 * at Main.main(Main.java:37)
 *
 * 例えば
 * setVisible()メソッドやpack()メソッドを一度も実行しない状態で、createBufferStrategy()メソッドをたたくとエラーになった
 *
 * peerはいつ確定するの?
 * ①super.addNotifyメソッドを呼び出した後(確実っぽい)
 * ②show()メソッド(フレームのsetVisible(true)メソッドにあたる)やpack()メソッドを呼び出した後(PC性能に依存?ちょっと怪しい)
 *
 * 解決するには?
 * ・①で対応することを考えた場合
 * 		今回の場合はCanvasクラスのaddNotifyをオーバーライドし、その中でsuper.addNotify()呼び出す、そのあとにcreateBufferStrategy()メソッドを呼び出して対応
 * ・②で対応することを考えた場合
 * 		frameにcanvasをaddしておくのはもちろんなのだが、
 * 		「frame.setVisible(true);」もしくは「frame.pack();」をした後に、canvas.createBufferStrategy();メソッドを呼び出して対応
 *	①、②両方とも対応しておけば間違いないはず!
 *
 * 関連事項
 * コンストラクタの中でcreateImageがnullを返すのはpeerが確定していないからのヨーダ
 * peerが確定するタイミングは、「コンストラクタが完了した後」、つまり「addNotifyメソッドを呼び出した後」となるヨーダ
 *
 *
 * ■Graphicsを取得するタイミングでぬるぽがでてしまう件について(まだよくわかっていないが)
 * createBufferStrategy()メソッドがエラーを吐かないパターンのやつ
 * あやしいBufferStrategyを生成してしまい、
 * getBufferStrategy()メソッドはもちろんnullを返さないので、
 * getDrawGraphics()メソッドを呼んでみるが、そこでエラーがでる、というものである
 *
 * エラーの例2)
 * Exception in thread "main" java.lang.NullPointerException
 * at java.awt.Component$BltBufferStrategy.revalidate(Component.java:4428)
 * at java.awt.Component$BltBufferStrategy.revalidate(Component.java:4406)
 * at java.awt.Component$BltBufferStrategy.getDrawGraphics(Component.java:4326)
 * at ActiveRenderCanvas.doActiveRendering(Main.java:304)
 * at Main.<init>(Main.java:188)
 * at Main.main(Main.java:98)
 *
 *
 * 例えば
 * フレームのサイズが確定していない状態でframe.setVisible(true);を実行したあとに
 * frame.setVisible(true);もframe.pack();も実行しないとエラーになった
 *
 * (レイアウト使用時)コンポーネントを追加後frame.pack();が実行できていればエラーはでなかった
 *
 * (よくわかっていないですが、)
 * 可視化する前にcreateBufferStrategyを実行して、
 * さらに正しく2回目(super.addNotifyをオーバーライドした対応)が呼び出されたとしても実は失敗しているようで
 * bufferStrategyはnullではないが内部でおかしなことになってしまっているようだ
 *
 * 解決するには?(対策?)
 * フレームまわりの実装を見直す(レイアウトの設定した場合はpack()に加えて(レイアウト無視の場合も含めて)setVisible()が呼ばれているかどうか)
 * 立ち上げ時のsetVisibleメソッドについては特に丁重に扱う
 * revalidateが関連してるっぽいけれど、エラーが出たタイミングでどう対応するのが正解なのかはわかっていまへん、すいまへん
 *
 *
 * ※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
 */
 
/*
 * 余談
 * frame.pack()について
 *
 * frame.pack();はframeにaddしたコンポーネントの「サイズ」や「レイアウト」を考慮してフレームのサイズを決定してくれるもの
 * 例)
 * canvasの推奨サイズを幅200、高さ200
 * scrollPaneの推奨サイズを幅200、高さ200
 * ウィンドズXPの場合のframeの初期サイズは幅208(4 + 200 + 200 + 4) 高さ234(30 + 200 + 4)になる
 *
 * もしframe.setLayout(null);とした場合は
 * いくらコンポーネントを追加してpack();をしても効果がないので
 * frameのサイズ設定忘れやコンポーネントのsetBounds忘れに注意
 */
 
/**
 * GUI生成と窓の可視化を行った後、ゲームループをに入るクラス
 *
 * メモ
 * ■GUI生成のおおまかな流れ(これに限らないが分かりやすさで考えてみた)
 *
 * ①各コンポーネント生成とパラメータ設定(Frame Canvas JTextArea JScrollPane などなど)
 * ※②と重複あり(サイズ設定など)
 *
 * ②レイアウト設定と各コンポーネントの配置
 * ∟レイアウトを使用する場合(setLayout(new BorderLayout());など)
 * ②-1 各コンポーネントの推奨サイズ設定 setPreferSizeメソッド
 * ②-2 add( コンポーネント, "West");
 * ②-3 pack();
 * ※フレームのサイズはpack();により決定し上書きされるので特に設定しなくてもよい(設定しても問題ないが追加するコンポーネントによって意味が無くなるぽい)
 * ∟レイアウトを使用しない場合(setLayout(null);)
 * ②-1 各コンポーネントの座標指定(setLocation)と各コンポーネントのサイズ設定(setSize)
 * ※座標とサイズを同時設定するにはsetBoundsメソッドが便利
 * ※フレームのサイズ設定を必ず行うこと
 * ②-2 add( コンポーネント);
 * ②-3 フレームのvalidate();を実行
 * ※調べ中
 * ※まだ不明瞭な点があるのだが、コンポーネントの重なりをaddした順から手前に表示されるよう更新してくれるようだ
 * ※重なりがないのであれば、呼ばなくても変わらないと思われる
 *
 * ③窓の可視化
 * ※最後にもってきたほうが無難っぽい
 * ※各コンポーネントのサイズ設定をする前にframe.setVisible(true);をやってそのまま・・・ということをしてぬるぽがでたことがあった
 * ※このメソッドが呼ばれた時点で窓が画面にでてくる
 * ※フレームのインセッツは可視化しないと値が入らなかったりするので、このタイミングが当たり前っていうのも違う
 * ※ExtendsFrameで実装したNowLoadingメソッドはメソッド内で可視化、不可視化して遊んでいる
 *
 * ここまでGUI関連の処理
 * ※実際のところ①、②、③に関しては前後しても問題なく、さらには可視化したあとサイズを変更しても問題なかったりする
 * ※peer確定問題に対応するために、上記の流れであれば、エラーが探しやすそうというだけである
 * ※各コンポーネントで設定する値を継承先のコンストラクタなどで設定するのもいいが、(peer確定問題含め)処理順序がごっちゃになると管理が大変な気がするし、悩むところ
 *
 * ④ゲームのメインループをスタート
 * ※ゲームの前準備(オセロの情報などの描画に必要なデータやリスナー登録など)は、メインループに入る前に済ませておけばどこでもよいと思われる
 * ※リスナー系の登録削除には気をつけないといけない場合がありそうだが、まだ未熟なのでなんともいえまへん
 *
 */
public class Main {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Main();
		System.out.println("メインスレッド完"); // ループを抜けることがあればの話
	}
 
	private Main() {
		/**
		 * ①各コンポーネント生成とパラメータ設定
		 * ※今回は全部ローカルでJFrame と Canvas と JTextArea と JScrollPaneなどを生成、パラメータ設定している
		 * ※なので必要に応じてMainのフィールドにもってくるなどして対応する必要がある
		 */
		// フレーム
		ExtendsFrame frame = new ExtendsFrame();
		// frame.nowLoading(100); // 危険なお遊びメソッド setVisibleを中で実行しているので、以降の処理に影響を与える可能性がある
		frame.setTitle("テスト");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
		// キャンバス
		ActiveRenderCanvas canvas = new ActiveRenderCanvas();
 
		// テキストエリア
		JTextArea textArea = new JTextArea(); // サイズの設定に関しては文字列の列数と行数に依存するのであまり気にしない
		textArea.setFont(new Font("MS ゴシック", Font.BOLD, 12));
		// textArea.setLineWrap(true); // とにかく折り返す
		textArea.setWrapStyleWord(true); // 単語単位で折り返す
 
		// スクロールペイン
		JScrollPane scrollPane = new JScrollPane(textArea);
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 垂直のバーは常に出す
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // 水平のバーは絶対出さない
 
		/**
		 * ②レイアウト設定と各コンポーネントの配置(■BorderLayoutバージョン)
		 *
		 */
		// canvas.setPreferredSize(new Dimension(200, 200));
		// scrollPane.setPreferredSize(new Dimension(200, 200));
		// frame.setLayout(new BorderLayout()); // デフォルトがBorderLayoutになるので、あってもなくても関係ない
		// frame.add(canvas /* , "West" */); // 指定無しで"Center"扱い
		// frame.add(scrollPane, "East");
		// System.out.println("BEFORE:frame.pack();");
		// frame.pack();
		// System.out.println("AFTER:frame.pack();");
 
		/**
		 * ②レイアウト設定と各コンポーネントの配置(■GridLayoutバージョン)
		 * ※左右で2分する、フレームのサイズ変更に反応して、均等にコンポーネントのサイズを更新してくれる
		 * ※なのでフレームサイズを設定すれば勝手にコンポーネントのサイズも決まる
		 */
		frame.setPreferredSize(new Dimension(400, 200));
		frame.setLayout(new GridLayout(1, 2)); // 1行×2列
		frame.add(canvas);
		frame.add(scrollPane);
		System.out.println("BEFORE:frame.pack();");
		// frame.pack();
		System.out.println("AFTER:frame.pack();");
 
		/**
		 * ②レイアウト設定と各コンポーネントの配置(■座標指定バージョン)
		 * ※setBoundsで場所とサイズを必ず設定(setLocationとsetSizeの組み合わせも同様)
		 * ※重ねることに成功したりしなかったりと不安定
		 * ※必ず最初のコンポーネントが手前に、あとに追加されたコンポーネントが後ろにいく
		 */
//		 // パネルを追加してみる実験
//		 JPanel panel = new JPanel();
//		 panel.setBackground(new Color(1, 1, 0, 0.3f));
//		 // 2つ目のキャンバスを追加してみる実験
//		 ActiveRenderCanvas canvas2 = new ActiveRenderCanvas();
//		 // ボタンを追加してみる実験
//		 JButton button = new JButton("ぼたん");
//		 // // インターナルフレーム
//		 // JInternalFrame internalFrame = new JInternalFrame("インターナルフレーム", true, true, true, true);
//		 // internalFrame.setBounds(0, 0, 200, 100);
//		 // internalFrame.setBackground(Color.ORANGE);
//		 // internalFrame.setVisible(true);
//		 // // デスクトップペイン
//		 // JDesktopPane desktopPane = new JDesktopPane();
//		 // desktopPane.setBounds(0,0,1000,1000);
//		 // desktopPane.setOpaque(false); // 透明化
//		 // desktopPane.setBackground(new Color(0,0,0,0));
//		 // desktopPane.add(internalFrame);
//		 // frame.add(desktopPane);
//		 // 座標、サイズ指定
//		 frame.setBounds(0, 0, 400, 400);
//		 canvas.setBounds(10, 10, 300, 300);
//		 scrollPane.setBounds(50, 50, 100, 100);
//		 panel.setBounds(250, 50, 400, 400);
//		 canvas2.setBounds(200, 200, 300, 300);
//		 button.setBounds(0, 200, 100, 100);
//		 frame.setLayout(null);
//		 frame.add(canvas2); // 手前に配置
//		 frame.add(scrollPane);
//		 frame.add(button);
//		 frame.add(panel);
//		 frame.add(canvas); // 奥に配置
 
		/*
		 * このタイミングでcreateBufferStrategyを実行してみる実験
		 * キャンバスのバッファストラテジーを生成しようとするのだが
		 * もしこの時点で、「frame.setVisible(true);」が実行されていない、
		 * またはレイアウト指定しておいて「frame.pack();」を実行していない、などでエラーを吐く
		 */
		{
			try {
				canvas.createBufferStrategy(3);
			} catch (Exception e) {
				System.out.println("エラーポイント①");
				System.out.println("	[frame.pack();]忘れの可能性");
				System.out.println("	[frame.add(canvas);]忘れの可能性");
				System.out.println("	[frame.setVisible(true);]忘れの可能性");
			}
		}
 
		/**
		 * ③窓の可視化
		 * 窓をディスプレイの中心で可視化
		 */
		frame.setLocationRelativeTo(null);
		System.out.println("BEFORE:frame.setVisible(true);");
		frame.setVisible(true); // コメントアウトして動作を確かめる
		System.out.println("AFTER:frame.setVisible(true);");
 
		/* ここまでがGUIの生成と可視化の処理**************************************************************************** */
 
		/*
		 * ゲームの前準備、初期化
		 */
		init();
 
		/**
		 * ④ゲームのメインループをスタート
		 */
		System.out.println("ゲームループスタート");
		long calcInterval = 0;
		char c = '\u0041';
		while (true) {
			// 大体1秒間隔で処理したい
			if ((calcInterval += 1000 / fps) >= 1000) {
				calcInterval = 0;
				// デバッグプリント
				// System.out.println("フレーム " + frame.getInsets() + " " + frame.getSize());
				// System.out.println("キャンバス 幅:" + canvas.getWidth() + " 高さ:" + canvas.getHeight());
				// textArea.append("フレーム " + frame.getInsets() + " " + frame.getSize() + crlf);
				// textArea.append("キャンバス 幅:" + canvas.getWidth() + " 高さ:" + canvas.getHeight() + crlf);
				textArea.append(Character.toString(c++) + crlf);
 
				/*
				 * パネルグラフィックス実験
				 */
				// Graphics g = panel.getGraphics();
				// if (g != null) {
				// g.setColor(new Color((float) Math.random(), (float) Math.random(), (float) Math.random(), 0.9f));
				// g.drawString("パネル", 10, 30);
				// g.dispose();
				// }
			}
 
			/*
			 * BufferStrategyとgetGraphics()の併用はどうなる実験
			 */
//			Graphics g = canvas.getGraphics();
//			g.setColor(Color.CYAN);
//			g.fillOval(50, 50, 100, 100);
//			g.dispose();
 
			/*
			 * 描画(各自がもってるバッファストラテジーを使ってる)
			 */
			 frame.draw(Color.WHITE); // ちらついてしまう原因
			canvas.draw(Color.BLACK);
			// canvas2.draw(Color.PINK);
 
 
			// 高速でまわってしまうし、他のスレッドに迷惑をかける可能性があるので本スレッドを一時休止
			fpsKeeper.sleep();
			// try {
			// Thread.sleep(1000 / 30); // FPS30に設定
			// } catch (InterruptedException e) {
			// e.printStackTrace();
			// }
		}
	}
 
	FPSKeeper fpsKeeper;
	int fps;
	final String crlf = System.getProperty("line.separator"); // 改行コード
 
	private void init() {
		fps = 60;
		fpsKeeper = new FPSKeeper(fps); // FPSを60(ぐらいになるよう)に設定
	}
 
	/**********************************************************************************************************************
	 * 以下、インナークラスの定義
	 **********************************************************************************************************************/
	/**
	 * アクティブレンダリングを実装したCanvas
	 */
	class ActiveRenderCanvas extends Canvas {
		BufferStrategy bufferStrategy;
 
		/**
		 * peer確定後、バッファストラテジー生成と参照コピーを実行するようにオーバーライド
		 * bufferStrategyがぬるぽエラーを回避できる!
		 */
		@Override
		public void addNotify() {
			super.addNotify(); // ☆ここでpeer確定
			System.out.println(this.getName() + "のpeer確定");
			/*
			 * キャンバスのバッファストラテジーを生成
			 */
			try {
				createBufferStrategy(3);
				bufferStrategy = getBufferStrategy(); // ループで使用するために参照を保持しておく
			} catch (Exception e) {
				System.err.println("エラーポイント③"); // もしここでエラーがでるというのであれば、わたしはお手あげですw
				System.err.println(this.getName() + "のバッファストラテジー生成に失敗");
			}
			System.out.println(this.getName() + "のバッファストラテジー生成に成功");
		}
 
		/**
		 * ゴリゴリ描画するゲームループ内で呼び出すメソッド
		 * bufferStrategyがnullまたは、Graphicsが取得できないなどのエラーに対応すれば、とまったりはしない
		 * 具体的な描画処理は別の関数にまとめた
		 */
		public void draw(Color color) {
			/*
			 * 一応ぬるぽ対応することでエラーでとまることはなくなるが、
			 * 可視化されなければ、ずっとnullかも
			 */
			if (bufferStrategy == null) {
				System.err.println("bufferStrategyがぬるぽ");
				try {
					createBufferStrategy(3);
					bufferStrategy = getBufferStrategy();
				} catch (Exception e) {
					System.err.println("エラーポイント②");
					System.err.println("	[frame.pack();]忘れの可能性");
					System.err.println("	[frame.add(canvas);]忘れの可能性");
					System.err.println("	[frame.setVisible(true);]忘れの可能性");
				}
				return;
			}
 
			/*
			 * 謎エラーポイント
			 * サイズ設定、レイアウト設定、可視化タイミングを疑う
			 */
			Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics(); // Graphicsをゲット!
			if (!bufferStrategy.contentsLost()) { // フルスクリーン化したときになにかをロストするらしいのでその対策
				/*
				 * g を使って描画処理
				 */
				draw(g, color);
				bufferStrategy.show();
				g.dispose();
			}
		}
 
		/**
		 * 具体的な描画
		 * 描画に必要なデータをここで参照できるようにクラス設計しなおす必要がある
		 *
		 * @param g
		 * @param color
		 */
		private void draw(Graphics2D g, Color color) {
			// キャンバスのサイズで塗りつぶし
			g.setColor(new Color(1, 1, 1, 0.1f));
			g.fillRect(0, 0, getWidth(), getHeight());
			// 左上
			g.setColor(Color.RED);
			g.fillRect(0, 0, 10, 10);
			// 右下
			g.setColor(Color.GREEN);
			g.fillRect(getWidth() - 10, getHeight() - 10, 10, 10);
			// ランダム円描画
			g.setColor(color);
			g.fillOval((int) (Math.random() * getWidth()), (int) (Math.random() * getHeight()), 20, 20);
			// canvasの枠を黒で描画
			g.setColor(Color.BLACK);
			g.setStroke(new BasicStroke(5.0f));
			g.drawRect(0, 0, getWidth(), getHeight());
			g.setStroke(new BasicStroke());
			// FPS描画
			g.setColor(Color.BLACK);
			g.drawString("FPS:" + fpsKeeper.getFPS(), 4, 20);
		}
 
	}
 
	/**
	 * アクティブレンダリング機能搭載JFrame
	 */
	class ExtendsFrame extends JFrame {
		private BufferStrategy bufferStrategy;
 
		@Override
		public void addNotify() {
			super.addNotify();
			System.out.println(this.getName() + "のpeer確定");
			try {
				createBufferStrategy(3);
				bufferStrategy = getBufferStrategy();
			} catch (Exception e) {
				System.err.println("エラーポイント③"); // もしここでエラーがでるというのであれば、お手あげですw
				System.err.println(this.getName() + "のバッファストラテジー生成に失敗");
			}
			System.out.println(this.getName() + "のバッファストラテジー生成に成功");
		}
 
		public void draw(Color color) {
			if (bufferStrategy == null) {
				System.err.print("bufferStrategyがぬるぽ");
				return;
			}
			Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics(); // Graphicsをゲット!
			if (!bufferStrategy.contentsLost()) {
				draw(g, color);
				bufferStrategy.show();
				g.dispose();
			}
		}
 
		private void draw(Graphics2D g, Color color) {
			// キャンバスのサイズで塗りつぶし
			g.setColor(new Color(0, 0, 0, 0.1f));
			g.fillRect(0, 0, getWidth(), getHeight());
			// 左上
			g.setColor(Color.RED);
			g.fillRect(0, 0, 10, 10);
			// 右下
			g.setColor(Color.GREEN);
			g.fillRect(getWidth() - 10, getHeight() - 10, 10, 10);
			// ランダム円描画
			g.setColor(color);
			g.fillOval((int) (Math.random() * getWidth()), (int) (Math.random() * getHeight()), 20, 20);
			// canvasの枠を黒で描画
			g.setColor(Color.WHITE);
			g.setStroke(new BasicStroke(5.0f));
			g.drawRect(0, 0, getWidth(), getHeight());
			g.setStroke(new BasicStroke());
			// FPS描画
			g.setColor(Color.WHITE);
			g.drawString("FPS:" + fpsKeeper.getFPS(), 4, getHeight() - 14);
		}
 
		/**
		 * プログレスバー思いつきでつくってみた
		 * とはいっても時間を浪費するだけのメソッド
		 * ※例外処理はまったく実装していない
		 *
		 * @param count
		 */
		void nowLoading(float count) {
			final Dimension progressBar = new Dimension(200, 50);
			setSize(progressBar);
			setLocationRelativeTo(null);
			setVisible(true);
			Graphics g = getGraphics();
			for (int loopCount = 1; loopCount <= count; loopCount++) {
				final int percentage = (int) ((progressBar.width / count) * loopCount);
				String period = "";
				for (int i = 0; i < percentage / 10 % 3; i++)
					period = period + ".";
				setTitle("NowLoading" + period);
				g.setColor(new Color(0.5f, 0, 0, 0.1F));
				g.fillRect(0, 0, percentage, getHeight());
				try {
					Thread.sleep(10);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
			}
			setTitle("Completed!");
			g.setColor(Color.GREEN);
			g.fillRect(0, 0, getWidth(), getHeight());
			try {
				Thread.sleep(300);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			g.dispose();
			setVisible(false);
		}
	}
}
 
/**
 * おまけ
 * Javaでゲーム作りますが何か?
 * http://d.hatena.ne.jp/aidiary/20070429/1251463673 のパクリです
 */
class FPSKeeper {
	// 期待するFPS(1秒間に描画するフレーム数)
	private int FPS; // ★値を変えてみよう
 
	// 1フレームで使える持ち時間
	private long PERIOD = (long) (1.0 / FPS * 1000000000); // 単位: ns
 
	// FPSを計算する間隔(1s = 10^9ns)
	private static long MAX_STATS_INTERVAL = 1000000000L; // 単位: ns
 
	// FPS計算用
	private long calcInterval = 0L; // in ns
	private long prevCalcTime;
 
	// フレーム数
	private long frameCount = 0;
 
	// 実際のFPS
	private double actualFPS = 0.0;
	private DecimalFormat df = new DecimalFormat("0.0");
 
	private long beforeTime, afterTime, timeDiff, sleepTime;
	private long overSleepTime = 0L;
	private int noDelays = 0;
 
	public FPSKeeper() {
		this(60);
	}
 
	public FPSKeeper(int FPS) {
		prevCalcTime = beforeTime = System.nanoTime();
		setFPS(FPS);
	}
 
	/**
	 * 途中でFPSを変更したい際に呼ぶ
	 *
	 * @param fps
	 */
	public void setFPS(int fps)
	{
		FPS = fps;
		PERIOD = (long) (1.0 / FPS * 1000000000); // 単位: ns
	}
 
	/**
	 * FPSを00.0の形の文字列で返す
	 *
	 * @return
	 */
	public String getFPS() {
		return df.format(actualFPS);
	}
 
	/**
	 * 設定した(デフォルトは60)FPSの値になるようにスレッドのsleep時間を制御する
	 */
	public void sleep() {
		getSleepTime();
		if (sleepTime > 0) {
			/*
			 * 休止時間がとれる場合
			 */
			try {
				Thread.sleep(sleepTime / 1000000L); // nano->ms
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		notifyOverSleepTimeAndCalcFPS(); // sleep()の誤差を通知 ※getSleepTime()に反映させる必要があるため
	}
 
	private long getSleepTime() {
		afterTime = System.nanoTime();
		timeDiff = afterTime - beforeTime;
		// 前回のフレームの休止時間誤差も引いておく
		sleepTime = (PERIOD - timeDiff) - overSleepTime;
 
		return sleepTime;
	}
 
	private void notifyOverSleepTimeAndCalcFPS() {
		if (sleepTime > 0) {
			overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
		} else {
			// 状態更新・レンダリングで時間を使い切ってしまい休止時間がとれない場合
			overSleepTime = 0L;
			// 休止なしが16回以上続いたら
			if (++noDelays >= 16) {
				Thread.yield(); // 他のスレッドを強制実行
				noDelays = 0;
			}
		}
		beforeTime = System.nanoTime();
 
		// FPSを計算
		calcFPS();
	}
 
	/**
	 * FPSの計算
	 */
	private void calcFPS() {
		frameCount++;
		calcInterval += PERIOD;
 
		// 1秒おきにFPSを再計算するようにする
		if (calcInterval >= MAX_STATS_INTERVAL) {
			long timeNow = System.nanoTime();
			// 実際の経過時間を測定
			long realElapsedTime = timeNow - prevCalcTime; // 単位: ns
 
			// 実際のFPSを計算
			// realElapsedTimeの単位はnsなのでsに変換する
			actualFPS = ((double) frameCount / realElapsedTime) * 1000000000L;
			frameCount = 0L;
			calcInterval = 0L;
			prevCalcTime = timeNow;
		}
	}
 
}
 

タグ:

+ タグ編集
  • タグ:
記事メニュー
ウィキ募集バナー