ゲーム
予測できない面白さ:ゲームデザインにおけるランダム化の価値
EDUARDO ORIZ / UNITY TECHNOLOGIESSenior Content Marketing Manager
Apr 11, 2022|10 Min
Hero image

ゲームにランダム性を注入して、プレイヤーを惹きつけ続け、次のシーンを見たいと思わせる方法を学びましょう。この記事は Christo Nobbs が担当するシリーズの第 2 弾で、「Unity Game Designer Playbook」での彼の貢献に基づき、さらに内容を充実させたものです。Unity でプロトタイプ、制作、ゲームプレイのテストを行う方法をさらに知りたい方は、e ブックもぜひご覧ください。

以前のブログ記事で、Christo はデザイナーがどのようにすれば魅力的かつ予測のつかないゲームプレイを生み出すシステムを作ることができるか、その方法に触れました。この記事では、彼はランダム化を行う方法の例についてさらに深く掘り下げています。

Image with light purple background with the GamePlay & Design Ebook cover
プレイヤーに視覚的な手掛かりの痕跡を残す

視覚的な刺激を使うことで、プレイヤーを自分のゲームの世界のシステムをもっと探ってみたいという気にさせたり、プレイヤーのためにユニークな体験を作り出すことができます。以前お届けした「生態系を作るシステム:創発的ゲームデザイン」という記事で、一定の確率で燃え出し、予測不可能な形で火が広がっていく木ですべてが構成されたサンドボックス環境を紹介しました。このサンプルを元に、プレイヤーに斧で木を切り倒す能力を与えたものを作ってみましょう。

プレイヤーが立っている場所は平坦だとしましょう。プレイヤーは木を切り倒したとき、それがどの方向に倒れるかは予測できません。木の中に「枯れ木」、つまり枯れているのに立っている木があったらどうでしょう。いつ倒れてくるかわかりません。ゲームの中にこうした予測不可能な要素があると、プレイヤーを驚かせることができますし、プレイヤーはゲームの環境に集中してくれるようになります。

また、この世界で倒れてくる木が危ないのだとプレイヤーに知らせる視覚上の手がかりを必要なだけ追加して、プレイヤーが慎重に立ち回るようにすることができます。こうした手がかりによって、プレイヤーはより危険な枯れ木と、あまり危険でない健康な木を見分けることができます。そしてプレイヤーは、「森ですでに倒れている木を取って燃料にして、いつか倒れてくる枯れ木にぶつかって傷を負うのを避けたほうが利口じゃないか」などと、リスクの評価をすることになるでしょう。

デザイナーとして、このような系統的かつ連鎖的な反応がどこで起きるかを緻密に計画し、これらの反応を作り込んで、予測不可能な形で木が倒れてきたり、木が燃え出したりするようにしてプレイヤーが仕掛けを発動させるように仕向け、楽しいカオスを振りまきましょう。

Unity.Engine.Random を使って驚きを作り出す

Unity の Random スクリプティングクラスは、ゲーム中にランダムなデータを生成するためのアプローチを提供する静的クラスです。このクラスは .NET Framework の System.Random クラスと同じ名前であり、かつ同じような目的に使われますが、いくつか重要な違いがあります。System.Random よりも 20% から 40% 高速だということもそうした違いの 1 つです。

以下に Random クラスで利用可能な静的なプロパティおよびメソッドを示します。

静的プロパティ

  • insideUnitCircle:円内のランダムな点を返します。半径に 1.0 を指定すると、円上の点を返します(読み取り専用)。
  • insideUnitSphere:球内のランダムな点を返します。半径に 1.0 を指定すると、球面上の点を返します(読み取り専用)。
  • onUnitSphere:半径 1.0 に対応する、球面上のランダムな点を返します(読み取り専用)。
  • Rotation:ランダムな回転を返します(読み取り専用)。
  • rotationUniform:一様分布に従うランダムな回転を返します(読み取り専用)。
  • state:乱数生成器の完全な初期状態を取得または設定します。
  • value:0.0 から 1.0 までのランダムな浮動小数点数値を1 つ返します(範囲の両端を含みます)(読み取り専用)。

静的メソッド

  • ColorHSV:HSV とアルファの範囲からランダムな色を生成します。
  • InitState:乱数の種を使って乱数生成器の状態を初期化します。
  • Range:minInclusive から maxInclusive の範囲のランダムな浮動小数点数値を 1 つ返します(範囲の両端を含みます)。

以前のブログ記事では、デザインレバーの役割と ScriptableObject を使ってそれらの値を保存する方法について説明しました。minInclusive から maxInclusive の範囲(範囲の両端を含む)からランダムな浮動小数点数値を 1 つ返す Unity の Random.Range を使って適切に設計された範囲により、それらの値を置き換えることができます。minInclusive と maxInclusive を含む、範囲内の任意の浮動小数点数値は、1000 万回のランダムサンプリングを行うごとに、およそ 1 回ずつ出現します。

