【JavaScript】アクションゲームのジャンプの実装【ゲーム制作】

2Dのアクションゲームにおける自機のジャンププログラムを作る。ジャンプは固定長ジャンプと可変長ジャンプの2種類。固定長ジャンプはどんなボタンの押し方をしても、ジャンプ高に違いがない。可変長ジャンプはボタンを押す長さでジャンプ高が変化する。

固定長ジャンプ

以下のスクリプトはPCの矢印キーの左右で横移動。上で固定長ジャンプする。

ドンキーコングのような固定長ジャンプ

別ページで表示

まず、猫画像を読み込み表示する簡素なCatクラス。

/**
 * 猫画像を表示(cat.js)
 */
export class Cat {
	constructor(parent = undefined, x = 0, y = 0) {
		if (parent !== undefined) {
			this._parent = parent;
		}
		this._x = x;
		this._y = y;
		this._rotation = 0;
		this._alpha = 1.0;
		this._img;
		this._width;
		this._height;
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		if (this._parent !== undefined) {
			this._ctx = this._parent;
		}
		this._img = new Image();
		this._img.src = "./cat.png";
		this._img.onload = () => {
			this._width	= this._img.width;
			this._height	= this._img.height;
		};
		this.draw();
	}

	//////////////////////////////////
	// Public
	//////////////////////////////////

	draw() {
		if (!this._img.complete) {
			return;
		}

		// 現在の描画状態を保存
		this._ctx.save();
		// コンテキストの座標を変更し、キャンバス中央に移動
		this._ctx.translate(this._x, this._y);
		// コンテキストの角度をラジアン値で指定
		this._ctx.rotate(this._rotation);
		this._ctx.globalAlpha = this._alpha;
		this._ctx.drawImage(this._img, -(this._img.width / 2), -(this._img.height / 2));
		// save()で保存した描画状態を復元
		this._ctx.restore();
	}

	//////////////////////////////////
	// Getters/Setters
	//////////////////////////////////

	get x() {
		return this._x;
	}
	set x(x) {
		this._x = x;
	}

	get y() {
		return this._y;
	}
	set y(y) {
		this._y = y;
	}

	get width() {
		return this._width;
	}

	get height() {
		return this._height;
	}
}

次に、固定長ジャンプを担当するfixed_length_jumpクラス。

import { Cat } from "./cat.js";

/**
 * 固定長ジャンプクラス(fixed_length_jump.js)
 */
export class FixedLengthJump {
	constructor() {
		this.cvs = document.getElementById('canvas');
		this.ctx = this.cvs.getContext('2d');
		this.cat;
		this.CAT_HEIHGT = 43.5;
		this.jumpSpeed = -40;	// ジャンプの初速度
		this.vx = 0;
		this.vy = 0;
		this.ay = 1.5;
		this.isJump = false;
		this.cvs.setAttribute('tabindex', 0);	// tabindexの指定でキーイベントを受け取れるようにする
		this.cvs.addEventListener('keydown', this._onKeyDown.bind(this));
		this.cvs.addEventListener('keyup', this._onKeyUp.bind(this));
		// タイマー関連
		this.animID;
		this.isAnim = 0;
		this.FPS = 60;
		this.frame = 0;
		this.startTime;
		this.nowTime;
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		this.cvs.style.backgroundColor = "#eeeeee";
		this.cat = new Cat(this.ctx, this.cvs.width / 2, this.cvs.height - this.CAT_HEIHGT);

		this.startTime = performance.now();
		this._mainLoop();
	}

	_mainLoop() {
		this.nowTime = performance.now();
		let elapsedTime = this.nowTime - this.startTime;
		let idealTime = this.frame * (1000 / this.FPS);
		if (idealTime < elapsedTime) {
			this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);

			// ①ジャンプ処理
			if (this.isJump) {
				// y座標の更新
				this.vy += this.ay;
				this.cat.y += this.vy;
				// x座標の更新
				this.cat.x += this.vx;

				// ②着地判定
				if (this.cat.y + this.CAT_HEIHGT >= this.cvs.height) {
					this.isJump = false;
					this.cat.y = this.cvs.height - this.CAT_HEIHGT;
					this.vx = 0;
				}
			} else {
				this.cat.x += this.vx;
			}
			this.cat.draw();

			this.frame++;
			
			if (elapsedTime >= 1000) {
				this.startTime = this.nowTime;
				this.frame = 0;
			}
		}
	
