JavaScript:ステージ上に出現したブロックを落とす

JavaScript

パズルゲーム風のマス目をクリックするとそこにブロックキャラが登場する。
ブロックキャラの下に何も無ければ、一定間隔で下に落ちる。素のJavaScriptのみで実践する。

ソースコード

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>OTIMONO(06ブロックを落とす)</title>
	<style>
		*{
			margin: 0;
			padding: 0;
		}
		body{
			margin: 0 50px;
		}
	</style>
	<script src="main.js" type="text/javascript"></script>
</head>
<body>
	<canvas id="canvas" width="512" height="512"></canvas>
</body>
</html>

main.js

// キャンバスでのマウスイベントの状態を表示する
let canvas = null;
let g = null;
let pos = {x:0, y:0};		// キャンバス上のマウス座標
let cpos = {x:0, y:0};		// マウス座標から得られるセル位置
let isMouseDown = false;	// マウスを押しているか?
let dropSpeed = 3;			// 落下速度(10-->1秒, 1-->0.1秒)
let gameTime = {start: 0, end: 0};	// 落下速度計測用

const CELL_SIZE = 64;							// パズルキャラのサイズ(64x64ピクセル)
const ROW_SIZE = COLUMN_SIZE = 512 / CELL_SIZE;	// 行と列に何個並べるか?
const CELL_LENGTH = ROW_SIZE * COLUMN_SIZE;		// パズルキャラの数

// ゲームに使う画像ファイル
const image_files = ["null.png", "alien.png", "apple.png", "dog.png", "obake.png", "ultra.png", "usi.png"];

// 画像オブジェクト(画像ファイルをゲームで利用するため)
let images = [];

// ステージ情報配列
let stageState = new Array(CELL_LENGTH);

/*
 * マウスを動かしている時の処理
 */
const mouseMoveListener = (e) => {
	// オフセット位置:キャンバスがブラウザの左上からどれくらいの位置にあるか?(rect.left, rect.top)
	const rect = e.target.getBoundingClientRect();

	// e.clientXとe.clientYはブラウザ左上からのキャンバスクリック位置なのでオフセット分を引くとキャンバス上の座標が分かる
	pos.x = e.clientX - rect.left;
	pos.y = e.clientY - rect.top;

	// マウス座標からセル位置を求める
	cpos.x = Math.floor(pos.x / CELL_SIZE);
	cpos.y = Math.floor(pos.y / CELL_SIZE);
};

/*
 * マウスボタンを押した時の処理
 */
const mouseDownListener = () => {
	isMouseDown = true;
	// ステージのクリック位置に画像を設定
	const index = Math.floor(Math.random() * (images.length-1)) + 1;	// ランダム
	stageState[cpos.x + cpos.y * COLUMN_SIZE] = index;
	console.log(cpos);
};

/*
 * マウスボタンを離した時の処理
 */
const mouseUpListener = () => {
	isMouseDown = false;
};

/*
 * キャンバスにマウス情報を表示(利用していない)
 */
const drawMouseInfo = ()=> {
	// 表示する文字列を設定
	const mouseDownString = isMouseDown ? "押した!" : "離した!";
	const txtPos = `x: ${pos.x} y:${pos.y} ${mouseDownString}`;

	// マウス情報を表示する
	g.fillStyle = "#555";
	g.fillText(txtPos, 150, canvas.height/2);
}

/*
 * 背景を表示
 */
const drawBackground = ()=> {
	let x = 0, y = 0;	// 描画するセル位置
	let color1 = "pink", color2 = "skyblue";	// 市松模様のステージ色

	for(i=0; i<CELL_LENGTH; i++){
		// 1段下がると先頭の色を入れ替える
		if(i % 8 == 0) [color1, color2] = [color2, color1];
		// 格子状にブロックを表示
		x = i % COLUMN_SIZE;			// x方向セル位置
		y = Math.floor(i / ROW_SIZE);	// Y方向セル位置

		// 色を交互に設定
		bgColor = (i % 2 == 0) ? color1: color2;
		g.fillStyle = bgColor;
		g.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
	}
}

