画像(logo)

HOME/[JAVA言語]ブロックパズルの作り方 目次/二日目 背景とブロックと壁を表示

[JAVA言語 ブロックパズルの作り方]
二日目 背景とブロックと壁を表示

広告

↓2017年04月19日発売↓

カラー図解 Javaで始めるプログラミング 知識ゼロからの定番言語「超」入門 (ブルーバックス)

新品価格
¥1,080から
(2017/5/1 13:45時点)

目次へ戻る

2Dゲームの土台

まずは2Dゲームの土台を作っていきます。

こちらの内容は冒頭にお話しした通り、クラスライブラリの扱い方などの知識が必要ですが、今回は初心者向けという事もあるので詳しい説明は避けたいと思います。

このように書けばひとまず2Dゲームの土台ができるとカタチで覚えて頂ければと思います。

ウィンドウを表示

では最初にウィンドウを表示してみます。

import javafx.application.Application;
import javafx.stage.Stage;

public class B_PMain extends Application{
	public static void main(String args[]){
		launch(args);
	}

	@Override
	public void start(Stage stage){
		stage.setTitle("BLOCK PUZZLE");
		stage.show();
	}
}

■実行結果■

画像(jb_2_1)

ウィンドウの表示には「javafx.stage.Stage;」ってものを使ってます。

この「Stage」なるものが下地となってここに絵を描く為の機能とかゲームやアニメーションに使う高速で場面を切り替える機能なんかを追加していく感じになります。

今までの「public static void main()」は「launch(args)」とかいうのがあるだけで、この後いじる事はありません。

ちなみに「B_PMain」の「B_P」は「BLOCK PUZZLE」の略です。

高速描画機能を追加

ではゲームの為の高速描画機能を追加していきます。

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.animation.AnimationTimer;

public class B_PMain extends Application{
	private B_PThread b_pthread;

	public static void main(String args[]){
		launch(args);
	}

	@Override
	public void start(Stage stage){
		stage.setTitle("BLOCK PUZZLE");
		Pane pane = new Pane();
		Scene scene = new Scene(pane);
		stage.setScene(scene);

		Canvas canvas = new Canvas(640,480);
		GraphicsContext gc = canvas.getGraphicsContext2D();
		pane.getChildren().add(canvas);

		b_pthread = new B_PThread(gc);
		b_pthread.start();

		stage.show();
	}
}

class B_PThread extends AnimationTimer{
	private GraphicsContext gc;

	/*変数の宣言などを書いていく*/
	private int count;

	B_PThread(GraphicsContext gc){
		this.gc = gc;

		/*コンストラクタ*/
		/*最初の1回だけ実行されるので初期化などを書く*/

		count = 0;
	}

	@Override
	public void handle(long time){
		gc.clearRect(0,0,640,480);

		/*ゲームループ*/
		/*この部分がすごい速さで繰り返される*/
		/*メインプログラムを書く*/

		gc.fillText("count = " + count,450,450);
		count++;		
	}

	/*この後に追加の自作メソッドなどを書いていく*/
}

■実行結果■

画像(jb_2_2)

すごい勢いで画面右下のカウントが増加すれば成功です。

いきなりプログラムが増えましたが案ずる事はありません。

これでほぼ土台は完了です。

キー入力の部分がここにはまだ入ってませんが、それは必要になった時に再び追加していきます。

一言ではとても説明しきれない内容ですが、「Canvas」や「GraphicsContext」と書いてあるらへんで絵を描く為の機能を追加して、その後の「class B_PThread extends AnimationTimer」の部分が高速に場面を切り替える為の機能を追加しているような感じになります。

まあ、細かい事は抜きにしてこれを一つのカタチとしてコメントの場所にプログラムを書いていけば2Dゲームはできるという事です。

もちろんこのカタチのまま他の場面にも使えますのでぜひコピペして使いまわしてください。

今回は高速描画されている事を確認する為に「count」という変数を用意してそれをインクリメントしながら画面に表示しております。