このアプローチにより、指定された範囲から結果に使う値を 1 つ引いてくることができます。いくつか範囲を変えて試し、そのランダムな値の範囲がゲームプレイの目的に適しているか、また自分で指定した範囲に合わせたデザインを自分で行っているかを確かめる必要もあるでしょう。

Image of a white robot watching tree branch fires in a forest
森の中のランダムな冒険

ランダム性は没入感を深めます。たとえば、どの木も 100 の HP を持っていて、斧を 1 回入れると木の HP が 25 減るとしましょう。このようなタスクはすぐに予測可能なものになり、そして退屈になります。木の HP を 76 から 100 の範囲でばらつかせてみても、どの木も 4 回斧を入れれば倒れることには変わりありません。しかし、HP を 75 か 76 のどちらかにすれば、ゲームプレイにより大きな変化が生まれます。3 回斧を入れると倒れる木と、4 回斧を入れないと倒れない木ができるからです。

このシナリオを面白くする他の方法として、HP バーの代わりに視覚的な手掛かりを使って HP の変化を伝えるというものがあります。この方法を使うと、プレイヤーはゲームプレイを通じて、木を倒すのにあと何回斧を入れる必要があるか、おおよその目安を付けるようになります。視覚的な手掛かりは、目標とするゲームプレイに向かってバランスを取ったり調整することのできる、ある種制限のかかった予測不可能性を付加してくれます。固定値の代わりに Random クラスを使うことで、変わり映えのしないタスクを楽しいものに変えることができます。

この例を拡張としては、斧を 1 回入れるごとに木の HP を 15 から 25 の範囲のランダムな値だけ減らすようにする、というものが考えられます。この方法を使うと、プレイヤーはあと何回斧を入れれば木を倒せるかを予測することが簡単にはできなくなります。プレイヤーはいつ木が倒れるかを予測するために、視覚的な手掛かりにさらに頼る必要が出てくるでしょう。木から飛んでくる破片のサイズや、幹に入るひび割れのサイズ、枝の落ち方、サウンドエフェクトなどが手掛かりとして使えるでしょう。

プレイヤーはいつ木が倒れるか正確に知ることはありませんが、時間が経ってプレイヤーが木をたくさん切り倒していくにつれ、木の倒れ方を学んでより正確な予測をするようになり、それは生き残る確率の向上につながっていくのです。

Screenshot of Random Hit Damage settings in the Inspector

ランダム性はプレイヤーに予測不可能な課題を与え、リスク計算や結果の管理をさせることを狙って導入されます。

Random クラスの使い方の例をもう 2、3 個見てみましょう。

カードゲームの乱数に重みづけをする

プレイヤーの手だけを見てカードを切る AI の敵がいるカードゲームを思い浮かべてみてください。ランダム性がなかったら、いつも同じ結果を返すのですぐに予測可能になってしまいます。

50% の確率で手を切り替えるようにしても、ランダム化としては単純すぎて、すぐにプレイヤーに手を読まれてしまうようになるでしょう。ここで、プレイヤーの手に基づいてランダム性のレイヤーを加えることを考えてみましょう。この手法を取り入れると、プールの中の 2 枚のカードから選ぶよりも動的で複雑なシステムを作り出すことができます。カードの数がもっと多いプールから 1 枚カードを選ぶ場合と比べてもそうでしょう。

敵が特定のカードを他のカードより好むようにすることで、カードテーブルに難易度のレベル分けを付けることができます。判断は、たとえば敵が与えられた数値や、攻撃前にカードの構成がどのくらい出来上がっているかに基づいて下されます。ボスのカードは他のパワーカードと組み合わせるとそのダメージ量が倍加するようにできます。こうすると、敵は手札にある程度パワーカードが揃うまで待ち、プレイヤーにとっての難易度が上がるように振る舞います。ボスのカードが特定のパワーカードと組み合わさって使われる確率を増やす、あるいは減らすことで、このような構成に「重み」を加えることができます。

Image of Heartstone card game
パーリンノイズ

ゲームの中ではさまざまな形でランダム性を活用することができます。たとえば、パーリンノイズは自然な性質を持ち、グラデーションノイズを乱数の種から生成します。これを Cinemachine で使い、3 人称視点の追跡カメラにより有機的な動きを持たせてみましょう。

Screenshot of the Perlin noise algorithm in Cinemachine

パーリンノイズを試す際は、アセットストアの Starter Assets – Third-person Character ControllerGaia などのパックや、ドキュメンテーションで Mathf.PerlinNoise の使い方をチェックしてください。

Image of the Perlin noise sampled texture (blurred black and white spots)
AI エージェントについて考える

