第2章: プレイヤー操作と入力管理
第1章ではゲームの土台と大容量メモリアリーナ(InstanceBufferArena)を構築しました。 第2章では、大群に立ち向かう主人公(プレイヤー)を登場させ、キーボード(WASD / 矢印キー)による滑らかでキビキビとした8方向移動を実装します!
ここで最も重要なのは、**斜め移動時の速度補正(ベクトルの正規化)を、新しいオブジェクトを作らずに計算する(ゼロアロケーション)**テクニックです。PlutoEngine v1.2.1のSoA(Structure of Arrays)とFlyweightパターンを活用し、ガベージコレクション(GC)の発生を完全に抑えます。
1. プレイヤースプライトの作成
シーンにプレイヤーを保持するプロパティを追加し、create() メソッドでスプライトを生成します。内部的には InstanceBufferArena から割り当てられ、Flyweightオブジェクトとして機能します。
import { PlutoEngine, ScaleMode, Scene, XPBDPlugin, MortonPlugin, type Sprite } from 'pluto-engine';
export class SwarmSurvivorScene extends Scene {
// プレイヤーのハンドル (Flyweightオブジェクト)
private player!: Sprite;
// プレイヤーステータス
private playerSpeed = 220; // 移動速度 (px/s)
public playerHp = 100;
public playerMaxHp = 100;
create(): void {
// 画面中央にサイズ 28px のプレイヤースプライトを生成
// 内部的には InstanceBufferArena から Float32Array のスロットが割り当てられます
this.player = this.add.sprite(960 / 2, 540 / 2, 'textureKey').setDisplaySize(28, 28);
// 鮮やかなエメラルドグリーン (0x10b981) を設定
this.player.setTint(0x10b981);
console.log(`プレイヤーが生成されました! Slot ID: ${this.player.id}`);
}
}2. 8方向移動とゼロアロケーション正規化
従来のゲーム制作では、斜め移動の正規化に new Vector2(dx, dy).normalize() といったオブジェクト生成がよく使われます。しかし、1秒間に60〜144回も new を行うと、メモリ上にゴミが溜まりGCスパイクの原因になります。
PlutoEngine v1.2.1 では、スカラー値(ローカル変数)だけで高速に計算します。さらに、this.player.x へのアクセスは、内部でSoA(Float32Array)の該当インデックスを直接読み書きするため、非常に高速です:
update(dt: number): void {
// 1. 入力ベクトルの集計 (ローカル変数のためヒープ割り当てゼロ)
let moveX = 0;
let moveY = 0;
if (this.input.isKeyPressed('KeyA') || this.input.isKeyPressed('ArrowLeft')) {
moveX -= 1;
}
if (this.input.isKeyPressed('KeyD') || this.input.isKeyPressed('ArrowRight')) {
moveX += 1;
}
if (this.input.isKeyPressed('KeyW') || this.input.isKeyPressed('ArrowUp')) {
moveY -= 1;
}
if (this.input.isKeyPressed('KeyS') || this.input.isKeyPressed('ArrowDown')) {
moveY += 1;
}
// 2. 移動入力がある場合の正規化と移動
if (moveX !== 0 || moveY !== 0) {
// 斜め移動時の長さを計算
const length = Math.hypot(moveX, moveY); // sqrt(moveX^2 + moveY^2)
const normX = moveX / length;
const normY = moveY / length;
// 速度と経過時間(dt)を掛けて移動
// Flyweight経由でSoAバッファを直接更新
const dist = this.playerSpeed * dt;
this.player.x += normX * dist;
this.player.y += normY * dist;
// 3. 進行方向に応じて向きを反転 (FlipX)
if (moveX < 0) {
this.player.setFlipX(true); // 左向き
} else if (moveX > 0) {
this.player.setFlipX(false); // 右向き
}
}
// 4. 画面外への侵入を防ぐクランプ処理
const halfSize = 14;
this.player.x = Math.max(halfSize, Math.min(960 - halfSize, this.player.x));
this.player.y = Math.max(halfSize, Math.min(540 - halfSize, this.player.y));
}3. 入力システムの利点 (this.input)
PlutoEngine の InputManager は、ブラウザの非同期な DOM キーボードイベントを、毎フレームの更新直前に**同期スナップショットとしてラッチ(固定)**します。
- 入力の取りこぼしがない: 連打やキーの組み合わせを正確に判定。
- スレッドセーフ: フレームの途中でキー状態が変わり、ロジックが矛盾することを防ぎます。
4. 全体コード (src/main.ts)
現在の src/main.ts は以下のようになります。v1.2.1アーキテクチャに合わせ、バックエンドに webgpu を指定し、今後の章で使う物理・空間ハッシュプラグイン (XPBDPlugin, MortonPlugin) を登録しています。描画には RenderGraph が自動的に使用されます。
import { PlutoEngine, ScaleMode, Scene, XPBDPlugin, MortonPlugin, type Sprite } from 'pluto-engine';
export class SwarmSurvivorScene extends Scene {
private player!: Sprite;
private playerSpeed = 220;
public playerHp = 100;
public playerMaxHp = 100;
create(): void {
this.player = this.add.sprite(960 / 2, 540 / 2, 'textureKey').setDisplaySize(28, 28);
this.player.setTint(0x10b981);
}
update(dt: number): void {
let moveX = 0;
let moveY = 0;
if (this.input.isKeyPressed('KeyA') || this.input.isKeyPressed('ArrowLeft')) moveX -= 1;
if (this.input.isKeyPressed('KeyD') || this.input.isKeyPressed('ArrowRight')) moveX += 1;
if (this.input.isKeyPressed('KeyW') || this.input.isKeyPressed('ArrowUp')) moveY -= 1;
if (this.input.isKeyPressed('KeyS') || this.input.isKeyPressed('ArrowDown')) moveY += 1;
if (moveX !== 0 || moveY !== 0) {
const length = Math.hypot(moveX, moveY);
const dist = this.playerSpeed * dt;
this.player.x += (moveX / length) * dist;
this.player.y += (moveY / length) * dist;
if (moveX < 0) this.player.setFlipX(true);
else if (moveX > 0) this.player.setFlipX(false);
}
const halfSize = 14;
this.player.x = Math.max(halfSize, Math.min(960 - halfSize, this.player.x));
this.player.y = Math.max(halfSize, Math.min(540 - halfSize, this.player.y));
}
}
new PlutoEngine({
canvas: 'game-canvas',
width: 960,
height: 540,
scaleMode: ScaleMode.FIT,
autoCenter: true,
backend: 'webgpu', // WebGPUレンダリングバックエンドを使用
maxInstances: 50000,
plugins: [XPBDPlugin, MortonPlugin], // 物理・空間ハッシュプラグイン
scene: [SwarmSurvivorScene],
});5. 動作確認
ブラウザを開いて、WASD または矢印キーを押してみてください。 エメラルドグリーンのプレイヤーが、斜め移動でも速度が変わることなく、滑らかに画面内を駆け巡るはずです!
操作感も抜群ですね! 続く第3章では、いよいよ数千体のモンスター大群(スウォーム)を一気に出現させ、プレイヤーに向かって押し寄せるAIロジックを実装します!