48.

UE5で配列から重複なしランダムにN個取得する方法|非復元抽出の作り方

編集

Unreal Engine 5(UE5)のブループリントで配列から重複なし(非復元抽出)でランダムにN個取り出すには、元配列のコピーを1つ作り、「ランダムなインデックスで要素を取得 → その要素をコピー配列から削除」をN回繰り返すのが基本です。削除しながら選ぶため、同じ要素が二度選ばれることはありません。

この記事の要点
  • 「重複なしランダム抽出」は、いわゆる非復元抽出(一度選んだものは選択肢から外す)のことです。
  • 基本の考え方は元配列のコピーを作り、ランダムindexで取り出しては元から削除を繰り返すこと。削除によって重複を自動的に防げます。
  • 手順は「Copy配列 → N回ループ → Random Integer in Range → Get → 結果へAdd → コピー配列からRemove Index」。
  • 別解として、配列をシャッフルして先頭からN個取り出す方法もあります。短く書けるのが利点です。
  • 落とし穴は「元配列を直接破壊しない」「Nが要素数を超えないか確認」「再現性が必要ならRandom Streamを使う」の3点です。

やりたいこと:重複しないランダム抽出とは

たとえば「10枚のカード(配列)から、同じカードが2枚出ないように3枚をランダムに選ぶ」「敵リストから重ならないように出現キャラを5体選ぶ」といったケースです。ポイントは、一度選んだ要素は二度と選ばれないようにすること。これを統計の用語で非復元抽出(without replacement)と呼びます。

単純に「ランダムなインデックスをN回引く」だけだと、同じインデックスが偶然2回出てしまい、重複が発生します。そこで「選んだら候補から外す」仕組みが必要になります。

基本の考え方

最もシンプルで分かりやすいのは、次の流れです。

  • (1) 元配列のコピーを1本作る(作業用の一時配列)。
  • (2) 0 〜「コピー配列の現在の要素数 - 1」の範囲でランダムなインデックスを1つ作る。
  • (3) そのインデックスの要素を取得し、結果用配列へ Add する。
  • (4) 取得した要素をコピー配列から Remove Index で削除する。
  • (5) (2)〜(4) を、欲しい個数 N 回ぶん繰り返す。

削除するたびにコピー配列は1つずつ短くなります。次のループでは短くなった配列の長さに合わせてランダム範囲も狭めるため、すでに選んだ要素は候補から消えており、自然に重複なしが実現します。元配列そのものには手を付けないので、元データは保持されたままです。

手順:ループで1つずつ抜き出す

ブループリントでの具体的な組み立て手順です。ここでは元配列を SourceArray、取り出したい個数を Num、結果を入れる配列を ResultArray とします。

// 手順(ノードの流れ)

1. 作業用変数 WorkArray を用意し、SourceArray を代入してコピーを作る

2. ResultArray を空にする(Clear)

3. For Loop(First Index = 0, Last Index = Num - 1)を組む

├ Random Integer in Range

│ Min = 0

│ Max = (WorkArray の Length) - 1

├ WorkArray を Get(上で得たインデックス)→ 要素を取得

├ ResultArray に Add(取得した要素)

└ WorkArray を Remove Index(同じインデックス)で削除

4. ループ終了後、ResultArray に重複なしのN個が入る

ここで重要なのは、「Get で使ったインデックス」と「Remove Index で削除するインデックス」を必ず同じ値にすることです。Random Integer in Range の結果を一旦ローカル変数に保存し、Get と Remove Index の両方へつなぐと安全です。

また、Random Integer in Range の Max は「ループのたびに WorkArray の最新の Length - 1」を再計算する必要があります。要素を削除するたびに配列が短くなるため、ループの先頭で毎回 Length を取り直してください。Length を最初に1回だけ取って使い回すと、範囲外アクセスの原因になります。

別解:シャッフルして先頭からN個

もうひとつの定番が、配列全体をシャッフルしてから先頭N個を取り出す方法です。配列ライブラリの Shuffle ノードで順番をランダムに入れ替え、その先頭から欲しい数だけ取り出します。

// シャッフル方式

1. WorkArray = SourceArray のコピー

2. WorkArray を Shuffle(順番がランダムに入れ替わる)

3. For Loop(0 〜 Num - 1)で WorkArray を Get(i) し、ResultArray に Add

シャッフル方式は記述が短く読みやすいのが利点です。シャッフル自体が並びを一意に入れ替えるので、先頭からN個を取れば自然に重複なしになります。一方で、N が要素数より小さくても配列全体をシャッフルするため、要素数が極端に多く N がごく少ない場合は、前述のループ方式(必要な回数だけ抜き出す)のほうが処理量を抑えられることがあります。用途に応じて選んでください。

