表示調整
閉じる
挿絵表示切替ボタン
▼配色
▼行間
▼文字サイズ
▼メニューバー
×閉じる

ブックマークに追加しました

設定
0/400
設定を保存しました
エラーが発生しました
※文字以内
ブックマークを解除しました。

エラーが発生しました。

エラーの原因がわからない場合はヘルプセンターをご確認ください。

ブックマーク機能を使うにはログインしてください。
2/10

第2日目:プレイヤーと物理の実装 - ゲーム世界の基盤づくり

こんにちは、蒼井 蓮です。「ゼロから始めるnew.world - AAA級ゲーム開発への道」の第2日目の記録をお届けします。昨日は基本的なゲームループの実装まで行いましたが、今日はそこにプレイヤーキャラクターと簡単な物理エンジンを追加していきたいと思います。


今日の目標

プレイヤーキャラクターの実装

基本的な物理エンジンの実装

キーボード入力の処理

プレイヤーキャラクターの実装

まずは、ゲームの主人公となるプレイヤーキャラクターを実装しました。今はシンプルな四角形ですが、これから徐々に発展させていく予定です。


// entity.js - 基本エンティティクラス

class Entity {

constructor(x, y, width, height, color) {

this.x = x;

this.y = y;

this.width = width;

this.height = height;

this.color = color;

this.velocityX = 0;

this.velocityY = 0;

}


update() {

// 基本的な更新処理

this.x += this.velocityX;

this.y += this.velocityY;

}


render(ctx) {

// 基本的な描画処理

ctx.fillStyle = this.color;

ctx.fillRect(this.x, this.y, this.width, this.height);

}

}


// player.js - プレイヤーキャラクタークラス

class Player extends Entity {

constructor(x, y) {

super(x, y, 40, 40, 'blue');

this.speed = 5;

this.jumpForce = 10;

this.isJumping = false;

this.isGrounded = false;

}


moveLeft() {

this.velocityX = -this.speed;

}


moveRight() {

this.velocityX = this.speed;

}


stop() {

this.velocityX = 0;

}


jump() {

if (this.isGrounded) {

this.velocityY = -this.jumpForce;

this.isGrounded = false;

this.isJumping = true;

}

}


update() {

super.update();


// 重力の適用

if (!this.isGrounded) {

this.velocityY += GRAVITY;

}


// 画面外に出ないようにする

if (this.x < 0) this.x = 0;

if (this.x + this.width > CANVAS_WIDTH) this.x = CANVAS_WIDTH - this.width;


// 地面との衝突判定

if (this.y + this.height > GROUND_LEVEL) {

this.y = GROUND_LEVEL - this.height;

this.velocityY = 0;

this.isGrounded = true;

this.isJumping = false;

}

}

}

このコードでは、基本的なエンティティクラスを定義し、それを継承したプレイヤークラスを実装しています。プレイヤーは左右移動とジャンプの機能を持ち、重力の影響も受けるようになっています。


簡易物理エンジンの実装

次に、シンプルな物理エンジンの基盤を追加しました。まずは重力と衝突判定から始めています。


// physics.js - シンプルな物理エンジン

const GRAVITY = 0.5;

const FRICTION = 0.8;

const GROUND_LEVEL = 550; // キャンバスの下部付近

const CANVAS_WIDTH = 800;

const CANVAS_HEIGHT = 600;


class Physics {

constructor() {

this.entities = [];

}


addEntity(entity) {

this.entities.push(entity);

}


update() {

// 全エンティティに重力を適用

for (const entity of this.entities) {

if (!entity.isGrounded) {

entity.velocityY += GRAVITY;

}


// 地面との衝突判定

if (entity.y + entity.height > GROUND_LEVEL) {

entity.y = GROUND_LEVEL - entity.height;

entity.velocityY = 0;

entity.isGrounded = true;


// 摩擦の適用

entity.velocityX *= FRICTION;


// 非常に小さい速度はゼロにする(数値の安定化)

if (Math.abs(entity.velocityX) < 0.1) {

entity.velocityX = 0;

}

}

}


// エンティティ同士の衝突判定

this.checkCollisions();

}


checkCollisions() {

for (let i = 0; i < this.entities.length; i++) {

for (let j = i + 1; j < this.entities.length; j++) {

const entityA = this.entities[i];

const entityB = this.entities[j];


// AABB衝突判定(Axis-Aligned Bounding Box)

if (this.isColliding(entityA, entityB)) {

this.resolveCollision(entityA, entityB);

}

}

}

}


isColliding(a, b) {

// 矩形同士の衝突判定

return a.x < b.x + b.width &&

a.x + a.width > b.x &&

a.y < b.y + b.height &&

a.y + a.height > b.y;

}


resolveCollision(a, b) {

// 簡易的な衝突応答(位置の調整のみ)

// 将来的には運動量保存などの物理法則に基づいた応答に発展させる


// 重なりの計算

const overlapX = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);

const overlapY = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);


// X軸かY軸のどちらで調整するか決定(小さい方を選択)

if (overlapX < overlapY) {

if (a.x < b.x) {

a.x -= overlapX;

} else {

a.x += overlapX;

}


// 速度の反転(単純な反射)

a.velocityX *= -0.5;

b.velocityX *= -0.5;

} else {

if (a.y < b.y) {

a.y -= overlapY;

a.isGrounded = true;

} else {

a.y += overlapY;

b.isGrounded = true;

}


// 速度の反転(単純な反射)

a.velocityY *= -0.5;

b.velocityY *= -0.5;

}

}

}

