【プログラミング】2Dゲーム作ってみた② 「BUMP OF FISH ver2」【Crafty】

この前、「Crafty」で作った「BUMP OF FISH ver1」に改良を加えました。

【プログラミング】2Dゲーム作ってみた①「BUMP OF FISH」【Crafty】
こんにちは、よみあきです。今回は「Crafty」というJavaScriptのゲームエンジンを用いて、簡単な2Dゲーム「BUMP OF FISH ver1」を作ってみました!赤い魚が右からやってるハリセンボンをひたす...

BUMP OF FISH ver2

BUMP OF FISH ver2

何が変わった?

改良点としては「スタート画面の追加」、「スコア1000ごとに難易度アップ」、「クリア画面の追加」の3点です!

スタート画面の追加

「BUMP OF FISH」というタイトルがどん!と表示されて、そのすぐ下にスタートボタンが設置してあります。

スタートボタンを押すことでゲームが開始されます。

スコア1000ごとに難易度アップ

前のままだと、右から流れてくる敵をただただよけるだけでゴールも緩急もなかったので、スコア1000ごとに難易度を上げ、5000でクリアとすることにしました!

結構難しいゲームとなったと思います。

score:1000まで(レベル1)

ver1のときと同じく、右から1匹ずつ敵がy座標とスピードを変えて飛んでくるのを避けます。

score:1000~2000(レベル2)

敵が横の1列になって飛んできます。

score:2000~3000(レベル3)

縦に間隔をあけて、敵が2匹飛んできます。

score:3000~4000(レベル4)

敵の列が縦に間隔をあけて飛んできます。

score:4000~5000(レベル5)

より間隔が狭くなります。

クリア画面の追加

スコアが5000を超えるとクリア画面が表示されるようにしました。

「CLEAR!」という文字が表示され、左から赤い魚の群れが飛んでくるという感じになっています。

コード

コードは以下のようになっています。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <title>BUMP OF FISH</title>
    <script src="crafty-min.js"></script>
    <style type="text/css">
        body{
            padding:0px;
            margin:0px;
        }
		#title{
			font-size:64px;
			position: absolute;
 			height: 50px;
 			width: 500px;
 			top: 30%;
 			left: 53%;
 			transform: translate(-50%,-50%);
		}
		#button1{
			border: 2px solid #333;
  			font-size: 16px;
  			color: #333;
  			font-weight: bold;
  			border-radius: 4px;
  			transition: .4s;
			position: absolute;
 			height: 50px;
 			width: 200px;
 			top: 60%;
 			left: 50%;
 			transform: translate(-50%,-50%);
		}
		#button1:hover{
  			background-color: #333;
  			border-color: #333;
  			color: #FFF;
		}
		#clear{
			font-size:72px;
			font-weight:900;
			position: absolute;
 			height: 100px;
 			width: 500px;
 			top: 50%;
 			left: -30%;
			text-align: center;
 			transform: translate(-50%,-50%);
			background: #333;
            background: -webkit-linear-gradient(top, #f3c5bd 0%,#e86c57 50%,#ea2803 51%,#ff6600 75%,#c72200 100%);
            -webkit-text-fill-color: transparent;
            -webkit-background-clip: text;
		}
	
    </style>