なお、Shuffle ノードの有無や正確な名称・挙動はエンジンのバージョンによって異なる場合があります。お使いの UE5 で利用できるか、また乱数シードを指定できるかは公式ドキュメントでの確認を推奨します。

具体的なノード構成(ループ方式)

ループ方式を関数(またはマクロ)としてまとめておくと再利用しやすくなります。代表的な構成は次の通りです。

[入力] SourceArray(配列) / Num(取得数, Integer)

[出力] ResultArray(配列)

────────────────────────

・Set WorkArray = SourceArray (コピー作成)

・Clear ResultArray

・Clamp: SafeNum = Min(Num, WorkArray Length) (超過防止)

・For Loop First=0 Last=SafeNum - 1

├ Length (WorkArray) → 引き算 (-1) → Max

├ Random Integer in Range (Min=0, Max) → Idx に保存

├ Get (WorkArray, Idx) → Add (ResultArray)

└ Remove Index (WorkArray, Idx)

・Completed → Return ResultArray

このように関数化しておけば、配列の型(Actor、Integer、構造体など)が違っても同じ考え方で流用できます。型ごとに専用ノードを用意したくない場合は、まずは具体的な型で1つ作り、必要に応じて複製していくのが現実的です。

落とし穴と注意点

つまずきやすいポイント
  • 元配列を直接破壊しない:Remove Index は必ずコピー(WorkArray)に対して行います。元の SourceArray に直接 Remove すると、元データが失われます。UE5では配列をそのまま代入するとコピーが作られるため、まず別変数へ代入してから操作しましょう。
  • N が要素数を超える場合:Num が配列の要素数より大きいと、途中で配列が空になり範囲外アクセス(Index out of bounds)でエラーや空要素が発生します。事前に Min(Num, Length) でクランプするか、要素数を超えないことを保証してください。
  • ランダム範囲は毎回更新:Random Integer in Range の Max は「その時点の WorkArray の Length - 1」です。削除で短くなるので、ループ内で毎回 Length を取り直します。固定値にすると重複・範囲外の原因になります。
  • Get と Remove のインデックスを揃える:同じインデックス値を Get と Remove Index の両方へ渡します。別々に乱数を引くとズレて意図しない要素を消してしまいます。
  • 再現性が必要なら Random Stream:通常の Random Integer in Range は毎回結果が変わります。デバッグやリプレイ、シード固定のステージ生成など同じ結果を再現したい場合は Random Integer in Range from Stream(Random Stream)を使い、シードを管理します。

2つの方法の比較

項目ループで削除方式シャッフル方式
分かりやすさ仕組みが追いやすいノード数が少なく簡潔
処理量必要なN回ぶんだけ抜き出す配列全体を毎回シャッフル
向いている場面要素数が多くNが少ないときNがLengthに近い/全体を使うとき
重複防止削除により保証並べ替えにより保証

よくある質問(FAQ)

Q1. 同じ要素が値として複数入っている配列(例:1, 2, 2, 3)ではどうなりますか?

この方法が防ぐのは「同じインデックスを二度選ぶこと」です。元の配列に同じ値の要素が複数あれば、それぞれ別の要素として扱われるため、結果に同じ値が複数入る可能性はあります。「値としての重複」まで除きたい場合は、抽出前に元配列の重複値を取り除く(Set的に一意化する)か、抽出後にチェックする処理を追加してください。

Q2. 毎回同じ結果にしたい(再現したい)のですが?

Random Stream を使います。Random Integer in Range の代わりに Random Integer in Range from Stream を使い、Random Stream 変数のシードを固定すれば、同じシードからは同じ抽選結果が得られます。シードを保存・共有すれば他環境でも同じ結果を再現できます。

Q3. 取得した要素を元配列からも消したい場合は?

その場合は「コピーを作らず元配列に対して削除する」設計も可能ですが、元データが変化することを理解したうえで行ってください。多くのケースでは元配列を保持しつつ抽出結果だけ欲しいはずなので、まずはコピー方式を基本とし、本当に元から消したいときだけ元配列を直接操作するのが安全です。

