走れない代わりに、走るゲームを創るプロジェクト、第9回。
過去のバックナンバーは以下のとおり。
- #1 横移動・ジャンプ動作
- #2 敵・ダメージ・スコア
- #3 ジャンプ動作・敵ランダム出現・ゲームオーバー表示
- #4 走る動作・地平線追加・タップして再開
- #5 タップdeスタート、登場シーン、スピードレベル追加
- #6 ダメージ動作
- #7 背景変更
- #8 地面画像変更
今回は、
- 新たなる敵キャラ
ランダム性が増し、絶妙な難易度になったんじゃないだろうか。
本日の成果物
- スタート・再開:タップ or [H]キー
- ジャンプ:タップ or [J]キー
メットール(従来の敵)とピピ(新敵キャラ)がランダムに変わるように分岐を追加した。
さらにピピは前へ飛んでくるようにスピードを速めるようにした。
また、ピピは飛び越えられる高さやくぐれる高さ、そして微妙に飛び越えられない高さなど、高さがランダムで変わるように設定した。
今までののように飛ぶだけじゃなくて、「飛ばない」という判断もプレイヤーは求められるようになったワケだ。
これを曲者と思うか、面白いと思うかはアナタ次第だ ^^;
代わりに敵が中途半端な位置から出てくるのを止めて、ちゃんと右端から出現するように戻したり。
難易度を下げるために当たり判定を少し小さくしたりしておいた。
どうだ、優しいだろう。
本日のドット絵
ピピ
今回、追加したのは敵キャラの「ピピ」である。
以前、ランニング用メトロノームで使ったドット絵を改良した。
ぜひ再活用したかったので、登場させられ満足している。
あと、これを機にメットールも高画質にリニューアルしておいた。
比べると差は歴然であるw
ロックマンの画像も粗さが目立ってきたので、近いうちにリメイクしたい。
本日のソースコード(JavaScript)
<script> var canvas, g; var characterPosX, characterPosY, characterImage, characterR; var enemyPosX, enemyPosY, enemyImage, enemySpeed, enemyR; var cloudPosX, cloudPosY, cloudImage, cloudSpeed; var cloud2PosX, cloud2PosY, cloud2Image, cloud2Speed; var groundPosX, groundPosY, groundImage, groundSpeed; var speed,acceleration; var score; var scene; var frameCount; var darken; // 画面の暗さ var damageCount; var enemyKbn, scrollSpeed; // シーンを配列に格納 const Scenes = {GameMain: "GameMain", GameOver: "GameOver", GameOpen: "GameOpen", GameDamage: "GameDamage"}; // キャラクター画像を配列に格納 const ImgArray = { CharaRun1: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220211/20220211130115.png" ,CharaRun2: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220213/20220213165305.png" ,CharaRun3: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220213/20220213165308.png" ,CharaJump: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220216/20220216232026.png" ,CharaWarp1: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220216/20220216000325.png" ,CharaWarp2: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220216/20220216000328.png" ,CharaWarp3: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220216/20220216000332.png" ,CharaDamage: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220216/20220216232029.png" ,Enemy1: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220219/20220219225723.png" ,Enemy2_1: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220219/20220219163636.png" ,Enemy2_2: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220219/20220219163639.png" ,Cloud1: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220217/20220217234842.png" ,Cloud2: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220217/20220217234839.png" ,imgGround: "https://cdn-ak.f.st-hatena.com/images/fotolife/y/you_key69/20220219/20220219092721.png" }; onload = function () { // 描画コンテキストの取得 canvas = document.getElementById("gamecanvas"); g = canvas.getContext("2d"); // 初期化 scene = ""; darken = 0; // 入力処理の指定 document.onkeydown = keydown; document.onmousedown = mousedown; // ゲームループの設定(FPS) setInterval("gameloop()", 16); }; // 画面クリックしてスタート function restart() { if (scene == "" || scene == Scenes.GameOver) { scene = Scenes.GameOpen; open(); } } function open() { // キャラクター表示位置 characterPosX = 70; characterPosY = 0; characterImage = new Image(); characterImage.src = ImgArray.CharaWarp1; // 敵 enemyImage = new Image(); enemyImage.src = ""; // 雲の表示位置 cloudPosX = 300; cloudPosY = 142; cloudImage = new Image(); cloudImage.src = ImgArray.Cloud1; // 小 cloud2PosX = 120; cloud2PosY = 71; cloud2Image = new Image(); cloud2Image.src = ImgArray.Cloud2; // 大 // スコア初期化 score = 0; frameCount = 0; darken = 0; damageCount = 0; //enemyKbn = 0; } function init() { // キャラクター表示位置・スピード・半径 characterPosX = 70; characterPosY = 299; characterR = 22; characterImage = new Image(); characterImage.src = ImgArray.CharaRun1; speed = 0; acceleration = 0; // 敵の表示位置・スピード enemyPosX = 630; enemyPosY = 313; enemyR = 17; enemyImage = new Image(); enemyImage.src = ImgArray.Enemy1; //enemySpeed = 5; frameCount = 0; scrollSpeed = 5; enemySpeed = scrollSpeed; scene = Scenes.GameMain; } // キー押下 function keydown(e) { if (e.keyCode == 74) { // [J]押下時 characterJump(); } else if (e.keyCode == 72) { // [H]押下時 restart(); } } // マウスクリック(タップ) function mousedown() { if (scene == Scenes.GameMain) { characterJump(); } } function characterJump() { if (characterPosY == 299) { speed = -16; // Y軸の移動スピード acceleration = 1.2; // 加速度(重力) } } function gameloop() { update(); // キャラクターの移動 draw(); // キャラクターの描画 } function update() { frameCount++; if (scene == Scenes.GameOpen) { // 着地するまで下移動 if (characterPosY <= 280) { characterPosY = characterPosY + 10; frameCount = 0; } else { //着地モーション if (frameCount <= 5 ) { characterImage.src = ImgArray.CharaWarp2; } else if (frameCount <= 10 ) { characterImage.src = ImgArray.CharaWarp1; } else if (frameCount <= 15 ) { characterImage.src = ImgArray.CharaWarp3; } else { characterImage.src = ImgArray.CharaRun1; // ランニングスタート! init(); } } } else if (scene == Scenes.GameMain) { speed = speed + acceleration; characterPosY = characterPosY + speed; if (characterPosY > 299) { characterPosY = 299; // 着地 speed = 0; acceleration = 0; } // 敵が左端まで行ったら戻る //enemyPosX -= enemySpeed; if (enemyPosX < -100) { enemyPosX = 420 + Math.floor(Math.random() * 100); // ランダムに出現 score += 100; // 避けたら100pt加算 scrollSpeed = 5 + Math.floor(score / 1000); // 1000点毎にスピードアップ darken = Math.floor(score / 1000) * -20; // 1000点毎に背景色暗くする // ランダムで敵を変更する enemyKbn = Math.floor(Math.random() * 10); if (enemyKbn < 5) { enemyImage.src = ImgArray.Enemy1; enemyPosY = 313; } else { enemyImage.src = ImgArray.Enemy2_1; // 鳥系は高さもランダム(240~280) enemyPosY = 240 + (Math.random() * 40); } } // 鳥系の敵は羽を羽ばたかせる かつ スピード速い if (enemyImage.src == ImgArray.Enemy2_1 || enemyImage.src == ImgArray.Enemy2_2) { enemySpeed = scrollSpeed + 3; if (frameCount <= 8 ) { enemyImage.src = ImgArray.Enemy2_1; } else if (frameCount <= 16 ) { enemyImage.src = ImgArray.Enemy2_2; } else if (frameCount <= 24 ) { enemyImage.src = ImgArray.Enemy2_1; } else { enemyImage.src = ImgArray.Enemy2_2; } } else { enemySpeed = scrollSpeed; } enemyPosX -= enemySpeed; // 雲の動き(敵の動きと同期) cloudPosX -= scrollSpeed; // 小 if (cloudPosX < -50) { cloudPosX = 420 + Math.floor(Math.random() * 110); // ランダムに出現 } cloud2PosX -= scrollSpeed; // 大 if (cloud2PosX < -50) { cloud2PosX = 420 + Math.floor(Math.random() * 80); // ランダムに出現 } groundPosX -= scrollSpeed; // 地面 if (groundPosX < 513) { groundPosX = 1026; // 左端までいったら戻る } // 走る動作 if (characterPosY == 299) { if (frameCount <= 8 ) { characterImage.src = ImgArray.CharaRun1; } else if (frameCount <= 16 ) { characterImage.src = ImgArray.CharaRun2; } else if (frameCount <= 24 ) { characterImage.src = ImgArray.CharaRun3; } else { characterImage.src = ImgArray.CharaRun2; } } // ジャンプ動作 else { characterImage.src = ImgArray.CharaJump; } // 当たり判定 var diffX = characterPosX - enemyPosX; var diffY = characterPosY - enemyPosY; var distance = Math.sqrt(diffX * diffX + diffY * diffY); if (distance < characterR + enemyR) { enemySpeed = 0; scene = Scenes.GameDamage; } else { // フレームカウントリセット if (frameCount == 32) { frameCount = 0; } } } // ダメージシーン(かつ超過タップによるリセット防止) else if (scene == Scenes.GameDamage) { // 後方へ戻す characterPosX = characterPosX - 1; // 点滅させる damageCount ++; if (damageCount <= 5 || (damageCount > 10 && damageCount <= 15) || (damageCount > 20 && damageCount <= 25)) { characterImage.src = ImgArray.CharaDamage; } else { characterImage.src = ""; } // ゲームオーバーシーンへ if (characterPosX == 0) { scene = Scenes.GameOver; } } } function draw() { // 背景描画 var red = 102 + darken; var green = 187 + darken; var blue = 221 + darken; var rgb = "rgb(" + red + "," + green + "," + blue + ")"; g.fillStyle = rgb; g.fillRect(0, 0, 420, 326); /* g.fillStyle = "rgb(188,153,51)"; g.lineWidth = 3; g.strokeStyle = 'black'; g.strokeRect(0, 326, 420, 34); g.fillRect(0, 326, 420, 34); */ // 初期画面は文言の表示のみ if (scene == "") { g.fillStyle = "rgb(255,255,255)"; g.font = "12pt Arial"; var overLabel = "Click Here or Press [H] to Start"; var overLabelWidth = g.measureText(overLabel).width; g.fillText(overLabel, 210 - overLabelWidth / 2, 220); // 表示文言,位置指定(x,y) // 地面 groundPosX = 1026; groundPosY = 360; groundImage = new Image(); groundImage.src = ImgArray.imgGround; g.drawImage( groundImage, groundPosX - groundImage.width, groundPosY - groundImage.height ); } else { // 背景描画(地面・雲) g.drawImage( groundImage, groundPosX - groundImage.width, groundPosY - groundImage.height ); g.drawImage( cloudImage, cloudPosX - cloudImage.width / 2, cloudPosY - cloudImage.height / 2 ); g.drawImage( cloud2Image, cloud2PosX - cloud2Image.width / 2, cloud2PosY - cloud2Image.height / 2 ); // キャラクタ描画 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) g.fillStyle = "rgb(255,255,255)"; g.font = "12pt Arial"; var overLabel = "Click Here or Press [H] to Restart"; var overLabelWidth = g.measureText(overLabel).width; g.fillText(overLabel, 210 - overLabelWidth / 2, 220); // 表示文言,位置指定(x,y) } } } </script> <canvas id="gamecanvas" width="420" height="360" onclick="restart()"></canvas>
従来の敵キャラ(メットール)はその場から動かなかったので「スクロールスピード=敵キャラのスピード」だったが、こっちに向かってくる敵はそれが通用しなくなった。
よって、メットールの場合はスクロールスピードのままだが、ピピ(新キャラ)の場合はスクロールスピード+3という調整を入れてロックマンに向かってくる様を表現した。
もちろん雲と地面はスクロールスピードを維持し続けている。
今後の展望
やりたかった新キャラの追加参戦が実現できた。
もう1つくらい敵のバリエーションは増やしてもいいかなと思っている。
あと、そんな敵をクリアするためのアイテムとかアクションがあるともっと面白くなりそうだ。
走る、を創ろう。