05.速度を調整
上:t, 下:b, 左:f, 右:h *操作が反映されない場合、画面部分を一度クリックしてみてください
オブジェクトを動かす準備
以降は画像(AA)をオブジェクトと呼んでいきます。ゲームを作っていく上での処理対象の1単位という感じで捉えれば、だいたい合ってます。基本的に描画と動きを併せ持つような情報です。動きとは止まっていることも含みます。移動しなければ静止画ですね。
というわけで、移動が発生するので自分用に度数法に合わせた移動量を先に計算して保持しておきます。
/* 前略 */
/* 計算を簡単にするために、度数法の単位あたりの移動量係数を保持 */
const v4deg = [];
const r = Math.PI / 180;
for (let i = 0; i < 90; i++) { // 方向と角度 => N:270, E:0, S:90, W:180
v4deg[i] = {
x: Math.floor(Math.cos(i * r) * 100) / 100,
y: Math.floor(Math.sin(i * r) * 100) / 100,
};
v4deg[i + 90] = {x: - v4deg[i].y, y: v4deg[i].x};
v4deg[i + 180] = {x: - v4deg[i].x, y: - v4deg[i].y};
v4deg[i + 270] = {x: v4deg[i].y, y: - v4deg[i].x};
}
/* 以下略 */
弧度法より度数法のほうが感覚的に分かりやすいかなと思って用意しています。なお、これでゲームが作りやすくなるわけではありません。
毎回計算させるよりはパフォーマンス的にも有利だろうということで配列で保持。小数以下の角度は考慮しません。
動かせるようにする
角度、つまり方向が決まれば、あとは速度(移動量)も持っていれば、動きの単位は考えられそうです。
/* 前略 */
function createMoveObject(imgData, px = 0, py = 0, moveFunction) {
const moveObject = Object.create(imgData);
return Object.defineProperties(moveObject, {
move: {value: moveFunction},
type: {value: 0, writable: true}, // 将来的に何かに使えるかも
x: {value: px, writable: true}, y: {value: py, writable: true},
vx: {value: 0, writable: true}, vy: {value: 0, writable: true},
deg: {value: 0, writable: true},
sp: {value: 0, writable: true},
setDeg: {value: function(degree) { // 角度による移動方向の設定
this.deg = degree % 360;
this.vx = v4deg[this.deg].x;
this.vy = v4deg[this.deg].y;
return this;
}},
setSp: {value: function(speed) { // 速度の設定
this.sp = speed;
return this;
}},
});
}
/* 以下略 */
vx, vy が移動量単位です。これに速度をかけ合わせて実際の移動量を計算します。あと初期位置を設定できるようにしました。これでどんどん新しいオブジェクトを作ることができます。
また、連続して設定できるように編集用の各関数は自身を返却するようにしています。チェーンメソッドってやつですね。
動かす
いよいよ動かせるようにします。前述の関数で引数として渡すようになっていますので、それに合わせます。
/* 前略 */
const myship = createMoveObject(img, 40, 12, function() {
const Key = screen.key;
const dx = (Key.stat & Key.map.R) ? 1 : ((Key.stat & Key.map.L) ? -1 : 0);
const dy = (Key.stat & Key.map.D) ? 1 : ((Key.stat & Key.map.U) ? -1 : 0);
this.setDeg([[225, 180, 135], [270, 0, 90], [315, 0, 45]][dx + 1][dy + 1]); // 見た目ややこしいけど四方の角度
if (dx === 0 && dy === 0) {
this.vx = 0;
this.vy = 0;
}
this.x += this.vx * this.sp;
this.y += this.vy * (this.sp / 2); // Y方向は高さ倍なので速度は半分で考慮
this.x = Math.min(Math.max(0, this.x), screen.width - this.width);
this.y = Math.min(Math.max(0, this.y), screen.height - this.height);
}).setSp(1.0);
/* 以下略 */
さて、myshipとか出てきましたね。そうです、見えないかも知れませんがシューティングゲームの自機をイメージしていました。
座標については仮想座標も考えたのですが、そっちの計算よりは速度調整で合わせてしまうほうが楽だと思ったので、文字単位の幅と高さの比率を考慮して増分計算するだけにしました。
前回と比較しやすいように、速度は「1」にしています。横方向は同じふるまいですが、縦とナナメ方向の移動に違いが出てきます。
表示する
各種情報と移動まで設定できたので、このオブジェクトを表示する処理を作ります。
オブジェクトが座標を持つようになったので、それを使うことで描画関数の引数を減らせるようになります。
/* 前略 */
function drawMoveObject(moveObject) {
drawImageData(Math.floor(moveObject.x), Math.floor(moveObject.y), moveObject);
}
/* 以下略 */
位置座標は移動量の都合から小数点以下の情報も持っているので、整数化して画面の描画位置を割り出しています。
組み込み
最後に、もろもろを組み込んでみましょう。
/* 前略 */
function gameFrame() {
const t = performance.now();
myship.move();
screen.clear();
drawMoveObject(myship);
const delta = performance.now() - t;
setTimeout(gameFrame, spf - delta);
}
/* 以下略 */
ものすごくシンプルになりました。今後は、オブジェクトを配列に格納しておくなどすれば、それをループして各move()を呼び出し、描画してあげることで、メイン部分がシンプルなままにいくつものオブジェクトを表示できそうです。さらに配列をレイヤ単位で分けておくと、描画順序や見た目の優先順位も整理できるでしょう。
夢はふくらみますが、区切りがよいので、一段落とします。
リソース
gconsole.css
gconsole2.js
g05.js
関連
00.画面表示
01.文字出力
02.無限ループ
03.画像出力
04.キー操作
06.敵の配置
07.ゲームにする
08.ゲームパッド