/*
 * カーソルを表示
 */
const drawCursor = ()=> {
	g.fillStyle = "rgba(128, 128, 128, 0.5)";	// グレー色で透過
	g.fillRect(cpos.x * CELL_SIZE, cpos.y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
}

/*
 * ステージを表示
 */
const drawStage = ()=> {
	for(let i=0; i<CELL_LENGTH; i++){
		x = i % COLUMN_SIZE;			// x方向セル位置
		y = Math.floor(i / ROW_SIZE);	// Y方向セル位置

		g.drawImage(images[stageState[i]], x*CELL_SIZE, y*CELL_SIZE);
	}
}

/*
 * 画像ファイル読み込み
 */
const loadImages = async ()=> {
	const images = [];
	const promises = [];

	image_files.forEach((url) =>{
		const promise = new Promise((resolve)=>{
			const img = new Image();
			img.addEventListener("load", ()=>{
				console.log("loaded " + url);
				resolve();	// 読み込み完了通知
			});
			img.src = "./asset/" + url;	// 画像ファイル読み込み開始
			images.push(img);	// 画像オブジェクト配列に格納
		});
		promises.push(promise);
	});
	await Promise.all(promises);
	return images;
}

/*
 * ステージ情報を生成
 */
const createStage = (stage)=> {
	stageState = [];
	stageState = [...stage];	// ステージ情報をコピー(stageStage <-- stage)
	console.log(stageState);
}

/*
 * ブロックを落とす
 */
const dropBlock = ()=> {
	// 落下タイミングチェック
	gameTime.end = new Date().getTime();
	const passed = (gameTime.end - gameTime.start) / 100;	// 10分の1秒に直す

	if(passed <= dropSpeed) return;	// 落下しない

	for(let y=ROW_SIZE-2; y>=0; y--){	// ステージの下方向からチェック
		for(let x=0; x<COLUMN_SIZE; x++){
			const upIndex = y*ROW_SIZE+x;
			const downIndex = (y+1)*ROW_SIZE+x;
			if(stageState[upIndex] != 0 && stageState[downIndex] == 0){
				// 入れ替え
				[ stageState[upIndex], stageState[downIndex] ]= [ stageState[downIndex], stageState[upIndex] ];
			}
		}
	}
	gameTime.start = new Date().getTime();	// 落下タイマー再スタート
}

/*
 * ゲームのメイン処理(まだゲームではない)
 */
const GameMain = () =>{
	// キャンバスクリア
	g.clearRect(0, 0, canvas.width, canvas.height);
	// ブロックを落とす
	dropBlock();
	// ステージ描画
	drawBackground();
	// カーソル描画
	drawCursor();
	// ステージ表示
	drawStage();
	// マウス情報表示
	//drawMouseInfo();
	// フレーム再描画
	requestAnimationFrame(GameMain);
}

/*
 * 起動時の処理
 */
window.addEventListener("load", async ()=>{
	canvas = document.getElementById("canvas");
	g = canvas.getContext("2d");
	g.font = "bold 24px System";	// フォントサイズ・フォント種類 設定

	// マウスイベント設定
	canvas.addEventListener("mousemove", mouseMoveListener, false);
	canvas.addEventListener("mousedown", mouseDownListener, false);
	canvas.addEventListener("mouseup", mouseUpListener, false);

	// 画像ファイルを読み込む
	images = await loadImages(image_files);
	console.log(images);

	// ステージ情報を生成する
	const stage1 = [
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 1, 2, 3, 4, 5, 6, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0
	];
	createStage(stage1);

	// 落下タイマースタート
	gameTime.start = new Date().getTime();

	// ゲームのメイン処理呼び出し(ゲーム開始)
	GameMain();
});

関連記事

コメント

タイトルとURLをコピーしました