</head>
<body>
    <div id="game"><div id="title">BUMP OF FISH</div>
	<input type="button" value="START" id="button1" onclick="myStart()">
	<div id="clear">CLERA!</div>
	</div>
    <script>
		//表示サイズ、背景色の設定
        Crafty.init(500,350, document.getElementById('game'));
        Crafty.background('#87ceeb');

		//START画面の無効化
		function myStart(){
		document.getElementById("title").style.opacity = "0";
		document.getElementById("button1").disabled = true;
		document.getElementById("button1").style.opacity = "0";
		
		//敵
		Crafty.sprite(64,64,"enemy.png",{enemy:[0,0]});
        var Enemy=Crafty.e('2D, Canvas,enemy, Collision')
            .attr({x:500, y:250,speed:5})
			.collision(10,10,49,10,49,54,10,54)
		//難易度ごとの処理
            .bind('EnterFrame', function() {
                this.x -= this.speed;
                if (this.x < -64) {
					this.x = 500;
					parent.y=Crafty.math.randomNumber(0,300);
					score += 100;
                    var Result=Crafty("Score").text("SCORE: "+score);
					//レベル1
					if(0<=score && score<=1000){
					this.x = 500;
					this.y=parent.y;
					parent.speed=Crafty.math.randomNumber(5,12);
					this.speed=parent.speed;
					};
					//レベル3,4
					if(2000<score && score<=4000){
					parent.y=Crafty.math.randomNumber(0,100);
					if(2000<score && score<=3000){
					this.y=parent.y;
					Chiledgenerate2();
				};
				};
					//レベル5
					if(4000<score && score<=5000){
					parent.y=Crafty.math.randomNumber(0,125);
					};
					if(5100<=score){
					Result=Crafty("Score").text("");
					};
				};
				//レベル2
				if(1000<score && score<=2000){
					if(this.x>100){
					this.y=-300;
					parent.speed=5;
					this.speed=parent.speed;
					 if (Crafty.frame() % 10 == 0){
						 Chiledgenerate1();
					};
					};
					};
				 //レベル4,5
				 if(3000<score && score<=5000){
					if(this.x>100){
					this.y=-300;
					parent.speed=5;
					this.speed=parent.speed;
					if(4000<score){interval=175;}
					 if (Crafty.frame() % 10 == 0){
						 Chiledgenerate1();
						 Chiledgenerate2();
					};
					};
					};
					//クリアの処理
					if(5100<=score){
						parent.y=Crafty.math.randomNumber(0,300);
						parent.speed=Crafty.math.randomNumber(5,12);
						if (Crafty.frame() % 10 == 0){
						Chiledgenerate3();
						document.getElementById("clear").style.left = "50%";
					};
					};
				})
			.flip("X");
		
        // プレイヤー
        Crafty.sprite(64,64,"player.png",{player:[0,0]});
        var Player=Crafty.e('2D, Canvas, player,Gravity, Jumper,Collision')
            .attr({x:50, y:50})
			.jumpSpeed(250)
			.collision(18,20,60,15,60,53,18,44)
		//ハリセンボンとぶつかった時
			.onHit("enemy",function(){
				if(score<5100){
				Crafty.pause();
				Crafty.e('2D, DOM, Text').attr({x:120, y:150, w:400})
                .text("YOU DIED").textColor('#dc143c')
                .textFont({size:'48px', weight:'bold'});
				Crafty.e('2D, DOM, Text').attr({x:120, y:200, w:400})
                .text("Cause of death: Collision with porcupinefish!").textColor('#dc143c')
                .textFont({size:'32px', weight:'bold'});
			};})
		//地面とぶつかった時
			.onHit("floor",function(){
				if(score<5100){
				Crafty.pause();
				Crafty.e('2D, DOM, Text').attr({x:120, y:150, w:400})
                .text("YOU DIED").textColor('#dc143c')
                .textFont({size:'48px', weight:'bold'});
				Crafty.e('2D, DOM, Text').attr({x:80, y:200, w:400})
                .text("Cause of death: Crash!").textColor('#dc143c')
                .textFont({size:'32px', weight:'bold'});
			};})
		//天井にぶつかった時
			.onHit("ceiling",function(){
				if(score<5100){
				Crafty.pause();
				Crafty.e('2D, DOM, Text').attr({x:130, y:150, w:400})
                .text("YOU DIED").textColor('#dc143c')
                .textFont({size:'48px', weight:'bold'});
				Crafty.e('2D, DOM, Text').attr({x:80, y:200, w:400})
                .text("Cause of death: Flying into space!").textColor('#dc143c')
                .textFont({size:'32px', weight:'bold'});
			};})
			.gravity('floor');
		//連続ジャンプの設定
		Player.bind("CheckJumping", function(e) {
        Player.canJump = true;
    });
		var Jump=Crafty.s('Mouse').bind('MouseDown', function(e) {
			Player.jump();
		});
		
        // 地面
        var Floor=Crafty.e('2D, Canvas,floor')
           .attr({x:0, y:450, w:500, h:40});
		
		// 天井
		var Ceiling=Crafty.e('2D, Canvas,ceiling')
           .attr({x:0, y:-140, w:500, h:40});
		
		// スコア
		let score = 0;
        Crafty.e("Score, DOM, 2D, Text")
            .attr({ x:20, y:15, w:280 })
            .textFont({size:'20px', weight:'bold'})
            .textColor('#111')
            .text("SCORE: 0");
		}
		
		//敵の間隔
		let interval=200;
		
		//敵の列
		function Chiledgenerate1(){
					Crafty.e('2D, Canvas,enemy, Collision')
            		.attr({x:500, y:parent.y,speed:parent.speed})
					.collision(10,10,49,10,49,54,10,54)
					.bind('EnterFrame', function() {
                	this.x -= parent.speed;
					if(this.x<-64)
						this.destroy();
					})
					.bind("HitOn",function(){
						if(score<5100){
						Crafty.pause();
						Crafty.e('2D, DOM, Text').attr({x:120, y:150, w:400})
						.text("YOU DIED").textColor('#dc143c')
						.textFont({size:'48px', weight:'bold'});
						Crafty.e('2D, DOM, Text').attr({x:80, y:200, w:400})
						.text("Cause of death: Crash!").textColor('#dc143c')
						.textFont({size:'32px', weight:'bold'});
					};})
					.flip("X");
					};
		
		//縦の敵
		function Chiledgenerate2(){
					Crafty.e('2D, Canvas,enemy, Collision')
            		.attr({x:500, y:parent.y+interval,speed:parent.speed})
					.collision(10,10,49,10,49,54,10,54)
					.bind('EnterFrame', function() {
                	this.x -= parent.speed;
					if(this.x<-64)
						this.destroy();
					})
					.bind("HitOn",function(){
						if(score<5100){
						Crafty.pause();
						Crafty.e('2D, DOM, Text').attr({x:120, y:150, w:400})
						.text("YOU DIED").textColor('#dc143c')
						.textFont({size:'48px', weight:'bold'});
						Crafty.e('2D, DOM, Text').attr({x:80, y:200, w:400})
						.text("Cause of death: Crash!").textColor('#dc143c')
						.textFont({size:'32px', weight:'bold'});
					};})
					.flip("X");
		};
		
		//赤い魚の群れ
		function Chiledgenerate3(){
					Crafty.e('2D, Canvas,player, Collision')
            		.attr({x:-70, y:parent.y,speed:parent.speed})
					.collision(10,10,49,10,49,54,10,54)
					.bind('EnterFrame', function() {
                	this.x += parent.speed;
					if(this.x>500){
						this.destroy();
					}})};
    </script>
