ダーツボード描画の実装知識とベストプラクティス。p5.jsを使用した描画順序、レイヤリング、座標系の扱いに関する専門知識を提供します。実装・テスト・レビュー時に参照してください。
ダーツボード描画に関する実装パターンとベストプラクティスを定義します。
ダーツボードは以下の順序で描画することで、正しいレイヤリングを実現します:
1. 背景クリア
2. ダブルリング(170mm円全体、赤/緑交互)
3. アウターシングル(162mm円全体、黒/ベージュ交互)
4. トリプルリング(107mm円全体、赤/緑交互)
5. インナーシングル(99mm円全体、黒/ベージュ交互)
6. ★ 外側スパイダー(放射線 + リング境界の同心円)
7. アウターブル(緑色、半径7.95mm)
8. インナーブル(赤色、半径3.175mm)
9. ★ ブル用スパイダー(ブル境界の同心円)
10. セグメント番号
重要: スパイダーは必ず2ステップに分けて描画する
// ❌ 悪い例: 全てのスパイダーを一度に描画
drawAllRings();
drawAllSpiders(); // 放射線がブルの色に被る!
drawBull();
// ✅ 良い例: スパイダーを2ステップに分ける
drawAllRings();
drawSpiderOuter(); // 1. 放射線 + リング境界の同心円
drawBull(); // ブルエリアの色を塗る
drawSpiderBull(); // 2. ブル境界の同心円
理由:
// ❌ 悪い例: セグメントの真ん中に配置される
const angle = -Math.PI / 2 + i * SEGMENT_ANGLE;
// ✅ 良い例: セグメント境界に配置される
const angle = -Math.PI / 2 + (i - 0.5) * SEGMENT_ANGLE;
理由:
drawRingSegments() 関数が (index - 0.5) * SEGMENT_ANGLE を使用してセグメントを描画している鉄則: ビジネスロジックは必ず物理座標で処理し、描画時のみ画面座標に変換する。
// ✅ 良い例
const physicalRadius = BOARD_PHYSICAL.rings.doubleOuter; // 170mm
const screenRadius = transform.physicalDistanceToScreen(physicalRadius);
p5.circle(center.x, center.y, screenRadius * 2);
// ❌ 悪い例
const radius = 170; // 物理座標?画面座標?不明確
p5.circle(center.x, center.y, radius * 2);
// 座標変換の例
const center = transform.getCenter(); // ボード中心の画面座標
const radius = transform.physicalDistanceToScreen(170); // 物理→画面
const width = transform.physicalDistanceToScreen(1.5); // 線幅も変換
// ✅ 良い例: 共通設定はループ外で一度だけ
p5.noStroke(); // ループ外で設定
SEGMENTS.forEach(() => {
p5.fill(color);
p5.arc(...);
});
// ❌ 悪い例: 毎回設定する
SEGMENTS.forEach(() => {
p5.noStroke(); // 20回呼ばれる(無駄)
p5.fill(color);
p5.arc(...);
});
export function drawSpiderOuter(p5: p5Types, transform: CoordinateTransform): void {
// 1. 放射線(セグメント境界): 20本
// - ボード中心からboardEdge(225mm)まで
// - (i - 0.5) * SEGMENT_ANGLE でセグメント境界に配置
// 2. リング境界の同心円: 4本
// - ダブル外側(170mm)
// - ダブル内側(162mm)
// - トリプル外側(107mm)
// - トリプル内側(99mm)
}
export function drawSpiderBull(p5: p5Types, transform: CoordinateTransform): void {
// ブル境界の同心円: 2本
// - アウターブル(7.95mm)
// - インナーブル(3.175mm)
}
// 放射線→同心円の順序を検証
const allCalls = [...lineSpy.mock.invocationCallOrder, ...circleSpy.mock.invocationCallOrder];
const lineCalls = lineSpy.mock.invocationCallOrder;
const circleCalls = circleSpy.mock.invocationCallOrder;
// 最後の放射線 < 最初の同心円
expect(Math.max(...lineCalls)).toBeLessThan(Math.min(...circleCalls));
/**
* @deprecated drawSpiderOuter と drawSpiderBull を個別に呼び出すことを推奨
*/
export function drawSpider(p5: p5Types, transform: CoordinateTransform): void {
drawSpiderOuter(p5, transform);
drawSpiderBull(p5, transform);
}
間違い: スパイダーを一度に全て描画 対策: スパイダーを2ステップに分ける(外側→ブル塗り→ブル用)
間違い: 物理座標と画面座標が混在 対策: 常に物理座標で計算し、描画直前に変換
間違い: i * SEGMENT_ANGLE でセグメント中央に配置
対策: (i - 0.5) * SEGMENT_ANGLE で境界に配置
間違い: ループ内で毎回描画状態を設定 対策: 共通設定はループ外で一度だけ
このskillは以下のコンテキストで参照してください:
このドメイン知識を常に参照し、ダーツボード描画に関わる実装やテストを作成してください。