threejs-game について
ブラウザー向け3DゲームをThree.jsで開発するためのスキルです。レンダリングやゲームシステム、アセット読み込み、最適化を扱い、実装パターンとトピック別資料を提供します。
主な機能
- 013Dシーンとゲームループの構築
- 02プレイヤー操作と入力処理
- 03衝突・物理・体力システム
- 04モデル読み込みとアニメーション
- 05レンダリングと性能最適化
活用シーン
- 01ブラウザー向け3Dゲームの開発
- 02移動や衝突処理の実装
- 03ゲームキャラクターの読み込みとアニメーション
- 04レンダリング性能の改善
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ natea/ fitfinder --skill threejs-gameインストール後、Claude Code で/threejs-gameを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/animation.mdcameras.mdeffects.mdgame_development.mdgeometry.mdgetting_started.mdindex.mdinteractivity.mdlighting.mdloading.mdmaterials.mdscene_graph.mdtextures.md詳細
Three.js ゲーム開発スキル
WebGL を使用した Three.js のゲーム開発を包括的に支援します。3D レンダリング、ゲームの仕組み、物理演算、アニメーション、インタラクティブなブラウザベースのゲームを扱います。
このスキルを使用するタイミング
次のような場合に、このスキルを有効にしてください。
- Three.js で 3D ウェブゲームを開発する
- ゲームの仕組み(プレイヤーの移動、衝突、スコアリング)を実装する
- カメラ、ライティング、シーン管理を設定する
- 3D モデル(GLTF、OBJ、FBX)を読み込む
- ユーザー入力(キーボード、マウス、タッチ、ゲームパッド)を処理する
- アニメーションやキャラクターコントローラーを作成する
- 物理エンジン(Cannon.js、Ammo.js)を統合する
- 3D ゲームのパフォーマンスを最適化する
- ゲームのビジュアル用にシェーダーやマテリアルを扱う
クイックリファレンス
基本的なゲームのセットアップ
import * as THREE from 'three';
// Create scene, camera, renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Game loop
function animate(time) {
requestAnimationFrame(animate);
// Update game logic here
updatePlayer(time);
updateEnemies(time);
checkCollisions();
renderer.render(scene, camera);
}
animate();
プレイヤーコントローラー(サードパーソン)
class PlayerController {
constructor(camera, target) {
this.camera = camera;
this.target = target;
this.distance = 10;
this.height = 5;
this.rotationSpeed = 0.005;
this.moveSpeed = 0.1;
}
update(input) {
// Movement
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(this.target.quaternion);
const right = new THREE.Vector3(1, 0, 0).applyQuaternion(this.target.quaternion);
if (input.forward) this.target.position.add(forward.multiplyScalar(this.moveSpeed));
if (input.backward) this.target.position.add(forward.multiplyScalar(-this.moveSpeed));
if (input.left) this.target.position.add(right.multiplyScalar(-this.moveSpeed));
if (input.right) this.target.position.add(right.multiplyScalar(this.moveSpeed));
// Rotation
if (input.rotateLeft) this.target.rotation.y += this.rotationSpeed;
if (input.rotateRight) this.target.rotation.y -= this.rotationSpeed;
// Update camera position
const offset = new THREE.Vector3(0, this.height, this.distance);
offset.applyQuaternion(this.target.quaternion);
this.camera.position.copy(this.target.position).add(offset);
this.camera.lookAt(this.target.position);
}
}
入力処理
class InputManager {
constructor() {
this.keys = {};
this.mouse = { x: 0, y: 0, buttons: {} };
window.addEventListener('keydown', (e) => this.keys[e.code] = true);
window.addEventListener('keyup', (e) => this.keys[e.code] = false);
window.addEventListener('mousemove', (e) => {
this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
});
}
getInput() {
return {
forward: this.keys['KeyW'] || this.keys['ArrowUp'],
backward: this.keys['KeyS'] || this.keys['ArrowDown'],
left: this.keys['KeyA'] || this.keys['ArrowLeft'],
right: this.keys['KeyD'] || this.keys['ArrowRight'],
jump: this.keys['Space'],
action: this.keys['KeyE'],
rotateLeft: this.keys['KeyQ'],
rotateRight: this.keys['KeyE']
};
}
}
衝突検出(レイキャスティング)
function checkCollisions(player, obstacles) {
const raycaster = new THREE.Raycaster();
const directions = [
new THREE.Vector3(1, 0, 0), // right
new THREE.Vector3(-1, 0, 0), // left
new THREE.Vector3(0, 0, 1), // forward
new THREE.Vector3(0, 0, -1), // backward
];
for (const direction of directions) {
raycaster.set(player.position, direction);
const intersects = raycaster.intersectObjects(obstacles);
if (intersects.length > 0 && intersects[0].distance < 1.0) {
return {
collision: true,
object: intersects[0].object,
distance: intersects[0].distance,
point: intersects[0].point
};
}
}
return { collision: false };
}
3D モデルの読み込み(GLTF)
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
function loadCharacter(path) {
return new Promise((resolve, reject) => {
loader.load(
path,
(gltf) => {
const model = gltf.scene;
model.scale.set(1, 1, 1);
scene.add(model);
// Setup animations if available
const mixer = new THREE.AnimationMixer(model);
const animations = {};
gltf.animations.forEach(clip => {
animations[clip.name] = mixer.clipAction(clip);
});
resolve({ model, mixer, animations });
},
(progress) => {
console.log(`Loading: ${(progress.loaded / progress.total * 100).toFixed(2)}%`);
},
(error) => reject(error)
);
});
}
// Usage
const character = await loadCharacter('/models/character.glb');
character.animations.idle.play();
基本的な物理演算(重力とジャンプ)
class PhysicsBody {
constructor(mesh) {
this.mesh = mesh;
this.velocity = new THREE.Vector3();
this.onGround = false;
this.gravity = -9.8;
this.jumpPower = 5;
}
update(deltaTime) {
// Apply gravity
if (!this.onGround) {
this.velocity.y += this.gravity * deltaTime;
}
// Apply velocity
this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
// Ground check
if (this.mesh.position.y <= 0) {
this.mesh.position.y = 0;
this.velocity.y = 0;
this.onGround = true;
}
}
jump() {
if (this.onGround) {
this.velocity.y = this.jumpPower;
this.onGround = false;
}
}
}
インタラクティブなオブジェクト(ピッキング)
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactableObjects);
if (intersects.length > 0) {
const object = intersects[0].object;
object.userData.onInteract?.();
}
}
window.addEventListener('click', onMouseClick);
体力とダメージのシステム
class Entity {
constructor(mesh, maxHealth) {
this.mesh = mesh;
this.maxHealth = maxHealth;
this.health = maxHealth;
this.isDead = false;
}
takeDamage(amount) {
if (this.isDead) return;
this.health = Math.max(0, this.health - amount);
if (this.health === 0) {
this.die();
}
return this.health;
}
heal(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
return this.health;
}
die() {
this.isDead = true;
this.mesh.visible = false;
// Trigger death animation, effects, etc.
}
}
主要な概念
シーングラフ
- ゲームオブジェクトを階層的に整理します
- 複雑なオブジェクトにはグループを使用します
- 親子間の変換を扱います
ゲームループ
- 60fps で動作させるには
requestAnimationFrameを使用します - フレームレートに依存しない移動を実現するため、デルタタイムを計算します
- 更新ロジックとレンダリングを分離します
カメラシステム
- PerspectiveCamera:一人称/三人称ゲーム向け
- OrthographicCamera:2D/アイソメトリックゲーム向け
- カメラの追従とスムーズな遷移を実装します
ライティング
- AmbientLight:基本の照明
- DirectionalLight:影を伴う太陽光/月光
- PointLight:松明、爆発
- SpotLight:懐中電灯、ステージライト
パフォーマンスの最適化
- 繰り返し使うオブジェクトにはインスタンシングを使用します
- 視錐台カリングを実装します
- 遠くのオブジェクトには LOD(詳細度)を使用します
- ドローコールを最小限に抑えます
- テクスチャアトラスを使用します
- シャドウマップを最適化します
アセットの読み込み
- ゲーム開始前にすべてのアセットを事前読み込みします
- 読み込みの進行状況バーを表示します
- 読み込みを調整するには LoadingManager を使用します
- 読み込んだアセットをキャッシュします
よく使われるゲームのパターン
ステートマシン(ゲーム状態)
class GameStateMachine {
constructor() {
this.states = {
menu: new MenuState(),
playing: new PlayingState(),
paused: new PausedState(),
gameOver: new GameOverState()
};
this.currentState = this.states.menu;
}
changeState(stateName) {
this.currentState.exit();
this.currentState = this.states[stateName];
this.currentState.enter();
}
update(deltaTime) {
this.currentState.update(deltaTime);
}
}
オブジェクトプーリング
class ObjectPool {
constructor(factory, initialSize = 10) {
this.factory = factory;
this.available = [];
this.inUse = [];
for (let i = 0; i < initialSize; i++) {
this.available.push(factory());
}
}
acquire() {
let obj = this.available.pop();
if (!obj) obj = this.factory();
this.inUse.push(obj);
return obj;
}
release(obj) {
const index = this.inUse.indexOf(obj);
if (index > -1) {
this.inUse.splice(index, 1);
this.available.push(obj);
}
}
}
// Usage
const bulletPool = new ObjectPool(() => createBullet(), 20);
const bullet = bulletPool.acquire();
// ... use bullet
bulletPool.release(bullet);
リファレンスファイル
トピック別に整理された詳細なドキュメント:
- getting_started.md - Three.js の基礎、セットアップ、基本概念
- game_development.md - ゲームループ、プレイヤーコントローラー、ゲームの仕組み
- scene_graph.md - シーンの構成、階層、変換
- materials.md - マテリアルの種類、シェーダー、視覚効果
- textures.md - テクスチャの読み込み、UV マッピング、アトラス
- lighting.md - ライトの種類、影、HDR
- cameras.md - カメラの種類、コントロール、ビューポート管理
- geometry.md - 組み込みジオメトリ、カスタムジオメトリ、バッファー
- loading.md - アセットの読み込み(モデル、テクスチャ、オーディオ)
- animation.md - アニメーションシステム、スケルタルアニメーション、トゥイーン
- interactivity.md - レイキャスティング、ピッキング、UI 統合
- effects.md - ポストプロセッシング、パーティクル、フォグ
リソース
公式ドキュメント
- Three.js マニュアル:https://threejs.org/manual/
- Three.js API:https://threejs.org/docs/
- Three.js の例:https://threejs.org/examples/
物理演算の統合
- Cannon.js:軽量な 3D 物理演算
- Ammo.js:Bullet 物理エンジンの完全移植版
- Rapier:高性能な物理演算
便利なライブラリ
- three-mesh-bvh:高速なレイキャスティング
- three-pathfinding:ナビゲーションメッシュ
- postprocessing:高度なエフェクト
このスキルの活用
初心者向け
- 基本的なシーンのセットアップから始めます
- 座標系を学びます
- ゲームループを理解します
- モデルを使う前に、シンプルな形状で練習します
ゲーム開発向け
- ゲームのアーキテクチャを計画します
- まず入力処理を実装します
- シンプルなプレイヤーコントローラーを作成します
- ゲームプレイの仕組みを段階的に追加します
- 開発を通してパフォーマンスを最適化します
高度な機能向け
- 物理エンジンを統合します
- 高度なシェーダーを実装します
- ポストプロセッシング効果を追加します
- マルチプレイヤー向けのネットワーク機能を構築します
注意事項
- Three.js は右手座標系を使用します(X は右、Y は上、Z は手前です)
- 早い段階から最適化します。定期的にプロファイリングし、ドローコールを最小限に抑えてください
- デバッグには開発用ビルドを、リリースには本番用ビルドを使用してください
- 最新のブラウザーでは WebGL 2 の機能を検討してください
- モバイルでのパフォーマンスには慎重な最適化が必要です
コメント
Markdown 対応