1. HOME»
  2. プログラミング・Web»
  3. JavaScript»
  4. JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方

JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方

りこ

あれ……アル、山田先生は?

アル

えっ、知らない……さっきまでいたよね……

山田

ここだべ……ふぅ、ちょっと校長先生に呼ばれてたべ……あぁ、怖かったべ

りこ

あ、先生。どうしたの!?

山田

どうやらわたすのクラスの生徒せいとが、こっそり教室きょうしつねこを育ててたらしいべ……学校に猫は連れてきちゃだめだって言ってたべ

アル

へぇ……猫ぐらいいいのにね

山田

ほんとうだべ……

目次
  1. キー入力で操作できるようにしてみよう!
  2. シーンをつくってみよう!
  3. シーンにも、毎フレームごとに呼び出される機能をつけよう!

キー入力で操作できるようにしてみよう!

つづいて、山田先生をキー入力で動かせるようにしましょう!

山田

つづいて、わたすを方向キーで移動いどうできるようにするべよ!

りこ

やったぁ

山田

まず、game.jsファイルに書き加えて、使いたいキーをかんたんに登録とうろくできる機能きのうを作っていくべよ!

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;

		//ゲームに登場する全てのもの(オブジェクト)を入れるための配列
		this.objs = [];

		//ゲームに使用するキーと、そのキーが押されているかどうかを入れるための連想配列
		//例 { up: false, down: false }
		this.input = {};
		//登録されたキーに割り当てられたプロパティ名と、キー名を、関連づけるための連想配列
		//例 { up: "ArrowUp", down: "ArrowDown" }
		this._keys = {};
	} //constructor() 終了

	/**
	 * startメソッドを呼び出すことで、メインループが開始される
	 */
	start() {
		//デフォルトのキーバインドを登録する(使いたいキーを登録する)
		this.keybind( 'up', 'ArrowUp' );
		this.keybind( 'down', 'ArrowDown' );
		this.keybind( 'right', 'ArrowRight' );
		this.keybind( 'left', 'ArrowLeft' );

		//メインループを呼び出す
		this._mainLoop();

		//イベントリスナーをセットする
		this._setEventListener();
	} //start() 終了

	/**
	 * イベントリスナーをセットするためのメソッド
	 */
	_setEventListener() {
		//なにかキーが押されたときと、はなされたときに呼ばれる
		const _keyEvent = e => {
			//デフォルトのイベントを発生させない
			e.preventDefault();
			//_keysに登録された数だけ繰り返す
			for ( let key in this._keys ) {
				//イベントのタイプによって呼び出すメソッドを変える
				switch ( e.type ) {
					case 'keydown' :
						//押されたキーが、登録されたキーの中に存在するとき、inputのそのキーをtrueにする
						if ( e.key === this._keys[key] ) this.input[key] = true;
						break;
					case 'keyup' :
						//押されたキーが、登録されたキーの中に存在するとき、inputのそのキーをfalseにする
						if ( e.key === this._keys[key] ) this.input[key] = false;
						break;
				}
			}
		}
		//なにかキーが押されたとき
		addEventListener( 'keydown', _keyEvent, { passive: false } );
		//キーがはなされたとき
		addEventListener( 'keyup', _keyEvent, { passive: false } );
	} //_setEventListener() 終了

	/**
	 * メインループ
	 */
	_mainLoop() {
		//画家さん(コンテキスト)を呼ぶ
		const ctx = this.canvas.getContext( '2d' );
		//塗りつぶしの色に、黒を指定する
		ctx.fillStyle = '#000000';
		//左上から、画面のサイズまでを、塗りつぶす
		ctx.fillRect( 0, 0, this.canvas.width, this.canvas.height );

		//ゲームに登場する全てのもの(オブジェクト)の数だけ繰り返す
		for ( let i=0; i<this.objs.length; i++ ) {
			//スプライトやテキストなど、すべてのオブジェクトのupdateメソッドを呼び出す
			this.objs[i].update( this.canvas );
		}

		//自分自身(_mainLoop)を呼び出して、ループさせる
		requestAnimationFrame( this._mainLoop.bind( this ) );
	} //_mainLoop() 終了

	/**
	 * オブジェクトをゲームに追加できるようになる、addメソッドを作成
	 *
	 * 引数
	 * obj : 追加したいオブジェクト
	 */
	add( obj ) {
		//this.objs配列の末尾に、objの値を追加
		this.objs.push( obj );
	} //add() 終了

	/**
	 * 使いたいキーを登録できるようになる、keybindメソッドを作成
	 *
	 * 引数
	 * name : キーにつける名前
	 * key : キーコード
	 */
	keybind( name, key ) {
		//キーの名前と、キーコードを関連づける
		this._keys[name] = key;
		//キーが押されているかどうかを入れておく変数に、まずはfalseを代入しておく
		this.input[name] = false;
	} //keybind() 終了

}
山田