コメントの内容とともに一つ一つ確認していってみましょう。

まずは先頭、変数の宣言部分です。

/*変数の宣言などを書いていく*/
private int count;

この「private」っていうのはこのクラス「class B_PThread extends AnimationTimer」のカッコの外からのアクセスを制限する為のものですが、とりあえずは何も考えず変数の宣言時は先頭にこれを書いておけば大丈夫です。

続いて「コンストラクタ」と書いてある部分です。

B_PThread(GraphicsContext gc){
	this.gc = gc;

	/*コンストラクタ*/
	/*最初の1回だけ実行されるので初期化などを書く*/

	count = 0;
}

この部分はコンストラクタという部分でコメントの通り最初の1回だけ実行されるので初期化などを書いていきます。

今回は先ほどの変数「count」を初期化しております。

続いて「ゲームループ」と書いてある部分です。

@Override
public void handle(long time){
	gc.clearRect(0,0,640,480);

	/*ゲームループ*/
	/*この部分がすごい速さで繰り返される*/
	/*メインプログラムを書く*/

	gc.fillText("count = " + count,450,450);
	count++;		
}

こちらもコメントの通りすごい速さでこの部分が繰り返されるので、たとえばキャラクターを描画、少し移動、再び描画などと書いていけば結果キャラクターが動いているように見えるというワケです。

なんとなく想像はつくかと思いますが

gc.clearRect(0,0,640,480);

このメソッドで一回一回画面をクリアにしております。

なのでこれを書かないと前に描いた内容がすべて残ります。

そして最後に自作メソッドなど追加したい場合はコメントの部分に書いていきます。

さきほどのも含め、なんとなく想像つくものをコメントアウトしたり、内容をちょこっと変えたりしてみれば、このプログラムの意味はだいたいつかめるかと思います。

あとは「gc.fillText()」というのを使用して文字を画面に描画しております。

gc.fillText("文章",x座標,y座標);

サンプルの通り変数の内容なども表示する事ができます。

ちなみに指定した座標の上に文字が現れます。

座標を(0,0)などに指定すると文字が隠れてしまっていたりするのでご注意ください。

もちろん文字を描くだけではなく図形なども描く事ができるので興味がある方は「java fx GraphicsContext リファレンス」などでインターネット検索してみてください。

背景を表示

それでは2Dゲームの土台ができたトコロで今回のテーマの一つ背景を表示してみたいと思います。

何もないとさみしいですもんね。

まずはお好みの背景画像(640×480)を用意しましょう。

画像(jb_2_3)

美しき星空です。

こちらの画像はぱくたそさんのサイトより拝借させて頂いております。

そして用意したファイルを「back_img.jpg」という名前でソースプログラムが置いてあるフォルダに置きます。

次はプログラムの方です。

まずは

import javafx.scene.image.Image;

こちらをインポートした上で画像用の変数を用意します。

private Image img;

「Image」型という型を使います。

次にこの変数に実際の画像を読み込みます。

img = new Image("back_img.jpg");

これで変数「img」に先ほどの画像が読み込まれました。

後は表示するだけです。

いちよう座標の確認ですが

画像(jb_2_4)

左上が(x0,y0)で右下が(x639,y479)になります。

それでは表示してみます。

gc.drawImage(img,0,0);

メソッド「drawImage(画像の変数,左上のx座標,左上のy座標)」になります。

画像の左上の角に合わせて指定した座標に画像を表示します。

ではこれらを先ほどのプログラムに追加すると・・・。

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.animation.AnimationTimer;
import javafx.scene.image.Image;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.paint.Color;

public class B_PMain extends Application{
	private B_PThread b_pthread;

	public static void main(String args[]){
		launch(args);
	}

