コンテンツにスキップ

Weighted Pick (重み付き抽選)

ゲームでよく使われる「コモンは当たりやすく、レアは当たりにくい」といった確率抽選を実装するためのロジックです。 各アイテムに weight プロパティを持たせ、その値の大きさに比例して選ばれる確率が決まります。

ボタンを押すと抽選が行われます。設定確率は Common(70), Rare(25), SR(5) です。 試行回数を重ねると、設定された確率に収束していく様子が確認できます。

シーンクラスに追加するヘルパーメソッドです。ジェネリクスを使用しているため、weight プロパティを持つ任意のオブジェクト配列に対応できます。

/**
* 重み付きランダム抽選を行う
* @param items 重み(weight)を持つアイテムの配列
* @returns 抽選されたアイテム
*/
public weightedPick<T extends { weight: number }>(items: T[]): T | null {
// 重みの合計を計算
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
// 0から合計重みまでのランダムな値を取得
let random = Math.random() * totalWeight;
// 重みを順番に減算して、0以下になった時点のアイテムを選択
for (const item of items) {
if (random < item.weight) {
return item;
}
random -= item.weight;
}
return null;
}

アイテム配列を定義してメソッドに渡すだけで、抽選結果取得できます。

// 抽選対象の定義 (weightが確率の重みになります)
const dropItems = [
{ name: 'Potion', weight: 100 },
{ name: 'Elixir', weight: 20 },
{ name: 'Rare Sword', weight: 5 }
];
// 抽選の実行
const result = this.weightedPick(dropItems);
if (result) {
console.log(`You got: ${result.name}`);
}
クラス全体を見る
import Phaser from 'phaser';
export class WeightedPickScene extends Phaser.Scene {
private resultText?: Phaser.GameObjects.Text;
private statsText?: Phaser.GameObjects.Text;
private counts: Record<string, number> = {};
// 各アイテムの定義
private items = [
{ name: 'Common', weight: 70, color: '#aaaaaa' },
{ name: 'Rare', weight: 25, color: '#0088ff' },
{ name: 'SR', weight: 5, color: '#ffaa00' }
];
constructor() {
super({ key: 'WeightedPickScene' });
this.items.forEach(item => this.counts[item.name] = 0);
}
create() {
this.add.text(10, 10, 'Weighted Pick Demo', {
fontFamily: '"Noto Sans JP", sans-serif',
fontSize: '24px',
color: '#00ff00'
});
// ... UI Setup ...
// 抽選実行の例 (ボタンクリック時など)
this.handleDraw();
}
private handleDraw() {
const picked = this.weightedPick(this.items);
if (picked) {
this.counts[picked.name]++;
console.log(`Pooled: ${picked.name}`);
// ... UI Update ...
}
}
/**
* 重み付きランダム抽選を行う
* @param items 重み(weight)を持つアイテムの配列
* @returns 抽選されたアイテム
*/
public weightedPick<T extends { weight: number }>(items: T[]): T | null {
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * totalWeight;
for (const item of items) {
if (random < item.weight) {
return item;
}
random -= item.weight;
}
return null;
}
}
  1. 汎用的な設計: weight: number を持つオブジェクトであれば何でも受け取れるようにジェネリクス (<T extends { weight: number }>) を使用しています。これにより、アイテムデータ、敵の行動パターン、ドロップテーブルなど様々な用途に再利用できます。

  2. 重みの計算: 最初に totalWeight を計算し、0 から totalWeight までのランダムな数値を生成します。これにより、各要素の weight の比率がそのまま確率になります(合計が100である必要はありません)。

  3. アルゴリズム: ランダム値から各要素の weight を順番に引いていき、0以下(または現在の weight 未満)になった時点でその要素を選択します。これを「ルーレット選択」や「線形探索」と呼びます。