これで使いたいキーをかんたんに登録できるんだべ
ちなみに38〜42行目で、上下左右の方向キーをデフォルトで登録しているべよ

りこ

そっか。デフォルトで登録しておけば、ゲームをつくるたびに登録する必要ひつようがなくなるのね!

山田

そして、登録したキーは、28行目で初期化しょきかしたthis.inputがおぼえておいてくれるべよ
この、覚えておいてもらうためのお願いは、112〜124行目に書かれているべよ

アル

keybindメソッドが、キーを登録するためのメソッドだね!

山田

もしほかに登録したいキーがあれば、このkeybindメソッドを呼び出すことでかんたんに登録できるんだべよ

りこ

便利べんりだね!

山田

つぎに、sprite.jsファイルのupdateメソッドから、onenterframeメソッドを呼び出すお願いを書いていくべよ
これは、ゲームクラスのループから、つねに呼び出されるメソッドだべ

js/engine/sprite.js

'use strict'

/**
 * スプライトに関してのクラス
 */
class Sprite {

	/**
	 * 引数
	 * img : 画像ファイルまでのパス
	 * width : 画像の表示する範囲(横幅)
	 * height : 画像の表示する範囲(縦幅)
	 */
	constructor( img, width, height ) {
		//this.imgに、あなたは画像ですよ、と教える
		this.img = new Image();
		//this.img.srcに画像ファイルまでのパスを代入
		this.img.src = img;
		//画像の初期位置
		this.x = this.y = 0;
		//画像を表示する範囲の横幅。引数widthが指定されていない場合、this.widthに32を代入
		this.width = width || 32;
		//画像を表示する範囲の縦幅。引数heightが指定されていない場合、this.heightに32を代入
		this.height = height || 32;
		//何番目の画像を表示するか
		this.frame = 0;
	} //constructor() 終了

	/**Gameクラスのメインループからずっと呼び出され続ける
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	update( canvas ) {
		//画像などを画面に表示するためのメソッドを呼び出す
		this.render( canvas );
		//スプライトを動かしたり、なにかのきっかけでイベントを発生させたりするために使うメソッドを呼び出す
		this.onenterframe();
	} //update() 終了

	/**
	 * 画像などを画面に表示するためのメソッド
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	render( canvas ) {
		//キャンバスの外にスプライトがあるとき、ここでこのメソッドを終了する
		if ( this.x < -1 * this.width || this.x > canvas.width ) return;
		if ( this.y < -1 * this.height || this.y > canvas.height ) return;

		//X,Y方向に、何番目の画像か
		const _frameX = this.frame % ( this.img.width / this.width );
		const _frameY = ~~( this.frame / ( this.img.width / this.width ) );

		//画家さん(コンテキスト)を呼ぶ
		const _ctx = canvas.getContext( '2d' );
		//画家さんに、絵を描いてとお願いする
		_ctx.drawImage(
			this.img,
			this.width * _frameX,
			this.height * _frameY,
			this.width,
			this.height,
			this.x,
			this.y,
			this.width,
			this.height
		);
	} //render() 終了

	/**
	 * 常に呼び出され、スプライトの移動やイベントの発生などに使うメソッド。空なのはオーバーライド(上書き)して使うため
	 */
	onenterframe() {}

}
山田

