JavaScriptでゲーム作り
JavaScriptでシューティングゲームを作りました。
JavaScriptとHTMLはオンラインでもオフラインでもできます。
便利な所はコンパイル(プログラムをコンピュータがわかるように変換すること)がいらない事です。
コンパイルがいらない分、エラーがどこかわかりづらいこともあります。
ストーリー:
確認飛行物体と猫型宇宙生物が攻めてきた。
そんな時頼りになる最新型宇宙戦闘機が迎撃に。
地球の平和はあなたにかかってる。
ゲーム画面:
猫型宇宙生物コニローをマウスの左クリックで迎撃します。
猫型宇宙生物が一定数出た後にボスが出現します。
ボスの攻撃を見事かいくぐるとエンディングです。
HPが0になるか、侵食率が30%になるとゲームオーバーです。
侵食率は敵が登場する度に増えていきます。
撃墜すると減ります。
得点は常時カウントされます。
侵食率の低さに応じて増えます。
敵や敵の弾に当たるとHPが減っていきます。
当たり方によってはダメージが大きくなります。
キャラクター:
プログラム:
結構長くなりました。
音楽や効果音も鳴るようになっています。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>シューティングゲーム</title>
<body>
<canvas id="field" width="500" height="500"></canvas>
<img id="back" src="back.png" style="display: none">
<img id="enemy" src="enemy.png" style="display: none">
<img id="ship" src="ship.png" style="display: none">
<img id="boss" src="boss.png" style="display: none">
</body>
<script>
"use strict";
class Star {
// 星クラス
constructor() {
this.x = Math.random() * 500; // x座標
this.y = Math.random() * 500; // y座標
this.r = Math.random() * 5 + 1; // 半径
}
tick() {
this.y += this.r; // 下に移動
if (this.y > 600) {
// 画面下部にきたら上へ移動
this.y -= 600;
}
drawCircle(this.x, this.y, this.r, "#ffd700");
}
}
class Ship {
// 自機クラス
constructor() {
this.img = document.getElementById("ship");
this.x = 300;
this.y = 500;
this.sx = 0;
this.sy = 0;
}
move(mouseX, mouseY) {
this.sx = (mouseX - this.x) / 10; // マウスx方向
this.sy = (mouseY - this.y) / 10; // マウスy方向
if(this.x > 500){this.x -= 90;} //右端から自機が出ないようにする
if(this.x < 90){this.x += 90;} //左端から自機が出ないようにする
if(this.y > 500){this.y -=90;} //下端から自機が出ないようにする
if(this.y < 90){this.y += 90;} //上端から自機が出ないようにする
}
tick() {
this.x += this.sx; // 速度sx(座標x)
this.y += this.sy; // 速度sy(座標y)
ctx.drawImage(this.img, this.x - 50, this.y - 50);
}
shoot() {
bullets.push(new Bullet(this.x, this.y, 0, -25, true)); // 弾を発射
}
}
class Enemy {
// 敵クラス
constructor() {
if(tekiflag < 50){
this.img = document.getElementById("enemy");
}
if(tekiflag >= 50 && bossflag < 20){
this.img = document.getElementById("boss");
}
this.x = Math.random() * 300 + 100; // x座標
this.y = 0; // y座標
this.sx = Math.random() * 5 - 2.5; // x方向最初の速度
this.sy = Math.random() * 15 + 15; // y方向最初の速度
this.shoot = false;
}
tick() {
this.sy -= 1; // 減速する
this.x += this.sx; // 座標xの速度sx
this.y += this.sy; // 座標yの速度sy
ctx.drawImage(this.img, this.x - 50, this.y - 50);
if (this.shoot == false && this.sy < 0) {
// 速度が上がったところで弾を発射
let theta = Math.atan2(ship.y - this.y, ship.x - this.x);
let sx = Math.cos(theta) * 10;
let sy = Math.sin(theta) * 10;
bullets.push(new Bullet(this.x, this.y, sx, sy, false));
this.shoot = true;
}
}
}
class Bullet {
// 弾のクラス
constructor(x, y, sx, sy, isShip) {
this.x = x;
this.y = y;
this.sx = sx;
this.sy = sy;
this.isShip = isShip; // 自機かどうか判定
}
tick() {
this.x += this.sx;
this.y += this.sy;
drawCircle(this.x, this.y, 10, this.isShip ? "#1e90ff" : "#ff69b4");
}
}
// (x,y)を中心に半径r、色colorの円を描画
function drawCircle(x, y, r, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
function drawScore() { //スコア表示
ctx.font = "25px Arial";
ctx.fillStyle = "#0095ff";
ctx.fillText("Score:" + score, 350, 480);
}
function drawHp() { //HP表示
ctx.font = "25px Arial";
ctx.fillStyle = "#0095ff";
ctx.fillText("HP:" + hp.toFixed(), 50, 480);
}
function Shinsyoku() { //侵食率
ctx.font = "25px Arial";
if(shi < 20){
ctx.fillStyle = "#0095ff";
}
if(shi > 20){ //侵食率20%を超えた場合の色
ctx.fillStyle = "#ff4500";
}
ctx.fillText("侵食率:" + shi +"%", 155, 480);
}
function Ending() {
main.currentTime = 0;
main.volume = 0;
main.pause();
boss.currentTime = 0;
boss.volume = 0;
boss.pause();
ending.volume = 0.3;
ending.play();
setTimeout(() => { clearInterval(timerId); }, 3000); //指定秒後に停止する
ctx.fillStyle = "#ff9000";
ctx.fillText("Mission Complete.Cnguratulations!", 70, 300);
}
let ctx; // 描画コンテキスト
let ship; // 自機
let enemy;
let back; // 背景画像
let count = 0; // 敵出現用カウンタ
let interval = 60; // 敵出現頻度
let timerId; // タイマー
let bullets = []; // 弾のリスト
let enemies = []; // 敵のリスト
let bosses = []; //ボス敵のリスト
const stars = []; // 星のリスト
let score = 0;
let hp = 100;
let shi = 0; //侵食率
let tekiflag = 0; //敵出現のフラグ
let bossflag = 0; //ボス敵出現のフラグ
let gameOver = false;
//BGM
const main = new Audio('amain.mp3'); //メイン
const ending = new Audio('aending.mp3'); //エンディング
const boss = new Audio('aboss.mp3'); //ボス敵
const gameover = new Audio('agameover.mp3'); //ゲームオーバー
const tama = new Audio('atama.mp3'); //弾発射音
//
onload = function () {
ctx = document.getElementById("field").getContext("2d");
ctx.font = "32px 'Times New Roman'";
ship = new Ship(); // 自機オブジェクト
enemy = new Enemy();
back = document.getElementById("back");
window.onpointermove = (e) => {
ship.move(e.clientX, e.clientY); // マウス移動で自機を移動
};
window.onpointerdown = (e) => {
ship.shoot(); // マウスクリックで弾を発射
if(bossflag < 20 && gameOver == false){
tama.volume = 0.2;
tama.currentTime = 0;
tama.play();
}
};
timerId = setInterval(tick, 50); // タイマー開始
for (let i = 0; i < 50; i++) {
stars.push(new Star()); // 星を作成
}
};
function tick() {
count++;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, 500, 500);
ctx.drawImage(back, 0, 0); // 背景描画
stars.forEach((s) => s.tick()); // 星の移動と描画
ship.tick();
drawScore(); //スコア表示
drawHp(); //HPを表示
Shinsyoku(); //侵食率を表示
if(bossflag >= 20){Ending();} //ボス敵が20体出現したらエンディング
if (count % interval == 0) {
if(tekiflag < 50){ //敵出現フラグ、指定数以降出ない
main.volume = 0.3; //メインBGMのボリューム
main.play(); //メインBGMを再生
tekiflag += 1; //敵出現フラグを加える
enemies.push(new Enemy()); // intervalフレームごとに敵を作成
interval = Math.max(5, interval - 5);
}
if(tekiflag >= 50){ //ボス敵出現フラグ、指定数以降出現
main.currentTime = 0; //メインBGMの再生位置を最初にする
main.volume = 0; //メインBGMのボリュームを0に
main.pause(); //メインBGMを停止する
boss.volume = 0.3;
boss.play(); //ボス敵BGMを再生
bossflag += 1;
enemies.push(new Enemy()); // intervalフレームごとにボス敵を作成
interval = Math.max(10, interval - 10);
}
}
enemies.forEach((e) => {
e.tick(); // 敵を移動
if (dist(e, ship) < 30) { //衝突判定:敵との距離
hp -= 20;
if(hp < 0){
hp = 0;
gameOver = true; // HPが0でゲームオーバー
}
}
});
//boss敵
bosses.forEach((e) => {
e.tick(); // 敵を移動
if (dist(e, ship) < 30) { //衝突判定:敵との距離
hp -= 20;
if(hp < 0){
hp = 0;
gameOver = true; // HPが0でゲームオーバー
}
}
});
//boss敵ここまで
bullets.forEach((b) => {
b.tick(); // 弾丸移動
if (!b.isShip && dist(b, ship) < 20) { //衝突判定:弾丸との距離
hp -= 10;
if(hp < 0){
hp = 0;
gameOver = true; // HPが0でゲームオーバー
}
}
});
let eNum = enemies.length;
enemies = enemies.filter((e) => { //敵リストのfilter関数
return !bullets.some((b) => { //弾のsome関数
return b.isShip && dist(e, b) < 50; // 弾丸と敵の衝突判定
});
});
let el=enemies.length;
shi=Math.round(el*100/69); //侵食率
if(shi > 30){gameOver = true;} //侵食率30%でゲームオーバー
score += Math.round((100-shi)/10); //スコア(侵食率の低さで獲得点数が増える)
if (gameOver) { //ゲームオーバーフラグオンでゲームオーバー処理
clearInterval(timerId);
ctx.fillStyle = "#ff9000";
ctx.fillText("GAME is OVER..Try Again", 90, 300); //テキストを表示
main.pause();
boss.pause();
gameover.volume = 0.3;
gameover.currentTime = 0;
gameover.play();
}
}
// 2つのオブジェクト間の距離を求める
function dist(e0, e1) {
return Math.sqrt(
Math.abs(e0.x - e1.x) ** 2 + Math.abs(e0.y - e1.y) ** 2
);
}
</script>
</head>
</html>
HTMLの部分は表示する画像について<body>タグ内に記述しています。
残りは全てJavaScriptで<script>タグ内に記述しています。