	@Override
	public void start(Stage stage){
		stage.setTitle("BLOCK PUZZLE");
		Pane pane = new Pane();
		Scene scene = new Scene(pane);
		stage.setScene(scene);

		Canvas canvas = new Canvas(640,480);
		GraphicsContext gc = canvas.getGraphicsContext2D();
		pane.getChildren().add(canvas);

		b_pthread = new B_PThread(gc);
		b_pthread.start();

		stage.show();
	}
}

class B_PThread extends AnimationTimer{
	private GraphicsContext gc;

	/*変数の宣言などを書いていく*/
	private int count;
	private Image img;

	B_PThread(GraphicsContext gc){
		this.gc = gc;

		/*コンストラクタ*/
		/*最初の1回だけ実行されるので初期化などを書く*/
		Font theFont = Font.font("Serif",FontWeight.BOLD,25);
		gc.setFont(theFont);
		count = 0;
		img = new Image("back_img.jpg");
	}

	@Override
	public void handle(long time){
		gc.clearRect(0,0,640,480);

		/*ゲームループ*/
		/*この部分がすごい速さで繰り返される*/
		/*メインプログラムを書く*/

		gc.drawImage(img,0,0);
		gc.setFill(Color.WHITE);
		gc.fillText("count = " + count,450,450);
		count++;		
	}

	/*この後に追加の自作メソッドなどを書いていく*/
}

■実行結果■

画像(jb_2_5)

背景画像がウィンドウに現れました!

話そびれましたが文字の大きさとタイプ、加えて色の指定など少し変更してあります。

import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.paint.Color;

こちらをインポートした上で文字のタイプ等はこちらを

Font theFont = Font.font("Serif",FontWeight.BOLD,25);
gc.setFont(theFont);

色の指定はこちらを

gc.setFill(Color.WHITE);

使用します。

「Font.font("Serif",FontWeight.BOLD,25)」ここの後半部分の数字で文字の大きさ、「Color.WHITE」ここを「Color.RED」などに変えれば色も変わったりするのでお好みで調整してみてください。

あと画像の読み込みなどは1回読み込めば十分なので「コンストラクタ」の部分で読み込みを行っております。

広告

2重の「for」ループ

続けて壁とブロックを表示したいトコロですがその前にブロックパズル作りでよく使われる2重の「for」ループの動きの確認をしておきたいと思います。

for(y=0;y<4;y++){
	for(x=0;x<4;x++){
		
	}
}

こちらが2重の「for」ループです。

ループの内側にループがありますね!

イメージとしては最初に内側のループを終わらせてから、外側のループを一つ進めるような感じになります。

これが慣れないとなかなかややこしいのですが、例えば

private int block[][] = {
	{0,0,0,0},
	{0,1,1,0},
	{0,1,1,0},
	{0,0,0,0}
};

こちらのブロックを

for(int y=0;y<4;y++){
	for(int x=0;x<4;x++){
		if(block[y][x] == 1){
			gc.fillText("1",x * 20,y * 20);
		}
		else if(block[y][x] == 0){
			gc.fillText("0",x * 20,y * 20);
		}
	}
}

こんな感じの2重ループで描いていくとして

まずは内側のループで横一列を描きます。

0000

そして内側のループを終わらせて・・・。

0000
0...

外側のループを一つ進めて再び内側のループに入る

こんな感じで進んでいくことになります。

なんとなく想像できましたでしょうか?

2重以上のループは内側から外側のループへと移ります。

これを使って2次元配列をいろいろといじるので動きをイメージできるようにしておきましょう。

左上から右下へ

2次元配列は左上が(0,0)でそこから右下方向に展開します。

たとえば「stage[23][18]」という2次元配列の場合は

画像(jb_2_6)

左上が(0,0)、右下が(22,17)というように左上から右下にかけて展開していきます。

ここで気づかれた方いるかもしれませんが、これを「x,y」の座標で考えた時、普通は(x,y)の順番で座標を表しますが、当サイトでは2次元配列表記に合わせて(y,x)の順番で表記します。

ブロックと壁を表示

それでは長らくお待たせしましたが、ようやくメインテーマの壁とブロックを表示したいと思います。