以上が、UE5ブループリントで配列から重複なしにランダムなN個を取得する方法です。基本は「コピーして、ランダムに取り出しては削除」。これさえ押さえれば、カード抽選・敵のランダム出現・ガチャ的な抽選など幅広く応用できます。なお、各ノードの正確な名称や挙動はエンジンのバージョンで変わる場合があるため、細部は公式ドキュメントの確認を推奨します。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 床の上に乗ったらイベントを発生させる方法
  2. OverlapAllDynamicとOverlapAllの違い
  3. タイトル画面を作る方法
  4. サードパーソンテンプレートでのキャラクター表示の仕組みと非表示にする方法
  5. ボタンに文字を記載する方法
  6. Event ActorBeginOverlapとOn Component Begin Overlapの違い
  7. キャラクターに特定のオブジェクトとの当たり判定を付ける
  8. 特定のオブジェクトに触れたとき、キャラクターが倒れるようにする
  9. 動いているオブジェクトに静止しているキャラクターが当たり判定を持たない原因と解決方法
  10. 「On Component Hit」に「Cast To ~」で複数のクラスを指定する方法
  11. Blenderファイルをインポートする方法
  12. 鏡を作成する方法
  13. レベルブループリントでキャラクターの出現を設定する方法
  14. サードパーソンテンプレートにおけるキャラクター出現の定義
  15. アイテムに近づいたらボタンを押してイベントを発火させる方法
  16. 画面の中央にメッセージを表示する方法
  17. どこからでも呼び出せるカスタムイベントを作成する方法
  18. カスタムイベントに引数を追加する方法
  19. 「Get Overlapping Actors」から特定のクラスの場合のみ処理を実行する方法
  20. オブジェクトに近づいている間だけメッセージを表示する方法
  21. PCの画面を操作するUIを作る方法
  22. コンテンツブラウザに画像を追加する方法
  23. SetInputMode_UIOnlyを取り消す方法
  24. 特定の画像の上にマウスカーソルを置いたら手マークにする方法
  25. オブジェクトがアウトライナーで選択できない原因と解決策
  26. PlayerStartを作成する方法
  27. メニュー画面を作成して開く方法
  28. 「Esc」キーを押してメニュー画面を開く方法
  29. イベントの「On Clicked」と「On Pressed」の違い
  30. 「Set Input Mode」の種類と使い方
  31. 「Set Game Paused」の使い方と詳細解説
  32. Motion Matchingとは?
  33. 「GameMode」と「GameModeBase」の違い
  34. マップに配置したTargetPointを取得する方法
  35. TargetPointにタグをつけて取得する方法
  36. Spawnしたインスタンスがイベントを実行する方法
  37. 特定の時間ごとに処理を実行する方法
  38. 数値をランダムで出力する方法
  39. ThirdPersonテンプレートでキャラクターの移動を歩くように変更する方法
  40. MaxWalkSpeedを変更する方法
  41. しゃがむ動作を導入する方法
  42. キャラクターのアニメーションを設定する方法
  43. 導入済みのプラグインを確認する方法
  44. Motion Matchingの導入と必要なプラグイン
  45. プレイヤーを非表示にする方法
  46. カメラを傾ける角度を制限させる方法
  47. 配列からランダムに重ならない要素を特定の数取得する方法
  48. カメラの映す画面に文字やエフェクトを付ける方法
  49. キャラクターやメッシュを非表示にした際にカメラが移動しなくなる問題の解決方法
  50. プライマリーデータアセットを活用する方法
  51. プレイヤーのHPといった変数を定義する最適な場所
  52. カメラに映った画面をスクリーンショットとして保存する方法
  53. ゲーム内のカメラ映像を保存して再表示する方法
  54. HighResShot を使って高解像度の画像を保存する方法(UE5)
  55. HighResShotで保存した画像のファイル名を取得する方法
  56. SceneCapture2DとFrameGrabberの画像保存方法の比較
  57. SceneCapture2Dを使用して画像を保存・取得する方法
  58. HighResShotとTake High Res Screenshotの違い
  59. ゲーム終了ボタンを作成する方法
  60. 「Save Game To Slot」の戻り値がfalseになる問題の解決方法
  61. 画面上にメッセージを指定された時間表示させる方法
  62. シェーダコンパイル時間を短縮する方法
  63. 「Take High Res Screenshot」実行時に「シェーダをコンパイル」に長時間待たされる問題とその解決策
  64. データベースを活用する方法
  65. UE5.5がインストールされた環境にUE5.4を追加で導入する方法
  66. World PartitionとWorld Compositionの違い
  67. オープンワールドテンプレートとは?
  68. ポーンをスポーンさせても視点を切り替えない方法
  69. キャラクター同士がすり抜けてしまう問題の解決方法
  70. キャラクターの外見を動的に変更
  71. World Partitionでインスタンスが「アンロード済み」になる問題
  72. データ アセットとデータ テーブルの違い
  73. コンポーネントイベントグラフ内で親クラスの変数にアクセスする方
  74. エディターのソースコードの自動保存の頻度を高める方法
  75. SpawnActorでSpawn Transform Rotationが反映されない理由
  76. ミニマップを表示しポーンの位置を反映する方法
  77. RInterp ToとVInterp Toの違い
  78. 毎秒実行するイベントの定義方法
  79. Niagara のエフェクトにコリジョンを持たせる方法
  80. 「Overlap」と「Hit」の違い
  81. OverlapはあるがHitがない原因
  82. Overlapした位置の座標を取得する方法
  83. ブループリントでレベル間のパラメータを受け渡す方法

最近更新/作成されたページ