		this.animID = requestAnimationFrame(this._mainLoop.bind(this));
	}

	//////////////////////////////////
	// Handlers
	//////////////////////////////////

	_onKeyDown(e) {
		if (this.isJump) { return; }

		switch(e.keyCode) {
		case 37: // 左矢印キー
			this.vx = -10;
			break;
		case 39: // 右矢印キー
			this.vx = 10;
			break;
		case 38: // 上矢印キー
			this.isJump = true;
			this.vy = this.jumpSpeed;
			break;
		}
	}

	_onKeyUp(e) {
		switch(e.keyCode) {
		case 37: // 左矢印キー
		case 39: // 右矢印キー
			if (!this.isJump) {
				this.vx = 0;
			}
			break;
		}
	}
}

requestAnimationFrameを使ったゲームループの実装は、「【JavaScript】requestAnimationFrameでゲームループを作る」を参照。

上キーを押すとフラグ変数のthis.isJumpにtrueが入り、this.vyにジャンプ初速度を代入。メインループの中で①this.vyに加速度this.ayを加算し、その値を猫画像のyに代入する。最初は-の値が、加速度(+の値)が加算されていくことで負数から正数になる。そこから落下が始まり、②猫画像が地面に着地したらthis.isJumpにfalseを代入してジャンプ処理終了。

可変長ジャンプ

以下は可変長ジャンプするスクリプト。操作は固定長ジャンプのものと同じ。

スーパーマリオのような可変長ジャンプ

別ページで表示

以下が可変長ジャンプを担当するvariable_length_jumpクラス。

import { Cat } from "./cat.js";

/**
 * 可変長ジャンプクラス(variable_length_jump.js)
 */
export class VariableLengthJump {
	constructor() {
		this.cvs = document.getElementById('canvas');
		this.ctx = this.cvs.getContext('2d');
		this.cat;
		this.CAT_HEIHGT = 43.5;
		this.jumpSpeed = -40;	// ジャンプの初速度
		this.vx = 0;
		this.vy = 0;
		this.ay = 1.5;
		this.isJump = false;
		this.cvs.setAttribute('tabindex', 0);	// tabindexの指定でキーイベントを受け取れるようにする
		this.cvs.addEventListener('keydown', this._onKeyDown.bind(this));
		this.cvs.addEventListener('keyup', this._onKeyUp.bind(this));
		// タイマー関連
		this.animID;
		this.isAnim = 0;
		this.FPS = 60;
		this.frame = 0;
		this.startTime;
		this.nowTime;
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		this.cvs.style.backgroundColor = "#eeeeee";
		this.cat = new Cat(this.ctx, this.cvs.width / 2, this.cvs.height - this.CAT_HEIHGT);

		this.startTime = performance.now();
		this._mainLoop();
	}

	_mainLoop() {
		this.nowTime = performance.now();
		let elapsedTime = this.nowTime - this.startTime;
		let idealTime = this.frame * (1000 / this.FPS);
		if (idealTime < elapsedTime) {
			this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);
			// ジャンプ処理
			if (this.isJump) {
				// y座標の更新
				this.vy += this.ay;
				this.cat.y += this.vy;
				// x座標の更新
				this.cat.x += this.vx;

				// 着地判定
				if (this.cat.y + this.CAT_HEIHGT >= this.cvs.height) {
					this.isJump = false;
					this.cat.y = this.cvs.height - this.CAT_HEIHGT;
					this.vx = 0;
				}
			} else {
				this.cat.x += this.vx;
			}
			this.cat.draw();

			this.frame++;
			
			if (elapsedTime >= 1000) {
				this.startTime = this.nowTime;
				this.frame = 0;
			}
		}
	
		this.animID = requestAnimationFrame(this._mainLoop.bind(this));
	}

	//////////////////////////////////
	// Handlers
	//////////////////////////////////

	_onKeyDown(e) {
		if (this.isJump) { return; }

		switch(e.keyCode) {
		case 37: // 左矢印キー
			this.vx = -10;
			break;
		case 39: // 右矢印キー
			this.vx = 10;
			break;
		case 38: // 上矢印キー
			this.isJump = true;
			this.vy = this.jumpSpeed;
			//this.ay = 1.5;
			break;
		}
	}

	_onKeyUp(e) {
		switch(e.keyCode) {
		case 37: // 左矢印キー
		case 39: // 右矢印キー
			if (!this.isJump) {
				this.vx = 0;
			}
			break;
		case 38: // 上矢印キー
			// ①ジャンプが上昇中のみ実行
			if (0 > this.vy) {
				this.vy = 0;
			}
			break;
		}
	}
}

固定長ジャンプのコードとの違いは110行目の部分だけ。①ジャンプの上昇中に上キーが離されたらthis.vyに0を代入してすぐに落下させている。

参考図書

コメント

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