「0」・・・何もない

「1」・・・ブロック

「9」・・・壁

を頭に入れておきましょう!

よく使う数字は定数化

よく使う数字は定数化してしまいます。

定数っていうのは定まった数という名のとおりプログラム中であまり変更する事のない、またはむやみに変更してはいけない数字になります。

/*ブロックや壁を描く場所x座標・y座標*/
private final int DRAW_POSITION_X = 0;
private final int DRAW_POSITION_Y = 20;

/*ブロックの大きさ縦・横*/
private final int BLOCK_HEIGHT = 4;
private final int BLOCK_WIDTH = 4;

/*ステージの大きさ縦・横*/
private final int STAGE_HEIGHT = 23;
private final int STAGE_WIDTH = 18;

/*描く時のブロック同士の幅*/
private final int DRAW_BLOCK_WIDTH = 20;

ブロックの幅、ステージの幅などはよく登場するので定数化しました。

変数の先頭に「final」というのをつけると変更できない数字になります。

これによって間違えて変更されるのを防止する事ができます。

さらに名前をつける事によってなんの役目の数字なのかわかるし、プログラムの変更が生じた場合でもこの定数を上手に使っていれば最小限の変更ですみます。

こういったテクニックはどんどん活用しましょう。

定数名は大文字でつけるのが、なんとなくのルールになっております。

変数

序盤で使う変数を軽く紹介します。

/*ブロック*/
private int block[][] = new int[BLOCK_HEIGHT][BLOCK_WIDTH];

/*ステージ*/
private int stage[][] = new int[STAGE_HEIGHT][STAGE_WIDTH];

/*元のブロック*/
/*ここにいろいろなブロックを追加して*/
/*ここから読み込んでいくようなカタチになります*/
/*最初は四角いブロックのみ*/
private int blocks[][] = {
	{0,0,0,0},
	{0,1,1,0},
	{0,1,1,0},
	{0,0,0,0}
};

/*ブロックの横位置*/
private int block_x;

/*ブロックの縦位置(内部計算用)*/
private int block_y;

/*ブロックの縦位置(表示用)*/
private float block_y_count;

/*画像*/
private Image img;

ブロックの横位置と縦位置は

↓この左上の部分

0000
0110
0110
0000

の座標になります。

縦位置の内部計算用と表示用に分かれているのはまた後ほど説明いたします。

それでは、いよいよ自作メソッドを紹介します。

初期化

まずは変数などの初期化をしていきます。

void my_init_var(){
	for(int i=0;i<STAGE_HEIGHT;i++){
		for(int j=0;j<STAGE_WIDTH;j++){
			stage[i][0] = 9;
			stage[i][1] = 9;
			stage[i][2] = 9;
			stage[20][j] = 9;
			stage[21][j] = 9;
			stage[22][j] = 9;
			stage[i][15] = 9;
			stage[i][16] = 9;
			stage[i][17] = 9;		
		}
	}

	block_x = 7;
	block_y = 0;
	block_y_count = 0;
	img = new Image("back_img.jpg");
}

「stage」に壁の部分を作るべく左右、下を壁である「9」にします。

上の部分には、はみ出さないように重ねあわせるので壁は作りません。

あとはブロックの横位置、縦位置、色、画像などを設定して初期化は終わりです。

ブロックを登録

次にブロックを登録します。

void my_make_block(){
	for(int y=0;y<BLOCK_HEIGHT;y++){
		for(int x=0;x<BLOCK_WIDTH;x++){
			block[y][x] = blocks[y][x];
		}
	}
}

ブロックの元となる「blocks」からブロックをゲーム進行時のブロックの受け皿である「block」に移し替えます。

最初はわかりやすいように四角いブロックのみで話を進めていきます。

後半にブロックの元となる「blocks」でブロックの種類を増やして、ここでブロックをランダムに発生させていきます。

別々に画面表示

「block」と「stage」を別々に画面に描いていきます。

