第26回【シューティング制作.6】


まずは動画を見てくださいね!
JavaScriptでシューティング制作です!
敵を表示するにあたって、敵クラスを作るのですが弾クラスを流用する形で新しくベースクラスを作っています。 クラスの継承を使います。
今回は動画と少し順番変わりますけど、おさらいという事で。

スプライトの追加

今回は敵スプライトを追加するためにネットからいくつかキャラクターを頂いてきて一つのイメージファイルにまとめました。
画像ファイルは[コチラ]
スプライトの定義はこちら↓↓
//スプライト
let sprite = [
	new Sprite(  0, 0,22,42 ),//0,自機 左2
	new Sprite( 23, 0,33,42 ),//1,自機 左1
	new Sprite( 57, 0,43,42 ),//2,自機 正面
	new Sprite(101, 0,33,42 ),//3,自機 右1
	new Sprite(135, 0,21,42 ),//4,自機 右2
	
	new Sprite(  0,50, 3, 7 ),//5,弾1
	new Sprite(  4,50, 5, 5 ),//6,弾2
	
	new Sprite(  3,42,16, 5 ),// 7,噴射 左2
	new Sprite( 29,42,21, 5 ),// 8,噴射 左1
	new Sprite( 69,42,19, 5 ),// 9,噴射 正面
	new Sprite(108,42,21, 5 ),//10,噴射 右1
	new Sprite(138,42,16, 5 ),//11,噴射 右2
	
	new Sprite( 11,50, 7, 7 ),//12,敵弾1-1
	new Sprite( 19,50, 7, 7 ),//13,敵弾1-2
	new Sprite( 32,49, 8, 8 ),//14,敵弾2-1
	new Sprite( 42,47,12,12 ),//15,敵弾2-2
	
	new Sprite(  5,351, 9, 9),//16  ,爆発1
	new Sprite( 21,346,20,20),//17  ,爆発2
	new Sprite( 46,343,29,27),//18  ,爆発3
	new Sprite( 80,343,33,30),//19  ,爆発4
	new Sprite(117,340,36,33),//20  ,爆発5
	new Sprite(153,340,37,33),//21  ,爆発6
	new Sprite(191,341,25,31),//22  ,爆発7
	new Sprite(216,349,19,16),//23  ,爆発8
	new Sprite(241,350,15,14),//24  ,爆発9
	new Sprite(259,350,14,13),//25  ,爆発10
	new Sprite(276,351,13,12),//26  ,爆発11
	
	new Sprite(  6,373, 9, 9),//27  ,ヒット1
	new Sprite( 19,371,16,15),//28  ,ヒット2
	new Sprite( 38,373,11,12),//29  ,ヒット3
	new Sprite( 54,372,17,17),//30  ,ヒット4
	new Sprite( 75,374,13,14),//31  ,ヒット5
	
	new Sprite(  4,62,24,27),	//32  ,黄色1
	new Sprite( 36,62,24,27),	//33  ,黄色2
	new Sprite( 68,62,24,27),	//34  ,黄色3
	new Sprite(100,62,24,27),	//35  ,黄色4
	new Sprite(133,62,24,27),	//36  ,黄色5
	new Sprite(161,62,30,27),	//37  ,黄色6
	
	new Sprite(  4,95,24,26),	//38  ,ピンク1
	new Sprite( 36,95,24,26),	//39  ,ピンク2
	new Sprite( 68,95,24,26),	//40  ,ピンク3
	new Sprite(100,95,24,26),	//41  ,ピンク4
	new Sprite(133,92,24,29),	//42  ,ピンク5
	new Sprite(161,95,30,26),	//43  ,ピンク6
	
	new Sprite(  4,125,24,29),	//44  ,青グラサン1
	new Sprite( 36,125,24,29),	//45  ,青グラサン2
	new Sprite( 68,125,24,29),	//46  ,青グラサン3
	new Sprite(100,125,24,29),	//47  ,青グラサン4
	new Sprite(133,124,24,30),	//48  ,青グラサン5
	new Sprite(161,125,30,29),	//49  ,青グラサン6
	
	new Sprite(  4,160,25,27),	//50  ,ロボ1
	new Sprite( 34,160,26,27),	//51  ,ロボ2
	new Sprite( 66,160,26,27),	//52  ,ロボ3
	new Sprite( 98,160,26,27),	//53  ,ロボ4
	new Sprite(132,160,26,27),	//54  ,ロボ5
	new Sprite(161,158,30,29),	//55  ,ロボ6
	
	new Sprite(  4,194,24,28),	//56  ,にわとり1
	new Sprite( 36,194,24,28),	//57  ,にわとり2
	new Sprite( 68,194,24,28),	//58  ,にわとり3
	new Sprite(100,194,24,28),	//59  ,にわとり4
	new Sprite(133,194,24,30),	//60  ,にわとり5
	new Sprite(161,194,30,28),	//61  ,にわとり6
	
	new Sprite(  4,230,22,26),	//62  ,たまご1
	new Sprite( 41,230,22,26),	//63  ,たまご2
	new Sprite( 73,230,22,26),	//64  ,たまご3
	new Sprite(105,230,22,26),	//65  ,たまご4
	new Sprite(137,230,22,26),	//66  ,たまご5
	
	new Sprite(  6,261,24,28),	//67  ,殻帽ヒヨコ1
	new Sprite( 38,261,24,28),	//68  ,殻帽ヒヨコ2
	new Sprite( 70,261,24,28),	//69  ,殻帽ヒヨコ3
	new Sprite(102,261,24,28),	//70  ,殻帽ヒヨコ4
	new Sprite(135,261,24,28),	//71  ,殻帽ヒヨコ5
	
	new Sprite(206, 58,69,73),	//72  ,黄色(中)
	new Sprite(204,134,69,73),	//73  ,ピンク(中)
	new Sprite(205,212,69,78),	//74  ,青グラサン(中)
	
	new Sprite(337,  0,139,147),//75  ,黄色(大)
	new Sprite(336,151,139,147),//76  ,ピンク(大)
	new Sprite(336,301,139,155),//77  ,青グラサン()
];