38行目で、onenterframeメソッドを呼び出しているべよ

アル

その、onenterframeメソッドはどこにあるの?

山田

75行目だべ

りこ

ちょっと待って
75行目のメソッドにはなにも書かれてないよ

山田

ふっふっふ。これはオーバーライドして使うんだべ

アル

オーバーライドって?

山田

メソッドを上書きするんだべよ
では、実際じっさいにこのonenterframeメソッドを使ってみるべ!

js/main.js

'use strict'

//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {

	//変数gameに、あなたはゲームですよ、と教える
	const game = new Game();

	//歩く速さ
	const WALKING_SPEED = 4;

	//変数yamadaに、あなたは山田先生のスプライト画像ですよ、と教える
	const yamada = new Sprite( 'img/yamada.png' );
	//常に呼び出される
	yamada.onenterframe = () => {
		//キーが押されたとき、山田先生が移動する
		if ( game.input.left ) yamada.x -= WALKING_SPEED;
		if ( game.input.right ) yamada.x += WALKING_SPEED;
		if ( game.input.up ) yamada.y -= WALKING_SPEED;
		if ( game.input.down ) yamada.y += WALKING_SPEED;
	} //yamada.onenterframe 終了
	//gameに、山田先生のスプライト画像を表示して、とお願いする
	game.add( yamada );

	//gameに、ゲームをスタートして、とお願いする
	game.start();

} );

では、ブラウザを再読み込みしてみましょう!

自由に移動できる!
アル

やった。山田先生が動いた!

りこ

やっぱり動くと楽しいね!

山田

ふっふっふ。注目ちゅうもくしてほしいのは15行目だべ
変数yamadaはスプライトだべから、Spriteクラスのメソッドを使うことができるんだべよ
そしてonenterframeは、Spriteクラスのメソッドだべ

Spiteクラスではなにも書かれていなかったonenterframeメソッドだべけど、ここでそのメソッドを上書きするんだべ
今回はわたすを操作そうさするためのお願いを、上書きしてるべ

りこ

onenterframeは常に呼び出されるメソッドだから、そこに、キー入力があったときに移動するってお願いを書けば、方向キーが押されるたびに動いてくれるのね!

山田

この上書きすることを、オーバーライドというから覚えておくべよ

りこ

はーい

アル

はーい

シーンをつくってみよう!

つづいて、シーンを作っていきます。

アル

シーンって、映画えいがとかのシーン?

山田

そうだべよ。場面ばめんのことだべ
といっても、今回のゲームはタイトルとメインの部分しかないべから、シーンはこの2つだけだべ

りこ

いまから作るの?

山田

まずはここまでで作った部分を、1つのシーンに追加ついかできるようにしてみるべ
タイトル部分は、もうちょっとあとの項目こうもくで作るから、すこし待っててシーンだべ……ぷぷぷっ

りこ

……

アル

……

りこ

……

アル

しーん……

山田

……はっ、一手いってさきをいかれたべ

りこ

アルまで……もう!

アル

えっ、なんのこと?

山田

まぁとにかく、シーンを作るべよ
まず、engineフォルダに、scene.jsファイル追加ついかするべ