一緒に描いても良いのですが、あとあとのラインクリア処理などの都合で別々に描きます。

まずはブロックです。

void my_draw_block(){
	for(int y=0;y<BLOCK_HEIGHT;y++){
		for(int x=0;x<BLOCK_WIDTH;x++){
			if(block[y][x] == 1){gc.setFill(Color.RED);
				gc.fillText("■",block_x * DRAW_BLOCK_WIDTH + x * DRAW_BLOCK_WIDTH,
					DRAW_POSITION_Y + block_y_count + y * DRAW_BLOCK_WIDTH);}
		}
	}
}

「block」を調べてブロックの部分「1」が出てきたら「■」に置き換えて表示します。

ちょっとだけ中身がややこしいので、余計な部分を整理して

if(block[y][x] == 1){
	gc.fillText("■",x * DRAW_BLOCK_WIDTH,y * DRAW_BLOCK_WIDTH);
}

これがまず基本になります。

「DRAW_BLOCK_WIDTH」で決めた幅ずつブロックを描いていきます。

これだけでブロックのカタチは描かれますが、ブロックはx方向、y方向に移動しますのでその分を足し合わせます。

if(block[y][x] == 1){
	gc.fillText("■",block_x * DRAW_BLOCK_WIDTH + x * DRAW_BLOCK_WIDTH,
		block_y_count + y * DRAW_BLOCK_WIDTH);
}

「block_x * DRAW_BLOCK_WIDTH」がx方向の移動分、「block_y_count」がy方向の移動分という事ですね。

最後に画面全体のどこに描くかという「DRAW_POSITION_Y」を加えてさきほどのカタチになります。

このブロック移動分などを削除して表示するなりしてみるとわかりやすいかと思います。

続いてステージです。

void my_draw_stage(){
	for(int y=0;y<STAGE_HEIGHT;y++){
		for(int x=0;x<STAGE_WIDTH;x++){
			if(stage[y][x] == 1){gc.setFill(Color.RED);
				gc.fillText("■",x * DRAW_BLOCK_WIDTH,DRAW_POSITION_Y + y * DRAW_BLOCK_WIDTH);}
			else if(stage[y][x] == 9){gc.setFill(Color.WHITE);
				gc.fillText("■",x * DRAW_BLOCK_WIDTH,DRAW_POSITION_Y + y * DRAW_BLOCK_WIDTH);}
		}
	}
}

同じように「stage」を調べて壁の部分「9」が出てきたら「■」に置き換えて表示します。

ブロックの部分である「1」が出てきても置き換えて表示しておりますが、これは固定されたブロックの情報がこの後「stage」にも保存されますのでこのようにしております。

ブロックと壁を表示

後はこれらを変数は変数の場所に、初期化メソッドはコンストラクタに、今作ったメソッド群は自作メソッドの場所に、それぞれ適切に配置、そしてゲームループの場所で今のメソッドを実行すれば・・・。

画像(jb_2_7)

壁とブロックが表示できました!

最初はわかりやすいように全体を描画してますが次回以降は内側の部分だけを描いていきます。

ここまでの中間ソースになります。

中間ソース1

それでは次回は落下させてみたいと思います。

次回

三日目 落下

□ページの先頭へ□

□目次へ戻る□

□HOME□

広告

↓2016年06月25日発売↓

新・明解Java入門 (明解シリーズ)

新品価格
¥2,916から
(2017/5/1 13:47時点)

↓2017年04月18日発売↓

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで

新品価格
¥3,218から
(2017/5/1 13:50時点)

↓2017年05月17日発売↓

新・明解 Javaで学ぶアルゴリズムとデータ構造 (明解シリーズ)

新品価格
¥2,592から
(2017/5/1 13:54時点)

↓2017年04月04日発売↓

Java 第2版 入門編 ゼロからはじめるプログラミング (プログラミング学習シリーズ)

新品価格
¥2,030から
(2017/5/1 13:55時点)