あっぷりノート

Fix the Bits | あっぷり工房

旅、ギア、サプリ、マインド、トレーニング ── “走る”は創れる

#3 JavaScriptでアクションゲームを作る │ ジャンプ画像、敵ランダム出現、ゲームオーバー表示

f:id:you_key69:20220213101731j:plain


走れない代わりに、走るゲームを創るプロジェクト、第3回。

過去のバックナンバーは以下のとおり。

今回は主に

  • 敵の出現タイミングをランダム化
  • ジャンプアニメーションの変更
  • ゲームオーバーシーンの追加
のバージョンアップを施した。

温かく見守っていただければ幸いである。


本日の成果物

↓STARTボタンで開始















  • STARTボタンでの開始
  • ジャンプアニメーションの追加
  • 敵の出現タイミングをランダム化
  • ゲームオーバーシーンの追加
  • 無限ジャンプの廃止

無限にジャンプできるバグ技は改修し、なおかつ敵の出現をランダムにしたことで、ゲームの難易度が少しあがったとおもう。

神出鬼没になってしまったので、のちのち修正していければ良い。


ジャンプ時のロックマンの縮尺が小さいのはご愛敬w

キャラクターの動きはぜんぶ同じ縮尺で作り始めるべきだったが、色々加工しすぎて元画像がどれかよくわからなくなってしまったのだ。


走る動作をつくるときにゼロから全部作り直すことにする (`・ω・´)シャキーン

本日のドット絵

ジャンプするロックマン

今回新たにロックマンのジャンプ動作のドット絵を描いた。

f:id:you_key69:20220213101959j:plain

Excelで。


プログラミングで壁にぶち当たってるときにドット打ちをすると、いくぶんか心が休まる気がする。

とりもなおさず写経効果だとおもっている。

本日のソースコード(JavaScript)

<input type="button" onclick="restart()" value="START" id="btn_Restart">
<canvas id="gamecanvas" width="420" height="360"></canvas>

<script>
var canvas, g;
var characterPosX, characterPosY, characterImage, characterR;
var enemyPosX, enemyPosY, enemyImage, enemySpeed, enemyR;
var speed,acceleration;
var score;
var scene;

const Scenes = {GameMain: "GameMain", GameOver: "GameOver"};

onload = function () {
  // 描画コンテキストの取得
  canvas = document.getElementById("gamecanvas");
  g = canvas.getContext("2d");
  
  // 初期化
  // init();
  
  // 入力処理の指定
  document.onkeydown = keydown;
  document.onmousedown = mousedown;
  
  // ゲームループの設定(FPS)
  setInterval("gameloop()", 16);
};

function restart() {
	init();
}

function init() {
	characterPosX = 0;
	characterPosY = 300;
	characterR = 26;
	characterImage = new Image();
	characterImage.src = "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220211/20220211130115.png";
	speed = 0;
	acceleration = 0;

	// 敵の表示位置・スピード
	enemyPosX = 630;
	enemyPosY = 310;
	enemyR = 18;
	enemyImage = new Image();
	enemyImage.src = "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220211/20220211222731.png";
	enemySpeed = 5;
	
	// スコア初期化
	score = 0;
	scene = Scenes.GameMain;
}

function keydown(e) {
	if (e.keyCode == 74) { // [J]押下時
		characterJump();
	}
}
function mousedown() {
	characterJump();
}
function characterJump() {
	if (characterPosY == 300) {
		speed = -20; // Y軸の移動スピード
		acceleration = 1.2; // 加速度(重力)
	}
}

function gameloop() {
	update(); // キャラクターの移動
	draw(); // キャラクターの描画
}

function update() {
	if (scene == Scenes.GameMain) {
		speed = speed + acceleration;
		characterPosY = characterPosY + speed;
		
		// 初期移動 
		if (characterPosX < 70) {
			characterPosX = characterPosX + 5; // 1フレーム毎に2ドット移動
		}
		if (characterPosY > 300) {
			characterPosY = 300; // 着地
			speed = 0;
			acceleration = 0;
		}
		
		// ジャンプ動作
		if (characterPosY == 300) {
			characterImage.src = "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220211/20220211130115.png";
		}
		else {
			characterImage.src = "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220213/20220213004026.png";
		}
		
		// 敵が左端まで行ったら戻る
		enemyPosX -= enemySpeed;
		if (enemyPosX < -100) {
			enemyPosX = 210 + Math.floor(Math.random() * 210); // ランダムに出現
			// enemyPosX = 630;
			score += 100; // 避けたら100pt加算
		}
		
		// 当たり判定
		var diffX = characterPosX - enemyPosX;
		var diffY = characterPosY - enemyPosY;
		var distance = Math.sqrt(diffX * diffX + diffY * diffY);
		if (distance < characterR + enemyR) {
			enemySpeed = 0;
			characterImage.src = "";
			scene = Scenes.GameOver;
		}
	}
}

function draw() {
	// 背景描画
	g.fillStyle = "rgb(153,204,51)";
	g.fillRect(0, 0, 420, 360);
	
	// キャラクタ描画
	g.drawImage(
		characterImage,
		characterPosX - characterImage.width / 2,
		characterPosY - characterImage.height / 2
	);
	
	// 敵描画
	g.drawImage(
		enemyImage,
		enemyPosX - enemyImage.width / 2,
		enemyPosY - enemyImage.height / 2
	);

	// スコア描画
	g.fillStyle = "rgb(255,255,255)";
	g.font = "16pt Arial";
	var scoreLabel = "SCORE : " + score;
	var scoreLabelWidth = g.measureText(scoreLabel).width;
	g.fillText(scoreLabel, 390 - scoreLabelWidth, 40); // 表示文言,位置指定(x,y)

	if (scene == Scenes.GameOver) {

		// ゲームオーバー表示
		g.fillStyle = "rgb(255,255,255)";
		g.font = "24pt Arial";
		var overLabel = "GAME OVER";
		var overLabelWidth = g.measureText(overLabel).width;
		g.fillText(overLabel, 210 - overLabelWidth / 2, 180); // 表示文言,位置指定(x,y)

	}
}
</script>

本日の教材

教材はこれ↑

本日の学び

今回はシーンの定義を勉強。

ゲームのメインシーンとゲームオーバーシーンを分けるのは、単にif文で分けるだけだった。


意外とシンプルなんだな。

今後の展望

前々から「ちゃんと走るアクションを描画したい」云っているが、実は果敢にトライしているにも関わらずなかなか上手くいっていないのである。

そういう試行錯誤が楽しいんだけど、ネタが尽きる前に早めに見通しは立てておきたい。


まだまだ音を出してみたり、スピードを上げてみたりと試してみてみたいことは山積みではあるが。

走る、を創ろう。