キャラのベースクラス作成

//キャラクターのベースクラス
class CharaBase
{
	constructor( snum, x,y, vx,vy )
	{
		this.sn   = snum;
		this.x    = x;
		this.y    = y;
		this.vx   = vx;
		this.vy   = vy;
		this.kill = false;
	}
	
	update()
	{
		this.x += this.vx;
		this.y += this.vy;
		
		if( this.x<0 || this.x>FIELD_W<<8 
			|| this.y<0 || this.y>FIELD_H<<8 )this.kill = true;
	}
	
	draw()
	{
		drawSprite( this.sn, this.x , this.y );
	}
}
スプライトのクラスは共通する部分が多いためベースクラスを用意しました。
基本的に全てのスプライトはフレーム毎のアップデート処理updateメソッドと、フレーム毎の描画処理drawメソッドを持っています。
updateメソッドは移動量vx,vyを加算して、範囲外に出たらkillフラグを立てるという処理も共通クラスに実装します。


敵クラスの作成、弾クラスの修正

敵クラス
//敵クラス
class Teki extends CharaBase
{
	constructor( snum,x,y,vx,vy )
	{
		super( snum,x,y,vx,vy );
		//
		//
	}
	
	update()
	{
		super.update();
		
		//
		//
	}
	
	draw()
	{
		super.draw();
	}
}
extendsでCharaBaseクラスを継承しています。
継承元(親クラス)のコンストラクタやメソッドを使う時のsuperを学びましょう!

弾クラス
//弾クラス
class Tama extends CharaBase
{
	constructor( x,y,vx,vy )
	{
		super( 5,x,y,vx,vy );
		//
		//
	}
	
	update()
	{
		super.update();
		
		//
		//
	}
	
	draw()
	{
		super.draw();
	}
}

今の所は敵クラスも弾クラスもほぼ一緒です!

