さて、見た感じはだいぶゲームらしくなってきたけど、まだやることは半分以上あるんだべ
そうなんだ……ゲーム開発ってたいへんなのね
まだスタートとゴールもないし、壁も通り抜けられちゃうもんね
はやく完成させたいなぁ
ついてこられるべか?
もちろん!
ぼくも!
スプライトを移動させる機能を作ろう!
つづいて、スプライトを移動させる機能を作ってみましょう!
つぎはスプライトを移動するための機能を作っていくべよ!
移動するための機能?
これまで、スプライトの移動は、X座標やY座標に移動したいだけの数値を足す方法だったべ
そうだね
これを、移動速度のプロパティに数値を代入すれば、自動的に移動してくれる、というふうになおしていくべ
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;
//数値によってスプライトを移動させることができる(移動速度)
this.vx = this.vy = 0;
} //constructor() 終了
/**Gameクラスのメインループからずっと呼び出され続ける
*
* 引数
* canvas : 紙(キャンバス)
*/
update( canvas ) {
//画像などを画面に表示するためのメソッドを呼び出す
this.render( canvas );
//スプライトを動かしたり、なにかのきっかけでイベントを発生させたりするために使うメソッドを呼び出す
this.onenterframe();
//スプライトを移動する
this.x += this.vx;
this.y += this.vy;
} //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() {}
}
まず28行目で、移動速度のプロパティを初期化しているべ
さらに、42〜43行目で、スプライトの座標に、移動速度を足しているんだべよ
移動速度に入れる数値によって、スプライトが自動的に動くのね!
あぁ、なるほど!
そして、タイルマップにも同じ機能をつけるべよ
js/engine/tilemap.js
'use strict'
/**
* タイルマップに関するクラス
*/
class Tilemap {
/**
* 引数
* img : 画像ファイルまでのパス
* size : タイルひとつの大きさ(一辺の長さ)
*
* タイルひとつは正方形にする
*/
constructor( img, size ) {
//Imageのインスタンスを作成
this.img = new Image();
//this.img.srcに画像ファイルまでのパスを代入
this.img.src = img;
//画像の初期位置
this.x = this.y = 0;
//数値によってタイルマップを移動させることができる(移動速度)
this.vx = this.vy = 0;
//引数sizeが指定されていない場合、this.sizeに32を代入
this.size = size || 32;
//二次元配列で数値を入力すると、マップをつくることができる
this.data = [];
//タイルマップに重ねるように置きたいタイルを追加できる
this.tiles = [];
} //constructor() 終了
/**
* タイルマップの上にタイルを重ねるように追加できるメソッド
*
* 引数
* tile : 追加したいタイル
*/
add( tile ) {
//引数がTileのとき、this.tilesの末尾にtileを追加
if ( tile instanceof Tile ) this.tiles.push( tile );
//引数がTileでなければ、コンソールにエラーを表示
else console.error( 'Tilemapに追加できるのはTileだけだよ!' );
} //add() 終了
/**Gameクラスのメインループからずっと呼び出され続ける
*
* 引数
* canvas : 紙(キャンバス)
*/
update( canvas ) {
//画像などを画面に表示するためのメソッドを呼び出す
this.render( canvas );
//常に呼び出される、オーバーライド用のメソッドを呼び出す
this.onenterframe();
//タイルマップを移動する
this.x += this.vx;
this.y += this.vy;
//タイルの数だけ繰り返す
for ( let i=0; i<this.tiles.length; i++ ) {
//それぞれのタイルのupdateメソッドを呼び出す
this.tiles[i].update( canvas );
}
} //update() 終了
/**
* Gameクラスのメインループからずっと呼び出され続ける。画像を表示したりするためのメソッド
*
* 引数
* canvas : 紙(キャンバス)
*/
render( canvas ) {
//マップの縦方向の数だけ繰り返す
for (let y=0; y<this.data.length; y++) {
//タイルの縦の位置
const _tileY = this.size * y + this.y;
//タイルが、画面から縦にはみ出しているとき、この下をスキップして、次から繰り返し
if ( _tileY < -1 * this.size || _tileY > canvas.height ) continue;
//マップの横方向の数だけ繰り返す
for (let x=0; x<this.data[y].length; x++) {
//タイルの横の位置
const _tileX = this.size * x + this.x
//タイルが、画面から横にはみ出しているとき、この下をスキップして、次から繰り返し
if ( _tileX < -1 * this.size || _tileX > canvas.width ) continue;
//X方向に、何番目の画像か
const _frameX = this.data[y][x] % ( this.img.width / this.size );
//Y方向に、何番目の画像か
const _frameY = ~~( this.data[y][x] / ( this.img.width / this.size ) );
//画家さん(コンテキスト)を呼ぶ
const _ctx = canvas.getContext( '2d' );
//タイルを表示
_ctx.drawImage(
this.img,
this.size * _frameX,
this.size * _frameY,
this.size,
this.size,
_tileX,
_tileY,
this.size,
this.size
);
}
}
} //render() 終了
/**
* 常に呼び出されるメソッド。空なのはオーバーライド(上書き)して使うため
*/
onenterframe() {}
}
追加したのは22〜23行目、55〜57行目だべ
やってることは、Spriteクラスと同じだべよ
さっそく使ってみましょ
js/main.js
'use strict'
//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {
//変数gameに、あなたはゲームですよ、と教える
const game = new Game();
//マップの作成
const map = [
[11,11,11,11,11,11,11,11,11,11],
[11,10,10,10,10,10,10,10,10,11],
[11, 4, 4, 4, 4, 4, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4,11,11,11,11,10,10, 4,11],
[11, 4,11,10,10,11, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4, 9, 4, 4, 9,10,11, 4,11],
[11, 4, 4, 4, 4, 4, 4,11, 4,11],
[11,11,11,11,11,11,11,11,11,11]
];
//タイルのサイズ
const TILE_SIZE = 32;
//歩く速さ
const WALKING_SPEED = 4;
//変数sceneに、あなたはシーンですよ、と教える
const scene = new Scene();
//変数tilemapに、あなたはタイルマップですよ、と教える
const tilemap = new Tilemap( 'img/tile.png' );
//tilemap.dataに、どんなマップなのか教える
tilemap.data = map;
//マップ全体の位置をずらす
tilemap.x = TILE_SIZE*4 - TILE_SIZE/2;
tilemap.y = TILE_SIZE*3 - TILE_SIZE/2;
//マップを登録する
scene.add( tilemap );
//変数yamadaに、あなたは山田先生のタイルですよ、と教える
const yamada = new Tile( 'img/yamada.png' );
//山田先生を画面の中央に配置
yamada.x = yamada.y = TILE_SIZE*5 - TILE_SIZE/2;
//tilemapに、山田先生のタイルを追加して、とお願いする
tilemap.add( yamada );
//ループから常に呼び出される
scene.onenterframe = () => {
//タイルマップの移動速度に0を代入する
tilemap.vx = tilemap.vy = 0;
//キーが押されたとき、山田先生(マップ)が移動する
if ( game.input.left ) tilemap.vx = WALKING_SPEED;
if ( game.input.right ) tilemap.vx = -1 * WALKING_SPEED;
if ( game.input.up ) tilemap.vy = WALKING_SPEED;
if ( game.input.down ) tilemap.vy = -1 * WALKING_SPEED;
} //scene.onenterframe 終了
//gameに、シーンを追加して、とお願いする
game.add( scene );
//gameに、ゲームをスタートして、とお願いする
game.start();
} );
49〜55行目を変更したべよ
キーが押されたときは、タイルマップの移動速度に、歩くスピードを代入して……あ、左や上の場合はマイナスをかけてるんだ
あれ? 50行目で移動速度に0を代入してるのね……どうして?
こうしなければ、ずっと移動を続けてしまうべよ
あ、そっか。移動速度だから、その移動を止めるお願いが必要なんだ!
よく分かんないよ……なにもキーが押されていないときだけ、0を代入しなくちゃいけないんじゃないの?
ふっふっふ。最初に0を代入して、キー入力があったときだけ移動速度を代入すれば、キーが押されていないときは0のままだべよ
そっか! なにも押されていないときに0を代入する、なんてお願いを、わざわざ書く必要がないんだ!
こういうふうに、書く順番を工夫することも、プログラミングでは大切なんだべ!
キャラクターが、決まった間隔ずつ移動するようにしてみよう!
つづいて、キャラクターの移動を、決まった間隔ずつになるようにしていきましょう!
いまのところ、わたすのタイル……つまりキャラクターは、タイルマップ上のどの位置にでも止まることができるべ
するとここで、じゃあそのキャラクターは、どのタイルの上にいるのか、という話になってしまうべよ
それじゃダメなの?
もちろん、ダメってことはないべ。ドラクエでもタイルとタイルのあいだで止まれたりするべな
でも、体がすこしだけ壁にぶつかったときは自動的に位置をなおしてくれたり、触れてさえいれば宝箱が開けられたり、といった工夫が、ドラクエにはちゃんとされているんだべよ
あっ、たしかに操作しやすいよね!
これが、わずかに壁に触れてるだけで移動できなかったり、ぴったり位置を合わせないと宝箱を開けられなかったりしたら、もうプレイしたくなくなるべ
むむ……そうだよね
そっかぁ。ぴったりタイルに合わせて止まるようにすれば、そういったことが防げるんだ
そういうことだべ!
じゃあ、作っていくべよ!
js/main.js
'use strict'
//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {
//変数gameに、あなたはゲームですよ、と教える
const game = new Game();
//マップの作成
const map = [
[11,11,11,11,11,11,11,11,11,11],
[11,10,10,10,10,10,10,10,10,11],
[11, 4, 4, 4, 4, 4, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4,11,11,11,11,10,10, 4,11],
[11, 4,11,10,10,11, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4, 9, 4, 4, 9,10,11, 4,11],
[11, 4, 4, 4, 4, 4, 4,11, 4,11],
[11,11,11,11,11,11,11,11,11,11]
];
//タイルのサイズ
const TILE_SIZE = 32;
//歩く速さ
const WALKING_SPEED = 4;
//変数sceneに、あなたはシーンですよ、と教える
const scene = new Scene();
//変数tilemapに、あなたはタイルマップですよ、と教える
const tilemap = new Tilemap( 'img/tile.png' );
//tilemap.dataに、どんなマップなのか教える
tilemap.data = map;
//マップ全体の位置をずらす
tilemap.x = TILE_SIZE*4 - TILE_SIZE/2;
tilemap.y = TILE_SIZE*3 - TILE_SIZE/2;
//マップを登録する
scene.add( tilemap );
//変数yamadaに、あなたは山田先生のタイルですよ、と教える
const yamada = new Tile( 'img/yamada.png' );
//山田先生を画面の中央に配置
yamada.x = yamada.y = TILE_SIZE*5 - TILE_SIZE/2;
//tilemapに、山田先生のタイルを追加して、とお願いする
tilemap.add( yamada );
//ループから常に呼び出される
scene.onenterframe = () => {
//タイルマップの位置がタイルのサイズで割り切れるとき
if ( ( tilemap.x - TILE_SIZE/2 ) % TILE_SIZE === 0 && ( tilemap.y - TILE_SIZE/2 ) % TILE_SIZE === 0 ) {
//タイルマップの移動速度に0を代入する
tilemap.vx = tilemap.vy = 0;
//キーが押されたとき、山田先生(マップ)が移動する
if ( game.input.left ) tilemap.vx = WALKING_SPEED;
if ( game.input.right ) tilemap.vx = -1 * WALKING_SPEED;
if ( game.input.up ) tilemap.vy = WALKING_SPEED;
if ( game.input.down ) tilemap.vy = -1 * WALKING_SPEED;
}
} //scene.onenterframe 終了
//gameに、シーンを追加して、とお願いする
game.add( scene );
//gameに、ゲームをスタートして、とお願いする
game.start();
} );
追加したのは49〜50行目と58行目だべ
えっと……移動の部分のプログラムを、タイルマップの位置がタイルのサイズで割り切れるときだけ動くようにしてるんだね
そうか。タイルのサイズで割り切れるときだけ移動速度の値を変えれば、タイルのサイズ分だけスクロールするんだ
そうだべ。ブラウザを読み込みなおすとこうなるべ!
ほんとだ! ちゃんと1まいのタイルの上で止まるよ!
やったね!
スタートとゴールのタイルを、タイルマップに配置してみよう!
つづいて、スタートとゴールのタイルを、タイルマップに追加して、配置してみましょう!
では、スタートとゴールのタイルを、マップに配置したいんだべけど……これにはちょっと工夫が必要だべ
えっ、なんで?
タイルマップは、わたすのタイルに合わせて、位置を調節しているべ
たとえば、わたすの初期位置の、上から3番目、左から2番目のタイルの位置にスタートのタイルの場所を指定しても、ずれてしまうべよ
そっかぁ……ってことは、タイルマップの位置の分、タイルの位置もずらさないといけないよね
そこで、スプライトをずらしてくれる機能を作るべ!
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;
//数値によってスプライトを移動させることができる(移動速度)
this.vx = this.vy = 0;
//スプライトの位置を、数値の分、ずらすことができる
this.shiftX = this.shiftY = 0;
} //constructor() 終了
/**Gameクラスのメインループからずっと呼び出され続ける
*
* 引数
* canvas : 紙(キャンバス)
*/
update( canvas ) {
//画像などを画面に表示するためのメソッドを呼び出す
this.render( canvas );
//スプライトを動かしたり、なにかのきっかけでイベントを発生させたりするために使うメソッドを呼び出す
this.onenterframe();
//スプライトを移動する
this.x += this.vx;
this.y += this.vy;
} //update() 終了
/**
* 画像などを画面に表示するためのメソッド
*
* 引数
* canvas : 紙(キャンバス)
*/
render( canvas ) {
//キャンバスの外にスプライトがあるとき、ここでこのメソッドを終了する
if ( this.x + this.shiftX < -1 * this.width || this.x + this.shiftX > canvas.width ) return;
if ( this.y + this.shiftY < -1 * this.height || this.y + this.shiftY > 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.shiftX,
this.y + this.shiftY,
this.width,
this.height
);
} //render() 終了
/**
* 常に呼び出され、スプライトの移動やイベントの発生などに使うメソッド。空なのはオーバーライド(上書き)して使うため
*/
onenterframe() {}
}
まず、30行目で、スプライトの位置をずらすためのプロパティを初期化しているんだべ
shiftXやshiftYってのだね
そして56〜57行目は、キャンバスの外にスプライトが出てしまったときの計算を、ずらした分も考えるようになおしたべ
X座標やY座標に、shiftXやshiftYの値を足しているのね
さらに72〜73行目では、shiftXやshiftYの値を、X座標とY座標に足して、位置をずらしているんだべよ
これでスプライトの基準の位置をずらすことができるね
そうなんだべよ。
そしてTileクラスはSpriteクラスを継承しているべから、タイルでも同じ機能が使えるようになったべ
こんどは、タイルの基準の位置を、タイルマップの位置にするんだべ
js/engine/tilemap.js
'use strict'
/**
* タイルマップに関するクラス
*/
class Tilemap {
/**
* 引数
* img : 画像ファイルまでのパス
* size : タイルひとつの大きさ(一辺の長さ)
*
* タイルひとつは正方形にする
*/
constructor( img, size ) {
//Imageのインスタンスを作成
this.img = new Image();
//this.img.srcに画像ファイルまでのパスを代入
this.img.src = img;
//画像の初期位置
this.x = this.y = 0;
//数値によってタイルマップを移動させることができる(移動速度)
this.vx = this.vy = 0;
//引数sizeが指定されていない場合、this.sizeに32を代入
this.size = size || 32;
//二次元配列で数値を入力すると、マップをつくることができる
this.data = [];
//タイルマップに重ねるように置きたいタイルを追加できる
this.tiles = [];
} //constructor() 終了
/**
* タイルマップの上にタイルを重ねるように追加できるメソッド
*
* 引数
* tile : 追加したいタイル
*/
add( tile ) {
//引数がTileのとき、this.tilesの末尾にtileを追加
if ( tile instanceof Tile ) this.tiles.push( tile );
//引数がTileでなければ、コンソールにエラーを表示
else console.error( 'Tilemapに追加できるのはTileだけだよ!' );
} //add() 終了
/**Gameクラスのメインループからずっと呼び出され続ける
*
* 引数
* canvas : 紙(キャンバス)
*/
update( canvas ) {
//画像などを画面に表示するためのメソッドを呼び出す
this.render( canvas );
//常に呼び出される、オーバーライド用のメソッドを呼び出す
this.onenterframe();
//タイルマップを移動する
this.x += this.vx;
this.y += this.vy;
//タイルの数だけ繰り返す
for ( let i=0; i<this.tiles.length; i++ ) {
//タイルマップの位置の分、それぞれのタイルの位置をずらす
this.tiles[i].shiftX = this.x;
this.tiles[i].shiftY = this.y;
//それぞれのタイルのupdateメソッドを呼び出す
this.tiles[i].update( canvas );
}
} //update() 終了
/**
* Gameクラスのメインループからずっと呼び出され続ける。画像を表示したりするためのメソッド
*
* 引数
* canvas : 紙(キャンバス)
*/
render( canvas ) {
//マップの縦方向の数だけ繰り返す
for (let y=0; y<this.data.length; y++) {
//タイルの縦の位置
const _tileY = this.size * y + this.y;
//タイルが、画面から縦にはみ出しているとき、この下をスキップして、次から繰り返し
if ( _tileY < -1 * this.size || _tileY > canvas.height ) continue;
//マップの横方向の数だけ繰り返す
for (let x=0; x<this.data[y].length; x++) {
//タイルの横の位置
const _tileX = this.size * x + this.x
//タイルが、画面から横にはみ出しているとき、この下をスキップして、次から繰り返し
if ( _tileX < -1 * this.size || _tileX > canvas.width ) continue;
//X方向に、何番目の画像か
const _frameX = this.data[y][x] % ( this.img.width / this.size );
//Y方向に、何番目の画像か
const _frameY = ~~( this.data[y][x] / ( this.img.width / this.size ) );
//画家さん(コンテキスト)を呼ぶ
const _ctx = canvas.getContext( '2d' );
//タイルを表示
_ctx.drawImage(
this.img,
this.size * _frameX,
this.size * _frameY,
this.size,
this.size,
_tileX,
_tileY,
this.size,
this.size
);
}
}
} //render() 終了
/**
* 常に呼び出されるメソッド。空なのはオーバーライド(上書き)して使うため
*/
onenterframe() {}
}
そして、スタートとゴールのタイルを配置するべ!
js/main.js
'use strict'
//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {
//変数gameに、あなたはゲームですよ、と教える
const game = new Game();
//マップの作成
const map = [
[11,11,11,11,11,11,11,11,11,11],
[11,10,10,10,10,10,10,10,10,11],
[11, 4, 4, 4, 4, 4, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4,11,11,11,11,10,10, 4,11],
[11, 4,11,10,10,11, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4, 9, 4, 4, 9,10,11, 4,11],
[11, 4, 4, 4, 4, 4, 4,11, 4,11],
[11,11,11,11,11,11,11,11,11,11]
];
//タイルのサイズ
const TILE_SIZE = 32;
//歩く速さ
const WALKING_SPEED = 4;
//変数sceneに、あなたはシーンですよ、と教える
const scene = new Scene();
//変数tilemapに、あなたはタイルマップですよ、と教える
const tilemap = new Tilemap( 'img/tile.png' );
//tilemap.dataに、どんなマップなのか教える
tilemap.data = map;
//マップ全体の位置をずらす
tilemap.x = TILE_SIZE*4 - TILE_SIZE/2;
tilemap.y = TILE_SIZE*3 - TILE_SIZE/2;
//マップを登録する
scene.add( tilemap );
//変数startに、あなたはスタートのタイルですよ、と教える
const start = new Tile( 'img/start.png' );
//マップ左上からの座標を指定する
start.x = TILE_SIZE;
start.y = TILE_SIZE*2;
//スタートのタイルを、tilemapに追加して、とお願いする
tilemap.add( start );
//変数goalに、あなたはゴールのタイルですよ、と教える
const goal = new Tile( 'img/goal.png' );
//マップ左上からの座標を指定する
goal.x = TILE_SIZE*8;
goal.y = TILE_SIZE*8;
//ゴールのタイルを、tilemapに追加して、とお願いする
tilemap.add( goal );
//変数yamadaに、あなたは山田先生のタイルですよ、と教える
const yamada = new Tile( 'img/yamada.png' );
//山田先生を画面の中央に配置
yamada.x = yamada.y = TILE_SIZE*5 - TILE_SIZE/2;
//tilemapに、山田先生のタイルを追加して、とお願いする
tilemap.add( yamada );
//ループから常に呼び出される
scene.onenterframe = () => {
//タイルマップの位置がタイルのサイズで割り切れるとき
if ( ( tilemap.x - TILE_SIZE/2 ) % TILE_SIZE === 0 && ( tilemap.y - TILE_SIZE/2 ) % TILE_SIZE === 0 ) {
//タイルマップの移動速度に0を代入する
tilemap.vx = tilemap.vy = 0;
//キーが押されたとき、山田先生(マップ)が移動する
if ( game.input.left ) tilemap.vx = WALKING_SPEED;
if ( game.input.right ) tilemap.vx = -1 * WALKING_SPEED;
if ( game.input.up ) tilemap.vy = WALKING_SPEED;
if ( game.input.down ) tilemap.vy = -1 * WALKING_SPEED;
}
} //scene.onenterframe 終了
//gameに、シーンを追加して、とお願いする
game.add( scene );
//gameに、ゲームをスタートして、とお願いする
game.start();
} );
40〜46行目、48〜54行目で、スタートとゴールのタイルを、タイルマップに追加してるのね。ちゃんとできてるかなぁ……
ブラウザを再読み込みするべ……けど……
あれ、先生のタイル、位置がずれちゃってる……
ほんとだ……せっかく真ん中に表示されてたのに……
タイルマップとタイルが一緒に動くようにしたべから、わたすのタイルまでもが、一緒に動いてしまってるんだべ……
そんなぁ
でも大丈夫だべ
タイルマップと一緒に動かないタイルも作れるようにすればいいんだべ!
タイルマップと一緒に動かない、固定されたタイルをつくろう!
つづいて、タイルマップに追加しても一緒に動かない、固定されたタイルを作れるようにしていきましょう!
さて、さきほどはわたすのタイルが、マップと一緒に動いてしまったべ
つぎはそれをなおしていくべよ!
やったぁ
ではtile.jsに、このように追加するべ
js/engine/tile.js
'use strict'
/**
* タイルに関してのクラス
*/
class Tile extends Sprite {
/**
* 引数
* img : 画像ファイルまでのパス
* size : タイルの大きさ
*/
constructor( img, size ) {
//親クラスのコンストラクタを呼び出す
super( img, size, size );
//引数sizeが指定されていない場合、this.sizeに32を代入
this.size = size || 32;
//タイルマップと同期して動くかどうか
this.isSynchronize = true;
} //constructor() 終了
}
19行目で、タイルマップと一緒に動くかどうかのプロパティを作っているべ
初期値はtrueだべから、なにも指定しなければ一緒に動く、というふうになるべ
逆に、isSynchronizeにfalseを指定すれば、固定されたタイルになるのね
そういうことだべ
さらにtilemap.jsで、上に重ねるタイルの位置を、isSynchronizeがtrueかfalseかによって切り替えるべ
js/engine/tilemap.js
'use strict'
/**
* タイルマップに関するクラス
*/
class Tilemap {
/**
* 引数
* img : 画像ファイルまでのパス
* size : タイルひとつの大きさ(一辺の長さ)
*
* タイルひとつは正方形にする
*/
constructor( img, size ) {
//Imageのインスタンスを作成
this.img = new Image();
//this.img.srcに画像ファイルまでのパスを代入
this.img.src = img;
//画像の初期位置
this.x = this.y = 0;
//数値によってタイルマップを移動させることができる(移動速度)
this.vx = this.vy = 0;
//引数sizeが指定されていない場合、this.sizeに32を代入
this.size = size || 32;
//二次元配列で数値を入力すると、マップをつくることができる
this.data = [];
//タイルマップに重ねるように置きたいタイルを追加できる
this.tiles = [];
} //constructor() 終了
/**
* タイルマップの上にタイルを重ねるように追加できるメソッド
*
* 引数
* tile : 追加したいタイル
*/
add( tile ) {
//引数がTileのとき、this.tilesの末尾にtileを追加
if ( tile instanceof Tile ) this.tiles.push( tile );
//引数がTileでなければ、コンソールにエラーを表示
else console.error( 'Tilemapに追加できるのはTileだけだよ!' );
} //add() 終了
/**Gameクラスのメインループからずっと呼び出され続ける
*
* 引数
* canvas : 紙(キャンバス)
*/
update( canvas ) {
//画像などを画面に表示するためのメソッドを呼び出す
this.render( canvas );
//常に呼び出される、オーバーライド用のメソッドを呼び出す
this.onenterframe();
//タイルマップを移動する
this.x += this.vx;
this.y += this.vy;
//タイルの数だけ繰り返す
for ( let i=0; i<this.tiles.length; i++ ) {
//タイルとタイルマップの位置を同期させるとき
if ( this.tiles[i].isSynchronize ) {
//タイルマップの位置の分、それぞれのタイルの位置をずらす
this.tiles[i].shiftX = this.x;
this.tiles[i].shiftY = this.y;
}
//それぞれのタイルのupdateメソッドを呼び出す
this.tiles[i].update( canvas );
}
} //update() 終了
/**
* Gameクラスのメインループからずっと呼び出され続ける。画像を表示したりするためのメソッド
*
* 引数
* canvas : 紙(キャンバス)
*/
render( canvas ) {
//マップの縦方向の数だけ繰り返す
for (let y=0; y<this.data.length; y++) {
//タイルの縦の位置
const _tileY = this.size * y + this.y;
//タイルが、画面から縦にはみ出しているとき、この下をスキップして、次から繰り返し
if ( _tileY < -1 * this.size || _tileY > canvas.height ) continue;
//マップの横方向の数だけ繰り返す
for (let x=0; x<this.data[y].length; x++) {
//タイルの横の位置
const _tileX = this.size * x + this.x
//タイルが、画面から横にはみ出しているとき、この下をスキップして、次から繰り返し
if ( _tileX < -1 * this.size || _tileX > canvas.width ) continue;
//X方向に、何番目の画像か
const _frameX = this.data[y][x] % ( this.img.width / this.size );
//Y方向に、何番目の画像か
const _frameY = ~~( this.data[y][x] / ( this.img.width / this.size ) );
//画家さん(コンテキスト)を呼ぶ
const _ctx = canvas.getContext( '2d' );
//タイルを表示
_ctx.drawImage(
this.img,
this.size * _frameX,
this.size * _frameY,
this.size,
this.size,
_tileX,
_tileY,
this.size,
this.size
);
}
}
} //render() 終了
/**
* 常に呼び出されるメソッド。空なのはオーバーライド(上書き)して使うため
*/
onenterframe() {}
}
追加したのは、61〜62行目と66行目だべ
あ、そっか。isSynchronizeがtrueのときだけ、タイルの位置をずらしてるんだね
じゃあ山田先生のタイルのisSynchronizeに、falseを入れてあげればいいのね!
js/main.js
'use strict'
//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {
//変数gameに、あなたはゲームですよ、と教える
const game = new Game();
//マップの作成
const map = [
[11,11,11,11,11,11,11,11,11,11],
[11,10,10,10,10,10,10,10,10,11],
[11, 4, 4, 4, 4, 4, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4,11,11,11,11,10,10, 4,11],
[11, 4,11,10,10,11, 4, 4, 4,11],
[11, 4,11, 4, 4,11,11,11, 4,11],
[11, 4, 9, 4, 4, 9,10,11, 4,11],
[11, 4, 4, 4, 4, 4, 4,11, 4,11],
[11,11,11,11,11,11,11,11,11,11]
];
//タイルのサイズ
const TILE_SIZE = 32;
//歩く速さ
const WALKING_SPEED = 4;
//変数sceneに、あなたはシーンですよ、と教える
const scene = new Scene();
//変数tilemapに、あなたはタイルマップですよ、と教える
const tilemap = new Tilemap( 'img/tile.png' );
//tilemap.dataに、どんなマップなのか教える
tilemap.data = map;
//マップ全体の位置をずらす
tilemap.x = TILE_SIZE*4 - TILE_SIZE/2;
tilemap.y = TILE_SIZE*3 - TILE_SIZE/2;
//マップを登録する
scene.add( tilemap );
//変数startに、あなたはスタートのタイルですよ、と教える
const start = new Tile( 'img/start.png' );
//マップ左上からの座標を指定する
start.x = TILE_SIZE;
start.y = TILE_SIZE*2;
//スタートのタイルを、tilemapに追加して、とお願いする
tilemap.add( start );
//変数goalに、あなたはゴールのタイルですよ、と教える
const goal = new Tile( 'img/goal.png' );
//マップ左上からの座標を指定する
goal.x = TILE_SIZE*8;
goal.y = TILE_SIZE*8;
//ゴールのタイルを、tilemapに追加して、とお願いする
tilemap.add( goal );
//変数yamadaに、あなたは山田先生のタイルですよ、と教える
const yamada = new Tile( 'img/yamada.png' );
//山田先生を画面の中央に配置
yamada.x = yamada.y = TILE_SIZE*5 - TILE_SIZE/2;
//タイルマップの動きと同期させない
yamada.isSynchronize = false;
//tilemapに、山田先生のタイルを追加して、とお願いする
tilemap.add( yamada );
//ループから常に呼び出される
scene.onenterframe = () => {
//タイルマップの位置がタイルのサイズで割り切れるとき
if ( ( tilemap.x - TILE_SIZE/2 ) % TILE_SIZE === 0 && ( tilemap.y - TILE_SIZE/2 ) % TILE_SIZE === 0 ) {
//タイルマップの移動速度に0を代入する
tilemap.vx = tilemap.vy = 0;
//キーが押されたとき、山田先生(マップ)が移動する
if ( game.input.left ) tilemap.vx = WALKING_SPEED;
if ( game.input.right ) tilemap.vx = -1 * WALKING_SPEED;
if ( game.input.up ) tilemap.vy = WALKING_SPEED;
if ( game.input.down ) tilemap.vy = -1 * WALKING_SPEED;
}
} //scene.onenterframe 終了
//gameに、シーンを追加して、とお願いする
game.add( scene );
//gameに、ゲームをスタートして、とお願いする
game.start();
} );
60〜61行目に追加したべよ
そして、ブラウザを読み込みなおすと、こうなるべ!
やった。スタートやゴールのタイルはマップと一緒に動いて、山田先生は止まったままだ!
やったぁ!
次のページでは、マップ上の座標を取得して、壁を通り抜けられないようにしていきます。
このシリーズの一覧はこちら
- 小学生からのプログラミング入門。プログラミングってなぁに?
- Scratchの使い方と、ゲーム作りの基礎知識を学ぼう! 小学生からのプログラミング入門
- Scratchでじゃんけんゲームを作ろう! 小学生からのプログラミング入門
- Scratchでシューティングゲームを作ろう! 小学生からのプログラミング入門
- Scratchでピアノ鍵盤を作って音を鳴らそう! 小学生からのプログラミング入門
- テキストエディタ(Visual Studio Code)をインストールしてみよう! 小学生からのプログラミング入門
- Visual Studio Codeを日本語化してみよう! 小学生からのプログラミング入門
- JavaScriptでおみくじを作ろう! 小学生からのプログラミング入門
- JavaScriptで今月の残り日数を計算してみよう! 小学生からのプログラミング入門
- JavaScriptで画像を表示してみよう! 小学生からのプログラミング入門
- JavaScriptで画像を移動してみよう! 小学生からのプログラミング入門
- 【JavaScript】キー入力でキャラを動かしてみよう! 小学生からのプログラミング入門
- 【JavaScript】ファイルを分けて管理してみよう! 小学生からのプログラミング入門
- 【JavaScript】オブジェクトを使ってみよう! 小学生からのプログラミング入門
- 【JavaScript】ゲームのメインループを作ってみよう! 小学生からのプログラミング入門
- 【JavaScript】キャラを決まった間隔ずつ動かす! 小学生からのプログラミング入門
- HTML5とCanvasを使ってみよう! 小学生からのプログラミング入門
- 【JavaScript】迷路やRPGで使えるマップを作ってみよう! 小学生からのプログラミング入門
- 【JavaScript】マップでキャラを動かせるようにしよう! 小学生からのプログラミング入門
- 【JavaScript】クラスの概念をしっかりと理解しよう! 小学生からのプログラミング入門
- 【JavaScript】プログラム全体をクラスを使って作ってみよう! 小学生からのプログラミング入門
- 【JavaScript】文字を表示するクラスを作ってみよう! 小学生からのプログラミング入門
- 【JavaScript】改行と一文字ずつ画面に表示する方法! 小学生からのプログラミング入門
- 【JavaScript】ノベルゲーム風にキー入力で文字を切り替える方法! 小学生からのプログラミング入門
- JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方
- webpackを使ってゲームエンジンを作ろう!(JSライブラリの作り方)
- WindowsにPythonをインストールしてみよう!小学生からのPython入門
- MacにPythonをインストールしてみよう!小学生からのPython入門
- Pythonでじゃんけんゲームを作ってみよう!小学生からのPython入門
- Pythonのtkinterを使って、ウィンドウを表示してみよう!
- Pythonのtkinterで、画像つきのおみくじゲームを作ろう!