</body>

CSS

#title{
			font-size:64px;
			position: absolute;
 			height: 50px;
 			width: 500px;
 			top: 30%;
 			left: 53%;
 			transform: translate(-50%,-50%);
		}
		#button1{
			border: 2px solid #333;
  			font-size: 16px;
  			color: #333;
  			font-weight: bold;
  			border-radius: 4px;
  			transition: .4s;
			position: absolute;
 			height: 50px;
 			width: 200px;
 			top: 60%;
 			left: 50%;
 			transform: translate(-50%,-50%);
		}
		#button1:hover{
  			background-color: #333;
  			border-color: #333;
  			color: #FFF;
		}
		#clear{
			font-size:72px;
			font-weight:900;
			position: absolute;
 			height: 100px;
 			width: 500px;
 			top: 50%;
 			left: -30%;
			text-align: center;
 			transform: translate(-50%,-50%);
			background: #333;
            background: -webkit-linear-gradient(top, #f3c5bd 0%,#e86c57 50%,#ea2803 51%,#ff6600 75%,#c72200 100%);
            -webkit-text-fill-color: transparent;
            -webkit-background-clip: text;
		}

「title」、「button1」、「clear」の3つの要素を配置します。

それぞれ画面の中央に配置して、上下左右の位置を調整してあります。

button1について