rpg/
|-- img (省略)/
|-- index.html
|-- js/
|   |-- engine/
|   |   |-- game.js
|   |   |-- scene.js (追加)
|   |   `-- sprite.js
|   `-- main.js
`-- sound (省略)/
山田

さらに、index.htmlから呼び出すべ

index.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>くろねこラビリンス</title>
	<style>
		* {
			padding: 0;
			margin: 0;
		}
		canvas {
			display: block;
		}
	</style>
</head>
<body>
	<script src="js/engine/scene.js"></script>
	<script src="js/engine/sprite.js"></script>
	<script src="js/engine/game.js"></script>
	<script src="js/main.js"></script>
</body>
</html>
アル

つぎはscene.jsにいろいろ書いていくんだね

山田

そうだべよ

js/engine/scene.js
'use strict'

/**
 * シーンに関してのクラス
 */
class Scene {

	constructor() {
		this.objs = [];
	} //constructor() 終了

	/**
	 * シーンにオブジェクトを追加するときに呼び出されるメソッド
	 *
	 * 引数
	 * obj : スプライトやテキストなど(オブジェクト)
	 */
	add( obj ) {
		//this.objsの末尾に、objを追加
		this.objs.push( obj );
	} //add() 終了

}
山田

さきほどは、Gameクラスにaddメソッドを作って、そこにオブジェクトを追加していたべな
今度はそれを、Sceneクラスでおこなっているんだべ

りこ

シーンにオブジェクトを追加できるようになったのね
でもそのシーンは、どうやって使うの?

山田

ふっふっふ。Gameクラスをこんなふうにして欲しいんだべ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;

		//ゲームに登場する全てのもの(オブジェクト)を入れるための配列(削除)
		//this.objs = [];(削除)

		//シーンを入れておくための配列
		this.scenes = [];
		//現在のシーンをいれておくためのもの
		this.currentScene;

		//ゲームに使用するキーと、そのキーが押されているかどうかを入れるための連想配列
		//例 { up: false, down: false }
		this.input = {};
		//登録されたキーに割り当てられたプロパティ名と、キー名を、関連づけるための連想配列
		//例 { up: "ArrowUp", down: "ArrowDown" }
		this._keys = {};
	} //constructor() 終了

	/**
	 * startメソッドを呼び出すことで、メインループが開始される
	 */
	start() {
		//デフォルトのキーバインドを登録する(使いたいキーを登録する)
		this.keybind( 'up', 'ArrowUp' );
		this.keybind( 'down', 'ArrowDown' );
		this.keybind( 'right', 'ArrowRight' );
		this.keybind( 'left', 'ArrowLeft' );

		//現在のシーン(currentScene)になにも入っていないときは、scenes[0]を代入
		this.currentScene = this.currentScene || this.scenes[0];

		//メインループを呼び出す
		this._mainLoop();

		//イベントリスナーをセットする
		this._setEventListener();
	} //start() 終了

	/**
	 * イベントリスナーをセットするためのメソッド
	 */
	_setEventListener() {
		//なにかキーが押されたときと、はなされたときに呼ばれる
		const _keyEvent = e => {
			//デフォルトのイベントを発生させない
			e.preventDefault();
			//_keysに登録された数だけ繰り返す
			for ( let key in this._keys ) {
				//イベントのタイプによって呼び出すメソッドを変える
				switch ( e.type ) {
					case 'keydown' :
						//押されたキーが、登録されたキーの中に存在するとき、inputのそのキーをtrueにする
						if ( e.key === this._keys[key] ) this.input[key] = true;
						break;
					case 'keyup' :
						//押されたキーが、登録されたキーの中に存在するとき、inputのそのキーをfalseにする
						if ( e.key === this._keys[key] ) this.input[key] = false;
						break;
				}
			}
		}
		//なにかキーが押されたとき
		addEventListener( 'keydown', _keyEvent, { passive: false } );
		//キーがはなされたとき
		addEventListener( 'keyup', _keyEvent, { passive: false } );
	} //_setEventListener() 終了

	/**
	 * メインループ
	 */
	_mainLoop() {
		//画家さん(コンテキスト)を呼ぶ
		const ctx = this.canvas.getContext( '2d' );
		//塗りつぶしの色に、黒を指定する
		ctx.fillStyle = '#000000';
		//左上から、画面のサイズまでを、塗りつぶす
		ctx.fillRect( 0, 0, this.canvas.width, this.canvas.height );

		//現在のシーンの、ゲームに登場する全てのもの(オブジェクト)の数だけ繰り返す
		for ( let i=0; i<this.currentScene.objs.length; i++ ) {
			//現在のシーンの、すべてのオブジェクトのupdateメソッドを呼び出す
			this.currentScene.objs[i].update( this.canvas );
		}

		//自分自身(_mainLoop)を呼び出して、ループさせる
		requestAnimationFrame( this._mainLoop.bind( this ) );
	} //_mainLoop() 終了

	/**
	 * ゲームにシーンに追加できるようになる、addメソッドを作成
	 *
	 * 引数
	 * scene : 追加したいシーン
	 */
	add( scene ) {
		//引数がSceneのとき、this.scenesの末尾にsceneを追加
		if ( scene instanceof Scene ) this.scenes.push( scene );
		//引数がSceneでなければ、コンソールにエラーを表示
		else console.error( 'Gameに追加できるのはSceneだけだよ!' );
	} //add()終了

	/**
	 * 使いたいキーを登録できるようになる、keybindメソッドを作成
	 *
	 * 引数
	 * name : キーにつける名前
	 * key : キーコード
	 */
	keybind( name, key ) {
		//キーの名前と、キーコードを関連づける
		this._keys[name] = key;
		//キーが押されているかどうかを入れておく変数に、まずはfalseを代入しておく
		this.input[name] = false;
	} //keybind() 終了

}
山田

まず、23〜24行目は、消してしまって欲しいべ
その代わりに、27行目のように、scenesというプロパティを作るんだべ

アル

29行目のcurrentSceneってのは?

山田

現在げんざいのシーンを入れておくためのものだべ
たとえば、タイトルシーンと、メインシーンを作ったとしたら、最初さいしょはタイトルシーンを表示ひょうじさせたいべな
だから最初にタイトルシーンを入れておいて、そのあと、メインシーンに切り替える、というふうに使うんだべ

りこ

50行目は?

山田

現在のシーン、つまりcurrentSceneになにも入ってなかったら、最初に追加したシーンを入れておくんだべよ

りこ

へぇ、じゃあcurrentSceneを指定していわすれちゃっても大丈夫なんだ……
あ、115〜120行目は、ゲームにシーンを追加できるメソッドに書き換えられてるんだね

山田

うむ。さらにここで、ゲームにシーン以外のものを追加しようとしたときに、コンソールにエラーが表示されるようにしてみたべよ
これは、このゲームエンジンを使う人が、使い方を間違えてしまったときのバグをふせぐためだべ

りこ

作ったゲームエンジンを、だれかに使ってもらうときのことも考えてるのね!

山田

では、いま作ったシーンの機能きのうを、実際じっさいに使ってみるべよ
main.jsを、こんなふうに書き換えて欲しいんだべ

js/main.js

'use strict'

//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {

	//変数gameに、あなたはゲームですよ、と教える
	const game = new Game();

	//歩く速さ
	const WALKING_SPEED = 4;

	//変数sceneに、あなたはシーンですよ、と教える
	const scene = new Scene();

	//変数yamadaに、あなたは山田先生のスプライト画像ですよ、と教える
	const yamada = new Sprite( 'img/yamada.png' );
	//常に呼び出される
	yamada.onenterframe = () => {
		//キーが押されたとき、山田先生が移動する
		if ( game.input.left ) yamada.x -= WALKING_SPEED;
		if ( game.input.right ) yamada.x += WALKING_SPEED;
		if ( game.input.up ) yamada.y -= WALKING_SPEED;
		if ( game.input.down ) yamada.y += WALKING_SPEED;
	} //yamada.onenterframe 終了
	//sceneに、山田先生のスプライト画像を追加して、とお願いする
	scene.add( yamada );

	//gameに、シーンを追加して、とお願いする
	game.add( scene );

	//gameに、ゲームをスタートして、とお願いする
	game.start();

} );
山田

13行目で、sceneというシーンを作っているんだべ
26行目で、そのsceneに、わたすのスプライトyamadaを追加して、29行目で、そのsceneをgameに追加してるんだべ

アル

段階だんかいになるんだね

山田

ちなみに、実行結果じっこうけっかはさきほどと変わらないべよ

実行結果は同じで山田先生が自由に動き回れる

シーンにも、毎フレームごとに呼び出される機能をつけよう!

つづいて、シーンにもonenterframeメソッドを作ってみましょう!

山田

さきほど、Spriteクラスにonenterframeメソッドを作ったべな?

りこ

たしか、つねに呼び出されるメソッドだったよね
オーバーライドして、キャラクターの移動いどうのおねがいを書けば、常に移動できるようになる!

山田

そうだべ。オーバーライドは上書きのことだべな

さて、りこちゃん。このonenterframeメソッド、シーンでも使えたら便利べんりだと思わないべか?

りこ

えっと……どうして?

山田

一番最初いちばんさいしょのサンプルゲーム、動いていたのはわたすのようで、実際じっさいは後ろのタイルマップが動いていたべよ……

りこ

あ、山田先生のスプライトのonenterframeメソッドを使ってるのに、実際に動かすのはタイルマップなんだ!

アル

え、それって、なにか問題もんだいがあるの?

山田

問題というわけではないべけど、わたすのスプライトのonenterframeメソッドに、音楽おんがく変更へんこうや、ゴールしたあとのタイトルシーンへの切り替えまで、全部作るべか?

アル

うーん、あまり気持ちよくないね……

山田

でも、シーンのonenterframeメソッドなら?

りこ

あ、すっきりする! たしかに便利ね

山田

そんなわけで、Sceneクラスのonenterframeメソッドを作っていくべよ!

js/engine/scene.js

'use strict'

/**
 * シーンに関してのクラス
 */
class Scene {

	constructor() {
		this.objs = [];
	} //constructor() 終了

	/**
	 * シーンにオブジェクトを追加するときに呼び出されるメソッド
	 *
	 * 引数
	 * obj : スプライトやテキストなど(オブジェクト)
	 */
	add( obj ) {
		//this.objsの末尾に、objを追加
		this.objs.push( obj );
	} //add() 終了

	/**Gameクラスのメインループからずっと呼び出され続ける
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	update( canvas ) {
		//スプライトを動かしたり、なにかのきっかけでイベントを発生させたりするために使うメソッドを呼び出す
		this.onenterframe();
	} //update() 終了

	/**
	 * 常に呼び出され、スプライトの移動やイベントの発生などに使うメソッド。空なのはオーバーライド(上書き)して使うため
	 */
	onenterframe() {}

}
山田

28〜31行目にupdateメソッドを作って、そこから36行目のonenterframeメソッドを呼び出しているべ

りこ

こんどはGameクラスのメインループから、updateメソッドを呼び出さなくっちゃ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;

		//シーンを入れておくための配列
		this.scenes = [];
		//現在のシーンをいれておくためのもの
		this.currentScene;

		//ゲームに使用するキーと、そのキーが押されているかどうかを入れるための連想配列
		//例 { up: false, down: false }
		this.input = {};
		//登録されたキーに割り当てられたプロパティ名と、キー名を、関連づけるための連想配列
		//例 { up: "ArrowUp", down: "ArrowDown" }
		this._keys = {};
	} //constructor() 終了

	/**
	 * startメソッドを呼び出すことで、メインループが開始される
	 */
	start() {
		//デフォルトのキーバインドを登録する(使いたいキーを登録する)
		this.keybind( 'up', 'ArrowUp' );
		this.keybind( 'down', 'ArrowDown' );
		this.keybind( 'right', 'ArrowRight' );
		this.keybind( 'left', 'ArrowLeft' );

		//現在のシーン(currentScene)になにも入っていないときは、scenes[0]を代入
		this.currentScene = this.currentScene || this.scenes[0];

		//メインループを呼び出す
		this._mainLoop();

		//イベントリスナーをセットする
		this._setEventListener();
	} //start() 終了

	/**
	 * イベントリスナーをセットするためのメソッド
	 */
	_setEventListener() {
		//なにかキーが押されたときと、はなされたときに呼ばれる
		const _keyEvent = e => {
			//デフォルトのイベントを発生させない
			e.preventDefault();
			//_keysに登録された数だけ繰り返す
			for ( let key in this._keys ) {
				//イベントのタイプによって呼び出すメソッドを変える
				switch ( e.type ) {
					case 'keydown' :
						//押されたキーが、登録されたキーの中に存在するとき、inputのそのキーをtrueにする
						if ( e.key === this._keys[key] ) this.input[key] = true;
						break;
					case 'keyup' :
						//押されたキーが、登録されたキーの中に存在するとき、inputのそのキーをfalseにする
						if ( e.key === this._keys[key] ) this.input[key] = false;
						break;
				}
			}
		}
		//なにかキーが押されたとき
		addEventListener( 'keydown', _keyEvent, { passive: false } );
		//キーがはなされたとき
		addEventListener( 'keyup', _keyEvent, { passive: false } );
	} //_setEventListener() 終了

	/**
	 * メインループ
	 */
	_mainLoop() {
		//画家さん(コンテキスト)を呼ぶ
		const ctx = this.canvas.getContext( '2d' );
		//塗りつぶしの色に、黒を指定する
		ctx.fillStyle = '#000000';
		//左上から、画面のサイズまでを、塗りつぶす
		ctx.fillRect( 0, 0, this.canvas.width, this.canvas.height );

		//現在のシーンのupdateメソッドを呼び出す
		this.currentScene.update();

		//現在のシーンの、ゲームに登場する全てのもの(オブジェクト)の数だけ繰り返す
		for ( let i=0; i<this.currentScene.objs.length; i++ ) {
			//現在のシーンの、すべてのオブジェクトのupdateメソッドを呼び出す
			this.currentScene.objs[i].update( this.canvas );
		}

		//自分自身(_mainLoop)を呼び出して、ループさせる
		requestAnimationFrame( this._mainLoop.bind( this ) );
	} //_mainLoop() 終了

	/**
	 * ゲームにシーンに追加できるようになる、addメソッドを作成
	 *
	 * 引数
	 * scene : 追加したいシーン
	 */
	add( scene ) {
		//引数がSceneのとき、this.scenesの末尾にsceneを追加
		if ( scene instanceof Scene ) this.scenes.push( scene );
		//引数がSceneでなければ、コンソールにエラーを表示
		else console.error( 'Gameに追加できるのはSceneだけだよ!' );
	} //add()終了

	/**
	 * 使いたいキーを登録できるようになる、keybindメソッドを作成
	 *
	 * 引数
	 * name : キーにつける名前
	 * key : キーコード
	 */
	keybind( name, key ) {
		//キーの名前と、キーコードを関連づける
		this._keys[name] = key;
		//キーが押されているかどうかを入れておく変数に、まずはfalseを代入しておく
		this.input[name] = false;
	} //keybind() 終了

}
山田

97行目で、現在のシーンのupdateメソッドを呼び出しているべ

アル

これでシーンのonenterframeメソッドが使えるんだね

js/main.js

'use strict'

//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {

	//変数gameに、あなたはゲームですよ、と教える
	const game = new Game();

	//歩く速さ
	const WALKING_SPEED = 4;

	//変数sceneに、あなたはシーンですよ、と教える
	const scene = new Scene();

	//変数yamadaに、あなたは山田先生のスプライト画像ですよ、と教える
	const yamada = new Sprite( 'img/yamada.png' );
	//sceneに、山田先生のスプライト画像を追加して、とお願いする
	scene.add( yamada );

	//ループから常に呼び出される
	scene.onenterframe = () => {
		//キーが押されたとき、山田先生が移動する
		if ( game.input.left ) yamada.x -= WALKING_SPEED;
		if ( game.input.right ) yamada.x += WALKING_SPEED;
		if ( game.input.up ) yamada.y -= WALKING_SPEED;
		if ( game.input.down ) yamada.y += WALKING_SPEED;
	} //scene.onenterframe 終了

	//gameに、シーンを追加して、とお願いする
	game.add( scene );

	//gameに、ゲームをスタートして、とお願いする
	game.start();

} );
山田

17〜18行目と20〜27行目は、順番を入れ替えたべ
そして21行目は、sceneのonenterframeメソッドを使うように書き換えたべよ

りこ

実行結果はさっきと同じだね

次のページでは、タイルマップを作っていきます。

このシリーズの一覧はこちら

  1. 小学生からのプログラミング入門。プログラミングってなぁに?
  2. Scratchの使い方と、ゲーム作りの基礎知識を学ぼう! 小学生からのプログラミング入門
  3. Scratchでじゃんけんゲームを作ろう! 小学生からのプログラミング入門
  4. Scratchでシューティングゲームを作ろう! 小学生からのプログラミング入門
  5. Scratchでピアノ鍵盤を作って音を鳴らそう! 小学生からのプログラミング入門
  6. テキストエディタ(Visual Studio Code)をインストールしてみよう! 小学生からのプログラミング入門
  7. Visual Studio Codeを日本語化してみよう! 小学生からのプログラミング入門
  8. JavaScriptでおみくじを作ろう! 小学生からのプログラミング入門
  9. JavaScriptで今月の残り日数を計算してみよう! 小学生からのプログラミング入門
  10. JavaScriptで画像を表示してみよう! 小学生からのプログラミング入門
  11. JavaScriptで画像を移動してみよう! 小学生からのプログラミング入門
  12. 【JavaScript】キー入力でキャラを動かしてみよう! 小学生からのプログラミング入門
  13. 【JavaScript】ファイルを分けて管理してみよう! 小学生からのプログラミング入門
  14. 【JavaScript】オブジェクトを使ってみよう! 小学生からのプログラミング入門
  15. 【JavaScript】ゲームのメインループを作ってみよう! 小学生からのプログラミング入門
  16. 【JavaScript】キャラを決まった間隔ずつ動かす! 小学生からのプログラミング入門
  17. HTML5とCanvasを使ってみよう! 小学生からのプログラミング入門
  18. 【JavaScript】迷路やRPGで使えるマップを作ってみよう! 小学生からのプログラミング入門
  19. 【JavaScript】マップでキャラを動かせるようにしよう! 小学生からのプログラミング入門
  20. 【JavaScript】クラスの概念をしっかりと理解しよう! 小学生からのプログラミング入門
  21. 【JavaScript】プログラム全体をクラスを使って作ってみよう! 小学生からのプログラミング入門
  22. 【JavaScript】文字を表示するクラスを作ってみよう! 小学生からのプログラミング入門
  23. 【JavaScript】改行と一文字ずつ画面に表示する方法! 小学生からのプログラミング入門
  24. 【JavaScript】ノベルゲーム風にキー入力で文字を切り替える方法! 小学生からのプログラミング入門
  25. JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方
  26. webpackを使ってゲームエンジンを作ろう!(JSライブラリの作り方)
  27. WindowsにPythonをインストールしてみよう!小学生からのPython入門
  28. MacにPythonをインストールしてみよう!小学生からのPython入門
  29. Pythonでじゃんけんゲームを作ってみよう!小学生からのPython入門
  30. Pythonのtkinterを使って、ウィンドウを表示してみよう!
  31. Pythonのtkinterで、画像つきのおみくじゲームを作ろう!
オリジナルゲーム.com