この物理エンジンは、重力、摩擦、地面との衝突、そしてエンティティ同士の衝突を処理します。現時点では簡易的な実装ですが、将来的には運動量保存則や材質による反発係数なども考慮した、より本格的な物理シミュレーションに発展させる予定です。


キーボード入力の処理

最後に、プレイヤーをコントロールするためのキーボード入力処理を実装しました。


// input.js - キーボード入力の処理

class InputHandler {

constructor(player) {

this.player = player;

this.keys = {};


// キーボードイベントの登録

window.addEventListener('keydown', (e) => this.keyDown(e));

window.addEventListener('keyup', (e) => this.keyUp(e));

}


keyDown(e) {

this.keys[e.code] = true;

}


keyUp(e) {

this.keys[e.code] = false;

}


update() {

// 左右移動

if (this.keys['ArrowLeft'] || this.keys['KeyA']) {

this.player.moveLeft();

} else if (this.keys['ArrowRight'] || this.keys['KeyD']) {

this.player.moveRight();

} else {

this.player.stop();

}


// ジャンプ

if (this.keys['ArrowUp'] || this.keys['KeyW'] || this.keys['Space']) {

this.player.jump();

}

}

}

このInputHandlerクラスは、キーボードの入力を監視し、対応するプレイヤーの操作に変換します。矢印キーやWASDキー、スペースキーなど、一般的なゲームコントロールに対応しています。


すべてを統合する

最後に、これらのコンポーネントをすべて統合して、ゲームの基本構造を完成させました。


// main.js - メインゲーム処理

const canvas = document.getElementById('gameCanvas');

const ctx = canvas.getContext('2d');


// キャンバスサイズの設定

canvas.width = CANVAS_WIDTH;

canvas.height = CANVAS_HEIGHT;


// ゲームオブジェクトの初期化

const game = new Game(canvas);

const physics = new Physics();

const player = new Player(100, 100);

const inputHandler = new InputHandler(player);


// 物理エンジンにプレイヤーを追加

physics.addEntity(player);


// ゲームにプレイヤーを追加

game.addEntity(player);


// メインゲームループの拡張

game.update = function() {

// 入力処理

inputHandler.update();


// 物理演算

physics.update();


// エンティティの更新

for (const entity of this.entities) {

entity.update();

}

};


// ゲーム開始

game.start();

実行結果と気づき

実際に実装したコードを実行してみると、青い四角形のプレイヤーが画面に表示され、キーボードで左右に移動したりジャンプしたりできるようになりました。地面との衝突も正しく処理されています。


今日の実装を通じて、いくつかの重要な気づきがありました:


コードの構造化の重要性:ゲーム開発では、コードをきちんと構造化することが非常に重要です。今回はEntity、Player、Physics、InputHandlerなどのクラスに分けることで、それぞれの責任を明確にしました。これはボルト君からのアドバイスもあり、実践してみました。


デバッグの難しさ:物理エンジンの実装中、エンティティが予期せぬ動きをすることがありました。このようなバグは視覚的に確認できるため発見は容易ですが、原因の特定には時間がかかることがあります。new.worldのコンソール出力とデバッグ機能が役立ちました。


フレームレート依存の問題:現在の実装では、ゲームの速度がフレームレートに依存しています。これは理想的ではないので、明日はデルタタイム(前回のフレームからの経過時間)を導入して、フレームレートに依存しない動きを実装する予定です。


不思議な出来事

今日、コーディングに没頭していると、ふと画面上のプレイヤーキャラクター(現時点では単なる青い四角形)が、私の入力とは無関係に一瞬だけ動いたように感じました。おそらく疲れからくる錯覚でしょうが、まるで四角形に意識があるかのような錯覚を覚えました。長時間のコーディングの影響かもしれませんね(笑)


明日の計画

明日は以下の機能を実装する予定です:


デルタタイムを導入して、フレームレートに依存しない動きを実現

簡単な障害物やプラットフォームの追加

カメラシステムの実装(プレイヤーを追従するスクロール機能)

衝突応答の改善(反発係数や摩擦係数の考慮)

まとめと質問

2日目にしてかなり基本的なゲームの形が見えてきました。プレイヤーキャラクターが動き、物理法則に従って振る舞う様子は、単純ながらも達成感があります。これがAAA級ゲームへの第一歩です。


今日も質問です:物理エンジンについて、より詳しく知りたい部分はありますか?衝突判定の最適化、流体シミュレーション、布シミュレーションなど、物理の分野は幅広いですが、特に興味のある分野があれば教えてください。次回以降で取り上げてみたいと思います。


明日もまた、新たな発見と進化をお届けします!


蒼井 蓮

評価をするにはログインしてください。
ブックマークに追加
ブックマーク機能を使うにはログインしてください。
― 新着の感想 ―
このエピソードに感想はまだ書かれていません。
感想一覧
+注意+

特に記載なき場合、掲載されている作品はすべてフィクションであり実在の人物・団体等とは一切関係ありません。
特に記載なき場合、掲載されている作品の著作権は作者にあります(一部作品除く)。
作者以外の方による作品の引用を超える無断転載は禁止しており、行った場合、著作権法の違反となります。

この作品はリンクフリーです。ご自由にリンク(紹介)してください。
この作品はスマートフォン対応です。スマートフォンかパソコンかを自動で判別し、適切なページを表示します。

↑ページトップへ