走れない代わりに、走るゲームを創るプロジェクト、第4回。
過去のバックナンバーは以下のとおり。
- #1 横移動・ジャンプ動作
- #2 敵・ダメージ・スコア
- #3 ジャンプ動作・敵ランダム出現・ゲームオーバー表示
今回は主に
- 走るアニメーションの追加
- 地平線の追加
- タップで再開機能
ようやく“走ってる感”が出てきたぞ!
本日の成果物
- STARTボタンで開始
- ゲーム画面クリック(タップ)または[H]キーで再開
とにかく走ってるアクションが反映できたのがウレシイ。
本日のドット絵
今回ロックマンが走っているドット絵は、以前Excelでパラパラ漫画をつくったときの画像をそのまま流用した。
走るロックマン
おさらいではあるが、Excelからpngファイルを抽出するには
- ドット打ちしたセルをコピー
- 形式を画像として貼り付け
- 画像をコピー
- 形式をPNGとして貼り付け
- xlsxをzipにリネーム
- xl
- media
- image*.png
PNG透過
そういえばExcelで作ったpngファイルの透過処理の仕方を紹介していなかった。
透過処理はPEKO STEPさんのWebサービスのお世話になっている。
pngファイルをWebページにドラッグ&ドロップして、透明色にしたい色をクリックするだけ。
実にカンタンでベンリである。
この場をお借りして御礼申し上げたい(あざっす)。
本日のソースコード(JavaScript)
今回のポイントは3枚の走るアクション画像だが、特にいったんすべて配列(ImgArray)に格納してから呼び出す、という知恵を活かせた自分をホメてあげたいとおもう。
<input type="button" onclick="start()" value="START" id="btn_Restart"> <canvas id="gamecanvas" width="420" height="360" onclick="restart()"></canvas> <script> var canvas, g; var characterPosX, characterPosY, characterImage, characterR; var enemyPosX, enemyPosY, enemyImage, enemySpeed, enemyR; var speed,acceleration; var score; var scene; var frameCount; // シーンを配列に格納 const Scenes = {GameMain: "GameMain", GameOver: "GameOver"}; // キャラクター画像を配列に格納 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/20220213/20220213004026.png" }; onload = function () { // 描画コンテキストの取得 canvas = document.getElementById("gamecanvas"); g = canvas.getContext("2d"); // 初期化 //init(); // 入力処理の指定 document.onkeydown = keydown; document.onmousedown = mousedown; // ゲームループの設定(FPS) setInterval("gameloop()", 16); }; // スタートボタン押下 function start() { init(); } // リスタート function restart() { if (scene == Scenes.GameOver) { init(); } } function init() { // キャラクター表示位置・スピード・半径 characterPosX = 0; characterPosY = 300; characterR = 22; characterImage = new Image(); characterImage.src = ImgArray.CharaRun1; speed = 0; acceleration = 0; // 敵の表示位置・スピード enemyPosX = 630; enemyPosY = 308; 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; frameCount = 0; scene = Scenes.GameMain; } // キー押下 function keydown(e) { if (e.keyCode == 74) { // [J]押下時 characterJump(); } else if (e.keyCode == 72) { // [H]押下時 if (scene == Scenes.GameOver) { init(); } } } // マウスクリック(タップ) function mousedown() { if (scene == Scenes.GameMain) { characterJump(); } } function characterJump() { if (characterPosY == 300) { speed = -20; // Y軸の移動スピード acceleration = 1.2; // 加速度(重力) } } function gameloop() { update(); // キャラクターの移動 draw(); // キャラクターの描画 } function update() { frameCount++; 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; } // 敵が左端まで行ったら戻る enemyPosX -= enemySpeed; if (enemyPosX < -100) { enemyPosX = 210 + Math.floor(Math.random() * 210); // ランダムに出現 score += 100; // 避けたら100pt加算 } // 走る動作 if (characterPosY == 300) { if (frameCount <= 10 ) { characterImage.src = ImgArray.CharaRun1; } else if (frameCount <= 20 ) { characterImage.src = ImgArray.CharaRun2; } else if (frameCount <= 30 ) { characterImage.src = ImgArray.CharaRun3; } else { characterImage.src = ImgArray.CharaRun2; } } // ジャンプ動作 else { characterImage.src = ImgArray.CharaJump; } // フレームカウントリセット if (frameCount == 40) { frameCount = 0; } // 当たり判定 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(51,204,153)"; //g.fillStyle = "rgb(153,204,51)"; 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); // キャラクタ描画 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 or Press [H] to Restart"; var overLabelWidth = g.measureText(overLabel).width; g.fillText(overLabel, 210 - overLabelWidth / 2, 220); // 表示文言,位置指定(x,y) } } </script>
あと、地平線を分けるのは単純に、ひとつのキャンバスに長方形を2つ並べる方式で実現した。
これも天才だとおもっている。
しかしながら、とにかく動くことを重視していたせいで、いよいよソースがスパゲッティ(複雑で冗長化)になってきた。
どこかのタイミングでキレイにしなければ。
本日の教材
教材はこれ↑
フレームカウントを増やして、10フレームごとに走るモーションが切り替える。
いや、その考え方はね、分かってたんだけどね…
ちょっと恥ずかしいミスを犯してたんだよね。
本日の学び
今まで走るモーションが実現できなかった原因は、ホントお恥ずかしい話
プログラミングしてるとこういうことって往々にしてある。
大文字と小文字が違ったりね。
なぜか自分だけ気づかなかったりするのだ。
今後の展望
ゲームの骨子ができあがってきたので、だんだん操作性とか画質とかが気になり始めてきた。
STARTボタンを廃止してクリック(またはタップ)で始められるようにするとか。
背景がちゃんと動いてるようにみせる、とか。
やりたいことは尽きない。
走る、を創ろう。