【コピペで使える】CSSで作るボタンデザインの「じわっと背景色が変わる」のコードを使わせていただきました。

ホバーでボタンの色が変わります。

clearについて

CSSのグラデーションを文字に使用したデザインの「複数の赤のグラデーション」のコードを使わさせていただきました。

clearはゲームのクリアの後に表示させたいので、初期位置は画面の左外に配置しておきます。

START画面の無効化

function myStart(){
		document.getElementById("title").style.opacity = "0";
		document.getElementById("button1").disabled = true;
		document.getElementById("button1").style.opacity = "0";

ここでゲームの処理をmyStartという関数にまとめて、スタートボタンを押したときにそれが動くようにします。

スタートボタンは押した後、2度押せないように無効にします。「title」「button1」を透明化して見えないようにします。

難易度ごとの処理

敵を生成する関数

Chiledgenerate1:列をなす敵の生成
function Chiledgenerate1(){
	Crafty.e('2D, Canvas,enemy, Collision')
	.attr({x:500, y:parent.y,speed:parent.speed})
	.collision(10,10,49,10,49,54,10,54)
	.bind('EnterFrame', function() {
	this.x -= parent.speed;
	if(this.x<-64)
		this.destroy();
	})
	.bind("HitOn",function(){
		if(score<5100){
		Crafty.pause();
		Crafty.e('2D, DOM, Text').attr({x:120, y:150, w:400})
		.text("YOU DIED").textColor('#dc143c')
		.textFont({size:'48px', weight:'bold'});
		Crafty.e('2D, DOM, Text').attr({x:80, y:200, w:400})
		.text("Cause of death: Crash!").textColor('#dc143c')
		.textFont({size:'32px', weight:'bold'});
	};})
	.flip("X");
	};

これまでの敵の生成とやってることはほぼ同じですが、x座標が画面左外に到達すると、その敵はdestroyされ、消えるようになっています。

基準の敵のy座標parent.yを一定にして、この関数を数フレーム間隔で実行すると敵が一列に生成されます。

Chiledgenerate2:縦の敵の生成

	function Chiledgenerate2(){
		Crafty.e('2D, Canvas,enemy, Collision')
		.attr({x:500, y:parent.y+interval,speed:parent.speed})
		.collision(10,10,49,10,49,54,10,54)
		.bind('EnterFrame', function() {
		this.x -= parent.speed;
		if(this.x<-64)
			this.destroy();
		})
		.bind("HitOn",function(){
			if(score<5100){
			Crafty.pause();
			Crafty.e('2D, DOM, Text').attr({x:120, y:150, w:400})
			.text("YOU DIED").textColor('#dc143c')
			.textFont({size:'48px', weight:'bold'});
			Crafty.e('2D, DOM, Text').attr({x:80, y:200, w:400})
			.text("Cause of death: Crash!").textColor('#dc143c')
			.textFont({size:'32px', weight:'bold'});
		};})
		.flip("X");
};

Chiledgenerate1との違いは、生成される敵のy座標が、基準の敵のy座標parent.yから敵と敵の間隔interval分下の位置から生成されるようになっています。

Chiledgenerate3:赤い魚の群れの生成

	function Chiledgenerate3(){
		Crafty.e('2D, Canvas,player, Collision')
		.attr({x:-70, y:parent.y,speed:parent.speed})
		.collision(10,10,49,10,49,54,10,54)
		.bind('EnterFrame', function() {
		this.x += parent.speed;
		if(this.x>500){
			this.destroy();
		}})};

最後のクリア画面で赤い魚の群れを生成する関数です。

画面左から画面右への移動という点が他の生成の関数との変更点です。

敵が画面外に出たときごとの処理

 .bind('EnterFrame', function() {
                this.x -= this.speed;
                if (this.x < -64) {…}

敵が左画面外に出たときにする処理を書きたいときは、まとめてこのif文中に書きます。

this.x = 500;
					parent.y=Crafty.math.randomNumber(0,300);
					score += 100;
                    var Result=Crafty("Score").text("SCORE: "+score);

これはどのレベルでも行う基本的な処理です。スコアの加算など書いてあります。

y座標を決める乱数はこの後更新することになります。

レベル1
if(0<=score && score<=1000){
					this.x = 500;
					this.y=parent.y;
					parent.speed=Crafty.math.randomNumber(5,12);
					this.speed=parent.speed;
					};

ここはver1のときと同じく、y座標とスピードをある範囲でランダムに決めています。

parent.yとparent.speedという変数は後々たくさん敵を生成するときの基準となるべく作ってあります。

レベル3,4
if(2000<score && score<=4000){
					parent.y=Crafty.math.randomNumber(0,100);
					if(2000<score && score<=3000){
					this.y=parent.y;
					Chiledgenerate2();
				};
	};

ここでレベル3,4での縦に間隔をあけて2匹敵が飛んでくるときの処理を書きます。

最初の間隔はintervalという変数を用いて、interval=200となっているので、上の方の敵は、y座標を0~100でとれるように乱数を決め、parent.yを更新します。

レベル4においては上のことに加えて、Chiledgenerate2を用いて、intervalで設定した間隔で2匹目の魚が出てくるようにします。

レベル5
if(4000<score && score<=5000){
					parent.y=Crafty.math.randomNumber(0,125);
					};
					if(5100<=score){
					Result=Crafty("Score").text("");
					};

レベル4より上下の敵の間隔が狭くなるので、上の方の敵のy座標の乱数の範囲を0~125に変更して、parent.yを更新します。

スコアが5000を超えたらスコアの表示を消すという処理もしています。

毎フレームごとの処理

レベル2
if(1000<score && score<=2000){
					if(this.x>100){
					this.y=-300;
					parent.speed=5;
					this.speed=parent.speed;
					 if (Crafty.frame() % 10 == 0){
						 Chiledgenerate1();
					};
					};
					};

横1列に敵が連なって出てきますが、列ごとの間隔をあけるために、if文で基準の敵のx座標が100より大きいときに行う処理として書きました。

基準の敵は生成される敵と位置がかぶってしまう恐れがあるので、y座標を‐300にして画面外で動いています。これが左画面外に出ると生成される敵のy座標(parent.y)が変更されるようになっています。

スピードは基準の敵、生成される敵の共に5で一定にしてあります。

if文で10フレーム毎にChildgenrate1で敵が列になって生成されます。

レベル4,5
if(3000<score && score<=5000){
		if(this.x>100){
		this.y=-300;
		parent.speed=5;
		this.speed=parent.speed;
		if(4000<score){interval=175;}
		 if (Crafty.frame() % 10 == 0){
			 Chiledgenerate1();
			 Chiledgenerate2();
		};
		};
		};

レベル4と5では2列になって敵が飛んできます。

レベル2のときとやってることはほぼ同じですが、ここではChiledgenerate1とChiledgenerate2を2つ実行しています。

スコアが4000を越えた時、intervalを175にして、列の幅を狭めます。

クリアの処理
if(5100<=score){
	parent.y=Crafty.math.randomNumber(0,300);
	parent.speed=Crafty.math.randomNumber(5,12);
	if (Crafty.frame() % 10 == 0){
	Chiledgenerate3();
	document.getElementById("clear").style.left = "50%";
};
};

スコアが5000を越えるとクリアということで、毎フレーム敵のy座標、スピードをある範囲内でランダムに設定します。

if文で10フレーム毎にChildgenrate3で赤い魚が次々に左から流れてきます。

画面左外にあった「clear!」のテキストを画面中央に移動させています。

やってみて

  • スタート画面は割と簡単に作れるが、リスタート画面は実装するのが難しかった。(出来ませんでした…)
  • スコアごとに難易度を調整することが実践できた。条件分岐の組み合わせに苦戦した。
  • 敵などのオブジェクトをたくさん生成する方法を知れた。フレームごとの処理、ある位置に到達したときの処理などによって、生成するタイミングを制御することができた。

今後の方針

  • 効果音の追加
  • 画面のカスタマイズ
  • 紹介動物の作成
  • スマホ向けの設定の追加

コメント

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