とあるインタビューで、Bungie Studios の『Halo 2』のリードエンジニアの 1 人、Chris Butcher 氏がゲームの AI についてディスカッションを行いました。その中で氏は、「予測不可能な何かを作るのが目的ではありません。本当に欲しいのは、一貫性があってプレイヤーが特定の入力を行うことのできる人工知能なんです。そうした人工知能に対しては、プレイヤーは何らかの行動を起こして、AI が特定の方法でそれに反応すると期待することができます」と語っています。

Image of a green robot standing in the middle of a lowpoly ground outside of cubed buildings

これを考慮に入れて、ではゲームに予測できない部分も作りつつ、意志を持っているように感じられる AI エージェントを組むにはどうすればよいでしょうか。

これについて実験を行うには、Starter Assets をアセットストアで手に入る AI ツール、たとえば AI エージェントを特定の場所まで動かすツール A* Pathfinding Project Pro などと組み合わせるという方法があります。

AI エージェントがプレイヤーに向かって動き出すと、プレイヤーは攻撃されると考えます。そのとき、攻撃ではなく会話が始まったらどうでしょうか。周りを動き回ったり、空間に溶け込んでより現実感を出す NPC を追加するのはどうでしょうか。このような NPCは、自分自身に直線的に与えられるポイントを選択することもできますし、Random クラスを使って、一連のルールに基づいて論理的なポイントを選択することもできます。

弱い弓矢でプレイヤーに射撃を行う AI エージェントについて考えてみましょう。AI エージェントにとっては不幸なことに、射撃を行える最大距離が 10 メートルに設定されているため、このエージェントはキャラクターから決まった距離の範囲内にいる必要があります。AI はプレイヤーの前、10 メートル離れた場所に陣取り、射撃を行います。これは面白くない作りですし、また理想的な設定でもありません。ナビメッシュに同じ場所から射撃を行う狙撃手をもう 1 人追加した場合は特にそう感じるでしょう。

もっと面白くするには、敵がプレイヤーによって来る範囲を設定するようにしましょう。これは Random.insideUnitCircle を使い、Vector2 で返ってくる結果を Vector3 に変換して、X 軸と Z 軸の座標を取り、RandomRange を使って 2 つの値を処理し、プレイヤーを中心とした大小 2 つの円が作る領域を取得することで実現できます。

Screenshot of script used for an AI agent selecting an attack point near the main character
screenshot of design levers in the Inspector

AI エージェントはプレイヤーの近くまで動くのではなく、プレイヤーの周りの適切な範囲内にある点を選び、射撃を行わなければなりません。最小限のコーディングでゲームをもっと面白くするには、これをすべての AI エージェントに適用して、いろいろな方角からエージェントがプレイヤーに攻撃するようにします。点で見れば、エージェントが特定の距離だけ離れて攻撃してくることを知っているので AI の行動は予測可能ですが、どの方角から攻撃してくるかまでは予測できません。

image of a lowpoly space with a white crosshair over a grey and green circle

このサンプルをベースに、AI エージェントに近接攻撃を行う能力を与えることもできます。プレイヤーを中心とした円の半径をより小さくして、同じようにランダムに点を選べばよいのです。こうすると、AI エージェントは攻撃するべきタイミングで、攻撃するポイントのプールから攻撃する場所を選ぶことができます。

プレイヤーは近接攻撃のフォーメーションでエージェントが襲ってくることは知っていますが、どの方角から来るかはわかりません。振り下ろす一撃が来るのでしょうか。それとも左から右へ大きく振った攻撃でしょうか。アニメーションに現れる情報を読み取り、どんな攻撃が来るのか読む必要が出てくるでしょう。

このシナリオは同時にプレイヤーを攻撃する敵 NPC が複数いるともっと複雑になります。しかし、たとえば Ubisoft の『ファークライ 2』では、プレイヤーはすべての敵から同時に攻撃されることはありません。複数の敵がそれぞれ違うタイミングで、ランダムに攻撃してきます。この例や、他の AI シナリオについて、Game Maker's Toolkit に収録されているこちらの動画でさらに学ぶことができます。

現実世界で行われた動作の結果をゲームで忠実に再現しようと試みるなら、それには複雑でいくつもの要素を含む方程式や、よくトレーニングされた ML エージェントが必要になるでしょう。結局、そうしたエージェントは場違いなものに見えるかもしれませんし、また実装には大きな時間と技術的なオーバーヘッドが必要になります。しかも、作りが悪いと理解した上でバランスを取ったり制御したりすることが難しくなります。

Unity の Random クラスを使えば、ゲームデザイナーは他の方法を使うより少ない時間で、シーンの中で自然に見える結果を得て、しかも面白さのレベルにより厳密な制御をかけることが可能になります。もちろん、ランダム性が常に良いものだとは限りません。予測可能性は必要不可欠であることに変わりはありません。しかし、ランダム性が適切な領域で、最も良いタイミングで使われれば、プレイヤーに何度も遊びたいと思ってもらえるユニークな体験を届けることができます。