テストに敵を作りメインループで表示させる

...
let teki=[
	new Teki( 39,200<<8,200<<8, 0, 0) )
];
...
	for(let i=teki.length-1;i>=0;i--)
	{
		teki[i].update();
		if(teki[i].kill)teki.splice( i,1 );
	}
...
	for(let i=0;i<teki.length;i++)teki[i].draw();
...
敵は最初に一つ適当な位置で止まってるキャラを作り、メインループ内でtamaと同じ様にtekiのメソッドを呼びます。

デバッグ情報の表示も追加しています。
	...
	con.fillText("Teki:"+teki.length,20,60);
	...


ゲームループ内の整理

update、drawメソッドを呼ぶ部分が無駄なので、関数化する。
//オブジェクトをアップデート
function updateObj( obj )
{
	for(let i=obj.length-1;i>=0;i--)
	{
		obj[i].update();
		if(obj[i].kill)obj.splice( i,1 );
	}
}

//オブジェクトを描画
function drawObj( obj )
{
	for(let i=0;i<obj.length;i++)obj[i].draw();
}

..
..
..
	updateObj(star);
	updateObj(tama);
	updateObj(teki);
	jiki.update();
	...
	...
	drawObj( star );
	drawObj( teki );
	drawObj( tama );
	jiki.draw();
	



ゲームループ内も分割しました。
//移動の処理
function updateAll()
{
	updateObj(star);
	updateObj(tama);
	updateObj(teki);
	jiki.update();
}

//描画の処理
function drawAll()
{
	//描画の処理
	
	vcon.fillStyle="black";
	vcon.fillRect(camera_x,camera_y,SCREEN_W,SCREEN_H);
	
	drawObj( star );
	drawObj( tama );
	jiki.draw();
	drawObj( teki );
	
	// 自機の範囲 0 ~ FIELD_W
	// カメラの範囲 0 ~ (FIELD_W-SCREEN_W)
	
	camera_x = (jiki.x>>8)/FIELD_W * (FIELD_W-SCREEN_W);
	camera_y = (jiki.y>>8)/FIELD_H * (FIELD_H-SCREEN_H);
	
	//仮想画面から実際のキャンバスにコピー
	
	con.drawImage( vcan ,camera_x,camera_y,SCREEN_W,SCREEN_H,
		0,0,CANVAS_W,CANVAS_H);
}

//情報の表示
function putInfo()
{
	if(DEBUG)
	{
		drawCount++;
		if( lastTime +1000 <= Date.now() )
		{
			fps=drawCount;
			drawCount=0;
			lastTime=Date.now();
		}
		
		
		con.font="20px 'Impact'";
		con.fillStyle ="white";
		con.fillText("FPS :"+fps,20,20);
		con.fillText("Tama:"+tama.length,20,40);
		con.fillText("Teki:"+teki.length,20,60);
	}
}

//ゲームループ
function gameLoop()
{
	updateAll();
	drawAll();
	putInfo();
}



drawSpriteバグの修正

スプライト描画の際に、範囲判定のチェックに間違いがあり修正
//スプライトを描画する
function drawSprite( snum, x, y )
{
	let sx = sprite[snum].x;
	let sy = sprite[snum].y;
	let sw = sprite[snum].w;
	let sh = sprite[snum].h;
	
	let px = (x>>8) - sw/2;
	let py = (y>>8) - sh/2;
	
	if( px+sw <camera_x || px >=camera_x+SCREEN_W 
			|| py+sh <camera_y || py >=camera_y+SCREEN_H )return;
	
	vcon.drawImage( spriteImage,sx,sy,sw,sh,px,py,sw,sh);
}


とりあえず、今回は以上で・・・
次回に続くのでした・・・



akichon/あきちょん
Youtubeでプログラミング講座やってるゲーム好きの(元?)プログラマ...
>>open profile...
■Twitter始めました
https://twitter.com/ak1chon
■Youtubeチャンネルはこちら
akichon(Youtube)