RTS Terrain Studio 1.0.0
マニュアル · バージョン 1.0.0

RTS Terrain Studio

台地、くっきりとした崖、スロープ、ペイントした地面、森、水、装飾オブジェクトを備えたクラシック RTS スタイルのマップを、Unity エディターでもゲーム内でも作成できます。戦場の霧、経路探索、地形クエリも組み込まれています。

Unity 6.3 LTSURP 17.3エディターとゲーム内Mirror、A* Pathfinding Project、Unity NavMesh

概要

RTS Terrain Studio は、クラシックスタイルの RTS(リアルタイムストラテジー)ゲーム向けのマップエディターで、Unity 6.3 LTS と Universal Render Pipeline (URP) 17.3 に対応しています。くっきりとした垂直の崖を持つ台地の造形、地面のペイント、森の配置、湖や川への水張り、装飾オブジェクトの配置、プレイヤーの開始地点の設定に加えて、ユニットが歩ける場所や建物を建てられる場所の確認も行えます。完成したマップは、そのままゲームプレイに使用できます。戦場の霧(Fog of War)、経路探索、地形クエリが組み込まれており、これらにコライダーは一切必要ありません。

エディターは 2 つの場所で動作し、どちらも同じツールと同じマップファイル形式(.rtsmap)を使用します。

  • Unity エディター内:Tools ▸ RTS Terrain Studio ▸ Editor Window とシーンビューを使用します。ここでは、ゲームと一緒にリリースするマップを作成します。
  • ゲーム内:RTSRuntimeEditor コンポーネントを使用します。UI Toolkit のインターフェースを備えたフル機能のエディターです。リリースしたゲームの中で、プレイヤーや MOD 制作者がマップを作成、保存、読み込みし、テストプレイできます。

このパッケージにはサードパーティ製のアート素材は含まれていません。その代わりに、1 つのメニューコマンドでデモコンテンツ一式(テクスチャ、ローポリの木とプロップ、2 つのタイルセット、3 つのマップ、すぐにプレイできる 3 つのシーン)を生成できます。パッケージをインポートしてから 1 分以内に、すぐに試せる内容がそろいます。

Gameplay Demo シーン:崖、川、森のある台地のマップと、戦場の霧の下にいるサンプルユニット
デモマップ Two Rivers (1v1) を使用した RTS Terrain Studio - Gameplay Demo シーン。

このマニュアルは、はじめてマップを作る人からソースコードを拡張する人まで、すべての人を対象としています。はじめて使う場合は、以下の最初の 5 分間の手順に従い、その後で各章を順番に読んでください。プログラマー向けの情報として、ソースコード全体にコメントが付いており、すべてのパブリック型に XML ドキュメントがあります。また、フォルダー構成セクションで、どこに何があるかを確認できます。

マップの構造

クラシック RTS ゲームのマップエディターを使ったことがあれば、ここで紹介する考え方の多くはなじみがあるはずです。マップは正方形のセル(デフォルトでは幅 2 m)が並んだグリッドです。セルの角は頂点と呼ばれ、ほとんどのデータは頂点ごとに、またはマップ全体を覆う高解像度テクスチャに保存されます。

概念説明
崖レベル(cliff level)頂点ごとに設定する、0~15 の整数の高さの段階です。隣り合う頂点のレベルが異なると、垂直な崖の壁面ができます。デフォルトのレベルの高さは 2 m です。新しいマップはレベル 2 から始まるため、地面を掘り下げて湖や川を作れます。
微細高さ(fine height)丘や谷を作るために崖レベルの上に重ねる、滑らかな高さのオフセットです。頂点の地面の高さは CliffLevel × CliffLevelHeight + Height で求められます。
スロープ(ramp)角にスロープフラグが付いたセルは、あるレベルから次のレベルへ滑らかにつながるため、ユニットが歩いて登れます。
崖タイプ(cliff type)崖の壁面の素材です(たとえば、岩の崖や草に覆われた崖)。1 つのタイルセットに最大 4 種類を含められます。
地面レイヤー(ground layer)最大 16 種類あるペイント用の地面テクスチャ(草、土、岩…)の 1 つで、スプラットマップに保存されます。
水頂点ごとの水面の高さです。水深によって、浅水域(歩行可能)か深水域(歩行不可)かが決まります。
ディテールと木ペイントした草や花(テクセルごとの密度)と、1 本ずつ配置した木です。
装飾オブジェクト(decor)マップ上に配置するあらゆるプレハブ(岩、遺跡、木箱、柵、建物)です。
通行判定(pathing)通行セルごとの状態(歩行可能、建設可能、飛行可能、浅水域または深水域、崖、ブロックされている、急斜面)です。マップから自動的に算出され、その上に通行オーバーライドをペイントできます。
マーカー名前付きのリージョン(ゲームロジック用のキーと値のプロパティを持つ矩形)、プレイヤーの開始地点、カメラ位置などの名前付きポイントです。
タイルセット見た目(地面レイヤー、崖タイプ、水のスタイル)を定義する RTSTileset アセットです。クラシック RTS のマップエディターにある「Summer」や「Winter」のタイルセットに相当します。
ライブラリRTSVegetationLibrary(ディテールと木)と RTSDecorLibrary(装飾プレハブ)です。ペイントや配置の際に使うパレットになります。

シーン内では、マップは RTSTerrain コンポーネントを持つゲームオブジェクト上に存在します。メニュー GameObject ▸ RTS Terrain Studio ▸ RTS Terrain を使うと、サブシステムのコンポーネント(RTSVegetationRenderer、RTSWaterRenderer、RTSDecorManager)も追加されます。地形は、プロジェクト内の .rtsmap ファイル(RTSMapAsset)からマップを読み込みます。表示されるチャンクメッシュ、草、水は、そのファイルから生成されます。これらがシーンに保存されることはないため、シーンのサイズは小さいままです。

最初の 5 分間

すべての機能が動作する様子を最も手早く確認できる方法です。パッケージをインポートする以外に、セットアップは必要ありません。

  1. Tools ▸ RTS Terrain Studio ▸ Validate Project を開き、Fix All をクリックします。これにより、URP、戦場の霧のレンダラー機能、水が使用するカメラテクスチャ、カラースペースが設定されます。実際に変更が行われる前に、確認ダイアログで変更内容が説明されます。
  2. Tools ▸ RTS Terrain Studio ▸ Create Demo Content を実行します。テクスチャ、モデル、タイルセット、マップ、シーンの生成中は進行状況バーが表示されます。完了すると、プロジェクトウィンドウで RTS Terrain Studio - Gameplay Demo シーンが選択された状態になります。
  3. そのシーンをダブルクリックして開き、Play をクリックします。
  4. マウスの左ボタンでユニットを選択し(クリック、またはボックスをドラッグ)、地面を右クリックして移動させます。WASD キーまたは矢印キーでパンし、マウスホイールでズームします。F1 キーを押すと、すべての操作方法が表示されます。
  5. 再生モードを停止し、RTS Terrain Studio - Editor Playground シーンを開きます。Tools ▸ RTS Terrain Studio ▸ Editor Window を開いてツールを選択し、シーンビューでペイントします。Shift キーを押したままにすると、逆の操作になります(たとえば、上げる代わりに下げます)。Ctrl+Z キーで元に戻せます。
  6. RTS Terrain Studio - In-Game Editor Demo シーンを開き、Play をクリックします。これが、ゲームのプレイヤーが使用するエディターです。F5 キーを押すとユニットを使ってマップをテストプレイでき、F2 キーを押すとエディターの表示と非表示を切り替えられます。

Tools ▸ RTS Terrain Studio ▸ Welcome ウィンドウはプロジェクトごとに 1 回だけ自動で開き、同じ手順をボタンとして表示します。メニューからいつでも再び開けます。

どこから始めるか

やりたいこと参照先
プロジェクトを正しくセットアップしたいプロジェクトのセットアップと Tools ▸ RTS Terrain Studio ▸ Validate Project
最初のマップを順を追って作成したいクイックスタートと Tools ▸ RTS Terrain Studio ▸ New Map Wizard...
エディターウィンドウとすべてのツールについて知りたいRTS Terrain Studio ウィンドウとシーンビューでのペイント
ゲーム内でプレイヤーがマップを作れるようにしたいゲーム内エディター
ゲームやマップエディターを Web ブラウザーで公開したいWeb(WebGL)ビルド
ユニットが探索するまでマップを隠したい戦場の霧
Mirror、A* Pathfinding Project、または Unity NavMesh を使いたい連携
地形にクエリを実行したい、または C# で独自のツールを作りたいスクリプティングガイド
見た目や動作が正しくない箇所を修正したいトラブルシューティング

主な機能

以下の編集ツールはすべて、Unity エディターとゲーム内エディターの両方に用意されています。どちらも同じツールクラス(RTSTool のサブクラス)を使用しているためです。この一覧に記載している名前は、ユーザーインターフェースに表示される名前と同じです。

Decor タブを開いた RTS Terrain Studio のエディターウィンドウ
RTS Terrain Studio ウィンドウ:ツールカテゴリ、ツールボタン、対称の設定、自動生成されたサムネイル付きの装飾パレット。

地形と崖

  • マップサイズは 1 辺あたり 16~1024 セルで、16 セル刻みです。New Map Wizard には Tiny (64)、Small (96)、Medium (128)、Large (192)、Huge (256)、Epic (384)、Epic XL (512) のプリセットがあり、任意のカスタムサイズも指定できます。セルサイズと崖レベルの高さは自由に設定できます。
  • 16 段階の崖レベル(0–15)と、プロシージャルに生成される丸みのある崖の壁面。各崖セル内のサブグリッド上で輪郭線をトレースし、ノイズと傾きによって壁面を自然な岩のように見せます(これらはすべて RTSTerrainSettings で設定します)。
  • レベルの上に微細高さを重ねられるため、台地の上に丘を作れます。平滑な地面には、セルの 1 辺あたり 1~4 個のクアッドを使用できます。1 ではクラシック RTS らしい見た目になり、2~4 ではより滑らかな丘になります。
  • Height ツール:Raise / Lower、Smooth、Flatten、Set Height、Noise、Terrace、Erode。
  • Cliffs ツール:Raise / Lower Level、Set Level、Ramp、Cliff Type、Clean Cliffs。
  • タイルセットごとに最大 4 種類の崖タイプを頂点ごとにペイントでき、壁面にはトライプラナーテクスチャリングが適用されます。
  • チャンクごとに MeshCollider をオプションで追加でき、配置するレイヤーは自由に選択できます。物理演算を使わないクエリ(GetHeight、GetNormal、Raycast、GetCliffLevel、IsWater、GetPathing、IsWalkable、IsBuildable)は、コライダーがなくても動作します。
  • 1 つのシーンに任意の数の地形を配置できます。各地形は独自の位置と Y 軸回転を持ちます。RTSTerrain.FindAt(worldPosition) と RTSTerrain.RaycastAll(ray, out hit) で、該当する地形を見つけられます。
  • アンカーを指定して、マップをリサイズ(拡大・縮小のどちらも可能)できます。ハイトマップは 16 ビット PNG または 16 ビット RAW 形式でインポート・エクスポートでき、スプラットマップは PNG 形式でエクスポートできます。

ペイント

  • タイルセットごとに 16 個の地面レイヤーがあり、4 枚の RGBA スプラットマップにペイントされます。デフォルトではセルあたり 4 スプラットテクセル(1~8 から選択可能)のため、ペイントした境界はセルグリッドよりもはるかに細かくなります。
  • Paint ツール:Paint Layer(ソフトブラシ)、Tile Paint(クラシック RTS スタイルの、セル単位でくっきり塗り分けるペイント)、Fill / Replace、Smooth Paint、Auto Paint。
  • Auto Paint は、RTSAutoPaintProfile アセットに保存されたルールに従ってペイントします。ルールでは、高さ、傾斜、崖レベル、崖までの距離、水までの距離、ノイズを条件にできます。Auto Paint ツールをブラシとして使うことも、マップジェネレーターでプロファイルをマップ全体に適用することもできます。
  • レイヤー間のハイトブレンド(高さベースのブレンド)、レイヤーごとのアンチタイリング、タイルセット全体のマクロバリエーション、レイヤーごとの色合い・滑らかさ・メタリック・法線の強さ。
  • レイヤーは、自身が優勢な場所の通行判定を変更できます。たとえばデモのタイルセットでは、クラシック RTS ゲームの建設不可タイルのように、Rock と Blight が建設不可になっています。
  • ブラシ:Circle、Square、Diamond、または任意のテクスチャをスタンプとして使う Custom(RTSBrushShape アセット)。半径、強さ、硬さ、オプションの減衰カーブ、回転、ランダム回転、間隔、ジッターを設定できます。
  • 公平な対戦マップのための対称ペイント:Mirror X、Mirror Z、Mirror XZ、Rotate 2(180° の点対称)、Rotate 4(90°、正方形のマップのみ)、Mirror Diagonal(正方形のマップのみ)。対称ペイントは、オブジェクトの配置を含むすべてのツールで機能します。

植生

  • 草、花、小さな植物などのディテール。レイヤーごとの密度としてペイントされ、GPU でメッシュまたは交差クアッドとして風の揺れ付きで描画されます。
  • 木は 1 本ずつ配置することも、ブラシで散布することもできます。木は GPU インスタンシングで描画する(オプションでコライダーのプロキシ付き)ことも、木材の伐採などのゲームプレイ用にゲームオブジェクトとしてスポーンすることもできます。
  • 木は、そのフットプリント(占有範囲)によって自動的に通行をブロックします。
  • Vegetation ツール:Paint Details、Erase Details、Place Trees、Erase Trees。
  • シェーダー RTS Terrain Studio/Vegetation は、風、光の透過、インスタンスごとの色合いを加えます。GPU インスタンシングに対応したほかのマテリアルも使用できます。
  • RTSVegetationRenderer の品質設定:ディテール密度、ディテール描画距離、木の描画距離それぞれの倍率。

水

  • 水面の高さは頂点ごとに設定されるため、湖をそれぞれ異なる高さに置けます。水は、Burst ジョブで構築される、チャンク分割された透明メッシュとして描画されます。
  • 浅水域と深水域は水深(WaterProfile.shallowDepthLimit)によって決まります。クラシック RTS ゲームと同様に、浅水域は歩行可能で、深水域は歩行できません。
  • シェーダー RTS Terrain Studio/Water は、水深に応じた色、スクロールする法線、波、屈折、岸辺の泡、岸辺のソフトなフェードを備えています。スタイルはタイルセットの WaterProfile で決まります。
  • Water ツール:Paint Water、Erase Water、Flood Fill。

装飾オブジェクト(ドゥーダッド)

  • どのプレハブでも装飾オブジェクトにできます。プロトタイプは、自動生成されるサムネイルとともに RTSDecorLibrary に格納されます。
  • 各プロトタイプは、ゲームオブジェクトとしてスポーンされるか(ゲームプレイで相互作用するオブジェクト向け)、GPU インスタンシングで描画されます(大量の静的なプロップ向け)。
  • ランダムな回転・スケール・色合い、傾斜への位置合わせ、グリッドスナップ、通行判定用のフットプリント(円形またはボックス)。
  • Decor ツール:Place Decor(単体配置または散布)、Erase Decor、Select Decor(移動、回転、スケール、複製、削除)。

通行判定と経路探索

  • 通行マップは自動的に算出され、編集中も常に最新の状態に保たれます。崖、傾斜、水深、木と装飾オブジェクトのフットプリント、地面レイヤーのルールが考慮されます。
  • Pathing ツール:Paint Pathing(歩行不可、建設不可、強制歩行可能などのオーバーライド)と Clear Pathing。結果は、各エディターの通行オーバーレイで確認できます。
  • 組み込みの経路探索エンジン(RTSPathfinder):通行グリッド上で動作する Burst コンパイル済みの A* で、8 近傍の探索、角の通り抜けの防止、ユニット半径(クリアランス)の考慮、経路の平滑化に対応しています。サードパーティ製のパッケージは必要ありません。
  • RTSPathfindingService による、多数のユニット向けの非同期の経路リクエスト。

戦場の霧

  • クラシック RTS のルール:各チームが独自の視界を持ち、探索済みエリアは記憶されます。デフォルトでは、ユニットは自身のいる崖レベルとそれより低いレベルしか見えません。木や装飾オブジェクトで視界を遮ることもできます(LineOfSightMode と、プロトタイプの blocksVision オプション)。
  • 最大 32 チーム、同盟チーム間の視界共有(RTSFogOfWarManager.SetAlliance)、滑らかにぼかした霧のテクスチャ、ミニマップへの対応。
  • URP のレンダラー機能(RTSFogOfWarRendererFeature)が、使用しているマテリアルに関係なく、画面上のすべてを暗くします。この機能がない場合は、パッケージ独自のシェーダーが自ら霧を適用します。
  • RTSFogOfWarRevealer を持つユニットはマップを可視化し、RTSFogOfWarHideable を持つオブジェクトは視界に入っていない間は非表示になります。建物やプロップは、いったん発見されると「ゴースト」として表示されたままにできます。
  • 可視性は Burst ジョブで計算されます。

ゲーム内エディター

  • GameObject ▸ RTS Terrain Studio ▸ In-Game Editor で追加します。メニューバー、カテゴリバーとツールバー、パレットとサムネイルを備えたツールパネル、ステータスバー、ツールチップ、通知を備えた UI Toolkit インターフェースです。
  • マップブラウザーによる New、Open、Save、Save As。マップはデフォルトで Application.persistentDataPath の下に保存されます。Web ビルドではブラウザーのストレージに保存され、Download と Import を使ってコンピューターとの間でやり取りできます。マップの生成、マップ情報の編集、マップのリサイズも行えます。
  • メモリ使用量の上限付きの元に戻す・やり直し、キーボードショートカット、対称、グリッド・通行判定・マーカーのオーバーレイ。
  • テストプレイの切り替え(F5)。編集中のマップでサンプルゲームプレイを開始し、終了後はエディターに戻ります。
  • 独自のエディターカメラ(RTSRuntimeEditorCamera)とギズモを備え、UnityEditor API に依存しないため、プレイヤービルドでも動作します。

マップジェネレーター

  • シードを使うプロシージャルジェネレーター(RTSMapGenerator)。スタイルプリセットとして Plains、Hills、Highlands、Islands、Canyon、Arena があります。
  • 台地と崖、スロープ(接続性チェック付き)、湖と海、マップ境界、自動ペイント、ディテール、森、装飾オブジェクト、開始地点を、任意の対称モードで生成します。
  • 同じシードからは常に同じマップが生成されます。設定は RTSMapGeneratorPreset アセットとして保存できます。
  • New Map Wizard、エディターウィンドウ、ゲーム内エディター、または独自のコードから実行できます。

サンプルゲームプレイ

  • RTS カメラ(パン、画面端スクロール、ズーム、回転、マップ範囲の制限)、ユニットの選択(クリック、ボックス、Shift キー、ダブルクリック、コントロールグループ)、命令(隊形での移動、キュー、停止、待機、パトロール)。
  • 地形の高さに沿って移動し、戦場の霧に覆われた範囲を可視化し、任意の経路プロバイダーを使用するプレースホルダーユニット。
  • クリックでカメラを移動できるミニマップ、HUD、建設可能フラグに従う建物配置のデモ、木の伐採デモ。
  • RTSDemoGame は、開始地点のある任意の地形上にこれらすべてをセットアップします。GameObject ▸ RTS Terrain Studio ▸ Demo Game で追加します。

連携

これらはオプションです。各連携は依存パッケージがインストールされている場合にのみコンパイルされるため、依存パッケージを削除してもプロジェクトが壊れることはありません。

連携追加される機能
Mirror(マルチプレイヤー)ホストが参加したクライアントにマップをストリーミングするため、プレイヤーはユーザーが作成したマップをオンラインでプレイでき、ダウンロードしたマップはキャッシュされます。ユニットはサーバー権威型で、各クライアントは自チームの戦場の霧を持ちます。また、RTSFogOfWarInterestManagement は霧に隠れた敵の情報を決して送信しません(マップハック対策)。
A* Pathfinding ProjectRTS の通行マップから生成され、編集のたびに差分更新されるグリッドグラフ(ユニットサイズごとに 1 つ)と、RVO によるローカル回避を使いながら AIPath で移動するユニット。
Unity AI Navigation(NavMesh)地形チャンクと通行マップから構築され、編集後に差分更新される NavMesh と、NavMesh の経路を返すか NavMeshAgent を駆動する経路プロバイダー。

エディターのワークフロー

  • 8 つのツールカテゴリと Map、Settings のタブを持つ、ドッキング可能な 1 つの RTS Terrain Studio ウィンドウ。アイコン付きの大きなツールボタン、状況に応じた設定、サムネイル付きのパレット、ステータスバーを備えています。
  • シーンビューとの連携。ブラシは地形シェーダーで描画され、地形に投影されます。さらに、グリッドと通行判定のオーバーレイ、対称ガイド、ショートカット([ / ] でブラシサイズ、- / = で強さ、1–8 でツールカテゴリ、G でグリッド、P で通行オーバーレイ、R でランダム回転)も用意されています。ショートカットは Edit ▸ Shortcuts...(macOS では Unity ▸ Shortcuts...)でキーを割り当て直せます。
  • マップの編集は Unity のアンドゥ履歴に含まれるため、Ctrl+Z と Ctrl+Y キーが通常どおり機能し、各ステップの名前も表示されます。アンドゥに使用するメモリの上限は設定で変更できます。
  • タイルセット(レイヤーのドラッグ&ドロップ、プレビュー、テクスチャ配列の自動ビルド)、植生ライブラリと装飾ライブラリ、ブラシ形状、自動ペイントプロファイル、RTSTerrain コンポーネント用のカスタムインスペクター。.rtsmap インポーターはミニマップのプレビューを表示します。
  • Welcome ウィンドウ、New Map Wizard、ワンクリックで修正できる Project Validator。

パフォーマンス

  • 地形メッシュは 16、32(デフォルト)、または 64 セルのチャンクに分割されます。チャンクは Burst ジョブ内で Mesh.MeshDataArray を使って構築され、編集したチャンクだけが再構築されます。
  • ブラシ操作、メッシュ生成、通行判定の算出、クリアランス、A*、戦場の霧、植生インスタンス、水メッシュ、マップジェネレーター、自動ペイント、ミニマップのベイクといった負荷の高い処理は、Burst コンパイル済みのジョブ(Unity ジョブシステム)で実行されます。
  • ディテールは Graphics.RenderMeshPrimitives でプロシージャルに描画されます。木とインスタンス描画の装飾オブジェクトは Graphics.RenderMeshInstanced を使用します。これらはすべて、パッチ単位で視錐台と距離によってカリングされます。
  • シェーダーは SRP Batcher に対応しており、すべてのチャンクで 1 つの地形マテリアルを共有します。
  • テクスチャ配列は、選択した解像度(256–2048)で、アクティブなプラットフォーム向けの形式(BC7、ASTC 6×6、ETC2、または DXT5)に圧縮されます。
  • ホットパスではフレームごとのガベージアロケーションが発生せず、コンポーネントを無効にするとすべてのネイティブメモリが解放されます。

設計方針の概要

RTS 向けの地形ツールについて特によく寄せられる質問と、それぞれに対する RTS Terrain Studio の答えをまとめました。

質問回答
1 つのマップで使用できる地面テクスチャはいくつですか? タイルセットごとに 16 個の地面レイヤーと 4 種類の崖タイプを使用でき、タイルセットの数に制限はありません。この上限には技術的な理由があります。16 個のレイヤーは 4 枚の RGBA スプラットマップ(レイヤーごとに 1 チャンネル)にちょうど収まります。また、16 個の地面レイヤーと 4 つの崖レイヤーはすべて、アルベド・高さ用と法線用の 2 つのテクスチャ配列(各 20 スライス)にまとめられるため、1 つの地形マテリアルですべてのチャンクを描画できます。
地形にコライダーはありますか? 物理演算を使わずにクエリできますか? どちらも可能です。各チャンクには、任意のレイヤーを設定した MeshCollider を追加できます(RTSTerrainSettings.colliderMode)。クエリ API(GetHeight、GetNormal、Raycast、GetCliffLevel、IsWater、TryGetWaterHeight、GetPathing、IsWalkable、IsBuildable)は物理演算を一切使用しません。編集モードでもランタイムでも、コライダーをオフにした状態でも動作し、見たままの地表を正確に返します。
複数のマップを使って大きなワールドを構築できますか? はい。1 つのシーンに RTSTerrain コンポーネントをいくつでも配置でき、それぞれに個別の位置と Y 軸回転を設定できます。RTSTerrain.All、RTSTerrain.FindAt、RTSTerrain.RaycastAll は、それらすべての地形を対象に動作します。チャンク単位のレンダリングにより、512×512 セルのマップも実用的に扱えます。
ディスプレイのスケーリングが有効でも、ブラシはカーソルの位置に正確に適用されますか? はい。Unity エディターでは DPI スケーリングを正しく処理する HandleUtility.GUIPointToWorldRay を使用し、ゲーム内エディターでは Input System から取得したポインター位置を使って Camera.ScreenPointToRay を呼び出します。ブラシカーソルは地形シェーダーが地表に投影して描画するため、見えている位置がそのままペイントされる位置になります。
どのテクスチャ形式とサイズを使用できますか? 任意のサイズと形式の通常の Unity テクスチャを使用できます。タイルセットのインスペクターがテクスチャのサイズを調整し、自動で Texture2DArray にまとめます(1 スライスあたり 256~2048 ピクセル、圧縮・非圧縮のどちらも可)。チャンネル構成はドキュメントで明示されています。RGB にアルベドを格納し、高さは必要に応じてアルファチャンネルまたは別の高さテクスチャに格納します。これに法線マップが加わります。
パッケージを決まったフォルダーに置く必要がありますか? いいえ。エディターはランタイムのアセンブリ定義からパッケージのルートを見つけるため、RTSTerrainStudio を Assets 以下の任意の場所(たとえば Assets/Plugins/RTSTerrainStudio)に移動できます。
マップサイズは後から変更できますか? はい。New Map Wizard で任意のサイズを選び、後から Resize で拡大・縮小できます。その際、どの辺でセルを追加・削除するかを決めるアンカーを選択します。
コードを読んだり変更したりできますか? はい。コードは複数のアセンブリ定義に分割されており、それぞれが 1 つの名前空間に対応しています。公開 API には XML ドキュメントが付いており、内部実装にもコメントが付いています。Burst ジョブは個別の *Jobs.cs ファイルにまとめられ、EditMode のユニットテストも用意されています。

API の概要

API がどのようなものかをつかめるよう、プログラマー向けに完全なスクリプトを 1 つ紹介します。このスクリプトは、クリックした地点について地形に問い合わせ、組み込みの経路探索エンジンでその地点までの経路を計画します。コライダーや追加のコンポーネントは不要です。RTSTerrain があるシーン内の任意のゲームオブジェクトに追加するだけで動作します。

using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;

/// <summary>Click on the ground: logs what the terrain knows about that point and plans a path to it.</summary>
public class ClickInfoExample : MonoBehaviour
{
    // Reused for every click, so no new list is allocated each time.
    readonly List<Vector3> path = new List<Vector3>();

    void Update()
    {
        // RTSInput works with the Input System package and with the old Input Manager.
        if (!RTSInput.GetMouseButtonDown(0) || Camera.main == null) return;

        Ray ray = Camera.main.ScreenPointToRay(RTSInput.PointerPosition);

        // Analytic ray cast against every RTSTerrain in the scene. Colliders are not needed.
        if (!RTSTerrain.RaycastAll(ray, out RTSTerrainHit hit)) return;
        RTSTerrain terrain = hit.terrain;

        // Gameplay questions, answered from the map data and the pathing map.
        int level = terrain.GetCliffLevel(hit.point);
        bool buildable = terrain.IsBuildable(hit.point);
        bool water = terrain.IsWater(hit.point, out float depth);
        Debug.Log($"Cell {hit.cell}: cliff level {level}, buildable {buildable}, water {water} ({depth:0.00} m)");

        // Built-in Burst A* on the pathing map, for a unit with a radius of 0.6 m.
        PathStatus status = RTSPathfinder.FindPath(terrain, transform.position, hit.point,
                                                   PathRequestOptions.ForUnit(0.6f), path);
        Debug.Log($"Path from {name}: {status}, {path.Count} waypoints");
    }
}

PathStatus は Success、Partial(到達可能な最も近い地点までの経路)、NoPath、InvalidStart のいずれかです。API 全体については、スクリプティングガイドの章で説明しています。

動作要件

Unity とレンダーパイプライン

要件バージョン備考
Unity6.3 LTS(6000.3)以降6000.3.3f1 で開発・テストしています。
Universal Render Pipeline17.3(Unity 6.3 に付属するバージョン)Graphics 設定と Quality 設定で URP アセットが有効になっている必要があります。Forward+ レンダリングパスで開発しています。地形シェーダーには Deferred パス用の GBuffer パスも含まれています。
カラースペースLinearGamma でも動作しますが、色、ライティング、水が色あせて見えます。
グラフィックスShader Model 3.5 以上地形、植生、ディテール、水の各シェーダーは #pragma target 3.5 を指定しているため、WebGL 2.0 と OpenGL ES 3.0 でも動作します。Shader Model 4.5 を使用するのは、地形の GBuffer パス(Deferred レンダリングパス)とディテールシェーダーの構造化バッファパスだけで、どちらも GPU が対応している場合に限られます。

ビルトインレンダーパイプラインと HDRP はサポートされていません。すべてのシェーダーは URP 向けに書かれており、戦場の霧のレンダラー機能は URP の RenderGraph API を使用しています。

URP のないプロジェクトにインポートしましたか?その場合、このパッケージはコンソールをエラーで埋め尽くすことなく、無効な状態のままになります。スクリプトはコンパイルされず、シェーダーはスキップされ、プロジェクトはこれまでどおり動作します。エディターセッションごとに 1 回、RTS Terrain Studio requires URP ダイアログが表示され、次のように対処方法を説明します。

  • ビルトインレンダーパイプライン:Install URP をクリックします。Unity がパッケージのコンパイルを終えたら、Tools ▸ RTS Terrain Studio ▸ Validate Project を開いて Use URP をクリックします。これにより URP アセットが作成され、グラフィックス設定と品質設定に割り当てられます。
  • 17.3 より古い URP:Open Package Manager をクリックし、Universal Render Pipeline を使用中のエディターに付属するバージョンに更新します。
  • 6.3 より古い Unity:URP 17.3 には Unity 6.3 LTS が必要なため、ダイアログには Open Manual と Later しか表示されません。Unity 6.3 LTS 以降でプロジェクトを開いてください。
  • HDRP またはカスタムレンダーパイプライン:代わりに、このパッケージを URP プロジェクト(たとえば Universal 3D テンプレートから作成した新しいプロジェクト)にインポートしてください。

Tools ▸ RTS Terrain Studio ▸ Set Up Universal Render Pipeline... を実行すると、ダイアログが再び表示されます。また、パッケージのシェーダーがサポートされていないという警告を Unity がログに出力することもありますが、これらの警告は URP をインストールすると表示されなくなります。

必須パッケージ

これらのパッケージは Unity Registry(Window ▸ Package Manager ▸ Unity Registry)から入手できます。Tools ▸ RTS Terrain Studio ▸ Validate Project ウィンドウは不足しているパッケージを検出し、ワンクリックでインストールします(チェック項目 Burst, Collections and Mathematics)。

パッケージ名前テスト済みバージョン用途
Burstcom.unity.burst1.8.27ジョブを高速なネイティブコードにコンパイルします。
Collectionscom.unity.collections2.6.2マップデータとジョブ用のネイティブコンテナー。
Mathematicscom.unity.mathematics1.3.3ジョブとデータモデルでのベクトル演算。
Universal RPcom.unity.render-pipelines.universal17.3.0レンダリング。

入力処理

ランタイムの入力はすべて静的クラス RTSInput を経由します。このクラスは次の 2 つの入力バックエンドの両方に対応しています。

  • Input System package(com.unity.inputsystem、1.17.0 でテスト済み)。パッケージがインストールされていて、Active Input Handling が Input System Package (new) または Both に設定されている場合に使用されます。
  • Input Manager (old)。プロジェクトで旧方式の入力処理を使用している場合に、フォールバックとして使用されます。

この設定は Edit ▸ Project Settings ▸ Player ▸ Other Settings ▸ Active Input Handling で行います。Project Validator の Input handling チェックで、どのモードが有効になっているかを確認できます。詳しくは、「プロジェクトのセットアップ」の章にある入力処理を参照してください。

連携用のオプションパッケージ

連携依存関係バージョン検出方法
MirrorMirror(Asset Store または GitHub)96 以降スクリプティング定義シンボル MIRROR(Mirror 自身が追加します)。
A* Pathfinding Projectcom.arongranberg.astarPro 5.4.4 以上 6.0 未満、5.4.7 でテスト済みバージョン定義 RTS_ASTAR。無効にするには RTS_ASTAR_DISABLED を追加します(同じパッケージ名を使用する無料版では必要です)。
Unity NavMeshAI Navigation、com.unity.ai.navigation2.0 以降、2.0.9 でテスト済みバージョン定義 RTS_AI_NAVIGATION。

経路探索の連携では RVO によるローカル回避と TraversalConstraint を使用するため、A* Pathfinding Project の Pro 版が必要です。Mirror を削除する前に、Mirror の削除を読んでください。Mirror は削除後も定義シンボルを残すため、RTS Terrain Studio にはそれらをクリーンアップする小さなエディターアセンブリが含まれています。

推奨プロジェクト設定

Project Validator はこれらの項目をすべてチェックし、自動で修正することもできます。

設定理由
URP アセットが有効このパッケージのシェーダーは URP でのみ描画されます。
URP アセットの Depth Texture と Opaque Texture水は深度テクスチャを使用して、水深による色、泡、岸辺のソフトな境界を、水の中に立っているオブジェクトにも合わせて表示します。屈折には不透明テクスチャを使用します。カメラ自身の設定はアセットの設定より優先され、水はカメラごとにその設定に従います。デモのカメラは両方を自動で有効にするため、この設定がなくてもゲームビューではデモシーンが正しく表示されます(シーンビューでは常に URP アセットの設定が使用されます)。
レンダラーに戦場の霧のレンダラー機能を追加済み独自のマテリアルを含め、霧の中にあるものすべてを暗くします。この機能がない場合、霧が表示されるのはこのパッケージのシェーダーだけです。
プレイヤービルドでフォグモードを保持Unity はビルドに含まれるどのシーンでも使用されていないフォグのシェーダーバリアントを除外しますが、RTSEnvironment はランタイムでフォグを有効にします。フォグモードが保持されていないと、フォグを使用するマップでもビルドではフォグが表示されません。
リニアカラースペース色とライティングを正しく表示するため。
シェーダー参照とタイルセットのテクスチャ配列が最新シェーダーと圧縮済みのテクスチャ配列がプレイヤービルドに含まれるようにします。プレイヤービルドの前には毎回、シェーダー参照アセットの更新と、古くなったテクスチャ配列の再構築が自動で行われます。

プラットフォーム

ランタイムはエディター専用の依存関係を持たないため、Web ビルド(WebGL 2.0)を含め、URP が動作するすべてのプラットフォームにおいて、プレイヤービルドでもマップの読み込み、描画、編集ができます。シェーダーのターゲットは、テクスチャ配列に対応する最も低いレベルである Shader Model 3.5 です。Deferred レンダリングパス用の地形の GBuffer パスには Shader Model 4.5 が必要で、URP 自体の Lit シェーダーの GBuffer パスと同様に、OpenGL ベースの API ではスキップされます。ディテールの草は、インスタンスデータの格納方法をランタイムで選択します。GPU が対応している場合は構造化バッファを、WebGL 2.0 と OpenGL ES 3 では浮動小数点テクスチャを使用します(見た目は変わりません)。

テクスチャ配列のベイク処理は、アクティブなビルドターゲットに応じて圧縮形式を選択します。デスクトップとコンソールでは BC7、Android・iOS・tvOS・visionOS では ASTC 6×6、Web ではビルドのテクスチャ圧縮形式(DXT5、ASTC 6×6、ETC2 のいずれか)、それ以外のプラットフォームでは DXT5 を使用します。ゲーム内エディターはすべてのプラットフォームで動作し、マウスとキーボードでの操作を前提に作られています。ただし、マップフォルダーを表示するコマンドとアプリケーションを終了するコマンドは、プラットフォームが対応している場合にのみ表示されます。フォルダーを表示できるのはデスクトップのプレイヤーとエディターだけで、WebGL、iOS、Android ではアプリケーションを終了できません。Web ビルドにはほかにもいくつか違いがあります。詳しくは Web(WebGL)ビルドを参照してください。

フォルダー構成

パッケージは 1 つのフォルダーにまとまっており、デフォルトでは Assets/RTSTerrainStudio です。このフォルダーは移動や名前の変更が可能です。エディターは RTSTerrainStudio.Runtime.asmdef の場所からパッケージのルートを特定し、デモコンテンツとドキュメントはそこからの相対位置で検出されます。

RTSTerrainStudio/
├── Runtime/                 Core, rendering, tools and runtime systems (RTSTerrainStudio.Runtime)
├── Editor/                  Editor window, Scene View, inspectors, setup, demo content (RTSTerrainStudio.Editor)
├── Shaders/                 URP shaders and HLSL includes
│   ├── Hidden/              Internal shaders (fog blur, minimap fog, gizmos, texture array blit)
│   └── Include/             Shared HLSL (fog of war, brush cursor, wind, terrain, vegetation, water)
├── Samples/
│   ├── Scripts/             Sample gameplay: camera, units, selection, orders, minimap, HUD (RTSTerrainStudio.Samples)
│   └── Scenes/              Demo scenes (created by Create Demo Content)
├── Integrations/
│   ├── Mirror/              Multiplayer (compiled only with Mirror)
│   ├── AStarPathfinding/    A* Pathfinding Project (compiled only with A* Pro 5.4.4+)
│   └── UnityNavMesh/        Unity AI Navigation (compiled only with com.unity.ai.navigation 2.0+)
├── Content/                 Generated demo content (empty until you run Create Demo Content)
├── Resources/
│   └── RTSTerrainStudio/    Runtime UI style sheets and theme, shader references, panel settings
├── Documentation/           This manual (index.html) and its images
└── Tests/
    └── Editor/              EditMode unit tests (RTSTerrainStudio.Tests.Editor)

最上位のフォルダー

フォルダー内容削除できますか?
Runtimeゲーム内で動作するすべての要素です。RTSTerrain コンポーネント、マップデータ、レンダリング、すべての編集ツール、植生、水、装飾オブジェクト、戦場の霧、通行判定、生成、ゲーム内エディター、アイコン、入力が含まれます。いいえ。
EditorUnity エディター専用の機能のみです。プレイヤービルドには一切含まれません。いいえ。
ShadersRTS Terrain Studio/Terrain、RTS Terrain Studio/Vegetation、RTS Terrain Studio/Water、Hidden/RTS Terrain Studio/Detail、Hidden/RTS Terrain Studio/FogOfWarFullscreen、Hidden 内の内部シェーダー、Include 内の HLSL インクルードファイル(RTSCommon.hlsl には戦場の霧、ブラシカーソル、風のグローバル変数が含まれます)。いいえ。
Samplesサンプルのゲームプレイスクリプトとデモシーン。エディターアセンブリと各連携はサンプルアセンブリを参照しています(たとえば RTSUnit や RTSDemoGame を使用するため)。連携と一緒に削除する場合のみ可能です。シーンはいつでも削除できます。
Integrationsサードパーティ連携ごとに 1 つのフォルダーがあり、それぞれに専用のアセンブリ、README.md、デモシーンを構築するスクリプトを含む Editor フォルダーが入っています。デモシーンは Demo サブフォルダーに作成されます。はい。連携フォルダーはそれぞれ個別に削除できます。Mirror の場合は、先に削除手順を読んでください。
Contentデモ用のテクスチャ、マテリアル、メッシュ、プレハブ、タイルセット、ライブラリ、ブラシ形状、マップ、レンダリング設定。ここにあるものはすべて生成されたものです。はい。いつでも再生成できます。ただし、削除する場合は自作のマップでこのフォルダーの内容を使用しないでください。
Resources/RTSTerrainStudioUI/RTSRuntimeEditor.uss と UI/RTSRuntimeTheme.tss(ゲーム内 UI のスタイル)。シェーダー参照アセット(エディターが自動で作成・更新)と UI/RTSRuntimePanelSettings.asset(Create Demo Content で作成)もここに追加されます。これらにより、パッケージのシェーダーと UI Toolkit のランタイムシェーダーが確実にビルドに含まれます。いいえ。
Documentationこのマニュアルです。Tools ▸ RTS Terrain Studio ▸ Documentation とすべての ? ボタンから開きます。はい。ただし、その場合はヘルプボタンを押しても何も開きません。
Testsマップデータ、ジオメトリ、ブラシ、対称、元に戻す操作、シリアライザー、通行判定、ジェネレーター、地形クエリ、ツールレジストリ、アイコンの EditMode テスト。Window ▸ General ▸ Test Runner で実行します。はい。

Runtime フォルダーの内容

フォルダー内容
CoreRTSTerrain(メインコンポーネント、マップのライフサイクル、変更追跡、クエリ)、MapData、MapTypes、MapGeometryView(メッシュ、クエリ、通行判定が使用する基準面)、PathingMap、RTSTerrainSettings、シェーダープロパティ ID、RTSShaderReferences、RTSRuntimeEditorEvents。
Dataアセット型:RTSTileset、RTSVegetationLibrary、RTSDecorLibrary、RTSMapAsset。
SerializationRTSMapSerializer:バイナリの .rtsmap 形式(セクション構造、deflate 圧縮)とファイル入出力。
BrushesBrushSettings、RTSBrushShape、SymmetrySettings、BrushUtility。
ToolsRTSTool(基底クラス)、RTSEditSession、MapUndo、およびカテゴリごとのサブフォルダー(Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers)。
Meshチャンクのジオメトリ生成と、その Burst ジョブ。
RenderingRTSTerrain のレンダリング部分、RTSTerrainChunk、スプラットテクスチャ、レイヤーデータ、RTSTextureArrayBuilder。
VegetationRTSVegetationRenderer、RTSTreeInstance。
WaterRTSWaterRenderer、RTSWaterChunk、水メッシュのジョブ。
DecorRTSDecorManager、RTSDecorInstance。
FogOfWarRTSFogOfWarManager、RTSFogOfWarRevealer、RTSFogOfWarHideable、RTSFogOfWarRendererFeature。
PathingRTSTerrain の通行判定部分、RTSPathingSettings、PathingMapBuilder、ClearanceMap、RTSPathfinder、RTSPathfindingService、通行オーバーレイ。
GenerationRTSMapGenerator、MapGeneratorSettings、RTSMapGeneratorPreset、RTSAutoPaintProfile、RTSAutoPaint、HeightmapIO、Auto Paint ツール。
MinimapRTSMinimapBaker。
EnvironmentRTSEnvironment(マップの太陽、環境光、フォグを適用します)。
ValidationMapValidator(到達できない開始地点など、マップの問題をチェックします)。
RuntimeEditorゲーム内エディター:RTSRuntimeEditor、RTSRuntimeEditorCamera、RuntimeToolGizmos、およびその UI Toolkit インターフェース。
InputRTSInput と RTSKey。
UIRTSIcons:エディターとゲーム内 UI で共有されるプロシージャルアイコン。
Plugins/WebGLWeb ビルドだけが使用する JavaScript プラグイン(.jslib):ブラウザーストレージ、ダウンロード、インポート、ページを離れる際の警告(RTSWebFileSystem.jslib)、キーボードレイアウトとブラウザーのショートカット(RTSWebKeyboard.jslib)、ポインターのページ外への移動(RTSPointer.jslib)。
link.xmlコードストリッピングを行うビルドでも、編集ツールが削除されずに残るようにします(コードストリッピングとカスタムツールを参照)。

Editor フォルダーの内容

フォルダー内容
CoreRTSEditorContext(共有の編集セッションと表示オプション)、RTSEditorPaths、ツール設定の保存。
WindowRTS Terrain Studio ウィンドウ(RTSTerrainStudioWindow)とその各ページ。
SceneViewシーンビューでのペイント、ブラシとギズモの描画、オーバーレイ、ショートカット、Unity の元に戻す機能との橋渡し。
Inspectorsタイルセット、ライブラリ、ブラシ形状、自動ペイントプロファイル、各コンポーネントのインスペクター。
Importers.rtsmap インポーターとそのプレビュー、および Assets ▸ Create ▸ RTS Terrain Studio ▸ Map (.rtsmap) メニュー。
Building圧縮テクスチャ配列のベイク処理、プレイヤービルドの前に古くなったテクスチャ配列を再構築するチェック、サムネイルジェネレーター、アセットファクトリ。
SetupWelcome ウィンドウ、New Map Wizard、Project Validator、GameObject の作成メニュー、シェーダー参照の更新処理、Web テンプレート(WebGLTemplate)とそのインストーラー。
Requirementsプロジェクトに URP 17.3 がない場合に表示される通知(Unity とレンダーパイプラインを参照)。Mirror の定義シンボルのクリーンアップ処理を除けば、URP がなくてもコンパイルされるのは、パッケージの中でこの部分だけです。
RuntimeEditorSupportGameObject ▸ RTS Terrain Studio ▸ In-Game Editor メニュー。
DemoContentプロシージャルなデモコンテンツジェネレーター(RTSDemoContent)。

アセンブリ

コードはアセンブリ定義に分割されているため、コンパイルが速く、オプションの部分を削除することもできます。各アセンブリは 1 つの名前空間を持ちます。

アセンブリ(asmdef)フォルダー名前空間プラットフォームコンパイル条件
RTSTerrainStudio.RuntimeRuntimeRTSTerrainStudioすべてURP 17.3 以降がインストールされている場合(RTS_URP)。Input System パッケージがインストールされている場合は RTS_INPUT_SYSTEM を定義します。
RTSTerrainStudio.SamplesSamples/ScriptsRTSTerrainStudio.SamplesすべてURP 17.3 以降がインストールされている場合。
RTSTerrainStudio.EditorEditorRTSTerrainStudio.EditorエディターURP 17.3 以降がインストールされている場合。
RTSTerrainStudio.Requirements.EditorEditor/RequirementsRTSTerrainStudio.RequirementsエディターURP 17.3 以降がない場合のみ。このパッケージに URP が必要であることを伝える通知を表示します。
RTSTerrainStudio.Tests.EditorTests/EditorRTSTerrainStudio.TestsエディターUNITY_INCLUDE_TESTS が定義されている場合(Unity Test Framework)。
RTSTerrainStudio.Integrations.Mirror
RTSTerrainStudio.Integrations.Mirror.Editor
Integrations/MirrorRTSTerrainStudio.Integrations.Mirrorすべて / エディターMIRROR が定義されている場合。
RTSTerrainStudio.Integrations.Mirror.DefineCleanup.EditorIntegrations/Mirror/Editor/DefineCleanupRTSTerrainStudio.Integrations.Mirrorエディター常にコンパイルされます。Mirror に依存せず、Mirror の削除後に残った古い Mirror の定義シンボルを削除します。
RTSTerrainStudio.Integrations.AStar
RTSTerrainStudio.Integrations.AStar.Editor
Integrations/AStarPathfindingRTSTerrainStudio.Integrations.AStarすべて / エディターRTS_ASTAR が定義され(A* 5.4.4 以上 6.0 未満がインストール済み)、かつ RTS_ASTAR_DISABLED が定義されていない場合。
RTSTerrainStudio.Integrations.NavMesh
RTSTerrainStudio.Integrations.NavMesh.Editor
Integrations/UnityNavMeshRTSTerrainStudio.Integrations.NavMeshすべて / エディターRTS_AI_NAVIGATION が定義されている場合(AI Navigation 2.0 以降がインストール済み)。

テストアセンブリと連携アセンブリには、表に記載された条件に加えて URP 17.3 以降も必要です。URP がない場合にコンパイルされるのは Mirror の定義シンボルのクリーンアップ処理と、URP が必要であることを伝える通知だけなので、このパッケージがコンパイルエラーを引き起こすことはありません。

RTSTerrainStudio.Runtime、RTSTerrainStudio.Samples、RTSTerrainStudio.Editor は自動的に参照されます。プロジェクトのデフォルトアセンブリ(Assembly-CSharp)内のスクリプトからは、設定なしでこれらを使用できます。独自のアセンブリ定義を持つコードの場合は、RTSTerrainStudio.Runtime への参照を追加してください(サンプルのゲームプレイクラスを使用する場合は RTSTerrainStudio.Samples への参照も追加します)。

ゲーム内エディターは Runtime/RuntimeEditor にあります。ゲームで必要ない場合は、このフォルダーを削除できます。その場合でも Unity エディターのツールとデモコンテンツジェネレーターは問題なくコンパイルされ、In-Game Editor Demo シーンはゲーム内エディターなしで作成されます。

同梱内容

このパッケージは、ソースコード、シェーダー、このマニュアルで構成されています。テクスチャ、モデル、タイルセット、マップ、シーンといった視覚的なコンテンツはすべて、スクリプトによって手元のマシン上で生成されます。これによりダウンロードサイズを小さく抑えられ、コンテンツにもコードとまったく同じライセンスが適用されます。

デモコンテンツジェネレーター

Tools ▸ RTS Terrain Studio ▸ Create Demo Content は RTSDemoContent.CreateAll を実行します。すべてのデモアセットを固定のシードでプロシージャルに生成するため、何度実行しても同じ結果になります。

  • 書き込むのは専用のフォルダー(Content、Samples/Scenes)と、Resources/RTSTerrainStudio 内の 2 つのアセットだけです。自分で作成したアセットに手が加えられることはありません。
  • 再実行すると、アセットはその場で再生成されるため、アセットへの参照は有効なままです。内容が変わっていないマップファイルは書き換えられないため、バージョン管理に変更が表示されることもありません。
  • 開いているシーンを閉じることはありません。デモシーンは、構築中に既存のシーンに追加する形で開かれます。
  • 最後に、デモシーンをビルド設定に追加するかどうかを尋ねます。Add Scenes をクリックしない限り、何も追加されません。
生成物場所(パッケージのルート以下)内容
テクスチャContent/Textures/(Ground、Cliffs、Water、Details、Props、Brushes)タイリング可能な地面、崖、水、プロップのテクスチャ(512 px)、草と花のスプライト、ブラシスタンプ(256 px)と、それぞれに対応する法線マップ。
マテリアル、メッシュ、プレハブContent/Materials、Content/Meshes、Content/Prefabsローポリの木、岩、遺跡、プロップと、それらのマテリアル。
タイルセットContent/Tilesets/Summer.asset、Winter.asset2 つのタイルセットで、それぞれ 10 個の地面レイヤーと 2 種類の崖タイプを持ちます。Summer:Grass、Lush Grass、Dirt、Rocky Dirt、Sand、Cobblestone、Dark Forest Floor、Rock、Snow、Blight と、崖タイプの Rock Cliff、Grassy Cliff。Winter:これらを凍らせたバージョンと、崖タイプの Ice Cliff、Snowy Cliff。
自動ペイントプロファイルContent/Tilesets/Summer Auto Paint.assetSummer タイルセット用のルール。たとえば、緩やかな傾斜に土のパッチを、岸辺に沿って砂をペイントします。
植生ライブラリContent/Libraries/Demo Vegetation.assetディテール:Grass、Tall Grass、Flowers Yellow、Flowers Purple、Fern。木:Pine、Broadleaf、Dead Tree、Bush。
装飾ライブラリContent/Libraries/Demo Decor.assetRock A、Rock B、Rock C、Boulder、Ruined Pillar、Crate、Barrel、Fence、Tree Stump、Mushrooms。Instanced と GameObject のプロトタイプが混在しており、それぞれ通行判定用のフットプリントを持ちます。
ブラシ形状Content/Brushesカスタムスタンプ:Noise Blob、Splatter、Star、Soft Square。
マップContent/Maps3 つの .rtsmap ファイル(以下を参照)。
レンダリング設定Content/Settingsデモシーンで使用するポストプロセッシングのボリュームプロファイル。
シーンSamples/Scenes3 つのデモシーン(以下を参照)。
ResourcesResources/RTSTerrainStudioシェーダー参照アセットと UI/RTSRuntimePanelSettings.asset。これにより、シェーダーと UI がビルドに含まれます。

デモマップ

3 つのマップはすべて、セルサイズ 2 m、崖レベルの高さ 2 m で、Summer タイルセットを使用しています。

マップサイズ対称説明
Two Rivers (1v1).rtsmap128 × 128 セルRotate 2プレイヤー 2 人、川 2 本のマップ。川のいたるところに浅瀬があって渡ることができ、中央の湖は深くなっています。スロープを上ると、遺跡や放棄された野営地のある、森に覆われた台地に出ます。
Four Kingdoms (4p).rtsmap192 × 192 セルRotate 44 つの王国が、それぞれ独自の高地に位置しています。スロープを下ると共有の低地に出ます。中央は、古い神殿の遺跡がある汚染された大地です。
Sandbox.rtsmap96 × 96 セルNoneツールを試すための、ほぼ平坦な小さいマップ。
台地、川、森、開始地点があるデモマップを真上から見た全体図
デモマップを真上から見たところ。

デモシーン

シーンマップ表示内容
RTS Terrain Studio - Gameplay DemoTwo Rivers (1v1)Play をクリックするとプレイできます。開始地点のユニット(敵は周囲を歩き回ります)、戦場の霧、RTS カメラ、選択、命令、ミニマップ、HUD、建物の配置(B キー)、木の伐採(T キー)を体験できます。
RTS Terrain Studio - In-Game Editor DemoSandbox再生モードで動作するゲーム内エディター。マップを編集し、F5 キーを押すと、ユニットを使ってテストプレイできます。
RTS Terrain Studio - Editor PlaygroundFour Kingdoms (4p)ゲームプレイ要素のないマップです。シーンビューで Unity エディター内のツールを試すために使います。

すべてのデモシーンには、太陽、ポストプロセッシングボリューム、深度テクスチャと不透明テクスチャを要求するカメラ、そして RTSTerrain、RTSVegetationRenderer、RTSWaterRenderer、RTSDecorManager、RTSEnvironment を持つ地形が含まれています。

各連携には専用のデモシーンビルダーがあります。デモシーンビルダーにはデモコンテンツが必要で、シーンはその連携の Demo フォルダーに作成されます。

  • Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Mirror ▸ Create Demo Scene は RTS Mirror Demo.unity を作成します。
  • Tools ▸ RTS Terrain Studio ▸ Integrations ▸ A* Pathfinding ▸ Create Demo Scene は RTS AStar Demo.unity を作成します。
  • Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Unity NavMesh ▸ Create Demo Scene は RTS NavMesh Demo.unity を作成します。

サンプルスクリプト

Samples/Scripts フォルダー(RTSTerrainStudio.Samples)は、小規模で読みやすい RTS フレームワークです。学習用としても、開発のベースとしても利用できます。

領域クラス
起動処理RTSDemoGame、RTSUnitSpawner
ユニットと移動RTSUnit、RTSUnitFactory、RTSUnitRegistry、RTSFormation、RTSWanderAI
経路プロバイダーIRTSUnitPathProvider、RTSBuiltinPathProvider、RTSUnitPathProviderComponent、RTSUnitPathProviderRegistry
カメラ、入力、選択、命令RTSCameraController、RTSGameInput、RTSSelectionManager、RTSCommandController
UIRTSMinimapUI、RTSDemoHUD、RTSRuntimeUI
ゲームプレイのデモRTSBuildingPlacementDemo、RTSDemoBuilding、RTSTreeHarvestDemo、RTSFallingTreeEffect
ヘルパーRTSMoveMarker、RTSGroundRing、RTSSampleMaterials、RTSSampleMeshes、RTSSampleUtility

アイコン

エディターウィンドウ、インスペクター、ゲーム内エディターのアイコンはすべて RTSIcons によってプロシージャルに描画されます。そのため、どのサイズでも鮮明に表示され、テクスチャアセットがなくてもビルドで動作します。

RTS Terrain Studio の全アイコン一覧:ツールカテゴリ、ツール、アクション
エディターとゲーム内 UI で使用されるアイコンセット。

メニューの概要

メニュー項目機能
Tools ▸ RTS Terrain Studio ▸ Editor Windowメインの編集ウィンドウを開きます。
Tools ▸ RTS Terrain Studio ▸ New Map Wizard...新しいマップを段階的に作成します(サイズ、タイルセット、平坦または自動生成)。
Tools ▸ RTS Terrain Studio ▸ Create Demo Content上記のデモコンテンツを生成します。
Tools ▸ RTS Terrain Studio ▸ Validate ProjectURP、プロジェクト設定、パッケージ、コンテンツ、シーンをチェックし、修正も行います。
Tools ▸ RTS Terrain Studio ▸ Install WebGL Templateゲームをブラウザーウィンドウ全体に表示する Web テンプレートをインストールして選択します(ウィンドウ全体に表示するテンプレートを参照)。
Tools ▸ RTS Terrain Studio ▸ Welcomeクイックスタートの手順、リンク、連携の状態を表示します。
Tools ▸ RTS Terrain Studio ▸ Documentationこのマニュアルを開きます。
Tools ▸ RTS Terrain Studio ▸ Integrations ▸ …インストール済みの連携のデモシーンビルダーとヘルパー。
GameObject ▸ RTS Terrain Studio ▸ RTS TerrainRTSTerrain、Vegetation Renderer、Water Renderer、Decor Manager を持つ RTS Terrain ゲームオブジェクトを作成します。プロジェクトのデフォルトのタイルセットとライブラリ、および新しい平坦な .rtsmap ファイルも設定されます。
GameObject ▸ RTS Terrain Studio ▸ Fog of War Manager戦場の霧のマネージャーを作成し、レンダラー機能を追加するかどうかを確認します。
GameObject ▸ RTS Terrain Studio ▸ EnvironmentRTSEnvironment を作成します(シーンに太陽がない場合は太陽も作成します)。
GameObject ▸ RTS Terrain Studio ▸ In-Game Editorシーンにゲーム内エディターを追加します。
GameObject ▸ RTS Terrain Studio ▸ Demo GameRTSDemoGame を追加します。これにより、開始地点のある地形ならどれでもプレイ可能なデモになります。
Assets ▸ Create ▸ RTS Terrain Studio ▸ …Map (.rtsmap)、Tileset、Vegetation Library、Decor Library、Brush Shape、Auto Paint Profile、Map Generator Preset、および Advanced ▸ Shader References。最後の項目が必要になることはほとんどありません。Project Validator とプレイヤービルドが RTSShaderReferences.asset を自動的に作成・更新するためです。

GameObject メニューの項目は、再生モードでは無効になります。保存が必要なアセットやシーンオブジェクトを作成するためです。

クイックスタート

この章では、空の Unity プロジェクトから始めて、自分で作ったマップをユニット、戦場の霧(Fog of War)、ミニマップ付きでプレイするところまでを説明します。プログラミングの知識は必要ありません。番号付きの手順に順番どおりに従ってください。各手順には、使用するメニュー、ウィンドウ、ボタンが正確に記載されています。

構成要素作業内容所要時間
インストールとセットアップパッケージをインポートし、Project Validator で URP 設定を修正して、デモコンテンツを生成します。約 5 分
デモシーン完成したマップをプレイし、ゲーム内エディターを試して、Editor Playground を見て回ります。5 分
はじめてのマップ地形を作成し、高さ、崖、テクスチャ、木、水をペイントして、開始地点を配置し、保存します。10 分
はじめてのテストプレイデモゲームでマップをプレイするか、実行中のゲーム内でマップを編集してテストプレイします。5 分
10 分間チュートリアル対称ペイントを使って、公平な 1v1 マップを最初から最後まで作成します。10 分

この章は、Welcome ウィンドウ(Tools ▸ RTS Terrain Studio ▸ Welcome)の Quick Start Guide リンクからいつでも開けます。マニュアル全体は Tools ▸ RTS Terrain Studio ▸ Documentation で開きます。

インストールとセットアップ

動作要件

項目バージョン備考
Unity6.3 LTS (6000.3)このパッケージの対象となっているエディターバージョンを使用してください。
Universal Render Pipeline17.3必須です。地形、水、植生、戦場の霧のシェーダーはすべて URP シェーダーです。新しいプロジェクトを作成する場合は、Universal 3D プロジェクトテンプレートから始めてください。URP がないと、このパッケージは非アクティブのままになり、通知が表示されます(動作要件を参照)。
Burst、Collections、MathematicsUnity Registry 版必須です。負荷の高い処理(メッシュの構築、ブラシ、通行判定、戦場の霧、経路探索)は Burst ジョブで実行されます。
Input SystemUnity Registry 版推奨です。ゲーム内エディター、RTS カメラ、サンプルで使用されます。旧 Input Manager もフォールバックとして動作します。
Mirror、A* Pathfinding Project、AI Navigationオプション対応する連携を使う場合にのみ必要です。連携は、対象のパッケージがインストールされると自動的にコンパイルされます。詳しくは連携を参照してください。

パッケージのインポート

  1. Unity 6.3 でプロジェクトを開きます。プロジェクトで URP が使用されていることを確認してください(Universal 3D テンプレートから作成した新しいプロジェクトは、すでに URP を使用しています)。
  2. Window ▸ Package Manager を開き、My Assets を選択して RTS Terrain Studio を探し、Download をクリックしてから Import をクリックします。
  3. インポートダイアログではすべての項目をオンにしたまま、Import をクリックします。パッケージは Assets/RTSTerrainStudio にインポートされます。
  4. Unity のコンパイルが完了するまで待ちます。Welcome ウィンドウが自動的に開きます(以下を参照)。

RTSTerrainStudio フォルダーは Assets 内の任意の場所に移動できます。このパッケージはフォルダーの場所に関係なく自身のファイルを見つけるため、プロジェクトを整理し直しても何も壊れません。

インポート直後にコンソールにコンパイルエラーが表示される場合、最も可能性の高い原因は Burst、Collections、Mathematics のいずれかのパッケージが不足していることです。Window ▸ Package Manager ▸ Unity Registry からインストールしてください。インストールが完了するとエラーは消えます。

Welcome ウィンドウの代わりに RTS Terrain Studio requires URP ダイアログが表示された場合、そのプロジェクトには URP 17.3 がインストールされていません。このとき、このパッケージは非アクティブのままとなり、プロジェクトには何も変更を加えません。ダイアログの手順に従うか、Unity とレンダーパイプラインを参照してください。

Welcome ウィンドウ

Welcome ウィンドウは、プロジェクトでこのパッケージが初めて読み込まれたときに自動的に開きます。Tools ▸ RTS Terrain Studio ▸ Welcome でいつでも再度開けます。Unity の起動時に毎回表示したい場合は、下部の Show at startup をオンにします。この設定はユーザーごと・プロジェクトごとに保存されるため、バージョン管理に含まれることはありません。

左の列はクイックスタートのリストで、各手順に 1 つずつボタンがあります。

手順ボタン機能
1. プロジェクトの検証と修正ValidateProject Validator を開きます。手順の下にあるステータス行に、「Project is ready」または検出されたエラーと警告の数が表示されます。
2. デモコンテンツの作成CreateTools ▸ RTS Terrain Studio ▸ Create Demo Content を実行します。
3. デモシーンを開くシーンリスト + Openデモシーンが作成済みであれば一覧表示し、選択したシーンを開きます(開く前に、開いているシーンを保存するかどうかを確認します)。
4. 自分のマップの作成New MapNew Map Wizard を開きます。
5. 編集の開始Editor WindowRTS Terrain Studio ウィンドウを開きます。

右の列には、このマニュアルへの Learn リンク(Documentation、Quick Start Guide、Scripting API、Integrations、Troubleshooting)と、Mirror、A* Pathfinding Project、Unity AI Navigation がプロジェクトにインストールされているかどうかを示す Integrations リストがあります。

プロジェクトの検証

Project Validator は、このパッケージに必要なものをすべてチェックし、ほとんどの問題をワンクリックで修正します。インポート後に一度実行し、その後も何かおかしいと感じたとき(マテリアルがピンク色になる、水がのっぺりして見える、戦場の霧を通してユニットが見えるなど)に再度実行してください。

  1. Tools ▸ RTS Terrain Studio ▸ Validate Project を開くか、Welcome ウィンドウで Validate をクリックします。
  2. 上部の概要を確認します。各チェックは、ステータスバッジ(OK、Info、Warning、Error)付きのカードとして表示されます。カードの Details を開くと、何が検出されたかを正確に確認できます。
  3. Fix All をクリックします。何かが変更される前に、すべての変更内容がダイアログに一覧表示されます。Apply Fixes をクリックします。
  4. Run Again をクリックし、エラーや警告が残っていないことを確認します。Info カードは残っていても問題ありません。情報を知らせるだけのものです。

Fix All は複数のパスで処理を行います。URP がまだ有効になっていなかった場合は、最初のパスでプロジェクトが URP に切り替わり、URP を必要とするカメラテクスチャと戦場の霧の修正も同じクリックで適用されます。1 つずつ修正したい場合は、各カードにある個別の修正ボタンを使うこともできます。

チェック問題レベル修正ボタン修正内容とその重要性
Rendering
Universal Render Pipeline is activeErrorUse URPプロジェクト内の URP アセットを(なければ新規作成して)デフォルトのレンダーパイプラインとして割り当て、ほかのパイプラインを使用しているすべての品質レベルにも割り当てます。URP がないと、地形、水、植生はピンク色で描画されるか、まったく描画されません。
Depth and Opaque Texture enabledWarningEnable Both品質レベルで使用されているすべての URP アセットで、Depth Texture と Opaque Texture をオンにします。水は深度テクスチャを使用して、水深に応じた色、泡、岸辺を、水中に立っているオブジェクトにも合わせて表示します。また、屈折には不透明テクスチャを使用します。これらがないと、水の見た目が簡素になります。ただし、これらの設定は各カメラの Rendering セクションで引き続きオーバーライドできます。
Fog of War renderer featureWarningAdd Featureプロジェクトで使用しているすべての URP アセットのデフォルトレンダラーに RTS Fog Of War レンダラー機能を追加し、そのシェーダーを割り当てます。これがないと、戦場の霧が反映されるのはこのパッケージ独自のシェーダーだけになり、ほかのマテリアルを使ったユニットやプロップは見えたままになります。
Fog kept in player buildsWarningKeep Fog Modesビルドに含まれるシーンまたは開いているシーンにある RTSEnvironment がフォグを適用する場合に表示されます。Project Settings ▸ Graphics ▸ Fog Modes を Custom に設定し、使用中のフォグモードを保持します。そうしないと、Unity がフォグのシェーダーバリアントを除外するため、フォグを使うマップでもビルドではフォグが表示されません。詳しくはプレイヤービルドでのフォグを参照してください。
Project
Linear color spaceWarningSwitch to LinearPlayer Settings ▸ Other Settings ▸ Color Space を Linear に設定します。ガンマ空間では、色、ライティング、水が色あせて見えます。切り替えると、すべてのテクスチャが再インポートされます。
Burst, Collections and MathematicsErrorInstall不足しているパッケージをパッケージマネージャー経由で追加します。これらがないと、このパッケージのスクリプトはコンパイルされません。
Input handlingInfoなしInput System パッケージがインストールされているかどうかと、どの入力処理が有効になっているかを報告します。旧 Input Manager でも動作します。
WebGL(Web がアクティブなビルドプラットフォームの場合のみ)
Web quality levels have Depth and Opaque TextureWarningEnable BothWeb ビルドで使用される品質レベルの URP アセットで、Depth Texture と Opaque Texture をオンにします。エディターはデスクトップ向けの品質レベルで描画することが多いため、このチェックがないと、ブラウザー上でだけ水が簡素に見えることになります。
MSAA is off for Web buildsErrorTurn MSAA OffWeb ビルドで使用される品質レベルのうち、Depth Texture または Opaque Texture も使用しているものについて、その URP アセットで MSAA をオフにします。URP は WebGL 2 上でこの組み合わせを描画できないため、ブラウザーではゲーム画面が真っ黒なままになります。
Web template fills the browser windowWarningInstall Templateこのパッケージの Web テンプレートをインストールして選択します。Unity の Default テンプレートと Minimal テンプレートは、ゲームを固定サイズの小さな枠内に表示するため、ゲーム内エディター、HUD、ミニマップが読みにくくなります。詳しくはウィンドウ全体に表示するテンプレートを参照してください。
Content
Package shaders compileErrorReimportコンパイルエラーのあるパッケージのシェーダーを再インポートします(URP の読み込みが完了する前にシェーダーがインポートされた場合に有効です)。シェーダーがまったく存在しない場合は、Asset Store からパッケージを再インポートしてください。
Shaders included in buildsErrorUpdateパッケージの Resources フォルダーにある RTSShaderReferences.asset と DecorPlaceholder マテリアルを作成または更新します。コードからのみ作成されるシェーダーは、何かから参照されていない限りプレイヤービルドから除外され、ビルドで地形がピンク色になってしまいます。このアセットは、スクリプトのリロード後、シェーダーの変更時、および毎回のプレイヤービルドの前にも自動的に更新されます。
Tileset texture arrays builtInfoBuild Arraysテクスチャ配列がない、または古くなっているタイルセットについて、圧縮済みのテクスチャ配列をビルドします。タイルセットはテクスチャ配列がなくても動作しますが、その場合はマップの読み込み時にエディターが非圧縮の配列をビルドするため、メモリと読み込み時間を余分に消費します。プレイヤービルドの際には、古くなった配列が自動的に再ビルドされます。一度もビルドされていないタイルセットは、ビルドしたゲームでも非圧縮の配列を使用します。
GPU instancing on instanced prototypesWarningEnable Instancingインスタンス描画される木、ディテール、装飾オブジェクトで使用されるマテリアルの Enable GPU Instancing をオンにします。
Scene(開いているシーンのみ)
Terrains have a tilesetWarningAssign '<tileset>'タイルセットのないすべての地形にタイルセットを割り当てます(元に戻せます)。タイルセットがないと、地形はニュートラルなプレースホルダーの外観で表示されます。
Terrains save to a map fileInfoCreate Map Files.rtsmap アセットを持たない各地形のマップを、そのシーンと同じ場所の新しいファイルに保存して割り当てます。マップファイルがないと、シーンを閉じたときに編集内容が失われます。

シーンの修正は再生モードでは提供されません。先に再生モードを終了してください。

デモコンテンツの作成

このパッケージには、大きなバイナリのアートアセットは含まれていません。代わりに、デモアセット一式をプロジェクト内に生成します。そのため、追加の準備なしにすべてが動作し、すぐにペイントを始められます。

  1. Tools ▸ RTS Terrain Studio ▸ Create Demo Content を選択するか、Welcome ウィンドウで Create をクリックします。
  2. 進行状況バーに、テクスチャ、マテリアル、メッシュとプレハブ、タイルセット・ライブラリ・ブラシ、シェーダー参照と UI 設定、デモマップ、デモシーンの各ステップが順に表示されます。初回の実行には少し時間がかかることがあります。
  3. 最後に、デモシーンをビルド設定に追加するかどうかを確認するダイアログが表示されます。デモを含むプレイヤーをビルドする予定がなければ、Not Now をクリックします(デモシーンは後から File ▸ Build Profiles で追加できます)。ビルド設定を変更するのは Add Scenes だけです。
  4. プロジェクトウィンドウで、ゲームプレイのデモシーンが選択された状態になります。開いているシーンはそのまま開いており、何も置き換えられません。

生成される内容:

フォルダー(パッケージフォルダー内)内容
Content/Textures地面、崖、水、ディテール、プロップ、ブラシのテクスチャ。
Content/Materials、Content/Meshes、Content/Prefabsローポリの木、茂み、岩、プロップと、それらのマテリアル。
Content/TilesetsSummer と Winter のタイルセット、および Summer Auto Paint プロファイル。Summer には、地面レイヤー Grass、Lush Grass、Dirt、Rocky Dirt、Sand、Cobblestone、Dark Forest Floor、Rock、Snow、Blight と、崖タイプ Rock Cliff、Grassy Cliff があります。
Content/LibrariesDemo Vegetation(草、背の高い草、花、シダと、木の Pine、Broadleaf、Dead Tree、Bush)と Demo Decor(岩、巨岩、切り株、その他のプロップ)。
Content/Brushesブラシ形状のスタンプ。
Content/Maps3 つのマップ:Two Rivers (1v1).rtsmap(128 × 128、点対称)、Four Kingdoms (4p).rtsmap(192 × 192、4 回回転対称)、Sandbox.rtsmap(96 × 96、台地 1 つと池 1 つがある、ほぼ平坦なマップ)。
Samples/Scenes以下で説明する 3 つのデモシーン。
Resources/RTSTerrainStudioシェーダー参照と、ランタイム UI のパネル設定。

ジェネレーターを再度実行すると、Regenerate / Cancel の確認が表示されます。上書きされるのは生成されたデモアセットとデモシーンだけで、自分で作成したアセットに手が加えられることはありません。内容が変わっていないファイルは書き換えられません。

URP レンダラーに戦場の霧のレンダラー機能がない場合は、ジェネレーターがそれも追加します。URP アセットの深度テクスチャと不透明テクスチャの設定は変更しません。デモ用のカメラが、これらのテクスチャを自分で要求するためです。自分のシーンで水を使う場合は、上記のとおり Validate Project を実行してください。

デモシーンを開く

デモシーンは RTSTerrainStudio/Samples/Scenes にあります。プロジェクトウィンドウでダブルクリックして開くか、Welcome ウィンドウの手順 3 で選択して Open をクリックします。

Gameplay Demo シーン:戦場の霧とミニマップが表示された Two Rivers マップ上のユニット
Two Rivers (1v1) マップの Gameplay Demo シーン。
シーンマップ内容試してみること
RTS Terrain Studio - Gameplay Demo Two Rivers (1v1) RTS カメラ、戦場の霧、およびゲームを自動的に開始する RTS Demo Game オブジェクト。 Play をクリックします。自分のユニット(プレイヤー 1)が開始地点にスポーンします。敵ユニットは周囲を歩き回り、戦場の霧の中で現れたり消えたりします。
RTS Terrain Studio - In-Game Editor Demo Sandbox ゲーム内マップエディター(RTS In-Game Editor オブジェクト)と、同じゲームプレイシステム。ゲームプレイシステムはテストプレイするまでオフのままです。 Play をクリックし、ゲーム内エディターでペイントしてから、Play Test(F5 キー)を押します。Esc キーを押すとエディターに戻ります。
RTS Terrain Studio - Editor Playground Four Kingdoms (4p) 地形、ライト、カメラのみで、ゲームプレイ要素はありません。 編集モードのまま Tools ▸ RTS Terrain Studio ▸ Editor Window を開き、完成したマップでツールを試します。

ゲームプレイデモの操作方法

入力操作
W A S D / 矢印キー、画面端、マウスの中ボタンのドラッグカメラをパンします。ユニットを選択しているときは、S キーでパンする代わりにユニットを停止します。矢印キーは常にパンします。
マウスホイールズーム
Q / E、Alt + 右ドラッグ視点を回転
左クリック / 左ドラッグユニットを選択 / 選択ボックスを描画
Shift + クリックユニットを選択に追加、または選択から除外
ダブルクリック、Ctrl + クリック画面上の同じ種類のユニットをすべて選択
右クリック隊形を組んで移動(Shift キーで命令をキューに追加)
S / H / P停止 / 位置を維持 / パトロール
Ctrl + 0…9、Shift + 0…9、0…9コントロールグループを登録 / 選択中のユニットをグループに追加 / グループを呼び出し(2 回押すとカメラをグループの位置に合わせます)
ミニマップ左クリックでカメラを移動、右クリックで選択中のユニットを移動
B、Delete建物を配置 / カーソル下の建物を撤去
T、木を右クリックカーソル下の木を切り倒す / 選択中の作業員に伐採させる
Esc現在のモードをキャンセル、または選択を解除
F1ヘルプパネルの表示・非表示を切り替え

はじめてのマップ

マップはシーン内の RTS Terrain ゲームオブジェクト上に存在し、プロジェクト内の .rtsmap ファイルに保存されます。シーンはこのファイルを参照するだけで、編集内容はファイルに保存されます。自由に試せるように、新しいシーンから始めます。

  1. File ▸ New Scene を選択し、URP のシーンテンプレート(たとえば Basic (URP))を選びます。
  2. Ctrl+S キーで、すぐにシーンを保存します(たとえば Assets/MyMaps/FirstMap.unity として)。新しいマップファイルは保存したシーンと同じ場所に作成されるため、マップとシーンをまとめて管理できます。

地形の作成

方法は 2 つあります。どちらの方法でも、RTSTerrain、RTSVegetationRenderer、RTSWaterRenderer、RTSDecorManager の各コンポーネントがセットアップ済みの、同じ RTS Terrain オブジェクトが作成されます。

方法メニュー使用する場面
ワンクリックGameObject ▸ RTS Terrain Studio ▸ RTS Terrainすぐにペイントを始めたいとき。プロジェクトのデフォルトのタイルセットとライブラリを使った平坦な 128 × 128 セルのマップ(セルサイズ 2 m、崖レベル 2)が作成され、シーンビューのピボット位置に配置されて選択された状態になります。シーンと同じフォルダーに <Scene name> Map.rtsmap という名前の新しいマップファイルが書き込まれます(シーンが未保存の場合は Assets/Maps/New Map.rtsmap)。
ガイド付きTools ▸ RTS Terrain Studio ▸ New Map Wizard...名前、サイズ、タイルセット、ベーステクスチャを自分で選びたいとき、またはプレイ可能なマップをランダムに生成したいとき。

プロジェクトにまだタイルセットがない場合、ワンクリックのメニューからは、まず Create Demo Content の実行が提案されます。Continue Without を選ぶと、タイルセットを割り当てるまで、地形はニュートラルなチェッカー模様で表示されます。

New Map Wizard は 5 つの手順で構成されています。Next と Back を使うか、上部のバーにある手順をクリックして移動します。

手順設定
1. BasicsMap Name(ファイル名にもなります)、Author(次のマップ用に記憶されます)、Description、Players(1–16。対戦マップは通常 2、4、8 人用に作られます)。
2. Sizeサイズカード:Tiny 64(短時間の 1v1 対戦やテスト向け)、Small 96(1v1 と 2v2)、Medium 128(2~4 人)、Large 192(4~6 人)、Huge 256(6~8 人)、Epic 384(8 人以上、キャンペーン)、Epic XL 512(非常に広大なワールド)、または Custom(16 の倍数)。ほかに、Cell Size (m)(迷ったら 2 m のままにします)、Cliff Level Height (m)、Base Cliff Level(デフォルトは 2。クラシック RTS のマップのように、下に掘り下げて湖を作れます)、テクスチャ・ディテール・通行判定の解像度を設定する Advanced 折りたたみがあります。
3. LookTileset(地面テクスチャをクリックすると、最初にマップ全体を覆う BASE レイヤーになります)、Vegetation Library、Decor Library。タイルセットがない場合は Create Demo Content ボタンが表示されます。
4. TerrainFlat(ベースの崖レベルにある、何もない平坦なマップ。ツールを覚えるのに最適です)または Generate(Preset と Seed から、台地、スロープ、水、開始地点、植生、装飾オブジェクトを生成します。Symmetry、Start Locations、Scatter Vegetation、Scatter Decor の設定と Preview ボタンがあります)。
5. CreateNew terrain object または Replace map of selected terrain、Map File のパス(Browse...)、Add Environment(マップの太陽、環境光、フォグを適用します。シーンに太陽のライトがない場合は Sun ライトを作成します)、Open Editor Window、および概要。最後に Create Map をクリックします。

ウィザードはマップファイルを書き込み、地形を作成し、シーンビューで地形全体が収まるように表示して、エディターウィンドウを開きます。すべての設定は、あとからエディターウィンドウの Map タブで変更できます。

エディターウィンドウを開く

Tools ▸ RTS Terrain Studio ▸ Editor Window を開きます(ウィザードや Welcome ウィンドウから開くこともでき、RTS Terrain のインスペクターにも Open RTS Terrain Studio ボタンがあります)。シーンビューを同時に見られるように、インスペクターの隣にドッキングします。

Cliffs タブを開いた RTS Terrain Studio のエディターウィンドウ
Cliffs タブを開いたエディターウィンドウ:Cliffs ツール、ツールの説明、ブラシ設定、対称設定。
領域表示内容
ヘッダー地形ピッカー、マップ名(ドットは未保存の変更があることを示します)、Save、Save As…、Open…、Reload、ステップ名を表示する Undo / Redo、ヘルプボタン、Editing On / Editing Off の切り替えボタン。
タブバー8 つのツールカテゴリ(Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers)と、それに続く Map と Settings。アクティブなツールのカテゴリにはドットが付きます。
ツールページ大きなツールボタン、アクティブなツールの説明とリアルタイムの状態を表示するカード、Brush カードと Symmetry カード、ツール固有の設定とパレット(レイヤー、崖レベル、プレイヤー、木、装飾オブジェクト)。
ステータスバーマウスカーソル下のセル、崖レベル、高さ、通行判定と、アンドゥに使用しているメモリ量。

8 つのカテゴリと、それぞれに含まれるツールは以下のとおりです。

キーカテゴリツール
1HeightRaise / Lower、Smooth、Flatten、Set Height、Noise、Terrace、Erode
2CliffsRaise / Lower Level、Set Level、Ramp、Cliff Type、Clean Cliffs
3PaintPaint Layer、Tile Paint、Fill / Replace、Smooth Paint、Auto Paint
4VegetationPaint Details、Erase Details、Place Trees、Erase Trees
5DecorPlace Decor、Erase Decor、Select Decor
6WaterPaint Water、Erase Water、Flood Fill
7PathingPaint Pathing、Clear Pathing
8MarkersStart Locations、Regions、Markers

各ツールの詳細は、それぞれの章で説明しています(たとえば Height ツールや Cliffs ツール)。ウィンドウ自体については RTS Terrain Studio ウィンドウで説明しています。

ペイントの基本

  1. エディターウィンドウでツールボタンをクリックします。これで編集が有効になります(ヘッダーに Editing On と表示されます)。アクティブなツールをもう一度クリックすると、編集が無効になります。
  2. シーンビューで地形の上にマウスを移動します。ブラシの円は地面の上に直接描画され、ペイントされる位置を正確に示します。
  3. マウスの左ボタンを押したままドラッグしてペイントします。
入力(シーンビュー)操作
マウスの左ボタンストロークをペイント(Start Locations などのクリックツールではクリック)
Shift + マウスの左ボタン逆の操作:下げる、消去する、削除する
Ctrl + クリック対応するツールで、カーソル下の値(高さ、地面レイヤー、水面の高さ…)をスポイトで取得
[ / ]ブラシを小さくする / 大きくする
- / =強さを下げる / 上げる
1 … 8ツールカテゴリを切り替え(もう一度押すと、そのカテゴリ内のツールを順に切り替え)
G / Pグリッドの表示切り替え / 通行オーバーレイの切り替え(Off、Walkability、Buildability、Combined の順)
Rブラシのランダム回転のオン/オフ
Ctrl+Z / Ctrl+Y地形の編集を元に戻す / やり直す(Edit メニューにステップ名が表示されます)
Esc実行中のストロークをキャンセル、または編集を終了

シーンビューには RTS Terrain Studio オーバーレイも表示されます。このオーバーレイには、編集の切り替えボタン、カテゴリボタンとツールボタン、Size と Strength のスライダー、Symmetry 設定があります。また、下部のステータスラインには、カーソル下のセル、崖レベル、高さが表示されます。ほとんどの作業は、シーンビューから離れずに行えます。

シーンビューのカメラ操作は通常どおりです(右ドラッグでフライスルー、Alt キー + 左ドラッグでオービット、中ドラッグでパン)。Unity の Hand ツール(Q)がアクティブな場合は、左ドラッグでペイントではなくパンが行われます。ペイントに戻るには W キーを押します。RTS Terrain Studio のショートカットはすべて、Edit ▸ Shortcuts...(macOS では Unity ▸ Shortcuts...)の RTS Terrain Studio カテゴリで割り当て直せます(使用しているレイアウトで 2 キーが Unity の 2D モード切り替えと競合する場合に便利です)。

高さのペイント

  1. Height タブ(1)を開き、Raise / Lower をクリックします。
  2. 地形の上をドラッグして丘を作ります。マウスボタンを押し続けると、さらに盛り上がります。Shift キーを押したままにすると下げられます。
  3. Smooth に切り替え、とがった部分の上をドラッグして滑らかにします。
  4. 起伏のある地面には Noise を、建物を建てるエリアには Flatten(Ctrl+クリックで高さをスポイトで取得)を試してみてください。

高さは、崖レベルの上に重なる細かな起伏です。台地の上の丘は、その台地の上にとどまります。

崖とスロープのペイント

崖はクラシック RTS のマップの要です。マップは崖レベル 0~15 で構成され、初期状態ではベースレベル(デフォルトでは 2)になっています。1 レベルが、くっきりとした垂直な崖 1 段分に相当します。

  1. Cliffs タブ(2)を開き、Raise / Lower Level をクリックします。
  2. 地形をクリックすると、地面が崖レベル 1 つ分盛り上がります。マウスボタンを押したままドラッグすると、作り始めた台地を同じレベルのまま広げられます。
  3. 台地の内側をクリックしてもう一度ドラッグすると、その上に 2 段目のレベルを重ねられます。
  4. Shift キーを押したままにすると、1 レベル下げられます。ベースレベルより下げると、湖や川のためのくぼ地を掘れます。
  5. Ramp をクリックし、1 レベル分の崖の縁をまたぐようにペイントすると、その縁を歩行可能な斜面に変えられます。セルの 4 つの角すべてがスロープである必要があるため、縁をまたいで広めにペイントしてください。Shift キーでスロープを削除できます。
  6. 乱れた縁には Clean Cliffs を使い、1 頂点だけの柱やくぼみを取り除きます。Cliff Type では、別の崖マテリアルをペイントできます(たとえば Rock Cliff の代わりに Grassy Cliff)。

テクスチャのペイント

  1. Paint タブ(3)を開き、Paint Layer をクリックします。
  2. レイヤーパレットで地面レイヤー(たとえば Dirt)をクリックします。
  3. 地形の上をドラッグして、道や開けた場所をペイントします。Shift キーでレイヤーを消去し、Ctrl+クリックでカーソル下のレイヤーをスポイトで取得します。
  4. クラシック RTS スタイルの境界がくっきりしたタイルには Tile Paint を、広い範囲には Fill / Replace を使います。
  5. 手早く自然な見た目にするには、Paint タブの Auto Paint Whole Map カードで Profile を Summer Auto Paint に設定して使います。崖の近くには土、岸辺には砂、急斜面には岩、というように自動でペイントされます。

木と草の配置

  1. Vegetation タブ(4)を開き、Place Trees をクリックします。
  2. Trees で 1 種類以上の木を選択します(たとえば Pine と Broadleaf)。複数の種類を選ぶと、ランダムに混ぜて配置されます。
  3. Mode は Scatter のままにし、マップの端に沿ってドラッグして森を作ります。Density は、1 回のダブ(ブラシを 1 回置いた跡)で追加される木の数を制御します。Max Slope より急な傾斜や水中(Avoid Water)には、木は配置されません。
  4. 1 回のクリックで木を 1 本ずつ配置するには、Mode を Single に設定します。Shift キーで木を削除できます。
  5. Paint Details をクリックし、Details で Grass または Flowers Yellow を選択して、草地をペイントします。Target Density は、ブラシでペイントする密度を設定します。

Vegetation ツールを使うには、地形に植生ライブラリが割り当てられている必要があります。Create Demo Content の実行後に作成した地形には、Demo Vegetation が自動的に割り当てられます。装飾オブジェクトも同様に、Decor タブ(5)の Place Decor と Demo Decor ライブラリで配置できます。

水の追加

水は、目標とする水面の高さより低い地面を満たします。最も簡単なのは、Cliffs ツールでくぼ地を掘り、そこを水で満たす方法です。

  1. Cliffs タブで Raise / Lower Level を Shift キーと組み合わせて使い、ベースレベルより 1 レベル低い湖や川を掘ります(デフォルトのマップではレベル 1)。
  2. Water タブ(6)を開き、Flood Fill をクリックします。
  3. デフォルトの Water Height モード(崖レベル + オフセット)のまま、Level を 1 にします。デフォルトのオフセットでは水面がベースレベルのすぐ下に来るため、くぼ地は水で満たされ、周囲の陸地は乾いたままになります。
  4. くぼ地の内側をクリックします。1 回のクリックで水が満たされます。Shift+クリックで、つながっている水を抜きます。
  5. 手動で水をペイントするには Paint Water を、水を取り除くには Erase Water を使います。

浅水域と深水域は水深から自動的に決まり、通行判定に影響します。地上ユニットは浅水域を歩いて渡れますが(建設には乾いた地面が必要なため、浅水域には何も建設できません)、深水域は通れません。

水に屈折がなく、岸辺の表現が水中に立っているオブジェクトに反応しない場合は、URP の Depth Texture と Opaque Texture がオフになっています。Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、Enable Both を適用してください。

開始地点の配置

開始地点は、プレイヤーのスポーン地点です。デモゲームでは、各プレイヤーのユニットがそこにスポーンします。

  1. Markers タブ(8)を開き、Start Locations をクリックします。
  2. プレイヤーパレットで P1(プレイヤー 1、赤)を選択します。
  3. プレイヤー 1 を開始させたい位置で地形をクリックします。開始地点はプレイヤーごとに 1 つだけなので、もう一度クリックすると開始地点が移動します。向きは Yaw で設定します。
  4. 次のプレイヤーを選択して、もう一度クリックします。Shift+クリックで、最も近い開始地点を削除します。

対称を有効にすると、1 回のクリックで全プレイヤーの開始地点を一度に配置できます。Symmetry Assigns Next Players がオンになっていれば、対称コピーは後続のプレイヤー(P1、P2、…)に割り当てられます。詳しくは 10 分間チュートリアルを参照してください。

開始地点は、崖の縁から離れた平坦で乾いた地面に配置してください。Map タブ(Validation カード)の Validate Map を使うと、開始地点の不足や、崖の縁または深水域にある開始地点などが報告されます。

マップの保存

編集内容は、.rtsmap ファイルに保存するまでメモリ上にだけ存在します。

  • エディターウィンドウのヘッダーにある Save をクリックします。未保存の変更があるあいだはこのボタンが強調表示され、マップ名の横にドットが表示されます。
  • Save As… は、Assets 内の任意の場所に新しい .rtsmap ファイルを書き込み、それを地形に割り当てます。
  • シーンを保存すると(Ctrl+S)、シーン内の地形のマップも保存されます。この動作は、シーンビューのオーバーレイのオプションメニュー(Save Maps When Saving the Scene)でオフにできます。
  • RTS Terrain のインスペクターとシーンビューのオーバーレイメニューにも、Save と Save As の項目があります。

マップに未保存の変更がある状態で再生モードに入る、シーンを閉じる、または Unity を終了すると、変更を保存するかどうかを確認するダイアログが表示されます。プロジェクトウィンドウで .rtsmap ファイルを選択すると、ミニマップのプレビューと Open In Scene ボタンが表示されます。

マップファイルのない地形は、マップをメモリ上にしか保持しません。「The map of '…' is not saved as a map asset yet」というダイアログが表示された場合は、Save As… を選択してファイルを作成してください。

はじめてのテストプレイ

マップをプレイする方法は 2 つあります。再生モードで Demo Game を使う方法と、実行中のゲーム内でゲーム内エディターとその Play Test ボタンを使う方法です。

Demo Game でのプレイ

  1. 地形があるシーンを開きます。開始地点が少なくとも 1 つあることを確認してください(ない場合、ユニットはマップの対角にある 2 つの隅にスポーンします)。
  2. GameObject ▸ RTS Terrain Studio ▸ Demo Game を選択します。RTS Demo Game オブジェクトが作成され、選択されます。
  3. Play をクリックします。確認を求められたら、マップを保存します。
  4. デモゲームは、必要なものをすべて自動でセットアップします(戦場の霧、RTS カメラ、ユニットの選択、移動命令、ミニマップ、HUD、建物の配置、木の伐採)。自分のユニット(プレイヤー 1)は自分の開始地点にスポーンし、ほかのプレイヤーのユニットは周囲を歩き回ります。
  5. F1 キーを押すとヘルプパネルが表示されます。操作方法の一覧はゲームプレイデモの操作方法を参照してください。
  6. 再生モードを終了します。ゲーム中に伐採された木は元に戻るため、マップは作成したときの状態のまま保たれます。

RTS Demo Game コンポーネントには、Local Team、Units Per Player、Unit Prefabs(空の場合はプレースホルダーのユニットを使用)、Fog Of War、Minimap、Hud などのオプションがあります。カメラには、シーンのメインカメラが使われます。

戦場の霧の中でもユニットが見えたままになる場合は、RTS Fog Of War レンダラー機能が追加されていません。Validate Project を実行し、Add Feature を適用してください。

ゲーム内での編集とテストプレイ

ゲーム内エディターは、同じツール一式を独自の UI 付きで実行中のゲームに組み込んだものです。これを使うと、リリースしたゲームの中で、プレイヤーや MOD 制作者がマップを作成してプレイできます。

  1. 地形とカメラがあるシーンを開きます。RTS Demo Game がまだない場合は、上記の手順で追加します。
  2. GameObject ▸ RTS Terrain Studio ▸ In-Game Editor を選択します。
  3. Play をクリックします。エディターが開き、上部バー(File、Map、元に戻す・やり直し、Symmetry、Grid、Pathing、Markers、Play Test)、左側のカテゴリとツールのバー、右側のツールパネル、ステータスバーが表示されます。デモゲームは、テストプレイを開始するまで待機します。
  4. Unity エディターと同じようにペイントします。マウスの左ボタンでペイント、Shift キーで反転、Ctrl+クリックでスポイト、[ ] と - = でブラシの調整、1…8 でカテゴリの切り替えです。カメラは W A S D キー、右ドラッグまたは中ドラッグ、Q/E キー、マウスホイールで動かします。
  5. Play Test をクリックするか、F5 キーを押します。ユニットがスポーンし、戦場の霧と RTS カメラが有効になります。
  6. Esc キーを押すとエディターに戻ります。マップはテストプレイ前の状態に復元されます。

ゲーム内エディターで便利なキー:F1 ヘルプ、F2 エディター全体の表示・非表示、H パネルを隠す、G グリッド、P 通行オーバーレイ、M マーカー、F カーソル下の地点にフォーカス、Ctrl+Z / Ctrl+Y 元に戻す・やり直し。

ゲーム内エディターでは、File ▸ Save(Ctrl+S)と File ▸ Save As...(Ctrl+Shift+S)を使うと、リリースしたゲームと同じように、ゲームのマップフォルダー(デフォルトでは Application.persistentDataPath 内の Maps フォルダー)にマップファイルが書き込まれます。編集後に Unity エディターで再生モードを終了すると、「Save map changes made in Play Mode?」というダイアログが表示されます。Save を選ぶと変更が地形の .rtsmap アセットに書き込まれ、Discard を選ぶと変更は破棄されます。

ゲーム内エディターのすべての機能については、ゲーム内エディターの章で詳しく説明しています。

プログラマー向け:テストプレイに反応する

自作のゲームプレイコードでも、デモゲームが使用しているのと同じイベントを受け取れます。次の例では、テストプレイの開始時に、各プレイヤーの開始地点をログに出力します。

using RTSTerrainStudio;
using UnityEngine;

// Add this component to any GameObject in a scene with an RTS Terrain and an in-game editor.
public class PlaytestStartLogger : MonoBehaviour
{
    void OnEnable()
    {
        // Raised by the in-game editor when a play test starts (true) or stops (false).
        RTSRuntimeEditorEvents.PlaytestChanged += OnPlaytestChanged;
    }

    void OnDisable()
    {
        RTSRuntimeEditorEvents.PlaytestChanged -= OnPlaytestChanged;
    }

    void OnPlaytestChanged(bool playing)
    {
        if (!playing) return;

        // The first enabled terrain of the scene.
        RTSTerrain terrain = RTSTerrain.Main;
        if (terrain == null || terrain.Map == null) return;

        foreach (StartLocation start in terrain.Map.StartLocations)
        {
            // Start locations are stored in map-local coordinates: convert to world space
            // and move the point onto the ground.
            var local = new Vector3(start.position.x, start.position.y, start.position.z);
            Vector3 world = terrain.SnapToGround(terrain.LocalToWorld(local));
            Debug.Log($"Player {start.player + 1} starts at {world}, facing {start.yaw:0} degrees");
        }
    }
}

地形へのクエリ、マップの読み込み、ツール API については、スクリプティングガイドを参照してください。

10 分間チュートリアル:はじめての 1v1 マップ

このチュートリアルでは、2 人用の小さく公平なマップを作成します。各プレイヤーにはスロープ付きの台地の上に拠点があり、川がマップを 2 つに分け、森がマップの周囲を囲みます。対称ペイントによって両側がまったく同じになるため、作るのは半分だけで済みます。

対称な 2 人用マップを上から見た図
上から見た点対称の 1v1 マップ。片側にペイントしたものはすべて、反対側に 180° 回転して現れます。

始める前に、プロジェクトの検証が済んでいて、デモコンテンツが作成済みであることを確認してください(インストールとセットアップを参照)。

1. マップの作成(1 分)

  1. File ▸ New Scene を選択し、URP テンプレートを選んで、シーンを Assets/MyMaps/Duel.unity として保存します。
  2. Tools ▸ RTS Terrain Studio ▸ New Map Wizard... を開きます。
  3. Basics:Map Name に Duel、Author に自分の名前を入力し、Players を 2 にします。Next をクリックします。
  4. Size:Small(96 × 96、1v1 と 2v2 向け)をクリックします。Cell Size は 2 m、Base Cliff Level は 2 のままにします。Next をクリックします。
  5. Look:Tileset に Summer を選択し、ベースレイヤーとして Grass をクリックします。Vegetation Library は Demo Vegetation、Decor Library は Demo Decor にします。Next をクリックします。
  6. Terrain:Flat を選択します。Next をクリックします。
  7. Create:New terrain object のまま、Add Environment と Open Editor Window をオンにして、Create Map をクリックします。

2. 対称の有効化(10 秒)

  1. エディターウィンドウで Cliffs タブを開きます。Brush カードの下にある Symmetry カードを探します。
  2. Rotate 180° を選択します。中心はマップの中心(0.5, 0.5)のままにします。
  3. 地形の上にマウスを移動すると、マップの反対側に 2 つ目のブラシが表示され、シーンビューに対称ガイドが描画されます。

対称設定はすべてのツールで共有されるため、ツールを切り替えても有効なままです。モードは以下のとおりです。

モード(ウィンドウ)コピー数主な用途
Off1キャンペーンやストーリー用のマップ
Mirror X2左右をミラーリング
Mirror Z2上下をミラーリング
Mirror XZ4両方の軸でミラーリング(4 人)
Rotate 180°2点対称。クラシックな 1v1 レイアウト(隅と隅で対峙)
Rotate 90°44 人用の 4 回回転対称。正方形のマップが必要
Diagonal2対角線でミラーリング。正方形のマップが必要

シーンビューのオーバーレイとゲーム内エディターでも、同じモードを選択できます(オーバーレイでは列挙型の名前で表示されます。たとえば Rotate 180° は Rotate 2 と表示されます)。

3. 拠点の構築(2 分)

  1. Raise / Lower Level をクリックします。[ キーと ] キーで、ブラシを大きめに設定します。
  2. マップの左下 4 分の 1 の領域でクリックしてドラッグし、マップの幅の約 4 分の 1 の大きさの台地をペイントします。右上 4 分の 1 の領域にも同じ台地が現れます。
  3. Ramp をクリックし、マップの中心に面した台地の縁をまたぐようにペイントします。ユニットの集団が通れるように、スロープの幅は少なくとも 2~3 セルにします。
  4. Clean Cliffs をクリックし、台地の輪郭に沿って 1 回ペイントして、ぽつんと残った 1 頂点だけの出っ張りを取り除きます。

4. 川の掘削(2 分)

  1. Raise / Lower Level のまま Shift キーを押し、左上の隅からマップの中心に向かってドラッグして川を掘ります。対称コピーが右下の隅から川を延長するため、川がマップ全体を横断します。
  2. プレイヤー同士が行き来できるように、中心付近に陸続きの通路を 1~2 か所残しておきます。
  3. Water タブを開いて Flood Fill をクリックし、Level を 1 のままにして川床をクリックします。まだ水が満たされていない部分があれば、そこをもう一度クリックします。

5. 地面のペイント(2 分)

  1. Paint タブを開きます。Auto Paint Whole Map カードで Profile を Summer Auto Paint に設定し(空のままにすると、タイルセットのレイヤー名に合わせた組み込みルールが使われます)、Auto Paint Whole Map をクリックします。確認ダイアログで確定します。マップ全体の再ペイントは 1 アンドゥステップにまとめられます。崖の近くには土、川の岸辺には砂、急斜面には岩がペイントされます。
  2. Paint Layer をクリックして Cobblestone を選択し、各台地のメインの建物を建てる場所に四角形をペイントします(1 回のダブで十分です。もう一方の拠点には対称コピーがペイントされます)。
  3. Dirt を選択し、拠点のスロープから陸続きの通路までの道をペイントします。
  4. 任意:Height タブで、強さを低くした(-)Raise / Lower を使って拠点のあいだにゆるやかな丘をいくつか追加し、Smooth で滑らかにします。

6. 森、草、岩(2 分)

  1. Vegetation タブを開き、Place Trees をクリックします。Pine と Broadleaf を選択し、Scatter のままにします。
  2. マップ境界に沿って、また各拠点の背後に森をペイントします。スロープ、道、陸続きの通路はふさがないようにします。Shift キーで木を削除できます。
  3. Paint Details をクリックし、Grass と Flowers Yellow を選択して、草地をいくつかペイントします。
  4. Decor タブを開いて Place Decor をクリックし、岩をいくつか選択して、川の岸辺の近くをクリックします。

7. 開始地点の配置(30 秒)

  1. Markers タブを開き、Start Locations をクリックします。
  2. プレイヤー P1 を選択し、Symmetry Assigns Next Players がオンになっていることを確認してください。
  3. 左下の拠点の中央を 1 回クリックします。そこに P1 が配置され、反対側の拠点に P2 が現れます。中心の方向を指す Yaw を設定しておけば、両方とも中心を向きます。

8. 検証、保存、プレイ(1 分)

  1. Map タブを開き、Validation カードの Validate Map をクリックします。エラー(たとえば崖の縁にある開始地点)が報告されたら、すべて修正します。
  2. ウィンドウのヘッダーにある Save をクリックします。マップが Assets/MyMaps/Duel.rtsmap に書き込まれます。
  3. GameObject ▸ RTS Terrain Studio ▸ Demo Game を選択し、Play をクリックします。
  4. 自分のユニットを選択し、右クリックで陸続きの通路の向こうへ送り出して、戦場の霧の中から敵が姿を現す様子を確かめます。

4 人用にしたい場合は、正方形のマップ(たとえば Medium 128 × 128)を作成し、Rotate 90° を選択して、Players を 4 に設定します。すると、Start Locations ツールで 1 回クリックするだけで P1~P4 が配置されます。ジェネレーターに任せることもできます。ウィザードの Terrain 手順で Generate を選択し、Symmetry と Start Locations を設定して Preview をクリックしたら、その結果を手作業で仕上げます。

次のステップ

  • エディターウィンドウ:Map タブ(サイズ変更、ジェネレーター、ハイトマップのインポートとエクスポート、ミニマップ、環境)を含む、すべてのタブ、カード、設定の詳しい解説。
  • ツールの各章(カテゴリごとに 1 章):Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers。
  • ゲーム内エディター:エディターをゲームに同梱し、プレイヤーがマップを作れるようにする方法。
  • スクリプティング:コードから高さ、崖レベル、通行判定を取得したり、ランタイムでマップを読み込んだり、編集に反応したりする方法。
  • 連携:Mirror によるマルチプレイヤー、A* Pathfinding Project または Unity NavMesh による経路探索。
  • トラブルシューティング:ピンク色のマテリアル、平坦に見える水、ペイント位置のずれなど、よくある問題への対処法。

プロジェクトのセットアップ

RTS Terrain Studio を使うには、いくつかのプロジェクト設定が必要です。2 つのカメラテクスチャを有効にした Universal Render Pipeline、戦場の霧(Fog of War)のレンダラー機能、Linear カラースペース、そして 3 つの Unity パッケージです。Tools ▸ RTS Terrain Studio ▸ Validate Project ウィンドウがこれらすべてをチェックし、ほとんどの項目をワンクリックで修正するため、たいていの場合、この章で説明する手動の手順は必要ありません。チェックが赤のまま解消されない場合、修正によって何が変わるのかを知りたい場合、またはプロジェクトを手動でセットアップする場合は、このまま読み進めてください。

Project Validator の各カードにあるヘルプボタンをクリックすると、この章の該当するセクションが開きます。

Project Validator

Tools ▸ RTS Terrain Studio ▸ Validate Project を開くか、Welcome ウィンドウで Validate をクリックします。ウィンドウを開くとすぐにチェックが実行されます。チェック項目は次の 5 つのカテゴリに分かれています。

カテゴリチェック内容
RenderingURP がアクティブであること。Depth Texture と Opaque Texture が有効になっていること。戦場の霧のレンダラー機能が追加されていること。プレイヤービルドで、RTS Environment が使用するフォグモードが保持されること。
Projectカラースペースが Linear であること。Burst、Collections、Mathematics がインストールされていること。どの入力処理が有効になっているか。
WebGLWeb がアクティブなビルドプラットフォームの場合のみ:Web ビルドの品質レベルで Depth Texture と Opaque Texture が有効になっていること。Web テンプレートでゲームがブラウザーウィンドウ全体に表示されること(Project Validator の Web チェックを参照)。
Contentパッケージのシェーダーがコンパイルでき、ビルドに含まれていること。タイルセットのテクスチャ配列がビルド済みであること。インスタンス描画されるプロトタイプで GPU インスタンシングが有効になっていること。
Scene開いているシーン内のすべての RTS Terrain に、タイルセットとマップファイルが割り当てられていること。

各結果には、次の 4 つのステータスのいずれかが付きます。

ステータス意味
OKすべて正しく設定されています。
Infoヒントまたは推奨事項です。不具合はありません。
Warning機能が制限された状態で動作します(たとえば、屈折のない水)。修正することをお勧めします。
Error必須の設定が不足しています。パッケージの一部が動作しません。

Fix All は、利用できるすべての修正を適用します。最初にダイアログですべての変更内容が一覧表示され、Apply Fixes をクリックするまでは何も変更されません。URP を必要とする修正(カメラテクスチャと戦場の霧のレンダラー機能)はパイプラインの割り当て後に自動的に実行されるため、新規プロジェクトもワンクリックでセットアップできます。Run Again はチェックを再実行します。すべてのチェックとその修正ボタンの一覧は、プロジェクトの検証に記載されています。

Project Validator がチェックするのはプロジェクトです。個々のマップ(開始地点、到達可能性、不足しているプロトタイプ、パフォーマンス上の制限)をチェックするには、Map タブのマップ検証機能を使用します。詳しくはマップの検証を参照してください。

コードからのチェックの実行

RTSProjectValidator.RunChecks() は何も変更せずに結果を返し、RTSProjectValidator.FixAll() は修正を適用します。これらはビルドスクリプトや継続的インテグレーション(CI)のジョブで使用できます。次の例のスクリプトは Editor フォルダーに配置する必要があります。

using UnityEditor;
using UnityEngine;
using RTSTerrainStudio.Editor;

// Command line: Unity -batchmode -projectPath <path> -executeMethod ProjectSetupCheck.Run -quit
public static class ProjectSetupCheck
{
    public static void Run()
    {
        // Runs every check. Nothing is modified.
        var results = RTSProjectValidator.RunChecks();

        // Applies every available fix without dialogs, then checks again.
        int applied = RTSProjectValidator.FixAll(results, interactive: false);
        if (applied > 0) results = RTSProjectValidator.RunChecks();

        // Logs warnings and errors ("[Status] Title: Message").
        foreach (var result in results)
            if (result.IsProblem) Debug.LogWarning(result.ToString());

        // Fails the job when a required setting is still missing.
        if (RTSProjectValidator.WorstStatus(results) == RTSValidationStatus.Error)
            EditorApplication.Exit(1);
    }
}

不足しているパッケージに対する Install 修正は、パッケージマネージャーへのリクエストを開始するだけです。リクエストが完了するまで、パッケージのスクリプトはコンパイルできません。バッチモードでパッケージを実行する前に、Burst、Collections、Mathematics をインストールしておいてください。

Universal Render Pipeline

このパッケージのシェーダーはすべて URP 用に書かれています。URP がないと、地形、水、植生はピンク色で表示されるか、まったく表示されません。Use URP 修正は、プロジェクト内の URP アセットを使用し(ない場合はレンダラー付きの Assets/Settings/RTS URP.asset を作成し)、それをデフォルトのレンダーパイプラインとして割り当てるとともに、ほかのパイプラインを使用しているすべての品質レベルにも割り当てます。同じことを手動で行うには、次の手順に従います。

  1. プロジェクトにまだ URP アセットがない場合は、プロジェクトウィンドウで Create ▸ Rendering ▸ URP Asset (with Universal Renderer) を選択して作成します。
  2. Edit ▸ Project Settings ▸ Graphics で、その URP アセットを Default Render Pipeline に割り当てます。
  3. Edit ▸ Project Settings ▸ Quality を開きます。Render Pipeline Asset のオーバーライドが設定されているすべての品質レベルについて、そのアセットも URP アセットであることを確認します。

Depth Texture と Opaque Texture

水は、カメラの深度テクスチャを使用して、水深に応じた色、泡、岸辺の柔らかな境界を、水中に立つオブジェクトにも合わせて表示します。屈折には不透明テクスチャを使用します。これらがないと、水の見た目が簡素になります。品質レベルで使用しているすべての URP アセットを選択し、それぞれの Rendering セクションで Depth Texture と Opaque Texture をオンにするか、Project Validator で Enable Both をクリックします。

カメラは、それぞれのインスペクターの Rendering セクションで、この 2 つの設定をオーバーライドできます。水は、描画するカメラごとにその設定に従います。それでもゲームビューの水が想定より簡素に見える場合は、ゲームのカメラでこれらがオフになっていないことを確認してください。シーンビューでは常に URP アセットの設定が使用されます。水の章の URP の要件も参照してください。

戦場の霧のレンダラー機能

RTS Fog Of War レンダラー機能(RTSFogOfWarRendererFeature)は、独自のマテリアルを使ったユニットやプロップも含め、画面上のすべてのものの上に霧を描画します。この機能がない場合、霧を表示できるのはパッケージ独自のシェーダーだけです。Add Feature は、使用中のすべての URP アセットのデフォルトレンダラーにこの機能を追加します。手動で追加するには、Universal Renderer Data アセットを選択し、Add Renderer Feature をクリックして RTS Fog Of War を選択します。詳しくはレンダリング:レンダラー機能とシェーダーのフォールバックを参照してください。

カラースペース

このパッケージは、URP のデフォルトである Linear カラースペースを前提に設計されています。Gamma カラースペースでは、スクリーンショットと比べて色、ライティング、水が色あせて見えます。Edit ▸ Project Settings ▸ Player ▸ Other Settings ▸ Color Space を Linear に設定するか、Project Validator で Switch to Linear をクリックします。

カラースペースを切り替えると、すべてのテクスチャが再インポートされます。大規模なプロジェクトでは時間がかかる場合があります。

必須パッケージ

ランタイムアセンブリは、ジョブとデータモデルに Burst(com.unity.burst)、Collections(com.unity.collections)、Mathematics(com.unity.mathematics)を使用します。これらのいずれか 1 つでも不足していると、パッケージのスクリプトはコンパイルされません。Window ▸ Package Manager ▸ Unity Registry からインストールするか、Project Validator で Install をクリックします。後者の場合、不足しているすべてのパッケージが 1 回のパッケージマネージャーリクエストで追加されます。動作確認済みのバージョンは必須パッケージに記載されています。

インポート時にコンパイルエラーが表示され、Tools ▸ RTS Terrain Studio メニューが見つからない場合は、Project Validator もパッケージの一部であるため、同様に実行できません。3 つのパッケージを手動でインストールしてから、Project Validator を開いてください。

入力処理

ランタイムのすべての入力(ゲーム内エディター、サンプルのカメラとユニット)は、静的クラス RTSInput を経由します。Input System パッケージ(com.unity.inputsystem)がインストールされている場合、ランタイムアセンブリは定義シンボル RTS_INPUT_SYSTEM 付きでコンパイルされ、Input System を使用します。それ以外の場合、またはプロジェクトが旧 Input Manager のみを使用している場合、RTSInput は旧来の UnityEngine.Input API にフォールバックします。

Active Input HandlingInput System のインストール結果
Input System Package (new)はいOK。Input System が使用されます。
BothはいOK。Input System が使用されます。
Input Manager (old)はいInfo。フォールバックが使用されます。
任意いいえInfo。フォールバックが使用されます。動作はしますが、新規プロジェクトにはお勧めしません。

この設定は Edit ▸ Project Settings ▸ Player ▸ Other Settings ▸ Active Input Handling にあります。設定を変更すると、Unity からエディターの再起動を求められます。適切な設定はゲームによって異なるため、Input handling チェックがこの設定を自動で変更することはありません。動作要件の入力処理も参照してください。

シェーダーとコンテンツ

  • Package shaders compile:パッケージのシェーダーにコンパイルエラーがある場合(該当するオブジェクトはピンク色で表示されます)、Reimport でそのシェーダーを再インポートします。URP の読み込みが完了する前にシェーダーがインポートされた場合は、これで解決します。必要なシェーダーそのものが見つからない場合は、Asset Store からパッケージを再インポートしてください。詳しくはトラブルシューティングを参照してください。
  • Shaders included in builds:ゲームのビルドを参照してください。
  • Tileset texture arrays built:最新のビルド済み配列がないタイルセットも動作しますが、配列が再ビルドされるまでは、マップの読み込み時にエディターが非圧縮の配列をビルドするため、余分なメモリと読み込み時間がかかります。Build Arrays は、古くなったすべてのタイルセットの配列をビルドします。プレイヤービルドでは、古くなった配列が自動的に再ビルドされます。一度もビルドされていないタイルセットは、ビルドしたゲームでも非圧縮の配列を使用します。詳しくはテクスチャ配列とプレイヤービルドでのテクスチャ配列を参照してください。
  • GPU instancing on instanced prototypes:インスタンス描画される木、Mesh モードのディテール、インスタンス描画される装飾オブジェクトのマテリアルでは、Enable GPU Instancing をオンにしてください。オフのままだと、マテリアルの一時的なコピーを介して描画され、インスタンシング用のバリアントがプレイヤービルドから除外される可能性があります。Enable Instancing は、一覧に表示されている編集可能なすべてのマテリアルでこの設定をオンにします。モデルやパッケージ内のマテリアルは読み取り専用のため、先に抽出してください。詳しくは GPU インスタンシングを参照してください。
  • Terrains have a tileset:タイルセットのない地形は、ニュートラルなプレースホルダーの外観で表示されます。この修正は、タイルセットのないすべての地形にプロジェクト内のタイルセットを割り当てます(元に戻せます)。詳しくはタイルセットを参照してください。

再生モード中にシーンに加えた変更は再生モードの終了時に失われるため、シーンに関する修正は再生モード中には利用できません。

シーン内の地形のマップファイル

RTS Terrain は、マップを .rtsmap アセット(Map Asset フィールドで指定)に保存します。このアセットがない地形はマップをメモリ上にしか持たないため、シーンを閉じると編集内容が失われます。Terrains save to a map file チェックには、こうした地形が一覧表示されます。Create Map Files は、一覧に表示された各地形の現在のマップを、そのシーンのフォルダー(シーンがまだ保存されていない場合は Assets/Maps)に新しい .rtsmap ファイルとして保存し、その地形に割り当てます。

New Map Wizard やデモコンテンツで作成した地形には、すでにマップファイルがあります。マップアセットや Save、Save As の仕組みについては、マップアセットとマップファイルを参照してください。

ゲームのビルド

Unity は、ビルドに含まれるマテリアルが使用しているシェーダーだけをプレイヤービルドに含めます。RTS Terrain Studio は地形、水、戦場の霧のマテリアルをランタイムでコードから作成するため、これらのシェーダーはパッケージフォルダー内のアセット Resources/RTSTerrainStudio/RTSShaderReferences.asset から参照されています。このアセットは DecorPlaceholder マテリアルも参照しており、これによって URP Lit のインスタンシング用バリアントがビルドに残ります。このアセットは、スクリプトのリロード後、シェーダーのインポート時、プレイヤービルドの前に毎回自動でチェックされ、更新されます。Shaders included in builds チェックが赤の場合は、Update をクリックします。

ビルドする前に、次の項目を順に確認してください。

  1. Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、すべてのエラーと警告を修正します。
  2. ターゲットプラットフォームのすべての品質レベルで、Depth Texture と Opaque Texture を有効にした URP アセットが使用されていることを確認します。戦場の霧を使用する場合は、そのレンダラーに RTS Fog Of War 機能が追加されていることも確認します。
  3. 地形が読み込み時に配列を非圧縮でビルドしなくて済むように、タイルセットのテクスチャ配列をあらかじめ一度ビルドしておきます(Project Validator の Build Arrays、またはタイルセットのインスペクターの Build Texture Arrays)。古くなった配列は、プレイヤービルドの前に毎回自動で再ビルドされます(以下を参照)。
  4. マップでフォグを使用する場合は、Fog kept in player builds チェックが OK であることを確認します(プレイヤービルドでのフォグを参照)。
  5. Map タブでマップを検証します(マップの検証を参照)。
  6. ビルドプロファイルのシーンリストにシーンを追加します。シーン内の RTS Terrain に割り当てられたマップは、ほかの参照アセットと同様にビルドに含まれます。
  7. ゲーム内エディターを同梱してリリースする場合は、ゲーム内エディターのリリースを参照してください。プレイヤーがランタイムで保存したマップは、RTSMapSerializer.StorageRoot(Application.persistentDataPath。Web ビルドではブラウザーのストレージ)内の Maps フォルダーに保存されます。詳しくはランタイムでのマップファイルを参照してください。
  8. Managed Stripping Level を Minimal より高く設定していて、独自の編集ツールを作成している場合は、コードストリッピングとカスタムツールを参照してください。
  9. Web ビルドの場合は、Web(WebGL)ビルドも参照してください。

RTSShaderReferences.asset やパッケージの Resources フォルダーを移動したり、名前を変更したりしないでください。パッケージはランタイムで Resources.Load を使ってこれらのアセットを読み込みます。

プレイヤービルドでのテクスチャ配列

ビルド済みのテクスチャ配列をソーステクスチャと比較できるのはエディターだけなので、ビルドしたゲームはタイルセットのビルド済み配列をそのまま使用します。古い地面テクスチャがビルドに含まれたままリリースされることがないように、以前に配列をビルドしたすべてのタイルセットは、プレイヤービルドの開始直前に次のようにチェックされます。

  • テクスチャ、解像度、圧縮設定と一致しなくなった配列や、別のプラットフォームまたは別のテクスチャ圧縮形式向けにビルドされた配列は、通常の圧縮品質で再ビルドされます。処理の進み具合は進行状況バーに表示されます。
  • 一度もビルドされていないタイルセットは対象外です。こうしたタイルセットについては、ビルドしたゲームがマップの読み込み時に非圧縮の配列をビルドします。
  • 再ビルドがキャンセルされたか失敗し、テクスチャまたは設定が変更されていた場合は、Try Again、Cancel Build、Build Anyway を選べるダイアログが表示されます。形式が異なるだけの配列でも正しいテクスチャが表示されるため、この場合は警告がログに出力されるだけです。
  • バッチモードのビルドは、このような場合には停止します。それでもビルドするには、コマンドラインに -rtsAllowOutdatedTextureArrays を追加します。その場合は、タイルセットごとに警告が出力されます。
  • グラフィックスデバイスのないエディター(ビルドサーバーでよく使われる -nographics)では、配列をビルドできません。この場合、古くなったタイルセットごとに警告を出力したうえでビルドが続行され、ビルドしたゲームでは最後にビルドされた状態の配列が使用されます。このようなビルドの前に、エディターで配列をビルドしておいてください。
  • 配列が再ビルドされるのは、アクティブなビルドプラットフォーム向けの場合だけです。別のプラットフォーム向けにビルドすると、代わりに警告がログに出力されます。先にそのプラットフォームに切り替えてください。
  • アセットバンドルや Addressables のコンテンツを単独でビルドする場合、このチェックは実行されません。先に配列をビルドしてください。

配列が古くなっているかどうかのチェックにはソーステクスチャのインポート状態が使われ、この状態は Unity のアップグレードによって変わることがあります。そのため、アップグレード後の最初のプレイヤービルドでは、デモのタイルセットも含め、ビルド済みのすべてのタイルセットの配列が再ビルドされる場合があります。

ビルド済みの配列は、読み取り可能な状態でタイルセットアセットに保存されます。ビルドしたゲームは、配列を初めて使用するときにこの CPU 側のコピーを解放します。これにより、タイルセット 1 つあたり、512 px で約 14 MB、1024 px で約 56 MB のメモリを節約できます。

プレイヤービルドでのフォグ

RTSEnvironment は、ゲームの実行中にマップに保存されたフォグを有効にします。Unity は、使われていないと判断したフォグモードのシェーダーバリアントを除外します。Project Settings ▸ Graphics ▸ Shader Stripping ▸ Fog Modes が Automatic に設定されている場合、フォグを有効にした状態で保存されたビルドシーンのフォグしか保持されません。RTS Environment は保存の前に毎回シーン本来のフォグ設定を復元するため、RTS Environment が適用したフォグはこの判定に含まれません。その結果、フォグを使うマップでも、ビルドではフォグが表示されなくなってしまいます。

Fog kept in player builds チェックは、アクティブなビルドプロファイルのビルドシーンと開いているシーンを読み取り、そこで RTS Environment が使用しているフォグモードを一覧表示します。Keep Fog Modes(Fix All にも含まれます)は、Fog Modes を Custom に設定し、これらのモードと、すでに保持されていたモードだけを保持します。保持するモードごとにシェーダーバリアントがビルドに追加されるため、使われていないモードは追加されません。アクティブなビルドプロファイルがグラフィックス設定をオーバーライドしている場合は、そのプロファイルの Fog Modes も確認してください。

Web(WebGL)ビルド

RTS Terrain Studio は Web ブラウザーで動作します。地形、植生、水、装飾オブジェクト、戦場の霧、ゲーム内エディター、サンプルのすべてが対象です。ただし、ブラウザーはデスクトップとは動作が異なります。プレイヤーが開けるフォルダーはなく、一部のキーはブラウザーが使用し、すべてのコードは 1 つのスレッドで実行され、ゲーム全体がサイズに上限のある 1 つのメモリヒープを共有します。このセクションでは、こうした違いにこのパッケージがどう対処しているか、そして Web ビルドの作成、テスト、公開の方法を説明します。

手短にまとめると、次のとおりです。

  1. File ▸ Build Profiles を開き、Web を選択して Switch Platform をクリックします(または、Web のビルドプロファイルをアクティブにします)。
  2. Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、Fix All をクリックします。Web がアクティブな場合、Project Validator はウィンドウ全体に表示するテンプレートもインストールし、Web ビルドの品質レベルもチェックします。
  3. マップでフォグを使用する場合は、Fog kept in player builds が OK になっていることを確認します。
  4. Build And Run をクリックします。Unity がゲームをビルドし、ブラウザーで開きます。

ブラウザーで動作する機能

項目サポート状況
グラフィックス APIWeb ビルドのデフォルトである WebGL 2.0(Auto Graphics API)。パッケージのすべてのシェーダーが動作します。このセクションでは WebGL 2.0 について説明します。Unity がオプションとして提供している WebGPU は対象外です。
ブラウザーUnity の Web プレイヤーがサポートしている現行のデスクトップブラウザー(マウスとキーボードで操作する場合)。
スマートフォンとタブレットページは読み込まれますが、ゲーム内エディター、RTS カメラ、サンプルのユニットはマウスとキーボード向けに作られています。タッチ入力はサポートされていません。このパッケージのテンプレートは、モバイルデバイスでは注意書きを表示します。
レンダリング地形、植生、水、装飾オブジェクト、戦場の霧は、デスクトップと同じ見た目になります。草は、構造化バッファの代わりに浮動小数点テクスチャに格納されます(レンダリングとパフォーマンスを参照)。
ゲーム内エディターすべてのツール、マップブラウザー、テストプレイ。マップはブラウザーのストレージに保存され、ダウンロードやインポートができます(ブラウザー内のマップを参照)。マップサイズとアンドゥ用メモリの上限は低くなります(ブラウザーでのゲーム内エディターを参照)。
スレッドWeb ビルドにはワーカースレッドがないため、すべてのジョブがメインスレッド上で順番に実行されます。

ウィンドウ全体に表示するテンプレート

Web ビルドを囲むページは、Web テンプレートから作られます。Unity 標準の Default テンプレートと Minimal テンプレートでは、デスクトップブラウザー上でゲームが固定サイズの枠内に表示されます(変更しない限り 960 × 600 ピクセル)。ゲーム内エディター、HUD、ミニマップはこの枠に合わせてスケーリングされるため、文字が読みにくくなります。そのため、このパッケージには独自のテンプレートが付属しています。

  • ゲームは、ゲーム名とフルスクリーンボタンを表示する細いフッターの上で、ブラウザーウィンドウいっぱいに表示され、ウィンドウのサイズに合わせてリサイズされます。スマートフォンとタブレットでは、ページ全体に表示されます。
  • 読み込みバー(パーセント表示付き)、フルスクリーンボタン、Unity のメッセージバナーはそのまま残ります。
  • 高解像度の画面では、レンダリング解像度が CSS ピクセルサイズの 1.5 倍に制限され、フレームレートが保たれます。調整するには、インストールしたテンプレートの index.html にある maxDevicePixelRatio を変更します(1 を下限として値を小さくするほど高速になり、大きくするほど鮮明になります)。
  • ゲーム内エディターのマップなど、ゲームが保存したファイルは、すぐにブラウザーのストレージに書き込まれます。
  • ページがほかのサーバーにリクエストを送信することはありません。アイコンもページに含まれています。

インストールするには、次の手順に従います。

  1. Tools ▸ RTS Terrain Studio ▸ Install WebGL Template を選択するか、Project Validator の Web template fills the browser window カードで Install Template をクリックします。
  2. テンプレートが Assets/WebGLTemplates/RTSTerrainStudio にコピーされ(Unity は Assets/WebGLTemplates 内のテンプレートしか検出しません)、Player Settings ▸ Resolution and Presentation ▸ Web Template で選択されます。
  3. テンプレートがすでにインストールされている場合は、既存のコピーを残して選択する(Keep and Select)か、パッケージのバージョンで置き換える(Replace)かを確認するダイアログが表示されます。このコピーはプロジェクトの一部なので、自由に編集できます。パッケージが確認なしにこのコピーを上書きすることはありません。

ビルドプロファイル:ビルドプロファイルはプレイヤー設定をオーバーライドでき、その場合はプロファイル独自のテンプレート設定が使われます。設定がそのプロファイルに反映されるように、テンプレートをインストールする前に Web のビルドプロファイルをアクティブにしてください。ほかのプラットフォームがアクティブな状態でインストールした場合は、Web のビルドプロファイルにあるプレイヤー設定のオーバーライドで、テンプレート RTSTerrainStudio を手動で選択してください。

このテンプレートは Unity の Default テンプレートをベースにしており、Unity 標準のテンプレート変数だけを使用しているため、プレイヤー設定に新しい項目は追加されません。スマートフォンとタブレットでは、This game is best played on a computer with a mouse and keyboard. という注意書きを数秒間表示します。ゲームがタッチ操作に対応している場合は、index.html からこの行を削除してください。

エディタースクリプト(たとえばビルドスクリプト)からは、RTSWebGLTemplateInstaller.InstallAndSelect(false) を使って、ダイアログを表示せずにテンプレートをインストールできます。既存のコピーがある場合はそれを残し、テンプレートを選択します。IsInstalled と IsSelected は現在の状態を返します。

Project Validator の Web チェック

Web がアクティブなビルドプラットフォームの場合、Project Validator には次の 3 つのチェックを含む WebGL グループが表示されます。

チェック修正チェック内容
Web quality levels have Depth and Opaque TextureEnable BothProject Settings ▸ Quality の Web 列でオンになっている品質レベル(Web のデフォルトレベルが最初に表示されます)と、それぞれが描画に使用する URP アセット。エディターは通常デスクトップの品質レベルで描画するため、Web ビルドでは、エディターでは一度も目にしない簡素な水になることがあります。各テクスチャには、毎フレーム画面全体のコピーが 1 回必要です。通常のチェック Depth and Opaque Texture enabled が同じアセットを報告する場合がありますが、Fix All は両方に対処します。
MSAA is off for Web buildsTurn MSAA OffWeb の品質レベルが、MSAA と Depth Texture または Opaque Texture を組み合わせた URP アセットで描画する場合はエラーになります。WebGL 2 の URP はこの組み合わせを描画できず、キャンバスは黒いままになり、ブラウザーのコンソールに Render Graph Execution error と表示されます。修正を実行すると、これらのアセットの MSAA がオフになります。同じアセットを共有するデスクトップの品質レベルでも MSAA が無効になるため、Web プラットフォームには専用の URP アセットを使う品質レベルを用意してください。
Web template fills the browser windowInstall Template / ReinstallUnity の Default テンプレートと Minimal テンプレートでは警告、パッケージのテンプレートが選択されているのにそのフォルダーがない場合はエラー(ビルドが失敗します)、パッケージのテンプレートでは OK になります。PWA テンプレートもゲームをウィンドウ全体に表示するので OK ですが、サービスワーカーがビルドをキャッシュするため、アップロードのたびに Player Settings ▸ Version を上げてください。それ以外のカスタムテンプレートの場合は、修正のない情報として報告されます。

Rendering カテゴリの Fog kept in player builds チェックはすべてのプラットフォームで実行され、Web ビルドにも関係します(プレイヤービルドでのフォグを参照)。

推奨ビルド設定

設定場所推奨値
Texture CompressionWeb のビルドプロファイルデスクトップブラウザー向けには Use Player Settings または DXT。モバイルブラウザー向けのビルドでは ASTC または ETC2 を選択します。タイルセットのテクスチャ配列はこの設定に従い(DXT5、ASTC 6×6、ETC2 RGBA8)、ビルドの前に再ビルドされます。選択した形式を使用できないブラウザーでは Unity がテクスチャを展開するため、約 4 倍のメモリが必要になります。
Maximum Memory SizePlayer Settings ▸ Publishing Settingsデフォルトの 2048 MB のままにします。ブラウザーでのゲーム内エディターの上限は、このヒープサイズを前提に設計されています。
Compression FormatPlayer Settings ▸ Publishing SettingsBrotli はダウンロードサイズが最も小さくなりますが、HTTPS でしか動作しません。Gzip は HTTP でも動作します。どちらも、サーバーで適切なヘッダーを設定する必要があります(Web サイトでの公開を参照)。
Decompression FallbackPlayer Settings ▸ Publishing Settingsゲームポータルなど、サーバーのヘッダーを設定できない場合はオンにします。起動が少し遅くなる代わりに、どこでもゲームを読み込めるようになります。
Data CachingPlayer Settings ▸ Publishing Settingsオン。ダウンロードしたゲームデータをブラウザーが保持するため、次回のアクセス時に速く起動します。
Enable ExceptionsPlayer Settings ▸ Publishing Settingsリリースビルドでは Explicitly Thrown Exceptions Only(デフォルト)。このモードでは null 参照が例外として報告されないため、バグを調査するときは Full With Stacktrace を設定した開発ビルドを使用してください。
Managed Stripping LevelPlayer Settings ▸ Other Settings ▸ Optimizationこのパッケージはどのレベルでも動作します。レベルを上げるとダウンロードサイズが小さくなりますが、その場合、カスタムの編集ツールには [Preserve] が必要です(コードストリッピングとカスタムツールを参照)。
品質レベルと URP アセットProject Settings ▸ Qualityブラウザーで使用しないデスクトップ向けの品質レベルでは Web 列をオフにし、それらの URP アセットがビルドにシェーダーバリアントを追加しないようにします。Web 用の URP アセットでは、シーンで必要な場合を除き、ライトクッキーとリフレクションプローブのブレンドをオフのままにしてください。WebGL 2.0 では、1 回の描画で使用できるテクスチャは 16 個までです(モバイル向けの注意点を参照)。このアセットでは Anti Aliasing (MSAA) もオフのままにしてください。Depth Texture または Opaque Texture と組み合わせると、WebGL 2 で URP が描画できなくなります。
Web TemplatePlayer Settings ▸ Resolution and PresentationRTSTerrainStudio(ウィンドウ全体に表示するテンプレートを参照)。

コンピューターでの Web ビルドのテスト

  • Build And Run を使うのが最も簡単です。Unity が小さなローカル Web サーバーを起動し、デフォルトのブラウザーでゲームを開きます。
  • ビルドされた index.html をダブルクリックしても動作しません。ブラウザーは file:// アドレスから Web ビルドを実行しないため、ページにはエラーメッセージしか表示されません。
  • 完成したビルドフォルダーを後で再び開くには、任意のローカル Web サーバーで配信します。圧縮ヘッダーを設定しないシンプルなサーバーで配信できるのは、Decompression Fallback をオンにしたビルドか、圧縮を無効にしたビルドだけです。
  • F1、F5、Ctrl+S、Alt+N、フルスクリーンでの Esc の各キーを、対象とするブラウザーで一度テストしてください(ブラウザーでのゲーム内エディターを参照)。

Web サイトでの公開

  • ビルドフォルダー全体(index.html、Build、TemplateData)をアップロードします。
  • 圧縮されたビルドでは、Build 内のファイルに 2 つのヘッダーが必要です。Brotli の場合は Content-Encoding: br、Gzip の場合は Content-Encoding: gzip と、適切な Content-Type(たとえば .wasm ファイルには application/wasm)です。Content-Encoding がないとページにエラーメッセージが表示され、Content-Type が正しくないと起動が遅くなります。サーバーを設定できない場合は、Decompression Fallback をオンにします。
  • itch.io などのゲームポータルは、ゲームを iframe 内に表示します。index.html を最上位に置いた ZIP ファイルをアップロードします。サンドボックス化された iframe では、Download Map に allow-downloads が、Leave site? の確認に allow-modals が必要です。実際のページで両方を確認してください。
  • 保存したマップは Web サイトに紐付いています。ブラウザーは、Web サイトのアドレス(プロトコル、ホスト、ポート)ごとにマップを保持します。同じ Web サイトに新しくアップロードしてもマップは残りますが、別の Web サイトに置いた同じゲームはマップのない状態で始まります。
  • Safari は、7 日間使用されなかった Web サイトの保存データを削除することがあります。また、iPhone と iPad の Safari は Leave site? の確認を無視します。残しておきたいマップはダウンロードするよう、プレイヤーに伝えてください。
  • iframe 内では、プレイヤーがゲーム内をクリックするまでキー入力はゲームに届きません。また、Esc キーを押すと常にブラウザーのフルスクリーンが解除されます(以下を参照)。

ブラウザー内のマップ

ブラウザーでは、Application.persistentDataPath はメモリ上のファイルシステムです。その内容は、要求があったときにだけブラウザーのデータベース(IndexedDB)にコピーされます。このパッケージのテンプレートは、変更のたびにこの要求を行います。ほかのテンプレートの場合は、保存、削除、インポートのたびにパッケージ自身が要求を行うため、リロード後も保存したマップは残ります。独自のコードでは、RTSWebFileSystem.Flush() で同じことを行えます(Web ビルド用のスクリプティング APIを参照)。

persistentDataPath はページのアドレスによって変わるため、別のアドレスに新しくアップロードすると(ゲームポータルではアップロードのたびに新しいアドレスになることがよくあります)、マップのない状態で始まってしまいます。そのため Web ビルドでは、マップは RTSMapSerializer.StorageRoot に保存されます。これは同じブラウザーのストレージ内にある、プレイヤー設定の会社名と製品名にちなんだ名前のフォルダーです。ほかのすべてのプラットフォームでは、StorageRoot は persistentDataPath です。ゲーム内エディターのマップフォルダーは、その中の Maps です。

プレイヤーはこのストレージをフォルダーとして開けないため、ゲーム内エディター自身がブラウザーとコンピューターの間でマップを移動します。

コマンド場所機能
Download MapFile メニュー未保存の変更を含む現在のマップを、ブラウザーで .rtsmap ファイルとしてダウンロードします(通常はダウンロードフォルダーに保存されます)。
Import Map...File メニューブラウザーのファイル選択ダイアログを開きます。選択した .rtsmap ファイルは、このブラウザーに保存されているマップに追加され、未保存の変更に関する通常の確認の後で開かれます。
Import...マップブラウザー(Open)Import Map と同じ機能です。場所を示す行の横にあります。
Downloadマップブラウザー(Open)選択したマップファイルをダウンロードします。
ドラッグ&ドロップゲームのキャンバスゲーム上にドロップした .rtsmap ファイルは、インポートされて開かれます。ほかのファイルはメッセージを表示して拒否されるため、ブラウザーがそのファイルを表示しようとしてページから移動することはありません。
  • ファイルは保存される前に全体がチェックされます。受け付けるファイルサイズは 128 MB までです。
  • インポートによって別のマップが置き換えられることはありません。同じファイルを再度インポートすると既存のものが再利用され、同じ名前の別のファイルは Name (2)、Name (3) のように保存されます。プレビュー画像はマップを開いたときに書き込まれます。
  • マップブラウザーには、フォルダーのパスの代わりに Saved in this browser. Use Download to keep a copy on your computer. と表示されます。ブラウザーでは、Show Maps Folder と Quit はありません。
  • Web サイトの閲覧データを削除すると、保存したマップも削除されます。

ブラウザーでのゲーム内エディター

未保存の変更があるタブを閉じるとき

マップに未保存の変更があり、Confirm Quit With Unsaved Changes がオンの場合、タブを閉じたりリロードしたりする前に、ブラウザーが Leave site? と確認します。この確認の文言はブラウザーが決めます。ファイルをすぐには保存しない Unity 標準のテンプレートでは、このオプションに関係なく、保存データがまだブラウザーのストレージに書き込まれている間も確認が表示されます。

閉じたエディターの再表示

ブラウザーでは、ファンクションキーがメディアキーとして使われていることが多く(Mac や多くのノートパソコン)、スマートフォンにはファンクションキーがありません。そのため、プレイヤーが閉じるボタン(×)でエディターを閉じると、左下に Open Map Editor (F2) ボタンが表示され、これでエディターを再び開けます。キーボードがない場合や Toggle Key が None の場合、ボタンの表示は Open Map Editor になります。このボタンは、閉じるボタンを使った後にだけ表示されます。ゲームのコードでエディターを非表示にした場合、どのように再表示するかはゲーム側で決めます。ブラウザーでは、Toggle Key が None の場合でも閉じるボタンを使用できます。

キーボード
  • New Map は Alt+N キーです。ブラウザーは Ctrl+N キーを新しいウィンドウ用に確保しており、ページには渡しません。Alt+N キーはデスクトッププラットフォームでも使用できます。Ctrl+T キーと Ctrl+W キーも、ページには届きません。
  • ブラウザーのショートカットは抑制されます。エディターが有効な間、ブラウザーは F1~F10 と Ctrl(または Cmd)+ S、O、D、A に反応しないため、これらはエディターのショートカットとして機能します。F11、F12、Ctrl+F5、Ctrl+R、コピー、貼り付け、ページのテキストフィールドに入力したキーは、引き続きブラウザーが処理します。
  • F5 キーでテストプレイを開始・終了できるため、Esc キーに頼る必要はありません。
  • フルスクリーンでの Esc:ブラウザーは、フルスクリーンの解除に Esc キーを使用します。Chrome と Edge では、iframe 内にない安全なページ(HTTPS または localhost)であれば、Esc キーを短く押すとゲームに届き、Esc キーを長押しするとフルスクリーンが解除されます。それ以外の環境では、最初の Esc でフルスクリーンが解除されます。代わりの操作として、テストプレイは F5 キーまたは Back to Editor で終了でき、ダイアログは × ボタンで閉じられ、ストロークは Ctrl+Z キーで元に戻せます。ヘルプウィンドウ(F1)にもこのことが記載されています。
  • キーボードレイアウト:文字キーのショートカット(Ctrl+Z、G、P …)は、ドイツ語(QWERTZ)やフランス語(AZERTY)のキーボードでも、キーに印字された文字に従います。ブラシのキーは入力された文字に従うため、[ が AltGr+8 で入力されるレイアウトでも機能します。これはデスクトッププラットフォームでも同じです。
  • ゲームプレイデモ:ほとんどのブラウザーでは、Ctrl+1~Ctrl+9(コントロールグループ)でブラウザーのタブが切り替わります。Web ゲームでは別のキーを選択してください。
制限

マップ、そのテクスチャ、アンドゥ履歴は、最大 2 GB(デフォルトの Maximum Memory Size)の 1 つのメモリヒープを共有し、このヒープが縮小することはありません。そのため、ブラウザーではエディターの上限が低く設定されています。

上限ブラウザーほかのプラットフォーム
New Map と Resize での最大マップサイズ1 辺あたりのセル数は 2048 ÷ スプラット解像度です。デフォルトのスプラット解像度 4 では 512、8 では 256 になります。それより大きいプリセットは表示されず、ダイアログには In the browser, maps can be up to N x N cells. と表示されます。ファイルから開いたマップがこれより大きい場合、Resize はそのサイズを維持しますが、それ以上大きくすることはできません。1024
アンドゥ用メモリ最大 192 MB。Undo Memory MB がこれより低い値の場合は、その値が維持されます。Undo Memory MB(デフォルトは 512)
インポートするマップファイル128 MBインポートコマンドなし
その他の違い
  • UI は CSS ピクセル換算で 85% より小さくならないため、高解像度の画面や小さなウィンドウでも読みやすさが保たれます。
  • エディターカメラとサンプルの RTS カメラのエッジパン(画面端スクロール)は、マウスがページまたはゲームの iframe の外に出ると停止します。

レンダリングとパフォーマンス

  • 草:WebGL 2.0 には構造化バッファがありません。そのため、ディテールレンダラーは各パッチのインスタンスを浮動小数点テクスチャに格納します(1 インスタンスあたり 2 テクセル)。この方式はランタイムで自動的に選択され、草の見た目は変わりません。
  • 地形:フォワードパスでは、WebGL 2.0 が 1 回の描画で許可する 16 個のテクスチャのうち 10 個を使用します。Web ビルドの URP アセットは軽量に保ってください(モバイル向けの注意点を参照)。
  • シングルスレッド:ワーカースレッドがないため、マップ全体に対する操作はデスクトップよりも明らかに時間がかかり、その間ページは反応しません。対象となるのは、Generate、New Map、Resize、大きなマップを開く操作、Auto Paint の Apply to Whole Map です。1 辺 128~256 セルのマップであれば快適に扱えます。
  • テクスチャ配列:Web ビルドの形式でビルドした配列を使用してください(推奨ビルド設定を参照)。ビルドしたゲームはビルド済み配列の CPU 側のコピーを解放します。ページで使えるメモリは限られているため、これは重要です。
  • レンダリング解像度:テンプレートはピクセル比を 1.5 に制限しています。フレームレートを上げるには、この値を下げます。

コードストリッピングとカスタムツール

編集ツールはリフレクション(RTSToolRegistry)によって検出・作成されるため、ツールを直接参照するコードはありません。サイズを抑えたい Web ビルドや IL2CPP ビルドでよく行われるように、Managed Stripping Level を Minimal より高く設定すると、Unity はツールが使われていることを認識できず、削除してしまいます。このパッケージの Runtime/link.xml は、RTSTerrainStudio 名前空間のクラスのうち名前が Tool で終わるものをすべて保持するため、組み込みのツールは常に残ります。

独自のツールクラスには [UnityEngine.Scripting.Preserve] 属性を付けるか(クラスとそのパラメーターなしのコンストラクターが保持されます)、プロジェクトの link.xml ファイルに記述してください。ツールが 1 つも見つからない場合、ビルドしたゲームは No editing tools were found in this build. というログを出力します。

using RTSTerrainStudio;
using UnityEngine.Scripting;

// [Preserve] keeps the tool in builds with a high Managed Stripping Level.
[Preserve]
public sealed class CraterTool : RTSTool
{
    // ...
}

Web ビルド用のスクリプティング API

メンバー説明
RTSWebFileSystem.IsSupportedWebGL プレイヤーでは true。ほかのプラットフォームや Unity エディターでは、このクラスは何も行いません。
RTSWebFileSystem.Flush()ページのファイルシステムをブラウザーのストレージにコピーし、System.IO で書き込んだり削除したりしたファイルがリロード後も保持されるようにします。1 フレーム内のすべての呼び出しは、1 回の書き込みにまとめられます。RTSMapSerializer.SaveToFile は自動でこれを呼び出します。
RTSWebFileSystem.DownloadFile(fileName, data, mimeType)バイト列をファイルとしてブラウザーでダウンロードさせます。ほかのプラットフォームの場合や、ブラウザーがダウンロードを拒否した場合は false を返します。
RTSMapSerializer.StorageRootセッションをまたいで保持するファイル用のフォルダー。Application.persistentDataPath です。Web ビルドでは、ページのアドレスに依存しない、ブラウザーのストレージ内のフォルダーになります。
RTSMapSerializer.DefaultMapsFolderStorageRoot 内の Maps。
RTSRuntimeEditor.RunsInBrowserWeb ビルドでは true。この場合、エディターはブラウザー用の上限を使用し、New Map に Alt+N キーを使用します。
RTSRuntimeEditor.StoresMapsInBrowserマップがブラウザーのストレージに保存される場合(WebGL プレイヤー)に true。
RTSRuntimeEditor.HasReliableFunctionKeysWeb ブラウザーとモバイルプラットフォームでは false。これらの環境では、閉じるボタンを使うと再表示用のボタンが表示されます。
editor.DownloadMap(), editor.ImportMap()File メニューのコマンドです。ブラウザーはクリックまたはキー入力に応じてしかファイル選択ダイアログを表示しないため、ImportMap はクリックハンドラーから呼び出してください。
RTSInput.SetBrowserShortcutGuard(bool)F1~F10 と Ctrl+S/O/D/A に対するブラウザー独自の動作を抑制し、フルスクリーンで短く押した Esc キーがゲームに届くようにします(Chrome、Edge)。true での呼び出しには、それぞれ対応する false での呼び出しが必要です。ゲーム内エディターと RTSDemoHUD は、有効な間これをオンにします。ほかのプラットフォームでは何も行いません。
RTSInput.GetKeyDownByLabel(RTSKey)GetKeyDown と同様ですが、プレイヤーのキーボードレイアウトでこの文字が表示されているキーを対象とします。Ctrl+Z などの文字キーのショートカットに使用します。
RTSInput.GetCharacterDown(char)キーボードレイアウトに関係なく、このフレームでその文字が入力された場合に true。[ ] - + などの記号キーに使用します。フォーカスされたテキストフィールドに入力された文字もここに届くため、先に UI のフォーカスを確認してください。
RTSInput.PointerInsideScreenポインターがゲームウィンドウ内にある間は true。Web ビルドでは、さらにマウスがページ上にある必要があります。
using System.IO;
using RTSTerrainStudio;

public static class MyGameFiles
{
    // Kept between sessions on every platform; in Web builds in the storage of the browser.
    static string SettingsPath => Path.Combine(RTSMapSerializer.StorageRoot, "settings.json");

    public static void SaveSettings(string json)
    {
        Directory.CreateDirectory(RTSMapSerializer.StorageRoot);
        File.WriteAllText(SettingsPath, json);

        // Web builds: write the change to the browser storage, so it survives a reload.
        // Does nothing on other platforms.
        RTSWebFileSystem.Flush();
    }

    /// <summary>Lets the player keep a copy of the current map on the computer (Web builds only).</summary>
    public static bool DownloadMap(RTSTerrain terrain)
    {
        if (!RTSWebFileSystem.IsSupported || terrain.Map == null) return false;
        byte[] bytes = RTSMapSerializer.Serialize(terrain.Map);
        return RTSWebFileSystem.DownloadFile("My Map" + RTSMapSerializer.FileExtension, bytes);
    }
}

基本概念

この章では、RTS Terrain Studio におけるマップの考え方を説明します。具体的には、何がどの単位で保存されるのか、そのデータから目に見える地表がどのように構築されるのか、どのアセット(タイルセット、ライブラリ、マップファイル)がマップに関わるのかを取り上げます。最初のマップを作る前に読む必要はありませんが、ある動作がなぜそうなるのかを知りたいときや、マップデータを扱うコードを書き始めるときには、この章に戻ってきてください。

要点は次のとおりです。

  • マップは正方形のセルのグリッドです。高さ、崖レベル、水はセルの角(頂点)に格納されます。
  • 崖レベルは 0~15 の整数です。隣り合う角のレベルが異なる場所には、垂直な崖の壁面が現れます。ただし、セルの 4 つの角すべてにスロープのフラグが付いている場合は除きます。
  • 地面テクスチャ、草の密度、通行オーバーライドは、セルごとに複数のテクセルを持つラスターレイヤーです。
  • 木、装飾オブジェクト、リージョン、開始地点、マーカーは、マップローカル座標で表されたオブジェクトのリストです。
  • 外観はタイルセット(地面、崖、水のテクスチャ)から、オブジェクトは植生ライブラリと装飾ライブラリから提供されます。マップにはインデックスと ID しか保存されないため、マップに手を加えずに外観を差し替えられます。
  • すべてのデータは 1 つのコンパクトなバイナリ形式の .rtsmap ファイルに保存され、Unity エディターとゲーム内エディターの両方で使用されます。

マップモデル

マップのデータはすべて、MapData クラス(名前空間 RTSTerrainStudio)の 1 つのオブジェクトに格納されます。RTSTerrain コンポーネントは 1 つの MapData(terrain.Map)を所有し、それをチャンクメッシュとしてレンダリングします。また、そこから派生する通行マップを常に最新の状態に保ち、「この地点の地面の高さは?」といったクエリに応答します。

セルと頂点

マップは Width × Height 個のセルから成るグリッドです。各セルには 4 つの角の頂点があり、隣り合うセルは角の頂点を共有します。そのため、4 × 2 セルのマップには 5 × 3 個の頂点があります:

 z
 2   v10 ---- v11 ---- v12 ---- v13 ---- v14
      |  cell  |  cell  |  cell  |  cell  |
      | (0,1)  | (1,1)  | (2,1)  | (3,1)  |
 1   v5 ----- v6 ----- v7 ----- v8 ----- v9
      |  cell  |  cell  |  cell  |  cell  |
      | (0,0)  | (1,0)  | (2,0)  | (3,0)  |
 0   v0 ----- v1 ----- v2 ----- v3 ----- v4
     x=0      1        2        3        4

 vertex index = z * VertexCountX + x      (VertexCountX = Width + 1)
  • 頂点レイヤー(微細高さ、崖レベル、フラグ、崖タイプ、水面の高さ)は (Width + 1) × (Height + 1) 個のエントリを持ち、1 行ずつ順に格納されます。インデックスの計算には map.VertexIndex(x, z) を使用してください。
  • セルサイズ(map.CellSize、デフォルト 2 m)は、1 セルのワールドサイズです。2 m のセルから成る 128 × 128 のマップは、256 × 256 m(map.WorldSize)の範囲をカバーします。
  • ある位置は floor(localXZ / CellSize) のセルに属します。map.LocalToCell(localXZ) はこの計算を行い、結果をマップの範囲内にクランプします。
  • レンダリングのために、セルはチャンク単位にまとめられます(1 辺あたり 16、32、または 64 セル。RTSTerrain コンポーネントの Chunk Size 設定で指定し、デフォルトは 32)。再構築されるのは、編集の影響を受けたチャンクだけです。チャンクサイズはマップデータには影響しません。

マップの寸法

寸法はマップの作成時に選択します(New Map Wizard、ゲーム内の New Map ダイアログ、またはコード内の MapCreationSettings)。有効範囲外の値は MapCreationSettings.Validate() によって補正されます。

設定デフォルト有効な値説明
width, height12816–1024、16 の倍数に丸められますX 方向と Z 方向のセル数。New Map Wizard には、Tiny(64)から Epic XL(512)までのプリセットが用意されています。
cellSize2≥ 0.11 セルのワールドサイズ(メートル単位)。
cliffLevelHeight2≥ 0.05崖レベル 1 段分のワールド空間での高さ(メートル単位)。
baseCliffLevel20–15すべての頂点の初期崖レベル。レベル 2 にしておくと、湖や川のために掘り下げる余地が残ります。
baseGroundLayer00–15最初にマップ全体を覆う地面レイヤー。
splatResolution41, 2, 4, 8セルの 1 辺あたりの地面テクスチャ(スプラット)のテクセル数。
detailResolution21, 2, 4セルの 1 辺あたりのディテール(草)密度のテクセル数。
pathingResolution21, 2, 4マップセルの 1 辺あたりの通行セル数。
name"New Map"任意のテキストマップ名(マップ情報に格納されます)。

2 のべき乗でない解像度は切り上げられます(3 は 4 になります)。セルサイズ、レベルの高さ、3 つのラスター解像度は、マップの作成後は変更できません。Resize 機能(RTSTerrain.ResizeMap)が変更するのはセル数だけです。内容は保持したままオフセット分だけずらし、新しい領域は指定した崖レベルと地面レイヤーで埋めます。

MapData.Resize(newWidth, newHeight, offsetCells, fillCliffLevel, fillGroundLayer) は新しいマップを返します(元のマップは変更されません)。新しい範囲の外にある木、装飾オブジェクト、開始地点は削除されます。マーカーとリージョンは位置がずらされますが、範囲外に出た場合でも削除されずに保持されます。コードから呼び出す場合、サイズは 16~1024(MapData.MinSize–MaxSize)にクランプされますが、16 の倍数には丸められません。RTSTerrain.ResizeMap はマップを置き換え(MapLoaded が発生します)、マップを未保存としてマークし、編集セッションのアンドゥ履歴を消去します。

マップの内容

すべてのデータには、MapLayers 列挙型に対応するフラグがあります。これらのフラグは、変更通知、アンドゥの記録、シリアライズに使用されます。

データ保存単位MapData メンバー型MapLayers フラグ
微細高さ頂点HeightsNativeArray<float>、メートル単位Heights
崖レベル頂点CliffLevelsNativeArray<byte>、0–15CliffLevels
頂点フラグ(スロープ、水)頂点VertexFlagsVertexFlags の NativeArray<byte>VertexFlags
崖タイプ頂点CliffTypesNativeArray<byte>、0–3CliffTypes
水面の高さ頂点WaterHeightsNativeArray<float>、マップローカルの YWater
地面レイヤーのウェイトスプラットテクセルSplatMaps[0..3]NativeArray<Color32> × 4Splat
草・植物の密度ディテールテクセルDetailLayersプロトタイプごとに 1 つの DetailLayer(テクセルあたり 1 バイト)Details
木インスタンスTrees, TreePrototypeIdsList<TreeInstanceData>Trees
装飾オブジェクト(ドゥーダッド)インスタンスDecor, DecorPrototypeIdsList<DecorInstanceData>Decor
手動の通行オーバーライド通行セルPathingOverridesPathingOverride の NativeArray<byte>PathingOverrides
リージョンリストRegionsList<MapRegion>Regions
開始地点リストStartLocationsList<StartLocation>StartLocations
マーカーリストMarkersList<MapMarker>Markers
マップ情報マップInfoMapInfoInfo
環境(ライティング)マップEnvironmentEnvironmentSettingsEnvironment

便利な組み合わせフラグが 2 つあります。MapLayers.Geometry(高さ、レベル、頂点フラグ、崖タイプ)と MapLayers.PathingInputs(通行マップに影響するすべてのデータ)です。MapLayers.All はすべてを対象にします。

通行マップ(terrain.Pathing、PathingMap クラス)は派生データです。上記のレイヤーから計算され、保存されることはありません。詳しくは通行マップを参照してください。

VertexFlags.User0 と VertexFlags.User1 は、独自のコード用に予約されています。このパッケージ自体がこれらを使用することはありません。また、これらはマップと一緒に保存されるため、カスタムファイルセクションを作らなくても特別な頂点(たとえば「クリープの出現エリア」)をマークできます。

MapData はネイティブメモリを保持します。自分で作成または読み込んだマップ(MapData.Create、RTSMapSerializer.LoadFromFile、RTSMapAsset.LoadMap、map.Clone())は、Dispose() で破棄する必要があります。ただし、terrain.SetMap(map) で地形に渡した場合は例外です。その場合は地形がマップを所有し、マップが置き換えられたときやコンポーネントが無効になったときに破棄します。

崖レベル、微細高さ、崖タイプ

クラシック RTS の地形には 2 種類の高さがあります。大きく不連続な段差(崖)と、小さく滑らかな起伏(丘)です。RTS Terrain Studio は、この 2 つを頂点ごとに別々に格納します。

崖レベル

  • 各頂点には 0~15 の崖レベルがあります(MapData.MaxCliffLevel = 15)。
  • 1 レベルの高さは CliffLevelHeight メートルです(デフォルト 2 m)。
  • 新しいマップはレベル 2 から始まります。そのため、地面より下には湖、川、峡谷用に 2 レベル、上には台地用に 13 レベルの余地があります。
  • レベルは Cliffs ツール(Raise / Lower Level、Set Level、Ramp、Cliff Type、Clean Cliffs)で編集します。
  • 戦場の霧(Fog of War)は、視線判定に崖レベルを使用します。ユニットは自分と同じレベルとそれより低い場所を見ることができ、それより高い地形は視界を遮ります。

微細高さ

各頂点には、レベルに加えて、メートル単位の微細高さ(Heights)があり、正の値も負の値も取れます。Height ツール(Raise / Lower、Smooth、Flatten、Set Height、Noise、Terrace、Erode)が変更するのは微細高さだけです。そのため、台地の上に丘を作っても、それが崖に変わることはありません。頂点の地面の高さは次のとおりです:

vertex height = CliffLevels[i] × CliffLevelHeight + Heights[i]      (map-local Y, metres)

例:レベルの高さが 2 m のとき、レベル 3 で微細高さが 0.4 m の頂点は、マップの原点から 6.4 m の高さにあります。map.GetVertexHeight(x, z) と map.GetVertexPosition(x, z) を使えば、この値を自動で計算できます。

崖タイプ

タイルセットには、最大 4 つの崖レイヤー(たとえば「Rock」と「Grassy Cliff」)を用意できます。各頂点には、どの崖レイヤーを使うかが格納されます(CliffTypes、0~3、Cliff Type ツールでペイントします)。崖の壁面には、そのセルの最も高い角の崖タイプが使用されます。崖タイプのインデックスに対応するレイヤーがタイルセットにない場合、壁面には代わりに崖レイヤー 0 が使用されます。

地表の構築方法(基準面)

MapGeometryView 構造体は、地形の唯一の正しい地表を定義します。チャンクメッシュ、ゲームプレイ用のクエリ(GetHeight、Raycast、…)、通行マップ、戦場の霧はすべて同じルールに従うため、見た目どおりの場所をユニットが歩きます。各セルは 4 つの角によって分類されます:

セルの種類条件地表
平滑セル4 つの角すべてが同じ崖レベルです。4 つの角の高さによるバイリニアパッチ。
スロープセル4 つの角すべてに VertexFlags.Ramp フラグが付いています(レベルは異なっていてもかまいません)。バイリニアパッチ:レベルが滑らかにブレンドされて傾斜になります。詳しくはスロープを参照してください。
崖セル角のレベルが異なり、スロープセルではありません。丸みのある輪郭線に沿った垂直な壁面で区切られた、平らなテラス。

丸みのある輪郭線。崖セル内では、各レベルのしきい値 t について、「角のレベル ≥ t」という指標(1 または 0)がセル全体でバイリニア補間されます。ある点は、補間された指標が 0.5 以上になる最も高いレベルに属します(MapGeometryView.LevelAt)。これはマーチングスクエア方式のルールです。1 つの角だけが高い場合は対角線ではなく 4 分の 1 円になり、長い崖のラインはまっすぐに保たれます。壁面自体は輪郭線の位置で垂直に立ちます。微細高さは常にバイリニア補間されて上乗せされます。これは崖セル内でも同じです。

描画される壁面の見た目は、RTSTerrain コンポーネント(Settings)で調整します:

設定デフォルト説明
Surface Subdivisions2平滑な地面での、セルの 1 辺あたりのクアッド数(1 = クラシック RTS の見た目、2–4 = より滑らかな丘)。
Cliff Subdivisions4崖セル内の丸みのある輪郭線をトレースするのに使うサブグリッドの解像度。
Cliff Noise0.18岩を自然に見せるための、壁面の頂点のランダムな水平方向のずれ(セルに対する割合)。
Cliff Noise Scale1.6崖のノイズのスケール(ワールド単位)。
Cliff Slant0.12壁面の上端を後方へ引き込む量(セルに対する割合)。これにより壁面がわずかに傾斜します。
Cliff Wall Segments3壁面ジオメトリの、崖レベル 1 段あたりの垂直方向のセグメント数。

ビューは map.GetGeometryView() で取得します。ネイティブ配列から成る構造体なので、Burst ジョブに渡すこともできます。すべてのメンバーはマップローカルの位置を受け取ります:

メンバー説明
SampleHeight(localXZ)地面の高さ(マップローカルの Y)。
SampleHeightInCell(cell, uv), SampleLevelHeightInCell(cell, uv)既知のセル内(uv 0–1)での同じ値。それぞれ微細高さを含む値と含まない値を返します。
SampleNormal(localXZ), SampleSlope(localXZ)地表の法線、および傾斜角(度単位)。
SampleCliffLevel(localXZ)ある位置の崖レベル。
SampleWater(localXZ, out waterHeight), SampleWaterDepth(localXZ)水があるかどうかと、その水面の高さ。水深(水がない場合は 0)。
IsCliffCell(cell), IsRampCell(cell)セルを分類します(上記の表を参照)。
LocateCell(localXZ, out cell, out uv)位置が属するセルと、そのセル内での 0–1 の位置。
Raycast(origin, direction, maxDistance, out hitPoint, out hitDistance)基準面に対する解析的なレイキャスト。コライダーは不要です。
static LevelAt(l00, l10, l01, l11, uv), static Bilerp(v00, v10, v01, v11, uv)崖セル内のレベル判定ルールと、バイリニア補間。
width, height, cellSize, cliffLevelHeight, WorldSizeセル単位のマップサイズ、セルサイズ、レベルの高さ、ワールドサイズ。

Cliff Noise と Cliff Slant は、描画される壁面の頂点を 1 セル未満の距離だけ動かすにすぎません。クエリは正確な輪郭線を使用するため、崖の縁のすぐそばでは、目に見える岩と基準面がわずかに異なることがあります。通行マップはこの差を Cliff Margin 設定で吸収し、すべての壁面に沿って歩行不可の細い帯を確保します。

スロープ

スロープを使うと、ユニットはある崖レベルから次の崖レベルへ歩いて移動できます。ルールは単純です:

セルの 4 つの角すべてにスロープフラグが付いている場合だけ、そのセルはスロープになります。1 つでもフラグのない角があると、そのセルは垂直な壁面を持つ崖セルのままです。

  • フラグは各頂点の VertexFlags.Ramp です。Ramp ツール(ID cliff.ramp)でペイントします。Shift キーを押したままペイントすると、フラグを削除できます。
  • スロープセルは、角の高さ(レベル + 微細高さ)の間を結ぶ滑らかなバイリニアの傾斜です。
  • スロープはちょうど 1 レベルをまたぐ場合に最もうまく機能します。それより急なスロープも作成できますが、2 レベル以上にまたがるスロープセルがあると、ステータスバーに警告が表示されます。
  • 通行判定:有効な 1 レベルのスロープでは、歩行可能かどうかは微細高さによる傾斜だけで評価されます(スロープが橋渡しするレベル間の勾配によって「急すぎる」と判定されることはありません)。スロープは常に建設不可です。
  • スロープ上で GetCliffLevel を呼び出すと、補間されたレベルを丸めた値が返されます。下半分では低いほうのレベル、上半分では高いほうのレベルになります。
  1. Raise / Lower Level ツールで台地を 1 レベル上げます。
  2. Ramp ツールを選択し、スロープを作りたい場所の崖の縁をまたぐようにペイントします。間にあるセルのすべての角にフラグが付くように、ブラシで少なくとも壁面の両側の頂点を覆ってください。
  3. 壁面が消えて傾斜が現れます。縁に沿ってさらに頂点をペイントすると、スロープの幅を広げられます。

地面レイヤーとスプラットマップ

地面テクスチャはスプラットマップにペイントされます。スプラットマップは、SplatWidth × SplatHeight テクセルの 4 枚の RGBA テクスチャ(MapData.SplatMapCount = 4)です。16 個のチャンネルはそれぞれ、タイルセットの 1 つの地面レイヤーのウェイトを保持します:

スプラットマップRGBA
SplatMaps[0]レイヤー 0レイヤー 1レイヤー 2レイヤー 3
SplatMaps[1]レイヤー 4レイヤー 5レイヤー 6レイヤー 7
SplatMaps[2]レイヤー 8レイヤー 9レイヤー 10レイヤー 11
SplatMaps[3]レイヤー 12レイヤー 13レイヤー 14レイヤー 15
  • レイヤー i は、マップ i / 4 のチャンネル i % 4 に格納されます。タイルセットの地面レイヤーがちょうど 16 個なのはこのためです(4 マップ × 4 チャンネル)。
  • 16 チャンネル全体で、1 テクセルのウェイトの合計は常に 255 になります。すべての Paint ツールはこの不変条件を維持します。スプラットデータを書き込むコードも、この条件を守る必要があります。
  • スプラット解像度は、セルの 1 辺あたりのテクセル数です(デフォルト 4)。2 m のセルでは、0.5 m ごとに 1 テクセルになります。
  • テクセル (x, z) の中心は、マップローカルの XZ で ((x + 0.5), (z + 0.5)) × CellSize / SplatResolution の位置にあります。ディテールや通行判定のテクセルにも、それぞれの解像度で同じ式が適用されます。
  • ヘルパー:map.SplatIndex(x, z)、map.GetSplatWeight(texelIndex, layer)、map.GetDominantLayer(texelIndex)、MapData.GetChannel / SetChannel、map.FillSplat(layer)。

マップは地面レイヤーを名前ではなくインデックス(0~15)で格納します。タイルセット内の地面レイヤーを並べ替えたり削除したりすると、ペイント済みのマップの表示が変わります。すでにそのタイルセットを使用しているマップがある場合は、新しいレイヤーを末尾に追加してください。

using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;

public static class SplatExample
{
    /// <summary>Paints one map cell 100 % with a ground layer (like the Tile Paint tool, without undo).</summary>
    public static void PaintCell(RTSTerrain terrain, int2 cell, int layer)
    {
        MapData map = terrain.Map;
        if (map == null || !map.IsValidCell(cell.x, cell.y)) return;
        layer = math.clamp(layer, 0, MapData.MaxGroundLayers - 1);

        int res = map.SplatResolution;
        for (int tz = cell.y * res; tz < (cell.y + 1) * res; tz++)
        {
            for (int tx = cell.x * res; tx < (cell.x + 1) * res; tx++)
            {
                int texel = map.SplatIndex(tx, tz);
                for (int m = 0; m < MapData.SplatMapCount; m++)
                {
                    // All channels 0, except the one of our layer: the sum stays exactly 255.
                    var c = new Color32(0, 0, 0, 0);
                    if (layer / 4 == m) MapData.SetChannel(ref c, layer % 4, 255);
                    map.SplatMaps[m][texel] = c;
                }
            }
        }

        // Tell the terrain which data changed and where (in cells). Textures and pathing update in LateUpdate.
        terrain.NotifyChanged(MapLayers.Splat, new RectInt(cell.x, cell.y, 1, 1));
    }
}

水

  • 頂点に VertexFlags.Water フラグがある場合、その頂点は水中にあります。その水面の高さ(マップローカルの Y、メートル単位)は WaterHeights に格納されます。この値は、フラグが設定されている場所でのみ意味を持ちます。
  • ある点は、その点が属するセルの 4 つの角のフラグからバイリニアに求めた被覆率が 0.5 以上のとき、水に覆われていると見なされます。水に覆われたセル内にある水のない角には、そのセルで最も高い水面の高さが使用されるため、岸辺でも水面が水平に保たれます。
  • 水深 = 水面 − 地面の高さ。通行判定は水深によって決まります。タイルセットの Shallow Depth Limit(デフォルト 0.9 m)より深い水は深水域(歩行不可)、それより浅い水は浅水域(歩行可能、デフォルトでは建設不可)になります。
  • Water ツールは Paint Water、Erase Water、Flood Fill です。水の編集では MapLayers.Water だけが通知されるため、水をペイントしている間に地形のチャンクが再構築されることはありません。
  • クエリ:terrain.IsWater(world, out depth) と terrain.TryGetWaterHeight(world, out waterWorldY)。

水の見た目(色、波、泡)は、タイルセットの水プロファイルで決まります。詳しくは水プロファイルを参照してください。

ディテール密度(草と小さな植物)

  • ディテールは個別のオブジェクトではありません。マップで使用される各ディテールプロトタイプは、map.DetailLayers 内に 1 つの DetailLayer を持ちます。これはディテールテクセルごとに 1 バイトのデータです(DetailWidth × DetailHeight、デフォルトではセルの 1 辺あたり 2 テクセル)。
  • 密度 0 は空を意味し、255 はテクセルあたり Max Density 個のインスタンス(ディテールプロトタイプのフィールド、デフォルト 6)を意味します。各テクセルは density / 255 × maxDensity 個のインスタンスを生成します。レンダラーはそれらを決定論的に散布するため、同じマップは常に同じ見た目になります。
  • レイヤーは prototypeId によって植生ライブラリと照合されます。map.GetOrAddDetailLayer(id) は必要に応じてレイヤーを作成します。map.FindDetailLayer(id) は、そのプロトタイプが使用されていない場合に null を返します。
  • Paint Details ツールでペイントし、Erase Details ツールで消去します。

木と装飾インスタンス

木(TreeInstanceData)と装飾オブジェクト(DecorInstanceData)は、単純なリストとして保存されます。各インスタンスは、プロトタイプ名の代わりに、マップに保存されたライブラリ ID のリストを指す小さなパレットインデックスを保存します:

map.TreePrototypeIds = [ "a3f0…", "77c1…" ]        <- ids of TreePrototype entries in the vegetation library
map.Trees[n].prototype = 1                          <- this tree uses "77c1…"
  • ライブラリ ID のパレットインデックスを取得するには、map.GetOrAddTreePrototype(id) / map.GetOrAddDecorPrototype(id) を使用します(ID がリストにない場合は追加されます)。
  • マップはプロトタイプを ID で参照するため、ライブラリの名前変更、並べ替え、拡張を安全に行えます。ライブラリに存在しないプロトタイプは、植生レンダラーではスキップされ、装飾マネージャーではマゼンタの立方体のプレースホルダーとして表示されます。同じ ID のプロトタイプを追加すると元に戻ります。通行判定:プロトタイプが見つからない木は描画されませんが、半径 PathingMapBuilder.DefaultTreeFootprintRadius(0.8 m)× スケールの円形の範囲は引き続きブロックします。装飾オブジェクトのプレースホルダーは、プロトタイプが戻るまで通行フットプリントを書き込みません。
  • 各インスタンスは、マップ内で一意かつ不変の id を持ちます。新しい ID は map.AllocateInstanceId() で取得します。
  • 位置はマップローカル座標です。回転はどこでも同じ規則に従います。単位は度で、上から見て時計回り、0 = +Z、90 = +X です(Unity の Y 軸回転と同じ)。

position.y の規則

position.y は地面からの追加オフセットで、通常は 0 です。絶対的な高さではありません。レンダラーは更新のたびに地面の高さをサンプリングするため、下の地形を編集しても、木や装飾オブジェクトは地面に接したままです。

オブジェクト描画位置(マップローカル Y)
木地面の高さ + position.y
装飾オブジェクト地面の高さ + position.y + プロトタイプの Y Offset
DecorFlags.KeepHeight 付きの装飾オブジェクトposition.y をマップローカルの絶対高さとして使用(地形を編集しても追従しません)

プロトタイプの yOffset を position.y に決してコピーしないでください。この値は描画時に加算されます。DecorMath.ComputeLocalPosition と DecorMath.ToStoredHeight は、保存されている位置と描画される位置を相互に変換します。

フィールドTreeInstanceDataDecorInstanceData
iduint、マップ内で一意uint、マップ内で一意
prototypeushort、TreePrototypeIds へのインデックスushort、DecorPrototypeIds へのインデックス
variant–byte、0 = メインのプレハブ、1… = variants の各エントリ
flags–DecorFlags:IgnorePathing、Locked(ツールで選択・消去できません)、KeepHeight
positionfloat3、マップローカル、y = オフセットfloat3、マップローカル、y = オフセット(KeepHeight の場合は絶対高さ)
回転yaw(度単位)rotation、マップローカルの quaternion
スケールscale、均一スケールの floatscale、float3
tintマテリアルの色に乗算される Color32マテリアルの色に乗算される Color32
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;

public static class InstanceExample
{
    /// <summary>Adds a tree of the first tree prototype of the terrain's vegetation library at a world position.</summary>
    public static uint AddTree(RTSTerrain terrain, Vector3 worldPosition, float yawDegrees)
    {
        MapData map = terrain.Map;
        RTSVegetationLibrary library = terrain.VegetationLibrary;
        if (map == null || library == null || library.trees.Count == 0) return 0;

        Vector3 local = terrain.WorldToLocal(worldPosition);
        var tree = new TreeInstanceData
        {
            id = map.AllocateInstanceId(),
            prototype = map.GetOrAddTreePrototype(library.trees[0].id), // palette index of the library id
            position = new float3(local.x, 0f, local.z),                 // y = offset above the ground, normally 0
            yaw = yawDegrees,                                             // clockwise from above, 0 = +Z
            scale = 1f,
            tint = new Color32(255, 255, 255, 255)
        };
        map.Trees.Add(tree);

        // Notify the cell that changed: vegetation, pathing (tree footprint) and fog blockers update.
        int2 cell = map.LocalToCell(new float2(local.x, local.z));
        terrain.NotifyChanged(MapLayers.Trees, new RectInt(cell.x, cell.y, 1, 1));
        return tree.id;
    }

    /// <summary>Adds a decor object (by library id) standing upright on the ground.</summary>
    public static uint AddDecor(RTSTerrain terrain, string decorPrototypeId, Vector3 worldPosition, float yawDegrees)
    {
        MapData map = terrain.Map;
        if (map == null) return 0;

        Vector3 local = terrain.WorldToLocal(worldPosition);
        var decor = new DecorInstanceData
        {
            id = map.AllocateInstanceId(),
            prototype = map.GetOrAddDecorPrototype(decorPrototypeId),
            variant = 0,                                  // main prefab
            flags = DecorFlags.None,
            position = new float3(local.x, 0f, local.z),  // the prototype's yOffset is added at render time
            rotation = DecorMath.Yaw(yawDegrees),
            scale = new float3(1f, 1f, 1f),
            tint = new Color32(255, 255, 255, 255)
        };
        map.Decor.Add(decor);

        int2 cell = map.LocalToCell(new float2(local.x, local.z));
        terrain.NotifyChanged(MapLayers.Decor, new RectInt(cell.x, cell.y, 1, 1));
        return decor.id;
    }
}

MapData を直接変更するコードは、アンドゥ履歴に記録されません。エディタースクリプトから元に戻せる編集を行うには、RTSEditSession.RunOperation(name, layers, operation) を使って編集セッション経由で実行します。このメソッドは、指定したレイヤーを 1 アンドゥステップとして記録します。

通行マップ

通行マップは、「ここをユニットが歩けるか、ここに建物を建てられるか」という問いに答えます。通行マップは通行セルのグリッドで、各セルが 1 バイトの PathingFlags を持ちます。通行セルの数はマップセルの 1 辺あたり PathingResolution 個(デフォルトは 2)なので、セルサイズ 2 m のマップでは通行セルの大きさは 1 m になります。通行マップは自動的に生成され、入力(ジオメトリ、水、地面レイヤー、木、装飾オブジェクト、オーバーライド)が変わるたびに逐次再構築されます。通行マップ自体は保存されず、保存されるのは手動のオーバーライドだけです。

PathingFlags意味
Walkable地上ユニットがここを歩けます。
Buildableここに建物を配置できます。
Flyable空中ユニットがここを飛べます。Unflyable 効果(ペイントした通行オーバーライド、タイルセットの地面レイヤールール、または装飾プロトタイプの Footprint Effect)によって解除されない限り、すべての場所に設定されます。
ShallowWater浅水域(デフォルトでは歩行可能、建設不可)。
DeepWater深水域(海上ユニットのみ通行可能)。
Cliffセルが崖の壁面に接しています。
Blocked木または装飾オブジェクトのフットプリントによってブロックされています。
Steep地上ユニットには傾斜が急すぎます。

フラグの算出方法

  1. 地形:崖の壁面(と Cliff Margin の範囲)には Cliff が設定されます。Max Walkable Slope(デフォルト 42°)を超える傾斜には Steep が設定されます。水には水深に応じて ShallowWater または DeepWater が設定されます。Flyable はすべての場所に設定されます。Buildable は、歩行可能で、傾斜が Max Buildable Slope(デフォルト 18°)以下で、水がない場所(Shallow Water Buildable がオンの場合は浅水域も含む)に設定されます。続いて、優勢な地面レイヤーの Pathing Effect が適用されます(たとえば、ごつごつした岩には「Unbuildable」)。
  2. フットプリント:木は円形の範囲(Footprint Radius × 木のスケール × Tree Footprint Scale)をブロックします。装飾オブジェクトは、インスタンスに DecorFlags.IgnorePathing が付いていない限り、円または回転したボックスの範囲内に Footprint Effect を適用します。
  3. Paint Pathing / Clear Pathing ツールでペイントした手動オーバーライドが最後に適用されます。

これらのルール設定は RTSTerrain コンポーネントにあります(RTSPathingSettings、プロパティ terrain.PathingSettings)。

PathingOverride効果
UnwalkableWalkable(と Buildable)を解除し、Blocked を設定します。
UnbuildableBuildable を解除します。
UnflyableFlyable を解除します。
ForceWalkable算出ルールの結果にかかわらず Walkable を設定します。
ForceBuildable算出ルールの結果にかかわらず Buildable を設定します。

地面レイヤーの通行効果、装飾オブジェクトのフットプリント効果、ペイントしたオーバーライドには、すべて同じ PathingOverride フラグが使われます。結果は terrain.GetPathing(world)、terrain.IsWalkable(world)、terrain.IsBuildable(world) でクエリするか、terrain.Pathing.Flags を直接読み取ります。

リージョン、開始地点、マーカー

これらはゲームロジック用の軽量なオブジェクトです。ゲーム内では描画されず、エディターではギズモとして表示されます。

型フィールド主な用途
MapRegion(Regions ツール)name、rect(メートル単位のマップローカル XZ 矩形:rect.x = X、rect.y = Z)、color、properties(キーと値のリスト)トリガー、スポーンゾーン、中立モンスターのキャンプ。Contains(localXZ)、GetProperty(key, fallback)。
StartLocation(Start Locations ツール)player(0 始まり)、position、yawプレイヤーのスポーン地点。サンプルのユニットスポナーが使用します。
MapMarker(Markers ツール)name、tag(「camera」や「goldmine」などの任意のテキスト)、position、rotation、propertiesシネマティックカメラ、注目地点、スクリプトで制御するスポーン地点。

ツールは開始地点とマーカーに position.y = 0 を保存し、木と同様に地面からのオフセットとして扱います。

using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;

public static class RegionExample
{
    /// <summary>Returns the "gold" property of the first region that contains the world position, or 0.</summary>
    public static int GetGoldAt(RTSTerrain terrain, Vector3 worldPosition)
    {
        if (terrain.Map == null) return 0;
        Vector3 local = terrain.WorldToLocal(worldPosition);
        foreach (MapRegion region in terrain.Map.Regions)
        {
            // Regions are rectangles in map-local XZ.
            if (region.Contains(new float2(local.x, local.z)) &&
                int.TryParse(region.GetProperty("gold", "0"), out int gold))
                return gold;
        }
        return 0;
    }
}

マップ情報と環境

MapInfo のフィールドデフォルト説明
name"New Map"表示名(非圧縮のファイルヘッダーにも保存されます)。
author""作成者名。
description""マップブラウザーに表示される自由記述のテキスト。
maxPlayers2推奨プレイヤー数。
tilesetId""マップの作成に使用したタイルセットの ID(RTSTileset.tilesetId)。地形にタイルセットが割り当てられている場合は、自動的に設定されます。割り当てられたタイルセットが一致しない場合は、RTSTerrain のインスペクターに警告が表示されます。
createdUtcTicks、modifiedUtcTicks自動設定作成日時と最終保存日時(UTC のティック数)。更新日時は、マップがシリアライズされるたびに RTSMapSerializer.Write によって設定されます。これには SaveToFile、エディターでの Save、Serialize(map)、RTSMapAsset.CreateFromMap が含まれます。

EnvironmentSettings はマップのライティングを保存します。この設定は RTSEnvironment コンポーネント(GameObject ▸ RTS Terrain Studio ▸ Environment)によってシーンに適用されます。このコンポーネントがない場合、値は保存されますが使用されません。

EnvironmentSettings のフィールドデフォルト説明
sunColor暖かみのある白 (1, 0.956, 0.84)太陽光の色。
sunIntensity1.2太陽光の強度。
sunRotation(50, −30)太陽の回転(度単位、x = ピッチ、y = ヨー)。
ambientColor(0.45, 0.5, 0.58)環境光の色。
fogオフ距離フォグを有効にします。
fogColor(0.6, 0.68, 0.75)フォグの色。
fogDensity0.004フォグの密度。

変更通知

コードから MapData を変更した場合は、何がどこで変更されたかをセル単位で地形に通知してください:

  • terrain.NotifyChanged(MapLayers layers, RectInt cellRect) は、ただちに MapChanged を発生させ、再構築をキューに追加します。
  • terrain.NotifyChangedAll(layers) は、マップ全体を変更済みとしてマークします(ジェネレーターやインポートの後に使用します)。
  • 保留中の変更は LateUpdate(再生モード外ではエディターのティックごと)で処理されるか、terrain.ProcessPendingChanges() でただちに処理されます。その後 ChangesProcessed が発生し、この時点でメッシュ、テクスチャ、通行判定は最新の状態になっています。
  • 1 つの頂点は最大 4 つのセルに接しています。頂点の矩形は BrushUtility.VertexRectToCellRect(map, vertexRect) で、テクセルの矩形は BrushUtility.TexelRectToCellRect(map, texelRect, resolutionPerCell) でセルの矩形に変換します。
using RTSTerrainStudio;
using UnityEngine;

public static class CliffExample
{
    /// <summary>Raises a square plateau by one cliff level around a world position.</summary>
    public static void RaisePlateau(RTSTerrain terrain, Vector3 worldCenter, int radiusInVertices)
    {
        MapData map = terrain.Map;
        if (map == null) return;

        // Nearest vertex to the centre (vertices sit on multiples of the cell size).
        Vector3 local = terrain.WorldToLocal(worldCenter);
        int cx = Mathf.RoundToInt(local.x / map.CellSize);
        int cz = Mathf.RoundToInt(local.z / map.CellSize);

        int x0 = Mathf.Max(0, cx - radiusInVertices), x1 = Mathf.Min(map.Width, cx + radiusInVertices);
        int z0 = Mathf.Max(0, cz - radiusInVertices), z1 = Mathf.Min(map.Height, cz + radiusInVertices);
        for (int z = z0; z <= z1; z++)
        {
            for (int x = x0; x <= x1; x++)
            {
                int i = map.VertexIndex(x, z);
                // Levels are bytes from 0 to 15: always clamp.
                map.CliffLevels[i] = (byte)Mathf.Min(map.CliffLevels[i] + 1, MapData.MaxCliffLevel);
            }
        }

        // Convert the changed vertices to the cells around them and notify the terrain.
        var vertexRect = new RectInt(x0, z0, x1 - x0 + 1, z1 - z0 + 1);
        RectInt cells = BrushUtility.VertexRectToCellRect(map, vertexRect);
        terrain.NotifyChanged(MapLayers.CliffLevels, cells);
    }
}

座標と複数の地形

マップデータはすべてマップローカル空間に保存されます。RTSTerrain ゲームオブジェクトのトランスフォームが、この空間をワールド内に配置します。

マップローカル空間

  • 原点は頂点 (0, 0)、つまりセル (0, 0) の角です。X と Z はメートル単位で 0 から map.WorldSize までの範囲をとり、Y が上方向です。
  • 頂点 (x, z) はローカル座標 (x × CellSize, height, z × CellSize) にあります。セル (x, z) の中心は ((x + 0.5) × CellSize, …, (z + 0.5) × CellSize) です。
  • ローカル Y = 0 は、レベル 0 かつ微細高さ 0 の位置です。ベースレベル 2、レベルの高さ 2 m の新しいマップでは、地面はローカル Y = 4 m にあります。
  • チャンクのゲームオブジェクトは、地形の非表示の子オブジェクト RTS Chunks(RTSTerrain.ChunkRoot)の下に配置されます。このルートはローカルのオフセットや回転を持たず、地形のスケールを打ち消します。さらに各チャンクのローカルトランスフォームは恒等変換なので、チャンクメッシュのオブジェクト空間はマップローカル空間と一致します。これは地形用のシェーダーを書くときに重要になります。

ワールド空間とマップ空間の変換

メソッド(RTSTerrain)説明
WorldToLocal(Vector3) / LocalToWorld(Vector3)ワールド空間とマップローカル空間の間で位置を変換します。
WorldToLocalDirection / LocalToWorldDirection方向(レイ、法線)を変換します。
WorldToCell(Vector3)ワールド位置を含むセル(マップの範囲内にクランプされます)。
CellCenterToWorld(int2)地面上にあるセル中心のワールド位置。
VertexToWorld(int2)頂点のワールド位置。
Contains(Vector3)ワールド位置がマップの XZ 範囲内にある場合に true。
WorldBoundsマップ全体のおおよそのワールド境界(とりうる最高の崖レベルを含む)。
GetHeight、SnapToGround、GetNormal、GetCliffLevel、IsWater、TryGetWaterHeight、GetPathing、Raycastワールド座標の値を受け取ってワールド座標の値を返すクエリ(変換は内部で行われます)。
GetGeometry()マップローカル空間で高速にクエリを行うための MapGeometryView(Burst ジョブ内でも使用できます)。
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;

/// <summary>Add to any GameObject and use the context menu to see the map data under it.</summary>
public class TerrainProbe : MonoBehaviour
{
    [ContextMenu("Probe Terrain")]
    void Probe()
    {
        // Several terrains can exist: find the one below this object.
        RTSTerrain terrain = RTSTerrain.FindAt(transform.position);
        if (terrain == null || terrain.Map == null) { Debug.Log("Not above an RTS Terrain."); return; }
        MapData map = terrain.Map;

        // World -> map-local (origin at vertex 0,0; position and Y rotation only).
        Vector3 local = terrain.WorldToLocal(transform.position);
        int2 cell = terrain.WorldToCell(transform.position);

        // Nearest vertex.
        int vx = Mathf.Clamp(Mathf.RoundToInt(local.x / map.CellSize), 0, map.Width);
        int vz = Mathf.Clamp(Mathf.RoundToInt(local.z / map.CellSize), 0, map.Height);
        int v = map.VertexIndex(vx, vz);

        // Splat texel below the object (SplatResolution texels per cell side).
        int tx = Mathf.Clamp((int)(local.x / map.CellSize * map.SplatResolution), 0, map.SplatWidth - 1);
        int tz = Mathf.Clamp((int)(local.z / map.CellSize * map.SplatResolution), 0, map.SplatHeight - 1);
        int dominantLayer = map.GetDominantLayer(map.SplatIndex(tx, tz));

        Debug.Log($"Cell {cell}, vertex ({vx},{vz}): level {map.CliffLevels[v]}, fine height {map.Heights[v]:0.00} m, " +
                  $"vertex height {map.GetVertexHeight(vx, vz):0.00} m. Ground at world Y {terrain.GetHeight(transform.position):0.00}, " +
                  $"cliff level {terrain.GetCliffLevel(transform.position)}, dominant ground layer {dominantLayer}, " +
                  $"pathing {terrain.GetPathing(transform.position)}.");
    }
}

位置、回転、スケールのルール

トランスフォームの要素サポート備考
位置はいマップ全体を移動します。複数の地形を並べて配置したり、異なる高さに配置したりするのに使用します。
Y 軸回転はい原点(頂点 0, 0)を中心にマップを回転します。クエリ、ツール、ブラシカーソル、インスタンスの回転もこれに追従します。
X / Z 軸回転いいえ使用を想定しているのは Y 軸回転だけです。RTSTerrain のインスペクターに警告と Fix ボタンが表示されます。このボタンは Y を維持したまま X と Z をリセットします。
スケール無視チャンクは、地形やその親のスケールをすべて打ち消します。インスペクターに警告と Reset Scale ボタンが表示されます。マップを大きくするには、セル数を増やすか、セルサイズを大きくします。

複数の地形

1 つのシーンには、RTSTerrain コンポーネントをいくつでも配置できます。それぞれが独自のマップ、タイルセット、ライブラリ、チャンク、通行マップを持ちます。

  • RTSTerrain.All は有効なすべての地形を列挙します。RTSTerrain.Main は最初の地形を返します(マップが 1 つのゲームで便利です。一時的なプレビュー用の地形はスキップされます)。
  • RTSTerrain.FindAt(worldPosition) は、指定した位置を XZ 範囲内に含む地形を返します。
  • RTSTerrain.RaycastAll(ray, out hit) は、すべての地形に対してレイキャストを行い、最も近いヒットを返します(どの地形かは hit.terrain でわかります)。
  • RTSTerrain.TerrainEnabled / TerrainDisabled は、地形を追跡するシステム向けの静的イベントです。
  • 地形は互いに独立しています。マップは境界でつなぎ合わされず、経路探索(RTSPathfinder.FindPath(terrain, …))も一度に 1 つの地形に対してのみ動作します。2 つのマップを継ぎ目なくつなげるには、境界の頂点に同じ高さを設定します。
using RTSTerrainStudio;
using UnityEngine;

public static class TerrainLayout
{
    /// <summary>Places "east" directly next to "west" (along west's local +X), with the same rotation.</summary>
    public static void PlaceNextTo(RTSTerrain west, RTSTerrain east)
    {
        if (west.Map == null) return;
        float widthMetres = west.Map.WorldSize.x;   // map-local X extent in metres
        east.transform.SetPositionAndRotation(
            west.transform.position + west.transform.rotation * new Vector3(widthMetres, 0f, 0f),
            west.transform.rotation);
    }
}

マップアセットとマップファイル

マップは .rtsmap ファイルとして保存されます。Unity エディター(プロジェクト内のアセットとして)とゲーム内エディター(ディスク上のファイルとして)は同じバイナリ形式を使用するため、プレイヤーが作成したマップを Unity で開くことも、その逆も可能です。

プロジェクト内の .rtsmap ファイル

Unity が Assets フォルダー内で .rtsmap ファイルを検出すると、インポーター RTSMapImporter がそれを RTSMapAsset に変換します。このアセットには、圧縮されたファイルのバイト列、小さなヘッダー(名前、作成者、サイズ、プレイヤー数、タイルセット ID)、そしてプロジェクトウィンドウのサムネイルとしても使われるミニマッププレビューが格納されます。

マップアセットの作成

  • Tools ▸ RTS Terrain Studio ▸ New Map Wizard...:ガイド付きのセットアップ(基本情報、サイズ、外観、地形)です。ファイルを書き出し、そのファイル用の地形を作成するか、選択中の地形にファイルを割り当てます。
  • Assets ▸ Create ▸ RTS Terrain Studio ▸ Map (.rtsmap):選択中のフォルダーに、デフォルト設定で 128 × 128 の平坦なマップを作成します。
  • RTS Terrain Studio ウィンドウの Save As…:地形の現在のマップを新しいファイルに書き出します。
  • ゲーム内エディターで作成した .rtsmap ファイルを Assets フォルダーにコピーします。ほかのファイルと同じようにインポートされます。

マップアセットのインスペクター

マップアセットを選択すると、プレビュー、名前、作成者、説明、サイズ、プレイヤー数、タイルセット、フォーマットバージョンに加えて、統計情報(セル数、ワールドサイズ、セルサイズ、レベルの高さ、開始地点、木、装飾オブジェクト、リージョン、マーカー、ディテールレイヤー、ファイルサイズ、メモリ、最終更新日時)が表示されます。

操作説明
Open In Scene選択中の地形にマップを割り当てます(シーン内の地形が 1 つだけならその地形に割り当て、複数ある場合はどの地形にするかを確認します)。シーンに地形がない場合は地形を作成します。再生モードでは無効です。
▾ メニューOpen In New Terrain、またはシーン内の特定の地形を指定する Open On。
Show In Explorer(macOS では Reveal In Finder)オペレーティングシステムのファイルブラウザーでファイルを表示します。
Tileset Id(Copy、Select)マップに保存されているタイルセット ID。Select は、対応するタイルセットアセットを選択します。
Preview Sizeベイクされるプレビューの長辺:128、256(デフォルト)、512 ピクセルのいずれか。
Preview Tilesetプレビューの色に使用するタイルセット。空 = マップに保存されている ID を持つタイルセット(自動的に検索されます)。
Re-bake Preview、Apply / Revertプレビューを再構築します / 変更したインポート設定を適用します。

マップの ID に対応するタイルセットが追加・削除されたとき、またはそのタイルセットのミニマップの色が変更されたときは、プレビューが自動的に再ベイクされます。テクスチャ配列を再構築しても、マップは再インポートされません。破損したファイルは、エラーアイコン付きの空のアセットとしてインポートされ、コンソールにインポートエラーが表示されます。

編集、Save、Save As

マップアセットを RTSTerrain の Map Asset フィールドに割り当てます。地形が有効になるとマップがメモリに読み込まれ、すべての編集はそのメモリ上のコピーに対して行われます。シーンファイルに保存されるのはアセットへの参照だけで、マップデータそのものが保存されることはありません。

  • Save(RTS Terrain Studio ウィンドウのヘッダー、またはシーンビューの RTS Terrain Studio オーバーレイのオプションメニューにある Save Map)は、マップを元の .rtsmap ファイルに書き戻して再インポートします。未保存の変更がある間は、Save がハイライト表示されます。
  • Save As… は新しいファイルを書き出し、再読み込みせずに地形に割り当てます。そのため、アンドゥ履歴は保持されます。
  • Open… は別のマップアセットを読み込みます。Reload はアセットからマップを再読み込みし、未保存の変更を破棄します。
  • マップアセットが割り当てられていない地形は、その Default Map Settings からマップを作成します。このマップは、Save As を使用するまでメモリ上にしか存在しません。
  • シーンを保存(Ctrl+S キー)すると、そのシーン内の地形の編集済みマップも保存され、ファイルがまだない新規マップについては Save As を促します。どちらの動作も、シーンビューのオーバーレイのオプションメニュー(Save Maps When Saving the Scene、Offer Save As for New Maps)でオフにできます。
  • マップに未保存の編集がある状態で再生モードに入る、シーンを閉じる、または Unity を終了しようとすると、保存するかどうかを確認されます。再生モード中の編集(シーンビューのツールまたはゲーム内エディターによるもの)についても、再生モードを終了するときに保存するかどうかを確認されます。

マップの編集はシーンの一部ではありません。Unity 標準の「Save scene?」ダイアログではマップの編集は保存の対象になりませんが、RTS Terrain Studio の確認ダイアログでは対象になります。.rtsmap ファイルが読み取り専用の場合やバージョン管理でロックされている場合、Save は書き込みを行わずにダイアログを表示します。

ランタイムでのマップファイル

ビルドしたゲームでは、マップはディスク上の通常のファイルです。

  • RTSMapSerializer.DefaultMapsFolder は、RTSMapSerializer.StorageRoot(Application.persistentDataPath)内の Maps フォルダーです。そのため、Windows では通常 %USERPROFILE%\AppData\LocalLow\<Company>\<Product>\Maps になります。Web ビルドでは、StorageRoot はブラウザーのストレージ内のフォルダーです(ブラウザー内のマップを参照)。
  • ゲーム内エディター(RTSRuntimeEditor)は、Maps Folder にマップを保存し、そこからマップを開きます(空 = 上記のデフォルトフォルダー。相対パスは RTSMapSerializer.StorageRoot からの相対パスです)。Write Preview Images がオンの場合は、マップブラウザー用に、保存した各マップの隣に小さなミニマップ画像 Name.preview.png を書き出します。
  • マップを共有するには、.rtsmap ファイルをほかのプレイヤーのマップフォルダー、または Unity プロジェクトにコピーします。Web ブラウザーでは、代わりにゲーム内エディターの Download Map と Import Map を使用します。
  • ゲームと一緒にリリースするマップは、地形やスクリプトから参照されるマップアセットにするだけで構いません。ほかのアセットと同じようにビルドに含まれます。
API説明
terrain.LoadFromFile(path) / terrain.SaveToFile(path)ファイルを地形に読み込みます / 地形のマップを保存します。どちらも terrain.CurrentFilePath を設定します。
terrain.MapAsset = assetマップアセットを割り当て、地形が有効であればすぐに読み込みます。
terrain.SetMap(mapData)作成または読み込んだ MapData を表示します。所有権は地形に移ります。
terrain.HasUnsavedChanges最後の読み込みまたは保存以降に編集が行われると true になります。
RTSMapAsset.LoadMap()アセットを新しい MapData にデシリアライズします(所有権は呼び出し側にあります)。
RTSMapAsset.CreateFromMap(map, preview)メモリ上のマップアセットを作成します。たとえば、シーン間でマップを受け渡す場合に使用します。
RTSMapSerializer.Serialize(map) / Deserialize(bytes)バイト列との変換。たとえば、ネットワーク経由でマップを送信する場合に使用します。
RTSMapSerializer.TryReadHeaderFromFile(path, out header)非圧縮のヘッダーだけを読み込みます(高速。マップ一覧向け)。
RTSMapSerializer.TryReadHeader(bytes, out header)メモリ上のマップのバイト列(たとえば、ネットワーク経由で受信したもの)に対して同じ処理を行います。
terrain.CreateNewMap(MapCreationSettings)新しい平坦なマップを作成して表示します。New Map ダイアログに相当するコード側の機能です。地形のタイルセットから Info.tilesetId を設定し、CurrentFilePath をクリアして、マップを未保存としてマークします。
terrain.LoadFromAsset()MapAsset からマップを再読み込みし、未保存の編集を破棄します。アセットを読み込めない場合は、エラーをログに出力し、DefaultMapSettings から作成したマップにフォールバックします。
terrain.SaveToMapAsset()エディター専用:マップを元の .rtsmap アセットに書き戻します。地形にマップアセットがない場合は false を返します。
terrain.PendingLayers変更が通知されたものの、まだ処理されていないレイヤー。
RTSMapAsset.Header / Preview / Dataマップのヘッダー(マップを展開せずに読み込まれます)、ミニマッププレビュー(null の場合があります)、ファイルの生のバイト列。
RTSMapAsset.SetData(bytes, preview)マップアセットの内容を置き換えます(ヘッダーはバイト列から読み込まれます)。
using System.IO;
using RTSTerrainStudio;
using UnityEngine;

public class SimpleMapList : MonoBehaviour
{
    [SerializeField] RTSTerrain terrain;

    /// <summary>Logs all maps in the default folder without loading them, then opens the first one.</summary>
    [ContextMenu("List And Open Maps")]
    void ListAndOpen()
    {
        string folder = RTSMapSerializer.DefaultMapsFolder;
        if (!Directory.Exists(folder)) { Debug.Log("No maps folder yet: " + folder); return; }

        string[] files = Directory.GetFiles(folder, "*" + RTSMapSerializer.FileExtension);
        foreach (string path in files)
        {
            // The header is stored uncompressed, so this is fast even for big maps.
            if (RTSMapSerializer.TryReadHeaderFromFile(path, out RTSMapHeader h))
                Debug.Log($"{h.name} by {h.author}: {h.width} x {h.height} cells, {h.maxPlayers} players, saved {h.Modified}");
        }

        if (files.Length > 0 && terrain != null)
        {
            try
            {
                terrain.LoadFromFile(files[0]);   // the terrain owns and disposes the loaded map
            }
            catch (InvalidDataException e)
            {
                Debug.LogError("The map file is damaged: " + e.Message);
            }
        }
    }
}

タイルセット

タイルセット(RTSTileset)は、マップの外観を定義します。最大 16 個の地面レイヤー、最大 4 つの崖レイヤー、そして水のスタイルで構成され、クラシック RTS のマップエディターにある夏、冬、砂漠のタイルセットに相当します。タイルセットはいくつでも作成でき、地形のタイルセットはいつでも切り替えられます。ペイントされたマップはレイヤーのインデックスしか保存しないため、同じマップを夏の外観でも冬の外観でも表示できます。

  1. Assets ▸ Create ▸ RTS Terrain Studio ▸ Tileset でタイルセットを作成します。
  2. 地面テクスチャをインスペクターの Ground Layers セクションにドラッグします。アルベドテクスチャ 1 枚につき 1 つのレイヤーが作成されます。対応する法線マップと高さテクスチャは、ファイル名のサフィックス(たとえば _n、_normal、_nrm、_normalgl、_h、_height、_disp)によって自動的に検出されます。ラフネス、AO、メタリックのマップは無視されるため、ダウンロードしたテクスチャフォルダーを丸ごとドロップできます。
  3. 同じ方法で、Cliff Layers に崖レイヤーを追加します。
  4. タイルセットを RTSTerrain の Tileset フィールドに割り当てます。
  5. Texture Arrays セクションで Build Texture Arrays を 1 回クリックします(テクスチャ配列を参照)。

タイルセットのフィールド

設定デフォルト説明
Tileset Id(tilesetId)自動生成マップファイルに保存される不変の識別子です。これにより、マップは作成時に使用したタイルセットを記憶します。読み取り専用で、Copy ボタンとともに表示されます。
Display Name(displayName)「New Tileset」エディターに表示される名前。
Ground Layers(groundLayers)空最大 16 個の GroundLayer エントリ。超過したエントリは削除されます。
Cliff Layers(cliffLayers)空最大 4 つの CliffLayer エントリ。
Water(water)青緑WaterProfile。以下を参照してください。
Resolution(arrayResolution)1024テクスチャ配列のスライスサイズ:256、512、1024、2048 のいずれか。ソーステクスチャは自動的にリサイズされます。
Compress(compressArrays)オンビルドした配列を圧縮します(BC7 / ASTC)。イテレーションを最速にするにはオフにします。
Material Override(materialOverride)なしデフォルトの地形マテリアルの代わりに使用する任意のマテリアル。RTS Terrain Studio/Terrain シェーダーまたは互換性のあるシェーダーを使用している必要があります。
Macro Variation(macroVariation)0.25広い範囲でのテクスチャの繰り返しを目立たなくする、大きなスケールのバリエーション。
Triplanar Sharpness(triplanarSharpness)6崖の壁面でのトライプラナーブレンドのシャープさ(1–16)。

地面レイヤーのフィールド

設定デフォルト説明
Name「Layer」パレットに表示されます。
Albedoなしベースカラー(RGB)。サイズと形式は任意です。
Normalなしタンジェント空間の法線マップ(Normal Map としてインポートします)。
Height Mapなしハイトブレンド(高さベースのブレンド)用の任意の高さ(R チャンネル)。アルベドのアルファより優先されます。
Height From Albedo Alphaオフ高さテクスチャが割り当てられていない場合に、アルベドのアルファを高さとして使用します。
Tint白アルベドに乗算される色。
Tile Size6テクスチャ 1 回の繰り返しのワールドサイズ(メートル単位、≥ 0.1)。
Normal Strength10–2。
Smoothness0.10–1。
Metallic00–1。
Height Blend0.5隣接するレイヤーに対するハイトブレンドの強さ(0 = 線形ブレンド)。高さのソースが必要です。
Anti Tiling0.3変化を加えた 2 回目のサンプリングで、目に見えるタイリングを崩します(0–1)。
Pathing EffectNoneこのレイヤーが優勢な場所に適用される通行ルール。たとえば、ごつごつした岩には Unbuildable、溶岩には Unwalkable を使用します。
Minimap Color黒ミニマップとマッププレビューでの色。黒 = アルベドから計算されます。

崖レイヤーのフィールド

設定デフォルト説明
Name「Cliff」Cliff Type パレットに表示されます。
Albedoなし壁面の色。設定されていない場合、このタイプの崖には濃い灰色のプレースホルダーが表示されます。
Normalなしタンジェント空間の法線マップ。
Tint白アルベドに乗算される色。
Tile Size4テクスチャ 1 回の繰り返しのワールドサイズ(メートル単位)。
Normal Strength10–2。
Smoothness0.150–1。
Top Ground Layer−1(なし)このタイプの崖の上面に自動的にペイントされる地面レイヤー。Auto Paint で使用されます。
Edge Ground Layer−1(なし)Cliffs ツールが、このタイプの新しい崖の縁に沿ってペイントする地面レイヤーです。たとえば、石の多い縁取りに使用します(崖の縁の自動ペイントを参照)。崖レイヤーのカードには Edge Layer として表示されます。−1 = 縁を自動的にペイントしません。
Minimap Color黒ミニマップの色。黒 = アルベドから計算されます。

崖の壁面はマップローカル空間でトライプラナーマッピングによってテクスチャリングされるため、崖テクスチャに特別な UV レイアウトは必要ありません。

水プロファイル

水プロファイルはタイルセットの一部で、水レンダラー、通行マップ、ミニマップで使用されます。

設定デフォルト説明
Shallow Color(0.22, 0.55, 0.6, 0.55)浅水域の色。アルファ = 不透明度。
Deep Color(0.03, 0.16, 0.27, 0.95)深水域の色。アルファ = 不透明度。
Color Depth3深水域の色に完全に達する水深(メートル単位)。
Shallow Depth Limit0.9これより深い水は深水域(歩行不可)になります。これより浅い水は、クラシック RTS ゲームと同様に歩行可能です。
Normal Mapなし波の法線マップ。
Normal Strength0.60–2。
Normal Tile Size8法線マップ 1 回の繰り返しのワールドサイズ(メートル単位)。
Flow Speed(0.03, 0.02)法線マップのスクロール速度。
Wave Height0.050–1。
Smoothness0.920–1。
Refraction0.030–0.2。
Foam Color白、アルファ 0.8岸辺の泡の色。
Foam Distance0.6岸辺の泡が広がる距離(メートル単位)。
Foam Textureなし任意の泡のパターン。
Minimap Color(0.1, 0.3, 0.55)ミニマップ上の水の色。

コードからのタイルセットの編集

タイルセットは ScriptableObject です。コードからフィールドを変更したら、tileset.NotifyChanged() を呼び出して、地形にマテリアルを更新させます。tileset.GetGroundLayer(index) と GetCliffLayer(index) は、未使用のスロットに対して null を返します。

テクスチャ配列

地形シェーダーは 20 個のレイヤーすべてを単一のパスで描画するため、すべてのレイヤーテクスチャは 2 つの GPU テクスチャ配列にパックされます。これらの配列を自分で作成する必要はありません。任意のサイズと形式の通常の Unity テクスチャを入力すれば、配列が出力されます。

レイアウト

配列スライスチャンネルカラースペース
_RTS_AlbedoArray0–15 地面レイヤー、16–19 崖レイヤーRGB = アルベド、A = ハイトブレンド用の高さsRGB
_RTS_NormalArray0–15 地面レイヤー、16–19 崖レイヤーRGB = 0–1 に再マッピングされたタンジェント空間の法線(n × 0.5 + 0.5)リニア
  • 最初の崖スライスは RTSTileset.CliffSliceOffset(16)、スライスの総数は RTSTileset.TotalSlices(20)です。
  • 高さ(アルファ)は、レイヤーの Height Map(R チャンネル)から取得されます。Height Map がない場合は、Height From Albedo Alpha がオンであればアルベドのアルファから取得され、それ以外の場合は定数 0.5 になります。
  • 法線はビルド時にアンパックされるため、法線マップには通常どおり Unity の法線マップインポート(DXT5nm / BC5)を使用できます。法線マップのないレイヤーには平坦な法線が使用されます。
  • アルベドのないレイヤーには、控えめなパターンの入ったニュートラルグレーのスライスが使用されます。その場合でも、Tint の色はシェーダーによって適用されます。
  • すべてのスライスは完全なミップチェーンを持ち、トライリニアフィルタリングを使用します。どのサイズのソースも、選択した Resolution にリサイズされます。

ビルド済み配列とランタイムフォールバック

配列が作成される方法は 2 つあります。

ビルド済み配列(推奨)ランタイムフォールバック
作成元タイルセットのインスペクターの Build Texture Arrays ボタン最新のビルド済み配列が存在しない場合に、レンダラーが自動的に作成
保存場所タイルセットアセット内のサブアセット(プレイヤービルドに含まれます)GPU メモリ内のみ(読み込み時に再構築されます)
形式BC7(デスクトップ、コンソール)、ASTC 6×6(Android、iOS、tvOS、visionOS)、Web ではビルドのテクスチャ圧縮形式(DXT5、ASTC 6×6、または ETC2 RGBA8)、DXT5(その他のプラットフォーム)。Compress がオフの場合は RGBA32非圧縮(1 ピクセルあたり 4 バイト)
1024 px 時のメモリBC7 で両方の配列合わせて約 56 MB両方の配列合わせて約 224 MB

Texture Arrays セクションには、ステータスバッジが表示されます。

  • Not built:地形はランタイム配列を使用しています。Build Texture Arrays を 1 回クリックします。
  • Up to date:形式、サイズ、ミップ数、メモリが表示されます。
  • Outdated:テクスチャ、解像度、圧縮設定のいずれかが変更されたか、配列が別のプラットフォームまたは別のテクスチャ圧縮形式用にビルドされています。再ビルドするか、次回のプレイヤービルドに任せてください。
  • Building:ビルドが実行中か、予約されています。

このセクションには、アクティブなビルドプラットフォーム向けのターゲット形式、推定サイズ、推定ビルド時間も表示されます。256 MB を超えるビルドの前には確認を求めます。Clear はビルド済みの配列を削除します。

プレイヤービルド

ビルド済み配列をソーステクスチャと比較できるのはエディターだけなので、プレイヤー(ビルドしたゲーム)はビルド済み配列をそのまま使用します。そのため、古くなった配列は毎回のプレイヤービルドの直前に自動的に再ビルドされ、一度もビルドされていないタイルセットは引き続きランタイム配列を使用します。バッチモードや -rtsAllowOutdatedTextureArrays を含む詳細は、プレイヤービルドでのテクスチャ配列を参照してください。また、プレイヤーはビルド済み配列を初めて使用するときに、その CPU 側のコピーを解放します。これにより、配列が GPU 上で使用するのとほぼ同じ量のメモリを節約できます。

コードから:tileset.HasValidBuiltArrays は、圧縮済みの配列が使用されるかどうかを示します。エディターでは、配列が存在し、ComputeContentHash() と一致する必要があります。プレイヤーは、ランタイムでコードからレイヤーのテクスチャ、解像度、圧縮設定のいずれかが変更されるまではビルド済み配列を有効とみなし、変更されるとランタイム配列にフォールバックします。エディターはテクスチャのインポート状態をハッシュ化するため、ComputeContentHash() の値を比較できるのは、エディター内で計算した値どうし、または同じプレイヤーセッション内で計算した値どうしに限られます。HasValidBuiltArrays が false の場合、RTSTerrain は非圧縮のランタイム配列をビルドします(RTSTextureArrayBuilder.Acquire)。tileset.BuiltAlbedoArray / BuiltNormalArray でビルド済みの配列にアクセスできます。独自のランタイムコードでは、Shader.Find の代わりに RTSShaderReferences.FindShader(name)(および FindMaterial(name))を使用してください。こうすることで、プレイヤービルドでも検索が機能します。

Auto Build

Auto Build がオンの場合(デフォルト。すべてのタイルセットに適用されるエディターの環境設定です)、以前にビルドされた配列は、テクスチャ、解像度、圧縮設定が変更されてから 1 秒後に、高速圧縮で再ビルドされます。最初のビルドは必ず手動で開始します。キャンセルされた自動ビルドや失敗した自動ビルドは、内容またはターゲット形式が変わるまで再試行されません。Build Texture Arrays ボタンで明示的にビルドした場合は通常の圧縮品質が使用され、見た目がわずかに良くなります。そのため、リリース前にはこのボタンを押してください。

推定サイズは簡単に計算できます:解像度² × 20 スライス × 2 配列 × 4/3(ミップ分)× 1 ピクセルあたりのバイト数(BC7 は 1、ASTC 6×6 は約 0.44、非圧縮は 4)。モバイルでは解像度を 512 に下げると、メモリが 4 分の 1 になります。

プレイヤービルドには、シェーダー RTS Terrain Studio/Terrain と Hidden/RTS Terrain Studio/TextureArrayBlit(ランタイムフォールバックで使用)を含める必要があります。Tools ▸ RTS Terrain Studio ▸ Validate Project を実行すると、Resources/RTSTerrainStudio に RTSShaderReferences アセットが作成されます。このアセットにより、パッケージのすべてのシェーダーがビルドに含まれます。

植生ライブラリ

植生ライブラリ(RTSVegetationLibrary)は、ペイントに使用できる草、植物、木の一覧です。Assets ▸ Create ▸ RTS Terrain Studio ▸ Vegetation Library で作成し、RTSTerrain の Vegetation Library フィールドに割り当てます。植生を表示するには、地形に RTSVegetationRenderer コンポーネントも必要です(RTSTerrain のインスペクターが追加を提案します)。

インスペクターには Details と Trees の 2 つのタブがあり、サムネイルがグリッド状に並んでいます。プレハブ(木)、テクスチャ(草のカード)、メッシュ(ディテールメッシュ)をそこにドロップすると、エントリが追加されます。各エントリには、マップが参照する不変の id が割り当てられます。複製したエントリには、自動的に新しい ID が割り当てられます。

ディテールのプロトタイプ

ディテールとは、密度を指定してペイントする、小さくて数の多い植物のことです(ディテール密度を参照)。ディテールは 16 × 16 セルのパッチ単位で生成され、内部のプロシージャルシェーダー Hidden/RTS Terrain Studio/Detail で描画されます。このシェーダーは、風、戦場の霧、色のバリエーションを処理します。

設定デフォルト説明
Id自動生成マップファイルに保存される不変の識別子。
Name「Grass」パレットに表示されます。
Render ModeCrossed QuadsCrossedQuads:Texture でテクスチャリングされた交差クアッド(クラシックな草の表現)。Mesh:Mesh を使用し、Material のメインテクスチャと色でテクスチャリングします。
MeshなしMesh モード用のメッシュ。
MaterialなしMesh モード用のマテリアル。そのメインテクスチャ、色、アルファカットオフがディテールシェーダーで使用されます。
TextureなしCrossed Quads モード用の、アルファ付きテクスチャ。
Healthy Color / Dry Color明るい緑 / 麦わら色各インスタンスは、ノイズに応じて 2 色の間でブレンドされます。
Min / Max Width0.6 / 1.0ランダムな幅の範囲(メートル単位)。
Min / Max Height0.4 / 0.8ランダムな高さの範囲(メートル単位)。
Max Density6最大密度(255)のときの、ディテールのテクセル 1 つあたりのインスタンス数。範囲 1–32。
Color Noise Scale12健康な色と枯れた色をブレンドするノイズのスケール。
Wind Strength0.50–1。風向きと風速は RTSVegetationRenderer で設定します。
Render Distance70カメラからこの距離より遠いインスタンスはカリングされます(その手前で滑らかに縮小して消えます)。
Cast Shadowsオフディテールの影は負荷が高いため、草ではオフのままにしてください。
Align To Groundオンインスタンスを地面の法線に沿って傾けます。
Max Slope40傾斜角がこの値(度)より急な場所には生成しません。
Avoid Waterオン水中には生成しません。
Iconなしパレットのアイコン。空の場合はエディターで生成されます。

木のプロトタイプ

木は、Place Trees で配置される個別のインスタンスです(木と装飾インスタンスを参照)。

設定デフォルト説明
Id自動生成マップファイルに保存される不変の識別子。
Name「Tree」パレットに表示されます。
PrefabなしMeshRenderer と任意の LODGroup を持つプレハブ(LOD0 と LOD1 が使用されます)。
Spawn ModeInstancedInstanced:GPU インスタンシングで描画され、ゲームオブジェクトを使用しません。最も高速です。GameObject:木 1 本につき 1 つのプレハブインスタンスを生成します。伐採などのゲームプレイ向けです。
Min / Max Scale0.85 / 1.2ランダムな均一スケールの範囲。
Random Rotationオン配置時にヨーをランダムにします。
Tint A / Tint B白 / 淡い緑インスタンスごとのランダムな色合いの範囲。
Footprint Radius0.8通行マップでブロックされる半径(メートル単位。木のスケールに合わせて拡縮されます)。
Blocks Visionオンクラシック RTS ゲームと同様に、木が戦場の霧の視線を遮ります。
Min Spacing1.6散布時のほかの木との最小距離。
CollidersオンInstanced モード:プールされたカプセルコライダーのプロキシが作成されます。GameObject モード:プレハブ自体のコライダーが使用されます。
Collider Radius / Height0.4 / 4カプセルプロキシのサイズ(Instanced モード)。
Render Distance250カメラからこの距離より遠い木はカリングされます。
Cast Shadowsオン影を落とすかどうか。
Iconなしパレットのアイコン。空の場合はエディターで生成されます。

GPU インスタンシング

Instanced モードの木は GPU インスタンシングで描画されるため、そのマテリアルでは Enable GPU Instancing をオンにしてください。マテリアルでこの設定がオフになっている場合、レンダラーはインスタンシングを有効にした非表示のコピーを自動的に使用します(編集中も元のマテリアルと同期されます)。また、ライブラリのインスペクターには、マテリアル自体を修正する Enable ボタン付きの情報メッセージが表示されます。ディテールは常にプロシージャルなディテールシェーダーで描画されるため、そのマテリアルでインスタンシングを有効にする必要はありません。

木や植物のプレハブのマテリアルに RTS Terrain Studio/Vegetation シェーダーを使用すると、風、光の透過、戦場の霧、インスタンスごとの色合いが得られます。インスタンシングに対応したほかの URP マテリアルも使用できますが、色合いは適用されません。

コードからは、RTSVegetationRenderer.RemoveTree(id) で木のインスタンスを削除でき(たとえば、ワーカーが木を伐採したとき)、TryFindNearestTree で検索できます。また、GetTreeObject(id) は木のゲームオブジェクトまたはコライダープロキシを返します。

コードからのライブラリの編集

マップはプロトタイプ ID のパレットを保存しているため、パレットのインデックスはライブラリを介して解決します。

  • 木:vegetationLibrary.FindTree(map.TreePrototypeIds[tree.prototype])。
  • ディテールレイヤー:vegetationLibrary.FindDetail(layer.prototypeId)。
  • 装飾オブジェクト:decorLibrary.Find(map.DecorPrototypeIds[d.prototype])。バリアントのプレハブは prototype.GetPrefab(d.variant) で取得します。0 はメインのプレハブで、無効なバリアントや空のバリアントはメインのプレハブにフォールバックします。VariantCount にはメインのプレハブも含まれます。
  • decorLibrary.GetCategories() は、カテゴリを表示順に列挙します(カテゴリのないプロトタイプは Misc として列挙されます)。
  • vegetationLibrary.EnsureUniqueIds() は、重複したエントリに新しい ID を割り当てます。

コードからライブラリを変更したら、library.NotifyChanged() を呼び出してください(ライブラリの Changed イベントが発生します)。これにより、RTSTerrain が LibrariesChanged を発生させ、レンダラーが更新されます。これはタイルセットの場合と同じルールです。

装飾ライブラリ

装飾ライブラリ(RTSDecorLibrary)は、配置できるプロップ(ドゥーダッド)の一覧です。岩、遺跡、柵、木箱、建物など、任意のプレハブを登録できます。Assets ▸ Create ▸ RTS Terrain Studio ▸ Decor Library で作成し、RTSTerrain の Decor Library フィールドに割り当てます。地形に付いている RTSDecorManager コンポーネントが、配置されたインスタンスとシーンの内容を同期させます。

インスペクターでは、プロトタイプが自動生成のサムネイル付きでグリッド表示され、カテゴリごとに 1 つずつタブ(ほかに All タブ)が並びます。プレハブをここにドロップすると、現在のカテゴリに追加されます。カテゴリタブを右クリックすると名前を変更できます。カテゴリのないプロトタイプは Misc に表示されます。

装飾プロトタイプのフィールド

設定デフォルト説明
Id自動生成マップファイルに保存される不変の識別子。
Name「Decor」パレットに表示されます。
Category「Props」プロトタイプが表示されるパレットのタブ。
Prefabなし配置するオブジェクト。
Variants空オプションの代替プレハブ。配置時にバリアントがランダムに選ばれます。インスタンスにはバリアントのインデックスが保存されます(0 = メインのプレハブ)。
Spawn ModeGameObjectGameObject:インスタンスごとに 1 つのゲームオブジェクトを生成します(スクリプト、コライダー、アニメーションを使用可能)。Instanced:GPU インスタンシングで描画し、ゲームオブジェクトは生成しません。静的なプロップ専用で、最も高速です。
Min / Max Scale0.9 / 1.1ランダムな均一スケールの範囲。
Random Yawオン上方向の軸を中心としたランダムな回転。
Yaw Step0ヨーをこの角度の倍数にスナップします(0 = 制限なし)。たとえば、壁や柵には 90 を使います。
Max Random Tilt0ランダムな傾き(度単位、0–45)。
Align To Normal0上方向の軸を地面の法線に合わせます(0 = 直立、1 = 法線に完全に合わせる)。
Snap To Gridオフ位置をセルの中心にスナップします。
Y Offset0描画時に加算される垂直方向のオフセット(メートル単位)。負の値にするとオブジェクトが地面に沈みます。
Tint A / Tint B白 / 白インスタンスごとのランダムな色合いの範囲。
Footprint ShapeCircleNone、Circle、Box のいずれか。
Footprint Size(0.75, 0.75)Circle:X が半径です。Box:オブジェクトの X 軸と Z 軸に沿った半分の大きさ(ハーフエクステント)です。メートル単位で、インスタンスのスケールに合わせて拡大縮小されます。
Footprint EffectUnwalkable, Unbuildableフットプリント(占有範囲)内に書き込まれる PathingOverride フラグ。
Blocks Visionオフ戦場の霧の視線判定で視線を遮ります。
Min Spacing1散布時の、ほかの装飾オブジェクトとの最小距離。
Cast Shadowsオン影を落とすかどうか。
Iconなしパレットのアイコン。空の場合はエディターで生成されます。

フットプリント

  • 円形のフットプリントは、Footprint Size の X を半径として使い、インスタンスの水平方向のスケールのうち最大の値を掛けます。
  • ボックス型のフットプリントは、インスタンスの回転(地面に投影した X 軸と Z 軸)と X/Z スケールに従います。そのため、回転させた壁でも、壁自身が占める範囲だけを正確にブロックします。
  • 形状が None の場合、効果が None の場合、またはインスタンスに DecorFlags.IgnorePathing フラグが付いている場合は、フットプリントは書き込まれません。
  • ユニットが上を歩ける平らな装飾(花、がれき)には Unbuildable だけを使い、実体のある物体には Unwalkable を使います。

スポーンモード

GameObjectInstanced
シーン内のオブジェクトインスタンスごとに 1 つ。「RTS Decor」という名前の子オブジェクトの下に置かれます(シーンには保存されません)なし
スクリプト、コライダー、アニメーション使用可(各ルートに RTSDecorInstance コンポーネントが追加されます)使用不可
レンダリングプレハブの通常のレンダラーLOD0 を使った Graphics.RenderMeshInstanced。パッチ単位でカリング
最適な用途インタラクティブなプロップ、金鉱、破壊可能なオブジェクト何千もの静的な岩や植物

Instanced モードの装飾オブジェクトは、インスタンス描画される木と同じように GPU インスタンシングを扱います。Enable GPU Instancing がオフのマテリアルは、非表示のインスタンシング用コピーを使って描画されます。生成されたオブジェクトはプールされます。RTSDecorManager.GetInstanceObject(id) はインスタンスに対応する生成済みのオブジェクトを返し、RTSDecorInstance.FromCollider(collider) は物理演算のヒットから対応するインスタンスを求めます。

.rtsmap ファイル形式

このセクションは、ほかのツールでマップを読み込みたい、コンバーターを作りたい、または独自のデータを追加したいプログラマー向けです。このパッケージを使うだけなら、この内容は必要ありません。すべて RTSMapSerializer が処理します。

バイナリレイアウト

数値はすべてリトルエンディアンです。文字列は .NET の BinaryWriter 形式で、7 ビットエンコードされた長さの後に UTF-8 のバイト列が続きます。

構成要素型内容
マジックナンバー4 バイトASCII の RTSM
フォーマットバージョンint32RTSMapSerializer.CurrentVersion(現在は 1)
ヘッダー長int32後続するヘッダーのバイト数
ヘッダーバイト列string name、string author、string description、int32 width、int32 height、float cellSize、int32 maxPlayers、string tilesetId、int64 modifiedUtcTicks
本体ファイル末尾までの raw deflate ストリーム(RFC 1951、zlib や gzip のラッパーなし)セクションの並び。値が 0 の int32 タグで終わります

ヘッダーは圧縮されていないため、マップブラウザーはマップを展開しなくても名前、作成者、サイズ、プレイヤー数を表示できます(RTSMapSerializer.TryReadHeader、TryReadHeaderFromFile、構造体 RTSMapHeader)。読み込み側はヘッダー長を使ってヘッダーの末尾までジャンプするため、将来のバージョンでヘッダーの末尾にフィールドを追加できます。

セクション

各セクションは [int32 tag][int32 length][payload] の形式です。タグはリトルエンディアンで格納された FourCC コードで、最初の文字が最下位バイトに入ります(DIMS = 0x534D4944)。ラスターデータの配列は、int32 の要素数の後に生の要素が続く形で書き込まれます。

タグ値ペイロード
DIMS0x534D4944int32 width、int32 height、float cellSize、float cliffLevelHeight、int32 splatResolution、int32 detailResolution、int32 pathingResolution、uint32 nextInstanceId。最初のセクションである必要があります。
INFO0x4F464E49string name・author・description、int32 maxPlayers、string tilesetId、int64 createdUtcTicks・modifiedUtcTicks
HGHT0x54484748微細高さ:頂点ごとに float
LEVL0x4C56454C崖レベル:頂点ごとに byte
VFLG0x474C4656頂点フラグ:頂点ごとに byte
CTYP0x50595443崖タイプ:頂点ごとに byte
WATR0x52544157水面の高さ:頂点ごとに float
SPLT0x544C5053int32 のマップ数(4)、続いてスプラットマップごとに、テクセルごとの RGBA バイトの配列
DETL0x4C544544int32 のレイヤー数、続いてレイヤーごとに:string prototypeId と、ディテールテクセルごとの密度のバイト配列
TREE0x45455254プロトタイプ ID のリスト(int32 の個数 + 文字列)、int32 の木の数、続いて木ごとに:uint32 id、uint16 prototype、float3 position、float yaw、float scale、4 バイトの tint(30 バイト)
DECO0x4F434544プロトタイプ ID のリスト、int32 の個数、続いてインスタンスごとに:uint32 id、uint16 prototype、byte variant、byte flags、float3 position、float4 rotation(x, y, z, w)、float3 scale、4 バイトの tint(52 バイト)
PATH0x48544150手動の通行オーバーライド:通行セルごとに byte
REGN0x4E474552int32 の個数、続いてリージョンごとに:string name、float x・z・width・depth、float4 colour、プロパティ
STRT0x54525453int32 の個数、続いて開始地点ごとに:int32 player、float3 position、float yaw
MARK0x4B52414Dint32 の個数、続いてマーカーごとに:string name、string tag、float3 position、float4 rotation、プロパティ
ENVR0x52564E45float4 sunColor、float sunIntensity、float2 sunRotation、float4 ambientColor、bool fog、float4 fogColor、float fogDensity

「プロパティ」は、int32 の個数の後に、キーと値の文字列ペアが続く形で書き込まれます。派生データである通行マップは保存されません。保存されるのは手動のオーバーライドだけです。

未知のセクション、破損ファイル、バージョン管理

  • 未知のセクションはスキップされます。セクションの長さを使って読み飛ばします。そのため、古いバージョンのパッケージでも、セクションが追加された新しいファイルを読み込むことができ、独自に追加したカスタムセクションがほかの読み込み処理を妨げることもありません。
  • 読み込み側より新しいフォーマットバージョンのファイルを読み込むと、警告がログに出力されますが、読み込み自体は行われます。未知のデータはスキップされます。
  • 要素数がマップサイズと一致しないラスターレイヤーがあると、警告がログに出力されます。収まる部分は読み込まれ、残りはスキップされます。
  • 途中で切れたファイルや破損したファイルでは、どちらの場合も同じ 1 種類の例外 System.IO.InvalidDataException がスローされます。マジックナンバーが正しくないファイルは「Not an RTS Terrain Studio map file」として報告されます。
  • RTSMapSerializer.SaveToFile は、まず <path>.tmp に書き込んでからファイルを置き換えます。そのため、保存中にクラッシュしても、書きかけのマップが残ることはありません。
  • フォーマットの互換性ルール:新しいデータは、新しいセクションまたはヘッダー末尾へのフィールド追加として加え、既存のセクションを変更することはありません。独自のセクションでも同じようにし、ペイロード内にバージョン番号を持たせてください。

カスタムセクション

マップファイルには、ゲームルール、トリガー、クエストデータなどの独自データを保存できます。RTSMapSerializer にある次の 2 つの静的フックで、これを実現できます。

フック型呼び出しタイミング
CustomSectionWriterAction<MapData, Action<int, byte[]>>保存ごとに 1 回、組み込みセクションの後に呼び出されます。書き込みたいセクションごとに、渡されたアクションを独自のタグとペイロードを指定して呼び出します。
CustomSectionReaderAction<MapData, int, byte[]>読み込み側が認識できないセクションごとに、そのタグとペイロードを渡して呼び出されます。この時点で MapData はすでに存在しています(DIMS が最初に来るため)。
  • 組み込みのタグと衝突しないタグを選んでください。先頭は X にします(たとえば XGLD)。0 はセクションの終わりを示すため、決して使わないでください。
  • どちらのフックも通常のデリゲートです。+= でハンドラーを追加し、-= で削除できるため、複数のシステムがそれぞれセクションを追加できます。
  • 読み込み処理ではタグを確認し、自分のものではないセクションは無視してください。
  • MapData には独自データを格納する場所がありません。データはマップと対応付けて別に保持します(例では ConditionalWeakTable を使用)。この方法で保持したデータは MapData.Clone() や ResizeMap ではコピーされず、元に戻す操作の対象にもなりません。
  • フックは、マップを読み込んだり保存したりする前に登録してください。Unity エディターでは、マップはインポーターでも読み込まれ、Save でも書き込まれるため、エディター用にも登録してください(例では InitializeOnLoadMethod を使用)。
using System;
using System.IO;
using System.Runtime.CompilerServices;
using RTSTerrainStudio;
using UnityEngine;

/// <summary>Stores a "starting gold" value in every map file, in a custom section "XGLD".</summary>
public static class StartingGoldSection
{
    // FourCC, little-endian like the built-in tags: the first character is in the lowest byte.
    const int Tag = 'X' | ('G' << 8) | ('L' << 16) | ('D' << 24);
    const int PayloadVersion = 1;

    /// <summary>Our data for one map.</summary>
    public sealed class Data
    {
        public int startingGold = 500;
    }

    // One Data object per MapData. Entries disappear automatically together with their map.
    static readonly ConditionalWeakTable<MapData, Data> s_Data = new ConditionalWeakTable<MapData, Data>();

    /// <summary>Returns the data of a map (created with defaults when the map has none).</summary>
    public static Data Get(MapData map) => s_Data.GetOrCreateValue(map);

#if UNITY_EDITOR
    [UnityEditor.InitializeOnLoadMethod]          // editor: importer and Save
#endif
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]   // player and Play Mode
    static void Register()
    {
        // Remove first so a second registration does not add the handlers twice.
        RTSMapSerializer.CustomSectionWriter -= Write;
        RTSMapSerializer.CustomSectionWriter += Write;
        RTSMapSerializer.CustomSectionReader -= Read;
        RTSMapSerializer.CustomSectionReader += Read;
    }

    static void Write(MapData map, Action<int, byte[]> writeSection)
    {
        if (!s_Data.TryGetValue(map, out Data data)) return;   // nothing to save for this map
        using (var stream = new MemoryStream())
        {
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(PayloadVersion);   // our own version, so the payload can grow later
                writer.Write(data.startingGold);
            }
            writeSection(Tag, stream.ToArray());
        }
    }

    static void Read(MapData map, int tag, byte[] payload)
    {
        if (tag != Tag) return;   // another system's section: ignore it
        using (var reader = new BinaryReader(new MemoryStream(payload)))
        {
            int version = reader.ReadInt32();
            if (version >= 1) Get(map).startingGold = reader.ReadInt32();
        }
    }
}

ゲームコードでは StartingGoldSection.Get(terrain.Map).startingGold のようにして値を使います。エディターで値を変更したら、保存されるようにマップを変更済みとしてマークしてください(たとえば terrain.NotifyChanged(MapLayers.Info, terrain.Map.CellBounds) を呼び出すか、terrain.HasUnsavedChanges = true を設定します)。

Unity エディターでの編集

この章では、Unity エディター内でマップを作るために使うものをすべて説明します。RTS Terrain Studio ウィンドウ、シーンビューでのペイント、キーボードショートカット、ブラシ、対称、元に戻す操作、すべての編集ツールとその全設定、Auto Paint、そして RTS Terrain コンポーネント自体です。

同じツール、ブラシ設定、対称設定はゲーム内エディターでも使われているため(ゲーム内エディターを参照)、ここで学んだことはそちらでもそのまま使えます。

5 つの手順ではじめての編集。このパッケージを使ったことがない場合は、次の手順を試してください。

  1. GameObject ▸ RTS Terrain Studio ▸ RTS Terrain で地形を作成します。プロジェクトにまだタイルセットがない場合は、先にデモコンテンツを作成するよう提案されるので、それを受け入れます。
  2. Tools ▸ RTS Terrain Studio ▸ Editor Window を開きます。
  3. Height タブで Raise / Lower をクリックします。編集が自動的にオンになります。
  4. シーンビューで地形の上にマウスを移動し、マウスの左ボタンを押したままにします。丘が盛り上がります。代わりに掘り下げるには、Shift キーを押したままにします。
  5. 2 キーを押して Cliffs ツールに切り替え、丘をクリックして台地を持ち上げます。終わったら Esc キーを押します。どの操作も Ctrl+Z キーで元に戻せます。

RTS Terrain Studio ウィンドウ

このウィンドウは、エディターの操作の中心です。ウィンドウ自体は編集状態を持たず、シーンビュー、シーンビューのオーバーレイ、RTS Terrain のインスペクターも使用している共有の編集セッションを表示・変更します。どこかで変更した内容は、ほかの場所にもすぐに反映されます。

次のいずれかの方法で開くことができます。

  • Tools ▸ RTS Terrain Studio ▸ Editor Window
  • RTS Terrain のインスペクターにある Open RTS Terrain Studio ボタン
  • シーンビューの RTS Terrain Studio オーバーレイにあるウィンドウボタン
Decor タブと装飾パレットを表示した RTS Terrain Studio ウィンドウ
RTS Terrain Studio ウィンドウ(Decor タブ)。上部にカテゴリ、その下にツール、続いてアクティブなツールの説明、対称設定、サムネイルパレット付きの設定が並びます。

ウィンドウは 4 つの領域で構成されています。上部のヘッダー、タブバー、スクロール可能なページ、下部のステータスバーです。

ヘッダー

ヘッダーには、編集中の地形とマップが表示され、ファイル操作と元に戻す操作のコマンドがあります。

操作機能
地形ピッカーウィンドウとシーンビューのツールが編集する地形を選択します。開いているシーン内のすべての RTS Terrain が一覧表示され、Create New Terrain 項目もあります。ヒエラルキーで地形を選択するか、シーンビューで別の地形をクリックしても、編集対象の地形が切り替わります。切り替え前に、入力途中のテキストは確定されます。
Select ボタンヒエラルキー内の地形のゲームオブジェクトを選択し、強調表示します。
マップ名とドットマップの名前(Map タブで設定)。横にドットがある場合は、マップに未保存の変更があることを示します。その下には、マップサイズと .rtsmap アセットの名前が表示されます。
Saveマップを .rtsmap アセットに書き込みます。マップアセットがない場合は Save As と同じ動作になります。未保存の変更がある間は、ボタンが強調表示されます。ファイルが読み取り専用またはロックされている場合は、ダイアログで問題が説明され、Save As が提案されます。
Save As…マップをプロジェクト内の新しい .rtsmap アセットとして保存し、地形に割り当てます。
Open…プロジェクト内の別の .rtsmap アセットをこの地形で開きます。Assets フォルダーの外にあるファイルは、先にプロジェクトにコピーする必要があります。
Reloadアセットからマップを再読み込みし、未保存の変更を破棄します。マップアセットが必要です。
Undo / Redo直前の地形編集を元に戻す、またはやり直します。ボタンにはステップ名が表示されます(例:Undo Raise / Lower)。詳しくは元に戻す・やり直しを参照してください。
?このマニュアルのエディターウィンドウの章を開きます。
Editing On / Editing Offシーンビューでの地形編集をオンまたはオフにします。まだツールが選択されていない場合、編集をオンにすると、表示中のタブの最初のツールが選択されます。

マップを読み込むとアンドゥ履歴が消去されるため、Open… と Reload は、未保存の変更やアンドゥ履歴があるマップを置き換える前に確認を求めます。再生モードでは、マップを保存しない限り、再生モードを終了したときに編集内容が失われることを警告で知らせます。

シーンにまだ地形がない場合、ヘッダーには代わりに Create Terrain ボタンが表示されます。このボタンは、メニュー項目 GameObject ▸ RTS Terrain Studio ▸ RTS Terrain を使って、植生、水、装飾のコンポーネントを備えた RTS Terrain ゲームオブジェクトを作成します。

タブバー

タブバーには 10 個のタブがあります。8 つのツールカテゴリ Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers と、それに続く Map と Settings です。現在ペイントに使っているツールのカテゴリには、小さなドットが付きます。ウィンドウは最後に使ったタブを記憶します。

ウィンドウにキーボードフォーカスがある間は、1~8 キー(テンキーも可)でカテゴリタブを切り替えられます。テキストフィールドに入力している間、これらのキーは無視されます。

ツールページ

各カテゴリタブには、カードが縦に並んで表示されます。各カードは、ヘッダーをクリックすると折りたためます。

カード内容
Category Toolsツールのグリッドです。ツールをクリックすると、そのツールでシーンビューにペイントできます。アクティブなツールをもう一度クリックすると、編集を終了します。ツールにマウスカーソルを合わせると、説明と修飾キーが表示されます。自作のカスタムツールも自動的にここに表示されます。
ツールカード選択中のツールの名前、説明、ヒント、このマニュアルの該当セクションを開く ? ボタン、そしてリアルタイムのステータス行です。ステータス行は、マウスを地形の上に移動すると表示されます。たとえば、Height ツールでは Ground 4.25 m | Fine +0.25 m | Level 2、Cliffs ツールでは Level 2 → 3 のように表示されます。
Getting Startedこのカテゴリのツールが選択されていないときに表示されます。別のカテゴリのツールでペイント中の場合は Active tool: X と表示され、Go to Tool ボタンでそのツールに移動できます。
Brush形状、半径、強さ、硬さ、減衰カーブ、回転、ランダム回転、間隔、ジッター。これらの設定はすべてのブラシツールで共有されます。ブラシを使わないツールでは非表示になります。詳しくはブラシを参照してください。
Symmetry対称モードと対称の中心(ドラッグツールでは表示されません)。詳しくは対称を参照してください。
Tool Settings選択中のツールの設定です。ツールのコードから自動的に構築されます。設定はエディターのセッションをまたいで記憶されます。すべての設定はツールリファレンスの表に記載されています。
カテゴリ別のアクション一部のカテゴリにある追加カード:Auto Paint Whole Map(Paint)、Selection(Decor、Select Decor がアクティブなとき)、Water at Cursor(Water)、Pathing Overlay(Pathing)。

編集が一時停止している間は、Editing is paused – the Scene View uses Unity's tools というバナーが表示されます。バナーの Resume ボタンを押すと、編集が再びオンになります。

RTS Terrain Studio の全アイコンの一覧:カテゴリ、ツール、アクション、対称モード、ブラシ形状
すべてのカテゴリ、ツール、アクション、対称モード、ブラシ形状に専用のアイコンがあります。ウィンドウ、オーバーレイ、ゲーム内エディターでは同じアイコンが使われます。

ツール設定とパレット

設定カードは、各設定の型に応じて UI コントロールを選びます。

設定の種類UI コントロール
範囲付きの数値値フィールド付きのスライダー
オン/オフの値トグル
選択肢(列挙型)ドロップダウン
色 / テキストカラーフィールド / テキストフィールド
地面レイヤータイルセットの地面レイヤーのパレット(テクスチャのサムネイル付き)。空のスロットも表示されたままになり、そのツールチップでスロットの埋め方を確認できます。
崖タイプタイルセットの崖レイヤーのパレット。
崖レベルレベル 0~15 のボタン。各ツールチップにはそのレベルの地面の高さが表示され、パレットにはカーソル位置のレベルが示されます。
プレイヤー16 色のプレイヤーカラー(Red、Blue、Teal、Purple、Yellow、Orange、Green、Pink…)。
ディテール、木、装飾のプロトタイプライブラリのプロトタイプのグリッド(プレビュー付き)、検索フィールド、および装飾の場合はカテゴリフィルター。クリックでプロトタイプを 1 つ選択し、Ctrl+クリックで 1 つ追加または削除し、Shift+クリックで範囲を選択します。None で選択を解除します。その場合、配置ツールはライブラリの最初のプロトタイプを使います。
アセット参照オブジェクトフィールド(たとえば Auto Paint プロファイル)。型が許す場合は、シーン内のオブジェクトも指定できます。

パレットに必要なアセットが地形にない場合は、不足しているものがカードで説明され、それを作成するボタン(Create Tileset、Create Vegetation Library、Create Decor Library)が表示されます。隣のフィールドに既存のアセットを割り当てることもできます。また、各パレットには Edit ボタンがあり、タイルセットまたはライブラリを選択して、インスペクターで項目を追加できるようにします。

シーンビューで Ctrl+クリックしてスポイトで取得すると、ウィンドウの設定も変わります。たとえば、選択中の地面レイヤーや平坦化の高さです。

ステータスバー

ウィンドウ下部のステータスバーには、左から順に次の情報が表示されます。

  • マウス位置の地点:セル、ワールド座標、崖レベル、地面の高さ、水深(水がある場合)、優勢な地面レイヤーの名前
  • その地点の通行フラグ(歩行可能、建設可能など)
  • ブラシ(例:Brush 6.0 m · 50%)
  • アンドゥ履歴(例:Undo 12 (+2) · 48 MB):元に戻せるステップ数、やり直せるステップ数、それらが使用しているメモリ量

Map タブ

Map タブには、マップ全体に関わる設定と操作がまとめられています。ダイアログで特に記載がない限り、どの変更も元に戻せます。上部の ? ボタンでこのセクションを開けます。

Map

設定説明
Nameマップブラウザーやロビーに表示されるマップの名前。ウィンドウのヘッダーにも表示されます。
Authorマップの作成者。
Descriptionプレイヤーに表示される短い説明。
Max Players推奨プレイヤー数(1–16)。
Map Assetこの地形が読み込み・保存する .rtsmap アセット。別のアセットを割り当てるとそのマップが開きます。その際は Open… と同じ確認が表示されます。

フィールドの下には、マップサイズ、スプラット・ディテール・通行の各レイヤーのセルあたりの解像度、マップが使用するメモリ量が表示されます。これらのフィールドに入力すると、フィールドから離れたときに 1 アンドゥステップ(Edit Map Info)が作成されます。

New Map

地形のマップを、新しい平らなマップに置き換えます。

設定デフォルト説明
サイズプリセット–64、96、128、192、256、384、512 セルの正方形マップ用のボタン。
Width / Height地形の Default Map の値(128)セル単位のサイズ。16 の倍数で、16~1024。
Cell Size2 m1 セルのサイズ(メートル単位)。クラシック RTS のマップでは約 2 m が使われます。
Cliff Level Height2 m崖レベル 1 段分のワールド空間での高さ。
Base Cliff Level2マップ全体の初期の崖レベル。2 にしておくと、湖や谷を掘る余地が残ります。
Base Ground Layer0最初にマップ全体を覆う地面レイヤー。
Splat4セルあたりの地面ペイントのテクセル数(1、2、4、8 のいずれか)。通常は 4 が適しています。8 にすると非常に細かくペイントできます。
Details2セルあたりの草の密度のテクセル数(1、2、4 のいずれか)。
Pathing2マップセルあたりの通行セル数(1、2、4 のいずれか)。値を大きくすると、ユニットをより正確に配置できます。
NameNew Map新しいマップの名前。

Create New Map をクリックする前に、作成されるワールドサイズが概要行に表示されます。

新しいマップを作成するとアンドゥ履歴が消去され、この操作は元に戻せません。古いマップアセットは地形から切り離されますが、変更はされません。新しいマップを専用のファイルに保存するには、Save As… を使います。

Resize

内容を保ったまま、マップを拡大または縮小します。

  1. New Width と New Height をセル単位(16 の倍数)で入力します。
  2. 9 つのアンカーボタンのいずれかをクリックします。矢印はマップが広がる方向を示し、既存の内容はアンカー側に残ります。
  3. 新しい領域用に Fill Level(崖レベル)と Fill Layer(地面レイヤー)を選択します。
  4. Resize Map をクリックします。ボタンの上のヒントに、内容の移動量と新しいワールドサイズが表示されます。

縮小すると、新しい領域の外にある地形、木、装飾オブジェクト、マーカーは削除されます。サイズを変更すると、アンドゥ履歴が消去されます。

Generator

シードから完全なマップレイアウト(台地、スロープ、水、地面ペイント、植生、装飾オブジェクト、開始地点)を生成します。同じシードと設定からは、常に同じマップが生成されます。

操作説明
Seed、Style、Symmetryシード、スタイル(Plains、Hills、Highlands、Islands、Canyon、Arena)、レイアウトの対称(デフォルトは 180° のレイアウトである Rotate 2。対称を参照)。
Noise、Cliffs、Ramps、Water、Border、Paint、Vegetation、Decor、Start Locations各段階の詳細なジェネレーター設定をまとめた折りたたみ。
Randomize Seed新しいシードをランダムに選びます。
Apply Style Preset選択したスタイルのノイズ、崖、スロープ、水の値を読み込みます。
Resetすべてのジェネレーター設定を、選択したスタイルのデフォルトにリセットします。
Generate高さ、崖、水、地面ペイント、植生、装飾オブジェクト、開始地点を置き換えます。マップサイズは維持されます。この操作は 1 アンドゥステップとして記録されます。

ジェネレーターは、ウィンドウのヘッダーで指定されている地形に対して動作します。別の地形が選択されている場合は、先にヘッダーでその地形を選択するよう求めるダイアログが表示されます。実行中は進行状況バーが表示され、生成された内容の概要がボタンの下に表示されます。

ハイトマップ

地形の高さを 16 ビットの PNG または RAW ファイルとしてインポート・エクスポートします。たとえば World Machine、Gaea、Photoshop と組み合わせて使う場合に利用します。

設定説明
Mode(インポート)Fine Heights Only:既存の崖レベルの上に丘を加えます。Cliff Levels And Fine Heights:画像を崖レベルと微細高さに分割し、地面を画像と正確に一致させます(段々になったハイトマップに最適です。レベルをまたぐ滑らかな傾斜は小さな崖になります)。Absolute Heights Keep Levels:画像を地面の絶対的な高さとして扱いますが、崖はそのまま維持します。Ground Height でエクスポートし、ほかのツールで編集した画像を再インポートするときに使います。
Height Scale16 ビットの全範囲に対応する高さ(メートル単位)。0 = 自動。
Height Offsetインポート:すべての高さに加算する値(メートル単位)。エクスポート:0 として書き出される高さ。
Flip Vertically画像を Z 方向に反転します。マップが鏡像のように見える場合に使います。
RAW Width / RAW HeightRAW ファイルのサイズ。0 = ファイルサイズから推定します(正方形のファイルのみ)。
RAW Little EndianRAW ファイルのバイトオーダー。Windows とほとんどのツールはリトルエンディアンを使用します。
Source(エクスポート)Ground Height:崖レベルと微細高さを合わせた高さ。Fine Heights:レベルの上に重なる微細高さのみ。

ボタンは Import PNG…(8 ビットまたは 16 ビットのグレースケール PNG、JPG、EXR)、Import RAW…、Export PNG…、Export RAW…、Export Splat PNG…(地面ペイントを優勢レイヤーの色で表した画像)です。サイズの異なる画像はリサンプリングされます。マップの頂点数は各方向でセル数より 1 つ多いため、サイズ 256 のマップでは 257 × 257 個のサンプルを使用します。インポートは 1 回ごとに 1 アンドゥステップになります。

ミニマップ

マップのミニマップ画像をベイクします。長辺の Size(128、256、512、1024 px)を選択し、Bake Options を調整します。設定できるのは、地面の色(優勢レイヤーまたはブレンドしたレイヤー)、崖レベルの明るさ、起伏の陰影、崖、水、木、装飾オブジェクト、開始地点です。Bake Preview はカード内に結果を表示し、Save PNG… はベイクしてファイルとして保存します。ベイク後にマップを編集すると、再度ベイクするよう促すメッセージがカードに表示されます。

検証

Validate Map は、テクスチャの欠落、到達できない開始地点、パフォーマンス上の制限など、マップの問題をチェックします。各問題はエラー、警告、情報のいずれかとして、修正方法のヒントとともに一覧表示されます。位置情報を持つ問題をクリックすると、その場所がシーンビューに表示されます。検証後にマップを編集すると、一覧が最新の編集より前のマップに基づいていることがカードに表示されます。

環境

太陽、環境光、フォグはマップに保存され、RTS Environment コンポーネントによってシーンに適用されます。

設定説明
Sun Colour太陽光の色。
Sun Intensity太陽光の強度(0–8)。
Sun Rotation度単位:x = ピッチ(地平線からの高さ)、y = ヨー。
Ambient Colour環境光(空)の色。
Fog距離フォグを有効にします。
Fog Colour / Fog Density指数フォグの色と密度(0–0.05)。

Capture From Scene は、現在のシーンのライティングをマップにコピーします(1 アンドゥステップ)。Apply はマップの環境設定をすぐにシーンへ適用し、Select Component は RTS Environment コンポーネントを選択します。地形に RTS Environment がない場合は、カードに Add Environment Component が表示されます。

Settings タブ

カード内容
ViewShow Grid(編集中の地形上のセルグリッド。G キーでも切り替え可能)、Show Markers(Markers ツールが有効でないときも開始地点、リージョン、マーカーを表示)、Pathing Overlay(Off、Walkability、Buildability、Flyability、Water、Combined)、Overlay Opacity。
Terrain Component編集中の RTS Terrain コンポーネントの完全なインスペクター(RTS Terrain コンポーネントを参照)と Select Terrain ボタン。
Undo HistoryMax Steps(10–1000、デフォルト 100)、Memory Limit (MB)(32–4096、デフォルト 512)、現在のメモリ使用量、Clear History。詳しくは制限とメモリを参照してください。
Shortcutsシーンビューのショートカットと現在のキー割り当て(Unity のショートカットマネージャーから読み取り)。Edit Shortcuts… はショートカットマネージャーを開きます。Refresh は、割り当てを変更した後にキー割り当てを再度読み込みます。
Help & MaintenanceOpen Documentation、Validate Project(Tools ▸ RTS Terrain Studio ▸ Validate Project を実行)、Reset All Tool Settings(ブラシ、対称、すべてのツールの設定をデフォルトに戻し、地形のアンドゥ履歴を消去)。
About製品のバージョンと Unity のバージョン。

シーンビューでのペイント

編集はすべてシーンビューで行います。地形編集がオンでツールが選択されているときは、マウスの左ボタンでオブジェクトを選択する代わりに、そのツールでペイントします。編集をオンにするには、ウィンドウの Editing ボタン、オーバーレイの Start Editing ボタン、インスペクターの Edit Terrain ボタンを使うか、ツールをクリックします。オフにするには Esc キーを押します。

マウスと修飾キー

入力操作
マウスの左ボタンアクティブなツールでペイントします(押す、ドラッグする、離す)。1 回のストロークが 1 アンドゥステップです。多くのツールは、ボタンを押したままマウスを止めていても効果を適用し続けます。たとえば Raise / Lower ツールでは丘が盛り上がり続けます。
Shift(押したまま)ツールを反転します。上げる代わりに下げる、ペイントする代わりに消去する、配置する代わりに削除する、といった動作になります。ブラシカーソルが赤みを帯び、ステータスパネルに (inverted) と表示されます。
Ctrl+クリックペイントする代わりに、カーソル下の値(高さ、地面レイヤー、崖レベル、崖タイプ、水面の高さ)をスポイトで取得します。スポイト操作のないツールでは This tool has nothing to pick と表示され、ペイントは行われません。
Alt+ドラッグ通常どおりカメラをオービット(周回)させます。ペイントは行いません。
マウスの右ボタン / 中ボタンUnity での通常の機能(フライスルー、パン)のままです。
Hand ツール(Q)Unity の Hand ツールでは、左ドラッグでカメラをパンし、ペイントは行いません。ステータスパネルには Hand tool (Q) active: press W to paint, or drag to pan と表示されます。
Esc次のうち最初に当てはまる操作を行います:実行中の装飾ハンドルのドラッグのキャンセル、実行中のストロークのキャンセル(その変更は元に戻ります)、選択中の装飾オブジェクトの選択解除(Select Decor)、編集の終了。Esc キーは、キーボードフォーカスを持つシーンビューが読み取ります(先にシーンビュー内をクリックしてください)。マウスを重ねるだけでは不十分です。RTS Terrain Studio ウィンドウにフォーカスがある場合、Esc キーはストロークをキャンセルし、編集をオフにします。
別の地形をクリックその地形を編集対象にします(通知 Editing X)。その地形にマウスを重ねている間、ステータスパネルに Click to edit 'X' と表示されます。

macOS では、このマニュアルで Ctrl キーと記載されている箇所はすべて Cmd キーで操作できます。

ポインターと地形の交点は数学的に計算されるため、コライダーは必要ありません。ブラシカーソルは地形シェーダーによって、ペイントする位置に正確に描画されます。複数のシーンビューを開いている場合は、マウスの下にあるビューだけがペイントを行い、ストロークは開始したビュー内にとどまります。

編集できるのは、有効になっていて、マップが読み込まれ、現在のステージに属している地形だけです。プレハブモードでは、プレハブ内の地形を編集してください。

RTS Terrain Studio オーバーレイ

シーンに RTS Terrain が含まれると、すぐにシーンビューにコンパクトな RTS Terrain Studio オーバーレイが表示されます。ウィンドウ全体を開かなくても、よく使う次の操作を利用できます:

  • Start Editing / Editing (Esc to stop) ボタン、ウィンドウを開くボタン、オプションメニュー
  • 編集中の地形(未保存の変更がある場合は Terrain: X • unsaved)
  • 8 つのカテゴリボタン(1–8 キー)と、選択したカテゴリのツール
  • Size(小さなブラシを細かく調整できるスライダーと、正確な値を入力するフィールド)と Strength
  • Symmetry ドロップダウン(正方形のマップが必要なモードでは警告を表示)
  • Grid と Markers の切り替えボタン、Pathing オーバーレイのドロップダウン

オプションメニューには Show Status Panel、Save Map、Save Map As…、Save Maps When Saving the Scene、Offer Save As for New Maps、Shortcuts…、Documentation があります。ほかの Unity のオーバーレイと同様に、移動、ドッキング、折りたたみが可能で、その位置は Unity に記憶されます。

Unity の Tools オーバーレイ

RTS Terrain を選択している間、Unity 標準の Tools オーバーレイに RTS Terrain Studio ツールが表示されます。このツールを有効にすると、選択中の地形が編集対象になり、編集がオンになります。ほかの Unity ツールに切り替えると、編集は再びオフになります。

例外は Select Decor です。このツールが有効な間は、Unity の Move、Rotate、Scale、Transform ツールで選択中の装飾オブジェクトに使うハンドルを選ぶため、編集はオンのままです。詳しくは装飾オブジェクトの選択ハンドルを参照してください。

ステータスパネルと通知

シーンビューの下部にあるステータスパネルには、2 行の情報が表示されます:

  1. アクティブなツール、Shift キーを押している間は (inverted)、ブラシのサイズと強さ(ブラシツールのみ)、対称モード(正方形のマップが必要なモードでは警告を表示)
  2. マウスを重ねているセル、その崖レベルと高さ、ツールの状態またはヒント、現在 Esc キーで実行される操作(Esc: cancel stroke、Esc: cancel edit、Esc: clear selection、Esc: stop editing のいずれか)

表示・非表示は、オーバーレイのオプションメニューにある Show Status Panel で切り替えます。ショートカットを使うと、実行した内容がシーンビューに短い通知として表示されます(たとえば Brush 8.0 m、Strength 55%、Grid on、Stroke cancelled)。Unity の起動時に前回のセッションから編集がオンのままになっている場合は、Terrain editing is on (Esc to stop) という通知が 1 回だけ表示されます。

プレビューとガイド

  • ブラシカーソル:ツールの色で地形に投影されます。対称コピーごとにも 1 つずつ表示されます。
  • ゴースト:Single モードでは、Place Trees と Place Decor で次に配置するオブジェクトの半透明のプレビューがカーソル位置に表示されます。
  • 対称ガイド:対称モードがオンの間、マゼンタの軸と中心点が表示されます。
  • マーカー:開始地点(プレイヤーカラーで表示)、リージョン、マーカー。Show Markers がオンの間、または Markers ツールが有効な間に描画されます。
  • グリッド(G)と通行オーバーレイ(P):地形シェーダーによって描画されます。

装飾オブジェクトの選択ハンドル

Select Decor が有効なときは、オブジェクトをクリックして選択し、矩形をドラッグしてボックス選択します。Shift キーを押したままにすると、選択に追加したり選択状態を切り替えたりできます。選択したオブジェクトの中心に、Unity と同じスタイルのハンドルが表示されます:

Unity ツールハンドル
Move (W)位置ハンドル。
Rotate (E)ヨーディスク:装飾オブジェクトは上方向の軸を中心にのみ回転します。Ctrl キーを押したままにするとスナップします。
Scale (R)均一スケールハンドル。Select Decor が有効な場合、R ショートカットはランダム回転を切り替える代わりにこのツールを選択します。
Transform (Y)位置ハンドルとヨーディスク。
Rect (T)位置ハンドル。
Hand (Q)ハンドルなし。左ドラッグでカメラをパンします。

Delete キーで選択中のオブジェクトを削除します。Ctrl+D キーで選択中のオブジェクトを複製します。複製は見分けやすいように X 方向と Z 方向に 1 セルずつずらして配置されるので、ハンドルで移動してください。Duplicated 3 objects のような通知で結果を確認できます。Ctrl+A キーでロックされていない装飾オブジェクトをすべて選択し、F キーで選択範囲をフレームに収めます。これらは Unity 標準の Delete、Duplicate、Select All、Frame Selected コマンドなので、ショートカットマネージャーでのキー割り当てに従います。ハンドルのドラッグは 1 回ごとに 1 アンドゥステップになります。ドラッグ中に Esc キーを押すと、ステップを作成せずにドラッグを元に戻します。そのほかのコマンドは Decor タブの Selection カードにあります(Decor ツールを参照)。

保存と保存ガード

編集中のマップは、シーンファイルとは別にメモリ上に保持されます。そのため、Unity 標準の Save scene? 確認ダイアログはマップを対象としません。RTS Terrain Studio は独自の保存ガードを追加します:

タイミング動作
シーンの保存時シーン内の地形のマップが、それぞれの .rtsmap アセットに自動的に保存されます(オーバーレイのオプション Save Maps When Saving the Scene、デフォルトでオン)。マップアセットのない地形については、エディターセッションごとに 1 回 Save As… が提案され、Not Now と Never Ask Again を選択できます(オーバーレイのオプション Offer Save As for New Maps)。
再生モードに入るときSave、Cancel(編集モードのまま)、Don't Save の選択肢とともに Save map changes before entering Play Mode? と表示されます。Unity はシーンをリロードし、それに伴ってマップもアセットから再読み込みされます。Enter Play Mode Options でシーンのリロードとドメインリロードの両方が無効になっている場合、この確認は表示されません。
再生モードの終了時Save または Discard の選択肢とともに Save map changes made in Play Mode? と表示されます。再生モード中にシーンビューのツールでマップを編集した場合、またはシーンに In-Game Editor(RTSRuntimeEditor)があり、マップに未保存の変更がある場合に確認されます。それ以外のシーンでは、伐採された木などゲームプレイによる変更があってもこの確認は表示されません。
シーンを閉じるとき、Unity の終了時変更されたマップを保存するかどうかを確認します。

マップファイルを上書きする前に、このパッケージはバージョン管理システムにファイルのチェックアウトを要求します。失敗した場合は、対処方法を説明するダイアログが表示されます。バッチモードやプレイヤーのビルド中には、保存ガードがダイアログを表示することはありません。

ビルドスクリプトやエディターテストでは、保存ガードを一時的にオフにできます:

using RTSTerrainStudio.Editor;
using UnityEditor;

static class MyBuildSteps
{
    [MenuItem("Tools/My Project/Run Map Batch")]
    static void RunMapBatch()
    {
        // No save prompts while this scope is open. Scopes can be nested.
        using (RTSMapSaveGuard.Suppress())
        {
            // ... open scenes, edit or generate maps, enter and leave Play Mode ...
        }
    }
}

キーボードショートカット

シーンビューのショートカットは、Unity のショートカットマネージャーの RTS Terrain Studio カテゴリに登録されています。これらのショートカットが有効になるのは、地形編集がオンで、シーンビューまたは RTS Terrain Studio のエディターウィンドウのいずれかにキーボードフォーカスがあり(またはマウスがシーンビュー上にあり)、テキストフィールドや数値フィールドに入力中でない場合だけです。編集時以外は、R キーや数字キーなどは Unity での通常の機能のままです。

デフォルトのキーショートカットマネージャーのエントリ操作
[RTS Terrain Studio/Brush Smallerブラシの半径 × 0.87。
]RTS Terrain Studio/Brush Largerブラシの半径 × 1.15。
-RTS Terrain Studio/Strength Downブラシの強さ − 5%。
=RTS Terrain Studio/Strength Upブラシの強さ + 5%。
RRTS Terrain Studio/Toggle Random Rotationブラシのランダム回転を切り替えます。Select Decor が有効な場合は、代わりに Unity の Scale ツールを選択します。
GRTS Terrain Studio/Toggle Gridセルグリッドの表示・非表示を切り替えます。
PRTS Terrain Studio/Cycle Pathing OverlayOff → Walkability → Buildability → Combined → Off の順に切り替えます。
1RTS Terrain Studio/Height Toolsカテゴリの最初のツールを選択します。もう一度キーを押すと、そのカテゴリのツールを順に切り替えます。
2RTS Terrain Studio/Cliff Tools
3RTS Terrain Studio/Paint Tools
4RTS Terrain Studio/Vegetation Tools
5RTS Terrain Studio/Decor Tools
6RTS Terrain Studio/Water Tools
7RTS Terrain Studio/Pathing Tools
8RTS Terrain Studio/Marker Tools

以下のキーとマウス操作はパッケージ自体が処理するため、割り当てを変更できません:

キー場所操作
Shift(押したまま)シーンビューツールを反転します(下げる、消去する、削除する)。
Ctrl+クリック(macOS では Cmd+クリック)シーンビュー値をスポイトで取得します(高さ、レイヤー、レベル、崖タイプ、水面の高さ)。
Escキーボードフォーカスのあるシーンビューハンドルのドラッグまたはストロークのキャンセル、装飾オブジェクトの選択解除、または編集の終了。
1–8(テンキーも可)RTS Terrain Studio ウィンドウカテゴリタブを切り替えます。
EscRTS Terrain Studio ウィンドウストロークをキャンセルし、編集を終了します。
Tab、Enter、SpaceRTS Terrain Studio ウィンドウツールとタブの間でフォーカスを移動し、フォーカスのある項目を有効にします。
Ctrl+クリック、Shift+クリックプロトタイプのグリッドそれぞれ、プロトタイプを 1 つ追加・削除する操作と、範囲を選択する操作です。

以下のキーは Unity 標準のショートカットとコマンドです。RTS Terrain Studio はこれらに反応しますが、キー割り当ては Edit ▸ Shortcuts... の Unity のカテゴリ(Main Menu/Edit、Tools)での設定に従います:

デフォルトのキー場所操作
Ctrl+Z / Ctrl+YUnity 内のどこでもUnity の Edit メニューを通じて、地形の編集を元に戻します / やり直します。
W E Yシーンビュー、Select DecorUnity の Move、Rotate、Transform ツール。選択中の装飾オブジェクトに使うハンドルを選びます。
Delete、Ctrl+D、Ctrl+A、Fシーンビュー、Select DecorDelete、Duplicate、Select All(ロックされていない装飾オブジェクト)、Frame Selected。

Select Decor 使用時の R キーは、このパッケージ独自の RTS Terrain Studio/Toggle Random Rotation エントリです。Select Decor が有効な間は、ランダム回転を切り替える代わりに Unity の Scale ツールに切り替えます。

ショートカットの割り当て変更

  1. Edit ▸ Shortcuts...(macOS では Unity ▸ Shortcuts...)を開くか、Settings タブの Edit Shortcuts… をクリックします。
  2. RTS Terrain Studio カテゴリを選択します。
  3. 上記のエントリに新しいキーを割り当てます。
  4. Settings タブの Refresh をクリックすると、新しいキー割り当てがウィンドウに表示されます。

使用中の環境で、キーがほかのショートカット(たとえば 2 キーに割り当てられたシーンビューの Toggle 2D Mode)と競合する場合、Unity はどちらを使うかを 1 回だけ確認します。どちらのショートカットも、ショートカットマネージャーで割り当てを変更できます。

ブラシ

すべてのブラシツールは 1 つのブラシを共有します。ブラシは、各ツールページの Brush カード、オーバーレイ(Size と Strength)、またはショートカットで設定します。ブラシの設定はエディターセッション間で保持されます。

設定範囲デフォルト説明
ShapeCircle、Square、Diamond、CustomCircleブラシの輪郭。Square と Diamond は回転できます。Custom は Brush Shape アセットを使用します。
Custom Shape–なしCustom 形状で使用する Brush Shape アセット。Custom 形状ボタンをクリックすると、プロジェクト内のすべての Brush Shape アセットがカードにタイルとして表示され、Custom Shape フィールドも表示されます。使用するには、タイルをクリックします(またはフィールドにアセットを割り当てます)。
Radius0.25–128 m6 mメートル単位のブラシの半径。小さなブラシを細かく調整できるよう、スライダーは対数スケールです。ショートカットは [ キーと ] キー。
Strength0.01–10.51 回のダブ(ブラシを 1 回置いた跡)ごとの効果の強さ。ショートカットは - キーと = キー。
Hardness0–10.350 = 半径全体にわたる柔らかい縁、1 = 硬い縁。Falloff Curve がオンの間は無効です。
Falloff Curveオン/オフオフHardness の代わりにカスタムカーブを使用します。
Curve–1 から 0 へのイーズインアウトブラシの中心(x = 0)から縁(x = 1)までのウェイト。
Rotation0–360°0正方形、ひし形、カスタムの各形状の回転(上から見て時計回り)。Circle 形状の場合と、Random Rotation がオンの間は無効です。
Random Rotationオン/オフオフダブごとにランダムに回転させます。カスタムスタンプと組み合わせると効果的です。ショートカットは R キー。
Spacing0.05–20.2ダブ同士の間隔(半径に対する比率)。値を小さくするとストロークが滑らかになります。
Jitter0–10各ダブのランダムなずれ(半径に対する比率)。

ダブのウェイトの決まり方

各ダブは、頂点、スプラットテクセル、オブジェクトのそれぞれに 0~1 のウェイトを与え、ツールはそのウェイトに Strength を掛けます。Hardness を使う場合、ウェイトは hardness × radius の内側では 1 で、そこから縁に向かって滑らかに 0 まで減衰します。Falloff Curve がオンの場合は、代わりにカーブがウェイトを決めます。x は中心からの距離(0 = 中心、1 = 縁)、y はウェイトです。正方形とひし形の形状では「距離」が輪郭に沿って測られるため、正方形のブラシは辺に沿って均一な強さになります。

一部のツールはブラシをハードセレクション(減衰なしの選択)として使います。崖、水、通行判定のツールは、ウェイトが 0.5 以上のもの、またはブラシの中心の下にある頂点だけを変更します。この場合、柔らかい縁は「弱い」ではなく「少し小さい」ことを意味します。

間隔、ジッター、リピート

  • Spacing:ドラッグ中、マウスの軌跡に沿って radius × spacing メートルごとにダブが置かれます。ただし、間隔は最小でも 1/10 セルです。頂点にスナップするツールでは、ダブの間隔は最小でも半セルになります。
  • Jitter:各ダブは最大 jitter × radius のランダムな距離だけずらされます。ジッターはブラシを使うツールにのみ適用されます。
  • リピート:多くのツールは、ボタンを押したままマウスを止めている間もダブを適用し続けます(たとえば Raise / Lower では毎秒 30 回)。どのツールがこの動作をするかは、ツールの表に記載されています。

回転の規則

RTS Terrain Studio のすべての回転(ブラシの回転、木と装飾オブジェクトのヨー、開始地点とマーカーの向き)は同じ規則に従います。単位は度で、上から見て時計回りです。0° は +Z(北)方向、90° は +X(東)方向を向きます。このため、回転したブラシやオブジェクトの対称コピーは、位置とまったく同じように回転・ミラーリングされます。Random Rotation がオンの場合、ランダムな角度はダブごとに 1 つ決まり、そのダブのすべての対称コピーで共有されるため、対称なストロークは対称のまま保たれます。

カスタムブラシ形状

Brush Shape アセットを使うと、任意のグレースケールテクスチャをブラシのスタンプにできます。白い部分には効果が最大限に適用され、黒い部分には適用されません。

  1. グレースケールテクスチャ(たとえば岩の輪郭や水しぶき)をインポートします。読み取り可能に設定する必要はありません。
  2. Assets ▸ Create ▸ RTS Terrain Studio ▸ Brush Shape でアセットを作成し、テクスチャを割り当てます。
  3. インスペクターでプレビューを確認します。プレビューには、ブラシが使用するとおりのベイク済みマスクと、そのカバー率が表示されます。
  4. Brush カードで Custom 形状ボタンをクリックします。カードにすべての Brush Shape アセットがタイルとして表示されるので、新しい形状のタイルをクリックします。
設定デフォルト説明
Textureなしグレースケールまたはアルファのテクスチャ。白 = 最大の効果。
Use Red Channelオン赤チャンネルを読み取ります。オフにすると、代わりにアルファチャンネルを読み取ります。
Invertオフマスクを反転します。
Resolution64ベイクされるマスクの解像度(16–256)。

マスクは変更のたびに自動的にベイクされます。Bake Mask を使うとすぐにベイクされます。カスタムスタンプはブラシの中心を囲む正方形の範囲に適用され、その上で硬さまたは減衰カーブも掛け合わされます。スタンプの縁を柔らかくするには硬さを下げ、スタンプをそのまま使うには硬さを 1 に設定します。

対称

対称機能は、すべてのダブ、クリック、配置したオブジェクトを、ミラーリングまたは回転した位置で繰り返します。これを使うと公平な対戦マップを作れます。すべてのプレイヤーに同じ丘、崖、スロープ、木、金鉱が与えられます。モードは Symmetry カードまたはオーバーレイで選択します。シーンビューのマゼンタのガイドが軸と中心を示します。

対称はブラシツールとクリックツールに適用されます。ドラッグツール(Regions と Select Decor)では対称は無視され、これらのツールが有効な間、ウィンドウには Symmetry カードが表示されません。ミラーリングしたリージョンは手動で作成してください。

ウィンドウでの名前オーバーレイ / コードでの名前コピー数機能用途
OffNone1ブラシ自体だけがペイントします。自由な形のマップ、キャンペーンマップ、繰り返したくない細部。
Mirror XMirror X (MirrorX)2中心を通る縦軸を境に、左 ↔ 右をミラーリングします。西と東で向かい合う 2 人のプレイヤーまたは 2 チーム。
Mirror ZMirror Z (MirrorZ)2横軸を境に、上 ↔ 下をミラーリングします。北と南で向かい合う 2 人のプレイヤーまたは 2 チーム。
Mirror XZMirror XZ (MirrorXZ)4両方の軸でミラーリングします。4 つの隅に拠点があるマップ、または各チームが片側を占める 2v2 マップ。
Rotate 180°Rotate 2 (Rotate2)2中心を基準とした点対称。拠点を対角の隅に配置するクラシックな 1v1 レイアウト。ミラーリングと異なり、回転では両プレイヤーにとって左右が同じになります。一方のプレイヤーの左側にある通路は、もう一方のプレイヤーにとっても左側にあります。
Rotate 90°Rotate 4 (Rotate4)490° ずつの回転対称。正方形のマップが必要です。クラシックな 4 人での個人戦(フリーフォーオール)。
DiagonalMirror Diagonal (MirrorDiagonal)2対角線を境にミラーリングします(x ↔ z)。正方形のマップが必要です。左上と右下の隅に拠点がある 1v1 マップ。

Centre は、正規化されたマップ座標で表した対称の中心です。(0.5, 0.5) がマップの中心で、デフォルト値です。Reset で中心を元に戻します。中心を移動すると、一部のコピーがマップの外に出てスキップされることがあり、その場合はカードに警告が表示されます。

Rotate 90° と Diagonal は X 軸と Z 軸を入れ替えます。正方形でないマップでは、コピーがマップの外に出たり、不均等になったりします。その場合、Symmetry カード、オーバーレイ、ステータスパネルに警告が表示されます。マップを正方形にリサイズするか、別のモードを選択してください。

コピーの動作

  • ほかのコピーとの距離が、半セルとブラシ半径の 1/10 のうち大きいほうより短いコピーは、1 つに統合されます。そのため、対称軸上でペイントしても効果が 2 回適用されることはありません。
  • 配置した木、装飾オブジェクト、開始地点、マーカーは、コピーに合わせて回転します。ミラーリングされたコピーは向きもミラーリングされます。
  • 回転・ミラーリングされたブラシやカスタムスタンプは、コピーに合わせて向きが変わります(回転の規則を参照)。
  • Start Locations:Symmetry Assigns Next Players がオン(デフォルト)の場合、1 回のクリックで選択したプレイヤーの開始地点が配置され、そのコピーで後続のプレイヤーの開始地点が配置されます。Rotate 90° で Player 1 を選択している場合は、1 回のクリックで Player 1~4 の開始地点が配置されます。
  • Raise / Lower Level は元のダブから目標レベルを計算するため、すべてのコピーが同じレベルをペイントします。
  • Replace Dominant Layer モードまたは Fill All モードの Fill / Replace はマップ全体に作用し、1 回だけ実行されます。Fill Connected は各コピーの位置から塗りつぶします。

マップの形(崖、スロープ、水、開始地点)は対称をオンにして作り、小さな装飾は対称をオフにして配置すると、マップがコピー&ペーストしたように見えません。通行オーバーレイ(P)を使うと、すべての拠点に同じ通路があるかをすばやく確認できます。

元に戻す・やり直し

地形の編集はすべてマップ独自のアンドゥ履歴に記録され、この履歴は Unity のアンドゥシステムと連動しています。Ctrl+Z、Ctrl+Y キーや Edit ▸ Undo メニューは、ほかの変更とまったく同じように地形の編集にも使えます。Edit メニューには Undo Raise / Lower のようにステップ名が表示されます。ウィンドウのヘッダーにある Undo ボタンと Redo ボタンも同じ動作をし、Unity の履歴との同期を保ちます。途中でほかのオブジェクトへの変更を元に戻しても、地形の履歴は乱れません。

操作アンドゥステップ
1 回のブラシストローク(押してから離すまで)または 1 回のクリックツール名の付いた 1 ステップ(たとえば Paint Layer)。すべての対称コピーが同じステップに含まれます。
装飾オブジェクトのハンドルのドラッグ1 ステップ(Move Decor、Rotate Decor、Scale Decor)。
Selection カードのボタン(Delete、Duplicate、Lock …)クリックごとに 1 ステップ。
Generate、ハイトマップのインポート、Auto Paint Whole Map、Capture From Sceneそれぞれ 1 ステップ(Generate Map、Import Heightmap、Auto Paint Map …)。
マップ情報と環境のフィールドフィールドからフォーカスを外したときに 1 ステップ(Edit Map Info、Edit Environment)。
Esc キーでキャンセルしたストロークステップなし。ストロークの変更は元に戻されます。
New Map、Resize、Open、Reload、別の地形への切り替え元に戻せません。これらの操作は履歴を消去します(各ダイアログにもその旨が表示されます)。

制限とメモリ

各ステップには、変更されたレイヤーだけが保存されます。ラスターレイヤー(高さ、崖レベル、水、スプラットマップ、ディテール、通行オーバーライド)は、編集した領域の周囲にある 16 × 16 セルのタイル単位で保存されます。オブジェクトレイヤー(木、装飾オブジェクト、リージョン、開始地点、マーカー、マップ情報、環境)はリスト全体のコピーとして保存されるため、木が非常に多いマップでは、木のストローク 1 回でもマップ全体の木の数に比例したメモリを消費します。そのため、高さやペイントの小さなストロークは数キロバイトで済みますが、マップ全体を生成するとマップのすべてのレイヤーが保存されます。ステータスバーと Undo History カードに、現在のメモリ使用量が表示されます。

Settings タブでは 2 つの制限を設定します。いずれかの制限を超えると古いステップから順に破棄されますが、最新のステップは常に保持されます。

設定範囲デフォルト説明
Max Steps10–1000100保持するアンドゥステップの数。
Memory Limit (MB)32–4096512アンドゥ履歴が使用できるメモリ量。

制限はユーザーごとにエディターの環境設定に保存され、ウィンドウを閉じている間も適用されます。Clear History は地形の元に戻す・やり直しのステップをすべて削除します。たとえば、大規模な生成の後にメモリを解放したい場合に使います。

独自のエディタースクリプトによる、元に戻せる編集

RTSEditSession.RunOperation は、マップ全体の指定したレイヤーを記録してからコードを実行し、その結果を 1 つのステップとして履歴に追加します。これにより、ほかのツールと同じように Ctrl+Z で元に戻せます。

using RTSTerrainStudio;
using RTSTerrainStudio.Editor;
using UnityEditor;

static class MyTerrainCommands
{
    [MenuItem("Tools/My Project/Raise Whole Map 1 m")]
    static void RaiseWholeMap()
    {
        // The terrain edited by the RTS Terrain Studio window and the Scene View tools.
        RTSTerrain terrain = RTSEditorContext.Terrain;
        if (terrain == null || terrain.Map == null) return;

        RTSEditSession session = RTSEditorContext.Session;

        // Records the Heights layer, runs the code and pushes one undo step named "Raise Whole Map".
        session.RunOperation("Raise Whole Map", MapLayers.Heights, ctx =>
        {
            var heights = ctx.Map.Heights; // fine height per vertex, on top of the cliff level
            for (int i = 0; i < heights.Length; i++)
                heights[i] = TerrainToolUtility.ClampFineHeight(ctx.Map, heights[i] + 1f);

            // Tell the terrain which layers changed so meshes, water and pathing are rebuilt.
            ctx.Terrain.NotifyChangedAll(MapLayers.Heights);
        });
    }
}

ゲーム内エディターも同じアンドゥ履歴の型(MapUndoStack)を使用し、独自の Undo ボタンと Redo ボタンを備えています。Unity の Ctrl+Z と連動するのは Unity エディター内だけです。

ツールリファレンス

以下のセクションでは、8 つのカテゴリに含まれるすべてのツールについて説明します。どのツールも、Unity エディターとゲーム内エディターで同じように動作します。すべてのツールに共通する決まりは次のとおりです:

  • ID は、ツール固有の ID です。たとえば RTSEditorContext.ActivateTool("height.raise") のように、コードで使用します。
  • 入力:ブラシツールはドラッグ中にペイントします。クリックツールはクリックごとに 1 回動作します。ドラッグツールは、ボタンを押す・ドラッグする・離すという一連の操作で動作します(矩形、ボックス選択)。
  • 繰り返し:マウスを動かさずにボタンを押し続けているときの、1 秒あたりのダブ数です。
  • 表に記載がある場合、Shift キーで効果が反転し、Ctrl+クリックすると値をスポイトで取得します。
  • 各設定には、範囲とデフォルト値を記載しています。設定はエディターのセッション間で保持されます。Settings タブの Reset All Tool Settings で、デフォルトに戻せます。

崖レベルと微細高さ

各頂点の地面の高さは、2 つの要素から成ります。崖レベル(0~15)は Cliffs ツールで設定し、垂直な崖の壁面を作ります。1 レベルの高さは Cliff Level Height メートル(デフォルトでは 2 m)です。微細高さは Height ツールで設定し、レベルの上に滑らかな丘やくぼみを加えます。微細高さの範囲は崖レベル ±8 段分(レベルの高さが 2 m の場合は ±16 m)に制限されます。この 2 つを分けて扱うからこそ、起伏のある地面の上に、くっきりとしたクラシック RTS スタイルの崖をペイントできます。

独自ツールの追加

RTSTool(または HeightTool などのカテゴリ基底クラスのいずれか)を継承し、パラメーターなしの public コンストラクターを持つクラスは、ウィンドウ、オーバーレイ、ゲーム内エディターに自動的に表示されます。[ToolSetting] が付いた public フィールドには UI が生成されます。次の例では、クリックごとに一定の段差だけ地面を上げ下げします:

using RTSTerrainStudio;

// Appears on the Height tab after the built-in tools (Order 100).
public sealed class StepTool : HeightTool
{
    public override string Id => "mygame.step";
    public override string DisplayName => "Step";
    public override string Description => "Raises the ground under the brush by a fixed step per click.";
    public override string Hint => "Shift: step down";
    public override int Order => 100;

    // One dab per click; keep the brush panel visible for the radius and falloff.
    public override ToolInputMode InputMode => ToolInputMode.Click;
    public override bool UsesBrush => true;

    [ToolSetting("Step", Min = 0.05f, Max = 4f, Order = 0, Tooltip = "Height change per click in metres at full strength.")]
    public float step = 0.5f;

    // Called once per click and once for every symmetry copy.
    public override void ApplyDab(RTSToolContext ctx, in BrushDab dab)
    {
        var map = ctx.Map;
        // Computes the vertex weights and records the Heights layer for undo.
        bool any = BeginVertexEdit(ctx, dab, out var w, out var cells);
        using (w) // the weights must always be disposed
        {
            if (!any) return;
            float sign = dab.invert ? -1f : 1f; // Shift inverts
            var heights = map.Heights;
            for (int z = w.rect.yMin; z < w.rect.yMax; z++)
            for (int x = w.rect.xMin; x < w.rect.xMax; x++)
            {
                float weight = w[x, z];
                if (weight <= 0f) continue;
                int i = map.VertexIndex(x, z);
                heights[i] = TerrainToolUtility.ClampFineHeight(map, heights[i] + sign * step * dab.strength * weight);
            }
        }
        // Rebuilds the chunks, water and pathing of the changed cells.
        EndVertexEdit(ctx, cells);
    }
}

ツール API の全容(ドラッグツール、スポイト、ギズモ、ステータステキスト)については、スクリプティングガイドを参照してください。Managed Stripping Level を高く設定したビルドでは、作成したツールに [UnityEngine.Scripting.Preserve] を付けてください(コードストリッピングとカスタムツールを参照)。

Height ツール

Height ツールは微細高さを造形し、崖レベルの上に丘、谷、道路、自然な細部を作り込みます。崖レベルは決して変更しないため、崖の壁面はくっきりしたままです。ステータス行には、カーソル位置の地面の高さ、微細高さ、崖レベルが表示されます。

ツールID機能入力と修飾キー
Raise / Lowerheight.raiseブラシの下の地面を上げます。ボタンを押し続けると、丘を盛り上げ続けます。ブラシ、30 回/秒で繰り返し。Shift:下げる。
Smoothheight.smooth崖の壁面をぼかすことなく、地面の凹凸や鋭い角を和らげます。ブラシ、20 回/秒で繰り返し。
Flattenheight.flatten地面を 1 つの高さにならします。建設用地や道路に最適です。ブラシ、20 回/秒で繰り返し。Ctrl+クリック:高さをスポイトで取得。
Set Heightheight.set地面を正確な高さに設定します。強さが最大のとき、ブラシの中心は 1 回のダブでその高さに達します。ブラシ、20 回/秒で繰り返し。Ctrl+クリック:高さをスポイトで取得。
Noiseheight.noiseシードに基づくプロシージャルノイズで、自然な凹凸、丘、または尾根を加えます。ブラシ、10 回/秒で繰り返し。Shift:ノイズを減算。
Terraceheight.terrace傾斜を、棚田や層状の岩のような、角の丸い平らな段々に変えます。ブラシ、15 回/秒で繰り返し。
Erodeheight.erode急な傾斜を土砂が滑り落ちる様子をシミュレーションし、丘に自然な風化した外観を与えます。ブラシ、15 回/秒で繰り返し。

Raise / Lower の設定

設定範囲デフォルト説明
Speed0.01–20.15ブラシの強さが最大のときの、1 ダブあたりの高さの変化量(メートル単位)。

Smooth の設定

設定範囲デフォルト説明
Filter Radius1–41平均化する近傍の範囲(頂点数)。値を大きくすると、より大きな起伏が取り除かれます。
Iterations1–821 ダブあたりのスムージングの反復回数。

Flatten の設定

設定範囲デフォルト説明
TargetStroke Start、Fixed HeightStroke StartStroke Start は、ストロークの開始時にカーソル位置にある高さを使用します。Fixed Height は Target Height の値を使用します。
Target Height−32–32 m0崖レベルを基準とした固定の微細高さ。Ctrl+クリックすると、スポイトで取得できます。マップの微細高さの上限(崖レベル ±8 段分、レベルの高さが 2 m の場合は ±16 m)を超える値は、適用時に上限内に収められます。
ModeFlatten、Raise Only、Lower OnlyFlatten両方向にならす、目標より低い地面だけを埋める、目標より高い地面だけを削る、のいずれかです。

Set Height の設定

設定範囲デフォルト説明
Height−32–64 m0設定する高さ。Height Space に従って解釈されます。Ctrl+クリックすると、スポイトで取得できます。マップの微細高さの上限(崖レベル ±8 段分、レベルの高さが 2 m の場合は ±16 m)を超える値は、適用時に上限内に収められます。
Height SpaceFine Height、Ground HeightFine Height崖レベルを基準とした微細高さ、または絶対的な地面の高さ。

Noise の設定

設定範囲デフォルト説明
Noise TypeFbm、Ridged、BillowFbmFbm:なだらかな丘。Ridged:鋭い稜線。Billow:丸みのある起伏。
Scale2–256 m24最も大きな起伏のサイズ。
Amplitude0.01–4 m0.25強さが最大のときに 1 ダブで加わる高さ。
Octaves1–84重ね合わせる細部の層の数。オクターブを増やすと、より細かな細部が加わります。
Roughness0.2–0.80.5オクターブ間の振幅の比率。値を大きくすると、地面が粗くなります。
Seed0–99991パターンを変更します。同じシードからは常に同じパターンが得られます。

Terrace の設定

設定範囲デフォルト説明
Step Height0.1–8 m1テラス 1 段分の高さ。
Sharpness0–10.70 = 柔らかく丸みのある段。1 = くっきりとした急な段差。
Offset−4–4 m0すべての段を上下にずらします。

Erode の設定

設定範囲デフォルト説明
Talus Angle5–70°30この角度より急な傾斜が侵食されます。
Erosion Strength0.05–10.51 回の反復で移動する余剰土砂の割合。
Iterations1–5081 ダブあたりの侵食の反復回数。

建設用地を作るには、Target = Stroke Start で Flatten を使用します。目的の高さの地点でストロークを開始し、そのまま範囲全体をペイントします。正確な高さの道路を作るには、ある地点を Ctrl+クリックしてその高さをスポイトで取得し、Target を Fixed Height に切り替えてからペイントします。

Cliffs ツール

Cliffs ツールは、頂点の崖レベル(0~15)と、レベル間にある崖の壁面の外観を変更します。ブラシは頂点にスナップしてハードセレクションとして動作するため、常に頂点単位でペイントされ、中途半端なレベルになることはありません。ステータス行には、カーソル下のレベルと、これからペイントするレベルが表示されます(例:Level 2 → 3)。

ツールID機能入力と修飾キー
Raise / Lower Levelcliff.raise地面を崖レベル 1 つ分上げます。ドラッグすると、ストロークを開始した台地を広げられます。ブラシ。Shift:1 レベル下げる。
Set Levelcliff.set指定した崖レベルを正確にペイントします。大きな台地、盆地、素早いブロックアウトに便利です。ブラシ。Ctrl+クリック:レベルをスポイトで取得。
Rampcliff.ramp崖の縁を、歩行可能な斜面に変えます。1 レベルの崖をまたぐようにペイントして、セルの 4 つの角すべてがスロープになるようにします。ブラシ。Shift:スロープを削除。
Cliff Typecliff.type崖の壁面のマテリアルを変更します。たとえば、草に覆われた崖の隣に岩の崖を作れます。ブラシ。Ctrl+クリック:崖タイプをスポイトで取得。
Clean Cliffscliff.smoothEdges孤立した 1 頂点だけの柱や穴を取り除き、崖の輪郭をきれいに整えます。ブラシ。

Raise / Lower Level の設定

目標レベルは、ストローク開始時にカーソル下にある頂点から決まり、その 1 レベル上(Shift キーを押している場合は 1 レベル下)になります。ドラッグ中は同じレベルをペイントし続けるため、レベルを積み重ねることなく台地を広げられます。このツールは頂点を上げるだけ(または下げるだけ)です。

設定範囲デフォルト説明
Break Rampsオン/オフオンレベルが変わった頂点からスロープフラグを外します。
Paint Cliff Edgesオン/オフオンストロークの終了時に、新しい崖の縁に沿って崖タイプの縁レイヤーをペイントします(崖の縁の自動ペイントを参照)。
Edge Width0.25–4 セル1崖の壁面から測った、ペイントする縁の幅。
Edge Strength0.1–10.9壁面のすぐ隣の地面を、縁レイヤーがどの程度強く覆うか。
Edge SidesBoth / Top / BottomBoth台地の縁、崖のふもと、またはその両方をペイントします。
Clean Up Old Edgesオン/オフオンストローク後に縁がなくなった場所(移動した崖、新しいスロープ)から縁レイヤーを取り除きます。

Set Level の設定

設定範囲デフォルト説明
Level0–15(パレット)2ペイントする崖レベル。
Break Rampsオン/オフオンレベルが変わった頂点からスロープフラグを外します。
縁の設定上記と同じPaint Cliff Edges、Edge Width、Edge Strength、Edge Sides、Clean Up Old Edges は、Raise / Lower Level と同じように動作します。Ramp と Clean Cliffs にも、同じ 5 つの設定があります。

Ramp

Ramp には設定がありません。セルの 4 つの角の頂点すべてにスロープフラグが付いていて、かつそのセルのレベル差がちょうど 1 の場合に、そのセルはスロープになります。ステータス行も参考になります。ブラシの下にある有効なスロープセルの数を表示し、フラグは付いているもののレベル差のないセルについて知らせ、スロープが 1 レベルを超えてまたがる場合(引き伸ばされてしまいます)は警告を表示します。

Cliff Type の設定

設定範囲デフォルト説明
Cliff Type0–3(パレット)0ペイントするタイルセットの崖レイヤー。

Clean Cliffs の設定

設定範囲デフォルト説明
Strictness5–88頂点を別のレベルに揃えるために、8 つの隣接頂点のうちいくつがそのレベルである必要があるか。8 にすると、完全に孤立した頂点だけが取り除かれます。値を小さくすると、ギザギザの角も丸められます。マップ境界上の頂点は隣接頂点が少ないため、その少ない隣接頂点のうち同じ割合が必要です。
Break Rampsオン/オフオンレベルが変わった頂点からスロープフラグを外します。

崖の縁の自動ペイント

クラシック RTS のマップエディターでは、崖を上げると、その隣の地面も変化します。Cliffs ツールも同じように動作します。ストロークが終わると、新しくできたすべての崖の縁に沿って、崖タイプの Edge Ground Layer(縁の地面レイヤー)で地面がペイントされます(たとえば、石の多い「Rocky Dirt」の縁取り)。この縁取りは丸みのある崖の輪郭に沿い、Edge Width の範囲でフェードアウトし、輪郭がわずかに不規則になっているため、セルの形が目立たず自然に見えます。

  1. タイルセットを選択し、Cliff Layers を開きます。各崖カードで Edge Layer を選択します(デモのタイルセットでは Rocky Dirt を使用しています)。
  2. Raise / Lower Level、Set Level、Ramp、Clean Cliffs のいずれかを選び、Paint Cliff Edges をオンのままにします。
  3. 通常どおり崖をペイントします。マウスボタンを離すと、縁取りが表示されます。
  • 1 アンドゥステップ:レベルの変更と縁のペイントは、まとめて元に戻されます。
  • 対称:すべての対称コピーにも、同じ処理で縁が付きます。
  • スロープ:スロープセルに縁取りが付くことはありません。Clean Up Old Edges がオンの場合、縁をまたいでスロープをペイントすると、その部分の縁取りが取り除かれます。
  • 移動した崖:Clean Up Old Edges がオンの場合、なくなった縁(下げた台地、広げた台地)の縁取りは再び取り除かれます。その場所に縁レイヤーを手作業でペイントしていた場合も、それごと取り除かれます。
  • 任意:ツールごとに Paint Cliff Edges をオフにするか、崖タイプの Edge Layer を None に設定してタイルセット全体で使わないようにできます。このオプションがオンなのに縁レイヤーが設定されていない場合は、ステータス行にその旨が表示されます。

崖レベルを自分で変更した場合(たとえばカスタムジェネレーター内)は、その後コードから CliffEdgePainter.PaintEdges(map, tileset, cellRect, CliffEdgeOptions.Default) を呼び出し、続けて terrain.NotifyChanged(MapLayers.Splat, returnedRect) を呼び出します。

典型的なワークフロー:Set Level で台地の大まかな形を作り、Raise / Lower Level で縁を調整し、輪郭に Clean Cliffs をかけてから、ユニットに登らせたい場所に Ramps を追加します。結果は通行オーバーレイ(P キー)で確認します。

Paint ツール

Paint ツールは、タイルセットの地面レイヤー(最大 16 個)をペイントします。スプラットの各テクセルはすべてのレイヤーのウェイトを保持しており、その合計は常に 100% になります。選択したレイヤーがタイルセットに存在しない場合やテクスチャがない場合は、代替のレイヤーで黙ってペイントするのではなく、ステータス行にその旨が表示されます。

ツールID機能入力と修飾キー
Paint Layerpaint.layer柔らかいブラシの縁で、地面テクスチャのレイヤーをペイントします。ブラシ、10 回/秒で繰り返し。Shift:レイヤーを消去。Ctrl+クリック:カーソル下のレイヤーをスポイトで取得。
Tile Paintpaint.tileクラシック RTS のマップエディターと同様に、セル単位で地面レイヤーをペイントします。中心がブラシの輪郭内にあるセルは、すべて 100% でペイントされます。ブラシ、セルの中心にスナップ。Shift:セルからレイヤーを削除。Ctrl+クリック:スポイトで取得。
Fill / Replacepaint.fillワンクリックで、地面レイヤーをマップ上のあらゆる場所で置き換えたり、領域を塗りつぶしたり、マップ全体を埋めたりします。クリック。Ctrl+クリック:スポイトで取得。
Smooth Paintpaint.smoothブラシの下にある地面レイヤーの境目をぼかし、より柔らかなブレンドにします。ブラシ、15 回/秒で繰り返し。
Auto Paintpaint.auto自動ペイントプロファイルを使用して、高さ、傾斜、崖、水に応じて地面レイヤーをペイントします。ブラシ、8 回/秒で繰り返し。Shift:プロファイルのベースレイヤーをペイント。

Paint Layer の設定

設定範囲デフォルト説明
Layerパレット0ペイントするタイルセットの地面レイヤー。
Opacity0.05–11レイヤーが到達できる最大の被覆率。値を小さくすると、レイヤーが下の地面とブレンドされます。

Tile Paint の設定

設定範囲デフォルト説明
Layerパレット0ペイントするタイルセットの地面レイヤー。
Soft Borderオン/オフオフペイントしたセルの周囲 1 テクセルをブレンドし、縁をわずかに柔らかくします。

Fill / Replace の設定

設定範囲デフォルト説明
Layerパレット0領域を埋める地面レイヤー。
ModeReplace Dominant Layer、Fill Connected、Fill AllFill ConnectedReplace Dominant Layer:優勢レイヤーがクリックしたレイヤーである、マップ上のすべてのテクセル。Fill Connected:クリックした優勢レイヤーを持つ、つながったテクセルの塗りつぶし。Fill All:マップ全体。
Preserve Blendsオン/オフオンクリックしたレイヤーだけを新しいレイヤーに置き換え、ほかのレイヤーとのブレンドは維持します(Replace と Flood Fill)。

1 回のクリックが 1 アンドゥステップになり、実際に変更された領域だけが記録されます。

Smooth Paint の設定

設定範囲デフォルト説明
Blur Radius1–31ブラーカーネルのサイズ(スプラットのテクセル単位)。値を大きくすると、より速く滑らかになります。

Auto Paint の設定

設定デフォルト説明
Profileなしブラシがペイントに使用する Auto Paint Profile アセット。空の場合は、タイルセットのレイヤー名に合わせた組み込みルールが使われます。詳しくは Auto Paint を参照してください。

Paint タブには Auto Paint Whole Map カードも表示されます。このカードでは、プロファイルに基づいてマップ全体を 1 ステップで再ペイントできます。

Vegetation ツール

Vegetation ツールは、地形の Vegetation Library を使って、草や小さな植物(ディテール)をペイントし、木を配置します。地形に植生ライブラリがない場合は、ツールページに Create Vegetation Library が表示されます。プロトタイプはグリッドで選択します。何も選択していない場合は、ライブラリの最初のプロトタイプが使用されます。

ツールID機能入力と修飾キー
Paint Detailsveg.details草、花、その他の小さな植物を、密度を指定してペイントします。ブラシ、10 回/秒で繰り返し。Shift:選択したディテールを消去。
Erase Detailsveg.detailsEraseブラシの下にある草やその他のディテール植生を取り除きます。ブラシ、10 回/秒で繰り返し。
Place Treesveg.treesブラシで木を散布するか、クリックで木を 1 本ずつ配置します。Scatter:ブラシ、Shift で木を削除。Single:クリック、Shift+クリックで最も近い木を削除。
Erase Treesveg.treesEraseブラシの下にある木を取り除きます。強さを下げると、ブラシの中心付近だけが消去されます。ブラシ。

Paint Details の設定

設定範囲デフォルト説明
Detailsプロトタイプグリッドなしペイントするディテールのプロトタイプ。
Target Density0–10.6ブラシがペイントで目指す密度(1 = プロトタイプの最大密度)。
ModeRaise、SetRaiseRaise は密度を上げるだけです。Set は、目標より密度の高い領域を目標まで間引きもします。

Erase Details の設定

設定範囲デフォルト説明
Only Selectedオン/オフオフすべてのディテールではなく、選択したディテールのプロトタイプだけを消去します。
DetailsプロトタイプグリッドなしOnly Selected がオンのときに消去するディテールのプロトタイプ。

Place Trees の設定

設定範囲デフォルト説明
Treesプロトタイプグリッドなしランダムに配置される木のプロトタイプ。
ModeScatter、SingleScatterScatter はブラシで木をペイントします。Single はクリックごとに木を 1 本配置し、ゴーストプレビューを表示します。
Density0.05–50.6強さが最大のとき、1 回のダブで 100 m² あたりに配置される木の本数。プロトタイプの最小間隔は引き続き適用されます。
Max Slope0–90°35散布する木は、これより急な傾斜には配置されません。
Avoid Waterオン/オフオン水中には木を散布しません。

Erase Trees の設定

設定範囲デフォルト説明
Only Selectedオン/オフオフ選択したプロトタイプの木だけを取り除きます。
TreesプロトタイプグリッドなしOnly Selected がオンのときに取り除く木のプロトタイプ。

Decor ツール

Decor ツールは、地形の Decor Library にある岩、遺跡、柵などのプロップを配置、消去、編集します。地形に装飾ライブラリがない場合は、ツールページに Create Decor Library が表示されます。プロトタイプグリッドには、検索フィールドとカテゴリフィルターがあります。

ツールID機能入力と修飾キー
Place Decordecor.placeプロップ、岩などの装飾オブジェクトを 1 つずつ配置するか、ブラシで散布します。Single:クリック。Shift+クリックで最も近いオブジェクトを削除します。Scatter:ブラシ。Shift キーでブラシの下の装飾オブジェクトを削除します。
Erase Decordecor.eraseブラシの下の装飾オブジェクトを削除します。ロックされたオブジェクトは残ります。強さを下げると、ブラシの中心付近だけが消去されます。ブラシ。
Select Decordecor.selectクリックまたは矩形のドラッグで装飾オブジェクトを選択し、移動、回転、拡大縮小、削除を行えるようにします。ドラッグ。クリック:選択。ドラッグ:ボックス選択。Shift キー:追加 / 切り替え。

Place Decor の設定

設定範囲デフォルト説明
ModeSingle、ScatterSingleSingle では、ゴーストプレビューを見ながらクリックごとに 1 つのオブジェクトを配置します。Scatter では、ブラシでオブジェクトをペイントします。
Decorプロトタイプグリッドなしランダムに配置する装飾プロトタイプ。何も選択されていない場合は、ライブラリの最初のプロトタイプが使用されます。
Density0.05–50.4Scatter:強さが最大のとき、1 回のダブで 100 m² あたりに配置されるオブジェクトの数。
Use Prototype Defaultsオン/オフオンランダムなヨー、地面への位置合わせ、グリッドへのスナップの設定をプロトタイプから取得します。
Random Yawオン/オフオン上方向の軸を中心としたランダムな回転(プロトタイプのデフォルトを使用しない場合)。
Yaw0–360°0Random Yaw がオフのときの、上方向の軸を中心とした回転。
Scale0.1–51プロトタイプのランダムなスケールに掛ける倍率。
Align To Ground0–100 = 直立、1 = 地面の傾斜に沿う(プロトタイプのデフォルトを使用しない場合)。
Snap To Gridオン/オフオフオブジェクトをセルの中心に配置します。
Random Variantオン/オフオンプロトタイプのプレハブのバリアントからランダムに 1 つを使用します。
Max Slope0–90°90Scatter:この値より急な傾斜には配置しません。90 = 無制限。
Avoid Waterオン/オフオフScatter:水中にはオブジェクトを配置しません。

Erase Decor の設定

設定範囲デフォルト説明
Only Selectedオン/オフオフ選択したプロトタイプのオブジェクトだけを削除します。
DecorプロトタイプグリッドなしOnly Selected がオンのときに削除する装飾プロトタイプ。

Select Decor の設定

設定範囲デフォルト説明
Pick Radius0.25–10 m1クリックでオブジェクトを選択できる最小の距離(大きなオブジェクトは、フットプリントの範囲内をクリックすれば選択されます)。
Select Lockedオン/オフオフロックされたオブジェクトを選択できるようにします。たとえば、ロックを解除するときに使用します。

Selection カード

Select Decor ツールがアクティブな間は、Decor タブに Selection カードが表示され、選択中のオブジェクトの数を確認できます。編集ボタン(Delete、Duplicate、Snap to Ground、Rotate 90°、Lock、Unlock)は、どれも 1 回の操作が 1 アンドゥステップになります。Select All と Clear は選択を変更するだけです。

ボタン操作
Select Allマップ上のすべての装飾オブジェクトを選択します(ロックされたオブジェクトは除外されます)。
Clear選択を解除します。
Delete選択したオブジェクトを削除します。
Duplicate選択中のオブジェクトを、斜めに 1 セルずらして(+X 方向と +Z 方向)複製します。複製されたオブジェクトが新たに選択され、ロックは解除された状態になります。
Snap to Ground選択したオブジェクトを地面の上に戻します。
Rotate 90°選択中のオブジェクトを、その中心を軸に時計回りに 90° 回転します。
Lock / Unlockロックされたオブジェクトは、ツールで選択したり消去したりできなくなります。選択してロックを解除するには、Select Locked をオンにします。この方法で選択した場合でも、ロックされたオブジェクトは Delete やハンドルの影響を受けません。

ハンドルによる移動、回転、拡大縮小については、装飾オブジェクトの選択ハンドルを参照してください。

Water ツール

水は頂点ごとに保存されます。各頂点は、水面の高さを持つ「水あり」か、「水なし」のどちらかの状態です。Water ツールは、Cliffs ツールと同様にハードセレクションを使用します。Water at Cursor カードには、マウスカーソル位置の水位と水深が表示されます。また、タイルセットの浅水域の上限に基づいて、その地点が浅水域(歩行可能)と深水域のどちらと見なされるかも表示されます。

ツールID機能入力と修飾キー
Paint Waterwater.paint目標の高さで湖や川をペイントします。水面より高い地面は水なしのままです。ブラシ。Shift キー:水を消去。Ctrl+クリック:高さをスポイトで取得。
Erase Waterwater.eraseブラシの下の頂点から水を削除します。ブラシ。
Flood Fillwater.floodカーソルの下のくぼ地を、1 回のクリックで目標の高さまで水で満たします。クリック。Shift+クリック:つながっている水を排水。Ctrl+クリック:高さをスポイトで取得。

水の高さの設定(Paint Water と Flood Fill)

設定範囲デフォルト説明
Water HeightCliff Level Plus Offset、Absolute Height、Picked HeightCliff Level Plus Offset水面の高さの決め方。
Level0–15(パレット)1水面の基準となる崖レベル(Cliff Level Plus Offset モード)。
Offset−8–16 m0.6 レベル崖レベルからの高さ(メートル単位)。値を変更するまでは、マップのレベルの高さの 0.6 倍(レベルの高さが 2 m なら 1.2 m)に追従します。
Absolute Height−16–64 m3.2マップローカル空間でのメートル単位の水面の高さ(Absolute Height モード)。
Picked Height−16–64 m3.2Ctrl+クリックでスポイトした高さ(Picked Height モード)。スポイトした地点に水があれば水面の高さを、なければ地面の高さを取得し、モードを Picked Height に切り替えます。

Paint Water の設定

設定範囲デフォルト説明
Forceオン/オフオフ地面が水面より高い頂点にも水のフラグを付けます。たとえば、滝や沼地に使用します。

Flood Fill の設定

設定範囲デフォルト説明
Max Vertices100–1,000,00020,000安全のための上限。これより多くの頂点に及ぶ塗りつぶしや排水は、何も変更せずにキャンセルされます。

デフォルト値(レベル 1 + 0.6 レベル)では、新規マップのベースレベル 2 より 1 レベル低く掘った湖が水で満たされます。Raise / Lower Level ツールで Shift キーを押しながらくぼ地を掘り、Flood Fill ツールでその中をクリックします。

Pathing ツール

通行マップ(歩行可能、建設可能、飛行可能、水)は、傾斜、崖、水、地面レイヤー、木、装飾オブジェクトから自動的に生成されます。Pathing ツールでは、通行セル単位のハードセレクションを使って、その上に手動のオーバーライドをペイントします。結果を確認するには、Pathing Overlay カードまたは P キーでオーバーレイをオンにします。

ツールID機能入力と修飾キー
Paint Pathingpathing.paintブロッカー、建設禁止ゾーン、強制的な歩行可能エリアなど、目に見えない通行ルールをペイントします。ブラシ。Shift キー:選択したオーバーライドを削除。
Clear Pathingpathing.clearペイントした通行オーバーライドをすべて削除し、自動生成される通行判定が再び適用されるようにします。ブラシ。

Paint Pathing の設定

設定範囲デフォルト説明
Override以下を参照Blockerペイントする通行オーバーライド。
オーバーライド効果
Blocker歩行不可かつ建設不可。目に見えない壁で、マップ境界や装飾用のエリアによく使われます。
Unwalkable地上ユニットはここを歩けません。
Unbuildableここには建物を配置できません。たとえば、通路や拡張拠点を空けておくために使います。
Unflyable空中ユニットはここを飛べません。
Force Walkable傾斜、水、フットプリントによってブロックされる場合でも、地上ユニットはここを歩けます。
Force Buildable自動生成されたルールでは禁止される場合でも、ここには建物を配置できます。

通行オーバーレイの色

オーバーレイ色
Walkability緑 = 歩行可能、赤 = ブロックされている、暗い赤 = 崖、オレンジ = 傾斜が急すぎる、青 = 深水域、シアン = 浅水域。
Buildability緑 = 建設可能、黄 = 歩行可能だが建設不可、赤 / 暗い赤 = 歩行不可。
Flyability水色 = 飛行可能、マゼンタ = 飛行不可。
Waterシアン = 浅水域、青 = 深水域。
Combinedすべてを同時に表示します。クラシック RTS のマップエディターの通行表示と同様です。

自動生成部分に使われるルール(最大傾斜角、崖マージンなど)は、RTS Terrain コンポーネントで設定します。詳しくは Pathing Rules を参照してください。

Markers ツール

Markers ツールは、プレイヤーの開始地点、トリガー用の名前付きリージョン、カメラ位置などの名前付きポイントといったゲームプレイ情報をマップに追加します。マーカーは、Markers ツールがアクティブなとき、または Show Markers がオンのときにシーンビューに描画されます。

ツールID機能入力と修飾キー
Start Locationsmarkers.startプレイヤーのスポーン地点を配置します。もう一度クリックすると、スポーン地点がクリックした位置に移動します。クリック。Shift+クリック:最も近い開始地点を削除。
Regionsmarkers.region矩形をドラッグして、ゲームロジック用の名前付きリージョンを作成します。ドラッグ。Shift+クリック:カーソルの下のリージョンを削除。
Markersmarkers.markerカメラやウェイポイントなど、向きを持つ名前付きポイントを配置します。クリック。Shift+クリック:最も近いマーカーを削除。

Start Locations の設定

設定範囲デフォルト説明
PlayerPlayer 1–16(パレット)Player 1 (Red)開始地点を配置するプレイヤー。各プレイヤーが持てる開始地点は 1 つです。
Yaw0–360°0開始地点の向き。
Symmetry Assigns Next Playersオン/オフオン対称が有効な場合、対称コピーによって後続のプレイヤーの開始地点が配置されます。オフにすると、元のクリックだけが開始地点を配置または移動します。

Regions の設定

設定範囲デフォルト説明
Color色半透明の青新しいリージョンの色。
Snap To Cellsオン/オフオンリージョンの境界をセルグリッドにスナップします。

新しいリージョンには、一意の名前が自動的に付けられます。

Markers の設定

設定範囲デフォルト説明
NameテキストMarker新しいマーカーの名前。名前がすでに使われている場合は、末尾に番号が付加されます。
Tagテキスト空ゲームのコードでマーカーを検索するときに使う任意のテキスト(たとえば camera や waypoint)。
Yaw0–360°0新しいマーカーの向き。

Auto Paint

Auto Paint は、地形の形状に基づいて地面レイヤーをペイントします。平らな地面には草、急斜面や崖沿いには岩、岸辺には砂を塗り、ノイズで不規則に散らした土のパッチを加えます。スカルプトしたばかりのマップや生成したばかりのマップを完成した見た目にする、最も手早い方法です。その後、手作業で調整できます。

使い方は次の 3 通りです。

  • Paint タブの Auto Paint ブラシツールは、ブラシの範囲内にルールを適用してペイントし、ブラシのウェイトと強さに応じて既存のペイントとブレンドします。Shift キーを押すと代わりにベースレイヤーがペイントされ、ルールによるペイントが取り除かれます。
  • Paint タブの Auto Paint Whole Map カードは、マップ上のすべての地面テクセルを 1 アンドゥステップで塗り直します。このカードには Profile フィールドと Create Profile ボタンがあります。
  • プロファイルのインスペクターにある Apply To Map ボタンと、マップジェネレーターの Paint ステージ。

プロファイルがない場合は、組み込みのデフォルトルールが使用されます。デフォルトルールは、タイルセットの地面レイヤーの名前に基づいて使用するレイヤーを選びます。

Auto Paint プロファイル

  1. Paint タブの Auto Paint Whole Map カードで Create Profile をクリックします。地形のタイルセットに合わせたデフォルトルールを持つプロファイルが作成され、割り当てられます。Assets ▸ Create ▸ RTS Terrain Studio ▸ Auto Paint Profile から作成することもできます。
  2. プロファイルを選択します。インスペクターで Preview Tileset を割り当てると、ルールカードにレイヤー名とサムネイルが表示されます。これは表示にのみ使用され、プロファイル自体はどのタイルセットでも機能します。
  3. ルールを編集し、Apply To Map をクリックして、選択中または編集中の地形を塗り直します。地形が複数あり、どれも選択されていない場合は、メニューから 1 つを選択できます。
  4. Use In Auto Paint Tool をクリックすると、ブラシツールがこのプロファイルでペイントするようになります。
プロファイル設定デフォルト説明
Base Layer0ルールを適用する前に、すべてのテクセルを覆うレイヤー。
Start From Existingオフベースレイヤーの代わりに、現在のスプラットウェイトから開始します。これにより、どのルールにも該当しない場所では手作業のペイントが保持されます。
Rules–上から順に適用され、後のルールが前のルールの上にペイントされます。各ルールカードには、有効化トグル、名前、レイヤー、並べ替え用の ▲ / ▼、複製ボタンと削除ボタン、Conditions 折りたたみがあります。リストの下には Add Rule と Reset To Defaults があります。

Auto Paint は、スプラットテクセルごとにベースレイヤー 100%(または既存のウェイト)から開始し、有効なルールを上から順に適用して、結果を正規化します。ルールの有効な各条件は 0~1 のウェイトを返し、範囲の境界では滑らかに遷移します。これらのウェイトを掛け合わせ、ノイズで不規則にし、ルールの強さでスケーリングします。

ルールの設定

設定デフォルト説明
NameRuleルールの表示名。
Layer0このルールがペイントする地面レイヤー(タイルセットのインデックス)。
Enabledオン無効なルールはスキップされます。
Use Height、Min Height、Max Heightオフ、0、20マップローカル空間での地面の高さ(メートル)が範囲内の場所にだけペイントします。
Height Falloff0.5高さの範囲外での滑らかな遷移の幅(メートル単位、0 = くっきりした境界)。
Use Slope、Min Slope、Max Slopeオフ、0、90傾斜角(度)が範囲内の場所にだけペイントします。
Slope Falloff5傾斜角の範囲外での滑らかな遷移の幅(度)。
Use Cliff Level、Min Cliff Level、Max Cliff Levelオフ、0、15これらの崖レベルにだけペイントします(くっきりした境界)。
Near Cliff Distance0崖の壁面からこの距離(セル単位)以内にだけペイントします(0 = オフ)。外側の半分でフェードアウトします。
Near Water Distance0水からこの距離(セル単位)以内にだけペイントします(0 = オフ)。外側の半分でフェードアウトします。
Noise Scale16ノイズの模様の大きさ(メートル単位)。
Noise Amount0ノイズがルールをどの程度不規則にするか(0 = ノイズなし、1 = パッチ状)。
Noise Seed0シードが異なるルールは、異なるパッチを生成します。
Strength1ルール全体の強さ(0–1)。
Blend ModeReplace上にあるルールとの合成方法(以下を参照)。
ブレンドモード効果
Addルールのレイヤーを上に加算します。結果を正規化するときに、既存のレイヤーは比例して減らされます。
Replaceルールのウェイトに応じて、すべてのレイヤーをルールのレイヤーに近づけるようにブレンドします。通常はこれを選択します。
Multiplyルールのレイヤーの既存のウェイトにルールのマスクを掛けます。これにより、ルールに該当しない場所ではそのレイヤーが取り除かれます。

デフォルトルール

組み込みのルールは、名前に grass、meadow、green のいずれかを含むレイヤーをベースレイヤーとして使用し、同様のキーワードで dirt、rock、sand の各レイヤーを探します(タイルセットがない場合はレイヤー 0~3)。

ルールペイント対象条件
Dirt Patchesdirt レイヤーノイズによるパッチ(スケール 14 m)、強さ 0.55。
Sand Shoresand レイヤー水から 3 セル以内。ノイズでわずかに不規則にします。
Rock Slopesrock レイヤー傾斜角 28° 以上(減衰 8°)。
Rock Near Cliffsrock レイヤー崖の壁面から 1.6 セル以内。ノイズで不規則にし、強さは 0.85。

コードからの Auto Paint

using RTSTerrainStudio;
using UnityEngine;

// Repaints the whole map with a profile, for example after your own procedural edits.
public class RepaintWithProfile : MonoBehaviour
{
    public RTSTerrain terrain;
    public RTSAutoPaintProfile profile;

    void Start()
    {
        if (terrain == null || terrain.Map == null || profile == null) return;

        // Evaluates the rules for every splat texel inside the rectangle (here: the whole map).
        RTSAutoPaint.Apply(terrain.Map, profile, terrain.Map.CellBounds);

        // Let the terrain upload the new splat maps and update dependent systems (pathing ground rules).
        terrain.NotifyChangedAll(MapLayers.Splat);
        terrain.ProcessPendingChanges();
    }
}

RTS Terrain コンポーネント

RTS Terrain コンポーネントは 1 つのマップを表示します。地形メッシュをチャンク単位で構築し、タイルセットで地面と崖をレンダリングし、通行マップを常に最新の状態に保ちます。また、ワールド位置での高さ、水、通行判定などのクエリも提供します。GameObject ▸ RTS Terrain Studio ▸ RTS Terrain で作成するか、Add Component ▸ RTS Terrain Studio ▸ RTS Terrain で任意のゲームオブジェクトに追加します。

このメニュー項目を使うと、すぐに編集できる地形が作成されます。作成されるのは、RTS Terrain、Vegetation Renderer、Water Renderer、Decor Manager の各コンポーネントを持つ RTS Terrain ゲームオブジェクトです。プロジェクトのデフォルトのタイルセットとライブラリが設定され、アクティブなシーンと同じフォルダー(または Assets/Maps)に新しい平らな .rtsmap ファイルが作られます。プロジェクトにまだタイルセットがない場合は、先にデモコンテンツを作成するよう提案されます。

インスペクター

領域内容
Headerマップ名、ワールドサイズ、セルサイズ、マップアセット、Unsaved changes バッジ、およびライブ統計(Cells、Chunks、Map Memory、Trees、Decor、Detail Layers、Regions、Start Locations、Markers)。
ActionsOpen RTS Terrain Studio、Edit Terrain(この地形のシーンビューでの編集のオン/オフを切り替えます)、Save、Save As…、Reload。
Warnings検出された問題を表示し、可能な場合はワンクリックで修正できます。対象となる問題:URP が有効でない、シェーダーが見つからない、タイルセットがない(マップの作成時に使われたタイルセットを使用するボタン付き)、テクスチャ配列が未構築または古い(Build)、マップが別のタイルセット用に作られている、マップに植生や装飾オブジェクトがあるのにライブラリがない、地形が X 軸または Z 軸を中心に回転している(Fix)またはスケールされている(Reset Scale)、サブシステムのコンポーネントが不足している(Add)。
AssetsMap、Tileset、Vegetation、Decor の各フィールド。それぞれに、新しいアセットを作成して割り当てる New ボタンがあります。
Chunks & Geometry、Cliff Shape、Physics、Rendering地形の設定(以下の表を参照)。
Pathing Rules通行マップの生成方法(以下の表を参照)。変更後は自動的に再構築されます。
Default Mapマップアセットが割り当てられていないときに作成されるマップのサイズとデフォルト値。
Rebuild Terrainすべてのチャンク、テクスチャ、通行マップを破棄して作り直します。植生、水、装飾オブジェクトも再構築されます。コードから設定を変更した後や、表示が古いままに見えるときに使用します。

同じインスペクターが、ウィンドウの Settings タブの Terrain Component にも組み込まれています。タイルセットとテクスチャ配列の割り当てについてはタイルセットを、マップファイルについてはマップアセットとマップファイルを参照してください。

地形は、トランスフォームの Y 軸回転だけを使用し、スケールは無視します。マップを大きくするには、Resize(RTSTerrain.ResizeMap)でセルを追加するか、より大きな Cell Size(MapCreationSettings.cellSize)で新しいマップを作成します。

地形の設定

これらのフィールドは RTSTerrainSettings(プロパティ RTSTerrain.Settings)に属します。

設定デフォルト説明
Chunks & Geometry
Chunk Size32チャンクの 1 辺あたりのセル数(16、32、64 のいずれか)。チャンクが小さいほど編集中の再構築が速くなり、大きいほどドローコールが少なくなります。
Surface Subdivisions2平滑な地面での、セルの 1 辺あたりのクアッド数(1–4)。1 = クラシック RTS の見た目、2–4 = より滑らかな丘。
Cliff Subdivisions4崖セル内で丸みのある崖の輪郭線をたどるために使うサブグリッドの解像度(2–8)。
Cliff Shape
Cliff Noise0.18崖の壁面の頂点に加えるランダムな水平方向のずれ。セルに対する割合で指定します(0–0.5)。自然な岩の見た目になります。
Cliff Noise Scale1.6崖のノイズのスケール(ワールド単位、最小 0.1)。
Cliff Slant0.12壁面をわずかに傾斜させるために、崖の上端をどれだけ後退させるか。セルに対する割合で指定します(0–0.4)。
Cliff Wall Segments3壁面のジオメトリで、崖レベル 1 つあたりに使う垂直方向のセグメント数(1–8)。
Physics
Collider ModeMesh ColliderNone:コライダーなし。クエリには地形のレイキャストを使用します。Mesh Collider:チャンクごとに 1 つの MeshCollider を持ち、編集後に再構築されます。
Chunk LayerDefault (0)チャンクのゲームオブジェクトのレイヤー。コライダーとカメラで使用されます。
Rendering
Cast ShadowsOnチャンクが影を落とすかどうかと、その方法(シャドウキャストモード)。
Receive Shadowsオンチャンクが影を受けるかどうか。
Material Overrideなし任意で指定するマテリアル。空の場合は、RTS Terrain Studio/Terrain シェーダーを使用するマテリアルが作成されます。
Show Gridオフセルグリッドを表示します。再生モードやビルドでも表示されます。エディターの G キーによる切り替えはこれとは別で、編集時にのみ影響します。
Grid Opacity0.35グリッドの不透明度(0–1)。

Pathing Rules

これらのフィールドは RTSPathingSettings(プロパティ RTSTerrain.PathingSettings)に属します。傾斜、崖、水、地面レイヤーがどのように歩行可能エリアや建設可能エリアになるかを決定します。Pathing ツールでペイントしたオーバーライドは、その上に適用されます。

設定デフォルト説明
Max Walkable Slope42°この角度より急な地面は歩行できません。
Max Buildable Slope18°この角度より急な地面には建設できません。
Cliff Margin0.25ブロックされた領域が崖の壁面からどこまで広がるか。通行セルに対する割合で指定します(0–1)。値を大きくすると、ユニットが崖の縁からより離れた位置にとどまります。
Shallow Water Buildableオフ浅水域での建物の建設を許可します。
Tree Footprint Scale1すべての木の、通行判定用フットプリント半径に掛ける倍率。
Use Ground Layer Rulesオン優勢な地面レイヤーの通行効果を適用します。たとえば、荒れた岩の上は Unbuildable になります。

Default Map

マップアセットが割り当てられていないときに作成されるマップです。Map タブの New Map カードも、これらの値を初期値として使用します。

設定デフォルト説明
Width / Height128 / 128セル単位のサイズ。16 の倍数(16–1024)。
Cell Size21 セルのサイズ(ワールド単位)。
Cliff Level Height2崖レベル 1 段分のワールド空間での高さ。
Base Cliff Level2すべての頂点の初期崖レベル(0–15)。
Base Ground Layer0最初にマップ全体を覆う地面レイヤー。
Splat Resolution41 セルあたりのスプラットテクセル数(1、2、4、8 のいずれか)。
Detail Resolution21 セルあたりのディテール密度テクセル数(1、2、4 のいずれか)。
Pathing Resolution2マップセル 1 つあたりの通行セル数(1、2、4 のいずれか)。

サブシステムコンポーネント

地形が描画するのは地面と崖だけです。マップのほかの要素は、同じゲームオブジェクト上、またはシーン内の別の場所にあるコンポーネントによって表示されます。以下の表の最初の 4 つのいずれかが欠けている場合、地形のインスペクターに警告が表示され、Add ボタンで追加できます。

コンポーネント作成メニューによる追加目的
Vegetation Rendererはいペイントした草や植物、および木を、GPU インスタンシングで、またはプレハブのインスタンスとしてレンダリングします。変更された領域だけを再生成します。
Water Rendererはいタイルセットの水プロファイルに沿った見た目で、水面をチャンクごとに構築してレンダリングします。最良の見た目を得るには、URP アセット(またはカメラ)で Depth Texture と Opaque Texture を有効にしてください。
Decor Managerはいマップの装飾オブジェクトを表示し、高さが変わっても地面に接した状態を保ちます。
EnvironmentNew Map Wizard で追加(任意)、または GameObject ▸ RTS Terrain Studio ▸ Environmentマップの太陽、環境光、フォグをシーンに適用します。無効にすると元のシーンのライティングが復元され、適用した設定がシーンファイルに書き込まれることはありません。
Fog of War ManagerGameObject ▸ RTS Terrain Studio ▸ Fog of War Managerローカルチームの戦場の霧を計算します。1 シーンにつき 1 つ配置します。
Pathfinding ServiceAdd Component ▸ RTS Terrain Studio ▸ Pathfinding Service、または RTSPathfindingService.GetOrCreate() によって必要に応じて作成地形の通行マップ上で、組み込みの経路探索を実行します。

コードからの地形設定の変更

using RTSTerrainStudio;
using UnityEngine;

public class TerrainQualityOptions : MonoBehaviour
{
    public RTSTerrain terrain;

    // Called from a graphics options menu, for example.
    public void SetSmoothHills(bool smooth)
    {
        // Settings fields can be changed directly, but the chunks are only rebuilt on request.
        // Use 1, 2 or 4: an odd value such as 3 rounds Cliff Subdivisions up to a multiple of 6 (expensive).
        terrain.Settings.surfaceSubdivisions = smooth ? 2 : 1;
        terrain.RebuildAll();
    }

    public void UseStricterSlopes()
    {
        // Assigning new pathing settings rebuilds the pathing map automatically.
        RTSPathingSettings rules = terrain.PathingSettings.Clone();
        rules.maxWalkableSlope = 35f;
        rules.maxBuildableSlope = 12f;
        terrain.PathingSettings = rules;
    }
}

新しいインスタンスを割り当てる代わりに、現在の PathingSettings インスタンスのフィールドを変更した場合は、その後で terrain.RebuildPathing() を呼び出してください。

ゲーム内エディター

ゲーム内エディターは、RTS Terrain Studio のマップエディターをビルドしたゲームに組み込みます。プレイヤーや MOD 制作者は、クラシック RTS ゲームに付属していたマップエディターと同じように、Unity なしでマップを作成、ペイント、保存、読み込み、テストプレイできます。UI Toolkit 上で動作し、Unity のエディターウィンドウと同じツール(Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers)を使用して、同じ .rtsmap ファイルを書き出します。ゲーム内で作成したマップは Unity プロジェクトにコピーしてそのまま開くことができ、その逆も可能です。

ゲームビューで実行中のゲーム内マップエディター
ゲーム内エディター:上部にトップバー、左側にツールカテゴリとツール、右側にブラシとツール設定、下部にステータスバー。

すべての機能は 1 つのコンポーネント RTSRuntimeEditor にまとめられています。RTS Terrain とカメラがあるシーンにこのコンポーネントを追加して Play をクリックするだけで、エディターが動作します。この章では、セットアップ、インターフェースの各部、すべてのキーボード操作とマウス操作、Play Test ボタン、ゲームのコードがそれにどう反応するか、マップの保存場所、UI のスタイルの変更方法、リリース前に確認すべき点について説明します。

Unity エディターでは、Shortcuts & Help ウィンドウ(F1 キー、またはトップバーの ? ボタン)の Open Manual ボタンでこの章が開きます。ビルドでは、このボタンは Manual Url に入力したページ(たとえば独自の MOD 制作ガイド)を開きます。このフィールドが空の場合、ビルドではボタンが非表示になります。

2 分で試す

ゲーム内エディターを最も手早く試すには、このパッケージに付属のデモシーンを使います。

  1. Tools ▸ RTS Terrain Studio ▸ Create Demo Content を選択します。デモ用のテクスチャ、ライブラリ、マップ、シーンが生成されます。この操作は 1 回だけ行えば十分です。
  2. プロジェクトウィンドウで Samples/Scenes/RTS Terrain Studio - In-Game Editor Demo を開きます。このシーンには、Sandbox マップ、RTS In-Game Editor オブジェクト、RTS Demo Game オブジェクト、戦場の霧マネージャーが含まれています。
  3. Play をクリックします。エディターがウェルカムヒント付きで開きます。左側でツールを選び(デフォルトでは、高さを変える Raise / Lower ツールが選択されています)、マウスの左ボタンで地形をペイントします。
  4. ペイント中に Shift キーを押したままにすると、地面が下がります。[ キーと ] キーでブラシサイズを変更します。マウスの右ボタンでドラッグするとカメラが移動し、マウスホイールでズームします。
  5. F5 キーを押します(またはトップバーの Play Test をクリックします)。デモゲームが開始地点にユニットを出現させ、戦場の霧を有効にします。ユニットを選択して右クリックすると、ユニットが移動します。
  6. Esc キーまたは F5 キーを押すとエディターに戻ります。マップはテストプレイ前とまったく同じ状態に戻ります。
  7. Ctrl+S キーを押し、名前を入力して Enter キーを押すと、マップが保存されます。

シーンへのゲーム内エディターの追加

動作要件

  • マップを持つ RTS Terrain がシーン内にあること(GameObject ▸ RTS Terrain Studio ▸ RTS Terrain)。
  • カメラがあること。デフォルトでは、エディターは Camera.main(MainCamera タグが付いたカメラ)を使用します。任意のカメラを割り当てることもできます。
  • このパッケージの Resources/RTSTerrainStudio/UI フォルダーがあること。このフォルダーには UI テーマとスタイルシートが含まれています。

手順

  1. シーンを開き、GameObject ▸ RTS Terrain Studio ▸ In-Game Editor を選択します。RTSRuntimeEditor コンポーネントを持つ RTS In-Game Editor という名前のオブジェクトが作成されます。ヒエラルキーでオブジェクトを右クリックし、そこに表示されるメニューを使うと、新しいオブジェクトはそのオブジェクトの子になります。
  2. RTS Terrain がないという警告がコンソールに表示された場合は、RTS Terrain を追加します。MainCamera タグが付いたカメラがないという警告が表示された場合は、カメラにそのタグを付けるか、カメラを Target Camera に割り当てます。
  3. 必要に応じて、地形とカメラを明示的に割り当て、ほかの設定を調整します(インスペクターリファレンスを参照)。
  4. Play をクリックします。エディターが表示されます。Start Visible をオフにした場合は、F2 キーを押して開きます。

このメニュー項目は再生モードでは無効です。シーンにすでにゲーム内エディターがある場合、Unity は新しく作成する(Create Another)か、既存のものを選択する(Select Existing)かを尋ねます。エディターは 1 シーンに 1 つあれば十分です。このコンポーネントは Add Component ▸ RTS Terrain Studio ▸ In-Game Editor から追加することもでき、同じゲームオブジェクトに追加できるのは 1 つだけです。

コンポーネントがランタイムで作成するもの

UI やカメラリグを自分で構築する必要はありません。RTSRuntimeEditor は、有効になると次のものを作成します。

オブジェクト目的
RTSEditSession編集セッションです。Session としてアクセスできます。ツール、ブラシ、対称設定、選択、アンドゥ履歴を保持します。Unity のエディターウィンドウが使用するのと同じクラスです。
子オブジェクト RTS In-Game Editor UIエディターのインターフェース全体を含む UIDocument を保持します。コンポーネントが無効になると、この子オブジェクトは破棄されます。
RTSRuntimeEditorCameraUse Built In Camera がオンのとき、ターゲットカメラに追加されます。カメラにすでにこのコンポーネントがある場合は、それが使用されます。エディター自身が追加したカメラコンポーネントは、エディターが破棄されると削除されます。
RuntimeToolGizmosゲームビューに、ブラシのアウトライン、選択ボックス、装飾オブジェクトのプレビュー、開始地点、対称ガイドを描画します。Gizmos としてアクセスできます。
PanelSettingsPanel Settings を空のままにした場合に作成されます。RTS テーマを使用し、画面に合わせてスケーリングされます。画面サイズに合わせてスケーリングし、テクスチャにはレンダリングしないパネル設定を割り当てた場合は、Enforce Minimum UI Scale がオフでない限り、エディターは代わりにそのランタイムコピーを使用します(UI のカスタマイズを参照)。

地形とカメラの検出方法

  • 地形:割り当てられた地形は、有効になるとすぐに使用されます。フィールドが空の場合、エディターはシーン内の最初の RTS Terrain である RTSTerrain.Main を使用します。地形が見つからない間、エディターは 1 秒間に 2 回再確認し、No RTS Terrain found という通知を表示します。
  • 複数の地形:シーンに RTS Terrain が 2 つ以上ある場合、Map メニューの Edited terrain にそれらが一覧表示されます。別の地形に切り替えると、アンドゥ履歴が消去されます。
  • カメラ:Target Camera が空の場合、エディターは Camera.main を使用し、それを探し続けます。カメラが存在するまで、No camera found という通知を表示します。

インスペクターリファレンス

設定デフォルト説明
Scene
Terrain空編集する地形。空の場合は、シーン内の最初の RTS Terrain(RTSTerrain.Main)を使用します。
Target Camera空ピッキング、ギズモ、組み込みのエディターカメラに使用するカメラ。空の場合は Camera.main を使用します。
Use Built In Cameraオンターゲットカメラに RTSRuntimeEditorCamera を追加し、エディターの表示中はカメラを制御します。テストプレイ中はオフになります。編集中に独自のカメラスクリプトでカメラを動かす場合は、この設定をオフにしてください。
Behaviour
Start Visibleオンシーンの開始時にエディターを表示します。オフの場合は、プレイヤーが切り替えキーで開くか、コードから EditorVisible を設定します。
Toggle KeyF2エディター全体の表示・非表示を切り替えます。None を選択するとキーが無効になります。その場合はトップバーの閉じるボタンも非表示になるため、ゲーム側でエディターを開閉する必要があります。ただし、Web ブラウザーやスマートフォン、タブレットでは、閉じるボタンは表示されたままです。これらの環境では、Open Map Editor ボタンでエディターを再表示できるためです。
Start Toolheight.raise開始時に有効になるツールの ID。たとえば paint.layer、veg.trees、decor.place、markers.start です。存在しない ID の場合は、最初のツールが使用されます。
Show First Time Hintオンエディターを初めて開いたときにウェルカムヒントを表示します。プレイヤーが Got it をクリックするか、ヒントを閉じるまで表示されます。この選択は PlayerPrefs に保存されます。
Show Quit Commandオンアプリケーションを終了する File ▸ Quit を追加します。Unity エディターでは再生モードを停止します。WebGL、iOS、Android では表示されません。
Confirm Quit With Unsaved Changesオンマップに未保存の変更があるとき、アプリケーションの終了前に確認します。終了要求がエディターの外部(ウィンドウの閉じるボタン、Alt+F4 キー、Application.Quit)から来た場合も同様です。その際は、まずエディターが表示され、実行中のテストプレイは停止されます。Web ビルドではアプリケーションを終了できません。代わりに、ページを閉じたり再読み込みしたりする前に、ブラウザーが Leave site? と確認します。
Undo Memory MB512アンドゥ履歴が使用できるメモリの上限(メガバイト単位、最小 16)。上限に達すると、最も古いステップから破棄されます。また、履歴に保持されるのは最大 100 ステップです。ブラウザーではゲーム全体で容量の限られた 1 つのメモリヒープを共有するため、Web ビルドで使用されるのは最大 192 MB です。
Manual Url空ビルドで、ヘルプウィンドウの Open Manual が開く Web ページ。空の場合、ビルドではこのボタンが非表示になります。Unity エディターでは、フィールドが空のときはこのマニュアルが開きます。
Files
Maps Folder空マップの一覧表示、読み込み、保存に使用するフォルダー。空の場合は、ストレージフォルダー RTSMapSerializer.StorageRoot 内の Maps フォルダーを使用します。UserMaps のような相対パスは、ストレージフォルダーからの相対パスとして扱われます。絶対パスはそのまま使用されます。ストレージフォルダーは Application.persistentDataPath です。Web ビルドでは、ブラウザーのストレージ内のフォルダーで、ページのアドレスが変わっても同じフォルダーのままです(ブラウザー内のマップを参照)。
Write Preview Imagesオン保存するマップごとに、小さなミニマップ画像(192 × 192 ピクセル)をマップの横に書き出します。この画像はマップブラウザーに表示されます。
User Interface
Panel Settings空任意の UI Toolkit パネル設定。空の場合、エディターはランタイムで RTS テーマのパネル設定を作成します。このパネル設定は画面サイズ(基準 1920 × 1080)に合わせてスケーリングされ、85% 未満には縮小されないため、1280 × 720 でもテキストが読みやすく保たれます。
Enforce Minimum UI Scaleオン小さなウィンドウやブラウザーのキャンバスでも、エディターを読みやすく保ちます。画面サイズに合わせてスケーリングする割り当て済みのパネル設定はランタイムでコピーされ(アセット自体は変更されません)、そのコピーでは UI が 85% 未満に縮小されることはありません。割り当てたアセットをそのまま使用する場合(たとえば、エディターとゲームの HUD で 1 つのパネルを共有する場合)は、この設定をオフにします。
Ui Scale1追加のスケール係数(0.5~2)。エディターがランタイムで作成するパネル設定と、割り当てたパネル設定のランタイムコピーに適用されます。
Sorting Order1000エディター UI のソート順。デフォルト値が大きいため、エディターはゲームの HUD より前面に表示されます。

インターフェースの概要

画面は 5 つの領域に分かれています。ボタン、フィールド、ステータス項目にはすべて、ポインターをしばらく重ねるとツールチップが表示されます。無効なボタンでも表示されます。機能がわからない要素があれば、ポインターを重ねてみてください。

領域位置内容
トップバー上端File メニューと Map メニュー、元に戻す・やり直し、対称、表示の切り替え、Play Test、ヘルプ、パネルの非表示、閉じる。
ツールバー左端8 つのカテゴリボタン(1~8 キー)と、選択中のカテゴリのツール。
ツールパネル右端アクティブなツールの名前と説明、ブラシ、対称、ツールの設定、追加コマンド。折りたたむことができます。
ステータスバー下端カーソル下の地点の情報、ツールのステータス、ブラシ、アンドゥのメモリ使用量、マップ名。
オーバーレイマップの上トースト、ツールチップ、ダイアログ、ウェルカムヒント、テストプレイバー、通知。

トップバー

操作機能
FileFile メニューを開きます(以下を参照)。
MapMap メニューを開きます(以下を参照)。
Undo / Redo元に戻す(Ctrl+Z キー)とやり直し(Ctrl+Y キー)。ツールチップにはステップの名前が表示されます(例:Undo "Raise / Lower")。元に戻す操作ややり直す操作がない場合、ボタンはグレー表示になります。
Symmetry: Off対称メニューを開きます。対称がオンのときは、すべてのストロークがミラーリングまたは回転されるため、公平な対戦マップを簡単に作成できます。ボタンには現在のモードが表示されます(例:Symmetry: 180°)。
Gridセルグリッドの表示・非表示を切り替えます(G キー)。
Pathing通行オーバーレイメニューを開きます。オーバーレイがオンのときは、ボタンにそのモードが表示されます(例:Pathing: Walkability)。P キーを押すと、最後に使用したモードでオーバーレイのオン/オフが切り替わります。
Markersほかのツールの使用中に、開始地点、リージョン、マーカーの表示・非表示を切り替えます(M キー)。デフォルトではオンです。
Play Testテストプレイを開始します(F5 キー)。
Help (?)Shortcuts & Help ウィンドウを開きます(F1 キー)。
Eyeパネルを非表示にして、マップを見やすくします(H キー)。非表示の間も編集は可能です。小さなボタン Panels hidden - press H to show them でパネルを再表示できます。
Close (×)エディターを非表示にし、再び開くためのキーをトーストで表示します。Toggle Key が None の場合、このボタンは非表示になります。ファンクションキーが確実に機能しない Web ブラウザーやスマートフォン、タブレットでは、左下に Open Map Editor ボタンも表示されます。また、これらの環境では、切り替えキーがなくてもこのボタンを使用できます(ブラウザーでのゲーム内エディターを参照)。

File メニュー

項目ショートカット機能
New Map...Ctrl+N(Web ブラウザーでは Alt+N)New Map ダイアログを開きます。現在のマップに未保存の変更がある場合は、先に確認が表示されます。Alt+N キーはすべてのプラットフォームで使用できます。
Open...Ctrl+Oマップブラウザーを開き、マップを選択できるようにします。現在のマップに未保存の変更がある場合は、先に確認が表示されます。
SaveCtrl+Sマップの読み込み元のファイルに保存します。初回は Save Map As が開き、名前の入力を求めます。
Save As...Ctrl+Shift+Sマップブラウザーを開き、新しい名前で保存できるようにします。
Download MapWeb ビルドのみ。未保存の変更を含むマップのコピーを、.rtsmap ファイルとしてプレイヤーのコンピューターに保存します(通常はダウンロードフォルダー)。ブラウザー内のマップを参照してください。
Import Map...Web ビルドのみ。プレイヤーのコンピューターから .rtsmap ファイルを開きます。ファイルはこのブラウザーに保存されているマップに追加され、開かれます。
Show Maps Folderオペレーティングシステムのファイルブラウザーでマップフォルダーを開きます。この項目は、Windows、macOS、Linux の Unity エディターとデスクトッププレイヤーにのみあります。
Quitアプリケーションを終了します。未保存の変更がある場合は、先に確認します。Show Quit Command がオンの場合にのみ表示され、WebGL、iOS、Android では表示されません。

Map メニュー

項目機能
Map Info...名前、作成者、説明、推奨プレイヤー数。
Resize...マップを拡大または縮小します。
Generate...シードからランダムなマップレイアウトを作成します。1 ステップで元に戻せます。
Frame Whole Mapマップ全体が見えるようにエディターカメラを移動します。組み込みのカメラを使用している場合にのみ使用できます。
Edited terrainシーンに RTS Terrain が 2 つ以上ある場合にのみ表示されます。編集する地形を選択します。切り替えるとアンドゥ履歴が消去されます。

対称メニュー

モードコピー数主な用途
Off1対称なしでペイントします。
Mirror Left / Right2垂直な中心線を軸にミラーリングします。
Mirror Top / Bottom2水平な中心線を軸にミラーリングします。
Mirror 4-Way4両方の軸でミラーリングします。
Rotate 180°2中心を基準とした点対称。1v1 マップで一般的です。
Rotate 90° (4 players)44 人用マップ向けの回転対称。正方形のマップが必要です。正方形でないマップでは、この項目はグレー表示になります。
Mirror Diagonal2対角線を軸にミラーリングします。正方形のマップが必要です。正方形でないマップでは、この項目はグレー表示になります。

Reset Centre は、対称の中心をマップの中央に戻します。ツールパネルの Symmetry セクションにある Centre X スライダーと Centre Z スライダーで、中心を移動できます。地形上の紫色のガイド線が対称軸を示します。

通行オーバーレイメニュー

このオーバーレイは、ユニットがその場所で何ができるかに応じて地形を色分けします。Off、Walkability(緑 = 歩行可能、赤 = ブロックされている)、Buildability、Flyability、Water(浅水域と深水域)、Combined(歩行可能、建設可能、水、ブロックされたセルを 1 つのビューに表示)から選択します。Opacity の項目で、オーバーレイの不透明度を 30%、55%(デフォルト)、80% に設定できます。

ツールバー(左)

上段には、ツールカテゴリごとに 1 つのボタンがあります。各ボタンに付いている小さな数字は、対応するキーです。下段には、選択中のカテゴリのツールがアイコンと名前付きで一覧表示されます。ツールにポインターを重ねると、その説明とヒントが表示されます。

キーカテゴリ説明ツール
1Height地面を上げ下げし、滑らかにし、形を整えます。Raise / Lower、Smooth、Flatten、Set Height、Noise、Terrace、Erode
2Cliffsクラシック RTS スタイルの崖レベル、スロープ、崖のマテリアル。Raise / Lower Level、Set Level、Ramp、Cliff Type、Clean Cliffs
3Paint地面テクスチャをペイントします。Paint Layer、Tile Paint、Fill / Replace、Smooth Paint、Auto Paint
4Vegetation草、花、木。Paint Details、Erase Details、Place Trees、Erase Trees
5Decorオブジェクト(ドゥーダッド)を配置、消去、選択します。Place Decor、Erase Decor、Select Decor
6Water湖、川、海。Paint Water、Erase Water、Flood Fill
7Pathingユニットが歩行、建設、飛行できる場所を決めます。Paint Pathing、Clear Pathing
8Markers開始地点、リージョン、名前付きマーカー。Start Locations、Regions、Markers

エディターは、カテゴリごとに最後に使用したツールを記憶します。ボタンまたは数字キーでカテゴリを選択すると、そのツールに戻ります。すでに選択中のカテゴリの数字キーを押すと、そのカテゴリ内の次のツールに移動します。登録したカスタムツールはここに自動的に表示され、その [ToolSetting] フィールドから設定 UI が構築されます。

ツールパネル(右)

ヘッダーには、アクティブなツールのアイコンと名前が表示されます。名前にポインターを重ねると、ツールの説明を読むことができます。矢印ボタンでパネルを折りたたみます。ヘッダーの下には、折りたためるセクションが並びます。折りたたんだセクションは、ツールを切り替えても折りたたまれたままです。

セクション表示対象内容
(概要)すべてのツールツールの説明と使い方のヒント(例:Click: place / move · Shift+click: remove the nearest)。
BrushブラシツールShape(Circle、Square、Diamond、およびカスタムスタンプが設定されている場合は Stamp)、Size(対数スライダーとメートル単位のフィールド)、Strength、Hardness、Rotation、Random Rotation、Spacing、Jitter、およびセクションヘッダーの Reset ボタン。すべてのツールで同じブラシを共有します。
Symmetryすべてのツール対称モード、Centre X と Centre Z、およびモードに正方形のマップが必要な場合の警告。
Settingsオプションのあるツールアクティブなツールのオプション。たとえば、目標の高さ、テクスチャパレット、木と装飾オブジェクトのサムネイル、崖レベル、プレイヤーなどです。
Actions一部のツール追加コマンド:Apply to Whole Map(Auto Paint)、New Random Layout(Place Trees、Place Decor)など。
SelectionSelect DecorDelete、Duplicate、回転(−15°、+15°、+90°)、半セル分の移動(−X、+X、−Z、+Z)、Smaller / Larger(10%)、地面へのスナップ、Lock / Unlock、Select All、Clear。

地形にマップがない場合、パネルとステータスバーの両方に There is no map to edit. と表示されます。

ステータスバー(下)

項目例意味
セルCell 42, 17カーソル下のセル(x, z)。
高さHeight 3.5 mカーソル下の地面の高さ(メートル単位)。
レベルLevel 2カーソル下の崖レベル(0~15)。
通行判定Walkable, Buildableその場所でユニットができること。たとえば Shallow water、Cliff、Too steep、Blocked、No flying などです。
水Water 1.2 m または Dryカーソル下の水深。
ツールのステータスアクティブなツールからの情報、またはそのヒント。
ブラシBrush 8.0 m 50 %ブラシの半径と強さ。
アンドゥUndo 12 4.2 MBアンドゥステップの数と、それらが使用するメモリ量。
マップ名● Sandboxマップの名前。点が付いている場合は未保存の変更があることを示し、Ctrl+S キーで保存できます。

トースト、ツールチップ、通知

  • トーストは、ステータスバーの上に表示され、自動的にフェードアウトする短いメッセージです(例:Saved "Sandbox"、Undo: Raise / Lower)。トーストには 4 つの種類があります。Info と Success は約 3 秒、Warning は 5 秒、Error は 6.5 秒表示されます。トーストをクリックすると閉じます。同時に表示されるのは最大 4 つです。エディターが非表示の間もトーストは表示されたままなので、Editor closed. Press F2 to open it again. というメッセージを読むことができます。
  • Open Map Editor:Web ブラウザーやスマートフォン、タブレットで、閉じるボタンを使った後に左下に表示されるボタンです。このボタンでエディターを再び開けます。キーボードがある場合は、Open Map Editor (F2) のように切り替えキーが表示されます。
  • ツールチップは、任意のコントロールにポインターを約 0.5 秒重ねると表示されます。無効なコントロール上やテストプレイ中も表示されます。クリックすると、ポインターがそのコントロールから離れるまでツールチップは非表示になります。
  • ウェルカムヒント:最も重要な 4 つの操作と、All Shortcuts (F1) ボタンおよび Got it ボタンを含む小さなパネルです。ヘルプウィンドウの Open Welcome Hint で再表示できます。
  • 通知:エディターが動作できない場合、画面中央に No RTS Terrain found または No camera found が表示され、何を修正すべきかの短い説明が添えられます。
  • テストプレイバー:テストプレイ中は、画面上部中央に Play test running と書かれた小さなバーが表示されます。このバーには Back to Editor (Esc or F5) ボタンがあります。

ダイアログ

ダイアログはモーダルです。ダイアログが開いている間は、マップを編集できず、ショートカットも無効になります。Enter キーを押すと強調表示された(プライマリ)ボタンの操作が実行され、Esc キーを押すとダイアログが閉じます。

ダイアログ開き方内容
New MapFile ▸ New Map...Name、Size(プリセット:Tiny (64 × 64)、Small (96)、Medium (128)、Large (192)、Huge (256)、Epic (384)、Giant (512))、Width と Depth(16~1024 セル、16 の倍数に丸められます。Web ブラウザーでは上限が低くなります。制限を参照)、Cell Size(1、2、4 m。2 m はクラシック RTS のマップに相当)、Base Level(崖レベル 0~15。2 にすると、湖や川のために掘り下げる余地が残ります)、Base Layer(地面テクスチャ)。その下の行に、作成されるマップのサイズがメートル単位で表示されます。新しいマップの作成は元に戻せないため、先に保存してください。
Generate MapMap ▸ Generate...Style(形状のオプションをリセットします)、Random ボタン付きの Seed、Symmetry。Terrain セクション:Cliffs、Plateaus、Ramps、Ramp Count、Water、Lake Coverage、Island、Border。Content セクション:Textures、Vegetation、Forests、Decor、Decor Count、Start Locations、Players。現在のマップの内容を 1 アンドゥステップで置き換えます。設定は、次にダイアログを開いたときのために保持されます。
Map InfoMap ▸ Map Info...Name、Author、Description(マップブラウザーに表示されます)、Players(1~16)、および統計情報(サイズ、開始地点、木、オブジェクト、リージョン)。適用すると 1 アンドゥステップになります。
Resize MapMap ▸ Resize...Width と Depth(16 の倍数。ブラウザーでは New Map と同じ上限が適用されます)、Anchor(3 × 3 のグリッドで、マップのどの部分をその場に残すかを指定)、新しい領域用の Fill Level と Fill Layer。サイズを変更するとアンドゥ履歴が消去されます。マップを縮小すると、新しい範囲の外にあるコンテンツは削除されます。
Open Map / Save Map AsFile ▸ Open... / Save As...マップブラウザー。
Unsaved Changes未保存の変更がある状態での New、Open、QuitSave(先に保存します。マップに名前がない場合は名前を尋ねます)、Don't Save、Cancel。
Shortcuts & HelpF1 キーまたは ? ボタンすべてのマウス操作とキーボード操作(操作とショートカットを参照)、Open Welcome Hint、Open Manual(マニュアルが利用可能な場合)、Close。

マップブラウザー

File ▸ Open...(Ctrl+O)と File ▸ Save As...(Ctrl+Shift+S)は、どちらも同じブラウザーを開きます。タイトルはそれぞれ Open Map、Save Map As です。ブラウザーには、マップフォルダー内のすべての .rtsmap ファイルが一覧表示されます。

構成要素説明
フォルダー行マップフォルダーのフルパスです。Refresh はフォルダーを読み込み直します。フォルダーにマップをコピーした後などに使用します。Web ビルドでは、この行に Saved in this browser. Use Download to keep a copy on your computer. と表示され、Open の場合はこの行に Import... ボタンも表示されます(ブラウザー内のマップを参照)。
マップ一覧1 マップにつき 1 行で、名前、サイズ、プレイヤー数、最終更新日時が表示され、更新日時が新しい順に並びます。読み込めないファイルには Unreadable or damaged file と表示され、開くことはできません。行にマウスポインターを合わせると、そのファイルパスが表示されます(Web ビルドではファイル名だけが表示されます)。
詳細選択したマップのミニマッププレビュー、名前、サイズ(セル数とメートル)、作成者、プレイヤー数、更新日時、説明が表示されます。プレビューがあるのは、マップの保存時に Write Preview Images がオンだった場合だけです。
Map NameSave As の場合のみ表示されます。ファイル名はこの名前から作られます。一覧のマップをクリックすると、その名前がフィールドにコピーされるので、そのマップを上書きできます。
Delete確認の後、選択したマップファイルとそのプレビュー画像を削除します。この操作は元に戻せません。
DownloadWeb ビルドで、Open の場合のみ表示されます。選択したマップのコピーを、.rtsmap ファイルとしてプレイヤーのコンピューターに保存します。
Cancel / Open / SaveOpen は選択したマップを読み込みます。行をダブルクリックするか Enter キーを押しても同じ動作になります。Save は入力した名前でマップを保存します。

ブラウザーは各ファイルの小さなヘッダーだけを読み込むため、フォルダーに大きなマップが多数ある場合でもすばやく開きます。

新しい名前での保存

  1. Ctrl+Shift+S キーを押します。名前フィールドには、現在のマップ名がすでに入力されています。
  2. 新しい名前(たとえば Glacier Pass (2v2))を入力し、Enter キーを押します。
  3. その名前の別のファイルがすでに存在する場合は、Replace Map? という確認が表示されます。Enter キーを押したときのデフォルトは Cancel なので、ファイルを上書きするには Replace をクリックする必要があります。
  4. マップが保存されてプレビュー画像が書き出され、トーストに Saved "Glacier Pass (2v2)" と表示されます。以降は Ctrl+S キーを押すと、確認なしでこのファイルに保存されます。

別の名前で保存すると、マップ自体の名前(Map Info に表示される名前)も変わります。この名前の変更は、Rename Map という通常のアンドゥステップとして記録されます。

操作とショートカット

同じ一覧は、エディター内で F1 キーを押しても表示できます。macOS では、Ctrl と書かれている操作はすべて Cmd キーでも行えます。数字キーは、メインキーボードの数字キーの列を指します。文字キーのショートカットはキーに印字された文字に従うため、ドイツ語(QWERTZ)配列やフランス語(AZERTY)配列のキーボードでも、Ctrl+Z の Z は Z と印字されたキーを指します。ブラシ用のキーは入力される文字に従うため、[ と ] は、キーボード上のどこにあっても(たとえば AltGr キーとの組み合わせで入力する場合でも)使用できます。Web ブラウザーでは、一部のキーの動作が異なります。ブラウザーでのゲーム内エディターを参照してください。

マウス

入力操作
マウスの左ボタンペイントするか、アクティブなツールを使用します。押したままドラッグするとストロークになります。1 回のストローク全体が 1 アンドゥステップです。
Shift + マウスの左ボタン逆の操作になります。上げる代わりに下げる、ペイントする代わりに消去する、配置する代わりに削除する、といった動作です。
Ctrl + 左クリック対応しているツールで、地形から値(高さ、地面レイヤー、崖レベル…)をスポイトで取得します。
右ボタンまたは中ボタンでドラッグカメラをパンします(「地面をつかんで動かす」操作)。
Alt + 左ボタンまたは右ボタンでドラッグカメラを回転させます。Alt キーを押している間は、左ボタンを押してもペイントされません。
マウスホイールカーソル下の点に向かってズームイン・ズームアウトします。

カメラ(組み込みのエディターカメラ)

キー操作
W A S D または矢印キーパンします。Shift キーを押したままにすると、速く移動します。
Q / E左 / 右に回転します。
Fカーソル下の点をカメラの注視点にします。

ブラシとツール

キー操作
[ / ]ブラシを小さく / 大きくします(1 回押すごとに約 15%)。
- / =(または +)強さを下げます / 上げます(1 回押すごとに 5%)。テンキーの - キーと + キーも使用できます。
1~8ツールカテゴリ(Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers)を選択します。もう一度押すと、そのカテゴリ内の次のツールに切り替わります。
Escペイント中のストロークをキャンセルします。地形は、マウスボタンを押した時点の状態に戻ります。

ファイルと履歴

キー操作
Ctrl+N または Alt+N新しいマップを作成します。Web ブラウザーでは、ブラウザーが Ctrl+N キーを新しいウィンドウ用に確保しているため、Alt+N キーだけが機能します。
Ctrl+Oマップを開きます(マップブラウザー)。
Ctrl+S保存します。初回は名前の入力を求められます。
Ctrl+Shift+S名前を付けて保存します。
Ctrl+Z元に戻します。
Ctrl+Y または Ctrl+Shift+Zやり直します。

表示とエディター

キー操作
Gセルグリッドの表示・非表示を切り替えます。
P通行オーバーレイのオン/オフを切り替えます。オンにすると最後に使用したモードで表示されます(最初は Combined)。
M開始地点、リージョン、マーカーの表示・非表示を切り替えます。
Hエディターパネルの表示・非表示を切り替えます。
F1Shortcuts & Help を開きます。
F5テストプレイを開始するか、実行中のテストプレイを終了します。
Esc(テストプレイ中)テストプレイを終了して、エディターに戻ります。
F2(Toggle Key)エディター全体の表示・非表示を切り替えます。テストプレイ中は、このキーを押しても何も起こりません。

装飾オブジェクトの選択(Select Decor ツールのみ)

入力操作
クリック / ボックスをドラッグオブジェクトを選択します。
Shift + クリック / ボックスオブジェクトを選択に追加するか、選択から外します。
選択したオブジェクトをドラッグ選択中のオブジェクトを移動します。1 回のドラッグ全体が 1 アンドゥステップです。ドラッグ中に Esc キーを押すと、オブジェクトは元の位置に戻ります。ロックされたオブジェクトはドラッグできません。
複数選択に含まれるオブジェクトをクリックそのオブジェクトだけを選択します。
Delete / Backspace選択したオブジェクトを削除します。
Ctrl+D選択中のオブジェクトを、斜めに 1 セルずらして(+X 方向と +Z 方向)複製します。
Ctrl+Aロックされていないすべてのオブジェクトを選択します。
Esc選択を解除します。

Esc キーの動作

Esc キーを押すと、以下のうち最初に当てはまる動作が実行されます。

  1. 開いているポップアップメニューを閉じます。
  2. テキストフィールドやスライダーからキーボードフォーカスを外します。
  3. 最前面のダイアログを閉じます。
  4. 装飾オブジェクトのドラッグをキャンセルします(オブジェクトは元の位置に戻ります)。
  5. ペイント中のストロークをキャンセルします(トースト Stroke cancelled が表示されます)。
  6. 装飾オブジェクトの選択を解除します(Select Decor ツール)。
  7. H キーでパネルを非表示にしていた場合は、パネルを再表示します。

ショートカットが効きませんか?テキストフィールドにキーボードフォーカスがある間は、文字キーはそのフィールドへの入力として扱われます。ダイアログが開いている間は、Enter キーと Esc キーしか機能しません。マップをクリックするか Esc キーを押してから、もう一度試してください。また、ポインターがパネル上にある間は、ペイントとマウスホイールによるズームもできません。

組み込みのエディターカメラ

Use Built In Camera がオンの場合、エディターはターゲットカメラに RTSRuntimeEditorCamera を追加します。このオービットカメラは、地面上の注視点の周りを回ります。カメラは地形の範囲に余白を加えた内側にとどまり、地面より下に入ることはなく、ズームアウトして高い位置になるほど、より下向きに傾きます。タイムスケールの影響を受けない時間を使用するため、ゲームが一時停止している間(Time.timeScale = 0)も動作し続けます。エディターは、自身が表示されている間はこのカメラをオンにし、非表示の間とテストプレイ中はオフにします。テストプレイが終わると、カメラはテストプレイ前の位置に戻ります。

動作を変更するには、RTSRuntimeEditorCamera を自分でカメラに追加し(Add Component ▸ RTS Terrain Studio ▸ In-Game Editor Camera)、フィールドを設定します。コンポーネントがすでにある場合、エディターは新しく追加せずに既存のものを使用します。

設定デフォルト説明
Movement
Pan Speed18カメラが最も低い位置にあるときの、キーボードによるパンの速度(メートル/秒)。高くなるほど速くなります。
Fast Multiplier2.5Shift キーを押している間のパン速度の倍率。
Edge Panオフマウスが画面の端に触れるとパンします。
Edge Pan Margin12エッジパン(画面端スクロール)に使う画面端の幅(ピクセル単位)。
Rotate Speed100Q / E キーによる回転の速度(度/秒)。
Mouse Rotate Speed0.25Alt + マウスドラッグによる回転量(度/ピクセル)。
Zoom
Min Height8注視点から測ったカメラの高さの最小値(メートル単位)。
Max Height120注視点から測ったカメラの高さの最大値(メートル単位)。
Zoom Step0.14マウスホイール 1 ノッチあたりの高さの変化量。現在の高さに対する割合で指定します。
Zoom Towards Cursorオン画面中央ではなく、カーソル下の点に向かってズームします。
Min Pitch40カメラが最も低い位置にあるときの、水平線から下向きに測ったピッチ(度)。
Max Pitch70カメラが最も高い位置にあるときのピッチ。
Pitch Curveイーズインアウト正規化された高さに応じて、Min Pitch(0)から Max Pitch(1)へブレンドするカーブ。
Limits & Smoothing
Ground Clearance3カメラとその真下の地面との間に確保される最小距離(メートル単位)。
Bounds Padding8地形の周囲に追加される、注視点が移動できる余白(メートル単位)。
Smooth Time0.1スムージングにかかる時間(秒単位、0 = スムージングなし)。

コードからは、RTSRuntimeEditor.EditorCamera でこのコンポーネントを取得できます。組み込みカメラがオフの場合は null が返ります。

メンバー説明
void Focus(Vector3 worldPosition, bool instant = false)注視点をワールド位置へ移動します。instant が true でない限り、滑らかに移動します。
void FrameTerrain(bool instant = false)カメラを地形の中央上空に配置し、地形の大部分が見えるようにズームアウトします。新しいマップやサイズを変更したマップでは、これが自動的に行われます。
void SnapToTargets()スムージングなしで、目標の位置、回転、高さへ即座に移動します。
Vector3 FocusPoint、float Yaw、float Height現在のオービットの状態(読み取り専用)。
bool IsDragging、bool WantsAltDragカメラがドラッグされている間、または Alt キーが押されている間は true。
bool PointerBlocked、bool KeyboardBlockedポインターが UI 上にある間、テキストフィールドにフォーカスがある間、またはダイアログが開いている間、エディターが毎フレーム設定します。
RTSTerrain Terrain範囲と地面の高さの取得に使用する地形。

編集中に独自のカメラスクリプトを使用する場合:Use Built In Camera をオフにします。エディターは、ピッキングとギズモの描画のためにターゲットカメラを参照するだけになります。RTSRuntimeEditor.IsPointerOverUI が true の間は、カメラが入力を無視するようにしてください。こうしておくと、パネルをスクロールしたときにカメラまでズームしてしまうのを防げます。

Play Test:ゲーム内でマップを試す

Play Test(F5)を使うと、エディターが一時的にゲームそのものになります。エディターの UI が退いて画面を空け、ゲームプレイコードがユニットをスポーンして自身のカメラをオンにし、プレイヤーがマップを試せるようになります。Esc キー、F5 キー、または Back to Editor でテストを終了すると、マップはテスト前とまったく同じ状態に戻ります。ユニット、建物、伐採された木、変更された通行判定はすべて消えます。

ユニット、ミニマップ、HUD、操作パネルが表示された、ゲーム内エディターでのテストプレイ
ゲーム内エディターから開始したテストプレイ:デモゲームがユニットをスポーンし、ミニマップには戦場の霧が表示され、Esc キーでエディターに戻ります。

エディターは、ゲームの内容については一切関知しません。テストプレイの開始と終了を静的クラス RTSRuntimeEditorEvents を通じて通知するだけで、何をするかはゲーム側が決めます。サンプルの RTSDemoGame コンポーネント(GameObject ▸ RTS Terrain Studio ▸ Demo Game)は、すでにこのイベントを受け取るように作られています。ゲーム内エディターのシーンに追加すると、コードを書かなくても、テストプレイ中にユニット、戦場の霧、ミニマップ、HUD が使えるようになります。

処理の流れ

タイミングエディターの処理
Play Test が押されたとき
  1. ペイント中のストロークを確定します。
  2. マップの完全なコピー(スナップショット)を作成し、ファイルパスと、未保存の変更があるかどうかを記憶します。
  3. ダイアログとメニューを閉じ、ブラシのプレビュー、ギズモ、グリッド、通行オーバーレイを非表示にします。
  4. 組み込みカメラをオフにし、パネルを小さなテストプレイバーに置き換えます。
  5. RTSRuntimeEditorEvents.PlaytestChanged(true) を発生させます。これを受けてゲームが始まります。
テストプレイ中すべての編集入力、元に戻す操作とファイルコマンド(Stop the play test first. と表示されます)、およびトグルキーを無視します。ゲームプレイ側でマップを自由に変更してかまいません。
Esc キー、F5 キー、または Back to Editor
  1. 最初に RTSRuntimeEditorEvents.PlaytestChanged(false) を発生させます。これにより、テストプレイ用のマップがまだ読み込まれている間に、ゲームが後片付けできます。
  2. スナップショットをマップに書き戻し、地形を再構築します。
  3. ファイルパスと未保存の変更の有無、パネル、エディターの表示状態、組み込みカメラを復元します。
  4. トースト Play test ended: the map was restored. を表示します。

アンドゥ履歴:エディターはスナップショットを同じマップインスタンスに書き戻すため、アンドゥ履歴はテストプレイ後も保持されます。テスト中にゲームプレイ側でマップを置き換えたり、再読み込みしたり、サイズを変更したりした場合は、代わりにスナップショットを新しいマップとして読み込みます。その場合、トーストには (undo history was reset) と表示されます。

RTSRuntimeEditorEvents リファレンス

RTSRuntimeEditorEvents はコアランタイム(Runtime/Core)に含まれています。そのため、プロジェクトからゲーム内エディターを削除しても、ゲームプレイコードはそのままコンパイルでき、動作します。

メンバー説明
static bool IsPlaytestingテストプレイの実行中は true。
static event Action<bool> PlaytestChangedテストプレイの開始時(true)と終了時(false)に発生します。
static event Action<RTSTerrain> MapOpenedゲーム内エディターがファイルからマップを開いた後、または新しいマップを作成した後に発生します。
static void ConsumeEscape()ゲームプレイコード自身が Esc キーを使用したフレーム(たとえば、建物の配置をキャンセルした場合)で呼び出します。そのフレームでは、エディターはテストプレイを終了しません。
static bool EscapeConsumedThisFrame現在のフレームで ConsumeEscape が呼び出された場合は true。
static void SetPlaytesting(bool playtesting)IsPlaytesting を設定し、PlaytestChanged を発生させます。このメソッドはエディターが呼び出します。自分のコードからテストを開始する場合は、代わりに RTSRuntimeEditor.StartPlaytest() を呼び出してください。マップのスナップショットを作成するのはエディターだけだからです。
static void RaiseMapOpened(RTSTerrain terrain)MapOpened を発生させます。このメソッドはエディターが呼び出します。

これらのイベントは、再生モードの開始時にクリアされます。イベントの登録はシーンオブジェクトの OnEnable(または Awake / Start)で行い、静的コンストラクターやエディタースクリプトでは行わないでください。また、登録の解除は OnDisable で行ってください。

例:テストプレイでユニットをスポーンする

このコンポーネントは、テストプレイの開始時に開始地点ごとに 1 体のユニットをスポーンし、ゲームプレイ用のシステム(RTS カメラ、戦場の霧、HUD)を有効にします。テストが終了すると、ユニットを削除してシステムを再び無効にします。ゲーム内エディターのシーンにある任意のオブジェクトに追加し、ユニットのプレハブを割り当ててください。

using System.Collections.Generic;
using UnityEngine;
using RTSTerrainStudio;

/// <summary>Starts a tiny "game" whenever the in-game editor runs a play test.</summary>
public class MyPlaytestGame : MonoBehaviour
{
    [Tooltip("Spawned once at every start location of the map.")]
    [SerializeField] GameObject unitPrefab;

    [Tooltip("Gameplay components that should only run during a play test, " +
             "for example your RTS camera script, the fog of war manager or your HUD.")]
    [SerializeField] Behaviour[] gameplaySystems = new Behaviour[0];

    readonly List<GameObject> spawned = new List<GameObject>();

    void OnEnable()
    {
        // Static event: always pair += in OnEnable with -= in OnDisable.
        RTSRuntimeEditorEvents.PlaytestChanged += HandlePlaytestChanged;
        SetSystemsEnabled(RTSRuntimeEditorEvents.IsPlaytesting);
    }

    void OnDisable()
    {
        RTSRuntimeEditorEvents.PlaytestChanged -= HandlePlaytestChanged;
        DespawnAll();
    }

    void HandlePlaytestChanged(bool playtesting)
    {
        if (playtesting) StartGame();
        else StopGame();
    }

    void StartGame()
    {
        var terrain = RTSTerrain.Main;          // the first RTS Terrain in the scene
        if (terrain == null || terrain.Map == null) return;

        foreach (var start in terrain.Map.StartLocations)
        {
            // Start locations are stored in map-local space: convert to world space
            // and put the unit on the ground.
            Vector3 position = terrain.LocalToWorld(start.position);
            position.y = terrain.GetHeight(position);
            Quaternion rotation = Quaternion.Euler(0f, terrain.transform.eulerAngles.y + start.yaw, 0f);

            var unit = Instantiate(unitPrefab, position, rotation);
            unit.name = "Player " + (start.player + 1) + " Unit";
            spawned.Add(unit);
        }
        SetSystemsEnabled(true);
    }

    void StopGame()
    {
        // Called BEFORE the editor restores the map, so everything is still in place.
        DespawnAll();
        SetSystemsEnabled(false);
    }

    void DespawnAll()
    {
        foreach (var unit in spawned)
            if (unit != null) Destroy(unit);
        spawned.Clear();
    }

    void SetSystemsEnabled(bool enabled)
    {
        foreach (var system in gameplaySystems)
            if (system != null) system.enabled = enabled;
    }
}

StartLocation.position は Unity.Mathematics.float3 型で、Vector3 に自動的に変換されます。スクリプトが独自のアセンブリ定義に含まれている場合は、RTSTerrainStudio.Runtime と Unity.Mathematics への参照を追加してください。

例:Esc キーをゲームと共有する

多くの RTS ゲームでは、Esc キーで命令や建物の配置をキャンセルします。そのキーを使用したフレームで ConsumeEscape() を呼び出せば、エディターはテストプレイを終了しません。エディターはこのチェックを LateUpdate で行うため、スクリプトの実行順序は関係ありません。

using UnityEngine;
using RTSTerrainStudio;

public class MyBuildingPlacement : MonoBehaviour
{
    bool placing;   // true while a building ghost follows the cursor

    void Update()
    {
        if (placing && RTSInput.GetKeyDown(RTSKey.Escape))
        {
            placing = false;                          // cancel the placement ...
            RTSRuntimeEditorEvents.ConsumeEscape();   // ... and keep the play test running
        }
    }
}

サンプルの RTSBuildingPlacementDemo、RTSCommandController、RTSSelectionManager は、すでにこの処理に対応しています。デモでは、最初に Esc キーを押すと選択が解除され、もう一度押すとエディターに戻ります。

自分のゲーム向けテストプレイのチェックリスト

  • RTS カメラスクリプト、戦場の霧マネージャー、HUD はシーン内で無効にしておき、PlaytestChanged(true) で有効にします。デモシーンでも RTSCameraController と RTSFogOfWarManager で同じことを行っています。
  • テスト中はエディターの組み込みカメラがオフになるため、カメラスクリプトがカメラを完全に制御できます。
  • スポーンしたものはすべて PlaytestChanged(false) で削除します。マップはエディターが復元するため、マップの変更を元に戻す必要はありません。
  • マルチプレイヤー(Mirror 連携)では、ゲームの実行中に行ったマップの編集はリアルタイムには配信されません。マップを再送信するには RTSNetworkMapSync.SendMapToAll() を呼び出します。

ビルドでのマップの保存と読み込み

マップの保存場所

マップは RTSRuntimeEditor.MapsFolder に保存されます。デフォルトの場所は RTSMapSerializer.StorageRoot 内の Maps フォルダーで、RTSMapSerializer.DefaultMapsFolder からも取得できます。StorageRoot は、Web 以外のすべてのプラットフォームで Application.persistentDataPath です。このフォルダーは最初の保存時に作成されます。persistentDataPath の場所はプラットフォームごとに Unity が決定します。Windows では、プレイヤー設定の会社名と製品名に基づいて %USERPROFILE%\AppData\LocalLow\<Company>\<Product> 以下になります。デスクトッププラットフォームでは File ▸ Show Maps Folder でフォルダーを開くことができ、マップブラウザーにはフルパスが表示されます。

Web ビルドでは、マップはブラウザーのストレージに保存されます。File ▸ Download Map と File ▸ Import Map... を使うと、マップをプレイヤーのコンピューターとの間でやり取りできます。詳しくはブラウザー内のマップを参照してください。

1 つのマップにつき、最大 2 つのファイルがあります。

ファイル例説明
マップGlacier Pass (2v2).rtsmapマップ全体のデータ(高さ、崖、テクスチャ、水、植生、装飾オブジェクト、通行判定、開始地点、リージョン、マーカー、マップ情報、環境)です。Unity エディターで使用するマップと同じ形式です。
プレビューGlacier Pass (2v2).preview.pngマップブラウザー用の 192 × 192 のミニマップ画像です。Write Preview Images がオンの場合にのみ書き出され、マップを削除すると一緒に削除されます。

ファイル名はマップ名をもとに付けられます。ファイル名に使用できない文字は _ に置き換えられ、末尾のドットは削除されます。また、名前が空の場合は New Map になります。

未保存の変更

  • 未保存の変更がある間は、ステータスバーのマップ名の前にドットが表示されます。
  • New Map、Open、Quit は、実行前に Save、Don't Save、Cancel のどれにするかを確認します。
  • Confirm Quit With Unsaved Changes がオフでない限り、マップに未保存の変更がある状態でプレイヤーがゲームウィンドウを閉じようとすると、エディターは終了をキャンセルし、同様に確認します。Web ブラウザーでは代わりに、タブを閉じたりリロードしたりする前に、ブラウザー自体が Leave site? と確認します。この確認も同じオプションに従います。
  • テストプレイによって未保存の状態が変わることはありません。エディターはマップと一緒にこの状態も復元します。
  • シーン開始時に使われるマップ(地形に割り当てられたマップアセット)は、まだマップフォルダーにファイルがないため、最初に Ctrl+S キーを押したときに名前の入力を求められます。

Unity エディターでのテスト:再生モードでゲーム内エディターを使ってマップを編集してから再生モードを停止すると、Unity に Save map changes made in Play Mode? という確認ダイアログが表示されます。Save を選択すると、変更がプロジェクト内の地形のマップアセットに書き込まれます。一方、Ctrl+S キーで保存したマップはマップフォルダーに保存されます。こうして保存した .rtsmap ファイルを Assets フォルダーにコピーすると、マップアセットとして使用できます。

ゲームへのマップの同梱

マップブラウザーに表示されるのはマップフォルダーの内容だけです。編集の出発点となる独自のマップをプレイヤーに提供するには、初回起動時にそのマップをマップフォルダーへコピーします。以下の例では、StreamingAssets/Maps 内のすべてのファイル(マップとプレビュー画像)をコピーし、プレイヤーが編集したバージョンは決して上書きしません。

using System.IO;
using UnityEngine;
using RTSTerrainStudio;

/// <summary>Copies bundled maps into the in-game editor's maps folder (desktop platforms).</summary>
public class InstallBundledMaps : MonoBehaviour
{
    [SerializeField] RTSRuntimeEditor editor;

    void Awake()
    {
        // Put your .rtsmap files (and optional .preview.png files) into Assets/StreamingAssets/Maps.
        string source = Path.Combine(Application.streamingAssetsPath, "Maps");

        // On Android and WebGL, StreamingAssets is not a normal folder: use UnityWebRequest there instead.
        if (!Directory.Exists(source)) return;

        string target = editor.MapsFolder;   // StorageRoot/Maps unless you changed it
        Directory.CreateDirectory(target);

        foreach (string file in Directory.GetFiles(source))
        {
            if (file.EndsWith(".meta")) continue;   // only matters when running in the Unity editor
            string destination = Path.Combine(target, Path.GetFileName(file));
            if (!File.Exists(destination)) File.Copy(file, destination);
        }
    }
}

プレイヤーが作成したマップのゲーム内でのプレイ

ゲームからは、エディターを使わずにマップフォルダーのマップを読み込むことができます。たとえばカスタムゲームのロビーで利用できます。RTSMapSerializer.TryReadHeaderFromFile は、マップ全体を読み込まずに名前、サイズ、作者、プレイヤー数を読み取ります。マップ自体の読み込みには RTSTerrain.LoadFromFile を使用します。

using System;
using System.IO;
using UnityEngine;
using RTSTerrainStudio;

public static class MapLobby
{
    /// <summary>Logs every map in the default maps folder.</summary>
    public static void ListMaps()
    {
        string folder = RTSMapSerializer.DefaultMapsFolder;
        if (!Directory.Exists(folder)) return;

        foreach (string path in Directory.GetFiles(folder, "*" + RTSMapSerializer.FileExtension))
        {
            // Reads only the small header: fast, even for large maps.
            if (RTSMapSerializer.TryReadHeaderFromFile(path, out RTSMapHeader header))
                Debug.Log(header.name + " by " + header.author + ": " + header.width + " x " + header.height +
                          " cells, " + header.maxPlayers + " players, modified " + header.Modified);
        }
    }

    /// <summary>Loads a map file into the main terrain. Returns false when the file cannot be read.</summary>
    public static bool Play(string path)
    {
        var terrain = RTSTerrain.Main;
        if (terrain == null) return false;
        try
        {
            terrain.LoadFromFile(path);   // throws when the file is missing or damaged
            return true;
        }
        catch (Exception e)
        {
            Debug.LogError("Could not load " + path + ": " + e.Message);
            return false;
        }
    }
}

マップは、その内容(地面レイヤー、植生と装飾のプロトタイプ)を ID で参照して保存しています。プレイヤーが作成したマップが正しく表示されるのは、ゲーム内エディターのシーンの地形と同じタイルセット、植生ライブラリ、装飾ライブラリを使用した場合だけです。そのため、ゲームのシーンの地形にも同じアセットを割り当ててください。

コードからのエディターの制御

UI でできることはすべて、RTSRuntimeEditor(名前空間 RTSTerrainStudio)を通じてコードからも実行できます。特に便利なメンバーは次のとおりです。

メンバー説明
状態
bool EditorVisible { get; set; }エディター全体を表示または非表示にします(切り替えキーと同じ)。非表示にすると、実行中のストロークが終了し、ダイアログが閉じて、地形ビューがゲームに戻ります。
bool PanelsVisible { get; set; }パネルだけを非表示にします(H キー)。編集は引き続き行えます。
bool IsPlaytesting { get; }このエディターから開始したテストプレイの実行中は true になります。
RTSKey ToggleKey { get; set; }表示・非表示を切り替えるキーです。RTSKey.None にするとキーが無効になり、閉じるボタンも非表示になります(ただし、Web ブラウザー、スマートフォン、タブレットでは閉じるボタンは表示されたままです。HasReliableFunctionKeys を参照)。
RTSTerrain Terrain { get; set; }編集対象の地形です。設定すると地形が切り替わり、アンドゥ履歴が消去されます。
Camera TargetCamera { get; set; }ピッキングとギズモに使用するカメラです。
RTSRuntimeEditorCamera EditorCamera { get; }組み込みのカメラです。ない場合は null です。
RTSEditSession Session { get; }編集セッションです(ツール、ブラシ、対称、選択、元に戻す操作)。
ToolCategory ActiveCategory { get; }アクティブなツールのカテゴリです。
bool HasHover, Vector3 HoverWorldPositionポインターが地形の上にあるかどうかと、ポインターの下にあるワールド位置です。
bool IsPointerOverUI { get; }ポインターがエディターのパネル上にある間は true になります。
bool ShowGrid, bool ShowMarkersG キーと M キーによる表示の切り替えです。
PathingOverlayMode? PathingOverlay, float OverlayOpacity通行オーバーレイ(null = オフ)と、その不透明度(0~1)です。
string MapsFolder { get; set; }マップフォルダーです。空の場合は RTSMapSerializer.DefaultMapsFolder が使用されます。相対パスは RTSMapSerializer.StorageRoot を基準に解決されます。
bool ShowQuitCommand, string ManualUrlFile ▸ Quit メニュー項目とマニュアルへのリンクです(インスペクターを参照)。
bool HasManual { get; }Open Manual で開くページがある場合に true になります。
static bool CanShowFolders, static bool CanQuitApplicationプラットフォームのチェックです。デスクトップのファイルブラウザーを利用できるかどうかと、アプリケーションを終了できるかどうか(WebGL、iOS、Android では不可)を示します。
static bool RunsInBrowser, static bool StoresMapsInBrowserRunsInBrowser は Web ビルドで true になります。Web ビルドではブラウザー用の上限が適用され、New Map のショートカットは Alt+N キーになります。StoresMapsInBrowser は、マップがブラウザーのストレージに保存される場合(WebGL プレイヤー)に true になります。詳しくは Web(WebGL)ビルドを参照してください。
static bool HasReliableFunctionKeysWeb ブラウザーとモバイルプラットフォームでは false になります。これらの環境では、閉じるボタンでエディターを閉じると Open Map Editor ボタンが表示されます。
VisualElement RootElement, UIDocument Document, RuntimeToolGizmos Gizmosエディター UI のルート、その UI ドキュメント、ギズモのレンダラーです。いずれも、コンポーネントが有効になる前は null です。
コマンド
bool SetActiveTool(string toolId)ID を指定してツールをアクティブにします(例:"paint.layer")。ID が存在しない場合は false を返します。
void SelectCategory(ToolCategory category)数字キーと同じ動作です。そのカテゴリで最後に使用したツールを選択し、カテゴリがすでにアクティブな場合は次のツールを選択します。
RTSTool GetRememberedTool(ToolCategory category)そのカテゴリで最後に使用したツール、またはカテゴリの最初のツールを返します。
void Undo(), void Redo()元に戻す・やり直しを実行します。テストプレイ中は何もしません。
bool OpenMap(string path).rtsmap ファイルを読み込みます。未保存の変更は確認なしで破棄されます。
bool SaveMap(string path)指定したパスに保存します。必要に応じて .rtsmap を付け、プレビュー画像も書き出します。
bool SaveCurrent()Ctrl+S キーと同じ動作です。マップがすぐに書き込まれた場合は true を返します。マップにまだファイルがない場合は Save As を開き、false を返します。
void NewMap(MapCreationSettings settings)未保存の変更について確認せずに、新しい平らなマップを作成します。
bool DeleteMapFile(string path)マップファイルとそのプレビュー画像を削除します。
ShowNewMapDialog(), ShowOpenDialog(), ShowSaveAsDialog(), ShowGenerateDialog(), ShowMapInfoDialog(), ShowResizeDialog(), ShowHelp()メニューと同じ動作でダイアログを開きます。New と Open は、先に未保存の変更について確認します。
void StartPlaytest(), void StopPlaytest()テストプレイを開始または終了します。
void TogglePathingOverlay()P キーと同じ動作です。
void DeleteSelectedDecor(), void DuplicateSelectedDecor()選択中の装飾オブジェクトを削除または複製します。どちらも 1 アンドゥステップになります。
void ShowMapsFolder(), void OpenManual(), void Quit()File メニューと Help メニューのコマンドです。Quit は未保存の変更について確認します。
bool DownloadMap(), void ImportMap()Web ビルド専用です。File ▸ Download Map と File ▸ Import Map... のコマンドです。ブラウザーはクリックやキー入力への応答としてのみファイル選択ダイアログを表示するため、ImportMap はクリックハンドラーから呼び出してください。
void ShowToast(string message, ToastType type = ToastType.Info)トーストを表示します。ToastType は Info、Success、Warning、Error のいずれかです。
void ShowToast(string message, ToastType type, string key)同じキーのトーストがまだ表示されている場合は、新しいトーストを追加せずに、そのトーストを更新します。
イベント
event Action<string> MapSavedマップを保存した後に発生します(完全なファイルパスが渡されます)。
event Action<string> MapLoadedマップをファイルから開いた後(完全なパスが渡されます)、または新規作成した後(null が渡されます)に発生します。
event Action<bool> VisibilityChangedEditorVisible が変化したときに発生します。

MapLoaded と RTSRuntimeEditorEvents.MapOpened は、File ▸ New と Open で発生します。Resize や独自のコードで読み込んだマップも含め、マップのすべての変更に対応するには、地形の RTSTerrain.MapLoaded を購読してください。

例:独自のメニューからエディターを開く

多くのゲームでは、キーではなくメインメニューからマップエディターを開きます。切り替えキーを無効にしてボタンからエディターを開き、エディターの最上位レイヤーに「Back to Menu」ボタンを追加します。

using UnityEngine;
using UnityEngine.UIElements;
using RTSTerrainStudio;

public class MapEditorLauncher : MonoBehaviour
{
    [SerializeField] RTSRuntimeEditor editor;   // with "Start Visible" turned off
    [SerializeField] GameObject mainMenu;       // your own menu UI

    void Start()
    {
        // No F2 key: the game decides when the editor is shown (on desktop this also hides the close button).
        editor.ToggleKey = RTSKey.None;

        // RootElement exists once the editor component is enabled (it is created in its OnEnable).
        // If you disable and enable the editor component later, its UI is rebuilt: add the button again.
        var back = new Button(BackToMenu) { text = "Back to Menu" };
        back.AddToClassList("rts-button");             // use the editor's button style
        back.style.position = Position.Absolute;
        back.style.right = 16;
        back.style.bottom = 56;                        // just above the status bar
        editor.RootElement.Add(back);                  // hidden together with the editor

        editor.VisibilityChanged += visible => mainMenu.SetActive(!visible);
    }

    /// <summary>Hook this up to a "Map Editor" button in your main menu.</summary>
    public void OpenEditor() => editor.EditorVisible = true;

    void BackToMenu()
    {
        if (editor.IsPlaytesting) editor.StopPlaytest();
        editor.EditorVisible = false;
        editor.ShowToast("Map editor closed", ToastType.Info);
    }
}

例:編集中にゲームの HUD を非表示にする

using UnityEngine;
using RTSTerrainStudio;

public class HudVisibility : MonoBehaviour
{
    [SerializeField] RTSRuntimeEditor editor;
    [SerializeField] GameObject hud;

    void OnEnable()
    {
        editor.VisibilityChanged += HandleVisibilityChanged;
        RTSRuntimeEditorEvents.PlaytestChanged += HandlePlaytestChanged;
        Refresh();
    }

    void OnDisable()
    {
        editor.VisibilityChanged -= HandleVisibilityChanged;
        RTSRuntimeEditorEvents.PlaytestChanged -= HandlePlaytestChanged;
    }

    void HandleVisibilityChanged(bool visible) => Refresh();
    void HandlePlaytestChanged(bool playtesting) => Refresh();

    // The editor stays "visible" during a play test (only its panels make room),
    // so both states are checked.
    void Refresh() => hud.SetActive(!editor.EditorVisible || RTSRuntimeEditorEvents.IsPlaytesting);
}

UI のカスタマイズ

スタイルの格納場所

ファイル(Resources/RTSTerrainStudio/UI 内)読み込みパス目的
RTSRuntimeTheme.tssRTSRuntimeEditor.ThemeResourcePath = "RTSTerrainStudio/UI/RTSRuntimeTheme"テーマスタイルシートです。Unity のデフォルトのランタイムテーマ(スライダー、トグル、テキストフィールド、スクロールビュー)と RTSRuntimeEditor.uss をインポートします。
RTSRuntimeEditor.ussRTSRuntimeEditor.StyleSheetResourcePath = "RTSTerrainStudio/UI/RTSRuntimeEditor"エディターのすべてのスタイルです。クラス名はすべて rts- で始まり、Label などの型セレクターは .rts-root 以下に限定されています。そのため、パネルでこのテーマを使用している場合でも、エディターがゲーム UI の見た目を変えることはありません。

Panel Settings で別のテーマを使用している場合は、エディターが自身のルートに RTSRuntimeEditor.uss を追加します。そのため、どちらの場合でも見た目は同じです。

カラートークン

すべての色は、.rts-root で定義された変数です。これらを変更すると、エディター全体の外観を変えられます。

変数デフォルト用途
--rts-panelrgba(20, 24, 30, 0.92)パネルの背景(トップバー、ツールバー、ツールパネル、ステータスバー)。
--rts-panel-solidrgb(22, 26, 33)不透明な背景(ダイアログ、ポップアップ)。
--rts-raised / --rts-hover / --rts-pressed不透明度 6% / 11% / 3% の白ボタンの状態。
--rts-border / --rts-border-strong不透明度 8% / 16% の白境界線と区切り線。
--rts-inputrgba(0, 0, 0, 0.32)テキストフィールドとスライダーの背景。
--rts-text / --rts-text-dim / --rts-text-faintrgb(226, 231, 238) / rgb(150, 160, 174) / rgb(104, 114, 128)テキスト、補助テキスト、ヒント。
--rts-accent / --rts-accent-hover / --rts-accent-dim / --rts-accent-text青(rgb(61, 169, 252))系選択中のツール、プライマリボタン、Play Test。
--rts-danger / --rts-danger-dimrgb(239, 83, 80)削除ボタンとエラーのトースト。
--rts-warning / --rts-successrgb(255, 183, 77) / rgb(102, 187, 106)警告と成功のトースト。
--rts-radius / --rts-radius-small6px / 4px角丸の半径。

パッケージのファイルを変更しない外観の変更

パッケージの Resources フォルダー内のファイルは直接編集しないでください。パッケージを更新すると上書きされます。変更は独自のスタイルシートに記述してください。

  1. プロジェクト内にスタイルシート(例:Assets/UI/MyEditorSkin.uss)を作成し、トークンをオーバーライドします。セレクター :root .rts-root はパッケージの .rts-root よりも詳細度が高いため、独自の値が優先されます。
    /* MyEditorSkin.uss: an orange accent and warmer panels */
    :root .rts-root {
        --rts-accent: rgb(255, 160, 50);
        --rts-accent-hover: rgb(255, 190, 110);
        --rts-accent-dim: rgba(255, 160, 50, 0.22);
        --rts-accent-text: rgb(255, 210, 160);
        --rts-panel: rgba(30, 24, 20, 0.94);
    }
  2. 起動時に、エディターの UI ドキュメントにスタイルシートを追加します。
    using UnityEngine;
    using UnityEngine.UIElements;
    using RTSTerrainStudio;
    
    public class EditorSkin : MonoBehaviour
    {
        [SerializeField] RTSRuntimeEditor editor;
        [SerializeField] StyleSheet skin;   // drag MyEditorSkin.uss here
    
        void Start()
        {
            // The document root holds the editor and its toast layer, so both get the new colours.
            editor.Document.rootVisualElement.styleSheets.Add(skin);
        }
    }

同じ方法で、個々のパーツのスタイルを変更することもできます。主なクラスは .rts-topbar、.rts-leftbar、.rts-toolpanel、.rts-statusbar、.rts-dialog、.rts-popup、.rts-toast(--success、--warning、--error のバリアントあり)、.rts-hint、.rts-playtest-bar、.rts-button、.rts-selected です。UI Toolkit Debugger(Window ▸ UI Toolkit ▸ Debugger)を使うと、ゲームの実行中にすべての要素のクラスを確認できます。

Panel Settings、スケール、ソート順

  • Panel Settings が空の場合(デフォルト):エディターは、RTS テーマを使用した独自の Panel Settings を作成します。UI は画面の高さに合わせてスケーリングされますが(基準解像度 1920 × 1080)、85% を下回ることはありません。その結果に Ui Scale の値が掛けられます。これは 4K 画面や、文字を大きくしたいプレイヤーに便利です。
  • 独自の Panel Settings を使用する場合:アセットを割り当てると、テーマとスケーリングを自分で制御できます。Tools ▸ RTS Terrain Studio ▸ Create Demo Content を実行すると、Resources/RTSTerrainStudio/UI/RTSRuntimePanelSettings.asset にすぐに使えるアセット(RTS テーマ、画面サイズに合わせたスケーリング、1920 × 1080)が作成されます。In-Game Editor Demo シーンはこのアセットを使用しています。
  • Enforce Minimum UI Scale(デフォルトでオン):アセットが画面サイズに合わせてスケーリングし、テクスチャにはレンダリングしない場合、エディターはそのアセットのランタイムコピーを使用します。アセット自体が変更されることはありません。コピーは、大きな画面ではアセットとまったく同じようにスケーリングされ、小さなウィンドウでも 85% を下回ることはなく、さらに Ui Scale の値が掛けられます。コピーのソート順はアセットより半段階上に設定されるため、同じアセットを使用するゲームの HUD よりもエディターが前面に表示されます(このとき、HUD はエディターとは別のパネルで表示されます)。アセットをそのまま使用するには(たとえば、ゲーム UI と 1 つのパネルを共有する場合)、このオプションをオフにします。この場合、Ui Scale と 85% の下限は適用されません。
  • Web ビルド:85% の下限は CSS ピクセル単位で計測されるため、高解像度の画面でも UI のサイズは変わりません。
  • Sorting Order:デフォルトは 1000 で、エディターが HUD の上に表示されます。UI の一部をエディターより前面に表示する必要がある場合は、この値を下げます。
  • 独自のコントロールの追加:RootElement に要素を追加します(上記のランチャーの例を参照)。追加した要素はエディターと一緒に非表示になり、ポインターがその上にあるときは、ほかのパネルと同様にペイントが行われません。

ゲーム内エディターのリリース

ゲーム内エディターを含めてゲームをビルドする前に、以下のリストを順に確認してください。

  1. Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、報告された問題を修正します。ビルドで特に重要なのは Shader references のチェックです(次の手順を参照)。
  2. シェーダーは RTSShaderReferences を通じてビルドに含まれます。Unity は、ビルド内のどのマテリアルにも使用されていないシェーダーをビルドから除外します。このパッケージはほとんどのマテリアルをランタイムで作成するため(地形、水、戦場の霧、およびエディターのギズモシェーダー Hidden/RTS Terrain Studio/GizmoLines と Hidden/RTS Terrain Studio/GizmoGhost)、それらのシェーダーを Resources/RTSTerrainStudio/RTSShaderReferences アセットで保持しています。このアセットは、スクリプトのコンパイル後、シェーダーのインポート時、および各プレイヤービルドの前に自動的に更新されます。アセットが存在しないか古くなっている場合は、Project Validator の Update 自動修正を使用します。これらのシェーダーがないと、ビルドでブラシの輪郭と装飾オブジェクトのプレビューが表示されません。
  3. Resources/RTSTerrainStudio フォルダーを残します。このフォルダーには UI/RTSRuntimeTheme.tss、UI/RTSRuntimeEditor.uss、RTSShaderReferences.asset が含まれており、エディターはこれらを Resources のパスで読み込みます。テーマが見つからない場合は、コンソールに Controls will be unstyled と表示されます。
  4. Panel Settings:Panel Settings アセットを割り当てた場合は、シーンがそのアセットを参照しているため、自動的にビルドに含まれます。フィールドを空のままにした場合は、ランタイムの Panel Settings がテーマを Resources から読み込みます(手順 3)。
  5. 入力:エディターは、Input System パッケージと旧 Input Manager の両方で動作します。詳しくは入力処理を参照してください。
  6. プレイヤー向けのオプションを決めます:Manual Url(MOD 制作ガイドへのリンク。空にするとボタンが非表示になります)、Show Quit Command(ゲームに独自のメニューがある場合はオフにします)、Toggle Key、Start Visible。
  7. マップフォルダー:RTSMapSerializer.StorageRoot(persistentDataPath)内のデフォルトの Maps フォルダーは、すべてのデスクトッププラットフォームで書き込み可能です。プレイヤーにサンプルマップを提供するには、初回起動時にそのフォルダーへコピーします(ゲームにマップを同梱するを参照)。
  8. Web ビルド:ウィンドウ全体に表示するテンプレートをインストールし、ブラウザーのストレージ、ダウンロード、キー操作、ホスティングについて説明している Web(WebGL)ビルドを確認します。
  9. カスタムツールとコードストリッピング:Managed Stripping Level を Minimal より高く設定する場合は、独自のツールに [Preserve] を付けます(コードストリッピングとカスタムツールを参照)。
  10. コンテンツ:プレイヤーが使用できるのは、エディターシーンの地形に割り当てられたタイルセット、植生ライブラリ、装飾ライブラリだけです。これらはシーンから参照されているため、ビルドに含まれます。
  11. 開発ビルドをテストします:ペイント、保存、マップを開く操作、テストプレイ、未保存の変更がある状態での終了を、それぞれ 1 回ずつ試します。

ゲーム内エディターをリリースしない場合は?エディターのコードは Runtime/RuntimeEditor に、メニューのサポートは Editor/RuntimeEditorSupport にあります。ゲーム内エディターが不要なプロジェクトでは、Runtime/RuntimeEditor フォルダーを削除できます。RTSRuntimeEditorEvents は Runtime/Core に残るため、テストプレイのイベントを受け取るゲームプレイコードは引き続きコンパイルできます。

トラブルシューティング

問題原因と解決策
No RTS Terrain found という通知が表示されます。シーンに有効な RTS Terrain がないか、割り当てられた地形が無効になっています。GameObject ▸ RTS Terrain Studio ▸ RTS Terrain で地形を追加するか、地形を有効にします。
No camera found という通知が表示されます。MainCamera タグが付いたカメラがなく、Target Camera も空です。カメラにタグを付けるか、カメラを割り当てます。
マップをクリックしても何も起こりません。ポインターがパネル上にあるか、Alt キーが押されている(カメラの回転)か、ダイアログが開いているか、アクティブなツールがありません。ツールがアクティブでない場合、ステータスバーには Pick a tool on the left と表示されます。
ショートカットが機能しません。テキストフィールドにキーボードフォーカスがあるか、ダイアログが開いています。Esc キーを押すか、マップをクリックします。
F5 キーまたは F2 キーを押しても何も起こりません。F5 キーは、エディターが表示されていて、ダイアログが開いていないときだけ機能します。切り替えキーは、テストプレイ中(先に Esc キーまたは F5 キーでテストプレイを終了します)とフィールドへの入力中は無視されます。Mac や多くのノートパソコンでは、ファンクションキーがメディアキーとして機能します。Fn キーを押したまま操作するか、Play Test ボタンを使用します。Web ビルドでは Open Map Editor ボタンも使用できます。
ゲームで Esc キーを使用したのに、テストプレイが終了してしまいます。そのフレームで RTSRuntimeEditorEvents.ConsumeEscape() を呼び出します(例を参照)。
テストプレイ中に何も起こりません。RTSRuntimeEditorEvents.PlaytestChanged を購読しているスクリプトがありません。RTSDemoGame または独自のコンポーネントを追加します(Play Test を参照)。
テストプレイ後、トーストに undo history was reset と表示されます。テスト中に、ゲームプレイコードがマップを置き換えたか、再読み込みしたか、サイズを変更しました。マップ自体は正しく復元されます。
UI が小さすぎるか、大きすぎます。Ui Scale を変更します。画面サイズに合わせてスケーリングしない独自の Panel Settings を割り当てた場合や、Enforce Minimum UI Scale をオフにした場合は、代わりにそのアセットでスケーリングを変更します。固定サイズのテンプレートを使用した Web ビルドでは、ウィンドウ全体に表示するテンプレートをインストールします(ウィンドウ全体に表示するテンプレートを参照)。
HUD がエディターを覆ってしまうか、エディターがメニューを覆ってしまいます。Sorting Order(デフォルトは 1000)を調整します。
コントロールにスタイルが適用されていません(Unity のデフォルトの白い外観)。Resources フォルダー内でテーマが見つかりませんでした。Resources/RTSTerrainStudio/UI を復元します。
ビルドでブラシの輪郭とプレビューが表示されません。ギズモシェーダーがビルドから除外されています。Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、シェーダー参照を更新します。
ウェルカムヒントが再表示されません。プレイヤーがヒントを閉じたためです。F1 ウィンドウの Open Welcome Hint を使用するか、PlayerPrefs のキー RTSTerrainStudio.InGameEditor.HintDismissed を削除します。
マップがマップブラウザーに表示されません。ブラウザーに表示されるのは、上部に表示されているマップフォルダーの直下にある .rtsmap ファイルだけです。ファイルをそこにコピーし、Refresh をクリックします。
マップに Unreadable or damaged file と表示されます。ファイルが有効な .rtsmap ファイルではないか、途中で切れています(たとえば、コピーが中断された場合)。
Web ビルドで保存したマップが消えました。ブラウザーはマップを Web サイトごとに保存します。そのため、別のアドレスで開いたり閲覧データを削除したりすると、マップがない状態から始まります。また、Safari は 7 日間使用されていない Web サイトのデータを削除することがあります。File ▸ Download Map を使用してコピーを保存しておきます(ブラウザー内のマップを参照)。
ビルドしたゲームのエディターにツールがありません。コードストリッピングによってツールが削除されています。パッケージの Runtime/link.xml を残し、独自のツールには [Preserve] を付けます(コードストリッピングとカスタムツールを参照)。

レンダリングとワールドシステム

この章では、マップがピクセルになるまでの仕組みを説明します。地形メッシュがどのように構築・シェーディングされるか、そして植生、水、装飾オブジェクト、シーンのライティングがどのように描画されるかを扱います。各セクションでは、まず画面に表示される内容と変更できる設定を説明します。その後で、プログラマーが必要とするスクリプティング API、シェーダーの規約、パフォーマンスの数値を取り上げます。

レンダリングコンポーネント

すべてのシステムは RTSTerrain の同じ MapData を読み取り、編集によって変更された部分だけを再構築します。各システムは、地形のゲームオブジェクト上にある個別のコンポーネントです。GameObject ▸ RTS Terrain Studio ▸ RTS Terrain を使うと、これらがすべて自動的に追加されます。

コンポーネント描画内容生成されるオブジェクトセクション
RTSTerrain地面、崖、スロープ(チャンクメッシュ、共有マテリアル 1 つ)、セルグリッド、オーバーレイ、ブラシカーソル「RTS Chunks」ルートと、チャンクごとに 1 つの子オブジェクト地形のレンダリング、地形シェーダー
RTSVegetationRendererペイントした草や植物(ディテール)、木非表示の「RTS Vegetation Objects」(コライダーのプロキシ、ゲームオブジェクトの木)植生
RTSWaterRenderer水深に応じた色、波、屈折、泡を備えた透明な水面非表示の「RTS Water」ルートと、水を含むチャンクごとに 1 つの子オブジェクト水
RTSDecorManagerプロップとドゥーダッド(ゲームオブジェクトまたは GPU インスタンシング)スポーンしたオブジェクト用の「RTS Decor」コンテナー装飾オブジェクト
RTSEnvironment(オプション)それ自体は何も描画しません。マップの太陽、環境光、フォグをシーンに適用しますなし環境

生成されたオブジェクトには DontSave が設定されており、シーンファイルに書き込まれることはありません。.rtsmap ファイルが唯一の正規データであり、シーンを開くとすべてがこのファイルから再構築されます。シーンビューで生成されたチャンク、木、水面をクリックすると、代わりにそれを所有する地形が選択されます。

レンダラーコンポーネントのいずれかが欠けている場合、RTSTerrain のインスペクターに警告(例:「No Water Renderer: painted water is not shown.」)が Add ボタンとともに表示されます。

地形のレンダリング

チャンク

マップは、1 辺が 16、32(デフォルト)、または 64 セルの正方形のチャンクに分割されます。各チャンクは、MeshFilter、MeshRenderer、オプションの MeshCollider、RTSTerrainChunk コンポーネントを持つゲームオブジェクトです。すべてのチャンクは 1 つのマテリアルインスタンス(RTSTerrain.TerrainMaterial)を共有しており、これにより地形は SRP Batcher との互換性を保っています。

  • チャンクは行ごとに格納され、インデックスは z * ChunkCount.x + x です。マップサイズがチャンクサイズの倍数でない場合、最後の行と列のチャンクは小さくなることがあります。
  • 編集では、影響を受けるチャンクだけが再構築されます。頂点の変更は隣接するセルの法線と崖の形状にも影響するため、変更された矩形はそれぞれ 1 セル分拡張されます。そのため、チャンクの境界付近でペイントすると、隣のチャンクも再構築されます。
  • 地面テクスチャのペイントでは、メッシュは再構築されません。更新されるのはスプラットテクスチャだけです(スプラット解像度を参照)。
  • 水のペイントでも、地形チャンクは再構築されません。反応するのは水レンダラーだけです。

チャンクメッシュはマップローカル空間にあります。チャンクオブジェクトは、地形の非表示の RTS Chunks ルートの下で恒等変換のローカルトランスフォームを持ちます。このルートが地形のスケールを打ち消すため、オブジェクト空間はマップローカル空間と一致します(X/Z は 0 からマップのワールドサイズまで、Y が上方向)。地形のトランスフォームは、移動と Y 軸周りの回転が可能です。スケールは無視されます。

メッシュ生成

RTSChunkMeshBuilder は Burst ジョブでチャンクメッシュを生成し、Mesh.MeshDataArray を通じて書き込みます。

  1. 要求されたチャンクは、最大 RTSChunkMeshBuilder.MaxChunksPerBatch(256)個ずつのバッチにまとめられます。これにより、非常に大きなマップでのピーク時のメモリ使用量が抑えられます。
  2. 再構築が必要なチャンクが少数だけの場合(ペイント中の典型的なケース)、各チャンクは行単位の帯に分割されます。各帯は別々のワーカースレッドで生成されてから結合されるため、1 つのチャンクの編集でもすべての CPU コアが使われます。
  3. BuildChunkGeometryJob(Burst、並列)が三角形を作成します。平滑セルとスロープセルは、S × S 個のクアッドからなる規則的なグリッドになります(S = Surface Subdivisions)。崖セルは、より細かいサブグリッド(Cliff Subdivisions)上でマーチングスクエア法によってトレースされます。これにより、丸みのある輪郭線、各レベルの平らな床、その間の垂直な壁面が生成されます。
  4. コライダーデータは Physics.BakeMesh で並列にベイクされ、チャンクの MeshCollider に割り当てられます。
  5. Build は戻る前にすべての処理を完了するため、編集は同じフレーム内で表示に反映されます。

各頂点の位置はマップ上の位置だけで決まるため、隣接するセルやチャンクは常にビット単位で同一の境界頂点を生成します。そのため、チャンク間に隙間ができません。ジェネレーターは基準面 MapGeometryView に従います。この基準面は、高さのクエリ、通行判定、水レンダラーでも使用されています。したがって、描画される地面は、ゲームプレイが認識する地面とまったく同じです。

頂点フォーマットは RTSTerrainVertex です(28 バイト:位置、法線、パックされたカラー)。カラーにはシェーダー用のデータが格納されます:r = 崖の壁面マスク、g = 崖タイプ、b = ベイクされたアンビエントオクルージョン。UV やタンジェントはありません。シェーダーは頂点位置からテクスチャをマッピングします。

崖の壁面の形状

崖はプロシージャルに生成されるため、アーティストが作成した崖パーツは必要ありません。壁面の見た目は、次の 4 つの設定で制御します。

設定デフォルト範囲説明
Cliff Noise0.180–0.5崖の壁面の頂点を水平方向にランダムにずらす量(セルに対する割合)。壁面に自然な岩の質感を与えます。0 = 完全に滑らかな壁面。
Cliff Noise Scale1.6≥ 0.1崖のノイズの特徴サイズ(ワールド単位)。値が小さいと細かくゴツゴツした岩に、大きいと大きくなだらかな膨らみになります。
Cliff Slant0.120–0.4崖の上端を後方に引っ込める量(セルに対する割合)。壁面が完全な垂直ではなく、わずかに傾斜します。
Cliff Wall Segments31–8崖レベル 1 段あたりの垂直方向のセグメント数。セグメントを増やすとノイズに使える頂点が増えて起伏が大きくなりますが、その分、頂点数も増えます。

ノイズと傾斜を合わせても、頂点がセルの 45% を超えて移動することはありません(RTSChunkMeshParams.MaxDisplacementFraction)。2 つの設定の合計がこの値を超える場合は、両方がまとめて縮小されるため、壁面の隣にある床の三角形が折り重なることはありません。スロープ付近とマップ境界では変位が徐々に弱まるため、それらの縁はまっすぐなままです。

また、壁面のジオメトリは、ベイクされたアンビエントオクルージョン(頂点カラーの b)によって根元が暗くなります。2 つの壁面がセルの辺で接する箇所では両方が同じ明るさを使うため、積み重なった崖や結合した崖も継ぎ目なく見えます。

コライダーとクエリ

Collider Mode = MeshCollider(デフォルト)の場合、各チャンクは Chunk Layer 上に MeshCollider を持ちます。このコライダーは、編集のたびにメッシュと一緒に再構築されます。ゲームが Unity の物理演算(物理レイキャスト、リジッドボディ、NavMesh のベイク)に依存している場合に使用します。

ColliderMode.None の場合、コライダーは作成されません。それでも、物理演算を使わずに地形へクエリを実行できます。RTSTerrain.Raycast、GetHeight、GetNormal、GetCliffLevel、IsWater、GetPathing は解析的な基準面に対して動作し、編集モードでもランタイムでも使用できます。これにより、コライダーに必要なメモリとクッキング時間を節約できます。

using RTSTerrainStudio;
using UnityEngine;

/// Finds the terrain point under the mouse, with or without chunk colliders.
public class TerrainPointer : MonoBehaviour
{
    [SerializeField] Camera viewCamera;

    void Update()
    {
        if (viewCamera == null) return;
        Ray ray = viewCamera.ScreenPointToRay(RTSInput.PointerPosition);

        // 1. Physics-free: works even when Collider Mode is None.
        //    RaycastAll tests every RTSTerrain in the scene and returns the nearest hit.
        if (RTSTerrain.RaycastAll(ray, out RTSTerrainHit hit))
        {
            Debug.Log($"Cell {hit.cell} at {hit.point}, cliff level {hit.terrain.GetCliffLevel(hit.point)}");
        }

        // 2. Unity physics: needs Collider Mode = MeshCollider.
        //    RTSTerrainChunk.GetTerrain maps the collider of a chunk back to its terrain.
        if (Physics.Raycast(ray, out RaycastHit physicsHit, 1000f))
        {
            RTSTerrain terrain = RTSTerrainChunk.GetTerrain(physicsHit.collider);
            if (terrain != null) Debug.Log($"Physics hit on terrain '{terrain.name}'");
        }
    }
}

地形のレンダリング設定

これらの設定は RTSTerrain のインスペクターにあり、Chunks & Geometry、Cliff Shape、Physics、Rendering にグループ分けされています。インスペクターで設定を変更すると、地形は自動的に再構築されます。コードから変更する場合は、terrain.Settings を変更してから terrain.RebuildAll() を呼び出します。

設定デフォルト説明
Chunk Size32チャンクの 1 辺あたりのセル数(16、32、64 のいずれか)。チャンクが小さいほど編集中の再構築が速くなり、大きいほどドローコールが少なくなります。詳しくはチャンクサイズの選択を参照してください。
Surface Subdivisions2平滑な地面とスロープでの、セルの 1 辺あたりのクアッド数(1–4)。1 = クラシック RTS の見た目、2–4 = より滑らかな丘。
Cliff Subdivisions4崖セル内で丸みのある崖の輪郭線をトレースするためのサブグリッド解像度(2–8)。偶数で、かつ Surface Subdivisions の倍数になるように切り上げられます(細分化を参照)。
Cliff Noise、Cliff Noise Scale、Cliff Slant、Cliff Wall Segments崖の壁面の形状を参照崖の壁面の見た目。
Collider ModeMeshColliderNone または MeshCollider(チャンクごとに 1 つ、編集後に再構築)。
Chunk Layer0 (Default)チャンクのゲームオブジェクトのレイヤー。コライダーとカメラのカリングマスクで使用されます。ペイントしたディテールもこのレイヤーに描画されます。
Cast Shadowsオンチャンクのレンダラーのシャドウキャストモード。
Receive Shadowsオンチャンクが影を受けるかどうか。
Material Override空デフォルトのマテリアルの代わりに使用する任意のマテリアル(マテリアル、オーバーレイ、グリッドを参照)。
Show Gridオフセルグリッドを常に表示します(主に編集用)。
Grid Opacity0.35Show Grid がオンのときのグリッドの不透明度。

インスペクターの下部にある Rebuild Terrain ボタン(RTSTerrain.RebuildAll())は、すべてのチャンク、テクスチャ、通行マップを破棄して再作成します。植生、水、装飾オブジェクトも一緒に再構築されます。

マテリアル、オーバーレイ、グリッド

デフォルトでは、地形はシェーダー RTS Terrain Studio/Terrain(RTSTerrain.TerrainShaderName)から独自のマテリアルを作成します。地形は、次の順序でマテリアルのソースを選択します。

  1. 地形設定の Material Override(RTSTerrainSettings.materialOverride)。
  2. タイルセットの Material Override(RTSTileset.materialOverride)。そのタイルセットを使用するすべての地形が同じ見た目になります。
  3. RTS Terrain Studio/Terrain を使用する新しいマテリアル。

地形は常にソースマテリアルのコピーを使用し、そこにスプラットマップ、テクスチャ配列、マップパラメーターを設定します。オーバーライドするマテリアルは、地形シェーダー、または同じプロパティを宣言したシェーダーを使用する必要があります(シェーダープロパティの規約を参照)。

地形はコピーを描画するため、オーバーライドマテリアルのアセットを後から編集しても、その変更は自動的には反映されません。編集後は Rebuild Terrain をクリックしてください(または RebuildAll() を呼び出します)。ランタイムで再構築せずに値を変更するには、terrain.TerrainMaterial に直接設定します。

地形シェーダーが見つからない場合(たとえば、シェーダーが含まれていないプレイヤービルド)、地形は無地のグレーの URP Lit マテリアルで描画され、「Shader 'RTS Terrain Studio/Terrain' was not found.」という警告がログに出力されます。Tools ▸ RTS Terrain Studio ▸ Validate Project を実行してください。その「Shaders included in builds」チェックによって RTSShaderReferences アセットが作成され、パッケージのすべてのシェーダーがビルドに含まれるようになります(ゲームのビルドを参照)。

オーバーレイ:SetOverlayTexture(texture, opacity) は、任意のテクスチャをマップ全体(最初の頂点から MapData.WorldSize まで)に重ねて表示します。地形の色は、テクスチャのアルファ × 不透明度の割合で、テクスチャの RGB に向かってブレンドされます。エディターはこれを通行判定の表示に使用しています。ヒートマップ、領土、建物の配置プレビューなどにも利用できます。オーバーレイは地表の色の一部なので、地形と同じようにライティングと影の影響を受けます。

グリッド:SetGridVisible(visible, opacity) を使うと、アンチエイリアスされたセルグリッドをコードから強制的に表示できます。4 本に 1 本の線が太く描画されます。シーンビューでの編集中は、G キーでグリッドの表示を切り替えられます。IsGridVisible は、グリッドがコードまたは Show Grid によって表示されているときに true になります。

using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;

/// Shows unwalkable cells in red on top of the terrain (one overlay pixel per cell).
public class WalkabilityOverlay : MonoBehaviour
{
    [SerializeField] RTSTerrain terrain;
    Texture2D overlay;

    public void Show()
    {
        MapData map = terrain != null ? terrain.Map : null;
        if (map == null) return;

        // One texel per cell. Point filtering keeps hard cell borders.
        overlay = new Texture2D(map.Width, map.Height, TextureFormat.RGBA32, false)
        {
            filterMode = FilterMode.Point,
            wrapMode = TextureWrapMode.Clamp
        };

        var pixels = new Color32[map.Width * map.Height];
        for (int z = 0; z < map.Height; z++)
        {
            for (int x = 0; x < map.Width; x++)
            {
                // Texture row 0 is the map edge at z = 0, like the map itself.
                Vector3 world = terrain.CellCenterToWorld(new int2(x, z));
                bool walkable = terrain.IsWalkable(world);
                // Alpha 0 leaves the terrain untouched, alpha 160 tints it red.
                pixels[z * map.Width + x] = walkable ? new Color32(0, 0, 0, 0) : new Color32(255, 40, 40, 160);
            }
        }
        overlay.SetPixels32(pixels);
        overlay.Apply(false, false);

        terrain.SetOverlayTexture(overlay, 0.8f);
    }

    public void Hide()
    {
        if (terrain != null) terrain.SetOverlayTexture(null, 0f);   // null hides the overlay
        if (overlay != null) Destroy(overlay);
        overlay = null;
    }

    void OnDisable() => Hide();
}

RTSTerrain のレンダリング API の概要は次のとおりです。

メンバー説明
Material TerrainMaterialすべてのチャンクで共有されるマテリアルインスタンス(地形が有効になる前は null)。
Transform ChunkRootチャンクのゲームオブジェクトを保持する「RTS Chunks」オブジェクト。
IReadOnlyList<RTSTerrainChunk> Chunks, int2 ChunkCount, int ChunkSizeInCellsチャンクのグリッド。
RTSTerrainChunk GetChunk(int2 chunkCoord), GetChunkAtCell(int2 cell)チャンクを取得します。マップ外では null を返します。
bool IsRenderingReadyチャンク、テクスチャ、マテリアルがそろうと true になります。
Texture GetSplatTexture(int index)スプラットテクスチャ 0–3(レイヤー i はマップ i / 4 のチャンネル i % 4 に格納)。
void SetOverlayTexture(Texture tex, float opacity), OverlayTexture, OverlayOpacityマップ全体のオーバーレイ。非表示にするには null または 0 を渡します。
void SetGridVisible(bool visible, float opacity), IsGridVisibleコードから制御するセルグリッド。
void RebuildChunkMeshes(RectInt cellRect)指定範囲にかかるチャンクのメッシュとコライダーをすぐに再構築します。通常は NotifyChanged がこの処理を行うため、呼び出す必要はありません。
void RefreshMaterial()タイルセット、テクスチャ配列、マップパラメーターをすぐにマテリアルへ再適用します。
RTSTerrainChunk: Terrain, ChunkCoord, CellRect, Mesh, MeshRenderer, MeshCollider, LocalBounds, static GetTerrain(Collider)チャンクごとのデータ。GetTerrain は、物理クエリのヒットから対応する地形を取得します。

地形シェーダー

RTS Terrain Studio/Terrain(Shaders/RTSTerrain.shader)は、手書きの URP 17.3 用 Lit シェーダーです。Forward、Forward+、Deferred の各レンダリング、SRP Batcher、影、SSAO、デカール、ライトレイヤー、ライトクッキー、ライトマップに対応しています。このシェーダーのマテリアルを手作業で編集することはありません。すべてのプロパティは、マップとそのタイルセットをもとに地形が設定します。

主な機能

機能内容制御方法
16 個の地面レイヤースプラットでブレンドされる最大 16 個のレイヤー。XZ 平面マッピングでサンプリングされます。各ピクセルでは、目に見えるウェイトを持つレイヤーだけがサンプリングされる(動的分岐)ため、マップにペイントするレイヤーを増やしても、すべてのピクセルの負荷が上がるわけではありません。タイルセットの地面レイヤー。Tile Size、Tint、Normal Strength、Smoothness、Metallic
ハイトブレンドブレンド領域では、テクスチャの高さが高いほうのレイヤーが優先されます。そのため、草は石の上にフェードして重なるのではなく、石の間から生えて見えます。コントラスト = Height Blend² × 16。0 にすると単純な線形ブレンドになります。レイヤーごとの Height Blend(0–1)と、高さのソース:高さテクスチャ(R チャンネル)またはアルベドのアルファ(Height From Albedo Alpha)
アンチタイリング低周波ノイズで選ばれた領域では、UV を回転・スケールした 2 つ目のサンプルへテクスチャがフェードし、目に見える繰り返しを崩します。強さが最大のとき、地面の約半分が 2 つ目のサンプルを使用します。これらの領域では、テクスチャのサンプリングが 1 回増えます。レイヤーごとの Anti Tiling(0–1)
マクロバリエーションマップ全体にわたる大きなスケールの明るさの変化と、暖色・寒色のわずかな色相の変化。遠くから見たときの繰り返しを目立たなくします。崖には半分の強さで適用されます。タイルセットの Macro Variation(0–1、デフォルト 0.25)
トライプラナーの崖最大 4 つの崖タイプが 3 つの軸すべてに沿って投影されるため、垂直な壁面が引き伸ばされて見えることはありません。法線にはホワイトアウトブレンドを使用します。2 つの崖タイプが接する場所では、短い帯状の範囲でクロスフェードします。タイルセットの崖レイヤー。Triplanar Sharpness(1–16、デフォルト 6)
傾斜面の岩約 50° より急な地面は、徐々に崖のマテリアルに変わります(約 63° を超えると完全に岩になります)。そのため、急な丘に引き伸ばされた草が表示されることはありません。岩は、その高さテクスチャの値が高い場所で地面から突き出して見えます。自動
オクルージョン崖の麓にベイクされた頂点 AO に、崖の高さテクスチャから求めたキャビティ項を掛け合わせたもの。自動
セルグリッドアンチエイリアスされた線(4 セルごとに太くなります)。遠くでは、モアレ模様が生じる前にフェードアウトします。Show Grid / SetGridVisible / シーンビューで G キー
オーバーレイマップ全体の RGBA オーバーレイ(通行判定の表示、ヒートマップ)。SetOverlayTexture。シーンビューでは P キーで通行オーバーレイを順に切り替え
ブラシカーソルブラシの輪郭と減衰は、シェーダー内で地表の正確な位置に投影されます。そのため、表示されている場所がそのままペイントされる場所になります(ディスプレイのスケーリングに依存しません)。対称ペイント用に最大 4 つの位置に対応します。最後に描画されるため、戦場の霧の中でも見えたままです。RTSBrushPreview(エディターツールが設定)
戦場の霧(フォールバック)戦場の霧のレンダラー機能がインストールされていない場合は、シェーダー自体が未探索のエリアと現在見えていないエリアを暗くします。RTSFogOfWarManager(戦場の霧を参照)
タイルセットなしのフォールバックタイルセットがなくてもエラーにはならず、地形はニュートラルなグレーの地面と濃いグレーの崖で描画されます。自動
ライティングオプションスペキュラハイライトと環境反射をオフにして、シェーディングの負荷を軽くします。マテリアルのトグル Specular Highlights / Environment Reflections(オーバーライドマテリアル上)

これらの値のほとんどはタイルセットの設定です。レイヤーの作成方法については、タイルセットを参照してください。

パス:ForwardLit(UniversalForward)、GBuffer(UniversalGBuffer、GLES/GLCore では非対応)、ShadowCaster、DepthOnly、DepthNormals、Meta。GBuffer 以外のすべてのパスは Shader Model 3.5 を対象としているため、地形は WebGL 2.0 と OpenGL ES 3.0 でも描画されます。GBuffer パスには Shader Model 4.5 が必要です。レンダーキューは Geometry-100 なので、大きな地形がほかの不透明オブジェクトより先に深度バッファを埋めます。LOD クロスフェードは使用しません。

スペキュラハイライトと反射の無効化

ローエンドデバイス向けや、よりフラットな手描き風の見た目にしたい場合は、ライティングのトグルをオフにしたオーバーライドマテリアルを使用します。

  1. プロジェクトウィンドウで Create ▸ Material を選択し、名前を付けます(例:Terrain Low End)。
  2. マテリアルのインスペクターで、シェーダーを RTS Terrain Studio ▸ Terrain に設定します。
  3. マテリアルのインスペクターの下部で、Specular Highlights と Environment Reflections のどちらか一方、または両方をオフにします。ほかのプロパティは地形によって上書きされるため、変更しないでください。
  4. このマテリアルを、タイルセット(そのタイルセットを使用するすべての地形)または個々の地形(RTSTerrain のインスペクターの Rendering セクション)の Material Override に割り当てます。
  5. 地形がすぐに変わらない場合は、Rebuild Terrain をクリックします。

これには、コードから作成したマテリアルではなく、マテリアルアセットを使用してください。Unity は、ビルドに含まれるマテリアルアセットが使用しているマテリアルキーワードのシェーダーバリアントしか保持しません。そのため、コードからのみ設定したキーワードは、プレイヤービルドから除外される可能性があります。

シェーダープロパティの規約

すべてのプロパティは、RTSShaderIDs の ID を使って RTSTerrain のレンダリングコードによって設定されます。カスタムシェーダーでは、使用するプロパティを同じ名前で宣言する必要があります。スカラーとベクトルは UnityPerMaterial 定数バッファ(SRP Batcher)に格納されます。

プロパティ型内容
_RTS_SplatMap0 … _RTS_SplatMap3Texture2D、リニア RGBAスプラットウェイト。レイヤー i はマップ i / 4 のチャンネル i % 4 にあります。すべてのウェイトの合計は 1 です。UV = positionOS.xz * _RTS_MapSize.zw。バイリニアでサンプリングされ、クランプされます。
_RTS_AlbedoArrayTexture2DArray、sRGBRGB = アルベド、A = 高さ(レイヤーに高さがない場合は 0.5)。スライス 0–15 = 地面レイヤー、16–19 = 崖レイヤー(RTSTileset.CliffSliceOffset)。
_RTS_NormalArrayTexture2DArray、リニアRGB = タンジェント空間の法線 × 0.5 + 0.5(配列の構築時に DXT5nm/BC5 から展開済み)。
_RTS_LayerDataTexture2D 20 × 4、float、Load で読み取り配列スライスごとに 1 列。以下の表を参照してください。
_RTS_LayerCountfloatタイルセットの地面レイヤー数。最小 1(1–16 にクランプ)。タイルセットがない場合、RTSTerrain は 1、ニュートラルなレイヤーデータ、プレースホルダーのテクスチャ配列(RTSTextureArrayBuilder.FallbackResolution = 128)を渡すため、地形はグレーの地面と濃いグレーの崖で表示されます。シェーダーは 0 も「データなし」として扱い、その場合は _RTS_LayerData を読み取らずに RTS_FALLBACK_GROUND_ALBEDO / RTS_FALLBACK_CLIFF_ALBEDO を描画します。これは、RTSTerrain の外部でカスタムマテリアルを使用した場合にのみ発生します。
_RTS_MapSizefloat4(ワールドサイズ X、ワールドサイズ Z、1 / X、1 / Z)。
_RTS_MapParamsfloat4(セルサイズ、崖レベルの高さ、マクロバリエーション、トライプラナーのシャープネス)。標準のシェーダーは崖レベルの高さを使用しません。これは、高さに応じた色付けなどのカスタマイズ用に用意されています。
_RTS_OverlayTex, _RTS_OverlayOpacityTexture2D RGBA、floatマップ全体のオーバーレイ。不透明度 0 = オフ。
_RTS_GridParamsfloat4(有効 0/1、不透明度、未使用、未使用)。
_SpecularHighlights, _EnvironmentReflectionsfloat のトグルキーワード _SPECULARHIGHLIGHTS_OFF、_ENVIRONMENTREFLECTIONS_OFF。
_RTS_LayerParams[20], _RTS_LayerTints[20], _RTS_LayerParams2[20]float4 配列同じレイヤーごとの値を float4 配列として提供します。配列を前提に書かれたカスタムシェーダー向けです。標準のシェーダーは _RTS_LayerData だけを読み取ります。

RTSShaderIDs.WorldToMap(_RTS_WorldToMap)は予約済みで、RTSTerrain によって設定されることはありません。オブジェクト空間(positionOS)を使用してください。チャンクメッシュでは、オブジェクト空間がマップローカル空間になります。

_RTS_LayerData のレイアウト(列 = 配列スライス):

行xyzw
01 / タイルサイズ法線の強さ滑らかさハイトブレンド
1リニアカラーとしてのティント RGB(Color.linear)メタリック
2アンチタイリング高さの有無(0/1)レイヤー使用(0/1)0
3ミニマップの色(シェーダーでは読み取らない)

「レイヤー使用」= 0 の崖スロット(16–19)は、崖スロット 16 として描画されます。タイルセットの崖レイヤー数を超える崖タイプをペイントしても、岩として表示されます。

メッシュ入力:POSITION(マップローカル)、NORMAL(平滑化済み、オブジェクト空間)、COLOR(r = 崖の壁面マスク、g = 崖タイプ × 85 / 255 で、タイプ 0–3 はそれぞれ 0、1/3、2/3、1 に対応、b = アンビエントオクルージョン、a = 未使用)。UV とタンジェントはありません。

グローバル変数(Shaders/Include/RTSCommon.hlsl で定義され、パッケージのすべてのシェーダーで共有):

グローバル変数設定元内容
_RTS_FogOfWarTex, _RTS_FogOfWarParams, _RTS_FogOfWarUnexplored, _RTS_FogOfWarExplored, _RTS_FogOfWarEnabledRTSFogOfWarManager可視性テクスチャ(R = 可視、G = 探索済み)、マッピング、ティント。HLSL:RTS_SampleFogOfWar(positionWS)、RTS_ApplyFogOfWar(color, positionWS)。
_RTS_BrushPositions[4], _RTS_BrushRotations[4], _RTS_BrushCount, _RTS_BrushParams, _RTS_BrushColor, _RTS_BrushMaskRTSBrushPreviewブラシカーソル。HLSL:RTS_BrushCursor(positionWS) は色(rgb)とカバレッジ(a)を返します。
_RTS_WindRTSVegetationRendererxy = 方向、z = 強さ、w = 速度。

カスタム地形シェーダーの作成

地形の地表に関するロジックはすべて、1 つのインクルードファイル Shaders/Include/RTSTerrainInput.hlsl にまとめられています。パスファイルの役割は、得られた地表にライティングを適用することだけです。見た目を変更するには、パッケージのシェーダーのコピーから始めます。

  1. Shaders/RTSTerrain.shader と Shaders/Include フォルダー全体を、相対的な配置を保ったまま、自分のプロジェクトのフォルダー(たとえば Assets/MyGame/Shaders)にコピーします。シェーダーはパスファイルを Include/… としてインクルードしており、インクルードファイル同士も相対パスで互いを参照しています。
  2. オリジナルと競合しないように、コピーの 1 行目でシェーダー名を変更します(例:Shader "MyGame/Terrain")。
  3. 変更を加えます。通常は、コピーした RTSTerrainInput.hlsl の RTS_EvaluateTerrainSurface を変更します。
  4. 自分のシェーダーでマテリアルを作成し、(タイルセットまたは地形の)Material Override に割り当てます。このマテリアルアセットにより、シェーダーが確実にプレイヤービルドに含まれるようにもなります。
  5. Rebuild Terrain をクリックします。

コピーで作業すれば、パッケージを更新しても変更が上書きされることはありません。更新後に最新の修正を取り込みたい場合は、コピーを新しいパッケージのファイルと比較してください。

例:高い台地に雪を積もらせる。コピーした RTS_EvaluateTerrainSurface の末尾、return surface; の直前に次のコードを追加します。

// Snow on everything above cliff level 6. positionOS.y is the map-local ground height, and
// _RTS_MapParams.y the height of one cliff level, so their ratio is the (fractional) cliff level.
half level = (half)(positionOS.y / max(_RTS_MapParams.y, 1e-3));
// Soft border between level 5.5 and 6.5; cliff walls (cliffBlend = 1) stay rocky.
half snow = smoothstep(5.5h, 6.5h, level) * (1.0h - surface.cliffBlend);
surface.albedo = lerp(surface.albedo, half3(0.92h, 0.94h, 0.97h), snow);
surface.smoothness = lerp(surface.smoothness, 0.35h, snow);

RTSTerrainInput.hlsl の主な構成要素は次のとおりです。

関数 / 型目的
struct RTSTerrainSurface地表評価の結果:アルベド、メタリック、滑らかさ、オクルージョン、ピクセルごとの法線とジオメトリ法線(オブジェクト空間)、タンジェントフレーム、cliffBlend(0 = 地面、1 = 崖)。
RTS_EvaluateTerrainSurface(positionOS, normalOS, vertexColor, dPdx, dPdy)地表全体の評価:崖の被覆、地面、崖、高さベースの遷移、マクロバリエーション、オクルージョン。
RTS_EvaluateGround(positionXZ, dPdx, dPdy, detileNoise), RTS_AccumulateGroundLayer(…)ハイトブレンドとアンチタイリングを適用した、スプラットブレンドの地面レイヤー(float のアキュムレーター RTSGroundAccumulator)。
RTS_EvaluateCliff(cliffTypeValue, positionOS, dPdx, dPdy, normalOS), RTS_SampleCliffLayer(…), RTS_TriplanarWeights(normalOS, sharpness)トライプラナーの崖マテリアルと、崖タイプ間のクロスフェード。
RTS_LoadLayerData(slice) → RTSLayerData_RTS_LayerData の 1 列を読み取ります。
RTS_DecodeArrayNormal(encoded, strength)法線配列のテクセルをデコードします。
RTS_EvaluateNoise(positionXZ), RTS_ValueNoise, RTS_Hash21プロシージャルノイズ(テクスチャ不使用)。モバイル GPU でも安定して動作します。
RTS_GridMask(…), RTS_ApplyEditorOverlays(…)グリッド線とオーバーレイテクスチャ。
RTS_CreateSurfaceData(surface)地表を URP の SurfaceData に変換します。
定数RTS_MAX_GROUND_LAYERS(16)、RTS_CLIFF_SLICE_OFFSET(16)、RTS_MAX_CLIFF_TYPES(4)、RTS_MIN_LAYER_WEIGHT(1/255)、RTS_FALLBACK_GROUND_ALBEDO、RTS_FALLBACK_CLIFF_ALBEDO

テクスチャのサンプリングには、明示的な勾配(SampleGrad)を使用します。勾配 dPdx/dPdy は、フラグメントシェーダーの先頭で、どの分岐よりも外側で 1 回だけ計算されます。分岐の中に独自のサンプリングを追加する場合も、この構成を維持してください。そうしないと、分岐の境界にミップマップの継ぎ目が現れます。

フォワードパスでは、地表の評価、エディターオーバーレイ、URP ライティング(UniversalFragmentPBR)、Unity のフォグ、戦場の霧、ブラシカーソルの順に処理が適用されます。

植生

RTSVegetationRenderer は、マップから 2 種類の植生を描画します。

  • ディテール:密度としてペイントされる草、花、小さな植物。パッチごとに Burst ジョブで生成され、プロシージャルな GPU インスタンシングで描画されます。ゲームオブジェクトとして存在することはありません。
  • 木:個別に配置されるインスタンス。GPU インスタンシングで描画する(最速)か、プレハブのゲームオブジェクトとしてスポーンする(伐採などのゲームプレイ用)かを選べます。

どちらも、RTSVegetationRenderer.PatchCells(16)× 16 セルのパッチ単位で管理されます。パッチはカリングと差分再構築の単位で、編集すると、その編集が影響したパッチだけが再生成されます。レンダラーは編集モードと再生モードの両方で、ゲームビューとシーンビューのカメラに対して動作します。

セットアップ

  1. 地形のゲームオブジェクトを選択します。Vegetation Renderer がない場合は、RTSTerrain のインスペクターの警告の横にある Add をクリックします(または Add Component ▸ RTS Terrain Studio ▸ Vegetation Renderer を使用します)。
  2. RTSTerrain のインスペクターの Assets セクションで Vegetation Library を割り当てます。ライブラリがない場合、Vegetation Renderer のインスペクターに「The terrain has no vegetation library, so nothing can be drawn.」という警告が表示されます。
  3. Vegetation ツールでディテールをペイントし、木を配置します(編集中にシーンビューで 4 キー)。
  4. インスタンシングで描画する木については、そのマテリアルで Enable GPU Instancing をオンにします。Tools ▸ RTS Terrain Studio ▸ Validate Project は、この設定がオフになっているマテリアルを一覧表示し、ワンクリックで修正できます。

ライブラリはプロトタイプ(メッシュ、テクスチャ、色、サイズ、距離)を定義します。プロトタイプの全フィールドについては、植生ライブラリを参照してください。

Vegetation Renderer のインスペクターには、リアルタイムの統計情報(Visible Details、Total Details、Visible Trees、Tree Objects、Colliders)が表示されます。また、Rebuild ボタン(RebuildAll())と Select Library ボタンもあります。

レンダラー設定

設定デフォルト説明
Quality
Detail Density Multiplier1テクセルあたりのディテールインスタンス数に掛ける係数(0–2、0 = ディテールなし)。変更すると、すべてのディテールが再生成されます。
Detail Distance Multiplier1すべてのディテールプロトタイプの描画距離に掛ける係数(0.1–3)。
Tree Distance Multiplier1インスタンシングで描画するすべての木のプロトタイプの描画距離に掛ける係数(0.1–3)。
Wind
Wind Direction(1, 0.35)ワールド空間での水平方向の風向き(x = ワールド X、y = ワールド Z)。
Wind Strength0.6全体の風の強さ(0–3)。各プロトタイプとマテリアルで、さらにスケールされます。
Wind Speed1.6風のアニメーションの速度(0–10)。
Rendering
Draw Detailsオン密度でペイントされたディテールを描画します。
Draw TreesオンGPU インスタンシングの木を描画します。
Draw In Scene Viewオンシーンビューのカメラにも植生を描画します。
Crossed Quad Count3Crossed Quads ディテール用に生成される草メッシュのクアッド数(2–3)。変更すると植生が再構築されます。
Collider Layer0インスタンシングの木のために作成されるコライダープロキシのレイヤー。

ディテール

ペイントされたディテールレイヤーとパッチごとに、DetailScatterJob(Burst)が密度マップからインスタンスを散布します。密度が 255 のとき、ディテールのテクセル 1 つあたりに生成されるインスタンス数は、プロトタイプの Max Density × Detail Density Multiplier になります。インスタンスは Max Slope より急な傾斜には配置されず、Avoid Water がオンの場合は水没した地面にも配置されません。地面に合わせて傾けたり(Align To Ground)、ノイズに基づいて Healthy Color と Dry Color の間の色を付けたりできます。

パッチのインスタンスは構造化バッファ(GraphicsBuffer)にアップロードされ、内部シェーダー Hidden/RTS Terrain Studio/Detail を通して Graphics.RenderMeshPrimitives で描画されます。WebGL 2.0 には構造化バッファがなく、OpenGL ES 3 では頂点シェーダーで構造化バッファを使用できる保証がないため、これらのデバイスではインスタンスを代わりに浮動小数点テクスチャに格納します(インスタンスごとに 2 テクセル)。どちらの方式を使用するかはレンダラーがランタイムで選択し、草の見た目は変わりません。シェーダーはバッファまたはテクスチャから各インスタンスを読み取り、風、描画距離の最後の 25% にかけての滑らかな縮小フェード、根元の暗化、光の透過、戦場の霧のフォールバックを処理します。視錐台カリングと距離カリングは、カメラごとにパッチ単位で行われます。

描画モードメッシュマテリアル
CrossedQuads2 枚または 3 枚の交差クアッドで構成される、生成された草メッシュ(Crossed Quad Count)プロトタイプの Texture から作成(アルファ 0.5 でクリップ)
Meshプロトタイプの Mesh(すべてのサブメッシュ)マテリアルがすでに Hidden/RTS Terrain Studio/Detail を使用している場合は、そのまま使用されます。それ以外の場合は、そのメインテクスチャと色からディテール用マテリアルが作成されます。アルファカットオフは、ソースマテリアルがアルファクリップを行う場合にのみ引き継がれます。

ほかのシェーダーはインスタンスデータを読み取れないため、Mesh モードのディテールは常にディテールシェーダーで描画されます。ソースマテリアルのカスタムシェーダー機能(頂点アニメーション、特殊なライティング)は、ディテールでは使用されません。それらが必要な場合は、代わりに木のプロトタイプを使用してください。

ディテールは、プロトタイプの Cast Shadows がオンの場合にのみ影を落とします(デフォルトではオフ)。ディテールは地形の Chunk Layer に、ライトプローブやリフレクションプローブを使わずに描画されます。

木:インスタンシングかゲームオブジェクトか

木のプロトタイプごとに Spawn Mode を選択します。

Instanced(デフォルト)GameObject
描画Graphics.RenderMeshInstanced。1 回のドローコールで最大 1023 インスタンス。パッチごとに、視錐台と Render Distance × Tree Distance Multiplier でカリング木 1 本につき 1 つのプレハブインスタンス。ほかのゲームオブジェクトと同様に Unity が描画
LODLODGroup の LOD0 と LOD1。Unity の LODGroup のルールに従って、パッチグループごとに選択されます(QualitySettings.lodBias を考慮)。LODGroup のないプレハブでは、アクティブな MeshRenderer がすべて使用されます。プレハブ自身の LODGroup
インスタンスごとのティントはい(RTS Terrain Studio/Vegetation を使用するマテリアルの場合。Tint A と Tint B の間でランダム)いいえ
コライダープールされたカプセルプロキシ(以下を参照)プレハブのコライダー
プレハブ上のスクリプト実行されない(何もインスタンス化されないため)通常どおり実行される
最適な用途森林、背景、大量の木ゲームプレイに関わる木:伐採、アニメーション、カスタムスクリプト

どちらのモードでも、木は地面の高さ + position.y の位置に配置され、ヨー角で回転し、均一にスケールされます。地形を編集すると、木は自動的に地面に合わせて再配置されます。スポーンされた木とコライダープロキシは、地形の非表示の子オブジェクト「RTS Vegetation Objects」の下に置かれ、地形のトランスフォームに追従します。

インスタンシングで描画する木には、そのマテリアルで Enable GPU Instancing を有効にする必要があります。オフの場合、レンダラーはマテリアルの非表示のインスタンシング用コピーを使って描画します(プロジェクトのアセットが変更されることはありません)。編集モードでは、このコピーは 1 秒あたり約 4 回ソースと再同期されます。プレイヤービルドでは、こうしたマテリアルのインスタンシングバリアントが除外される可能性があるため、チェックボックスをオンにしてください(Project Validator で自動的に行えます)。

レンダラーは、各パーツのシャドウキャストモードとレンダリングレイヤーマスクを維持します。Shadows Only に設定されたパーツ(シャドウプロキシ)も期待どおりに動作します。Cast Shadows がオフのプロトタイプでは、そのすべてのパーツの影がオフになります。

木のコライダー

木のプロトタイプで Colliders が有効な場合:

  • Instanced モード:木 1 本ごとに、見えないゲームオブジェクト「Tree Collider」が作成されます。このオブジェクトは CapsuleCollider(Collider Radius、Collider Height、木に合わせてスケール)と RTSTreeInstance コンポーネント(IsColliderProxy = true)を持ちます。プロキシはプールされて再利用され、レンダラーの Collider Layer を使用します。
  • GameObject モード:プレハブのコライダーが使用されます。レンダラーは、スポーンしたすべての木に RTSTreeInstance を追加します。

どちらの場合も、hit.collider.GetComponentInParent<RTSTreeInstance>() で、物理クエリがどの木にヒットしたかを確認できます。通行判定と戦場の霧にはコライダーは不要です。これらはプロトタイプの Footprint Radius と Blocks Vision を使用します。

風

レンダラーは毎フレーム、グローバルな _RTS_Wind ベクトル(方向、強さ、速度)をすべてのシェーダーに渡します。植物の揺れの大きさは、最終的に次のように決まります:

  • ディテール:グローバルな Wind Strength × プロトタイプの Wind Strength(0–1)。
  • RTS Terrain Studio/Vegetation を使用する木と植物:グローバルな Wind Strength × マテリアルの Wind Strength。これに Wind Mode に応じた重みが掛かります。Object Height では、Wind Reference Height を基準として上部ほど大きくしなります。Vertex Color R では、頂点カラーの赤チャンネルを頂点ごとの重みとして使用します(幹には 0、枝先には 1 をペイントします)。Leaf Flutter は、その上にさらに速く細かな揺れを加えます。

風のない静かな夜のシーンなどで風を完全に止めるには、レンダラーの Wind Strength を 0 に設定します。

Vegetation シェーダー

RTS Terrain Studio/Vegetation は、木や植物のプレハブのマテリアル向けの、ライティングに対応した葉用シェーダーです。SRP Batcher、インスタンスごとの色合い(_RTS_InstanceTint)付きの GPU インスタンシング、Forward+、DBuffer デカール、Rendering Debugger、APV、ライトマップに加え、戦場の霧のフォールバックにも対応しています。インスタンシングに対応したほかのマテリアル(URP Lit など)も使用できますが、その場合はインスタンスごとの色合い、光の透過、風は使えません。

プロパティデフォルト説明
Albedo、Color白ベーステクスチャ(アルファはクリッピングに使用)とカラー。
Alpha Clipping、Alpha Cutoffオン、0.5葉のアルファテスト。キーワードを設定しなくてもクリッピングは有効です。反転キーワード _ALPHATEST_OFF で無効になります。
Use Normal Map、Normal Map、Normal Strengthオフ、–、1オプションの法線マップ(_NORMALMAP)。
Smoothness、Specular Color0.2、濃い灰色鏡面反射の特性。
Wrap Lighting0.35葉に当たる光の減衰をなだらかにします(0–1)。
Translucency、Translucency Color0.6、明るい緑太陽の方向を見たときに葉を透過して差し込む光。
Wind ModeObject HeightOff、Vertex Color R、または Object Height。
Wind Strength、Wind Reference Height、Leaf Flutter1、6、0.5マテリアル側の風のスケール(レンダラーのグローバルな風と乗算されます)。
CullOff面のカリング。Off にすると、リーフカードの両面が描画されます。
Receive Shadowsオンオフにすると、キーワード _RECEIVE_SHADOWS_OFF が設定されます。

植生マテリアルをコードで作成する場合:アルファクリッピングはデフォルトで有効です。キーワード _ALPHATEST_OFF(RTSVegetationShaderIDs.VegetationAlphaTestOffKeyword)は、クリッピングを無効にしたい場合にだけ有効にしてください。

品質の乗数

3 つの品質フィールドを使うと、ライブラリ自体を編集せずに、ライブラリの値をスケーリングできます。ゲームメニューのグラフィックオプションに最適です:

using RTSTerrainStudio;
using UnityEngine;

/// Applies a vegetation quality level (0 = low, 1 = medium, 2 = high) to every terrain in the scene.
public static class VegetationQuality
{
    public static void Apply(int level)
    {
        foreach (RTSTerrain terrain in RTSTerrain.All)
        {
            if (!terrain.TryGetComponent(out RTSVegetationRenderer vegetation)) continue;

            switch (level)
            {
                case 0:
                    vegetation.detailDensityMultiplier = 0.4f;   // fewer grass blades (regenerates details)
                    vegetation.detailDistanceMultiplier = 0.6f;  // grass disappears earlier
                    vegetation.treeDistanceMultiplier = 0.7f;    // instanced trees culled earlier
                    vegetation.crossedQuadCount = 2;             // 2 quads per grass tuft instead of 3
                    break;
                case 1:
                    vegetation.detailDensityMultiplier = 0.75f;
                    vegetation.detailDistanceMultiplier = 0.8f;
                    vegetation.treeDistanceMultiplier = 1f;
                    vegetation.crossedQuadCount = 3;
                    break;
                default:
                    vegetation.detailDensityMultiplier = 1f;
                    vegetation.detailDistanceMultiplier = 1f;
                    vegetation.treeDistanceMultiplier = 1.2f;
                    vegetation.crossedQuadCount = 3;
                    break;
            }
            // The renderer notices the changed values on its next update and rebuilds what is needed.
        }
    }
}

木の API と伐採

メンバー説明
bool RemoveTree(uint id)木をマップから削除し、そのゲームオブジェクトまたはコライダープロキシをただちに非アクティブにします。さらに地形に変更を通知し(MapLayers.Trees。これにより通行判定ではフットプリントが再び通行可能になり、戦場の霧からは視界遮蔽物が取り除かれます)、TreeRemoved を発生させます。アンドゥステップは記録されません。この ID の木が存在しない場合は false を返します。
event Action<RTSVegetationRenderer, TreeInstanceData> TreeRemovedRemoveTree によって発生し、削除された木のデータが渡されます。
bool TryFindNearestTree(Vector3 worldPosition, float maxDistance, out TreeInstanceData tree)地面の平面(XZ)上で測った最も近い木。Mathf.Infinity を指定するとマップ全体を検索します。
GameObject GetTreeObject(uint id)スポーンされたプレハブのインスタンス(GameObject モード)、またはコライダープロキシ(インスタンス描画でコライダーありの場合)。それ以外の場合は null。
Vector3 GetTreeWorldPosition(in TreeInstanceData tree)木の根元のワールド位置(地面の高さ + オフセット)。
void RebuildAll(), void ProcessChanges()前者は完全な再構築、後者は保留中のマップの変更の即時適用です(通常は毎フレーム自動で行われます)。
VisibleDetailInstances, VisibleTrees, TotalDetailInstances, SpawnedTreeObjects, TreeColliderCount統計情報。Visible 系の値は、直近にレンダリングされたゲームビューまたはシーンビューのカメラについての値です。
RTSTreeInstance: TreeId, Owner, IsColliderProxy, TryGetData(out TreeInstanceData), Remove()スポーンされた木とコライダープロキシに付いているコンポーネント。Remove() は Owner.RemoveTree(TreeId) と同じです。
using RTSTerrainStudio;
using UnityEngine;

/// A minimal lumberjack: chops the nearest tree in reach, or the tree under the mouse.
public class Lumberjack : MonoBehaviour
{
    [SerializeField] RTSVegetationRenderer vegetation;
    [SerializeField] Camera viewCamera;
    [SerializeField] float reach = 2f;
    public int wood;

    void OnEnable()
    {
        if (vegetation != null) vegetation.TreeRemoved += HandleTreeRemoved;
    }

    void OnDisable()
    {
        if (vegetation != null) vegetation.TreeRemoved -= HandleTreeRemoved;
    }

    /// Physics-free: finds the nearest tree around this unit on the ground plane.
    public bool ChopNearestTree()
    {
        if (vegetation == null) return false;
        if (!vegetation.TryFindNearestTree(transform.position, reach, out TreeInstanceData tree)) return false;
        return vegetation.RemoveTree(tree.id);
    }

    /// Physics based: needs "Colliders" on the tree prototype.
    public bool ChopTreeUnderMouse()
    {
        if (viewCamera == null) return false;
        Ray ray = viewCamera.ScreenPointToRay(RTSInput.PointerPosition);
        if (!Physics.Raycast(ray, out RaycastHit hit, 500f)) return false;

        // Works for collider proxies (instanced trees) and for colliders inside spawned tree prefabs.
        var tree = hit.collider.GetComponentInParent<RTSTreeInstance>();
        return tree != null && tree.Remove();
    }

    void HandleTreeRemoved(RTSVegetationRenderer renderer, TreeInstanceData tree)
    {
        wood += 10;
        Vector3 position = renderer.GetTreeWorldPosition(tree);
        Debug.Log($"Tree {tree.id} felled at {position}. Wood: {wood}");
    }
}

サンプルスクリプトには、完全な伐採デモ(RTSTreeHarvestDemo:ワーカーが木まで歩いて伐採し、RTSFallingTreeEffect で木が倒れるアニメーションを再生します)が含まれています。テストプレイの終了時に伐採した木を復元する方法も含め、ゲームプレイの完全な例として参考にしてください。

木を削除するとマップが変更されます。ゲーム内エディターまたは Unity エディターでは、そのマップは未保存の変更があるものとしてマークされます。ゲームのコードは通常、対戦用に読み込んだマップを扱い、それを保存し直すことはありません。

水

RTSWaterRenderer は、ペイントされた水をチャンクに分割された透明なメッシュとして描画します。水フラグが付いた各頂点はそれぞれ独自の水面の高さを持つため、異なる崖レベルに湖を作ることができます。メッシュは MapGeometryView.SampleWater に従います。これは、ゲームプレイのクエリや通行判定が使用するものとまったく同じです。水のチャンクは地形のチャンクサイズを使用し、Burst ジョブ(WaterChunkMeshJob)で生成され、水がある場所にだけ存在します。再構築されるのは、編集の影響を受けたチャンクだけです。

セットアップ

  1. 地形のゲームオブジェクトに Water Renderer があることを確認してください(追加するには Add Component ▸ RTS Terrain Studio ▸ Water Renderer を使うか、RTSTerrain のインスペクターの Add ボタンをクリックします)。
  2. アクティブな URP アセットを開き、Depth Texture と Opaque Texture を有効にして、Opaque Downsampling を None に設定します(以下を参照)。Tools ▸ RTS Terrain Studio ▸ Validate Project でこれをチェックして修正できます。また、カメラ側の設定で、そのカメラに対してだけ両方を有効にすることもできます。
  3. Water ツールで水をペイントします(編集中にシーンビューで 6 キー)。
  4. 見た目は、タイルセットのインスペクターの Water セクション(タイルセットの水プロファイル)で変更します。

URP の要件

設定(URP アセットまたはカメラ)この設定が必要な機能設定しない場合
Depth Texture:オン深度に基づく色、岸辺の泡、柔らかな岸辺の境界(いずれも水中に立つオブジェクトの周囲)、および水より手前にあるオブジェクトが映り込まない屈折(_CameraDepthTexture)水は、メッシュに保存された深度を使用します。深度に基づく色、泡、柔らかな岸辺は残りますが、水中に立つオブジェクトは考慮されません。
Opaque Texture:オン屈折(_CameraOpaqueTexture)屈折のない単純なアルファブレンディングになります。
Opaque Downsampling:None(URP アセット)くっきりした湖底と、くっきりした水中のオブジェクト水中の景色がぼやけて見えます(URP のデフォルトは 2x Bilinear)。

URP は、これらのテクスチャを生成するかどうかをカメラごとに判断します。カメラ自身の Depth Texture と Opaque Texture の設定(カメラのインスペクターの Rendering セクション)は URP アセットの設定より優先され、シーンビューでは常に URP アセットの設定が使用されます。水は、各カメラがレンダリングする直前にマテリアルをそのカメラに合わせるため、どのビューでも、そのビューで可能な最良の水が描画されます。水のレイヤーをレンダリングしないカメラはスキップされます。また、オーバーレイカメラが不透明テクスチャを受け取ることはありません。

Water Renderer のインスペクターには、不足している設定ごとにメッセージが Select URP Asset ボタンとともに表示されます。再生モードでは、メインカメラ自身の設定も考慮されます。テクスチャが不足していても、水の表示が壊れることはありません。テクスチャがないビューでは、上記のフォールバックが使用されます。ただし、両方のテクスチャを有効にしたほうが、見た目は格段に良くなります。

コードから:RTSWaterMaterialUtility.CheckPipelineSupport(Camera camera, out bool depthTexture, out bool opaqueTexture) は、1 つのカメラに対して URP が何を生成するかを、URP と同じ方法で判定して返します(null を渡すと、URP アセットだけをチェックします)。CheckPipelineSupport(out bool depthTexture, out bool opaqueTexture) は、水を表示するビューをチェックします。ビルドしたゲームではメインカメラ(メインカメラがない場合は URP アセット)をチェックし、エディターではシーンビューが URP アセットを使用するため、URP アセットもチェックします。どちらも、URP がアクティブでない場合は false を返します。RTSWaterMaterialUtility.IsOpaqueTextureDownsampled() はダウンサンプリングをチェックします。

レンダラー設定

設定デフォルト説明
Material Template空オプションのマテリアル。そのシェーダーとプロパティがコピーされ、その上にタイルセットの水プロファイルが適用されます。プロファイルで扱わない値(Reflection Strength、Specular Strength、Foam Tile Size、Foam Lines、Shore Fade)の調整に使用します。
Cast Shadowsオフ水面は通常、影を受けるだけです。
Receive Shadowsオン水面にメインライトの影を表示します。ビルドしたゲームのランタイムマテリアルでも機能します。
Render Layer4 (Water)水のチャンクのゲームオブジェクトのレイヤー(RTSWaterRenderer.DefaultLayer は Unity 組み込みの Water レイヤーです)。
Wave Amplitude Multiplier1プロファイルの Wave Height をスケーリングします(0–4、0 = 静止した水面)。
Cell Subdivisions2セルの 1 辺あたりのクアッド数(1–4)。値を大きくすると岸辺の輪郭と波が滑らかになりますが、頂点数が増えます。
Light Probe UsageOffOff では、すべてのチャンクをアンビエントプローブでライティングします。プローブをブレンドすると、チャンクごとに環境光の色がわずかに異なり、チャンクの境界に段差が見えてしまいます。
Reflection Probe UsageBlend ProbesForward+ では、プローブはピクセルごとに評価されます。Forward では、各チャンクが自身のバウンズに基づいてプローブを選択するため、プローブが複数あると、隣り合うチャンクが異なるプローブを反射することがあります。

インスペクターのボタン:Rebuild(RebuildAll())と Refresh Material(RefreshMaterial()。テンプレートとプロファイルを再適用します)。インスペクターには、チャンクのグリッド、水のあるチャンク、合計頂点数、プロファイルの取得元も表示されます。

水プロファイル(タイルセット)

見た目と通行ルールは RTSTileset.water(WaterProfile)から取得されます。タイルセットがない場合は、共有のデフォルトプロファイルが使用されます。これは読み取り専用として扱い、編集可能なコピーが必要な場合は WaterMath.CreateDefaultProfile() または WaterProfile.Clone() を使用してください。

フィールドデフォルト説明
Shallow Colorティール、アルファ 0.55浅水域の色(アルファ = 不透明度)。
Deep Color濃い青、アルファ 0.95Color Depth の深さで到達する色(アルファ = 不透明度)。
Color Depth3 m水の色が完全に Deep Color になる水深。
Shallow Depth Limit0.9 mこれより深い水は深水域(歩行不可)になります。これより浅い水は、クラシック RTS ゲームと同様に歩行可能です。
Normal Map、Normal Strength、Normal Tile Sizeなし、0.6、8 m水面のさざ波。法線マップがない場合、水面は頂点の波だけを使用します。
Flow Speed(0.03, 0.02)法線マップのスクロール。
Wave Height0.05 m頂点の波の振幅(レンダラーの乗数が掛けられます)。
Smoothness0.92水面の滑らかさ。
Refraction0.03屈折オフセットの強さ(0–0.2)。
Foam Color、Foam Distance、Foam Texture白 0.8、0.6 m、なし岸辺の泡。Foam Distance は浅水域の波も穏やかにします。
Minimap Color青ミニマップ上の水の色。

Water シェーダー

RTS Terrain Studio/Water(RTSWaterMaterialUtility.ShaderName)は透明な URP シェーダーです。チャンクのメッシュは、マップローカルの位置に加えて、頂点ごとの水データ(水深、カバー率、岸辺までの距離。距離は最大 ShoreSearchRadius = 4 セル)を提供します。主な機能:

  • 浅い所から深い所への深度に応じた色の変化と、水が地面と接する部分でフェードする柔らかな岸辺。
  • 4 方向の頂点の波。浅水域では穏やかになるため、水面が浜辺より上に持ち上がることはありません。
  • スクロールする法線マップによるさざ波、水中の景色の屈折、太陽のスペキュラー、リフレクションプローブ。
  • 岸辺の泡(プロシージャルなライン、または泡テクスチャ)。
  • メインライトと追加ライト(頂点ごとの追加ライトを含む)、ライトクッキー、ライトレイヤー、影の受け取り、戦場の霧のフォールバック。SRP Batcher に対応しています。

マテリアルはランタイムで作成され(WaterMaterial。すべてのチャンクで共有)、RTSWaterMaterialUtility.ApplyProfile で値が設定されます。シェーダーはコンポーネントのシリアライズされたフィールドから参照されるため、ビルドに含まれます。

頂点ごとのモードでは、URP 自体の頂点ライティングと同様に、追加ライトは拡散光だけを加えます(スペキュラーも影もありません)。

Water Renderer の API

メンバー説明
Material WaterMaterialすべてのチャンクで共有されるランタイムマテリアル(無効の間は null)。
Material MaterialTemplate { get; set; }オプションのテンプレート。設定するとマテリアルが更新されます。
WaterProfile Profileタイルセットのプロファイル、または共有のデフォルト。
Transform ChunkRoot, ChunkCountX, ChunkCountZ, ActiveChunkCount, VertexCount, bool HasWaterチャンクのグリッドと統計情報。
MeshRenderer GetChunkRenderer(Vector2Int chunkCoord)チャンクのレンダラー。チャンクに水がない場合は null。
void RebuildAll(), void RebuildCells(RectInt cellRect), void RefreshMaterial()完全な再構築、部分的な再構築、マテリアルの更新。
event Action<RTSWaterRenderer> MeshesRebuilt完全または部分的な再構築の後に発生します。
RTSWaterChunk: Owner, Terrain, ChunkCoord, CellRect非表示のチャンクオブジェクトに付いているマーカー。

WaterMath:ゲームプレイ用の水

WaterMath は、物理演算を使わずに水に関する問い合わせに答えます。すべての位置はワールド空間で指定します。

メソッド戻り値
GetWaterHeight(RTSTerrain, Vector3, out float height), GetWaterHeight(Vector3, out float height)静止した水面のワールド Y 座標。水がない場合や地形の外では false。2 つ目のオーバーロードは RTSTerrain.FindAt で地形を見つけます。
GetWaterDepth(RTSTerrain, Vector3, out float depth)水面の高さから地面の高さを引いた値(メートル単位)。水がない場合は false。
Classify(RTSTerrain, Vector3, out float depth)WaterDepthClass.Dry、Shallow、または Deep(Shallow Depth Limit を使用)。
IsShallowWater(…), IsDeepWater(…), GetShallowDepthLimit(RTSTerrain)分類用のショートカット。
GetWaterSurface(RTSTerrain, Vector3, float time, out float height, out Vector3 normal)描画されるものとまったく同じ、アニメーションする水面。静止した高さ + 波で、岸辺の近くでは波が穏やかになり、レンダラーの Wave Amplitude Multiplier も反映されます。
float ShaderTimeシェーダーがアニメーションに使用するクロック(再生モードとビルドしたゲームでは Time.time、編集モードでは Time.realtimeSinceStartup)。これを time 引数に渡すと、CPU と GPU の波が一致します。
SampleWaveOffset(Vector3, float waveHeight, float time)岸辺での減衰を考慮しない波のオフセット。
EvaluateWaves(float2, float time), EvaluateWaveSurface(float2, float time, float amplitude, out float offset, out float3 normal), WaveAttenuation(float depth, float foamDistance)ジョブシステムで使える Burst 互換の構成要素(たとえば、水に浮かぶ何千ものユニットの処理に)。
GetProfile(RTSTerrain), CreateDefaultProfile()前者は使用中のプロファイル、後者は自由に変更できる新しいデフォルトプロファイルを返します。
using RTSTerrainStudio;
using UnityEngine;

/// Makes a boat float on the animated water surface and keeps it out of shallow water.
public class FloatingBoat : MonoBehaviour
{
    [SerializeField] float draft = 0.15f;        // how deep the hull sits in the water
    [SerializeField] float heading;              // yaw in degrees, set by your movement code
    [SerializeField] float tiltSmoothing = 4f;

    void LateUpdate()
    {
        Vector3 p = transform.position;
        RTSTerrain terrain = RTSTerrain.FindAt(p);
        if (terrain == null) return;

        // ShaderTime is the clock the water shader uses, so the boat bobs in sync with the rendered waves.
        if (!WaterMath.GetWaterSurface(terrain, p, WaterMath.ShaderTime, out float height, out Vector3 normal))
            return; // dry ground: leave the boat alone

        transform.position = new Vector3(p.x, height - draft, p.z);

        // Tilt with the wave normal, keep our own heading.
        Quaternion target = Quaternion.FromToRotation(Vector3.up, normal) * Quaternion.Euler(0f, heading, 0f);
        transform.rotation = Quaternion.Slerp(transform.rotation, target, 1f - Mathf.Exp(-tiltSmoothing * Time.deltaTime));
    }

    /// Boats need deep water: use this before accepting a move order.
    public static bool CanSailTo(Vector3 world)
    {
        RTSTerrain terrain = RTSTerrain.FindAt(world);
        return terrain != null && WaterMath.Classify(terrain, world, out _) == WaterDepthClass.Deep;
    }
}

装飾オブジェクト

RTSDecorManager は、シーンをマップの装飾オブジェクト(ドゥーダッド:岩、木箱、遺跡、柵など、任意のプレハブ)と同期させます。地形の変更イベントを監視し、差分だけを処理します。編集で処理されるのは、追加、削除、または変更されたインスタンスだけです。トランスフォームだけが変わった場合はオブジェクトが再利用され、削除されたオブジェクトはプールされます。地形の高さを編集すると、変更された範囲のインスタンスが地面に合わせて再配置されます。マップを読み込んだり装飾ライブラリを変更したりすると、すべてが再構築されます。

スポーンモード

装飾ライブラリの各プロトタイプで Spawn Mode を選択します:

GameObject(デフォルト)Instanced
表現方法子オブジェクト「RTS Decor」の下に置かれるプレハブ(バリアント)のコピー。RTSDecorInstance コンポーネント付きGraphics.RenderMeshInstanced で描画(プレハブの最初の LOD レベル)。1 ドローコールあたり最大 1023 インスタンス
カリングUnity 標準のカリングパッチ単位(視錐台 + Max Draw Distance)
スクリプト、コライダー、アニメーションはいいいえ(オブジェクトが存在しないため)
色合いMaterialPropertyBlock 経由(_BaseColor と _Color)対応しているシェーダーのみ(独自シェーダーでのインスタンスごとの色合いを参照)
最適な用途インタラクティブなプロップや破壊可能なプロップ。数百~数千個のオブジェクト大量の静的な背景オブジェクト

配置ルール:位置 = LocalToWorld(x, ground + y + prototype yOffset, z)。KeepHeight フラグがある場合は、保存された絶対高さを使用します。回転 = 地形の回転 × インスタンスの回転、スケール = インスタンスのスケール。スポーンされたプレハブでは、これに加えて、プレハブのルートに設定されている回転とスケールも維持されます。

インスタンス描画の装飾オブジェクトでは、マテリアルの Enable GPU Instancing を有効にする必要があります。オフの場合は、インスタンシングを有効にしたランタイムコピーが使用され、チェックボックスをオンにするよう警告が表示されます。Project Validator を使うと、すべてのマテリアルを一度に修正できます。

プロトタイプのフィールド(バリアント、ランダムな回転とスケール、色合い、フットプリント、視界の遮蔽、影)については、装飾ライブラリで説明しています。

マネージャーの設定

設定デフォルト説明
Show In Scene Viewオンインスタンス描画の装飾オブジェクトをシーンビューのカメラで描画します。スポーンされたゲームオブジェクトは常に表示されます。
Max Draw Distance0カメラからこの距離より遠いインスタンス描画の装飾オブジェクトは描画されません(0 = 無制限)。
Shadow Culling Margin12 m視錐台カリング用にパッチの周囲に追加するマージン。これにより、視野のすぐ外にあるプロップも視野内に影を落とせます。
Instanced Tintオンシェーダーが対応しているインスタンス描画の装飾オブジェクトに、インスタンスごとの色合いを渡します。
Patch Size16カリングとクエリに使うパッチの 1 辺の長さ(セル単位、4–64)。変更すると装飾オブジェクトが再構築されます。
Pool Capacity Per Prefab64削除された GameObject モードの装飾オブジェクトを、再利用のために非アクティブのまま保持しておく数の上限(プレハブごと)。
Log Missing Prototypesオン見つからないプロトタイプ ID、見つからないライブラリ、破棄されたプレハブのそれぞれについて、警告を 1 件ずつログに出力します。

Decor Manager のインスペクターには、Instances、GameObjects、Instanced、Sync(保留中の変更があるかどうか)が表示されるほか、Rebuild(RebuildAll():すべてを破棄して再作成し、プールをクリアし、プレハブを読み込み直します)、Sync Now(SyncNow())、Select Library の各ボタンがあります。

プレースホルダー

マップがライブラリにないプロトタイプ ID を参照している場合、地形に装飾ライブラリが割り当てられていない場合、またはプレハブが削除された場合、そのインスタンスは小さなマゼンタの立方体として表示されます。データは失われません。インスタンスはマップに残り、プロトタイプが戻れば再び通常どおり表示されます。インスタンスがプレースホルダーかどうかは IsPlaceholder(id) で確認できます。プレースホルダーは、インスタンス描画のオブジェクトとして数えられます。

ほかの人のマップを開いた後にマゼンタの立方体が表示される場合、通常は、そのマップの作成時とは異なる装飾ライブラリを地形が使用していることが原因です。RTSTerrain のインスペクターで正しいライブラリを割り当ててください。

独自シェーダーでのインスタンスごとの色合い

インスタンス描画の装飾オブジェクトにインスタンスごとの色合いが適用されるのは、シェーダーがインスタンシングバッファから色合いを読み取れる場合だけです。URP Lit、Simple Lit、Unlit では、マテリアルの色がそのまま使われます。シェーダー側で SubShader タグを指定すると、この機能が有効になります:

タグ定数シェーダーが行う必要があること
"RTSInstanceTint" = "True"RTSDecorManager.InstanceTintShaderTagアルベドに、インスタンスごとの _RTS_InstanceTint(rgb = 色合い、a = 1)を乗算します。このパッケージの RTS Terrain Studio/Vegetation シェーダーは自動的に検出されます。
"RTSInstancedColor" = "True"RTSDecorManager.InstancedColorShaderTagUNITY_INSTANCING_BUFFER から UNITY_ACCESS_INSTANCED_PROP を使って _BaseColor と _Color を読み取ります。書き込まれる値は、マテリアルの色 × 色合いです。

装飾 API:ピッキング、クエリ、イベント

ピッキングとクエリにコライダーは必要ありません。各インスタンスは、プレハブのメッシュのバウンズから作った向き付きのボックスとして判定されます。そのため、どちらのスポーンモードでも使え、ゲームオブジェクトを持たないインスタンス描画の装飾オブジェクトにも対応します。

メンバー説明
bool TryPick(Ray worldRay, float maxDistance, out uint instanceId)(out float hitDistance 付きのオーバーロードもあり)ワールド空間のレイが当たった最も近いインスタンス。
int QueryRadius(Vector3 world, float radius, List<uint> results)地面の平面上で測った半径内にある ID をリストに追加します。リストはクリアされません。メモリ割り当ては発生しません。IEnumerable<uint> 版のオーバーロードは新しいリストを割り当てます。
int QueryLocalRect(Rect localRect, List<uint> results)ピボットがマップローカル空間の XZ 矩形内にある ID を追加します(ボックス選択用)。
GameObject GetInstanceObject(uint id)スポーンされたオブジェクト。インスタンス描画の場合や不明な ID の場合は null。
ContainsInstance、TryGetInstance(uint, out DecorInstanceData)、IsInstanced、IsPlaceholderインスタンスの検索(データは最後の同期時点のもの)。
TryGetWorldBounds(uint, out Bounds)、TryGetObjectMatrix(uint, out Matrix4x4)ワールド空間のバウンズと、オブジェクトのルート行列(プレハブに設定されているルートの回転とスケールを含む)。
Matrix4x4 GetWorldMatrix(in DecorInstanceData)、GetWorldMatrix(in DecorInstanceData, string prototypeId)ピボットのワールド行列。まだマップに含まれていないデータにも使えます(配置プレビュー用)。
SpawnedCount、InstancedCount、InstanceCount、Container、PatchSize、HasPendingChanges状態と統計。
InstancesRebuilt、InstancesChangedそれぞれ、完全な再構築の後と、差分更新の後に発生します。
InstanceSpawned、InstanceDespawningそれぞれ、オブジェクトがスポーンされたかプールから取り出されたとき、およびオブジェクトがプールに戻されるか破棄される直前に発生します。ソフト再構築で維持されたオブジェクトでは、InstanceSpawned は再度発生しません。
RTSDecorInstance:InstanceId(プール中は 0)、PrototypeId、Variant、Owner、Prototype、IsLive、TryGetData、static FromCollider(Collider)スポーンされたすべてのオブジェクトのルートに付くコンポーネント。フィールドはプレハブ自身の Awake/OnEnable が実行される前に設定されるため、プレハブのスクリプトはそれらのメソッドの中で値を読み取れます。
using System.Collections.Generic;
using RTSTerrainStudio;
using Unity.Mathematics;
using UnityEngine;

/// Click a decor object to destroy it; Shift+click destroys everything within 5 m of the click.
public class DecorDemolisher : MonoBehaviour
{
    [SerializeField] RTSDecorManager decor;
    [SerializeField] Camera viewCamera;

    // Reused every click: QueryRadius appends to it and never allocates.
    readonly List<uint> nearby = new List<uint>();

    void Update()
    {
        if (decor == null || viewCamera == null || !RTSInput.GetMouseButtonDown(0)) return;

        Ray ray = viewCamera.ScreenPointToRay(RTSInput.PointerPosition);
        // Works for GameObject and instanced decor alike, without colliders.
        if (!decor.TryPick(ray, 500f, out uint id, out float distance)) return;

        if (RTSInput.Shift)
        {
            nearby.Clear();
            decor.QueryRadius(ray.GetPoint(distance), 5f, nearby);
            foreach (uint other in nearby) Remove(other);
        }
        else
        {
            Remove(id);
        }
    }

    void Remove(uint id)
    {
        RTSTerrain terrain = decor.Terrain;
        MapData map = terrain.Map;
        int index = map.FindDecorIndex(id);
        if (index < 0) return;

        DecorInstanceData data = map.Decor[index];
        map.Decor.RemoveAt(index);

        // Tell the terrain what changed. The decor manager removes (pools) the object,
        // and the pathing map reopens the footprint automatically.
        int2 cell = map.LocalToCell(data.position.xz);
        terrain.NotifyChanged(MapLayers.Decor, new RectInt(cell.x, cell.y, 1, 1));
    }
}
using RTSTerrainStudio;
using UnityEngine;

/// Gives every spawned decor object of the category "Destructible" some hit points.
public class DestructibleDecorSetup : MonoBehaviour
{
    [SerializeField] RTSDecorManager decor;

    void OnEnable()
    {
        if (decor != null) decor.InstanceSpawned += HandleSpawned;
    }

    void OnDisable()
    {
        if (decor != null) decor.InstanceSpawned -= HandleSpawned;
    }

    void HandleSpawned(RTSDecorManager manager, RTSDecorInstance instance)
    {
        // Prototype looks up the library entry, e.g. its category or footprint.
        DecorPrototype prototype = instance.Prototype;
        if (prototype == null || prototype.category != "Destructible") return;

        // Pooled objects come back here when they are reused, so add the component only once.
        if (!instance.TryGetComponent(out Health health)) health = instance.gameObject.AddComponent<Health>();
        health.hitPoints = 100;
    }
}

/// Example gameplay component.
public class Health : MonoBehaviour
{
    public int hitPoints;
}

インスタンス描画の装飾オブジェクトはオブジェクトを生成しないため、InstanceSpawned を発生させるのは GameObject モードのプロトタイプだけです。

環境

各マップは、独自のライティングを MapData.Environment に保存します。内容は、太陽の色・強度・回転、環境光の色、フォグです。そのため、雪景色のマップには寒色系の光を、砂漠のマップには暖色系の光を設定した状態で配布できます。RTSEnvironment コンポーネントは、マップが読み込まれたときと環境が変更されたときに、これらの値をシーンに適用します。

セットアップ

  1. GameObject ▸ RTS Terrain Studio ▸ Environment を選択します。RTSEnvironment を持つゲームオブジェクト「Environment」が作成され、シーンにディレクショナルライトがない場合は、ディレクショナルライト「Sun」も作成されます。開いているシーンにすでに環境がある場合は、代わりにそれを選択できます。環境が複数あると、同じライティング設定を奪い合うことになります。
  2. または、地形のゲームオブジェクトに Add Component ▸ RTS Terrain Studio ▸ Environment でコンポーネントを追加します(RTSTerrain のインスペクターにある Add ボタンからも追加できます)。
  3. エディターウィンドウ(Tools ▸ RTS Terrain Studio ▸ Editor Window)の Map タブにある Environment カードで、次の値を編集します:Sun Colour、Sun Intensity、Sun Rotation(x = 地平線からのピッチ、y = ヨー)、Ambient Colour、Fog、Fog Colour、Fog Density。編集は元に戻すことができ、マップと一緒に保存されます。
  4. あるいは、シーンのライティングを手動で設定してから、Capture From Scene(エディターウィンドウ)または Use Current Lighting(コンポーネントのインスペクター)をクリックします。現在の太陽、環境光、フォグが、1 アンドゥステップとしてマップに保存されます。

適用されたライティングはプレビューです。アンドゥ履歴には記録されず、シーンがダーティ(未保存の変更あり)としてマークされることもありません。シーンが保存される直前に元のシーンのライティングが復元され、保存後にマップのライティングが再び適用されます。そのため、マップのライティングがシーンファイルに書き込まれることはありません。インスペクターの Restore Scene Lighting を使うと、最初に適用する前のライティングに戻せます。

コンポーネント設定

設定デフォルト説明
Terrain空マップのライティングを適用する地形。空の場合は、このゲームオブジェクトの RTSTerrain、それがなければメインの地形(RTSTerrain.Main)を使用します。後から有効になった地形も自動的に検出されます。
Sun空太陽として使うディレクショナルライト。空の場合は RenderSettings.sun、それがなければ有効なディレクショナルライトのうち最も明るいものを使用します。
Apply In Edit Modeオン編集中にマップのライティングを適用します。オフの場合は、再生モードでのみ適用されます。
Apply Sunオン太陽の色、強度、回転(ワールド空間)を適用します。
Apply Ambientオン環境光を適用します。
Ambient ModeTrilightFlat = 単一の環境光の色。Trilight = マップの環境光の色から導出した、空 / 赤道 / 地面のグラデーション。
Sky Factor1.2Trilight:空の色 = 環境光 × この係数(0–3)。
Ground Factor0.55Trilight:地面の色 = 環境光 × この係数(0–3)。
Apply Fogオンフォグのオン/オフ、色、密度を適用します。
Fog ModeExponential Squaredフォグを適用するときに使う減衰モード(マップには色と密度だけが保存されます)。
Restore On Disableオンコンポーネントが無効になったときに、最初に適用する前のシーンのライティングを復元します。

新しいマップのデフォルト値:太陽の色は暖かみのある白(1, 0.956, 0.84)、強度 1.2、回転(50, −30)、環境光(0.45, 0.5, 0.58)、フォグはオフ、フォグの色(0.6, 0.68, 0.75)、密度 0.004。

ゲームで昼夜サイクルを実現するには、独自のスクリプトから太陽のライトを直接制御してください。RTSEnvironment がマップの値を再適用するのは、マップが読み込まれたときか、その環境が変更されたときだけです。あるいは、Apply Sun をオフにして、マップからは環境光とフォグだけを使うこともできます。

環境 API

メンバー説明
void Apply()マップの環境を今すぐシーンに適用します(Apply In Edit Mode の設定に従います)。
void CaptureFromScene(RTSEditSession session)シーンのライティングを、セッションの 1 アンドゥステップとしてマップに書き込みます。
void CaptureFromScene(RTSToolContext ctx = null)シーンのライティングをマップに書き込みます。アンドゥが記録されるのは、RTSEditSession.RunOperation の内部で呼び出した場合だけです。
void RestoreSceneState()最初の適用前に保存されたライティングを復元します。
RTSTerrain Terrain { get; set; }、Light Sun { get; set; }、bool CanApplyバインドされた地形、太陽のライト、現在適用が許可されているかどうか。
static Light FindSun()RenderSettings.sun がディレクショナルライトであればそれを、そうでなければ有効なディレクショナルライトのうち最も明るいものを返します。
using RTSTerrainStudio;
using UnityEngine;

/// Switches the current map to a stormy mood. RTSEnvironment applies it immediately.
public static class StormLighting
{
    public static void Apply(RTSTerrain terrain)
    {
        MapData map = terrain != null ? terrain.Map : null;
        if (map == null) return;

        // Work on a copy, then assign it: this is how the editor tools change the environment too.
        EnvironmentSettings env = map.Environment.Clone();
        env.sunColor = new Color(0.7f, 0.75f, 0.85f);
        env.sunIntensity = 0.45f;
        env.ambientColor = new Color(0.3f, 0.33f, 0.4f);
        env.fog = true;
        env.fogColor = new Color(0.45f, 0.48f, 0.52f);
        env.fogDensity = 0.012f;
        map.Environment = env;

        // MapLayers.Environment tells RTSEnvironment (and the in-game editor) that the lighting changed.
        terrain.NotifyChanged(MapLayers.Environment, map.CellBounds);
    }
}

パフォーマンスガイド

デフォルト設定は、一般的な RTS マップ(1 辺 128~256 セル)であれば、デスクトップで快適に動作します。このセクションでは各設定にかかるコストを説明します。これを踏まえて、大きなマップやモバイル向けビルドを的確に調整してください。

クイックチェックリスト

  • Burst パッケージをインストールしたままにし、Burst コンパイルを有効にしておきます(Jobs ▸ Burst ▸ Enable Compilation)。
  • Compress Arrays をオンにして、リリースするプラットフォーム向けにタイルセットのテクスチャ配列をエディターでビルドします(タイルセットのインスペクターに形式と推定サイズが表示されます)。
  • インスタンス描画の木、ディテール、装飾オブジェクトのすべてのマテリアルで、Enable GPU Instancing をオンにします(Project Validator ならワンクリックで設定できます)。
  • 背景用の木やプロップには、Instanced スポーンモードを使用します。ゲームプレイに関わるオブジェクトは GameObject モードのままにします。
  • スプラット解像度とテクスチャ配列の解像度は、プラットフォームに合わせて選択します(以下を参照)。どちらも主にメモリ使用量に影響します。
  • Validate Map(Map タブ ▸ Validation)を実行します。木(20 000)、GameObject モードの木(1 500)、装飾オブジェクト(6 000)、GameObject モードの装飾オブジェクト(2 000)、ディテールレイヤー(8)が多すぎる場合や、マップが大きすぎる場合(512 セル超)に警告が表示されます。

チャンクサイズの選択

Chunk Sizeドローコール(256 × 256 マップ)編集ごとの再構築コスト用途
16256 チャンク最小:ブラシの反応が最も速い小さなマップ、頻繁な編集(低速なマシンでのゲーム内エディター)
32(デフォルト)64 チャンクバランス型ほとんどのマップ
6416 チャンク最大:1 回の編集で最大 64 × 64 セルを再構築し、境界では隣接チャンクも再構築編集がまれな大きなマップ(リリース済みのマップ、プレイヤーが見るだけの場合)

各チャンクは、レンダリングされるビュー(メインカメラと各シャドウカスケード)ごとに 1 ドローコールになります。すべてのチャンクが 1 つのマテリアルを共有するため、SRP Batcher が低コストでバッチ処理します。チャンクはカリングの単位でもあるため、カメラが地面に近いときは、チャンクが小さいほど正確にカリングされます。

細分化

平滑な地面の頂点数は、Surface Subdivisions の 2 乗に比例して増えます。

Surface Subdivisions平らな 32 × 32 チャンクの頂点数実効 Cliff Subdivisions:デフォルトの 4 の場合(括弧内は 8 の場合)
11 0894 (8)
2(デフォルト)4 2254 (8)
39 4096 (12)
416 6414 (8)

Cliff Subdivisions は、Surface Subdivisions の倍数でもある偶数に切り上げられます。これにより、崖セルと平滑セルが境界の頂点をすべて共有します。Surface Subdivisions が 3 の場合、崖のサブグリッドは 6 の倍数になり、コストが高くなります。1、2、4 のいずれかをお勧めします。各崖セルのコストは、およそ(崖の細分化数)² × 2 個の三角形に、崖の壁面(レベルごとに Cliff Wall Segments の段数)を加えたものです。そのため、崖の多いマップほど、崖の設定を控えめにすることで大きな効果が得られます。

チャンクの頂点は 1 つあたり 28 バイトです。チャンクの頂点数が 65 536 未満であれば、インデックスは 16 ビットになります。細分化 2 の平らな 256 × 256 マップには約 260 000 個の頂点があり、頂点データとインデックスデータで約 10 MB になります。崖があるとさらに増え、チャンクのコライダーには別途クック済みのデータが必要です。

スプラット解像度

スプラット解像度(セルあたりのテクセル数:1、2、4、8 のいずれか、デフォルトは 4)は、マップの作成時(New Map ウィザード)に選択します。地面テクスチャをどれだけ細かくペイントできるかを決める値です。スプラットデータは常に 4 枚の RGBA32 マップ(1 テクセルあたり 16 バイト)で構成され、マップデータ、テクスチャの CPU コピー、GPU 上の 3 か所に存在します(後者の 2 つはミップマップを含むため +33%)。

マップサイズ(セル)スプラット解像度 2スプラット解像度 4(デフォルト)スプラット解像度 8
128 × 128データ 1 MiB + GPU 1.3 MiB4 MiB + 5.3 MiB16 MiB + 21 MiB
256 × 2564 MiB + 5.3 MiB16 MiB + 21 MiB64 MiB + 85 MiB
512 × 51216 MiB + 21 MiB64 MiB + 85 MiB256 MiB + 341 MiB
1024 × 102464 MiB + 85 MiB256 MiB + 341 MiB1 GiB + 1.3 GiB

(テクスチャの CPU コピーの分として、GPU の値をもう一度加算してください。)ペイントのコストは低く、変更された行だけがコピーされます。100 万テクセルを超えるテクスチャでは、変更された領域が 512 × 512 テクセル以下であれば、その領域だけがアップロードされます。その領域のミップチェーンは Burst ジョブ(SplatMipDownsampleJob)によって CPU 上で再構築され、GPU 上の該当位置にコピーされます。

くっきりとしたクラシック RTS スタイルのタイルの見た目(Tile Paint)には、解像度 2 で十分です。解像度 4 では、柔らかく精細なブレンドが得られます。解像度 8 は、セルサイズが非常に小さい場合や、カメラを地面に近づける場合にのみ使用します。

テクスチャ配列の解像度

各タイルセットは、地面と崖のテクスチャを 2 つの Texture2DArray(アルベド/高さ用と法線用、それぞれ 20 スライス)にまとめます。解像度は、タイルセットの Array Resolution で 256、512、1024(デフォルト)、2048 から選択します。ミップマップを含めた 2 つの配列の合計メモリは次のとおりです。

Array Resolution非圧縮 RGBA32BC7 / DXT5(デスクトップ、コンソール)ASTC 6×6(Android、iOS)
25613 MiB3.3 MiB1.5 MiB
51253 MiB13 MiB5.9 MiB
1024(デフォルト)213 MiB53 MiB24 MiB
2048853 MiB213 MiB95 MiB

Compress Arrays がオンの場合、タイルセットのインスペクターは、アクティブなビルドプラットフォーム向けに圧縮された配列をベイクします。Windows、macOS、Linux、コンソールでは BC7、Android、iOS、tvOS、visionOS では ASTC 6×6、Web ではビルドのテクスチャ圧縮形式(DXT5、ASTC 6×6、ETC2 RGBA8 のいずれか)、それ以外では DXT5 です。ビルド済みの配列がない場合、またはエディターで配列が古くなっている場合、レンダラーは代わりに非圧縮の RGBA32 配列を GPU 上で構築します。素早い試行錯誤には十分ですが、BC7 の 4 倍のメモリを消費します。プレイヤービルドでは、ビルドの前に古くなった配列が再構築されます(プレイヤービルドでのテクスチャ配列を参照)。配列は、同じタイルセットを使うすべての地形で共有されます。

ベイクされた形式をサンプリングできない GPU やブラウザーでは、読み込み時に Unity が配列を非圧縮の形式に展開するため、メモリ使用量が約 4 倍になります。プレイヤービルドでは、ビルド済みの配列を初めて使用するときに、その CPU 側のコピーが解放されます。そのため、プレイヤービルドでビルド済みの配列が占めるのは、表に示した GPU メモリだけです。

実行中のゲームがどの配列を使っているかを確認するには、Memory Profiler でキャプチャを取り、「RTS Albedo Array (…)」と「RTS Normal Array (…)」という名前のレンダーテクスチャを検索します。これらが見つかった場合は、ランタイムで構築された非圧縮の配列が使われています。

ディテール描画距離と植生

  • ディテールは、GPU で最もボトルネックになりやすい要素です。草は、アルファクリップされた小さな三角形が大量に集まったものだからです。表示される数は Render Distance² × 密度に比例します。ディテール描画距離を半分にすると、描画される草はおよそ 4 分の 1 になります。
  • ディテールのメモリ:ペイントされたパッチはすべて、カメラの位置に関係なく GPU 上にインスタンスを保持します(1 インスタンスあたり 32 バイト)。例:256 × 256 のマップ全体に 1 つのディテールレイヤーを最大密度(Max Density 6、ディテール解像度 2 = セルあたり 4 テクセル)でペイントすると、約 160 万インスタンス、約 50 MB になります。草は必要な場所にだけペイントするか、Max Density を下げてください。
  • ディテールの影はデフォルトでオフです。オンにすると、シャドウカスケードごとにすべての草が再度レンダリングされます。
  • 木:インスタンス描画の木は、パッチグループごと・パーツごとに、1 023 インスタンスあたり 1 ドローコールのコストがかかります。低コストな LOD1 を持つ LODGroup を使用してください。GameObject モードの木は、1 本ごとにゲームオブジェクトを 1 つ消費します(約 1 500 本未満に抑えてください)。
  • 装飾オブジェクト:大きなマップでは、インスタンス描画の装飾オブジェクトに Max Draw Distance を設定します。GameObject モードの装飾オブジェクトは Unity によってカリングされますが、それでも 1 つごとにゲームオブジェクトを 1 つ消費します(約 2 000 個未満に抑えてください)。
  • 水:Cell Subdivisions を 1 にすると、デフォルトの 2 に比べて水の頂点数が 4 分の 1 になります。

Burst とジョブ

負荷の高いループはすべて、Burst でコンパイルされたジョブで実行されます。対象は、チャンクメッシュ(BuildChunkGeometryJob、CopyChunkPartsJob)、スプラットのミップ(SplatMipDownsampleJob)、水のメッシュ(WaterChunkMeshJob)、ディテールの散布(DetailScatterJob)、木の行列(TreeMatrixJob)、装飾オブジェクトの配置(DecorPlacementJob、DecorPartMatricesJob)です。ブラシ、通行判定、戦場の霧、経路探索エンジン、ミニマップのベイク処理も Burst を使用します。チャンクのコライダーは、ワーカースレッド上で並列にベイクされます。

  • メッシュジョブは同期的にコンパイルされるため、ドメインリロード後の最初のメッシュ構築が低速なマネージドのフォールバックで実行されることはありません。
  • 計測は、プレイヤービルドで行うか、Burst の安全性チェックをオフにして(Jobs ▸ Burst ▸ Safety Checks)行ってください。エディターの安全性チェックと Jobs Debugger により、ジョブは目に見えて遅くなります。
  • Burst コンパイルを無効にしてもすべて動作しますが、メッシュの再構築とブラシストロークが何倍も遅くなります。
  • Web ビルドにはワーカースレッドがないため、すべてのジョブがメインスレッド上で 1 つずつ順番に実行されます。そのため、Web ビルドではマップ全体に対する操作に目に見えて長い時間がかかります(Web ビルドのレンダリングとパフォーマンスを参照)。

プロファイリング

Window ▸ Analysis ▸ Profiler を開き、メインスレッド上で次のマーカーを探します。

マーカー計測内容
RTSVegetation.Updateマップの変更を植生に適用する処理(以下の 2 つのビルドマーカーを含む)。
RTSVegetation.BuildDetails更新が必要なディテールパッチの再生成。
RTSVegetation.BuildTreesインスタンス描画の木のグループの再構築。
RTSVegetation.Draw1 台のカメラに対する植生のカリングとドローコールの発行。

CPU モジュールの表示を Timeline に切り替えると、上記のジョブ名をワーカースレッド上で確認できます。GPU 側では、Frame Debugger(Window ▸ Analysis ▸ Frame Debugger)を使って、地形チャンク、ディテール、インスタンス描画のドロー数を数えます。Vegetation Renderer、Water Renderer、Decor Manager のインスペクターには、インスタンス数と頂点数がリアルタイムで表示されます。RTSTerrain のインスペクターには、チャンク数とマップのメモリ使用量が表示されます。

メモリ使用量の目安

マップデータ自体のサイズ(RTSTerrain のインスペクターの Map Memory の値、MapData.GetMemoryUsage())は次のとおりです。

  • 頂点ごとに 11 バイト(高さ、崖レベル、フラグ、崖タイプ、水面の高さ)、
  • スプラットテクセルごとに 16 バイト、
  • 通行テクセルごとに 1 バイト、
  • ディテールテクセルごと、ディテールレイヤーごとに 1 バイト。
マップサイズ(デフォルト:スプラット 4、ディテール 2、通行 2、ディテールレイヤー 1 つ)マップデータスプラットテクスチャ(GPU)テクスチャ配列(1024、BC7)
128 × 128(2 m セルで 256 m)≈ 4.3 MiB≈ 5.3 MiBタイルセットごとに 53 MiB
256 × 256≈ 17 MiB≈ 21 MiBタイルセットごとに 53 MiB
512 × 512≈ 69 MiB≈ 85 MiBタイルセットごとに 53 MiB
1024 × 1024≈ 275 MiB≈ 341 MiBタイルセットごとに 53 MiB

これに加えて、チャンクのメッシュとコライダー(細分化を参照)、ディテールインスタンス(1 つあたり 32 バイト)、通行マップ、戦場の霧のテクスチャのメモリも必要です。エディターのアンドゥ履歴には、独自のメモリ上限が設定されています(エディターウィンドウの設定を参照)。

推奨マップサイズ

マップの 1 辺は 16~1024 セルで、16 刻みで設定できます。デフォルトは 128 × 128 セル(1 セル 2 m)です。

サイズ(セル)主な用途備考
64–1281v1 や小規模な対戦マップ、モバイルすべてが即座に再構築されます。どのデバイスでも、ゲーム内エディターに適したサイズです。
128–2562–4 人用最適なサイズです。すべてのデフォルト設定はこのサイズ向けに調整されています。
256–512大きなマップ、4–8 人用デスクトップ向け。スプラット解像度 2–4、Chunk Size 32–64 を検討してください。
512 超非常に大きなマップマップの検証で、編集と通行判定の再構築に時間がかかる旨の情報が表示されます。スプラットのメモリ(上記の表)に注意し、ワールドを複数の RTSTerrain に分割することを検討してください。

複数の地形を、それぞれ独自のトランスフォームを持たせて隣り合わせに配置できます。RTSTerrain.FindAt と RTSTerrain.RaycastAll を使うと、ワールド位置に対応する地形を見つけられます。

モバイル向けの注意点

  • テクスチャ配列:Android/iOS プラットフォームをアクティブにした状態でビルドし、ASTC 6×6 としてベイクされるようにします(一度ビルドすれば、プレイヤービルドがアクティブなプラットフォーム向けに配列を最新の状態に保ちます)。スマートフォンでは通常、Array Resolution 512 で十分です。
  • スプラット解像度:セルあたり 2 テクセルにすると、デフォルトの 4 に比べてスプラットのメモリが 4 分の 1 になります。
  • レイヤー:1 つのピクセルに表示される地面レイヤーの数だけ、テクスチャサンプリングのコストがかかります。領域ごとのレイヤー数を減らし、タイリングが目立たないレイヤーでは Anti Tiling を 0 に設定します(2 回目のサンプリングが不要になります)。
  • ライティング:Specular Highlights と Environment Reflections をオフにしたオーバーライドマテリアルを使用します(上記を参照)。
  • OpenGL ES と WebGL 2.0:シェーダーは Shader Model 3.5 を対象としているため、OpenGL ES 3.0 デバイスやブラウザーでも動作します。これらの API で保証されるフラグメントテクスチャは 1 回の描画あたり 16 個だけで、地形のフォワードパスはそのうち 10 個を自身で使用します。Unity の URP プロジェクトテンプレートに含まれる Mobile URP アセット(メインライトの影、リフレクションプローブのブレンド)では、すでにさらに 4 個が追加され、ライトクッキーを有効にすると上限に達します。上限を超えるテクスチャを必要とするバリアントは読み込みに失敗し、地形が消えるか、ピンク色で描画されます。GLES デバイスと Web ビルドに使用する URP アセットでは、シーンでプローブをブレンドしない場合はリフレクションプローブのブレンドをオフにし、ライトクッキー、デカール、SSAO、追加ライトの影、アダプティブプローブボリュームは必要な場合を除いてオフのままにしてください。Shaders/Include/RTSTerrainInput.hlsl 内のコメント Texture slot budget に、URP の各機能のコストが記載されています。
  • 水:帯域幅を節約するためにモバイル用の URP アセットで Depth Texture または Opaque Texture をオフにすると、水はフォールバック表示に切り替わります。Opaque Texture がないと屈折がなくなり、Depth Texture がないと岸辺の表現が水中のオブジェクトを考慮しなくなります。カメラ側の設定でこれらをオンにしたカメラでは、引き続きフル機能の水が描画されます。Cell Subdivisions は 1 にします。
  • 植生:Detail Density と Detail Distance の倍率を下げ、Crossed Quad Count を 2 にし、ディテールの影をオフにします。
  • 物理:ゲームで地形に Unity の物理演算を使わない場合は、Collider Mode を None に設定し、ピッキングには RTSTerrain.Raycast / RaycastAll を使用します。
  • ジオメトリ:Surface Subdivisions を 1(クラシック RTS の見た目)、Cliff Wall Segments を 2 にすると、頂点数を大幅に削減できます。

コードで適用するローエンド向けプリセット

using RTSTerrainStudio;
using UnityEngine;

/// Applies cheap settings to a terrain and its systems, e.g. from a "Low" graphics option.
public static class LowEndPreset
{
    public static void Apply(RTSTerrain terrain)
    {
        if (terrain == null) return;

        // Terrain mesh: fewer vertices, larger chunks, no colliders (picking uses RTSTerrain.Raycast).
        RTSTerrainSettings s = terrain.Settings;
        s.surfaceSubdivisions = 1;
        s.cliffSubdivisions = 4;
        s.cliffWallSegments = 2;
        s.chunkSize = ChunkSize._64;
        s.colliderMode = ColliderMode.None;
        // Settings changed from code are applied by a rebuild (the inspector does this automatically).
        terrain.RebuildAll();

        if (terrain.TryGetComponent(out RTSVegetationRenderer vegetation))
        {
            vegetation.detailDensityMultiplier = 0.4f;
            vegetation.detailDistanceMultiplier = 0.5f;
            vegetation.treeDistanceMultiplier = 0.7f;
            vegetation.crossedQuadCount = 2;
        }

        if (terrain.TryGetComponent(out RTSWaterRenderer water))
        {
            water.cellSubdivisions = 1;   // rebuilt automatically when the value changes
        }

        if (terrain.TryGetComponent(out RTSDecorManager decor))
        {
            decor.maxDrawDistance = 120f; // instanced props beyond 120 m are skipped
        }
    }
}

ColliderMode.None はチャンクのコライダーを削除します。ゲーム内で地形に対して Physics.Raycast を使う処理、地形の上に立つリジッドボディ、チャンクのコライダーからの NavMesh のベイクは、すべて機能しなくなります。物理演算を使わないクエリだけを使用している場合にのみ切り替えてください。

ゲームプレイシステム

RTS Terrain Studio は単なるマップエディターではありません。作成したマップには、クラシック RTS ゲームに必要なシステムが備わっています。崖レベルと木を考慮する戦場の霧(Fog of War)、経路探索を組み込んだ通行マップ、すぐに使えるサンプルゲームプレイスクリプト(カメラ、ユニット、選択、命令、ミニマップ、HUD)、そしてテクスチャ、モデル、マップ、プレイ可能なデモシーンを生成するデモコンテンツジェネレーターです。

この章では、各システムを順に説明します。各セクションでは、まずコードを書かずにできることを紹介し、次にコンポーネントとその設定を一覧にし、最後にプログラマー向けのスクリプティング API を説明します。

すぐに遊んでみたいですか?Tools ▸ RTS Terrain Studio ▸ Create Demo Content を実行し、プロジェクトウィンドウで選択されたシーン RTS Terrain Studio - Gameplay Demo をダブルクリックして、Play をクリックします。詳しくはデモシーンを参照してください。

Gameplay Demo の戦場の霧。ユニットと初期配置の建物があり、高い崖の向こう側のエリアは黒く表示されています
Gameplay Demo の戦場の霧:ユニットは周囲を可視化します。高所は視線を遮るため、高い崖の向こうにある台地は黒いままです(クラシック RTS のルール)。

戦場の霧

戦場の霧は、プレイヤーから見えないものを隠します。次のようなクラシック RTS のルールに従います。

  • チームごとの視界。可視状態は最大 32 チーム(チームインデックス 0~31)について追跡されます。各ユニットや建物は、自チームに視界をもたらします。
  • 3 つの状態。チームが一度も見たことのない地面は黒く表示されます(未探索)。以前に見たことはあっても現在は見えていない地面は暗く表示されます(探索済み)。現在見えている地面は通常どおり表示されます(可視)。
  • 探索の記憶。各チームは、リセットされるまで探索した範囲を記憶します。
  • 崖レベルによる視線判定。ユニットは、自分のいる崖レベルと、それより低い場所をすべて見渡せます。高所は下からは決して見えず、その背後にあるセルへの視界も遮ります。
  • 視界遮蔽物。木(および視界を遮るよう設定した装飾オブジェクト)は、その背後にあるセルを隠します。
  • 同盟と視界共有。チーム同士は、一方向または双方向で視界を共有できます。
  • 隠れた敵。敵ユニットは、見えていない間は表示されません。建物は、一度発見すると「ゴースト」として表示されたままになります。

可視状態は Burst ジョブによって毎秒数回計算され、テクスチャに格納されます。このテクスチャは時間方向に平滑化されたうえで GPU でぼかされるため、霧の境界はセルからセルへ飛び移るのではなく、柔らかく移動します。

クイックスタート:シーンに戦場の霧を追加する

  1. RTS Terrain を含むシーンを開きます。
  2. GameObject ▸ RTS Terrain Studio ▸ Fog of War Manager を選択します。RTSFogOfWarManager コンポーネントを持つ Fog of War ゲームオブジェクトが作成され、選択された状態になります。
  3. URP レンダラーに戦場の霧のレンダラー機能がまだない場合は、追加を提案するダイアログが表示されます。Add Feature をクリックします。(後から Tools ▸ RTS Terrain Studio ▸ Validate Project で追加することも、手動で追加することもできます。詳しくはレンダリングを参照してください。)
  4. 視界を持たせるすべてのユニットと建物に RTSFogOfWarRevealer コンポーネント(Add Component ▸ RTS Terrain Studio ▸ Fog of War Revealer)を追加します。Team と Radius を設定します。
  5. 敵から見えないようにするすべてのオブジェクトに RTSFogOfWarHideable コンポーネント(Add Component ▸ RTS Terrain Studio ▸ Fog of War Hideable)を追加します。Team を設定します。ユニットには Hide When Not Visible、建物には Ghost When Explored を使用します。
  6. Play をクリックします。Local Team(デフォルトではチーム 0)の視界源の周囲を除き、マップは真っ黒になります。

霧は再生モードでのみ動作します。編集中に確認するには、マネージャーの Preview In Edit Mode を有効にします(またはインスペクターの Enable Preview をクリックします)。隠蔽対象コンポーネントがオブジェクトを非表示にするのは再生モードのときだけで、編集モードではすべて表示されたままです。

サンプルユニット RTSUnit は、視界源と隠蔽対象のコンポーネントを必須コンポーネントとして持ち、その設定も自動で行います。サンプル(または RTSDemoGame)を使用する場合は、これらのコンポーネントを手動で追加する必要はありません。RTSDemoGame は、シーンにマネージャーがない場合はマネージャーの作成まで自動で行います。

可視状態の計算方法

マップは霧セルに分割されます。Resolution Per Cell がデフォルトの 2、マップセルのサイズがデフォルトの 2 m の場合、霧セル 1 つの幅は 1 m です。マネージャーは、視界源ごとに次の処理を行います。

  1. 視点レベルを求めます。これは、視界源の正確な位置における崖レベルに Height Bonus を加えたものです。
  2. 視界の Radius 内にあるすべての霧セルを調べます。
  3. Line Of Sight の設定に応じて、視界源からそのセルまでの線上を調べます。そのセル自体の崖レベルが視点レベル以下で、途中に視点レベルより高いセルも、(遮蔽物を考慮する場合は)木や視界を遮る装飾オブジェクトを含むセルも 1 つもなければ、そのセルは可視になります。遮っているセル自体は可視になるため、森の縁は見えますが、その奥は見えません。
  4. そのセルを、視界源のチームと、このチームの視界を受け取るすべてのチームについて、可視かつ探索済みとしてマークします。
Line Of Sight モード動作用途
None視界源は円形の範囲全体を見渡せます。崖や木は視界を遮りません。最も負荷の低いモードです。視線判定を使わないゲーム、ユニット数が非常に多い場合。
CliffLevelsクラシック RTS のルールです。視界源は自分のいる崖レベルと、それより低い場所をすべて見渡せます。高所は視界を遮ります。開けたマップ、または木で視界を遮りたくない場合。
CliffLevelsAndBlockers(デフォルト)CliffLevels と同様で、さらに木や視界を遮る装飾オブジェクトがその背後のセルを隠します。クラシック RTS の体験を完全に再現したい場合。

どのオブジェクトが視界を遮るかは、ライブラリで設定します。

  • 木:植生ライブラリにある木のプロトタイプの Blocks Vision オプション(デフォルトでオン)。遮られる範囲は、木の Footprint Radius にインスタンスのスケールを掛けたものです。
  • 装飾オブジェクト:装飾ライブラリにある装飾プロトタイプの Blocks Vision オプション(デフォルトでオフ)。遮られる範囲はプロトタイプのフットプリント(円または回転したボックス)で、インスタンスに合わせてスケーリングされます。

監視塔、丘、飛行偵察ユニットには、視界源の Height Bonus を 1 レベル以上に設定すると、より高い崖の向こうまで見渡せるようになります。空中ユニットには Ignore Line Of Sight を有効にすると、何にも視界を遮られなくなります。

マップの編集は自動的に追跡されます(Unity エディター、ゲーム内エディター、および RTSTerrain.NotifyChanged を呼び出すゲームプレイコードでの編集)。崖レベルの編集(MapLayers.CliffLevels / VertexFlags)では、変更されたセルとその周囲 1 セル分のマージンだけが再サンプリングされます。木と装飾オブジェクトの編集では、次の更新時にマップ全体の視界遮蔽物ラスターが再構築されます。NotifyChangedAll を呼び出すと、崖のラスターが完全に再構築されます。

RTSFogOfWarManager

シーンごとに 1 つのマネージャーが、1 つの地形に対して霧を計算し、描画します。GameObject ▸ RTS Terrain Studio ▸ Fog of War Manager または Add Component ▸ RTS Terrain Studio ▸ Fog of War Manager で作成します。複数のマネージャーが有効になっている場合は、最初のもの(RTSFogOfWarManager.Active)だけが使用され、インスペクターに警告が表示されます。

設定デフォルト説明
Map
Terrain空霧で覆う地形です。空の場合は RTSTerrain.Main(最初に有効になっている地形)が使われます。
Resolution Per Cell2マップセル 1 つあたり、1 軸ごとの霧セル数です(1、2、4 のいずれか)。値を大きくすると境界が滑らかになりますが、負荷が増えます。霧グリッドが各軸 2048 セル以下に収まるよう、自動的に下げられます。変更するとグリッドが再構築され、探索状態がクリアされます。
Simulation
Update Interval0.1可視状態の更新間隔(秒)です。0 にすると毎フレーム更新します。
Local Team0視界を描画し、隠蔽対象の判定に使用するチームです(0~31)。インスペクターで切り替えると、ほかのプレイヤーから何が見えているかを確認できます。
Shared Visionすべて 0受け取る側の各チームについて、そのチームと視界を共有するチームのビットマスクです(ビット t = チーム t)。通常はコードから設定します。詳しくは同盟を参照してください。
Line Of SightCliffLevelsAndBlockers崖やオブジェクトが視界をどのように遮るかを指定します。詳しくは可視状態の計算方法を参照してください。
Compute All Teamsオフローカルチームだけでなく、すべてのチームの可視状態を計算します。サーバーでのインタレストマネジメント(関心領域管理)や、ほかのチームから何が見えるかを問い合わせる AI コードで必要になります。
Start Exploredオフマップ全体が探索済みの状態で始まります。黒いエリアはなく、暗くなった霧だけが表示されます。霧グリッドの作成時に適用されます。
Reveal Allオフデバッグ用:すべてのチームに対して、すべてが可視かつ探索済みになります。
Look
Unexplored Color黒、アルファ 1一度も探索されていないエリアの色です。アルファ値が強さを表します。
Explored Color(0.3, 0.32, 0.38)、アルファ 0.6探索済みで現在は見えていないエリアに乗算される色合いです。アルファ値が強さを表します。
Fade Speed4霧がフェードイン・フェードアウトする速さです(1 秒あたりの完全なフェード回数)。0 にすると即座に切り替わります。
Blur Iterations1霧テクスチャに適用する GPU ブラーのパス数です(0~4、0 = オフ)。
Blur Upscale2ブラー後のテクスチャの解像度倍率です(1~4)。値を大きくすると境界がより丸くなります。ブラー後のテクスチャが 2048 ピクセル以下に収まるよう、自動的に下げられます。
Editor
Preview In Edit Modeオフ編集モードで戦場の霧をプレビューとして実行します。
Shaders
Blur Shader、Minimap Shader自動で割り当て済みHidden シェーダーがプレイヤービルドに含まれるよう、ここで参照しています。変更しないでください。

インスペクターには、リアルタイムの統計情報(状態、霧グリッドのサイズ、メモリ、アクティブな視界源、更新回数)、霧テクスチャのリアルタイムプレビュー(赤 = 現在可視、緑 = 探索済み、黒 = 一度も見ていない)、および以下のボタンも表示されます。

ボタン機能
Explore Allローカルチームについて、マップ全体を探索済みにします。
Reset Explorationすべてのチームの探索状態をすべて消去します。
Update Now可視状態をただちに再計算します。
Rebuild Terrain Data地形から崖レベルと遮蔽物を読み込み直します。

RTSFogOfWarRevealer

ゲームオブジェクトの周囲の視界を、そのチームに与えます。ユニット、建物、ワードなど、視界を持つあらゆるものに追加します。視界源は、有効になるとマネージャーに自身を登録します。マネージャーが更新のたびにその位置を読み取るため、このコンポーネント自体にフレームごとの負荷はありません。選択すると、シーンビューに視界半径が黄色の円で表示されます。

設定デフォルト説明
Team0視界を受け取るチームです(0~31)。
Radius12メートル単位の視界半径です。
Height Bonus0視界源が越えて見渡せる追加の崖レベル数です(監視塔、丘、飛行偵察ユニット)。
Ignore Line Of Sightオフ崖や遮蔽物を越えて見渡します(空中ユニット)。
Reveal When Disabledオフこのコンポーネントまたはそのゲームオブジェクトが無効になっている間も、破棄されるまで可視化を続けます。

Reveal When Disabled がオンの視界源のうち、シーンの読み込み時にすでに非アクティブなゲームオブジェクト上にあるものは、マネージャーが有効になったときに一度だけ収集されます。後から非アクティブな状態でインスタンス化したオブジェクト(オブジェクトプール、加算読み込みしたシーン)では Awake が実行されないため、RTSFogOfWarManager.RegisterRevealer(revealer) で自分で登録するか、コードから RevealWhenDisabled = true を設定してください。

RTSFogOfWarHideable

ローカルチームから見えていない間、敵オブジェクトのレンダラー(およびオプションでキャンバスとコライダー)を非表示にします。ローカルチームのオブジェクトと、ローカルチームと視界を共有しているチームのオブジェクトは決して非表示になりません。オブジェクトが再び見えるようになったときは、非表示になった時点で有効だったコンポーネントだけが再び有効になるため、独自の有効化・無効化ロジックはそのまま機能します。

設定デフォルト説明
Team0オブジェクトを所有するチームです(0~31)。
ModeHideWhenNotVisibleHideWhenNotVisible:その位置が現在可視である間だけ表示されます(ユニット向け)。GhostWhenExplored:一度発見されると、そのエリアが再び霧に覆われても表示されたままになります(クラシック RTS ゲームと同様に、建物や中立のプロップ向け)。
Require Sightingオンゴーストモードのみ:オブジェクトが一度は目撃されている必要があります。オフの場合は、エリアが探索済みであれば表示されます。
Hide CanvasesオンCanvas コンポーネント(体力バー、ネームプレート)も無効にします。
Hide Collidersオフ非表示の間はコライダーも無効にし、隠れた敵をクリックできないようにします。権威型サーバーやホストではオフのままにしてください。それらの物理クエリは、ローカルプレイヤーから見えないユニットにもヒットする必要があります。
Pivot空位置の判定に使う任意のトランスフォームです。空の場合は、このオブジェクト自身のトランスフォームが使われます。

隠蔽対象は、オブジェクトが表示または非表示になると VisibilityChanged(bool) を発生させます。ランタイムで子のレンダラーを追加・削除した後は、RefreshComponents() を呼び出してください。

レンダリング:レンダラー機能とシェーダーのフォールバック

霧の描画方法は 2 つあります。どちらを使うかはカメラごとに決まるため、霧が二重に適用されることはありません。

方式使用される条件暗くなる対象
RTS Fog Of War レンダラー機能(推奨)カメラが、この機能を含む Universal Renderer で描画している場合。画面上のすべて(地形、植生、水、装飾オブジェクト、ユニット、任意のカスタムマテリアル)。空には影響しません。
シェーダーフォールバックカメラのレンダラーにこの機能が含まれていない場合。このパッケージ独自の地形、水、植生のシェーダーのみ。ほかのマテリアルを使うユニットやプロップは暗くなりません(敵ユニットは、それぞれの隠蔽対象コンポーネントによって引き続き非表示になります)。

レンダラー機能を手動で追加する手順:

  1. Universal Renderer Data アセット(ゲームのカメラが使用するレンダラー)を選択します。
  2. そのインスペクターで Add Renderer Feature をクリックし、RTS Fog Of War を選択します。
  3. 一貫した結果を得るため、ゲームのカメラが使用するすべてのレンダラーについてこの手順を繰り返します。

または、Tools ▸ RTS Terrain Studio ▸ Validate Project で修正することもできます。使用中のすべての URP アセットのデフォルトレンダラーに、この機能が追加されます。

機能の設定デフォルト説明
Show In Scene Viewオンシーンビューのカメラにも霧を描画します。オフの場合、このレンダラーを使うシーンビューのカメラでは、霧なしでマップが表示されます。
Render Pass EventAfterRenderingTransparents霧を描画するタイミングです。デフォルトではポストプロセスの前に実行されるため、ブルームとトーンマッピングは霧の上から適用されます。

この機能はカメラの深度テクスチャを自動的に要求し、それを使って各ピクセルのワールド位置を復元します。RTSFogOfWarRendererFeature.IsActive は、アクティブな URP アセットのいずれかのレンダラーに有効な機能が存在するかどうかを示します。この値は情報提供のみを目的としています。

カスタムシェーダー(上級者向け)。マネージャーは霧のデータを、_RTS_FogOfWarTex(R = 可視、G = 探索済み)、_RTS_FogOfWarParams(ワールド空間での最小 X、最小 Z、1/サイズ X、1/サイズ Z)、_RTS_FogOfWarUnexplored、_RTS_FogOfWarExplored、_RTS_FogOfWarEnabled というシェーダーのグローバル変数として公開します。パッケージのシェーダーは Shaders/Include/RTSCommon.hlsl を(URP の Core.hlsl の後に)インクルードし、ライティング後の色に対してそのヘルパー関数を呼び出します。カスタム URP シェーダーでも、フォールバック方式向けに次のように同じ処理を行えます。

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Include RTSCommon.hlsl after Core.hlsl. Adjust the path if you moved the package folder.
#include "Assets/RTSTerrainStudio/Shaders/Include/RTSCommon.hlsl"

half4 frag(Varyings input) : SV_Target
{
    half3 color = /* your lit colour */ half3(1, 1, 1);
    // Darkens unexplored and fogged areas. Does nothing while the renderer feature handles this camera.
    color = RTS_ApplyFogOfWar(color, input.positionWS);
    return half4(color, 1);
}

戦場の霧のスクリプティング

戦場の霧の型はすべて RTSTerrainStudio 名前空間にあります。シーンのマネージャーは RTSFogOfWarManager.Active で取得します(マネージャーが追加・削除される場合は、静的イベント ActiveChanged を監視します)。

メンバー説明
クエリ
bool IsVisible(Vector3 world, int team)チームが現在その位置を見ている場合(自チームの視界と共有された視界)に true を返します。計算対象外のチームでは false を返します(IsTeamComputed を参照)。
bool IsExplored(Vector3 world, int team)チームがその位置を一度でも見たことがある場合に true を返します。
bool IsVisibleToLocalTeam(Vector3)、bool IsExploredByLocalTeam(Vector3)ローカルチーム用のショートカットです。
uint GetVisibilityMask(Vector3 world)その位置を見ている計算対象チームのビットマスクです(ビット t = チーム t)。サーバーのインタレストマネジメントに最適です。
uint GetExploredMask(Vector3 world)その位置を探索済みのチームのビットマスクです。
bool IsTeamComputed(int team)Compute All Teams がオンの場合は常に true で、それ以外の場合はローカルチームに対してのみ true です。
bool TryGetFogCell(Vector3, out int2)、Vector3 FogCellToWorld(int2)ワールド位置と霧セルを相互に変換します。
bool IsCellVisible(int2, int team)、bool IsCellExplored(int2, int team)各クエリのセル版です。
int GetCellLevel(int2)、bool IsCellBlocked(int2)視線判定に使われる崖レベルと遮蔽状態です。
Vector2 SampleDisplayedFog(Vector2 normalizedMapPosition)ローカルチームについて画面に表示されている、平滑化された値です(x = 可視、y = 探索済み)。霧がアクティブでない場合は (1, 1) を返します。
可視化と探索
void RevealArea(Vector3 world, float radius, int team, float duration, bool ignoreLineOfSight = false)チームに対して円形の範囲を可視化します(フレアや「マップの一部を可視化する」能力など)。duration は、再生モードではゲーム時間、編集モードのプレビューでは実時間です。0 以下の場合は、1 回の更新の間だけ可視化します。
void ClearTemporaryReveals()RevealArea で可視化したすべてのエリアを解除します。
void ExploreAll(int team)、void ExploreAllTeams()マップ全体を探索済みにします。
void ResetExploration()、void ResetExploration(int team)探索状態を消去します(現在可視のエリアは、次の更新で再び探索済みになります)。
同盟と視界共有
void SetAlliance(int teamA, int teamB, bool allied)双方向で視界を共有するか、共有を解除します。
void SetSharedVision(int sourceTeam, int receiverTeam, bool shared)sourceTeam が見ているものを receiverTeam も見られるようにします(一方向)。
void SetSharedVisionMask(int receiverTeam, uint sourceTeams)、uint GetSharedVision(int receiverTeam)受け取る側のチームのマスク全体を置き換える、または読み取ります。
bool HasSharedVision(int sourceTeam, int receiverTeam)、uint GetViewMask(int team)共有状態を読み取ります。ビューマスクにはチーム自身も含まれます。
bool IsFriendlyToLocalTeam(int team)ローカルチームと、ローカルチームと視界を共有しているチームに対して true を返します。これらのチームのオブジェクトは決して非表示になりません。
更新の制御
void RequestUpdate()、void ForceUpdate()次の更新時、またはただちに再計算します。
void MarkTerrainDirty()崖レベルのラスターを完全に再構築します。マップの編集は自動的に追跡されるため、これが必要になることはほとんどありません。
void MarkBlockersDirty()RTSTerrain.NotifyChanged を呼び出さずにコードから木や装飾オブジェクトを移動した後に、遮蔽物のラスターを再構築します。
event Action Updated可視状態の更新のたびに、隠蔽対象の更新が終わった後で発生します。
状態
IsSimulating、IsRunning、IsRenderingFog、HasGrid、HasVisibilityData霧が動作しているか、グリッドがあるか、少なくとも 1 回計算されたかを示します。
FogTexture、RawFogTexture、FogWidth、FogHeight、FogCellSize、Version、ActiveRevealerCount、MemoryUsageグリッドとテクスチャの情報です。
int EffectiveResolutionPerCell実際に使用されている、マップセルあたりの霧セル数です。グリッドが MaxFogGridSize(2048)に制限される巨大なマップでは、ResolutionPerCell より小さくなります。
RTSTerrain Terrain、RTSTerrain BoundTerrain覆う対象の地形(null = RTSTerrain.Main)と、現在グリッドが構築されている地形です。
設定(インスペクターと同じ)
ResolutionPerCell、LocalTeam、LineOfSight、ComputeAllTeams、RevealAll、FadeSpeed、BlurIterations、BlurUpscale設定可能なプロパティです。ResolutionPerCell を変更すると、グリッドが再構築され、探索状態がクリアされます。
const int MaxTeams(32)、const int MaxFogGridSize(2048)チーム数の上限と、霧グリッドの 1 辺の最大サイズです。
レジストリ
static IReadOnlyList<RTSFogOfWarRevealer> Revealers、static IReadOnlyList<RTSFogOfWarHideable> Hideables全マネージャーにおける、有効なすべての視界源と隠蔽対象です。
static RegisterRevealer / UnregisterRevealer、RegisterHideable / UnregisterHideableコンポーネント自身が呼び出します。カスタムコンポーネントの場合にのみ必要です。
RTSFogOfWarHideable と RTSFogOfWarRevealer
bool IsVisible、bool HasBeenSighted(隠蔽対象)現在の状態と、オブジェクトがこれまでに目撃されたことがあるかどうか(ゴーストモード)です。
Vector3 TestPosition(隠蔽対象)霧に対して判定される位置です(Pivot、またはトランスフォーム)。
void Reevaluate()、void ResetSighting()(隠蔽対象)前者はただちに再判定し、後者はオブジェクトの目撃記録を消去します。
event Action<bool> VisibilityChanged(隠蔽対象)ローカルチームに対してオブジェクトが表示または非表示になったときに発生します。
bool IsRevealing(視界源)視界源が視界の計算に含まれている間(有効になっている場合、または Reveal When Disabled がオンの場合)は true です。

例:フレア能力と可視状態のチェック

using RTSTerrainStudio;
using UnityEngine;

// Reveals an area for a few seconds and tells whether a target can be attacked.
public class ScoutAbilities : MonoBehaviour
{
    [SerializeField] int team = 0;          // the team that casts the flare
    [SerializeField] float flareRadius = 15f;
    [SerializeField] float flareSeconds = 5f;

    // Call this with the world position the player clicked.
    public void CastFlare(Vector3 worldPosition)
    {
        var fog = RTSFogOfWarManager.Active;
        if (fog == null) return; // no fog of war in this scene

        // ignoreLineOfSight: true lets the flare see over cliffs and trees, like an air unit.
        fog.RevealArea(worldPosition, flareRadius, team, flareSeconds, ignoreLineOfSight: true);
    }

    // True when our team may target something at this position.
    public bool CanTarget(Vector3 targetPosition)
    {
        var fog = RTSFogOfWarManager.Active;
        // Without a running fog, everything is visible.
        if (fog == null || !fog.IsRunning) return true;
        // For teams other than the local one, enable "Compute All Teams" on the manager,
        // otherwise IsVisible returns false for them.
        return fog.IsVisible(targetPosition, team);
    }
}

例:同盟と、コードからスポーンしたユニット

using RTSTerrainStudio;
using UnityEngine;

public class TeamSetup : MonoBehaviour
{
    [SerializeField] GameObject unitPrefab;

    void Start()
    {
        var fog = RTSFogOfWarManager.Active;
        if (fog != null)
        {
            fog.SetAlliance(0, 2, true);       // teams 0 and 2 see what the other sees
            fog.SetSharedVision(3, 0, true);   // team 0 also sees what team 3 sees (one way)
        }
        SpawnUnit(1, new Vector3(40f, 0f, 40f));
    }

    // Creates an enemy unit that sees and can be hidden by the fog of war.
    void SpawnUnit(int team, Vector3 position)
    {
        var go = Instantiate(unitPrefab, position, Quaternion.identity);

        // Reuse components the prefab may already have (RTSFogOfWarHideable allows only one per object).
        if (!go.TryGetComponent(out RTSFogOfWarRevealer revealer))
            revealer = go.AddComponent<RTSFogOfWarRevealer>();
        revealer.Team = team;
        revealer.Radius = 12f;               // metres

        if (!go.TryGetComponent(out RTSFogOfWarHideable hideable))
            hideable = go.AddComponent<RTSFogOfWarHideable>();
        // Subscribe BEFORE setting Team and Mode: in Play Mode both setters re-test the object immediately,
        // so a unit spawned in the fog is hidden (and the event raised) right away.
        // React when the unit appears or disappears for the local player (sounds, minimap pings ...).
        hideable.VisibilityChanged += visible => Debug.Log(go.name + (visible ? " spotted" : " lost"));
        hideable.Team = team;
        hideable.Mode = FogOfWarHideMode.HideWhenNotVisible;
        // The current state after configuring.
        Debug.Log(go.name + (hideable.IsVisible ? " starts visible" : " starts hidden"));
    }
}

戦場の霧付きのミニマップ

RTSFogOfWarMinimapUtility は、クラシック RTS のミニマップのように、ローカルチームの戦場の霧でミニマップ画像を暗くします。ミニマップはマップ領域をちょうど覆っている必要があり、UV (0, 0) がマップの原点側の角、UV (1, 1) がその反対側の角になるようにします。RTSMinimapBaker でベイクした画像は、このレイアウトになっています。サンプルの RTSMinimapUI は、このユーティリティを使用しています。

メソッド説明
static bool Composite(Texture minimap, RenderTexture destination, RTSFogOfWarManager manager = null)GPU による処理で、リアルタイムに更新されるミニマップに最適です。Updated の後、または数フレームごとに呼び出します。有効な戦場の霧がない場合、ミニマップはそのままコピーされます。シェーダーが使用できない場合は false を返します。
static bool CompositeCpu(Texture2D source, Texture2D destination, RTSFogOfWarManager manager = null)CPU による処理(Burst ジョブ)です。レンダーテクスチャが不要なので、スクリーンショットなどに適しています。source は読み取り可能である必要があります。destination は、同じサイズの読み取り可能な RGBA32 テクスチャである必要があります(同じテクスチャでもかまいません)。
static bool CompositeCpu(NativeArray<Color32> source, NativeArray<Color32> destination, int width, int height, RTSFogOfWarManager manager = null)生のピクセルバッファを対象とした CPU による処理です。
static void ReleaseResources()キャッシュされたマテリアルとコマンドバッファを破棄します。ドメインリロードの前に自動的に呼び出されます。ミニマップを完全に閉じるときは、自分で呼び出してください。
using RTSTerrainStudio;
using UnityEngine;

// Bakes the map once and keeps a fogged copy in a render texture.
// Show Output with a RawImage (uGUI) or a UI Toolkit Image.
public class FoggedMinimap : MonoBehaviour
{
    [SerializeField] int size = 256;      // longest side in pixels

    Texture2D baked;
    RenderTexture output;
    RTSFogOfWarManager fog;

    public RenderTexture Output => output;

    void OnEnable()
    {
        var terrain = RTSTerrain.Main;
        if (terrain == null) return;

        baked = RTSMinimapBaker.Bake(terrain, size);   // map image with cliffs, water, trees ...
        if (baked == null) return;
        output = new RenderTexture(baked.width, baked.height, 0, RenderTextureFormat.ARGB32);

        fog = RTSFogOfWarManager.Active;
        if (fog != null) fog.Updated += Refresh;          // refresh after each visibility update
        Refresh();
    }

    void OnDisable()
    {
        if (fog != null) fog.Updated -= Refresh;
        if (output != null) { output.Release(); Destroy(output); }
        if (baked != null) Destroy(baked);
    }

    void Refresh()
    {
        if (baked != null && output != null)
            RTSFogOfWarMinimapUtility.Composite(baked, output, fog);
    }
}

マルチプレイヤーのヒント

  • 各クライアントは、自身の Local Team を基準に、それぞれ独自に戦場の霧を処理します。戦場の霧のデータがネットワーク経由で送信されることはありません。
  • サーバー権威型の構成では Compute All Teams を有効にし、GetVisibilityMask(position) を使用して、どのクライアントにどのユニットを送信するかを決定します。参照はユニットごとに 1 回だけなので、負荷はごくわずかです。
  • サーバーとリッスンサーバーのホストでは、隠蔽対象の Hide Colliders をオフのままにしてください。こうすることで、ホストのプレイヤーから見えないユニットにも、サーバー側のレイキャストが当たります。プレイヤーによる選択は、代わりに IsVisibleToLocalTeam で絞り込みます。
  • 同盟はローカルの設定です。クライアントで同盟チームの視界を表示する場合は、サーバーとクライアントの両方で SetAlliance を呼び出してください。

Mirror 連携は、これらをすべて自動で行います。RTSNetworkFogOfWar がマネージャーを設定し(サーバーはすべてのチームを計算し、クライアントは自分のチームだけを計算します)、RTSFogOfWarInterestManagement は戦場の霧に隠れた敵ユニットを決して送信しません(マップハック対策)。詳しくは Mirror 連携の章を参照してください。

パフォーマンスと制限

  • 可視性は毎フレームではなく、Update Interval 秒ごと(デフォルトでは 0.1)に Burst ジョブで計算されます。視界源と隠蔽対象は内部リストで管理されるため、フレームごとにシーンを検索する処理は発生しません。
  • メモリ使用量は、霧セル 1 つあたり約 18 バイトです。Resolution Per Cell が 2 の 128 × 128 マップでは 256 × 256 のグリッドになり、約 1.2 MB です。
  • 大きなマップ:グリッドは 1 軸あたり 2048 セルに制限されており、この範囲に収まるように、セルあたりの解像度が自動的に下げられます。
  • 視線判定モードのうち、None が最も負荷が低く、CliffLevelsAndBlockers が最も負荷が高くなります。ただし、視線がトレースされるのは、近くにより高いセルまたは視界遮蔽物がある視界源だけです。
  • 霧のテクスチャはマップの軸に揃っています。地形が回転していると、警告がログに出力されます。
  • 木や装飾オブジェクトを編集すると、視界遮蔽物のラスターがマップ全体で再構築されます。崖の編集では、変更された領域だけが更新されます。

何も霧に覆われませんか?マネージャーが有効で、再生モード中であること(または Preview In Edit Mode がオンであること)、Reveal All がオフであること、地形にマップがあることを確認してください。地形だけが霧に覆われ、ユニットやプロップが覆われない場合は、RTS Fog Of War レンダラー機能を追加します。すべてが真っ黒な場合は、ローカルチーム(またはローカルチームと視界を共有しているチーム)の視界源が 1 つも有効になっていません。インスペクターの「No revealers exist」というメッセージは、シーンに視界源がまったくない場合にしか表示されません。そのため、視界源の Team が Local Team と合っているかも確認してください。

通行判定と経路探索

すべての RTS Terrain は通行マップを保持しています。これは、マップ上の小さな正方形の区画ごとに、地上ユニットが歩けるか、建物を配置できるか、空中ユニットが飛べるか、水があるかを示すグリッドです。通行マップは地形から自動的に生成され、編集中も更新されます。さらにこのパッケージには、この通行マップを使って動作する高速な組み込みの経路探索エンジン(Burst A*)が付属しており、サードパーティ製のパッケージなしで使用できます。

A* Pathfinding Project や Unity の NavMesh を使いたい場合は、それぞれの連携機能が同じ通行マップからグラフを構築します。詳しくは各連携の章を参照してください。

通行マップ

通行マップは、マップセルの 1 辺あたり PathingResolution 個の通行セルで構成されます(1、2、4 のいずれかで、マップ作成時に選択します。デフォルトは 2)。デフォルトの 2 m のマップセルでは、通行セルの幅は 1 m です。各セルには、次の PathingFlags の組み合わせが格納されます。

フラグ意味
Walkable地上ユニットがここを歩けます。
Buildableここに建物を配置できます。
Flyable空中ユニットがここを飛べます。すべてのセルに設定されますが、Unflyable 効果によって解除される場合があります。この効果を与えるのは、ペイントされた通行オーバーライド、タイルセットの地面レイヤールール、装飾プロトタイプの Footprint Effect です。
ShallowWater浅水域:歩行可能ですが、デフォルトでは建設できません。
DeepWater深水域:海上ユニット専用です。
Cliffセルが崖の壁面に接しています。
Blockedセルが木または装飾オブジェクトのフットプリントによってブロックされています。
Steepセルの傾斜が、地上ユニットには急すぎます。

フラグの決定方法。ルールは Burst ジョブとして、次の順序で適用されます。

  1. 地形。崖の壁面(Cliff Margin の分だけ広げたもの)は Cliff になります。傾斜が Max Walkable Slope を超える場所は Steep になります。水深がタイルセットの浅水域の深さ上限を超える水は DeepWater、それ以外は ShallowWater になります。Flyable はすべてのセルに設定されます。Buildable は、歩行可能で、傾斜が Max Buildable Slope より緩やかで、水がない(または Shallow Water Buildable がオンの場合は浅水域の)セルに設定されます。続いて、優勢な地面レイヤーの通行効果が適用されます(たとえば、粗い岩のレイヤーの「Unbuildable」)。2 つの崖レベルをつなぐ有効なスロープでは、傾斜の判定に微細高さだけが使われるため、歩行可能なままです。ただし、スロープが建設可能になることはありません。
  2. フットプリント。木は円形の範囲(木のプロトタイプの Footprint Radius × インスタンスのスケール × Tree Footprint Scale)をブロックします。この範囲には Blocked が設定され、Walkable と Buildable が解除されます。装飾オブジェクトは、プロトタイプの Footprint Effect(デフォルトでは Unwalkable と Unbuildable)を、円形または回転したボックスの範囲に適用します。IgnorePathing フラグを持つ装飾インスタンスはスキップされます。
  3. Pathing ツール(後述)でペイントした手動オーバーライド。Unwalkable 効果は、Force Buildable が設定されていない限り、Buildable も解除します。

通行マップは逐次再構築されます。ある領域を編集すると、その領域(とその周囲のわずかなマージン)だけが再計算されます。

通行ルール(RTSPathingSettings)

RTS Terrain を選択し、インスペクターの Pathing Rules セクションを開きます。値を変更すると、通行マップが再構築されます。

設定デフォルト説明
Max Walkable Slope42この角度(度)より急な地面は歩行できません。
Max Buildable Slope18この角度(度)より急な地面には建設できません。
Cliff Margin0.25ブロックされる領域が、崖の壁面からどこまで外側に広がるか(通行セルに対する割合)。値を大きくすると、ユニットが崖の縁からより離れた位置にとどまります。
Shallow Water Buildableオフ浅水域での建物の建設を許可します。
Tree Footprint Scale1すべての木の、通行判定用フットプリント半径に掛ける倍率です。
Use Ground Layer Rulesオン優勢な地面レイヤーの通行効果を適用します(タイルセットでレイヤーごとに設定します。例:粗い岩のレイヤーに「Unbuildable」)。

コードからは、terrain.PathingSettings = settings で新しい設定を割り当てる(これにより再構築されます)か、現在のインスタンスのフィールドを変更してから terrain.RebuildPathing() を呼び出します。

通行判定の手動ペイント

自動ルールだけでは、意図どおりの結果にならないこともあります。たとえば、マップ境界に見えない壁を置きたい場合、拡張拠点の場所を空けておくために建設禁止エリアを作りたい場合、橋を歩いて渡れるようにしたい場合などです。そのような場合は Pathing ツールを使用します(シーンビューで 7 キーを押すと、Pathing カテゴリが選択されます)。

ツール / オーバーライド効果
Paint Pathing: Blocker歩行不可かつ建設不可:見えない壁です(マップ境界、装飾用のエリアなど)。
Paint Pathing: Unwalkable地上ユニットはここを歩けません。
Paint Pathing: Unbuildableここには建物を配置できません(通路や拡張拠点の場所を空けておくため)。
Paint Pathing: Unflyable空中ユニットはここを飛べません。
Paint Pathing: Force Walkable傾斜、水、フットプリントによってブロックされる場合でも、地上ユニットはここを歩けます。
Paint Pathing: Force Buildable自動で決定されたルールが禁止している場合でも、ここに建物を配置できます。
Clear Pathingブラシの下にあるペイント済みのオーバーライドをすべて削除し、自動の通行判定が再び適用されるようにします。

Paint Pathing の使用中に Shift キーを押したままにすると、選択中のオーバーライドを削除できます。あるモードをペイントすると、それと矛盾するオーバーライドが削除されます(Force Walkable をペイントすると Unwalkable が削除されます)。そのため、1 つのセルに矛盾するルールが共存することはありません。

通行オーバーレイ

オーバーレイは、クラシック RTS のマップエディターの通行表示のように、地形を通行判定に応じて色分けします。オーバーレイは次の方法でオンにできます。

  • シーンビューのオーバーレイにある Pathing ドロップダウン(Pathing: Off、Walkability、Buildability、Flyability、Water、Combined)
  • RTS Terrain Studio ウィンドウの Pathing Overlay ドロップダウンと不透明度スライダー
  • シーンビューでの P キー。Off → Walkability → Buildability → Combined → Off の順に切り替わります(ショートカット RTS Terrain Studio/Cycle Pathing Overlay。Edit ▸ Shortcuts...、macOS では Unity ▸ Shortcuts... でキーを割り当て直せます)
  • ゲーム内エディターの通行表示
モード色
Walkability緑 = 歩行可能、赤 = ブロックされている、暗い赤 = 崖、オレンジ = 傾斜が急すぎる、青 = 深水域、シアン = 浅水域。
Buildability緑 = 建設可能、黄 = 歩行可能だが建設不可、赤 / 暗い赤 = 歩行不可。
Flyability水色 = 飛行可能、マゼンタ = 飛行不可。
Waterシアン = 浅水域、青 = 深水域。
Combinedすべてを同時に表示します(クラシック RTS スタイルの通行表示)。

自分のゲームでは、PathingOverlayController を使ってオーバーレイを表示します。このコントローラーは、通行判定が変化してもテクスチャを常に最新の状態に保ちます。

using RTSTerrainStudio;
using UnityEngine;

// Shows the buildability overlay on the terrain while this component is enabled (e.g. in build mode).
public class BuildModeOverlay : MonoBehaviour
{
    [SerializeField] RTSTerrain terrain;
    [SerializeField, Range(0f, 1f)] float opacity = 0.6f;

    PathingOverlayController overlay;

    void OnEnable()
    {
        if (terrain == null) terrain = RTSTerrain.Main;
        if (terrain == null) return;
        overlay = new PathingOverlayController(terrain, PathingOverlayMode.Buildability);
        // The texture can be replaced (for example after a map resize): re-assign it when that happens.
        overlay.TextureChanged += texture => terrain.SetOverlayTexture(texture, opacity);
        terrain.SetOverlayTexture(overlay.Texture, opacity);
    }

    void OnDisable()
    {
        if (terrain != null) terrain.SetOverlayTexture(null, 0f);
        overlay?.Dispose();   // destroys the overlay texture
        overlay = null;
    }
}

表示する情報は overlay.Mode = PathingOverlayMode.Walkability で切り替えられます(テクスチャが再描画されます)。overlay.Refresh() で全体の再描画を強制することもできます。新しいコントローラーを作成する必要はありません。

その場限りのテクスチャには、PathingOverlay.CreateOrUpdate(map, mode, ref texture)、PathingOverlay.UpdateRegion(...)、PathingOverlay.DestroyTexture(ref texture) を使用します。PathingOverlay.GetColor(flags, mode) は、フラグの組み合わせに対応する凡例の色を返します。

コードからの通行マップの読み取り

RTSTerrain のメンバー説明
PathingFlags GetPathing(Vector3 world)ワールド位置のフラグ(マップの外側、または通行マップが構築される前は None)。
bool IsWalkable(Vector3 world), bool IsBuildable(Vector3 world)最もよく使う 2 つのフラグを調べるためのショートカット。
PathingMap Pathing通行グリッド:Width、Height、CellSize、Flags(セルごとに 1 バイト、インデックス = z × Width + x)、Get(x, z)、Version。
event Action<RTSTerrain, RectInt> PathingChanged全体または部分的な再構築の後に、変更された矩形(通行セル単位)とともに発生します。マップを再読み込みしても登録は維持されます。サンプルのユニットは、このイベントを使って経路を再計画します。
RTSPathingSettings PathingSettings, void RebuildPathing()ルールと、全体の再構築。
ClearanceMap GetPathingClearance()最新の状態に更新されたクリアランスマップ(ユニットのサイズを参照)。
void RegisterPathingReader(JobHandle), void CompletePathingReaders()Flags やクリアランスを読み取る独自のジョブ用です。地形は、通行データを書き込む前にそれらのジョブの完了を待ちます。

経路の探索:RTSPathfinder

RTSPathfinder は、Burst でコンパイルされた A* を使って通行マップ上の経路を探索します。角をすり抜けない 8 方向の移動、大型ユニット向けのクリアランス(障害物までの余裕距離)、経路を滑らかにするストリングプリングに対応しています。始点やゴールがブロックされている場合は、最も近い空きセルに置き換えられます。メインスレッドで同期的に実行されます。1 フレームあたり数件程度のリクエストや、編集モードでの使用であれば問題ありません。

using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;

public class PathDebug : MonoBehaviour
{
    [SerializeField] Transform target;
    readonly List<Vector3> path = new List<Vector3>();

    void OnDrawGizmos()
    {
        var terrain = RTSTerrain.Main;
        if (terrain == null || target == null) return;

        // A unit with a radius of 0.6 m: the path keeps that distance from obstacles.
        var options = PathRequestOptions.ForUnit(0.6f);
        PathResult result = RTSPathfinder.FindPathDetailed(terrain, transform.position, target.position, options, path);

        // Success = reached the goal, Partial = closest reachable point.
        Gizmos.color = result.status == PathStatus.Success ? Color.green : Color.yellow;
        for (int i = 1; i < path.Count; i++) Gizmos.DrawLine(path[i - 1], path[i]);
    }
}
メソッド説明
static PathStatus FindPath(RTSTerrain, Vector3 startWorld, Vector3 endWorld, List<Vector3> resultWorld)PathRequestOptions.Default を使用します。
static PathStatus FindPath(RTSTerrain, Vector3, Vector3, PathRequestOptions, List<Vector3>)resultWorld はクリアされ、始点と終点を含むウェイポイントが格納されます。
static PathResult FindPathDetailed(RTSTerrain, Vector3, Vector3, PathRequestOptions, List<Vector3>)経路の長さ、統計情報、始点またはゴールが移動されたかどうかも返します。
static void DisposeSharedBuffers()探索バッファを解放します(ドメインリロード時と終了時にも自動的に行われます)。

PathRequestOptions(PathRequestOptions.Default または PathRequestOptions.ForUnit(radius) をベースにします):

フィールドデフォルト説明
unitRadius0ユニットの半径(ワールド単位)。経路は障害物からこの距離を保ちます。クリアランスは、required に Walkable が含まれている場合にのみ適用されます。海上(DeepWater)や空中(Flyable)のリクエストでは、unitRadius は効果がありません。クリアランスマップは歩行不可のセルまでの距離を測定し、マップ境界も障害物として扱われます。PathingQueries.FindNearest、LineOfSight、Raycast にも同じことが当てはまります。
requiredWalkable経路上のすべてのセルが持っている必要があるフラグ(None は Walkable として扱われます)。
allowShallowWatertrue浅水域(浅瀬)の歩行を許可します。
maxSearchNodes500000探索を打ち切るまでに展開するノードの最大数(0 = 無制限)。
smoothPathtrueグリッド上の視線判定で、不要なウェイポイントを削除します(ストリングプリング)。
snapToGroundtrueすべてのウェイポイントの Y を地面の高さに合わせます。
allowPartialtrueゴールに到達できない場合、到達可能な最も近いセルまでの経路を返します。
goalSearchRadius16ゴールのセルがブロックされている場合に、最も近い空きセルを探す距離(ワールド単位)。
PathStatus意味
Successゴール(ゴールがブロックされている場合は、その最寄りの空きセル)までの経路が見つかりました。
Partialゴールに到達できません。経路は、ゴールに最も近い到達可能なセルまで続きます。
NoPath経路が存在せず(または探索の上限に達し)、部分的な経路も許可されていませんでした。
InvalidStart始点がマップの外側にあるか、通行データがないか、始点の近くに通行可能なセルがありません。

PathResult のフィールド:requestId、terrain、status、points(ワールド座標のウェイポイント)、length(水平方向の長さ、ワールド単位)、searchedNodes、startAdjusted、goalAdjusted、userData、HasPath(Success または Partial で、ウェイポイントが 1 つ以上あること)。

多数のユニット:RTSPathfindingService

多数のユニットには RTSPathfindingService を使用します。このサービスは 1 フレーム分のリクエストを集め、LateUpdate で(地形が編集を適用した後に)並列の Burst ジョブとして実行し、次のフレームの Update でコールバックを通じて結果を返します。Add Component ▸ RTS Terrain Studio ▸ Pathfinding Service で追加するか、再生モードで RTSPathfindingService.GetOrCreate() を呼び出します(シーンを読み込んでも破棄されないサービスオブジェクトが作成されます)。このサービスは再生モードでのみ動作します。編集モードでは RTSPathfinder を使用してください。

設定デフォルト説明
Max Requests Per Frame641 フレームあたりに開始する経路探索の数。それを超えるリクエストはキューで待機します。
Max Parallel Jobs4並列で実行するジョブの数(1~16)。各ジョブには専用の探索バッファが必要です(通行セル 1 つあたり約 24 バイト)。
Memory Budget MB256探索バッファのメモリの上限。非常に大きなグリッドでは、並列ジョブの数が減らされます。バッファ 1 セットだけでも上限を超える場合は、警告がログに出力されます。
Complete In Same Frameオフジョブをスケジュールしたフレームの終わりに完了させます(レイテンシーは低くなりますが、並列性も下がります)。
メンバー説明
int RequestPath(RTSTerrain, Vector3 startWorld, Vector3 endWorld, PathRequestOptions, Action<PathResult> callback, List<Vector3> resultBuffer = null, object userData = null)リクエストをキューに追加し、その ID を返します。resultBuffer を指定すると、ウェイポイントは指定したリスト(最初にクリアされます)に書き込まれるため、リクエストごとのメモリ割り当てを避けられます。
int RequestPath(RTSTerrain, Vector3, Vector3, Action<PathResult>)上記と同じで、デフォルトのオプションを使用します。
bool Cancel(int requestId)キュー内のリクエスト、実行中のリクエスト、または完了済みで未配信のリクエストをキャンセルします。キャンセルしたリクエストのコールバックは実行されません。
void CancelAll(), void CompleteAll()すべてをキャンセルするか、すべてを今すぐ完了させます(コールバックはメソッドが戻る前に実行されます)。
PendingCount, InFlightCountキューとジョブの統計情報。
static Instance, static GetOrCreate()有効になっている最初のサービス、または新しく作成したサービス。
using RTSTerrainStudio;
using UnityEngine;

// Asks the service for a path and replaces the previous request when a new order arrives.
public class AsyncMover : MonoBehaviour
{
    [SerializeField] float radius = 0.5f;
    int requestId;   // 0 = no request pending

    public void OrderMove(Vector3 target)
    {
        var terrain = RTSTerrain.Main;
        var service = RTSPathfindingService.GetOrCreate(); // null outside Play Mode
        if (terrain == null || service == null) return;

        if (requestId != 0) service.Cancel(requestId);       // the old callback will not run
        requestId = service.RequestPath(terrain, transform.position, target,
            PathRequestOptions.ForUnit(radius), OnPathFound);
    }

    void OnPathFound(PathResult result)
    {
        requestId = 0;
        if (!result.HasPath) { Debug.Log("Can't reach that place."); return; }
        // result.points holds the waypoints. Copy them if you keep them.
        Debug.Log($"{result.points.Count} waypoints, {result.length:0.0} m, status {result.status}");
    }
}

ユニットのサイズ:クリアランス

大型ユニットが、小型ユニット向けの隙間をすり抜けてはいけません。地形はクリアランスマップを保持しています。これは、歩行可能な各セルについて、最も近い歩行不可のセルまでの距離(上限 64 セル)を記録したものです。リクエストに unitRadius が指定されている場合、経路探索エンジンはユニットが収まるセルだけを使用します。クリアランスマップは、編集後に逐次更新されます。

  • PathingQueries.GetClearance(terrain, world) は、ある位置の空き半径をワールド単位で返します(歩行不可のセルでは負の値)。
  • ClearanceMap(terrain.GetPathingClearance() で取得)は、GetClearanceCells(x, z)、GetClearanceMetres(x, z)、ClearanceMap.RequiredValue(unitRadius, cellSize) を提供します。障害物の斜めの角の近くでは、値が最大で約 0.13 セル分大きめになることがあります。これにより、半径 0.3~0.5 セルのユニットが、物理的に通れる幅 1 セルの通路を利用できるようになっています。

ゲームプレイ用クエリ:PathingQueries

PathingQueries は、RTS のゲームプレイで頻繁に必要になる問い合わせに答えます。位置はすべてワールド空間で、サイズと半径はワールド単位で指定します。

メソッド説明
bool FindNearestWalkable(RTSTerrain, Vector3 world, float maxRadius, float unitRadius, out Vector3 result)半径 unitRadius のユニットが収まる歩行可能な位置のうち、world に最も近い位置(スポーン地点、集結地点など)。
bool FindNearest(RTSTerrain, Vector3 world, float maxRadius, float unitRadius, PathingFlags required, out Vector3 result)任意のフラグを対象に同じ処理を行います(例:船には DeepWater)。
Vector3 SnapToBuildGrid(RTSTerrain, Vector3 world, Vector2 sizeMetres)フットプリントが通行セルをちょうど覆うように、建物の位置をスナップします(サイズが偶数ならセルの角、奇数ならセルの中心に合わせます)。結果の位置は地面上になります。
bool IsAreaBuildable(RTSTerrain, Vector3 worldCenter, Vector2 sizeMetres)フットプリント内のすべての通行セルが Buildable の場合に true を返します。
bool IsAreaClear(RTSTerrain, Vector3 worldCenter, Vector2 sizeMetres, PathingFlags required)任意のフラグを対象に同じ判定を行います。マップの外側では false を返します。
bool TryGetAreaCells(RTSTerrain, Vector3 worldCenter, Vector2 sizeMetres, out RectInt cells)フットプリントが覆う通行セル。フットプリントが完全にマップ内に収まっていない場合は false を返します。
float GetClearance(RTSTerrain, Vector3 world)ある位置の周囲の空き半径。
bool LineOfSight(RTSTerrain, Vector3 fromWorld, Vector3 toWorld, PathingFlags required = Walkable, float unitRadius = 0)直進できる場合に true を返します(グリッド上の視線判定、角のすり抜けなし)。これは移動に関する判定で、戦場の霧の視界とは関係ありません。
bool Raycast(RTSTerrain, Vector3 fromWorld, Vector3 toWorld, PathingFlags required, float unitRadius, out Vector3 lastFreeWorld)線分に沿って進み、障害物の手前にある最後の空き位置を返します(突撃、ノックバックなど)。

建物の配置とランタイムでの通行判定の変更

クラシック RTS の建物の配置は 3 つの手順で行います。フットプリントをグリッドにスナップし、配置できるかをチェックし、通行マップ上でブロックします。ブロックするには、MapData.PathingOverrides に手動のオーバーライドを書き込み、そのことを地形に通知します。地形はその領域を差分で再構築して PathingChanged を発行し、移動中のユニットは新しい建物を迂回する経路を計画し直します。

using RTSTerrainStudio;
using UnityEngine;

public static class BuildingFootprint
{
    // Tries to place a footprint of sizeMetres (X/Z) at the cursor and blocks it for walking and building.
    // Returns the covered pathing cells and their previous override bytes, so Remove can restore them
    // (cells may already hold painted overrides, for example Force Walkable on a bridge).
    public static bool TryPlace(RTSTerrain terrain, Vector3 cursorWorld, Vector2 sizeMetres,
        out RectInt pathingCells, out byte[] previous)
    {
        pathingCells = default;
        previous = null;
        if (terrain == null || terrain.Map == null) return false;

        // 1. Align the footprint with the pathing grid.
        Vector3 center = PathingQueries.SnapToBuildGrid(terrain, cursorWorld, sizeMetres);

        // 2. Every cell must be buildable.
        if (!PathingQueries.IsAreaBuildable(terrain, center, sizeMetres)) return false;
        if (!PathingQueries.TryGetAreaCells(terrain, center, sizeMetres, out pathingCells)) return false;

        // 3. Keep the old bytes, then write Unwalkable | Unbuildable overrides (one byte per pathing cell).
        MapData map = terrain.Map;
        var overrides = map.PathingOverrides;
        previous = new byte[pathingCells.width * pathingCells.height];
        byte block = (byte)(PathingOverride.Unwalkable | PathingOverride.Unbuildable);
        int n = 0;
        for (int z = pathingCells.yMin; z < pathingCells.yMax; z++)
            for (int x = pathingCells.xMin; x < pathingCells.xMax; x++, n++)
            {
                int i = map.PathingIndex(x, z);
                previous[n] = overrides[i];
                overrides[i] = (byte)(overrides[i] | block);
            }

        Notify(terrain, pathingCells);
        return true;
    }

    // Restores the override bytes stored by TryPlace (call it when the building is destroyed).
    public static void Remove(RTSTerrain terrain, RectInt pathingCells, byte[] previous)
    {
        if (terrain == null || terrain.Map == null || previous == null) return;
        MapData map = terrain.Map;
        var overrides = map.PathingOverrides;
        int n = 0;
        for (int z = pathingCells.yMin; z < pathingCells.yMax; z++)
            for (int x = pathingCells.xMin; x < pathingCells.xMax; x++, n++)
                overrides[map.PathingIndex(x, z)] = previous[n];
        Notify(terrain, pathingCells);
    }

    // 4. NotifyChanged expects map cells, so convert the pathing rectangle.
    static void Notify(RTSTerrain terrain, RectInt pathingCells)
    {
        int res = Mathf.Max(1, terrain.Map.PathingResolution);
        int xMin = pathingCells.xMin / res, zMin = pathingCells.yMin / res;
        int xMax = Mathf.CeilToInt(pathingCells.xMax / (float)res);
        int zMax = Mathf.CeilToInt(pathingCells.yMax / (float)res);
        terrain.NotifyChanged(MapLayers.PathingOverrides, new RectInt(xMin, zMin, xMax - xMin, zMax - zMin));
    }
}

建物を削除するには、保存しておいたセルとバイトを BuildingFootprint.Remove に渡します。このメソッドは以前のオーバーライドのバイトを書き戻し、もう一度 NotifyChanged を呼び出します。サンプルの RTSDemoBuilding はまさにこの処理を行っています。詳しくは建物配置デモを参照してください。

木の削除も、植生レンダラーを通じて同じように行えます。RTSVegetationRenderer.RemoveTree(id) が地形に変更を通知するため、通行マップでは木のフットプリントが自動的に再び通行可能になり、戦場の霧からは視界遮蔽物が取り除かれます。詳しくは木の伐採デモを参照してください。

応用:独自のジョブでの通行データの利用

カスタムの Burst ジョブ(フローフィールド、影響マップなど)では、new PathingGrid(terrain.Pathing, terrain.GetPathingClearance()) で読み取り専用のビューを作成します。このビューは IsPassable、LineOfSight、Trace、FindNearestPassable を通行セル単位で提供し、判定には PathingFilter(PathingFilter.ForUnit(required, allowShallowWater, unitRadius, cellSize))によるフィルターが適用されます。ジョブハンドルは terrain.RegisterPathingReader(handle) で登録してください。こうすると、地形は通行データを変更する前にそのジョブを完了させます。

PathingMapBuilder は、どの地形にも表示されていない MapData オブジェクト(たとえば、マップをレンダリングせずに読み込むサーバーの場合)の通行マップを構築します:PathingMapBuilder.Build(map, pathing, settings, tileset, vegetation, decor)。

サンプルゲームプレイスクリプト

Samples/Scripts フォルダーには、小規模ながら完結した RTS のゲームレイヤーが含まれています。RTS カメラ、地形に沿って移動するユニット、コントロールグループ付きの選択、隊形を組んだ移動命令、ミニマップ、HUD、そしてランタイムでマップを変更する 2 つのデモです。これらはアセンブリ RTSTerrainStudio.Samples、名前空間 RTSTerrainStudio.Samples に含まれています。

これらのスクリプトは、作成したマップをすぐに試すのにも、自分のゲームの出発点にも、ランタイム API の使い方の参考にも使えます。すべてのスクリプトにはコメントが付いており、Unity メッセージは必要に応じて protected virtual になっているため、派生クラスを作って拡張できます。

すべてのサンプルコンポーネントは Add Component ▸ RTS Terrain Studio ▸ Samples にあります。入力は RTSInput を通じて読み取られるため、サンプルは Input System パッケージと旧 Input Manager の両方で動作します。UI は UI Toolkit で構築されています。

クイックスタート:任意のマップをプレイ可能にする

  1. RTS Terrain があり、そのマップに開始地点が設定されているシーンを開きます(開始地点は Markers ツールで配置します)。
  2. GameObject ▸ RTS Terrain Studio ▸ Demo Game を選択します。RTSDemoGame コンポーネントを持つ RTS Demo Game オブジェクトが作成されます。
  3. Play をクリックします。デモゲームは戦場の霧、カメラコントローラー、選択、命令、ミニマップ、HUD を作成し、すべての開始地点にユニットをスポーンして(自分はプレイヤー 1 で、ほかのプレイヤーのユニットは歩き回ります)、カメラを自分の拠点に移動します。
  4. F1 キーを押すと、操作説明の表示・非表示を切り替えられます。

デフォルトの操作

入力操作
W A S D / 矢印キーカメラをパンします。ユニットを選択している間は、S キーはユニットを停止させ、パンは行いません(矢印キーは常にパンします)。
画面端、マウスの中ボタンでドラッグカメラをパンします / 地面をつかんでドラッグします。
マウスホイールズームします(ズームインするほど視点が水平に近づきます)。
Q / E、Alt + 右ドラッグ視点を回転します。
左クリック / ドラッグユニットを選択します / 選択ボックスを描きます。
Shift + クリックユニットを選択に追加、または選択から除外します。
ダブルクリック、Ctrl + クリック画面上にある同じ種類のユニットをすべて選択します。
Escまず現在のモードをキャンセルし、もう一度押すと選択を解除します。ゲーム内エディターでのテストプレイ中は、さらにもう一度 Esc キーを押すとエディターに戻ります。
Ctrl+1…9、0コントロールグループを登録します(Shift+数字キーでグループに追加します)。
1…9、0グループを呼び出します(2 回押すと、カメラがそのグループの中心に移動します)。
右クリック隊形を組んで移動します(Shift キーを押しながらだと、命令がキューに追加され、前の命令に続けて隊形が組まれます)。
S / H / P停止 / 位置を保持 / パトロール(パトロールの場合は、続けて 2 つ目のパトロール地点を左クリックします)。
ミニマップ左クリックまたはドラッグ:カメラを移動します。右クリック:選択中のユニットを移動させます。
B建物を配置します(ランタイムで通行判定が変化します)。
Deleteカーソル下の建物を削除します。
Tカーソル下の木を伐採します。
木を右クリック選択中のワーカーがその木まで歩いて行き、伐採します。
F1ヘルプパネルの表示・非表示を切り替えます。

すべてのキーは各コンポーネント(カメラ、選択、コマンド、デモ、HUD)の RTSKey フィールドなので、インスペクターでキーを割り当て直すことができます。

RTSDemoGame:ブートストラップ

RTS Terrain がある任意のシーンを、プレイ可能な小さな RTS に変えます。ゲームが始まると、次の処理を行います:

  1. RTSFogOfWarManager(ローカルチーム = Local Team、探索状態をリセット)と経路探索サービスを用意します。
  2. カメラコントローラー、選択、コマンド、ミニマップ、HUD、建物配置デモ、木の伐採デモを検索し、足りないものを作成します。
  3. すべての開始地点にユニットをスポーンし(チーム = プレイヤーのインデックス。プレイヤー 1 はチーム 0)、敵ユニットに RTSWanderAI を付与します。これにより、敵ユニットが霧の中に現れたり消えたりする様子を観察できます。
  4. カメラをローカルプレイヤーの開始地点に移動します。

ゲーム内エディター:このコンポーネントは RTSRuntimeEditorEvents.PlaytestChanged を監視します。ゲーム内エディターでテストプレイを開始するとゲームが始まります。テストプレイを終了すると、ユニットのデスポーン、デモ用の建物の削除、伐採された木の復元(オプション)、ゲームプレイシステムの無効化が行われ、カメラの制御も元に戻ります。Auto Start がオンで、シーンにゲーム内エディターがない場合は、ゲームがすぐに始まります。

設定デフォルト説明
Start
Auto Startオンシーンの再生時にゲーム内エディターがなければ、ゲームを開始します(ある場合は、エディターでテストプレイを開始したときにゲームが始まります)。
Local Team0ローカルプレイヤーのチーム(0 = プレイヤー 1)。コードから設定すると、戦場の霧と選択も更新されます。
Terrain空プレイする地形。空の場合は RTSTerrain.Main を使用します。
Units
Unit Prefabs空ユニットのプレハブ。順番に使用されます。空の場合は、プロシージャルに生成されるプレースホルダーユニットを使用します。
Units Per Player8プレイヤーごとにスポーンするユニット数(1~60)。
Max Players8ユニットが与えられるプレイヤーの最大数。
Enemy Wander AIオン敵ユニットが歩き回ります。
Wander Radius30敵 AI が歩き回る半径(メートル単位)。
Systems
Create Missing Systemsオンカメラコントローラー、選択、コマンド、ミニマップ、HUD、デモがない場合に作成します。ユニットスポナーは常に作成されます。
Fog Of Warオンプレイ中に戦場の霧を有効にします。
Minimapオンミニマップを表示します。
HudオンHUD(情報パネル、アクションバー、F1 ヘルプ)を表示します。
Show Help On Startオンゲーム開始時にヘルプパネルを表示します。
Building Demoオン建物配置デモ(B)を有効にします。
Tree Harvest Demoオン木の伐採デモ(T、ワーカー)を有効にします。
Restore Map On Stopオンゲームの停止時に伐採された木を復元し、テストプレイ後も編集中のマップが変わらないようにします。
References
Camera Controller、Selection Manager、Command Controller、Minimap UI、Demo HUD、Spawner、Building Placement、Tree Harvest、Fog Of War Manager空使用するシステム。空の場合は、シーン内から検索するか、新しく作成します。

API:StartGame()、StopGame()、IsRunning、AutoStart、LocalTeam、イベント GameStarted と GameStopped、プロパティ Spawner、CameraController、SelectionManager、CommandController。独自のゲーム内エディターを使用する場合は、protected virtual bool DetectInGameEditor() をオーバーライドします。

独自のゲームを作成する場合は、RTSDemoGame を依存先としてではなく、レシピとして利用してください。セットアップ手順の順序を自分のゲームマネージャーにコピーし、不要なものは省きます。Mirror を使ったマルチプレイヤーでは、RTSDemoGame を追加しないでください。ユニットはネットワークマネージャーがスポーンします。

RTSCameraController

クラシック RTS のカメラです。キーボード、画面端、ドラッグによるパン、ピッチカーブ付きのズーム(ズームインするほど視点が水平に近づきます)、回転、地面への滑らかな追従、マップ境界による制限を備えています。LateUpdate で動作します。RTSCameraController.Main は、最初に有効化されたコントローラーです。

設定デフォルト説明
Target Camera空移動させるカメラ。空の場合は、このゲームオブジェクトの Camera を使用し、それもなければ Camera.main を使用します。
Terrain空地面の高さと境界に使用する地形。空の場合は RTSTerrain.Main を使用します。
Keyboard Pan、Pan Forward/Back/Left/Right Key、Arrow Keysオン、W/S/A/D、オンキーボードによるパン。
Yield Keys To Ordersオンコマンドコントローラーの命令キーを兼ねるパンキー(S = 停止)は、ユニットの選択中はパンを行いません。
Pan Speed / Fast Pan Multiplier35 / 2デフォルトのズームでの 1 秒あたりの移動距離(メートル単位、ズーム距離に応じて変化)と、Shift キーを押している間の倍率。
Edge Scrolling、Edge Size、Edge Speed Multiplierオン、12 px、1マウスが画面端に触れたときにパンします。ゲームウィンドウにフォーカスがない間、ポインターがゲームウィンドウの外にある間(Web ビルドでは、マウスがページまたはその iframe の外に出た場合も含みます)、およびタッチスクリーンの場合は、画面端スクロールが一時停止します。
Middle Mouse Dragオンマウスの中ボタンを押したままにすると、地面をつかんで動かせます。
Min / Max Distance12 / 75最大までズームイン / ズームアウトしたときの、ピボットからの距離(メートル)。
Zoom Step、Start Zoom0.08, 0.6ホイール 1 目盛りあたりのズームの変化量と、初期ズーム(0 = 最も近い、1 = 最も遠い)。
Distance Curve、Pitch Curve-ズーム(0~1)を、距離とピッチ(度単位。デフォルトのズームで約 55°)に対応付けます。
Keyboard Rotate、Rotate Left/Right Key、Rotate Speedオン、Q/E、100キーボードによる回転。速度は 1 秒あたりの度数です。
Alt Right Drag Rotate、Drag Rotate Sensitivityオン、0.3Alt + 右ドラッグで回転します。
Snap Back Rotation、Snap Back Delay、Default Yawオフ、0.25、0回転の入力がないときに、デフォルトの角度に戻ります(クラシック RTS の動作)。ヨー 0 は +Z 方向(北)を向きます。
Follow Ground Height、Height Smooth Timeオン、0.3ピボットを地面の高さに保ちます。崖で視点が跳ねないよう、滑らかに補間されます。
Clamp To Terrain、Bounds Marginオン、4ピボットをマップ内に保ちます(マージンを負の値にすると、マップの端の外側まで見渡せます)。
Min Camera Clearance2カメラ直下の地面からの、カメラの最低高さ。
Pan / Zoom / Rotation Smooth Time0.08 / 0.12 / 0.08スムージングの時間(秒。0 = 即時)。
Ignore Mouse Over UIオンポインターが UI 上にある間は、ホイール、画面端スクロール、ドラッグを無視します。
Use Unscaled Timeオンゲームの一時停止中もカメラが動きます。

API:JumpTo(Vector3)、FocusOn(Vector3)、FocusOn(IReadOnlyList<RTSUnit>, bool instant = false)、SetZoom(float, bool instant = false)、SetYaw(float, bool instant = false)、GetViewFootprint(Vector3[4])(地面上の画面の四隅。ミニマップで使用)、InitializeFromCamera()、InputEnabled、および読み取り専用の Pivot、Zoom、Yaw、Pitch、Distance、IsDragging。

RTSUnit とユニットヘルパー

シンプルな RTS ユニットです。チーム、地形上の移動、角を滑らかに曲がる経路追従、ローカル回避、戦場の霧の視界を備えています。経路プロバイダーに経路をリクエストし、先読み点を使って経路をたどり、到着時には減速し、ほかのユニットとの距離を保ち、歩行不可のセルには決して踏み込みません。また、動けなくなったときや、経路上の通行マップが変化したとき(建物の配置、木の伐採、ゲーム内エディターでの地形編集)には、経路を計画し直します。RTSFogOfWarRevealer(チーム、視界半径)と RTSFogOfWarHideable(チーム)を必要とし、それらを自動で設定します。

設定デフォルト説明
Team0チーム / プレイヤーのインデックス(0 = プレイヤー 1)。ローカルチームのユニットは、選択して命令を出せます。
Use Team Color、Player Colorオン、赤チームが変わるたびに、クラシックなプレイヤーカラー表から色を取得します。選択サークル、ミニマップのドット、チームカラー用のレンダラーに使用されます。
Unit Type「Unit」ダブルクリックで同じ種類のユニットをすべて選択する機能に使う種類名(例:Footman、Worker)。木の伐採デモでは「Worker」を使用します。
Move Speed、Turn Speed、Acceleration5, 540, 25単位はそれぞれメートル毎秒、度毎秒、メートル毎秒²です。
Radius、Height0.5, 1.8衝突半径(経路探索のクリアランス、回避、選択サークル)と高さ(ピッキング、選択ボックス)。
Align To Ground、Ground Align Speedオフ、8地面の傾きに合わせてユニットを傾けます(車両、動物向け)。オフにすると、歩兵は直立したままになります。
Look Ahead、Arrival Distance、Slow Down Distance1.5, 0.15, 1.5経路追従の設定(メートル)。
Stuck Check Interval0.6進行状況をチェックする間隔(秒)。進んでいないユニットは経路を計画し直します。
Path Request Timeout3経路の応答を待つ秒数。これを過ぎるとリクエストを 1 回だけ再送し、2 回目もタイムアウトした場合は移動が失敗します。
Avoidance、Separation Strength、Personal Spaceオン、8、0.1シンプルな分離処理。待機中のユニットは、移動中のユニットに道を譲ります。
Sight Radius14戦場の霧における視界半径(メートル単位)。
Selectableオンプレイヤーがこのユニットを選択できます。
Terrain空ユニットが歩く地形。空の場合は、ユニットの下にある地形(または RTSTerrain.Main)を使用します。
Path Provider空IRTSUnitPathProvider を実装したコンポーネント(省略可)。空の場合はグローバルなデフォルトを使用します(経路プロバイダーを参照)。
Team Color Renderers、Team Color Modeなし、PropertyBlockプレイヤーカラーを表示するレンダラー。MaterialPropertyBlock を使う方法(任意のマテリアルで可)と、色ごとの共有マテリアルを使う方法(バッチングに最適)があります。
Draw Path Gizmosオン現在の経路をシーンビューに描画します。

コマンド:MoveTo(Vector3, bool queue = false)、Stop()、HoldPosition()、Patrol(Vector3 a, Vector3 b)、Warp(Vector3, float? yaw = null)、FaceTowards(Vector3)、Repath()。イベント:OnArrived、OnPathFailed(経路がない、ブロックされたまま断念した、または到達可能な最も近い地点で停止した場合)、OnStateChanged。状態:State(Idle、Moving、Patrolling、Holding)、IsMoving、IsWaitingForPath、IsPathPartial、LastMoveReachedDestination、Destination、CurrentPath、QueuedDestinations、PlannedEndPosition、Velocity、Position、Yaw、IsSelected、IsVisibleToLocalTeam。

using RTSTerrainStudio;
using RTSTerrainStudio.Samples;
using UnityEngine;

// Spawns a knight for player 2 and listens to its movement events.
public class KnightSpawner : MonoBehaviour
{
    [SerializeField] Transform target;

    void Start()
    {
        var terrain = RTSTerrain.Main;
        // Nearest walkable spot where a unit of radius 0.75 m fits.
        Vector3 position = RTSUnitFactory.FindSpawnPosition(terrain, transform.position, 0.75f);
        RTSUnit knight = RTSUnitFactory.CreatePlaceholder(1, position, 180f, RTSUnitFactory.Knight);

        knight.OnArrived += unit => Debug.Log(unit.name + " arrived.");
        knight.OnPathFailed += unit => Debug.Log(unit.name + " can't reach that place.");
        if (target != null) knight.MoveTo(target.position);
    }
}

ヘルパー:

クラス目的
RTSUnitFactoryプロシージャルなプレースホルダーユニット。スタイルは Footman、Worker、Scout、Knight、DefaultMix です。CreatePlaceholder(team, position, yaw, style, parent)、Spawn(prefab, team, position, yaw, parent)(プレハブに RTSUnit がない場合は追加します)、FindSpawnPosition(terrain, position, radius, searchRadius = 16)。
RTSUnitSpawnerマップの開始地点(または Custom Spawn Points、あるいはマップの対角にある角)に、プレイヤーごとのユニットをスポーンします。設定:Unit Prefabs、Units Per Player(6)、Mix Placeholder Types、Terrain、Use Start Locations、Custom Spawn Points、Max Players、Spacing(2 m)、Start Offset(5 m、タウンホールを置くためのスペース)、Container、Spawn On Start。API:SpawnAll()、SpawnForPlayer(...)、SpawnUnit(team, position, yaw, variant)、DespawnAll()、TryGetStartLocation(player, out position, out yaw)。
RTSWanderAIユニットを、ホーム地点の周囲にあるランダムな地点へ歩かせます。設定:Wander Radius(25 m)、Min/Max Wait(2 / 7 s)、Home Is Start Position、Home Position。
RTSUnitRegistryすべてのユニットと空間ハッシュ:All、GetTeam(team)、QueryRadius(...)、QueryScreenRect(...)、PickAtScreen(...)、イベント UnitRegistered、UnitUnregistered、UnitTeamChanged。

経路プロバイダー:IRTSUnitPathProvider

ユニットは特定の経路探索ソリューションに依存しません。ユニットは IRTSUnitPathProvider に経路を問い合わせます。プロバイダーは次の順序で選ばれます:ユニット自身の Path Provider(または PathProviderOverride)、それがなければ RTSUnitPathProviderRegistry.Default、それもなければ組み込みのプロバイダー RTSBuiltinPathProvider(このパッケージの Burst A*。再生モードでは RTSPathfindingService を経由します)。A* Pathfinding Project 連携と NavMesh 連携は、ここに組み込まれます。

プロバイダーには 2 種類あります。

  • 経路プロバイダー(HandlesMovement = false)は、RequestPath を通じてウェイポイントを返します。ユニットはそのウェイポイントを自分でたどります(ステアリング、回避、地面への位置合わせ)。
  • 移動プロバイダー(HandlesMovement = true)は、ユニットを自分で移動させます(たとえば NavMeshAgent)。ユニットはコマンドを OnUnitMoveCommand と OnUnitStop を通じて転送し、プロバイダーは結果を unit.NotifyArrived() または unit.NotifyPathFailed() で報告します。このようなプロバイダーがユニットを担当している間、ユニットが自分のトランスフォームを書き換えることはありません。

実装の規約:onComplete コールバックは、メインスレッド上で必ず 1 回だけ呼び出してください(その場で呼び出しても、後から呼び出してもかまいません)。成功した場合はウェイポイント(始点と終点を含む)と true を渡し、失敗した場合は空のリストまたは null と false を渡します。到達可能な最も近い地点までの経路を true で返してもかまいません。経路が目的地の手前で終わっていることは、ユニット側で検出されます。移動命令を受け取ったプロバイダーは、その移動が終わるまで担当を続けます。移動中にプロバイダーが変更または破棄された場合は、古いプロバイダーに OnUnitStop が送られ、命令は新しいプロバイダーに引き継がれます。Path Request Timeout 以内に応答がないリクエストは 1 回だけ再試行され、それでも応答がなければ失敗します。

RTSUnitPathProviderComponent から派生させると、シーンコンポーネントとして使えるプロバイダーを作成できます。このコンポーネントは、自身をデフォルトとして登録することも(Make Default、デフォルトでオン)、ユニットの Path Provider フィールドにドラッグして割り当てることもできます。

using System;
using System.Collections.Generic;
using RTSTerrainStudio;
using RTSTerrainStudio.Samples;
using UnityEngine;

// A path provider for heavy units that must never cross water (not even shallow fords).
// Turn off "Make Default" and drag it into the Path Provider field of the units that should use it.
public class NoWaterPathProvider : RTSUnitPathProviderComponent
{
    [SerializeField] int maxSearchNodes = 200000;
    readonly List<Vector3> path = new List<Vector3>(64);

    // The unit follows the waypoints itself.
    public override bool HandlesMovement => false;

    public override void RequestPath(RTSUnit unit, Vector3 from, Vector3 to, Action<List<Vector3>, bool> onComplete)
    {
        RTSTerrain terrain = unit != null ? unit.Terrain : RTSTerrain.Main;
        if (terrain == null) { onComplete(null, false); return; }

        var options = PathRequestOptions.ForUnit(unit != null ? unit.Radius : 0.5f);
        options.allowShallowWater = false;
        options.maxSearchNodes = maxSearchNodes;

        // Synchronous search; the list is reused for the next request, the unit copies what it keeps.
        PathStatus status = RTSPathfinder.FindPath(terrain, from, to, options, path);
        onComplete(path, status == PathStatus.Success || status == PathStatus.Partial);
    }
}

コードから RTSUnitPathProviderRegistry.Default = myProvider; を設定すると、独自のプロバイダーを持たないすべてのユニットが切り替わります。Default = null を設定すると、組み込みの経路探索エンジンに戻ります。RTSBuiltinPathProvider は Options(その unitRadius は各ユニットの半径で置き換えられます)と UseAsyncService を公開しています。

RTSSelectionManager

クリック選択、ボックス選択、Shift キーによる選択、ダブルクリック選択、コントロールグループ、ホバー表示と選択リングを提供します。選択できるのはローカルチームのユニットだけです。RTSSelectionManager.Main は、最初に有効化されたマネージャーです。

設定デフォルト説明
Target Camera空ピッキングに使用するカメラ。空の場合は Camera.main を使用します。
Use Fog Of War Local Teamオンアクティブな戦場の霧マネージャーのローカルチームを使用します。
Local Team0ユニットを選択できるチーム(戦場の霧から取得しない場合)。
Drag Threshold、Double Click Time6 px, 0.3 sクリックがボックス選択に切り替わるドラッグ量と、ダブルクリックの判定時間。
Max Selection0選択できるユニットの最大数(0 = 無制限。クラシック RTS ゲームでは 12 がよくある上限です)。
Pick Tolerance6 px小さなユニットもクリックしやすくするための、追加のピッキング半径。
Keep Selection On Empty Clickオン何もない地面をクリックしたり、自分のユニットを含まないボックスを描いたりしても、選択が維持されます(クラシック RTS ゲームと同様)。
Clear Selection KeyEscape選択を解除します。
Control Groups、Group Double Tap Timeオン、0.35 sCtrl+数字キーで登録、Shift+数字キーで追加、数字キーで呼び出し、2 回押すとカメラがグループの中心に移動します。グループは 10 個(RTSSelectionManager.GroupCount = 10)あり、キー 1~9 と 0 に割り当てられています。StoreGroup、RecallGroup、GetGroup のグループインデックス i は、数字キー i に対応します(インデックス 0 はキー 0)。
Show Selection Circles、Show Hover、Use Team Color For Circles、Selection Color、Circle Scale、Circle Width、Box Color-リングと選択ボックスの外観。

API:Selection、Count、Select(unit, additive)、Deselect、ToggleSelection、SetSelection(units, additive)、ClearSelection()、SelectAllOfTypeOnScreen(unit, additive)、GetSelectionCenter()、StoreGroup(i)、AddToGroup(i)、RecallGroup(i)、GetGroup(i)、ClearGroups()、HoveredUnit、イベント SelectionChanged と GroupRecalled。

RTSCommandController と隊形

右クリックと命令キーを、選択中のユニットへの命令に変換します。RTSCommandController.Main は、最初に有効化されたコントローラーです。

設定デフォルト説明
Selection、Target Camera空空の場合は RTSSelectionManager.Main / Camera.main を使用します。
Stop Key、Hold Key、Patrol Key、Cancel KeyS、H、P、Escape命令キー。
Use Formationオングループをグリッド状の隊形で移動させます(オフ = すべてのユニットがクリックした地点へ歩きます)。
Spacing Multiplier、Extra Spacing、Width To Depth1.15, 0.3, 1.5最大のユニット直径に対する倍数で表したスロット間隔、追加の隙間(メートル)、望ましい列数と行数の比率。
Show Marker、Move Marker Color、Patrol Marker Colorオン命令の目標地点に表示される、アニメーション付きのリング。

API:IssueMove(Vector3 target, bool queue = false)、IssueMove(IReadOnlyList<RTSUnit> units, Vector3 target, bool queue = false)、IssuePatrol(Vector3)、IssueStop()、IssueHold()、BeginPatrolTargeting()、CancelTargeting()、IsOrderKeyActive(RTSKey)、イベント MoveIssued、CommandIssued、TargetingChanged。

RTSFormation.ComputeGrid(units, target, slots, spacingMultiplier, extraSpacing, widthToDepth, fromPlannedEnd) は、ユニットごとに 1 つのスロットを計算し、グループ内での各ユニットの位置に最も近いスロットを割り当てます。そのため、経路が交差することはほとんどありません。戻り値は隊形の向きです。fromPlannedEnd(Shift キーでキューに追加した命令で使用)を指定すると、ユニットが現在の命令を終えた後に立つ位置から隊形が続きます。

入力の調整。RTSGameInput は、1 回のクリックが必ず 1 つのシステムだけで処理されるようにします。建物の配置などのモードはポインターを占有し(Acquire、Release、CanUsePointer)、クリックやキーを処理したシステムは Consume / ConsumeKey を呼び出します。サンプルは、RTSGameInput.Order… 定数で定義された固定の順序(建物の配置、木の伐採、コマンド、選択、ユニット、カメラ、UI)で実行されます。独自のモードでも同じ仕組みを使うと、サンプルと協調して動作します。

RTSMinimapUI

実行中のゲーム用の UI Toolkit 製ミニマップです。マップ画像は RTSMinimapBaker でベイクされ、マップが変更されると(頻度を抑えながら)再ベイクされます。その上にローカルチームの戦場の霧が合成されます。ユニットはプレイヤーカラーの点で描画され(敵ユニットは見えている間だけ)、カメラの表示範囲は枠線で描画されます。左クリックまたはドラッグでカメラが移動し、右クリックで選択中のユニットにその場所への移動を命令します。

設定デフォルト説明
Terrain、Camera Controller、Command Controller空空の場合は、それぞれ RTSTerrain.Main、RTSCameraController.Main、RTSCommandController.Main を使用します。
CornerBottomLeft表示する画面の角(クラシック RTS では左下が定番です)。
Size、Margin248、16枠のサイズと画面端からの距離(UI ピクセル単位、基準解像度 1920 × 1080)。
Resolution256ベイクされるマップ画像の解像度(長辺)。
Rebake Delay、Min Rebake Interval0.35 s、1 s複数の編集をまとめて 1 回のベイクにします。
Bake Options-ベイクされる画像の見た目(地面の表示モード、ヒルシェーディング、崖、水、木、装飾オブジェクト、開始地点、プレイヤーカラー)。
Show Fog、Fog Refresh Intervalオン、0.1 s3D ビューと同じように、未探索のエリアと霧に覆われたエリアを暗くします。
Show Units、Unit Dot Size、Show Camera View、Camera View Color、Overlay Rateオン、4、オン、白、20ユニットの点、カメラの枠線、およびそれらの再描画レート。
Left Click Moves Camera、Right Click Moves Unitsオン、オンマウス操作。

API:Visible、Terrain、MapTexture、BakeOptions、Rebake()。

RTSDemoHUD と RTSRuntimeUI

RTSDemoHUD は、選択対象の情報パネル、アクションバー(Stop、Hold、Patrol、Build)、フレームレート、短いフィードバックメッセージ(「Can't reach that place.」「Can't build there.」「Can't build in unexplored land.」)、および F1 キーのヘルプパネルを表示します。設定項目は Help Key(F1)、Show Help On Start、Show Fps、Show Feedback Messages、Message Duration(2.5 s)と、選択・命令・デモの各コンポーネントへの参照です。API:Visible、HelpVisible、SetMode(string)、ShowMessage(string)。このコンポーネントは、有効になっている間、RTSInput.SetBrowserShortcutGuard をオンにします。そのため、Web ビルドでは F1 キーを押すと、ブラウザーのヘルプページではなく HUD のヘルプが開きます。このガードは、F10 までのほかのファンクションキー(再読み込みの F5 キーを含む)と Ctrl+S/O/D/A キーについても、ブラウザー側の動作を抑制します(ブラウザーでのゲーム内エディターを参照)。ほかのプラットフォームでは効果はありません。

RTSRuntimeUI は、サンプル間で共有される UI Toolkit 用の小さなツールキットです。CreateDocument、CreatePanel、CreateLabel、CreateButton(text, tooltip, action)、AttachTooltip、IsScreenPointOverUI、およびサンプル共通のカラーパレットを提供します。これを使うと、同じ見た目の独自パネルを追加できます。

すべてのサンプル UI は、1 つのパネル(RTSRuntimeUI.PanelSettings)を共有しています。このパネルは、1920 × 1080 を基準として画面サイズに合わせてスケーリングされます。ゲーム内エディターと同様に、パネルのスケールが RTSRuntimeUI.MinimumScale(デフォルトは 0.85。Web ビルドでは CSS ピクセル単位で計測)を下回ることはありません。そのため、小さなウィンドウやブラウザーのキャンバスでも、HUD、ミニマップ、選択表示が読みやすく保たれます。大きな画面には影響しません。下限を無効にするには、この値を 0 に設定します。

RTSBuildingPlacementDemo

ゲームコードからランタイムで通行マップを変更する方法を示します。B キーを押すと、建物のフットプリント(占有範囲)がゴースト表示でカーソルに追従します。フットプリントは通行グリッドにスナップし、各セルが緑(建設可能)または赤(ブロックされている)で描画されます。左クリックすると、チームカラーのシンプルな建物が配置されます(Shift キーを押したままにすると、続けて複数配置できます)。フットプリントは通行オーバーライドに書き込まれ、地形がその領域を再構築し、移動中のユニットは建物を避けるように経路を再計算します。ブロックされた場所をクリックすると、赤いマーカーとメッセージが表示されます。右クリックまたは Esc キーでキャンセルします。Delete キーを押すと、カーソル下の建物が削除され、通行判定が元に戻ります。

設定デフォルト説明
Size In Cells2 × 2マップセル単位のフットプリントのサイズ(X × Z)。
Building Height3プロシージャルに生成される建物の高さ(メートル単位)。
Building Prefab空プロシージャルな建物の代わりに使う任意のプレハブ。プレハブのピボットは、フットプリント中心の地面上に配置されます。
Team-1所有チーム。-1 の場合は、選択マネージャー(または戦場の霧)のローカルチームになります。
Sight Radius10建物の視界半径(0 = 視界なし)。建物には、ゴーストモードの隠蔽対象コンポーネントも追加されます。
Footprint FlagsUnwalkable, Unbuildableフットプリントに書き込まれる通行オーバーライド。
Push Units Outオンフットプリント上に立っているユニットが、歩いてその場から退避します。
Require Exploredオンローカルチームが探索済みの地面にのみ建設を許可します(戦場の霧が動作している場合)。
Place Key、Cancel Key、Demolish KeyB、Escape、Delete使用するキー。
Valid Color、Invalid Color緑、赤フットプリントのセルの色。
Terrain、Target Camera空空の場合は、それぞれカーソル下の地形 / Camera.main を使用します。

API:BeginPlacement()、CancelPlacement()、IsPlacing、GetFootprintSize(terrain)、CanPlaceAt(terrain, worldCenter)、GetPlacementError(terrain, worldCenter)(プレイヤー向けのメッセージ、または null)、PlaceBuilding(terrain, worldCenter, owner)、RemoveBuilding(building)、RemoveAllBuildings()、Buildings、イベント BuildingPlaced、BuildingRemoved、PlacementModeChanged、PlacementFailed(Vector3, string)。配置された各建物には RTSDemoBuilding コンポーネント(Team、PathingCells、ContainsPoint、Remove())が付いています。このコンポーネントは以前のオーバーライドを保存しておき、建物が削除または破棄されたときに復元します。

これを実際の建物の仕組みに発展させるには、自分のプレハブを Building Prefab に割り当て、BuildingPlaced をサブスクライブしてコストの支払いと建設の開始を処理し、プレイヤーが建設できない場合は GetPlacementError を呼び出して理由を表示します。

RTSTreeHarvestDemo

ゲームプレイで木を取り除く方法を示します。木にカーソルを合わせ(黄色いリングが表示されます)、T キーを押すと、その木がすぐに伐採されます。または、ワーカー(ユニットタイプ「Worker」)を選択して木を右クリックします。ワーカーは木まで歩いて木のほうを向き、しばらく伐採作業を行い(リングが幹に向かって縮んでいきます)、木を切り倒します。木は RTSVegetationRenderer.RemoveTree で削除されるため、その場所が通行可能になり、戦場の霧からも視界遮蔽物が取り除かれます。元の位置では倒木アニメーション(RTSFallingTreeEffect)が再生されます。

設定デフォルト説明
Harvest KeyTカーソル下の木を伐採します。
Workers Harvest On Right Clickオンワーカーを選択した状態で木を右クリックすると、ワーカーがその木を伐採しに向かいます。
Worker Type「Worker」木を伐採できるユニットタイプ。
Pick Radius1.5木を選択するときの、カーソルのレイから木までの最大距離。
Chop Time、Work Distance1.5 s、1.2 mワーカーが伐採にかける時間と、作業中に幹からどれだけ離れて立つか。
Fall Duration、Fade Duration1.1 s、0.5 s倒れるアニメーションと、最後に縮んで消える演出の長さ。
Highlight Color、Show Chop Progress、Chop Progress Color-カーソルを合わせた木と、伐採中の木の下に表示されるリング。
Target Camera空木の選択(ピッキング)に使用するカメラ。

API:TryGetHoveredTree(out RTSVegetationRenderer, out TreeInstanceData)、HarvestTree(vegetation, treeId, fallAwayFrom)、OrderHarvest(workers, vegetation, tree)、RestoreHarvestedTrees()、CancelAllJobs()、HarvestedCount、イベント TreeHarvested。

using RTSTerrainStudio;
using UnityEngine;

// Cuts the tree nearest to a point, e.g. when a catapult hits a forest.
public static class TreeCutter
{
    public static bool CutNearest(RTSTerrain terrain, Vector3 worldPoint, float maxDistance)
    {
        var vegetation = terrain != null ? terrain.GetComponent<RTSVegetationRenderer>() : null;
        if (vegetation == null) return false;

        if (!vegetation.TryFindNearestTree(worldPoint, maxDistance, out TreeInstanceData tree)) return false;
        // Removes the tree from the map and notifies the terrain:
        // pathing and fog of war blockers update automatically.
        return vegetation.RemoveTree(tree.id);
    }
}

小さなヘルパー

クラス目的
RTSMoveMarker命令の目標地点に表示されるアニメーション付きのリング:RTSMoveMarker.Spawn(position, color, radius, seconds)。
RTSGroundRing地形に沿って描画されるリング(選択サークル、射程範囲など):RTSGroundRing.Create(name, color, radius, width, parent) で作成してから Place(position) を呼び出します。
RTSSampleMaterials、RTSSampleMeshes色ごとにキャッシュされる URP マテリアルと、プレースホルダー用のプリミティブメッシュ。
RTSSampleUtility地形参照の解決、地面のピッキング、XZ 平面上の距離、および PathingRectToCellRect(map, pathingRect)。

デモコンテンツジェネレーター

このパッケージには、サードパーティ製のアートは含まれていません。その代わりに、ジェネレーターがプロジェクト内ですべてのデモコンテンツをプロシージャルに作成します。作成されるのは、タイリング可能な地面・崖・水のテクスチャ、草と花のスプライト、ローポリの木とプロップ、マテリアル、プレハブ、2 つのタイルセット、植生ライブラリと装飾ライブラリ、自動ペイントプロファイル、ブラシ形状、3 つのデモマップ、3 つのデモシーンです。出力は決定論的(シードは固定)なので、何度実行しても同じコンテンツが作成されます。

湖、崖、道、森、荒廃した中央部を備えた Four Kingdoms (4p) デモマップ
Editor Playground シーンの Four Kingdoms (4p) デモマップ。

デモコンテンツの作成

  1. 編集モードであることと、シーンが保存されていることを確認してください(Unity で扱える無題のシーンは 1 つだけです。必要に応じて、ジェネレーターが保存を求めます)。
  2. Tools ▸ RTS Terrain Studio ▸ Create Demo Content を選択します。進行状況バーに処理中の手順が表示されます。完了までには少し時間がかかります。
  3. デモシーンをビルド設定に追加するかどうかを尋ねられたら、デモを含むプレイヤーをビルドする場合以外は Not Now を選択します(後から File ▸ Build Profiles で追加できます)。
  4. ジェネレーターがプロジェクトウィンドウでシーン RTS Terrain Studio - Gameplay Demo を選択します。ダブルクリックして開きます。

ジェネレーターは、Welcome ウィンドウ(Tools ▸ RTS Terrain Studio ▸ Welcome)のデモコンテンツセクションで Create をクリックしたときや、タイルセットがまだないプロジェクトで RTS Terrain を作成したときに表示される提案を受け入れたときにも実行されます。開いているシーンを閉じたり置き換えたりすることはありません。デモシーンは、それらとは別に作成されます。

ジェネレーターは次の手順を実行します(それぞれをコードから呼び出すこともできます)。

手順メソッド作成されるもの
テクスチャCreateTextures()地面、崖、水、木、プロップのテクスチャ(512 px)、草と花のスプライト、ブラシスタンプ(256 px)。
マテリアルCreateMaterials()木と植物のマテリアル(RTS Terrain Studio/Vegetation シェーダー、風あり)、プロップのマテリアル(URP Lit)、サンプルで使用するテンプレートマテリアル、スカイマテリアル、ポストプロセッシングプロファイル。
メッシュとプレハブCreateModels()ローポリの木、茂み、岩、プロップと、それらのプレハブ。
タイルセット、ライブラリ、ブラシCreateDataAssets()Summer と Winter のタイルセット、Demo Vegetation と Demo Decor のライブラリ(パレットアイコン付き)、Summer Auto Paint プロファイル、ブラシ形状。
シェーダー参照と UI 設定UpdateResources()パッケージのすべてのシェーダーと、ランタイム用の UI Toolkit Panel Settings が、プレイヤービルドに確実に含まれるようにします。
デモマップCreateMaps()3 つの .rtsmap ファイル(以下を参照)。
デモシーンCreateScenes(bool interactive)3 つのシーン(デモシーンを参照)。また、戦場の霧のレンダラー機能を URP レンダラーに追加します。

得られるもの

  • Summer タイルセット(ID rts-demo-summer):10 個の地面レイヤー(Grass、Lush Grass、Dirt、Rocky Dirt、Sand、Cobblestone、Dark Forest Floor、Rock、Snow、Blight)を含みます。Rock と Blight は Unbuildable に設定されており、地面レイヤーの通行ルールのよい例になっています。
  • Winter タイルセット(ID rts-demo-winter):同じテクスチャを寒色系に着色したもので、レイヤーの順序も同じです。そのため、どのデモマップも Summer と Winter を切り替えられます。
  • Summer Auto Paint:草地には生い茂った草と土のパッチ、崖の縁に沿って土と岩混じりの土、岸辺には砂、急斜面には岩、高地には雪をペイントします。
  • Demo Vegetation と Demo Decor ライブラリ:草、背の高い草、黄色と紫の花、シダ。松、広葉樹、枯れ木、茂み。岩、巨岩、崩れた柱、木箱、樽、柵、切り株、キノコ。
  • 3 つのマップ:
    • Two Rivers (1v1):128 × 128 セル、点対称。蛇行する 2 本の川が中央の湖で合流し、浅瀬を通ってユニットが川を渡れます。
    • Four Kingdoms (4p):192 × 192 セル、4 回回転対称。段々に重なった台地と、遺跡のある荒廃した中央部があります。
    • Sandbox:96 × 96 セル。ほぼ平坦で、台地と池が 1 つずつあります。ツールを試すためのマップです。
    すべてのマップには、開始地点の石畳の広場、林床をペイントした森、プロップの小さなまとまりがあります。マップが公平になるよう、これらはすべて対称に配置されています。

ファイルの保存先

ジェネレーターは、パッケージフォルダー(移動していなければ Assets/RTSTerrainStudio。パッケージはどのフォルダーでも動作します)の下にある専用のフォルダーにのみ書き込みます。

フォルダー内容
Content/Textures生成されたテクスチャ。Ground、Cliffs、Water、Details、Props、Brushes の各サブフォルダーに分かれています。
Content/Materials、Content/Meshes、Content/Prefabs木とプロップのマテリアル、メッシュ、プレハブ。
Content/TilesetsSummer.asset、Winter.asset、Summer Auto Paint.asset。
Content/LibrariesDemo Vegetation.asset、Demo Decor.asset とそれらのアイコン。
Content/Brushesブラシ形状。
Content/MapsTwo Rivers (1v1).rtsmap、Four Kingdoms (4p).rtsmap、Sandbox.rtsmap。
Content/Settingsレンダリング設定(ポストプロセッシングプロファイル)。
Samples/Scenes3 つのデモシーン。
Resources/RTSTerrainStudioシェーダー参照と UI/RTSRuntimePanelSettings.asset。

再生成

Tools ▸ RTS Terrain Studio ▸ Create Demo Content をもう一度実行し、Regenerate で確定します。既存のデモアセットはその場で上書きされるため、それらを参照しているマップ、シーン、プレハブは引き続き動作します。自分で作成したアセットに手が加えられることはありません。内容が変わっていないマップファイルは書き換えられないため、バージョン管理に余計な差分が出ません。

再生成すると、生成されたアセットそのものに加えた変更(たとえば、調整したデモタイルセットや編集したデモマップ)はすべて上書きされます。デモコンテンツをカスタマイズするには、先にアセットを複製する(Ctrl+D キー)か、マップを別の名前で保存してから、そのコピーを編集してください。

コードから(エディターアセンブリ、名前空間 RTSTerrainStudio.Editor):

メンバー説明
RTSDemoContent.CreateAll(bool openScene)進行状況バーを表示しながら、すべての手順を実行します。キャンセルされた場合や、いずれかの手順が失敗した場合は false を返します。
RTSDemoContent.CreateAllNonInteractive()ダイアログを表示せず、シーンも開きません。自動化やバッチモード向けです。短い概要を返します。
RTSDemoContent.IsCreated主要なデモアセットが存在する場合は true。
RTSDemoContent.ContentFolder、ScenesFolder、SummerTilesetPath、TwoRiversMapPath、GameplayScenePath、…生成されたフォルダーとアセットのパス。
RTSDemoContent.RenderPreview(scenePath, pngPath, width, height)シーンのプレビュー画像をレンダリングします。
// Editor script (place it in an Editor folder): create the demo content from a build pipeline or a CI job.
using RTSTerrainStudio.Editor;
using UnityEngine;

public static class BuildDemoContent
{
    // Run with: Unity -batchmode -executeMethod BuildDemoContent.Run -quit
    public static void Run()
    {
        if (RTSDemoContent.IsCreated) return;               // nothing to do
        Debug.Log(RTSDemoContent.CreateAllNonInteractive()); // logs where the content was written
    }
}

デモシーン

ジェネレーターは Samples/Scenes に 3 つのシーンを作成します。どのシーンにも、Sun、Post Processing ボリューム、RTS Terrain(植生、水、装飾、環境の各コンポーネント付き)、および水の描画に必要な深度テクスチャと不透明テクスチャを有効にする Main Camera が含まれています。

シーンマップ内容
RTS Terrain Studio - Gameplay DemoTwo Rivers (1v1)RTS カメラ、戦場の霧、Auto Start が有効な RTSDemoGame。Play をクリックするとゲームが始まります。
RTS Terrain Studio - In-Game Editor DemoSandboxゲーム内エディター。編集中は戦場の霧と RTS カメラがオフのままで、テストプレイを開始するとユニットがスポーンします。
RTS Terrain Studio - Editor PlaygroundFour Kingdoms (4p)ゲームプレイなし。Unity エディター用のツールを試すための大きなマップです。

Gameplay Demo:試してみること

  1. Play をクリックします。自分のユニット(プレイヤー 1)が開始地点の広場に立っています。マップのほかの部分は真っ黒です。
  2. ボックス選択で数体のユニットを選び、川の向こう側を右クリックします。ユニットが隊形を保ったまま浅瀬を選び、浅水域を渡っていく様子を確認してください。
  3. 崖のそばまで歩かせます。下にいるユニットからは上の台地が見えないこと、また森があるとその向こう側が隠れて見えないことに注目してください。次に、スロープを登って上から見下ろします。
  4. あるエリアから離れると、そのエリアは真っ黒には戻らず、暗く表示されたまま(探索済み)になります。そこをうろつく敵ユニットは、視界から外れると見えなくなります。
  5. B キーを押し、移動中のユニットの経路上に建物を配置します。ユニットは建物を迂回して歩きます。建物にカーソルを合わせて Delete キーを押すと、建物が削除されます。
  6. 木にカーソルを合わせて T キーを押すか、ワーカーを選択して木を右クリックします。森にできた隙間は歩行可能になり、その先を見通せるようになります。
  7. Ctrl+1 キーでグループを登録し、カメラを別の場所へスクロールしてから 1 キーを 2 回押すと、元の場所に戻ります。ミニマップを使って、カメラを移動したりユニットを送り出したりします。
  8. 再生モードを終了し、Fog of War オブジェクトを選択してほかの Line Of Sight モードを試すか、RTS Demo Game の Local Team を変更して別のプレイヤーとしてプレイします。

In-Game Editor Demo:試してみること

  1. Play をクリックします。ゲーム内エディターが Sandbox マップで開き、マップ全体が表示されます。
  2. マップを編集します。台地を盛り上げたり、地面をペイントしたり、森を植えたり、Markers ツールで開始地点を配置したりします。
  3. Play Test(F5)をクリックします。RTSDemoGame が開始地点にユニットをスポーンし、戦場の霧と RTS カメラをオンにします。編集内容はそのまま反映されます。崖は視線を遮り、森は経路をふさぎます。
  4. Esc キー(実行中のモードをキャンセルし、選択を解除した後)または F5 キーを押すか、Back to Editor (Esc or F5) をクリックします。ユニットが消え、マップはテストプレイ前の状態に戻ります。

Editor Playground:試してみること

  1. シーンと RTS Terrain Studio ウィンドウ(Tools ▸ RTS Terrain Studio ▸ Editor Window)を開きます。
  2. RTS Terrain を選択し、Four Kingdoms マップで崖、スロープ、ペイント、植生、装飾オブジェクトの各ツールを試します。
  3. シーンビューで P キーを押して通行オーバーレイを順に切り替え、崖、傾斜、水、木、Blight の地面が歩行可能性と建設可能性にどう影響するかを確認します。
  4. プレイしてみたい場合は、GameObject ▸ RTS Terrain Studio ▸ Demo Game を追加して Play をクリックします。4 人のプレイヤー全員の開始地点にユニットがスポーンします。

各連携には専用のデモシーンがあり、必要なときに作成できます:Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Mirror ▸ Create Demo Scene、Tools ▸ RTS Terrain Studio ▸ Integrations ▸ A* Pathfinding ▸ Create Demo Scene、Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Unity NavMesh ▸ Create Demo Scene。これらには、デモコンテンツと、対応するサードパーティ製パッケージが必要です。

デモの見た目がおかしい、またはテクスチャが表示されませんか?Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、提案された修正(URP アセット、レンダラー機能、カメラのテクスチャ)を適用してください。デモシーンが見つからない場合は、ジェネレーターをもう一度実行してください。足りないシーンが再作成され、自分のシーンには手が加えられません。

マップ生成とマップツール

RTS Terrain Studio ウィンドウ(Tools ▸ RTS Terrain Studio ▸ Editor Window)の Map タブは、Settings の 1 つ手前にあるタブです。ここには、マップ全体を対象とする Map、New Map、Resize、Generator、Heightmap、Minimap、Validation、Environment の各カードがあります。この章では、プロシージャルなマップジェネレーター、ハイトマップのインポートとエクスポート、ミニマップのベイク、マップの検証について説明します。これらはすべて自分のコードからも使用できます。掲載しているコード例は、パッケージに手を加えなくてもそのままコンパイルできます。

すべてのカードは、ウィンドウのヘッダーで選択されている地形を対象に動作します。ボタンに「Select this terrain in the window header first」と表示された場合は、ヘッダーの地形フィールドで地形を選択してください(シーン内で地形を選択しても、ここで選択されます)。

マップジェネレーター

マップジェネレーター(RTSMapGenerator)は、1 つのシードから、クラシック RTS の完全なレイアウトを作成します。きれいな崖で区切られた台地、台地をつなぐスロープ、湖または島、オプションのマップ境界、地面ペイント、草と森、装飾オブジェクト、プレイヤーの開始地点が含まれます。ジェネレーターは既存のマップに対して動作し、そのサイズを維持します。同じシード、同じ設定、同じマップサイズからは常に同じマップが生成されるため、ファイルの代わりにシードを共有できます。

生成を実行すると、高さ、崖レベル、スロープ、崖タイプ、水、およびすべての通行オーバーライドが必ず置き換えられます(後から書き戻されるのはマップ境界だけなので、Border を None に設定していても、ペイントした通行オーバーライドは消去されます)。地面ペイント、ディテールレイヤー、木、装飾オブジェクト、開始地点は、対応するオプションがオンの場合にのみ置き換えられます。具体的には、地面ペイントは Paint Mode が KeepExisting 以外の場合、ディテールレイヤー・木・装飾オブジェクトは、それぞれ Vegetation または Decor が Enabled で、地形に植生ライブラリまたは装飾ライブラリがあり、かつ Clear Existing がオンの場合、開始地点は Start Locations が Enabled の場合です(Update Max Players がオンの場合は Max Players も更新されます)。それ以外の場合は既存の内容が保持されます。Clear Existing がオフの場合、新しい木と装飾オブジェクトは既存のものに追加されます。リージョン、マーカー、環境設定が変更されることはありません。生成全体が 1 アンドゥステップになるため、安心して試行錯誤できます。

はじめての生成マップ(Unity エディター)

  1. プロジェクトにタイルセットとライブラリがまだない場合は、Tools ▸ RTS Terrain Studio ▸ Create Demo Content を一度実行します。ジェネレーターは地形のタイルセットでペイントし、地形のライブラリから植物と装飾オブジェクトを配置します。ライブラリがない場合、植生と装飾オブジェクトの配置はスキップされます。
  2. RTS Terrain を選択し(または GameObject ▸ RTS Terrain Studio ▸ RTS Terrain で作成し)、Tools ▸ RTS Terrain Studio ▸ Editor Window を開きます。
  3. Map タブをクリックし、Generator カードを展開します。
  4. Style(たとえば Highlands)を選択し、Apply Style Preset をクリックします。そのスタイルらしい見た目になるノイズ、崖、スロープ、水の値が読み込まれます。
  5. Symmetry を選択します。公平な 1v1 マップには Rotate2、正方形のマップで 4 人対戦にするには Rotate4 を選びます。
  6. Start Locations を開き、Count をプレイヤー数に設定します。
  7. Randomize Seed をクリックしてから Generate をクリックし、ダイアログで確定します。
  8. ボタンの下に表示されるレポートを確認します。台地のレベル数、スロープ、開始地点、木、装飾オブジェクトの数と、処理にかかった時間が表示されます。警告(例:4 つの開始地点のうち 3 つしか見つからなかった)は、こことコンソールに表示されます。
  9. 結果が気に入らない場合は、Ctrl+Z キーを押し、Randomize Seed をクリックしてもう一度生成します。気に入った結果が得られたら、マップを保存します(ウィンドウのヘッダーまたは RTS Terrain のインスペクターにある Save)。
生成したマップはあくまで出発点です。その後は通常のツールで仕上げます。スロープを動かしたり、湖を追加で掘ったり、道をペイントしたり、厳選したドゥーダッド(装飾オブジェクト)をいくつか配置したりします。ブラシパネルの対称ペイントを使えば、手を加えた後もマップの公平性を保てます。

このカードにあるそのほかのボタンは次のとおりです。

ボタン機能
Randomize Seed新しいシードをランダムに選びます。そのほかの設定はそのままです。
Apply Style Preset選択したスタイルのノイズ、崖、スロープ、水の値を読み込みます。シード、対称、マップ境界、ペイント、植生、装飾オブジェクト、開始地点の設定は保持されます。
Resetすべてのジェネレーター設定を、選択したスタイルのデフォルトにリセットします。
Generate確認を求めてから、Generate Map という名前の 1 アンドゥステップとしてマップを生成します。

ウィンドウのジェネレーター設定はエディターの環境設定に保存されるため、Unity を再起動しても保持されます。

新しいマップの生成(New Map Wizard)

Tools ▸ RTS Terrain Studio ▸ New Map Wizard... は新しいマップファイルを作成します。ウィザードの Terrain ステップで Flat の代わりに Generate を選択し、Preset(6 つのスタイルと、プロジェクト内のすべての RTSMapGeneratorPreset アセット。アセットは Preset: name として表示されます)、シード、対称を選びます。Preview をクリックすると、保存する前に生成結果をミニマップで確認できます。ウィザードは自身のプレイヤー数の設定からプレイヤー数を取得し、開始地点に反映します。

ゲーム内エディターでの生成

リリースしたゲームでも、プレイヤーがマップを生成できます。ゲーム内エディターのツールバーで Map ▸ Generate... を開きます。ダイアログには、簡略化されたオプションが用意されています。

オプション対応する設定
Styleスタイルプリセットを適用します(その下にある形状のオプションはリセットされます)。
Seed / Randomseed
Symmetrysymmetry
Terrain:Cliffs、Plateaus、Ramps、Ramp Count、Water、Lake Coverage、Island、Bordercliffs.enabled、cliffs.plateauCount、ramps.enabled、ramps.count、water.enabled、water.lakeCoverage、water.islandMode、border.mode
Content:Textures、Vegetation、Forests、Decor、Decor Count、Start Locations、Playerspaint.mode、vegetation.enabled、vegetation.forestCoverage、decor.enabled、decor.count、startLocations.enabled、startLocations.count

Enter キー(または Generate)で生成し、Esc キーでキャンセルします。結果は 1 アンドゥステップになり(Ctrl+Z キーで元に戻せます)、ダイアログの設定はゲームを終了するまで保持されます。

スタイルプリセット

スタイル(TerrainStylePreset)は、ベースとなる高さフィールドの形状を変え、MapGeneratorSettings.ApplyPreset を通じて、それに合ったデフォルト値を読み込みます。

スタイル見た目レベル / 台地スロープ湖の割合
Plains低い台地がいくつかある、ほぼ平坦な土地。見通しのよい、クラシックな対戦マップです。2–3 / 230.05
Hills(デフォルト)なだらかに起伏する丘(微細高さが大きい)と、いくつかの台地とスロープ。万能な出発点です。2–3 / 240.06
Highlands何段にも重なった台地と崖、その間をつなぐスロープ。2–6 / 580.04
Islands水に囲まれた陸地(島モード、より深い水、より広い岸辺)。2–4 / 350.38
Canyonリッジノイズから作られる、高い台地の間を曲がりくねる峡谷。2–5 / 360.03
Arenaリング状の高地に囲まれた、低い中央のアリーナ。2–4 / 360.03

独自プリセットの保存

気に入った設定が見つかったら、アセットとして保存できます。Assets ▸ Create ▸ RTS Terrain Studio ▸ Map Generator Preset を実行すると、Description と、設定一式を収めた Settings を持つ RTSMapGeneratorPreset が作成されます(初期値は Hills の値です)。New Map Wizard にはプロジェクト内のすべてのプリセットアセットが表示され、自分のコードから preset.settings をジェネレーターに渡すこともできます(コードからの生成を参照)。プリセットは通常のアセットなので、ゲームと一緒にリリースできます。たとえば、スカーミッシュメニューの「2 人用キャニオン」や「4 人用アイランド」といった選択肢として使えます。

対称:対戦向けの公平なマップ

対称を使うと、ジェネレーターはマップの 1 つのセクターだけを作成し、それをコピーします。そのため、どのプレイヤーにも同じ地形、同じスロープ、同じ量の木と装飾オブジェクト、そして同等の開始地点が与えられます。傾いた装飾オブジェクトは、ミラーリングされたコピーでは鏡像の向きに傾きます。

対称コピー数主な用途正方形のマップが必要
None1キャンペーンマップや自由な形式のマップ。いいえ
MirrorX2左 ↔ 右にミラーリング(2 人のプレイヤーが左右に向かい合う)。いいえ
MirrorZ2上 ↔ 下にミラーリング。いいえ
MirrorXZ4両方の軸でミラーリング(4 つの角)。いいえ
Rotate2(デフォルト)2180° の点対称。クラシックな 1v1 のレイアウトです。いいえ
Rotate4490° の回転対称。クラシックな 4 人対戦のレイアウトです。はい(正方形でない場合は Rotate2 にフォールバック)
MirrorDiagonal2対角線でミラーリング(x ↔ z)。はい(正方形でない場合は MirrorX にフォールバック)
開始地点の数は、コピー数の倍数にしてください(Rotate2 なら 2、4、6 …)。そうでない場合、ジェネレーターはレイアウトが公平にならないことを警告します。実際に使用された対称は MapGenerationReport.symmetryUsed で確認できます。

ジェネレーター設定リファレンス(MapGeneratorSettings)

ジェネレーターカードには、MapGeneratorSettings のすべてのフィールドが折りたたみごとにグループ化されて表示されます。デフォルト列は new MapGeneratorSettings() の値で、Hills スタイルの値と同じです。Noise、Cliffs、Ramps、Water の各グループの値は、選択したスタイルによって変わります。有効範囲外の値は、生成前に Validate() によって範囲内に収められます。

General

設定デフォルト説明
Seed (seed)12345ランダムシードです。同じシードと設定からは、常に同じマップが生成されます。
Style (style)Hills地形の形状です。Apply Style Preset をクリックする(または ApplyPreset を呼び出す)と、そのスタイルに合ったデフォルト値が読み込まれます。
Symmetry (symmetry)Rotate21 つのセクターを生成してそれをコピーし、公平なマップにします(対称を参照)。

Noise(noise、GeneratorNoiseSettings)

設定デフォルト説明
Scale85 m地形の大きな特徴のサイズ(メートル単位)です。値を大きくすると、台地が大きくなります。
Octaves4ノイズレイヤーの数(1–8)です。オクターブを増やすと、輪郭に細かな凹凸が加わります。
Persistence0.5オクターブごとの振幅の倍率です(0.5 = オクターブごとに強さが半分になります)。
Lacunarity2オクターブごとの周波数の倍率です。
Ridged Amount0リッジノイズ(鋭い尾根と峡谷)へのブレンド量です。Canyon スタイルでは 1 を使用します。
Warp0.4ドメインワープの強さです。輪郭を曲げて、より自然で流れるような形状にします。
Contrast1値を大きくすると、より多くの領域が最も低い台地と最も高い台地に割り当てられます。
Smoothing2フィールドを崖レベルに変換する前に行うぼかしパスの回数です(ノイズによるごく小さな凹凸を取り除きます)。
Fine Height Amplitude1.6 m微細高さのノイズの振幅です(台地の上の丘)。
Fine Height Scale22 m微細高さのノイズの特徴サイズです。
Fine Height Octaves3微細高さのノイズのオクターブ数です。
Cliff Edge Fade4 セル崖の縁に向かって微細高さがフェードアウトしていく距離です。これにより、崖の上面がきれいに保たれます。

Cliffs(cliffs、GeneratorCliffSettings)

設定デフォルト説明
Enabledオン崖レベルを生成します。オフの場合、マップ全体が Min Level になり、微細高さだけが変化します。
Min Level2最も低い台地の崖レベル(0–15)です。レベル 2 にしておくと、その下に湖を掘る余地が残ります。
Max Level3最も高い台地の崖レベルです。
Plateau Count2Min Level と Max Level の間にある、異なる台地レベルの数です。
Hysteresis0.04各レベルのしきい値の周囲に設ける帯の幅です(高さの範囲に対する割合)。この帯の中にある頂点は隣接する頂点のレベルを引き継ぐため、崖のラインが滑らかに保たれます。
Min Feature Width8 セルすべての台地、テラス、谷、湖の最小幅です。これより狭い部分(2 つの崖に挟まれた細いテラス、尾根、溝、ごく小さな台地)は隣接するレベルに統合されます。また、すべての台地は、その下の崖との間にこの幅の約 4 分の 3 のマージンを確保します。湖については、この幅より 4 分の 1 狭くてもかまいません。プリセットでは 6–10 を使用しています。
Cleanup Passes2孤立した頂点やスパイクを取り除くパスの回数です。
Cliff Type0すべての頂点に書き込む崖タイプ(タイルセットの崖レイヤー 0–3)です。

Ramps(ramps、GeneratorRampSettings)

設定デフォルト説明
Enabledオン隣り合う台地の間にスロープを配置します。
Count4生成するセクター内のスロープの数です(対称コピーによってさらに増えます)。
Width3 セル崖に沿った方向のスロープの幅です。
Length2 セル崖を横切る方向の、崖レベル 1 段あたりのスロープの長さです。2 にすると緩やかな傾斜になります。
Ensure Connectivityオン必要な場所にスロープを追加し、すべての台地からほかのすべての台地へ到達できるようにします。
Min Spacing12 セル2 つのスロープ間の最小距離です。重なり合うスロープはスキップされます。
Min Plateau Size40頂点数がこれより少ない台地は、接続性を確保する処理で無視されます。

Water(water、GeneratorWaterSettings)

設定デフォルト説明
Enabledオン湖または海と、その水面を生成します。
Sea Level Offset−0.25最も低い台地を基準とした、崖レベル単位の水面の高さです(−0.9~2)。負の値では最も低い台地が水に浸からず、正の値では水没します。
Lake Coverage0.06マップのうち湖または海にする割合です(最も低い領域から順に、0–0.7)。
Max Depth2.5 m水面から測った、湖の最深点の深さです。
Shore Width4 セル水と陸の境界から最大水深までの、傾斜した岸辺の幅です。
Island Modeオフマップの端に向かって地形を押し下げ、マップを 1 つ以上の島にします。島マップには常に 15% 以上の海が含まれます(割合が引き上げられた場合は、レポートで警告されます)。
Border Falloff0.25Island Mode 使用時:端に向かう減衰の幅です(マップサイズに対する割合)。

Border(border、GeneratorBorderSettings)

設定デフォルト説明
ModeNoneNone:特別な境界を作りません。Unwalkable:端に沿って、歩行不可かつ建設不可の通行オーバーライドをペイントします。HighCliffs:端に沿って高い崖をリング状に隆起させます(こちらも歩行不可)。
Width2 セル境界の幅です。
Cliff Level0HighCliffs 境界の崖レベルです。0 = 自動(最も高い台地より 2 レベル上)。

Paint(paint、GeneratorPaintSettings)

設定デフォルト説明
ModeAutoPaintKeepExisting:地面ペイントに手を加えません。FillBaseLayer:マップ全体を Base Layer で塗りつぶします。AutoPaint:Auto Paint プロファイルを使い、高さ、傾斜、崖レベル、水に応じてペイントします。
ProfileNoneRTSAutoPaintProfile(Assets ▸ Create ▸ RTS Terrain Studio ▸ Auto Paint Profile)を指定します。空の場合は、タイルセットのレイヤー名に合わせた組み込みのデフォルトルールが使用されます。
Base Layer0FillBaseLayer で使用する地面レイヤーです。

Vegetation(vegetation、GeneratorVegetationSettings)

設定デフォルト説明
Enabledオンディテールレイヤーと森を生成します(地形に植生ライブラリが設定されている必要があります)。
Use Whole Library When Emptyオン以下のリストが空の場合は、植生ライブラリのすべてのプロトタイプを使用します。
Clear Existingオン生成の前に、既存のディテールレイヤーと木を削除します。
Details空生成するディテール(草)レイヤーです。次の表を参照してください。
Tree Prototype Ids空森に使用する木のプロトタイプの ID です。
Forest Coverage0.22適した地面のうち、森で覆う割合です。
Forest Noise Scale55 m森のノイズの特徴サイズです。
Tree Spacing2.6 m木と木の間隔です。
Tree Density0.85森の中の空いた場所に木が置かれる確率です(値を小さくすると森がまばらになります)。
Max Tree Slope26°これより急な傾斜には木を配置しません。
Start Location Clearance14 m開始地点の周囲のこの距離内には、木とディテールを配置しません。

Details の各エントリ(GeneratorDetailEntry):

設定デフォルト説明
Prototype Id""植生ライブラリ内のディテールプロトタイプの ID です。
Density0.6覆われる領域内での密度です(1 = プロトタイプの最大密度)。
Coverage0.55適した地面のうち、ディテールで覆う割合です。
Noise Scale28 m覆う範囲を決めるノイズの特徴サイズです。
Max Slope32°これより急な傾斜にはディテールを配置しません。

Decor(decor、GeneratorDecorSettings)

設定デフォルト説明
Enabledオン装飾オブジェクトを散布します(地形に装飾ライブラリが設定されている必要があります)。
Use Whole Library When Emptyオン以下のリストが空の場合は、装飾ライブラリのすべてのプロトタイプを使用します。
Clear Existingオン生成の前に、既存の装飾オブジェクトを削除します。
Prototype Ids空散布する装飾プロトタイプの ID です。
Count60生成するセクター内の装飾オブジェクトの数です(対称コピーによってさらに増えます)。
Min Spacing3 m装飾オブジェクト間の最小距離です(プロトタイプ自体の間隔のほうが大きい場合は、そちらが使用されます)。
Max Slope30°これより急な傾斜には装飾オブジェクトを配置しません。
Start Location Clearance12 m開始地点の周囲のこの距離内には、装飾オブジェクトを配置しません。

生成される装飾オブジェクトには、ランダムなヨーとヨーの刻み幅、スケール範囲、ティント、バリアント、法線への位置合わせ、ランダムな傾きの最大値といった、各装飾プロトタイプの設定が適用されます。

Start Locations(startLocations、GeneratorStartLocationSettings)

設定デフォルト説明
Enabledオン開始地点を配置します。オフの場合は、既存の開始地点がそのまま残ります。
Count2開始地点の数(1–16)です。
Flat Radius5 セル開始地点の周囲で平らでなければならない範囲の半径です(単一のレベルで、崖、スロープ、水がないこと)。
Edge Margin8 セルマップの端までの最小距離です。
Update Max PlayersオンCount の値を、マップの Max Players(MapInfo.maxPlayers)に書き込みます。
マップに平らな場所が足りない場合、ジェネレーターは収まる数だけ開始地点を配置し、「Only N of M」と報告します。Flat Radius または Edge Margin を小さくするか、台地の数を減らすか、マップを大きくしてください。

コードからの生成

次の 3 つのエントリポイントで、あらゆる状況に対応できます:

メソッド用途
RTSMapGenerator.Generate(RTSTerrain, MapGeneratorSettings, RTSToolContext ctx = null)ゲームコード。地形のマップに生成し、地形を更新して、警告をログに出力します。RTSEditSession.RunOperation 呼び出しの ctx を渡した場合にのみ、元に戻せます。
RTSMapGenerator.Generate(RTSEditSession, MapGeneratorSettings)エディターツールとゲーム内エディターの拡張機能。Generate Map という名前の 1 アンドゥステップになります。
RTSMapGenerator.GenerateInto(MapData, settings, tileset, vegetation, decor)表示されていない MapData への生成(プレビュー、一括生成、テスト)。通知もアンドゥもありません。MapGenerationReport を返します。
using RTSTerrainStudio;
using UnityEngine;

// Generating maps from game code, e.g. for a "Random map" button in a skirmish menu.
public class GeneratorExample : MonoBehaviour
{
    [SerializeField] RTSTerrain terrain;
    [SerializeField] RTSMapGeneratorPreset preset;   // optional: settings saved as an asset

    // Generates a random 4-player layout into the current map (the map size is kept). No undo.
    public void GenerateRandomMap(int seed)
    {
        // Start from a saved preset or from a style preset, then change what you need.
        MapGeneratorSettings settings = preset != null
            ? preset.settings.Clone()                                  // never modify the asset itself
            : MapGeneratorSettings.CreatePreset(TerrainStylePreset.Highlands);
        settings.seed = seed;                       // same seed + settings + map size = same map
        settings.symmetry = SymmetryMode.Rotate4;   // needs a square map, otherwise Rotate2 is used
        settings.startLocations.count = 4;          // a multiple of the symmetry copies is fairest
        settings.water.lakeCoverage = 0.1f;
        settings.Validate();                        // clamps every value into its valid range

        RTSMapGenerator.Generate(terrain, settings);   // generates, refreshes the terrain, logs warnings
    }

    // Generates a map in memory without showing it (previews, batch generation, tests).
    public static MapData GenerateOffscreen(RTSTerrain template, int seed)
    {
        MapCreationSettings creation = MapCreationSettings.Default;   // 128 x 128 cells of 2 m
        creation.width = 192;
        creation.height = 192;
        MapData map = MapData.Create(creation);

        MapGeneratorSettings settings = MapGeneratorSettings.CreatePreset(TerrainStylePreset.Islands);
        settings.seed = seed;
        // Pass the tileset and libraries so painting, vegetation and decor can be generated.
        MapGenerationReport report = RTSMapGenerator.GenerateInto(map, settings,
            template.Tileset, template.VegetationLibrary, template.DecorLibrary);
        foreach (string warning in report.warnings) Debug.LogWarning(warning);
        Debug.Log(report);   // plateaus, ramps, start locations, trees, decor and time

        // You own the map: Dispose() it, or give it to a terrain with terrain.SetMap(map).
        return map;
    }
}

MapGenerationReport には、symmetryUsed、plateauLevels、ramps、startLocations、detailLayers、trees、decor、milliseconds、および warnings リストが含まれます。ToString() は 1 行の要約を返します。RTSMapGenerator.GeneratedLayers には、ジェネレーターが書き込むすべての MapLayers フラグが含まれています。GenerateInto を自分でラップする場合は、これを RunOperation に渡します。

ハイトマップのインポートとエクスポート

Map タブの Heightmap カードでは、地形の高さを 16 ビット PNG または 16 ビット RAW としてインポート・エクスポートできます。World Machine、Gaea、Photoshop、または実世界の標高データから地形を取り込んだり、マップをそうしたツールに渡してから再び取り込んだりするのに使用します。

ハイトマップと地形の対応

  • ハイトマップは頂点グリッドに対応します。256 × 256 セルのマップには 257 × 257 個の頂点があるため、最適なハイトマップのサイズは 257 × 257 ピクセルです。これ以外のサイズの画像は、バイリニア補間でリサンプリングされます。
  • ピクセル値は height = offset + value / 65535 × scale として高さに変換されます。8 ビット画像も使用できますが、高さの段階が少なくなります。
  • デフォルトの Height Scale は、16 個の崖レベルすべてをカバーする 16 × Cliff Level Height です(デフォルトのレベルの高さ 2 m では 32 m)。この値は HeightmapIO.DefaultHeightScale(map) で取得できます。
  • RAW ファイルの行 0 と PNG の最下行が、Z = 0(頂点の最初の行)に対応します。マップが鏡写しのように反転して表示される場合は、Flip Vertically をオンにします。
  • インポートは毎回、Import Heightmap という名前の 1 アンドゥステップになります。

インポート

  1. Map タブを開き、Heightmap カードを展開します。
  2. インポートの Mode を選択します(以下の表を参照)。一般的な外部ハイトマップには Cliff Levels And Fine Heights を使用します。既存の台地の上に丘を加えるには Fine Heights Only を使用します。
  3. 画像が表す高さ(メートル単位)がわかっている場合を除き、Height Scale は 0(自動)のままにしておきます。
  4. Import PNG…(PNG、JPG、EXR。8 ビットまたは 16 ビットのグレースケールで、赤チャンネルを使用)または Import RAW…(.raw / .r16、16 ビット)をクリックします。
  5. 結果を確認します。Ctrl+Z キーで元に戻し、別のモードやスケールを試すことができます。
インポートモード書き込むデータ説明
Fine Heights Only高さ微細高さのみを書き込みます(微細高さ = 値 × スケール + オフセット)。崖レベルは維持され、その高さに微細高さが上乗せされるため、画像によって既存の台地の上に丘が加わります。
Cliff Levels And Fine Heights高さ、崖レベル画像を地面の絶対的な高さとして扱います。これを崖レベル(最も近いレベルに丸め、孤立した頂点を整理)と微細高さに分割するため、地面の高さが画像と完全に一致します。テラス状のハイトマップに最適です。ただし、レベルをまたぐ滑らかな斜面は小さな崖になります。
Absolute Heights Keep Levels高さ画像を地面の絶対的な高さとして扱いますが、既存の崖レベルは維持されます(微細高さ = 高さ − レベル × レベルの高さ)。Ground Height でエクスポートして外部ツールで編集した画像を、崖を変えずに再インポートするときに使用します。
インポート設定デフォルト説明
Mode上記を参照画像をレベルと高さに変換する方法です。
Height Scale0(自動)16 ビットの全範囲が表すメートル数です。0 = 16 × レベルの高さ。
Height Offset0インポートしたすべての高さに加算するメートル数です。
Flip Verticallyオフ画像を Z 方向に反転します(マップが鏡写しのように反転して表示される場合に使用します)。
RAW Width / RAW Height0RAW ファイルの 1 行あたりのサンプル数と行数です。0 = ファイルサイズから推測します(正方形のファイルのみ)。
RAW Little EndianオンRAW ファイルのバイト順です。Windows とほとんどのツールは、リトルエンディアンで書き込みます。
最もきれいな結果を得るには、外部ツールからハイトマップを 1 辺がちょうどセル数 + 1 ピクセルになるようにエクスポートし、崖レベルの高さに合わせてテラス状にしておきます。残った凹凸は Smooth ツールでならし、ユニットが登れるようにしたい場所にスロープを追加します。

エクスポート

エクスポート設定 / ボタンデフォルト説明
SourceGround HeightGround Height:崖レベルと微細高さを合わせた高さ(絶対値)。Fine Heights:レベルの上に乗る微細高さのみ。
Height Scale0(自動)65535 が表すメートル数です。
Height Offset00 として書き込まれる高さです。
RAW Little Endianオンエクスポートする RAW ファイルのバイト順です。
Export PNG…16 ビットグレースケールの PNG です。
Export RAW…16 ビットの RAW ファイルです。
Export Splat PNG…地面ペイントの画像(優勢レイヤーの色)です。ドキュメントや外部ツールで使用できます。

往復編集:Ground Height でエクスポートし、外部で画像を編集してから、同じスケールとオフセットのまま Absolute Heights Keep Levels で再インポートします。

コードからのハイトマップ操作

HeightmapIO には次のメソッドがあります。地形向けの ImportPng、ImportRaw16、ImportHeights(独自のノイズなどから得た、正規化済みの float[] または NativeArray<float> の値)、素の MapData 向けの ApplyHeights、ExportPng16、ExportRaw16、ExportSplatPng(モードは DominantLayerColor と SplatWeights)、さらに TryGuessRawSize と、アンドゥ記録用の GetImportLayers(mode) です。すべてのエクスポーターには、byte[] 版とファイルパス版のオーバーロードがあります。

using RTSTerrainStudio;
using UnityEngine;

// Heightmap import and export from code.
public static class HeightmapExample
{
    public static bool Import(RTSTerrain terrain, string pngPath)
    {
        // Imports need a height scale > 0. DefaultHeightScale = 16 cliff levels (all levels + headroom).
        float scale = HeightmapIO.DefaultHeightScale(terrain.Map);
        var options = new HeightmapImportOptions { heightOffset = 0f, flipZ = false };

        // The image is resampled to the vertex grid (cells + 1 per side) and the terrain is refreshed.
        // Pass an RTSToolContext (inside RTSEditSession.RunOperation) as the 5th argument to make it undoable.
        return HeightmapIO.ImportPng(terrain, pngPath, HeightmapImportMode.CliffLevelsAndFineHeights, scale,
            null, options);
    }

    public static bool Export(RTSTerrain terrain, string pngPath)
    {
        // 16-bit grayscale PNG of the absolute ground height. Scale 0 = DefaultHeightScale.
        return HeightmapIO.ExportPng16(terrain.Map, pngPath, 0f, 0f, HeightmapExportSource.GroundHeight);
    }
}
ウィンドウとは異なり、インポートメソッドは渡された heightScale をそのまま使用します。必ず 0 より大きい値(たとえば HeightmapIO.DefaultHeightScale(map))を渡してください。エクスポートでは、0 は自動を意味します。

ミニマップのベイク

RTSMinimapBaker は、マップを真上から見た画像をレンダリングします。地面の色、丘の陰影、崖、水深に応じた色の水、木、装飾オブジェクト、開始地点、リージョンが描かれます。このパッケージでは、次のすべての場所で同じベイカーが使用されています:

  • Map タブの Minimap カード(プレビューと PNG エクスポート)
  • プロジェクトウィンドウとインスペクターでの .rtsmap アセットのプレビュー、および New Map Wizard のプレビュー
  • ゲーム内エディターのマップブラウザー(各マップの隣に小さなプレビュー画像が保存されます。これは RTSRuntimeEditor の Write Preview Images で制御します)
  • RTSMinimapUI サンプル(戦場の霧、ユニットのドット、カメラの表示範囲の枠を表示するゲーム内ミニマップ。クリックでカメラを移動し、右クリックでユニットを移動します)。マップが変更されると、一定の間隔を空けて自動的に再ベイクします。

エディターでのベイク

  1. Map タブを開き、Minimap カードを展開します。
  2. Size を選択します(長辺の長さが 128、256、512、1024 px のいずれか。正方形でないマップは縦横比が維持されます)。
  3. 必要に応じて Bake Options を調整します(以下の表を参照)。
  4. Bake Preview をクリックするとカードに結果が表示され、Save PNG… をクリックするとファイルに書き出されます。書き出した画像は、たとえばロード画面やマップ選択メニューに使用できます。

ベイクオプション(MinimapBakeOptions)

設定デフォルト説明
Ground ModeBlendedLayersBlendedLayers は、ペイントされたすべてのレイヤーの色を混ぜ合わせます。DominantLayer は、ピクセルごとに最も強いレイヤーを使用します(よりくっきりとした、クラシック RTS スタイルの表示になります)。レイヤーの色はタイルセットから取得されます(レイヤーのミニマップ用の色、またはレイヤーテクスチャの平均色)。
Level Brightness0.05レベル 2 を基準とした、崖レベル 1 段あたりの明るさの変化です(高い台地ほど明るく見えます)。
Hill Shading / Strengthオン / 1光と反対側を向いた斜面を暗くします。
Light Direction(−0.55, 0.75, 0.45)マップローカル空間での、光源に向かう方向です(x = 東、y = 上、z = 北)。
Use Map Sun DirectionオフLight Direction の代わりに、マップの環境で設定された太陽の方向を使用します。
Draw Cliffs / Cliff Color / Cliff Opacityオン / 黒 / 0.8崖セルに色を付けます。黒 = タイルセットの崖の色を使用します。
Draw Waterオン水深に応じて水に色を付けます(タイルセットの水プロファイル)。
Draw Trees / Tree Color / Tree Dot Sizeオン / 濃い緑 / 0.75 px木をドットで描画します(1 未満の値では、1 ピクセルだけの点になります)。
Draw Decor / Decor Color / Decor Dot Sizeオフ / 茶色 / 0.75 px装飾オブジェクトをドットで描画します。
Draw Start Locations / Start Location Sizeオン / 4 pxプレイヤーカラーのマーカーです。
Scale Markersオンテクスチャサイズに合わせてマーカーのサイズをスケーリングします(基準サイズは 256)。
Player Colorsクラシック RTS の 16 色のプレイヤーカラープレイヤーのインデックスごとの色です。足りないエントリには MinimapBakeOptions.DefaultPlayerColors が使用されます。
Draw Regionsオフリージョンの輪郭を、それぞれのリージョンの色で描画します。

コードからのミニマップ作成

RTSMinimapBaker.Bake(terrain, size, options) は、読み取り可能な sRGB の新しい Texture2D を返します(不要になったら自分で破棄してください)。Update(existing, terrain, size, options) は既存のテクスチャに再ベイクし、古いテクスチャを再利用できない場合にのみ新しいテクスチャを作成します。BakePng(terrain, size) は PNG のバイト列を直接返します。また、MapData を受け取るオーバーロードを使うと、表示されていないマップもベイクできます。

using RTSTerrainStudio;
using UnityEngine;
using UnityEngine.UIElements;

// Keeps a minimap image up to date in a UI Toolkit Image named "minimap".
public class MinimapExample : MonoBehaviour
{
    [SerializeField] RTSTerrain terrain;
    [SerializeField] UIDocument document;
    [SerializeField] int size = 256;      // longer side in pixels
    Texture2D minimap;

    void OnEnable()
    {
        if (terrain == null) return;
        terrain.MapLoaded += OnMapUpdated;
        terrain.ChangesProcessed += OnChangesProcessed;
        if (terrain.Map != null) Refresh();
    }

    void OnDisable()
    {
        if (terrain == null) return;
        terrain.MapLoaded -= OnMapUpdated;
        terrain.ChangesProcessed -= OnChangesProcessed;
    }

    void OnMapUpdated(RTSTerrain t) => Refresh();
    // Called after every processed edit. While someone paints, throttle this (the RTSMinimapUI sample
    // waits a moment after the last change and limits the bakes per second).
    void OnChangesProcessed(RTSTerrain t, System.Collections.Generic.IReadOnlyList<DirtyRegion> regions) => Refresh();

    void Refresh()
    {
        var options = new MinimapBakeOptions { drawDecor = true, hillShadingStrength = 1.2f };
        // Update reuses the texture when it can. When it returns a new texture, destroy the old one.
        Texture2D result = RTSMinimapBaker.Update(minimap, terrain, size, options);
        if (minimap != null && result != minimap) Destroy(minimap);
        minimap = result;
        Image target = document != null ? document.rootVisualElement?.Q<Image>("minimap") : null;
        if (target != null) target.image = minimap;
    }

    void OnDestroy()
    {
        if (minimap != null) Destroy(minimap);
    }
}

マップの検証

マップ検証機能(MapValidator)は、見落としやすい問題がマップにないかをチェックします。たとえば、テクスチャの欠落、到達できない開始地点、壊れたオブジェクト参照、パフォーマンス上の上限超過などです。マップをリリースする前や、プレイヤーに公開する前に実行してください。

  1. Map タブを開き、Validation カードを展開します。
  2. Validate Map をクリックします。サマリーにエラー、警告、情報の件数が表示されます。問題がない場合は No problems found. と表示されます。
  3. 問題をクリックすると、その位置がシーンビューに表示されます(開始地点など、特定の場所に関係する問題の場合)。
  4. 問題を修正し、もう一度検証します。
通行判定に基づくチェック(開始地点が歩行可能か、到達可能性、マップの端が歩行可能か)には、地形の通行マップが必要です。通行マップがない場合、検証機能はこれらのチェックをスキップしたことを示す情報を追加します。
これはマップの検証機能です。プロジェクトの検証機能(Tools ▸ RTS Terrain Studio ▸ Validate Project)は、URP、シェーダー、入力処理、パッケージをチェックします。詳しくはプロジェクトのセットアップを参照してください。

チェック内容

領域チェック重大度
タイルセットタイルセットが割り当てられていない(地形はニュートラルなフォールバックカラーで描画されます)警告
タイルセットに地面レイヤーがない警告
マップにタイルセット ID が保存されていない(タイルセットを割り当てた状態で保存すると自動的に設定されます)情報
マップが、割り当てられているものとは別のタイルセット用に作成されている警告
ジオメトリ無効な高さ(NaN または無限大)を持つ頂点があるエラー
崖セルが、タイルセットで定義されていない崖タイプを使用している警告
マップに崖があるのに、タイルセットに崖レイヤーがない警告
地面ペイントウェイトの合計が 255 にならないスプラットテクセルがある(ペイントデータの破損)警告
タイルセットにない地面レイヤーがペイントされている警告
ペイントされた地面レイヤーにアルベドテクスチャがない情報
開始地点マップに開始地点がないエラー
開始地点の数が Max Players より少ない警告
1 人のプレイヤーに複数の開始地点がある警告
開始地点がマップの外にあるエラー
開始地点が崖の縁にある警告
開始地点が深水域に覆われている / 浅水域にあるエラー / 情報
2 つの開始地点が非常に近い(距離が Min Start Distance Cells 未満)警告
開始地点が歩行可能でないエラー
到達可能性あるプレイヤーがほかのプレイヤーの位置まで歩いて行けない(開始地点がある崖レベルを起点に、通行マップ上で塗りつぶしを行って判定します)エラー
マップの端の大部分が歩行可能になっている(歩行不可の境界を設けることを検討してください)情報
木、装飾オブジェクト、ディテール木、装飾オブジェクト、ディテールレイヤーがあるのに、植生ライブラリ / 装飾ライブラリが割り当てられていない警告
オブジェクトが、マップのパレットにないプロトタイプインデックスを参照しているエラー
オブジェクトまたはディテールレイヤーが、ライブラリにないプロトタイプを使用している警告
プロトタイプにプレハブがない(そのオブジェクトは表示されません)警告
マップ外のオブジェクト、重複したインスタンス ID、または既存の ID より小さいインスタンス ID カウンター警告
リージョン面積のないリージョンがある。複数のリージョンが同じ名前を使っている(名前でリージョンを検索するゲームコードが、誤ったリージョンを取得する可能性があります)情報
パフォーマンス木、装飾オブジェクト、ディテールレイヤーが多すぎる警告
GameObject スポーンモードの木または装飾オブジェクトが多すぎる警告
大きなマップ(メモリ使用量とビルド時間)情報

しきい値(MapValidationSettings)

設定デフォルト説明
maxTrees20000木の数がこの値を超えると、パフォーマンスの警告が表示されます。
maxGameObjectTrees1500GameObject スポーンモードの木の数がこの値を超えると、警告が表示されます。
maxDecor6000装飾オブジェクトの数がこの値を超えると、警告が表示されます。
maxGameObjectDecor2000GameObject スポーンモードの装飾オブジェクトの数がこの値を超えると、警告が表示されます。
maxDetailLayers8ディテールレイヤーの数がこの値を超えると、警告が表示されます。
largeMapSize512マップがこのサイズ(1 辺あたりのセル数)より大きいと、メモリとビルド時間に関する情報が表示されます。
minStartDistanceCells12開始地点同士の距離がこの値より近いと、警告が表示されます。
walkableEdgeFraction0.1外周のセルのうち歩行可能なセルの割合がこの値を超えると、マップの端が歩行可能であることを示す情報が表示されます。
checkReachabilitytrueすべての開始地点が互いに到達できるかをチェックします。

コードからの検証

MapValidator.Validate(terrain) は List<MapIssue> を返します。各 MapIssue は、Severity(Info、Warning、Error)、Message、オプションの WorldPosition、影響を受ける Layers を持ちます。オーバーロードを使うと、MapValidationSettings を渡したり、地形の代わりに MapData 単体をタイルセット、ライブラリ、通行マップと一緒に渡したりすることもできます。

using RTSTerrainStudio;
using UnityEngine;

// Checks a map before it is published (for example in a "Publish map" button of your game).
public static class ValidationExample
{
    public static bool CanPublish(RTSTerrain terrain)
    {
        // Optional thresholds; null uses the defaults.
        var settings = new MapValidationSettings { maxTrees = 10000, checkReachability = true };
        var issues = MapValidator.Validate(terrain, settings);

        foreach (MapIssue issue in issues)
        {
            // WorldPosition is set for issues that belong to a place on the map (e.g. a start location).
            string where = issue.WorldPosition.HasValue ? " at " + issue.WorldPosition.Value : "";
            switch (issue.Severity)
            {
                case MapIssueSeverity.Error: Debug.LogError(issue.Message + where, terrain); break;
                case MapIssueSeverity.Warning: Debug.LogWarning(issue.Message + where, terrain); break;
                default: Debug.Log(issue.Message + where, terrain); break;
            }
        }
        return MapValidator.Count(issues, MapIssueSeverity.Error) == 0;
    }
}

連携

RTS Terrain Studio は単体で動作します。Burst による組み込みの経路探索エンジン、独自の戦場の霧(Fog of War)、サンプルゲームプレイを備えています。3 つのオプションの連携を使うと、人気のパッケージと組み合わせることができます。これらの連携はこのパッケージに含まれており(Integrations フォルダー)、必要なパッケージがインストールされると自動的に有効になります。

連携必要なもの得られるものデモシーンのメニュー
MirrorMirror 96 以降任意のマップでのオンラインプレイ:クライアントへのマップストリーミング、サーバー権威型のユニット、チームごとの戦場の霧、マップハック対策のインタレストマネジメント(関心領域管理)。Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Mirror ▸ Create Demo Scene
A* Pathfinding ProjectA* Pathfinding Project Pro 5.4.4 以降、6.0 未満通行マップから生成され、差分更新されるグリッドグラフ。AIPath と RVO によるローカル回避で移動するユニット。Tools ▸ RTS Terrain Studio ▸ Integrations ▸ A* Pathfinding ▸ Create Demo Scene
Unity NavMeshAI Navigation(com.unity.ai.navigation)2.0 以降地形のチャンクと通行マップから構築され、マップの編集中も常に最新の状態に保たれる NavMesh。NavMesh の経路に沿って、または NavMeshAgent で移動するユニット。Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Unity NavMesh ▸ Create Demo Scene

Welcome ウィンドウ(Tools ▸ RTS Terrain Studio ▸ Welcome)には、各連携の状態が表示されます。状態は Active(コンパイル済みで使用可能)、Installed(依存パッケージは見つかったが連携がコンパイルされていない状態。通常はバージョンの不一致が原因)、Not installed のいずれかです。

すべてのデモシーンにはデモコンテンツが必要です。連携のデモを作成する前に、Tools ▸ RTS Terrain Studio ▸ Create Demo Content を 1 回実行してください。

オプションアセンブリの仕組み

各連携は、それぞれ専用のアセンブリ定義に含まれています。Unity は、アセンブリの定義制約(Define Constraints)がすべて満たされた場合にのみ、そのアセンブリをコンパイルします。シンボルは次の 2 か所から提供されます。

  • バージョン定義(.asmdef の versionDefines):対応するバージョンのパッケージがインストールされていると、アセンブリが自らシンボルを定義します。A* と NavMesh はこの方式を使うため、パッケージのインストールや削除に自動的に反応します。
  • プレイヤー設定のスクリプティング定義シンボル:Mirror は通常、パッケージではなく Asset Store のフォルダーとしてインポートされます。そのため、Mirror 連携のアセンブリは、Mirror 自身がプレイヤー設定に追加する MIRROR シンボルを使用します。
アセンブリバージョン定義定義制約
RTSTerrainStudio.Integrations.Mirror と .Mirror.EditorなしMIRROR
RTSTerrainStudio.Integrations.Mirror.DefineCleanup.Editorなしなし(常にコンパイルされ、古い Mirror シンボルを削除します。詳しくは連携の削除を参照してください)
RTSTerrainStudio.Integrations.AStar と .AStar.Editorcom.arongranberg.astar が [5.4.4,6.0.0) の範囲 → RTS_ASTARRTS_ASTAR、!RTS_ASTAR_DISABLED
RTSTerrainStudio.Integrations.NavMesh と .NavMesh.Editorcom.unity.ai.navigation 2.0.0 以降 → RTS_AI_NAVIGATIONRTS_AI_NAVIGATION

その結果、依存パッケージがない場合、連携は単にスキップされるだけで何も壊れず、ユニットは組み込みの経路探索エンジンを使用します。A* と NavMesh では、シンボルを手動で定義・削除する必要はありません。

経路プロバイダー。サンプルユニット(RTSUnit)は、IRTSUnitPathProvider に経路を問い合わせます。デフォルトでは組み込みのプロバイダーが使用されます。A* と NavMesh のプロバイダーは、Make Default がオンのときに自身を RTSUnitPathProviderRegistry.Default として登録します。または、個々のユニットの Path Provider フィールドにプロバイダーを割り当てることもできます。独自の経路探索ソリューションも、IRTSUnitPathProvider を実装すれば同じ方法で組み込めます。
連携アセンブリは RTSTerrainStudio.Samples を参照しています(RTSUnit と、選択・コマンド用のコントローラーを使用するため)。連携を使用している間は、Samples/Scripts フォルダーを残しておいてください。

Mirror マルチプレイヤー

Mirror(バージョン 96 以降)を使うと、RTS Terrain Studio のマップをオンラインでプレイできます。

  • マップストリーミング:サーバーは、参加するすべてのクライアントにマップを送信します。そのため、プレイヤーは自分が持っていないマップ(ゲーム内エディターで作成したマップを含む)のゲームにも参加できます。ダウンロードしたマップはキャッシュされるため、同じマップでの次のゲームはすぐに始まります。
  • サーバー権威型のユニット:経路探索と移動はサーバー上でのみ実行されます。クライアントは命令を送信し、サーバーはそのユニットがプレイヤーのものかどうかを確認します。
  • すべてのクライアントでチームごとの戦場の霧が機能します。
  • マップハック対策のインタレストマネジメント:戦場の霧で隠れているユニットが、敵のクライアントに送信されることはありません。

クイックスタート:デモシーン

  1. Mirror をインポートします(Asset Store または GitHub から)。Mirror が MIRROR スクリプティング定義シンボルを追加し、連携がコンパイルされます。
  2. Tools ▸ RTS Terrain Studio ▸ Create Demo Content を実行します(プロジェクトごとに 1 回)。
  3. Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Mirror ▸ Create Demo Scene を実行します。Integrations/Mirror/Demo に次のものが作成されます:シーン RTS Mirror Demo.unity、プレハブ Prefabs/RTS Network Unit.prefab(RTSUnit、RTSNetworkUnit、NetworkIdentity、NetworkTransformReliable を持つプレースホルダーのカプセル)と Prefabs/RTS Network Player.prefab、ユニットのマテリアル、さらにプロジェクトに開始地点が 2 つ以上あるマップがない場合は、生成された 4 人用マップ RTS Mirror Demo Map.rtsmap。
  4. 確認を求められたら、戦場の霧のレンダラー機能を追加し、シーンをビルドのシーンリストの先頭に置くことをビルダーに許可します。
  5. Play をクリックしてから、Mirror HUD の Host (Server + Client) をクリックします。チーム 1(赤)としてプレイし、ユニットは自分の開始地点にスポーンします。
  6. 2 つ目のインスタンスを起動し(以下を参照)、Client をクリックします。アドレスは localhost のままにします。クライアントはマップをダウンロードし(進行状況バーが表示されます)、チーム 2(青)として参加し、自分のユニットが割り当てられます。敵のユニットは、自分のユニットから見えているときにだけ表示されます。

ホストとクライアントのテスト

方法手順
プレイヤービルド(最も確実)File ▸ Build Profiles を開きます。RTS Mirror Demo が最初のシーンになっていることを確認してから、ビルドします。ビルドを 2 つ起動するか、ビルドとエディターを起動します。一方で Host を、もう一方で Client をクリックします。
ParrelSync(無料、GitHub)ParrelSync ▸ Clones Manager を開き、クローンを作成して新しいエディターで開きます。両方で Play をクリックし、オリジナルでは Host を、クローンでは Client をクリックします。クローンは Assets フォルダーを共有するため、デモビルダーはオリジナルでのみ実行してください。
Multiplayer Play Mode(com.unity.multiplayer.playmode)Window ▸ Multiplayer ▸ Multiplayer Play Mode を開きます。仮想プレイヤーを有効にして Play をクリックします。メインエディターで Host を、仮想プレイヤーで Client をクリックします。
ネットワーク経由クライアントのアドレスフィールドにホストの IP アドレスを入力します。KCP は UDP ポート 7777 を使用します。ホストのファイアウォールでこのポートを開放してください。
専用サーバーDedicated Server ビルドプロファイルでビルドします。ネットワークマネージャーは Headless Start Mode = Auto Start Server を使用するため、サーバーは自動的に起動します。クライアントは Client で接続します。

各マシンは、ダウンロードしたマップを Application.persistentDataPath/MapCache に保存します。新規ダウンロードをテストするには、Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Mirror ▸ Clear Map Cache または … ▸ Open Map Cache Folder を使用します。同じプロジェクトのエディターと Windows プレイヤーは、このフォルダーを共有します。

デモでマップのストリーミングを確認したい場合は?ホストとクライアントはどちらもデモマップで起動し、クライアントはサーバーとまったく同じマップをすでに表示していることを検出するため、何もダウンロードされません。クライアントの RTSNetworkMapSync で Reuse Loaded Map と Use Cache をオフにするか、ホストを開始する前にホスト側でマップを変更してください(ゲーム内エディターを使うなど)。

自分のシーンへのマルチプレイヤーの追加

コンポーネント追加先目的
RTSNetworkManagerネットワークマネージャーのオブジェクトチームを割り当て、クライアントがマップを取得するまで待ってから、プレイヤーとその初期ユニットを開始地点にスポーンします。プレイヤーが退出すると、そのユニットをデスポーンします。
RTSNetworkMapSync同じオブジェクト(自動的に追加)チェックサム、キャッシュ、進行状況イベントを使って、マップをチャンク単位でクライアントにストリーミングします。
RTSFogOfWarInterestManagement同じオブジェクトMirror のインタレストマネジメント:各クライアントは、自分のチームから見えるユニットだけを受信します。
RTSNetworkFogOfWar同じオブジェクトRTSFogOfWarManager を設定します。サーバーはすべてのチームを計算し、クライアントは自分のチームだけを計算します。
RTSNetworkStatusUI同じオブジェクト(任意)ダウンロードの進行状況バーと「You are Player N」の表示(IMGUI)。
RTSNetworkPlayerプレイヤーのプレハブチームの SyncVar。ローカルプレイヤー(霧のチーム、選択、カメラ)をセットアップし、命令をコマンドとして送信します。
RTSNetworkUnitユニットのプレハブチームの SyncVar。移動はサーバー上でのみ実行され、クライアントはユニットを表示するだけです。
RTSNetworkCommandRelayクライアントで自動的に追加サンプルの RTSCommandController の命令をサーバーに送信します。
  1. 通常の RTS Terrain Studio のシーンを構築します:開始地点のあるマップを持つ RTSTerrain、RTSFogOfWarManager、RTSCameraController、RTSSelectionManager、RTSCommandController。RTSDemoGame は追加しないでください。代わりにネットワークマネージャーがユニットをスポーンします。
  2. 空のオブジェクトを作成し、KcpTransport、RTSNetworkManager(RTSNetworkMapSync が自動的に追加されます)、RTSFogOfWarInterestManagement、RTSNetworkFogOfWar、必要に応じて NetworkManagerHUD を追加します。マネージャーの Transport フィールドにトランスポートを割り当てます。
  3. NetworkIdentity と RTSNetworkPlayer を持つプレイヤーのプレハブを作成し、Player Prefab に割り当てます。
  4. NetworkIdentity、RTSUnit、RTSNetworkUnit、NetworkTransformReliable(Sync Direction は Server To Client、Sync Scale はオフ)を持つユニットのプレハブを作成します。これを Unit Prefab に割り当てると、スポーン可能なプレハブに自動的に追加されます。スポーンするほかのネットワーク対応プレハブは、すべて Registered Spawnable Prefabs に登録されている必要があります。
  5. Units Per Player(デフォルト 5)と、必要に応じて Max Players Override(0 = マップの開始地点と同じ数のプレイヤー)を設定します。
  6. ユニットのプレハブにある AI などのロジックコンポーネント(たとえば RTSWanderAI や独自の AI スクリプト)は、クライアントで実行されないように、RTSNetworkUnit の Server Only Behaviours に追加します。
設定デフォルト説明
RTSNetworkManager
Terrain空ゲームをプレイする地形。空の場合は RTSTerrain.Main。
Unit Prefab空各プレイヤーの初期ユニット(NetworkIdentity、RTSUnit、RTSNetworkUnit が必要)。
Units Per Player5初期ユニットの数(0–64)。
Max Players Override0プレイヤー数をマップのスロット数より少なくします(0 = すべての開始地点)。
Unit Spacing2 m初期ユニット同士の間隔。
Spawn Offset4 m開始地点から初期ユニットの最前列までの距離(マップ中心方向)。
Despawn Units On Disconnectオン退出したプレイヤーのユニットを破棄します。
Reserve Team Of Kept Unitsオンユニットを残す場合:そのうち 1 体でも生存している間は、チームのスロットを予約したままにします。
Reject Disconnect Delay0.5 s拒否メッセージを送ってから切断するまでの秒数。メッセージが確実に届くようにします。
RTSNetworkUnit
Server Only Behaviours空クライアント側で無効にする Behaviour(ユニットのロジックはサーバーが実行するため)。
RTSNetworkPlayer
Jump Camera To Startオンゲーム開始時に RTS カメラを開始地点に移動します。
Create Command Relayオンホストを兼ねないクライアントのみ:シーンに RTSNetworkCommandRelay がない場合に追加し、サンプルのコマンドコントローラーが命令をサーバーに送信できるようにします。
Max Units Per Command、Max Commands Per Second256, 20サーバーによる命令の検証(サーバー権威型のユニットを参照)。
Formation (Server)-サーバーが使用する隊形の設定。RTSCommandController と同じ値にしておいてください。

ゲームへの参加とマップストリーミング

client connects
  server: team = first free player slot of the map (rejected when the map is full)
  server -> client: map header (name, size, CRC-32)
  client: same map already loaded, or in the cache?  yes -> "no data needed"
                                                     no  -> download the chunks
  client: check the CRC-32 -> terrain.SetMap(RTSMapSerializer.Deserialize(bytes)) -> "map ready"
  client: NetworkClient.Ready() + AddPlayer()
  server: spawns the RTSNetworkPlayer and the starting units (owned by the connection)

サーバーは、クライアントが正しいマップのチェックサムを報告するまで、そのクライアントからの Ready と AddPlayer を無視します。そのため、クライアントが持っていないマップのワールドオブジェクトを受信することはありません。ホストモードでは、ローカルクライアントはサーバーの地形を使用し、すぐに参加します。

  • 送信データは .rtsmap ファイルそのものです(deflate で圧縮済み)。植生のある 128 × 128 のマップは、通常数百 KB です。
  • サーバーのマップがアセットまたはファイルから読み込まれた後に変更されていない場合は、元のファイルのバイト列がそのまま送信されます。このバイト列はどのセッションでも同一なので、クライアントのキャッシュはセッションをまたいで機能します。編集されたマップや生成されたマップは、必要なときにシリアライズされます。
  • サーバーは、未確認のチャンクのうち最も古いものに対する確認応答だけを受け付けます。そのため、改造されたクライアントがマップ全体を一度に取得することはできません。
  • シーンを切り替えると(ServerChangeScene または Mirror のオンラインシーン)、新しいシーンのマップがすべてのプレイヤーに再送信されます。クライアントは、そのマップを取得してから新しいシーンに参加します。
RTSNetworkMapSync の設定デフォルト説明
Terrain空マップを送信する地形(サーバー)、およびマップを置き換える地形(クライアント)。空の場合は RTSTerrain.Main。
Chunk Size16 KBチャンクメッセージ 1 つあたりのバイト数。トランスポートの最大メッセージサイズに収まるよう、自動的に小さくなります。
Chunks In Flight8クライアントへの送信中で、まだ確認されていないチャンクの数。ping が高い環境では、値を大きくするとダウンロードが速くなります。値を小さくすると、ゲーム用により多くの帯域幅を残せます。
Transfer Timeout30 s進行がないままこの秒数が経過すると、転送は失敗します。
Load Timeout120 sサーバー:ダウンロード後、クライアントがシーンを読み込んでマップを適用するまでに許容される時間。待機中、クライアントはキープアライブメッセージを送信します。
Disconnect On Failureオンサーバー:転送に失敗したクライアントを切断します(マップがないとプレイできないため)。
Max Map Size MB64クライアント:クライアントが受け入れる最大のマップサイズ(不具合のあるサーバーや悪意のあるサーバーからクライアントを保護します)。
Reuse Loaded Mapオンクライアントがサーバーとまったく同じマップをすでに表示している場合は、ダウンロードをスキップします。
Use Cache / Max Cached Mapsオン / 20ダウンロードしたマップを Application.persistentDataPath/MapCache に保存します(最後にプレイしてから最も時間が経ったものから削除。0 = 無制限)。
Log Transfersオン各転送の開始と終了をログに出力します。

ロード画面用のイベント:ClientHeaderReceived、ClientProgressChanged(float)、ClientMapReady、ClientTransferFailed(string)。サーバー側では ServerClientReady と ServerClientFailed を使用できます。SendMapToAll() は、変更されたマップ(たとえばロビーでのマップ投票の後)を、接続中のすべてのクライアントに再送信します。

サーバー権威型のユニット

  • サーバー上では、RTSUnit がシングルプレイヤーと同じように経路を計画して移動します。位置と回転は NetworkTransformReliable が同期します。
  • クライアント上では、RTSNetworkUnit がそのユニットの経路プロバイダー(HandlesMovement = true)になるため、ユニットが自分で移動することはありません。ネットワークトランスフォームが書き込んだ内容を反映するだけです。
  • ローカルの命令はサーバーに送られます。右クリック(隊形を組んで移動、Shift キーで命令を予約)、Stop、Hold、Patrol は RTSNetworkCommandRelay 経由で、クライアントのユニットに対するそれ以外の RTSUnit.MoveTo 呼び出しは RTSNetworkUnit 経由で送信されます。独自のコードからは、RTSNetworkPlayer.RequestMove、RequestMoveTo、RequestPatrol、RequestStop、RequestHold のいずれかを呼び出すか、RTSNetworkPlayer の派生クラスに [Command] メソッドを追加します。
  • 隊形は、RTSNetworkPlayer の Formation (Server) 設定を使ってサーバー上で計算されます。コントローラーの隊形設定を変更した場合は、シーンの RTSCommandController と同じ値に設定してください。
  • サーバーはすべての命令を検証します。ユニットが存在し、プレイヤーのチームに属していること、目標が地形上の有限な位置であること、1 つの命令あたりのユニット数が Max Units Per Command(デフォルト 256)以下であること、1 秒あたりの命令数が Max Commands Per Second(デフォルト 20)以下であることを確認します。
  • 退出したプレイヤー:Despawn Units On Disconnect がオン(デフォルト)の場合、そのユニットは破棄され、スロットが空きます。オフの場合はユニットが残り、Reserve Team Of Kept Units(デフォルトでオン)によって、そのうち 1 体でも生存している間はスロットが予約されたままになります。拒否されたクライアント(たとえばゲームが満員の場合)は、切断される前にその理由を受け取ります(RTSNetworkManager.ClientRejected、LastRejectReason)。

戦場の霧とマップハック対策

サーバーはすべてのチームの戦場の霧を計算します。RTSFogOfWarInterestManagement は Rebuild Interval 秒ごとに、RTSNetworkUnit を持つすべてのユニットをチェックします。

  • 自軍と同盟チームのユニット(RTSFogOfWarManager.SetAlliance で視界を共有するチーム)は常に送信されます。
  • 敵ユニットは、戦場の霧によってクライアントのチームから見えている間だけ送信されます。Linger Time により、ユニットは視界から外れた後も少しの間保持されるため、視界の端にいるユニットがちらつくことはありません。
  • RTSNetworkUnit を持たないオブジェクト(プレイヤー、マネージャー)は常に送信されます。

隠れている敵はクライアント上でデスポーンされ、その移動情報も一切送信されないため、改造されたクライアントでもそれを可視化することはできません。クライアント上では、RTSFogOfWarHideable も、ネットワークの範囲内にいるがまだ見えていない敵を隠します。

設定デフォルト説明
Fog Of War空サーバー側の戦場の霧。空の場合は RTSFogOfWarManager.Active。
Rebuild Interval0.2 s可視性チェックの間隔(秒)。0.1–0.3 s に保ってください。ユニットが見えるようになるたびに、クライアントは完全なスポーンメッセージを受信するためです。
Linger Time0.5 sユニットがチームの視界から外れた後も、監視対象として残る秒数。
Share With Alliesオン視界を共有するチームは、互いのユニットを常に見ることができます。
Show All Without Fogオンサーバーで戦場の霧が動作していない間は、すべての接続がすべてのユニットを見ることができます。オフの場合は、自分のユニットのみです。
Spectators See Allオフチームのない接続(観戦者)は、すべてのユニットを見ることができます。

RTSNetworkFogOfWar には、Compute All Teams On Server(オン。インタレストマネジメントに必要)、サーバーの更新間隔(0 = マネージャーの値をそのまま使用。値を小さくするとユニットが早く表示されます)、Disable Reveal All Online(オン)があります。1 回の可視性チェックはユニットごとに霧グリッドを 1 回参照するだけなので、数千のユニットでも 1 ミリ秒を大きく下回るコストで済みます。サーバーの霧のコストはチーム数に応じて増えますが、2~8 チームであれば軽量です。

Mirror でのスクリプティング

using RTSTerrainStudio.Integrations.Mirror;
using RTSTerrainStudio.Samples;
using UnityEngine;

// Needs Mirror (96 or newer). Sends orders of the local player to the server and shows the map download.
public class MirrorExample : MonoBehaviour
{
    [SerializeField] RTSNetworkMapSync mapSync;   // on the network manager object

    void OnEnable()
    {
        if (mapSync == null) return;
        mapSync.ClientProgressChanged += OnProgress;          // 0-1 while the map downloads
        mapSync.ClientMapReady += OnMapReady;                 // the map is applied to the terrain
        mapSync.ClientTransferFailed += OnTransferFailed;
    }

    void OnDisable()
    {
        if (mapSync == null) return;
        mapSync.ClientProgressChanged -= OnProgress;
        mapSync.ClientMapReady -= OnMapReady;
        mapSync.ClientTransferFailed -= OnTransferFailed;
    }

    void OnProgress(float progress) => Debug.Log($"Downloading map: {progress:P0}");
    void OnMapReady() => Debug.Log("Map ready");
    void OnTransferFailed(string reason) => Debug.LogWarning("Map download failed: " + reason);

    // For your own UI (e.g. a "Move here" button): the server validates the order (ownership,
    // target, unit count, rate limit) before the units move.
    public void SendSelectedTo(Vector3 target, bool queue)
    {
        RTSNetworkPlayer player = RTSNetworkPlayer.Local;
        RTSSelectionManager selection = RTSSelectionManager.Main;
        if (player == null || selection == null) return;
        player.RequestMove(selection.Selection, target, queue);
    }

    // Host only: after changing the map (map vote, in-game editor), send it to every client again.
    public void ResendMap()
    {
        if (mapSync != null) mapSync.SendMapToAll();
    }
}
メンバー説明
RTSNetworkManager(RTSNetworkManager.Instance)
ServerPlayerJoined(conn, team)、ServerConnectionRejected(conn)サーバー:接続にチームが割り当てられたとき(マップ転送の前)と、すべてのスロットが埋まっているために接続が拒否されたときに、それぞれ発生します。
ServerPlayerSpawned(conn, player)、ServerUnitSpawned(conn, unit)サーバー:プレイヤーオブジェクトがスポーンされたとき(そのプレイヤーのマップの準備が完了した後)と、ユニットがスポーンされたとき(SpawnUnit によるものも含む)に、それぞれ発生します。プレイヤーに初期オブジェクトを与えるときに使用します。
ServerPlayerLeft(conn, team)サーバー:接続が離脱したときに、その接続のオブジェクトが破棄される前に発生します。
ClientJoinedGame、ClientDisconnected、ClientRejected(reason)クライアント:マップを読み込んだ後にゲームに参加したとき、接続が終了したとき、サーバーがこのクライアントを拒否したときに、それぞれ発生します。
RTSNetworkUnit SpawnUnit(owner, team, position, yaw, prefab = null)サーバー:ユニットをスポーンします(最も近い歩行可能な地点に移動されます)。owner が null の場合は所有者なしになります(たとえば中立モンスターや建物)。プレハブは、RTSNetworkUnit を持つ登録済みのスポーン可能なプレハブである必要があります。
GetUnits(conn)、TryGetTeam(conn, out team)、IsTeamReserved(team)サーバー:接続のユニット、その接続のチーム、および離脱したプレイヤーのユニットがまだチームスロットを確保しているかどうか。
RejectConnection(conn, reason)サーバー:理由をクライアントに送信し、Reject Disconnect Delay の経過後に切断します。
RTSNetworkUnit と RTSNetworkPlayer
static RTSNetworkUnit.Spawned、Despawned、Allネットワークユニットがスポーンされたとき(チームの適用後)またはデスポーンされたときに、すべてのマシンで発生するイベント。このマシンで認識されているすべてのユニット。
static RTSNetworkPlayer.LocalPlayerStarted、LocalPlayerStopped、Localローカルプレイヤーオブジェクトが出現したとき、または消えたときのイベント。現在のローカルプレイヤー。
using Mirror;
using RTSTerrainStudio.Integrations.Mirror;
using UnityEngine;

// Server: gives every player an extra starting object (for example a town hall) next to its start location.
// Put it on the network manager object.
public class StartingObjectsExample : MonoBehaviour
{
    [SerializeField] GameObject townHallPrefab;   // NetworkIdentity + RTSUnit + RTSNetworkUnit, in Registered Spawnable Prefabs

    RTSNetworkManager manager;

    void Awake() => manager = GetComponent<RTSNetworkManager>();

    void OnEnable()
    {
        if (manager != null) manager.ServerPlayerSpawned += OnPlayerSpawned;
    }

    void OnDisable()
    {
        if (manager != null) manager.ServerPlayerSpawned -= OnPlayerSpawned;
    }

    void OnPlayerSpawned(NetworkConnectionToClient conn, RTSNetworkPlayer player)
    {
        if (player == null || townHallPrefab == null) return;
        // Start location of the player's team (a fallback position when the map has none).
        RTSNetworkUtility.GetStartLocation(manager.Terrain, player.Team, manager.MaxPlayers, out Vector3 start, out float yaw);
        // Owned by the connection, so the player can select and order it.
        manager.SpawnUnit(conn, player.Team, start, yaw, townHallPrefab);
    }
}

制限事項とトラブルシューティング

  • ゲーム中のマップ編集(ホスト上のゲーム内エディターによる編集)は、リアルタイムではストリーミングされません。マップを再送信するには RTSNetworkMapSync.SendMapToAll() を呼び出します。
  • RTSBuildingPlacementDemo と RTSTreeHarvestDemo はローカルで動作し、ネットワーク同期されません。RTSDemoGame はシングルプレイヤー用のブートストラッパーであり、ネットワークシーンには配置しないでください。
  • クライアントでは RTSUnit.State は Idle のままです。歩行アニメーションなどには RTSUnit.Velocity を使用するか、独自の SyncVar を追加してください。
  • 同盟はサーバーの戦場の霧に設定されます。クライアントは同盟情報を受信しないため、同盟チームの視界をクライアントでも表示したい場合は、クライアントでも SetAlliance を呼び出してください。
  • シーンを Additive で読み込んでも、現在のマップは維持されます。サーバーとクライアントは、同じタイルセットとライブラリ(同じプロジェクトコンテンツ)を使用する必要があります。
問題解決策
「The server's fog of war computes only its local team」と表示されるネットワークマネージャーに RTSNetworkFogOfWar を追加するか、サーバーの戦場の霧マネージャーで Compute All Teams を有効にします。
クライアントが「Connecting...」から先に進まないサーバーに到達できない(アドレス、ファイアウォール、UDP ポート 7777 の問題)か、サーバーのシーンに RTSNetworkManager がありません。
「Could not join the game: The game is full」と表示されるマップの空きスロットがプレイヤー数より少なくなっています。開始地点を追加するか、マップの Max Players を増やすか、Max Players Override を確認してください。離脱したプレイヤーのスロットは、そのユニットが生存している間は予約されたままです。
Mirror を削除した後に、Mirror の型が見つからないというコンパイルエラーが出るMIRROR 定義シンボルがまだ設定されています。詳しくは連携の削除を参照してください。
クライアントでユニットが移動しないユニットのプレハブに NetworkTransformReliable が、シーンに RTSCommandController が必要です。
敵ユニットが常に表示されるRTSFogOfWarInterestManagement がアクティブなオブジェクト上にあり、戦場の霧マネージャーがサーバー上で動作している必要があります。
マップが毎回ダウンロードされるサーバーが、読み込み後に編集または生成されたマップを送信しています。ホストする前に、マップをそのアセットまたはファイルに保存してください。

A* Pathfinding Project

この連携は、地形の通行マップを A* のグリッドグラフに変換し、マップの編集中(エディター内とゲーム内の両方)もグラフを最新の状態に保ちます。また、サンプルユニットが A* で移動できるようにします。移動方法は、A* の経路に沿ってユニット独自のステアリングで進む方法と、A* の AIPath と RVO によるローカル回避を使う方法の 2 つです。

要件:A* Pathfinding Project Pro 5.4.4 以降、6.0 未満(5.4.7 でテスト済み)。この連携は TraversalConstraint(5.4 で追加)と RVO によるローカル回避(RVOController、RVOSimulator)を使用しますが、これらは Pro 版にしか含まれていません。無料版も同じパッケージ名を使用しているため、Unity は両者を区別できません。無料版を使用する場合は、RTS_ASTAR_DISABLED を Project Settings ▸ Player ▸ Scripting Define Symbols に追加してください(または連携フォルダーを削除します)。

クイックスタート:デモシーン

  1. A* Pathfinding Project Pro 5.4 以降をインストールします。
  2. Tools ▸ RTS Terrain Studio ▸ Create Demo Content を 1 回実行します。
  3. Tools ▸ RTS Terrain Studio ▸ Integrations ▸ A* Pathfinding ▸ Create Demo Scene を実行します。シーンは Integrations/AStarPathfinding/Demo/RTS AStar Demo.unity として保存されます。デモコンテンツにマップがない場合は、RTS AStar Demo Map.rtsmap(台地、スロープ、湖)がその隣に生成されます。
  4. Play をクリックします。マウスの左ボタンでユニットを選択し、右クリックで移動させます。騎士はグラフ RTS Large を使用し、ほかのすべてのユニットは RTS Ground を使用します。
  5. A* Pathfinding オブジェクトを選択し、A* Path Provider の Mode を Agent Movement と Path Only で切り替えて、2 つの移動方法を比較します。

自分のシーンへの A* の追加

RTS Terrain を選択し、Tools ▸ RTS Terrain Studio ▸ Integrations ▸ A* Pathfinding ▸ Add A* To Selected Terrain を実行します。以下のコンポーネントを持つゲームオブジェクト A* Pathfinding が追加されます。

コンポーネント目的
AstarPathA* のメインコンポーネント。Scan on Startup はオフになり(スキャンはグラフビルダーが行います)、経路のログ出力はエラーのみに抑えられます。
RTSAstarGraphBuilder(A* Graph Builder)通行マップからグリッドグラフを作成し、マップが編集されるたびに更新します。
RVOSimulatorRVOController を持つエージェントのローカル回避。
RTSAstarPathProvider(A* Path Provider)すべてのユニットが A* を使用するようにします(RTSUnitPathProviderRegistry.Default として登録されます)。

手動でセットアップする場合も同じ要領です。これらのコンポーネントを任意のゲームオブジェクトに追加し(Add Component ▸ RTS Terrain Studio ▸ Integrations)、グラフビルダーに地形を割り当てます。または、ビルダーを地形のゲームオブジェクトに追加すると、ビルダーが自動的に地形を見つけます。AstarPath の Scan On Startup はオフにします。

複数の地形:地形ごとに、独自のグラフ名を持つ専用のグラフビルダーが必要です。2 つ目の地形に対して Add A* To Selected Terrain を実行すると、そのグラフには地形の名前を含む名前が付けられます。たとえば RTS Ground (Island) のようになります。ほかのビルダーがすでにデータを書き込んだグラフが共有されることはありません。

グラフの構築方法

  • 正確なレイアウト。通行セル 1 つにつき 1 ノードで、マップの中心に合わせて配置され、地形の Y 軸回転に合わせて回転します。地形を移動または回転すると、グラフが再構築されます(編集モードでは、地形が 0.2 秒間静止した時点で再構築されます)。
  • 物理演算は不要。A* の衝突判定と高さ判定はオフになっています。ノードの高さは地形の表面から取得され(RTSTerrain.GetHeight と同じ)、歩行可能性、タグ、ペナルティは通行フラグから取得されます。データは Burst ジョブで計算されます。
  • タグ(ビルダーで変更可能):0 = 地面、1 = 浅水域、2 = 深水域、3 = 崖の縁。Seeker の Tags 設定でこれらを使用すると、ユニットごとに特定の種類の地面を禁止したり、ペナルティを課したりできます。
  • グラフプロファイル。プロファイル 1 つにつき A* のグリッドグラフが 1 つ作られます(たとえばユニットサイズごとに 1 つ)。各プロファイルには、グラフ名、Traversal(Land、Shallow Water、Deep Water)、Unit Radius(障害物にこれより近いノードは歩行不可になるため、大きなユニットが障害物にめり込むことはありません)、地面の種類ごとのペナルティ(1000 ≈ 1 ノードあたり 1 メートル分の追加距離)、海上グラフ用の Use Water Surface Height、そして登れる高さの上限があります。ユニットは、自分の半径に合う最小のグラフを選択します。
  • 海上ユニットと水陸両用ユニット。プロファイルの Traversal を Deep Water(船)または Land | Shallow Water | Deep Water に設定し、Use Water Surface Height を有効にします。船には、Required Traversal を Deep Water に設定した RTSAstarUnitSetup を追加します。コードからは、ファクトリーメソッド RTSAstarGraphProfile.Ground()(「RTS Ground」、半径 0.5 m、Land | Shallow Water)、Naval()(「RTS Naval」、半径 1 m、Deep Water、水面の高さ)、Amphibious()(「RTS Amphibious」、半径 0.75 m、すべての種類の地面、水面の高さ)で、すぐに使えるプロファイルを作成できます。新しいプロファイルのデフォルトは、Land | Shallow Water、浅水域のペナルティ 600、崖の縁のペナルティ 200 です。
  • 更新。新しいマップ、地形の移動、設定の変更があると、すべてが再構築されます。マップを編集するたびに(RTSTerrain.PathingChanged)、影響を受けるノード、その周囲のクリアランス範囲、ノード間の接続だけが、AstarPath.AddWorkItem の呼び出しの中で書き換えられます。
  • 巨大なマップ。A* のグリッドグラフは 1 軸あたり 1024 ノードまでに制限されています(ASTAR_LARGER_GRIDS 最適化定義を使用すると 8192 まで)。これより大きな通行マップでは、1 ノードあたり複数の通行セルが自動的に使用されます。Cells Per Node を使うと、グラフを高速化するために同じことを意図的に行えます。
  • Build In Edit Mode を有効にすると、マップの編集中にグラフが A* のギズモとして表示されます。
RTSAstarGraphBuilder のメンバー説明
Cells Per Node(設定、デフォルトは 1)各軸方向における、グラフノード 1 つあたりの通行セル数。1 = 通行マップと完全に一致。巨大なマップでは自動的に引き上げられます(EffectiveCellsPerNode)。
Log Rebuilds(設定、デフォルトはオフ)完全な再構築のたびに、コンソールに 1 行出力します。
Profiles、Tagsグラフプロファイルとタグ番号。コードから変更した後は RequestRebuild() を呼び出してください。
RequestRebuild()次の更新時に完全な再構築を予約します(繰り返し呼び出しても負荷はわずかです)。
bool RebuildNow()すべてのグラフを即座に再構築します。アクティブな AstarPath またはマップがない場合は false を返します。
UpdateRegion(RectInt pathingRect)通行セルの矩形範囲内のノードを書き換えます。RTSTerrain.PathingChanged から自動的に呼び出されます。このイベントを発生させずに PathingMap.Flags を変更した場合は、自分で呼び出してください。
ProcessPendingChanges()保留中の更新をすぐに適用します(マップの編集と同じフレームで経路をリクエストする場合に使用します)。
GetGraph(index)、GetGraph(name)、GetProfileIndex(name)プロファイルの A* GridGraph。
FindProfileFor(radius, traversal)、FindGraphFor(radius, traversal)ユニットに合う最小のプロファイル(またはグラフ)。その通行タイプを許可するプロファイルがない場合は -1 / null。
GetStats(index)RTSAstarGraphStats:サイズ、ノードサイズ、歩行可能なノード数、再構築と更新の所要時間、isBuilt。
IsBuilt、IsProfileBuilt(index)、bool TryGetProblem(out string problem)グラフが現在のマップを反映しているかどうか。グラフを構築できない理由(AstarPath がない、地形がない、マップがない)。
GraphsRebuilt、GraphsUpdated完全な再構築の後と、差分更新の後に発生するイベント(差分更新では通行セルの矩形範囲が渡されます)。
static FindFor(terrain)、static All地形のビルダー。有効なすべてのビルダー。

プロバイダーのモードと RVO

モード(RTSAstarMovementMode)動作
Agent Movement(デフォルト)HandlesMovement = true。移動命令はユニットの AIPath の目的地を設定して探索を開始します。Stop と Hold は経路をクリアし、ホールド中のユニットは RVOController をロックします。到着と失敗はユニットに通知されるため、キューに入れた命令(Shift)、パトロール、ユニットイベントは組み込みの経路探索エンジンと同じように動作します。RVO によるローカル回避により、移動中のユニット同士の間隔が保たれます。
Path OnlyHandlesMovement = false。A* はウェイポイントの計算だけを行います(ユニットの Seeker を通じて計算されるため、Funnel Modifier などのモディファイアーが適用されます)。ユニットは独自のステアリングと分離処理でウェイポイントに沿って移動します。Funnel Modifier のないユニットは、経路が直線化されます(Smooth Paths)。
プロバイダー設定デフォルト説明
ModeAgent Movement上記を参照してください。
Graph Builder空使用するグラフの提供元となるビルダー。空の場合は、ユニットがいる地形のビルダーが使用されます。
Auto Configure Unitsオンすべてのユニットに RTSAstarUnitSetup を追加して設定します。これにより Seeker、FunnelModifier、RTSAstarAIPath(物理レイキャストを使わずに地形の表面に立つ AIPath)、RVOController が追加され、速度、旋回速度、加速度、半径、高さが RTSUnit からコピーされます。
Fallback To BuiltinオンA* のグラフが利用できない間(編集モード中と最初のスキャン前)は、組み込みの経路探索エンジンを使用します。
Smooth PathsオンPath Only:Funnel Modifier のないユニットの経路を直線化します。ショートカットは許可されたタグ上にとどまり、よりコストの高いノードを横切ることはありません。
Stuck Time1.5 sAgent Movement:移動中にエージェントが静止していられる秒数。この時間を超えると経路を 1 回だけ再計算し、それでも動けなければ移動を中止します。

グラフを名前で選択したり、ユニットの種類ごとにローカル回避や経路の平滑化をオフにしたりするには、ユニットのプレハブに RTSAstarUnitSetup を自分で追加します。RTSAstarAIPath のインスペクターには、A* の通常の移動設定に加えて RTS Terrain セクション(Follow Terrain Height、Stay On Water Surface、Terrain)が表示されます。

using RTSTerrainStudio.Integrations.AStar;
using RTSTerrainStudio.Samples;
using UnityEngine;

// Needs the A* Pathfinding Project Pro 5.4+. Put it into an assembly that references
// RTSTerrainStudio.Integrations.AStar (or into Assembly-CSharp, which references it automatically).
public class AstarExample : MonoBehaviour
{
    [SerializeField] RTSUnit unit;

    void Start()
    {
        // Configure one unit by hand (Seeker, FunnelModifier, RTSAstarAIPath and RVOController).
        RTSAstarUnitSetup setup = RTSAstarUnitSetup.Configure(unit, agentMovement: true);
        setup.Agent.maxSpeed *= 1.5f;

        // The graph that fits this unit's radius.
        RTSAstarGraphBuilder builder = RTSAstarGraphBuilder.FindFor(unit.Terrain);
        if (builder != null)
            Debug.Log("Unit uses graph " + builder.FindGraphFor(unit.Radius)?.name);
    }

    // After changing the pathing map from code in the middle of a frame, push the change to A* now
    // instead of waiting for the next update.
    public static void FlushGraphs(RTSUnit anyUnit)
    {
        RTSAstarGraphBuilder.FindFor(anyUnit.Terrain)?.ProcessPendingChanges();
    }
}

制限事項

  • A* のグリッドグラフのルール(GridGraph.rules)は効果がありません。ビルダーがスキャンのたびに、歩行可能性、タグ、ペナルティ、位置を自身で書き込むためです。代わりにビルダーのタグとペナルティを使用してください。
  • X 軸または Z 軸を中心に回転させた地形はサポートされていません(グリッドグラフは水平面であるため)。
  • ペイントした Unwalkable 通行オーバーライドがブロックするのは地上ユニットだけです。船をブロックするのは障害物と崖です。海上の航路は、ドゥーダッドや深水域の境界で形作ってください。海上ユニットには Agent Movement モードが必要です。
  • ビルダーから削除したグラフプロファイルのグラフは、AstarPath コンポーネントに残ります。不要になった場合は、そこで削除してください。
  • A* は再生モードでのみ経路を探索します。編集モードでは、経路リクエストは組み込みの経路探索エンジンにフォールバックします。

Unity NavMesh(AI Navigation)

この連携は、RTSTerrain 用の Unity NavMesh を構築し、サンプルユニットがその上で経路を見つけられるようにします。ゲームですでに Unity の NavMesh(NavMeshAgent、NavMesh クエリ、NavMeshLink)を使用している場合や、Unity の群衆回避を使いたい場合に使用します。AI Navigation パッケージ(com.unity.ai.navigation)2.0 以降が必要で、エディター内とゲーム内の両方で動作します。ランタイムで読み込んだマップや編集したマップにも対応しています。

連携の削除

連携を削除する必要はまったくありません。依存先がなければ、連携はコンパイルされないためです。依存先をアンインストールする場合や、プロジェクトを小さくしたい場合に削除してください。いずれの場合も、デモシーンを作成していたときは、まず連携の Demo フォルダーを削除してください。そのシーンとプレハブは連携のコンポーネントを使用しているため、スクリプトが見つからない状態になってしまいます。

A* Pathfinding Project または AI Navigation の削除

  1. Integrations/AStarPathfinding/Demo(または Integrations/UnityNavMesh/Demo)が存在する場合は、削除します。
  2. 自分のシーンとプレハブから、この連携のコンポーネント(グラフビルダー、経路プロバイダー、ユニットセットアップ / NavMesh ビルダー、NavMesh 経路プロバイダー)を削除します。
  3. パッケージ(A* Pathfinding Project、またはパッケージマネージャーの AI Navigation)をアンインストールします。バージョン定義はパッケージと一緒に消え、連携アセンブリは自動的にスキップされます。
  4. 無料版の A* 用に RTS_ASTAR_DISABLED を追加していた場合は、Scripting Define Symbols から削除して元の状態に戻してかまいません。

Mirror の削除

Mirror はスクリプティング定義シンボル MIRROR、MIRROR_89_OR_NEWER、MIRROR_90_OR_NEWER、MIRROR_93_OR_NEWER、MIRROR_96_OR_NEWER をプレイヤー設定に追加しますが、Mirror を削除してもこれらのシンボルは削除されません。MIRROR が定義されたままだと、Unity は存在しなくなった Mirror のアセンブリを参照して連携コードをコンパイルしようとし続けます。その結果発生するコンパイルエラーによって、プロジェクト内のすべてのスクリプトがブロックされます。

  1. まず Integrations/Mirror/Demo を削除します。
  2. Unity エディター内で(プロジェクトウィンドウから)Mirror フォルダーを削除します。小さなエディターアセンブリ RTSTerrainStudio.Integrations.Mirror.DefineCleanup.Editor がこの削除を検知し、すべてのビルドターゲットから Mirror のシンボルを削除して、削除した内容をログに出力します。この処理は、Mirror がない状態でエディターを起動したときにも毎回実行されます。Tools ▸ RTS Terrain Studio ▸ Integrations ▸ Mirror ▸ Remove Stale Mirror Defines を実行すると、同じ処理を手動で行えます。
  3. ビルド対象のすべてのプラットフォームについて、Project Settings ▸ Player ▸ Other Settings ▸ Scripting Define Symbols を確認してください。MIRROR や MIRROR_..._OR_NEWER のエントリが 1 つも残っていてはいけません。たとえば、エディターを閉じている間に Mirror を削除し、エディターがセーフモードで起動した場合などは、残っているエントリを手動で削除します。Mirror の Edgegap プラグインは EDGEGAP_PLUGIN_SERVERS も追加します。これは無害ですが、同様に削除してもかまいません。

連携スクリプトはプロジェクトに残しておいてかまいません。Mirror を再びインストールするまで、これらはコンパイルされません。

連携フォルダーの削除

連携はそれぞれ独立して完結しています。(プロジェクトウィンドウで)Integrations/Mirror、Integrations/AStarPathfinding または Integrations/UnityNavMesh を削除すると、その連携は完全に取り除かれます。パッケージ内のほかの部分がこれらのフォルダーを参照することはありません。Integrations フォルダー全体を削除することもできます。

連携のフォルダーを削除する前に、シーンとプレハブからその連携のコンポーネントを削除してください。そうしないと、スクリプトが見つからないコンポーネント(Missing Script)になってしまいます。Mirror をインストールしたまま Integrations/Mirror を削除し、後で Mirror も削除する場合は、Mirror の定義シンボルを手動で削除してください(自動クリーンアップはそのフォルダー内にあるためです)。

スクリプティングガイド

エディターウィンドウでできることは、すべて C# からも行えます。この章では、地形へのクエリ、コードからのマップの変更(元に戻せる方法と元に戻せない方法)、独自ツールの作成、イベントへの対応、ランタイムでのマップの保存と読み込みの方法を説明します。以下の例はすべて、配布されたままの状態のパッケージでコンパイルできます。プロジェクトにコピーして、用途に合わせて変更してください。

アセンブリ、名前空間、座標

アセンブリ名前空間内容
RTSTerrainStudio.RuntimeRTSTerrainStudio地形、マップデータ、ツール、ブラシ、植生、水、装飾オブジェクト、戦場の霧、通行判定、マップジェネレーター、シリアライザー、ゲーム内エディター。
RTSTerrainStudio.SamplesRTSTerrainStudio.SamplesRTS カメラ、ユニット、選択、コマンド、ミニマップ UI、デモゲーム。
RTSTerrainStudio.EditorRTSTerrainStudio.Editorエディターウィンドウ、シーンビューのツール、インスペクター、インポーター、セットアップウィンドウ、RTSEditorContext。
RTSTerrainStudio.Integrations.*RTSTerrainStudio.Integrations.Mirror / .AStar / .NavMeshオプションの連携。

アセンブリ定義のないスクリプト(Assembly-CSharp、および Editor フォルダー内のスクリプト用の Assembly-CSharp-Editor)は、これらのアセンブリを自動的に参照します。コードに独自の .asmdef がある場合は、その参照に RTSTerrainStudio.Runtime を追加してください(RTSTerrainStudio.Samples や RTSTerrainStudio.Editor を使用する場合はそれらも追加します)。float3 や NativeArray などの型を使用する場合は、さらに Unity.Mathematics と Unity.Collections も追加します。

マップを構成する 3 つのグリッド

  • セル:マップは、1 辺が CellSize メートル(デフォルトは 2 m)のセルを Width × Height 個並べたものです。NotifyChanged に渡す矩形はセル単位です。
  • 頂点:セルの角で、(Width + 1) × (Height + 1) 個あります。インデックスは z * VertexCountX + x(MapData.VertexIndex(x, z))です。高さ、崖レベル、フラグ、崖タイプ、水はここに格納されます。頂点 (x, z) は (x * CellSize, z * CellSize) に位置します。
  • テクセル:1 セルあたり N 個のテクセルを持つラスターレイヤーです。スプラット(SplatResolution)、ディテール(DetailResolution)、通行判定(PathingResolution)があります。テクセル (x, z) の中心は (x + 0.5, z + 0.5) * CellSize / N にあります。

マップローカル空間は最初の頂点を原点とします。X/Z は 0 から MapData.WorldSize までのメートル単位で、Y は上方向です。使用されるのは地形の位置と Y 回転だけです(スケールは無視されます)。座標の変換には WorldToLocal、LocalToWorld、WorldToLocalDirection、LocalToWorldDirection、WorldToCell、CellCenterToWorld、VertexToWorld を使用します。頂点の地面の高さは CliffLevels[i] × CliffLevelHeight + Heights[i] です。回転は度単位で、Unity の Y 回転と同じく上から見て時計回りです(0 = +Z、90 = +X)。

地形へのクエリ

RTSTerrain のクエリ API は、丸みを帯びた崖の輪郭も含め、画面に見えている地表と正確に一致します。コライダーは必要ありません。編集モードでもランタイムでも動作します。

メンバー戻り値
RTSTerrain.Main, RTSTerrain.All最初の有効な地形 / 有効なすべての地形。
RTSTerrain.FindAt(Vector3 world)その位置を領域内に含む地形。見つからない場合は null。
RTSTerrain.RaycastAll(Ray, out RTSTerrainHit, maxDistance)すべての地形の中で最も近いヒット。
Raycast(Ray, out RTSTerrainHit, maxDistance)この地形に対する解析的なレイキャスト。RTSTerrainHit には point、normal、distance、localPoint、cell、terrain があります。
GetHeight(world), SnapToGround(world)地面のワールド Y 座標 / 地面上に移動させた位置。
GetNormal(world)ワールド空間での地表の法線。
GetCliffLevel(world)その位置の崖レベル(0–15)。
IsWater(world, out depth), TryGetWaterHeight(world, out waterY)その位置が水中にあるかどうか、その水深、水面のワールド Y 座標。
GetPathing(world), IsWalkable(world), IsBuildable(world)PathingFlags(Walkable、Buildable、Flyable、ShallowWater、DeepWater、Cliff、Blocked、Steep)。マップの外や、通行マップが構築される前は None になります。
Contains(world), WorldBoundsその位置がマップ領域内にあるかどうか / ワールド空間での境界。
GetGeometry()独自のジョブで使える、Burst 対応の MapGeometryView(マップローカルの SampleHeight、SampleNormal、SampleCliffLevel、SampleWater、Raycast など)。
RTSPathfinder.FindPath(terrain, from, to, options, list)組み込みの Burst 経路探索エンジンによる経路(PathStatus:Success、Partial、NoPath、InvalidStart)。
using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;

// Logs what is under the mouse cursor when you left-click: height, cliff level, water and pathing.
// Works with any number of RTS terrains in the scene and needs no colliders.
public class TerrainQueryExample : MonoBehaviour
{
    [SerializeField] Camera viewCamera;

    void Update()
    {
        // RTSInput works with the Input System and with the legacy Input Manager.
        if (!RTSInput.GetMouseButtonDown(0)) return;

        Camera cam = viewCamera != null ? viewCamera : Camera.main;
        if (cam == null) return;
        Ray ray = cam.ScreenPointToRay(RTSInput.PointerPosition);

        // Analytic ray cast against every enabled RTS terrain (closest hit wins).
        if (!RTSTerrain.RaycastAll(ray, out RTSTerrainHit hit)) return;

        RTSTerrain terrain = hit.terrain;
        Vector3 p = hit.point;

        float groundY = terrain.GetHeight(p);           // world Y of the ground
        Vector3 normal = terrain.GetNormal(p);          // world space surface normal
        int level = terrain.GetCliffLevel(p);           // 0-15
        bool walkable = terrain.IsWalkable(p);          // ground units can walk here
        bool buildable = terrain.IsBuildable(p);        // buildings can be placed here
        PathingFlags flags = terrain.GetPathing(p);     // all pathing flags of the cell

        string water = "dry";
        if (terrain.IsWater(p, out float depth) && terrain.TryGetWaterHeight(p, out float waterY))
            water = $"water, depth {depth:0.00} m, surface at y = {waterY:0.00}";

        Debug.Log($"Cell {hit.cell} on '{terrain.name}': ground y = {groundY:0.00}, level {level}, " +
                  $"slope {Vector3.Angle(normal, Vector3.up):0} deg, {water}, " +
                  $"walkable {walkable}, buildable {buildable}, flags {flags}");
    }

    // Keeps an object standing on the ground of whatever terrain is below it.
    public static void PlaceOnGround(Transform t)
    {
        RTSTerrain terrain = RTSTerrain.FindAt(t.position);
        if (terrain != null) t.position = terrain.SnapToGround(t.position);
    }

    // Asks the built-in Burst pathfinder for a path (main thread, no third-party package needed).
    public static bool TryFindPath(RTSTerrain terrain, Vector3 from, Vector3 to, List<Vector3> waypoints)
    {
        // ForUnit sets the clearance for a unit of this radius (large units avoid narrow gaps).
        PathRequestOptions options = PathRequestOptions.ForUnit(0.5f);
        PathStatus status = RTSPathfinder.FindPath(terrain, from, to, options, waypoints);
        // Partial: the goal is unreachable, the path ends at the closest reachable cell.
        return status == PathStatus.Success || status == PathStatus.Partial;
    }
}
マウスによるピッキングには、Physics.Raycast よりも RTSTerrain.RaycastAll を使用することをお勧めします。結果が正確で、コライダーがなくても、編集モードでも動作します。ただし、コライダー(地形の設定の Collider Mode)は、物理オブジェクトや、ほかのオブジェクトにもヒットさせる必要があるレイキャストには引き続き役立ちます。

コードからのマップの編集

マップのコンテンツはすべて、MapData 型の terrain.Map に格納されています。変更は常に次の 3 つの手順で行います。

  1. (メインスレッドで)terrain.Map のデータを変更します。
  2. 変更した MapLayers と、変更したセル単位の矩形を指定して terrain.NotifyChanged(layers, cellRect) を呼び出します。マップ全体を変更した場合は、代わりに NotifyChangedAll(layers) を呼び出します。
  3. これだけです。地形は次の LateUpdate(編集モードでは次のエディター更新)で、変更のあったチャンク、スプラットテクスチャ、水、植生、装飾オブジェクト、通行マップだけを再構築します。同じフレーム内で結果が必要な場合は terrain.ProcessPendingChanges() を呼び出します(頻繁に呼び出しても負荷はわずかです)。
データ(MapData)グリッド / 型MapLayers フラグ
Heights頂点、float、レベルの上に重なるメートル単位の微細高さHeights
CliffLevels頂点、byte 0–15CliffLevels
VertexFlags頂点、VertexFlags(Ramp、Water、User0、User1)VertexFlags
CliffTypes頂点、byte 0–3CliffTypes
WaterHeights頂点、マップローカルのメートル単位の水面の高さ(Water フラグが設定されている場所で使用)Water
SplatMaps[0..3]スプラットテクセル、4 × RGBA32。レイヤー i はマップ i/4 のチャンネル i%4。ウェイトの合計は 255Splat
DetailLayersディテールテクセル、レイヤーごとに 1 バイトの密度(DetailLayer.prototypeId、density)Details
Trees, DecorTreePrototypeIds / DecorPrototypeIds へのパレットインデックスを持つ TreeInstanceData / DecorInstanceData のリストTrees, Decor
PathingOverrides通行テクセル、ペイントされた通行オーバーライドPathingOverrides
Regions, StartLocations, MarkersリストRegions, StartLocations, Markers
Info, Environmentマップ情報とライティングInfo, Environment
マップを有効な状態に保つためのルール:崖レベルは 0~15、崖タイプは 0~3 の範囲に収めます。微細高さは許容範囲内に保ちます(TerrainToolUtility.ClampFineHeight)。すべてのスプラットテクセルで、ウェイトの合計を常にちょうど 255 に保ちます(SplatRaster と SplatMath を使用します)。新しい木や装飾オブジェクトのインスタンス ID は map.AllocateInstanceId() で、プロトタイプのインデックスは GetOrAddTreePrototype / GetOrAddDecorPrototype で割り当てます。マップの読み込みやリサイズをまたいで、MapData やその配列への参照を決して保持しないでください。SetMap、LoadFromFile、ResizeMap はオブジェクト全体を置き換え、古いオブジェクトを破棄します。

ランタイムでの地形変形とペイント:クレーターと荒廃地

ゲームプレイでの典型的な使用例です。爆発でクレーターができ、アンデッドの建物が周囲に「荒廃地」の地面を広げていきます。通行判定や、植生と装飾オブジェクトの高さは自動的に追従します。

using RTSTerrainStudio;
using UnityEngine;

// Runtime terrain changes from gameplay code: explosion craters and spreading "blight".
// Both edit MapData directly and then call NotifyChanged. The terrain rebuilds only the
// touched chunks (and the pathing map) in its next LateUpdate.
public static class TerrainEffects
{
    // Digs a bowl-shaped crater into the fine height around a world position.
    public static void Crater(RTSTerrain terrain, Vector3 worldPosition, float radius, float depth)
    {
        MapData map = terrain != null ? terrain.Map : null;
        if (map == null || radius <= 0f) return;

        // Work in map-local space: X/Z in metres from the first vertex.
        Vector3 local = terrain.WorldToLocal(worldPosition);

        // Vertex rectangle covered by the crater (vertices sit at x * CellSize, z * CellSize).
        int x0 = Mathf.Max(0, Mathf.FloorToInt((local.x - radius) / map.CellSize));
        int z0 = Mathf.Max(0, Mathf.FloorToInt((local.z - radius) / map.CellSize));
        int x1 = Mathf.Min(map.Width, Mathf.CeilToInt((local.x + radius) / map.CellSize));
        int z1 = Mathf.Min(map.Height, Mathf.CeilToInt((local.z + radius) / map.CellSize));
        if (x1 < x0 || z1 < z0) return; // completely outside the map

        for (int z = z0; z <= z1; z++)
        for (int x = x0; x <= x1; x++)
        {
            float dx = x * map.CellSize - local.x;
            float dz = z * map.CellSize - local.z;
            float t = Mathf.Sqrt(dx * dx + dz * dz) / radius;
            if (t >= 1f) continue;

            // Smooth bowl: deepest in the centre, zero at the rim.
            float falloff = 1f - t * t;
            int i = map.VertexIndex(x, z);
            // Heights is the fine height on top of the cliff level. Clamp it to the allowed range.
            map.Heights[i] = TerrainToolUtility.ClampFineHeight(map, map.Heights[i] - depth * falloff * falloff);
        }

        // Tell the terrain which cells changed. Meshes, colliders, water, vegetation, decor and
        // pathing follow automatically.
        var vertexRect = new RectInt(x0, z0, x1 - x0 + 1, z1 - z0 + 1);
        terrain.NotifyChanged(MapLayers.Heights, BrushUtility.VertexRectToCellRect(map, vertexRect));
    }

    // Paints a ground layer (for example a "blight" texture) around a position with a soft edge.
    // amount is the coverage reached in the centre (0-1).
    public static void PaintLayer(RTSTerrain terrain, Vector3 worldPosition, float radius, int layer, float amount = 1f)
    {
        MapData map = terrain != null ? terrain.Map : null;
        if (map == null || radius <= 0f) return;
        layer = Mathf.Clamp(layer, 0, MapData.MaxGroundLayers - 1);

        Vector3 local = terrain.WorldToLocal(worldPosition);
        int res = map.SplatResolution;            // splat texels per cell
        float texel = map.CellSize / res;         // texel size in metres

        // Texel rectangle (texel centres sit at (x + 0.5) * texel).
        int x0 = Mathf.Max(0, Mathf.FloorToInt((local.x - radius) / texel));
        int z0 = Mathf.Max(0, Mathf.FloorToInt((local.z - radius) / texel));
        int x1 = Mathf.Min(map.SplatWidth - 1, Mathf.CeilToInt((local.x + radius) / texel));
        int z1 = Mathf.Min(map.SplatHeight - 1, Mathf.CeilToInt((local.z + radius) / texel));
        if (x1 < x0 || z1 < z0) return;

        // SplatRaster gives read/write access to all 16 layer weights of a texel.
        var splat = new SplatRaster(map);
        for (int z = z0; z <= z1; z++)
        for (int x = x0; x <= x1; x++)
        {
            float dx = (x + 0.5f) * texel - local.x;
            float dz = (z + 0.5f) * texel - local.z;
            float t = Mathf.Sqrt(dx * dx + dz * dz) / radius;
            if (t >= 1f) continue;

            int index = splat.Index(x, z);
            SplatTexel weights = splat.Read(index);
            int target = Mathf.RoundToInt(Mathf.Clamp01(amount * (1f - t)) * SplatMath.Total);
            if (weights[layer] >= target) continue; // never remove blight that is already there

            // Sets the layer weight and rescales the other layers, so the texel still sums to 255.
            SplatMath.SetLayerWeight(ref weights, layer, target, 0);
            splat.Write(index, weights);
        }

        var texelRect = new RectInt(x0, z0, x1 - x0 + 1, z1 - z0 + 1);
        terrain.NotifyChanged(MapLayers.Splat, BrushUtility.TexelRectToCellRect(map, texelRect, res));
    }
}
次のように使用します:TerrainEffects.Crater(RTSTerrain.FindAt(hitPoint), hitPoint, 4f, 1.5f); または TerrainEffects.PaintLayer(terrain, building.position, 12f, blightLayer);。毎フレーム数千個の頂点を変更するようなエフェクトでは、組み込みツールと同様に、ループを Burst ジョブに移してください。

元に戻せる編集:RTSEditSession.RunOperation

データを直接編集した場合は元に戻せませんが、ゲームプレイにはそれが適しています。エディターツールやマップ編集機能には RTSEditSession を使用してください。RunOperation(name, layers, action) は、指定したレイヤーをマップ全体について記録してからアクションを実行し、すべてを 1 アンドゥステップにまとめます。Unity エディターでは共有セッション RTSEditorContext.Session を使用します。このセッションのステップは Unity のアンドゥ機能に組み込まれるため、Ctrl+Z / Ctrl+Y キーがいつもどおり機能します。

// Put this file into a folder named "Editor".
using RTSTerrainStudio;
using RTSTerrainStudio.Editor;
using UnityEditor;
using UnityEngine;

// Editor menu command that raises a plateau in the middle of the edited map.
// It runs through the shared edit session, so Ctrl+Z / Ctrl+Y undo and redo it in one step.
public static class PlateauMenu
{
    [MenuItem("Tools/My Game/Raise Centre Plateau")]
    static void RaiseCentrePlateau()
    {
        RTSTerrain terrain = RTSEditorContext.Terrain;   // the terrain selected in the RTS Terrain Studio window
        if (terrain == null || terrain.Map == null) return;

        // The shared session of the window and the Scene View. It always edits RTSEditorContext.Terrain.
        RTSEditSession session = RTSEditorContext.Session;

        // RunOperation records the listed layers of the whole map BEFORE the operation runs,
        // so inside it you only change data and notify the terrain.
        session.RunOperation("Raise Centre Plateau", MapLayers.CliffLevels | MapLayers.Heights, ctx =>
        {
            MapData map = ctx.Map;
            int size = Mathf.Min(map.Width, map.Height) / 4;          // plateau size in cells
            var vertexRect = new RectInt(map.Width / 2 - size / 2, map.Height / 2 - size / 2, size + 1, size + 1);

            for (int z = vertexRect.yMin; z < vertexRect.yMax; z++)
            for (int x = vertexRect.xMin; x < vertexRect.xMax; x++)
            {
                int i = map.VertexIndex(x, z);
                // Cliff levels are bytes 0-15. Ground height = level * CliffLevelHeight + fine height.
                map.CliffLevels[i] = (byte)Mathf.Min(MapData.MaxCliffLevel, map.CliffLevels[i] + 1);
                map.Heights[i] = 0f;                                   // flat top
            }

            ctx.MarkChanged(MapLayers.CliffLevels | MapLayers.Heights,
                BrushUtility.VertexRectToCellRect(map, vertexRect));
        });
    }
}

実行中のゲームでは、ゲーム内エディターのセッション(RTSRuntimeEditor.Session)を使用して操作がそのアンドゥ履歴を共有するようにするか、独自のセッションを作成します。

using RTSTerrainStudio;
using UnityEngine;

// Undoable edits in a running game: use the session of the in-game editor, or your own session.
public class RuntimeUndoExample : MonoBehaviour
{
    RTSEditSession ownSession;

    public void FloodLowGround(RTSTerrain terrain, float waterLocalY)
    {
        // Prefer the in-game editor's session, so the edit shares its undo history (Ctrl+Z in the editor UI).
        var ingameEditor = FindFirstObjectByType<RTSRuntimeEditor>();
        RTSEditSession session = ingameEditor != null ? ingameEditor.Session : null;
        if (session == null)
        {
            // Without the in-game editor: a plain session works as well (call Dispose when done).
            if (ownSession == null) ownSession = new RTSEditSession();
            ownSession.Terrain = terrain;
            session = ownSession;
        }

        session.RunOperation("Flood Low Ground", MapLayers.Water | MapLayers.VertexFlags, ctx =>
        {
            MapData map = ctx.Map;
            const byte waterFlag = (byte)VertexFlags.Water;
            for (int i = 0; i < map.VertexCount; i++)
            {
                // Ground height of a vertex = cliff level * level height + fine height.
                float ground = map.CliffLevels[i] * map.CliffLevelHeight + map.Heights[i];
                if (ground >= waterLocalY) continue;
                // A vertex is wet when it has the Water flag; WaterHeights holds its surface height.
                map.VertexFlags[i] = (byte)(map.VertexFlags[i] | waterFlag);
                map.WaterHeights[i] = waterLocalY;
            }
            // Water changes only need the Water layer: terrain meshes are not rebuilt.
            ctx.Terrain.NotifyChangedAll(MapLayers.Water);
        });
    }

    public void UndoLast()
    {
        ownSession?.Undo();
    }

    void OnDestroy()
    {
        ownSession?.Dispose();
    }
}
ストロークベースのツールの中では RunOperation を呼び出しません。代わりに、ダブごとに ctx.Record と ctx.MarkChanged を呼び出します(次のセクションを参照してください)。RunOperation はレイヤー全体を記録するため、大きなマップでは MapLayers.All よりも特定のレイヤーを指定することをお勧めします。

カスタムツールの作成

ツールは、RTSTool を継承した通常の C# クラスです。このパッケージはリフレクション(RTSToolRegistry)を使って、public なパラメーターなしのコンストラクターを持つ非抽象のツールクラスをすべて検出します。そのため、作成したツールは RTS Terrain Studio ウィンドウとゲーム内エディターの両方で、該当するカテゴリのタブに自動的に表示されます。[ToolSetting] フィールドから生成される設定 UI、ブラシパネル、対称、元に戻す・やり直し、投影されるブラシカーソル、ステータスバーもそのまま使えます。エディターでは、ツールの設定はセッションをまたいで保持されます。

  1. プロジェクト内の任意の場所に C# ファイルを作成します(ゲーム内エディター用にビルドにもツールが含まれるよう、Editor フォルダーには置かないでください)。
  2. RTSTool を継承し、Id(一意の値。たとえば "height.crater")、DisplayName、Category をオーバーライドします。
  3. ユーザーが変更できるオプションごとに、[ToolSetting("Label", ...)] を付けた public フィールドを追加します。
  4. ApplyDab をオーバーライドします。BrushUtility.ComputeWeights でブラシのウェイトを計算し、データを変更する前に ctx.Record(layers, cells) を呼び出します。続いてデータを変更し、変更した後に ctx.MarkChanged(layers, cells) を呼び出します。
  5. Unity に戻ります。スクリプトのコンパイルが終わると、ツールが該当するタブに表示されます。
  6. Managed Stripping Level が Minimal より高いビルドにツールを含めてリリースする場合は、コードストリッピングで削除されないように、クラスに [UnityEngine.Scripting.Preserve] を付けます(コードストリッピングとカスタムツールを参照)。
using RTSTerrainStudio;
using UnityEngine;

// A custom tool: every click digs a crater and paints scorched ground into it.
// No registration is needed. The class is found by reflection and appears in the Height tab
// of the RTS Terrain Studio window and of the in-game editor, with a UI for every [ToolSetting].
public sealed class CraterTool : RTSTool
{
    // Unique id. The part before the dot also picks a fallback icon ("height" = height tools).
    public override string Id => "height.crater";
    public override string DisplayName => "Crater";
    public override ToolCategory Category => ToolCategory.Height;
    public override string Description => "Digs a crater with a scorched floor at the click position.";
    public override string Hint => "Click: dig a crater | Shift+click: raise a mound";
    public override int Order => 100;                                   // after the built-in height tools
    public override ToolInputMode InputMode => ToolInputMode.Click;     // one dab per click
    public override bool UsesBrush => true;                             // keep the brush size / shape panel
    public override MapLayers ModifiedLayers => MapLayers.Heights | MapLayers.Splat;
    public override Color PreviewColor => new Color(1f, 0.45f, 0.1f, 1f);

    // Public fields with [ToolSetting] get a slider, toggle or palette automatically.
    [ToolSetting("Depth", Min = 0.1f, Max = 6f, Order = 0, Tooltip = "Depth of the crater centre in metres.")]
    public float depth = 1.5f;

    [ToolSetting("Paint Floor", Order = 1, Tooltip = "Paint a ground layer into the crater.")]
    public bool paintFloor = true;

    [ToolSetting("Floor Layer", Kind = ToolSettingKind.GroundLayer, Order = 2, Tooltip = "Ground layer of the crater floor.")]
    public int floorLayer = 0;

    public override void ApplyDab(RTSToolContext ctx, in BrushDab dab)
    {
        MapData map = ctx.Map;

        // 1. Brush weights (0-1) for every vertex under the brush. Shape, hardness, falloff and
        //    rotation are already applied; strength is not (use dab.strength). Dispose the result.
        using BrushWeights w = BrushUtility.ComputeWeights(map, dab, ctx.BrushMask, BrushGrid.Vertices, 1);
        if (w.IsEmpty) return;
        // Cells touched by the dab (covers both the vertex and the splat texel grid).
        RectInt cells = BrushUtility.GetCellRect(map, dab);

        // 2. Record for undo BEFORE changing anything. Record every layer you are going to write.
        MapLayers layers = paintFloor ? MapLayers.Heights | MapLayers.Splat : MapLayers.Heights;
        ctx.Record(layers, cells);

        // 3. Change the data. Shift (dab.invert) raises a mound instead.
        float delta = depth * dab.strength * (dab.invert ? 1f : -1f);
        for (int z = w.rect.yMin; z < w.rect.yMax; z++)
        for (int x = w.rect.xMin; x < w.rect.xMax; x++)
        {
            float weight = w[x, z];
            if (weight <= 0f) continue;
            int i = map.VertexIndex(x, z);
            map.Heights[i] = TerrainToolUtility.ClampFineHeight(map, map.Heights[i] + delta * weight);
        }

        if (paintFloor && !dab.invert)
        {
            // The splat maps use their own texel grid: compute weights for it as well.
            int res = map.SplatResolution;
            using BrushWeights sw = BrushUtility.ComputeWeights(map, dab, ctx.BrushMask, BrushGrid.Texels, res);
            var splat = new SplatRaster(map);
            int layer = Mathf.Clamp(floorLayer, 0, MapData.MaxGroundLayers - 1);
            for (int z = sw.rect.yMin; z < sw.rect.yMax; z++)
            for (int x = sw.rect.xMin; x < sw.rect.xMax; x++)
            {
                int target = Mathf.RoundToInt(sw[x, z] * SplatMath.Total);
                int index = splat.Index(x, z);
                SplatTexel t = splat.Read(index);
                if (t[layer] >= target) continue;
                SplatMath.SetLayerWeight(ref t, layer, target, 0);
                splat.Write(index, t);
            }
        }

        // 4. Notify the terrain AFTER the change (the same rectangle you recorded).
        ctx.MarkChanged(layers, cells);
    }
}

オーバーライドできるメンバー

メンバーデフォルト説明
Id、DisplayName、Category必須一意の ID、UI 上の名前、タブ(Height、Cliffs、Paint、Vegetation、Decor、Water、Pathing、Markers)。
Description、Hint""ツールチップとヘルプテキスト、およびステータスバーに表示される使い方のヒント。
IconNameIdアイコン名。不明な名前の場合は、ID のグループ接頭辞(height.、paint. …)のアイコンが使われ、それもなければ汎用のレンチアイコンになります。独自に描画したアイコンは RTSIcons.RegisterIcon で追加できます。
Order0カテゴリ内での表示位置。
InputModeBrushBrush:ポインターの軌跡に沿ってダブを置きます(ブラシの間隔に従います)。Click:1 回押すごとにダブを 1 つ置きます。Drag:OnDragBegin / OnDragUpdate / OnDragEnd を使います(矩形や線など)。
UsesBrushBrush では trueブラシパネル(サイズ、形状、強さ …)を表示します。
RepeatRate0ポインターを動かさずにボタンを押し続けている間の、1 秒あたりのダブ数(連続して上げ下げする場合など)。
ModifiedLayersNone情報用です。オーバーレイとステータスバーで使われます。
PreviewColor水色投影されるブラシカーソルの色。
SnapNoneブラシの中心を頂点(Cliffs ツール)またはセルの中心にスナップします。
SupportsPick + OnPickfalseCtrl+クリックすると、ペイントする代わりに値(高さやレイヤー)をスポイトで取得します。
OnActivated、OnDeactivated、OnSettingChangedライフサイクルのコールバック。
OnStrokeBegin、OnStrokeEnd、OnStrokeCancelledストロークの前後で呼び出されます。キャンセルされたストローク(Esc キー)は、コールバックの後で元に戻されます。
DrawGizmos(ctx, gizmos, hasHover, hoverLocal)IToolGizmos を介した追加のプレビュー。エディターでは Handles、ゲーム内では GL のラインで描画されます。
GetStatus(ctx, hoverLocal)nullステータスバーのテキスト。例:「Level 3 → 4」。

[ToolSetting] には Min/Max(スライダーの範囲)、Order、Tooltip、Kind を指定できます。Auto は、フィールドの型に応じて編集 UI を選びます(float/int のスライダー、bool のトグル、enum のポップアップ、Color、string)。特殊な種類として、GroundLayer と CliffType(サムネイル付きのタイルセットパレット)、CliffLevel、DetailPrototype / DetailPrototypeList、TreePrototype / TreePrototypeList、DecorPrototype / DecorPrototypeList(ライブラリのパレット)、Player(0–15)、Hidden があります。

Hidden の設定は、汎用の設定 UI には表示されません。ただし例外が 1 つあります。Unity エディターのウィンドウでは、UnityEngine.Object 型の Hidden フィールド(AutoPaintTool.profile のようなアセット参照)は、それでもオブジェクトフィールドとして表示されます。汎用 UI ではほかにアセット参照を表示する方法がないためです。ゲーム内エディターには表示されません。非表示の設定も、ほかのツール設定と一緒に保存されます。

知っておくと便利なこと

  • 対称は自動で処理されます。ApplyDab は対称コピーごとに 1 回ずつ呼び出されます。どのコピーかは dab.symmetry でわかります(ヨーのオフセットや、ミラーリングされているかどうかも含みます)。dab.rotation は変換済みです。
  • ダブ(BrushDab)には、localPosition、worldPosition、radius、strength、rotation、invert(Shift キーを押しているかどうか)、isFirstDab、dabIndex が含まれます。
  • ブラシのウェイト:頂点レイヤーには BrushGrid.Vertices を解像度 1 で使います。ラスターレイヤーには BrushGrid.Texels を使い、解像度として map.SplatResolution、DetailResolution または PathingResolution を渡します。矩形の変換には BrushUtility.VertexRectToCellRect、TexelRectToCellRect、GetCellRect(map, dab) を使います。ウェイトは必ず破棄してください。
  • 乱数値は、1 つのダブのすべての対称コピーで同じにする必要があります。ctx.Random はストロークごとにシードが再設定され、呼び出すたびに進みます。そのため、コピー側で呼び出すと元のダブとは異なる値が得られます。乱数値は dab.symmetry.index == 0 のときだけ生成して、その dab.dabIndex 用にキャッシュし、ほかのコピーでは SymmetricScatter.TransformOffset と SymmetryPoint.TransformYaw でミラーリングまたは回転して再利用します。public な SymmetricScatter クラス(GetCandidates、GetSingleSeed)を使えば、この処理を任せられます。
  • ハードセレクション:クラシック RTS スタイルの「全か無か」の編集を行うには、CliffTool、WaterTargetTool または PathingTool を継承するか(これらはカテゴリ、カーソルの色、ステータステキストを提供します。WaterTargetTool は Water Height 設定と Ctrl+クリックによるスポイトも追加します)、TerrainToolUtility.BeginHardVertexSelection(ctx, dab, layers, out weights, out forcedIndex, out cells) を呼び出します。ウェイトが TerrainToolUtility.HardSelectionThreshold(0.5)以上の頂点と、ブラシ中心の下にある頂点(forcedIndex)が対象となり、アンドゥの記録もその範囲だけに対して行われます。メソッドが false を返した場合も、weights は破棄してください。
  • パフォーマンス:組み込みツールは、ループを Burst ジョブで実行しています(Runtime/Tools にある RaiseLowerTool と PaintLayerTool を参照してください)。小さなブラシなら、通常のループで問題ありません。
  • [HideTool] を付けたクラスは一切登録されません。どちらのエディターにも session.Tools にも含まれないため、SetActiveTool、RTSEditorContext.ActivateTool、FindTool では見つかりません。このようなツール(たとえばデバッグ用ツール)をコードから使うには、インスタンスを直接割り当てます(session.ActiveTool = new MyDebugTool();)。Unity エディターでは、RTSEditorContext.EditingEnabled = true も設定してください。
  • レジストリが検索するのは、このパッケージのランタイムアセンブリと、それを直接参照しているアセンブリ、またはそうしたアセンブリを介して間接的に参照しているアセンブリだけです。ツールは必ず RTSTool を継承するため、ツールのアセンブリは必然的にランタイムアセンブリを参照しています(アセンブリ定義のないスクリプトは Assembly-CSharp に含まれ、このアセンブリはランタイムアセンブリを自動的に参照します)。また、レジストリは、名前が System、Unity.、UnityEngine、UnityEditor、Mono.、mscorlib、netstandard、nunit で始まるアセンブリをスキップします。自分のアセンブリには、このような名前を付けないでください。
  • コードストリッピング:ツールはリフレクションによって作成されるため、Managed Stripping Level が Minimal より高いビルドでは、ツールが使われていることを認識できません。組み込みツールは、このパッケージの Runtime/link.xml によって保持されます。独自のツールには [UnityEngine.Scripting.Preserve] を付けるか、プロジェクトの link.xml に記載してください(コードストリッピングとカスタムツールを参照)。
  • コードからツールをアクティブにするには、session.SetActiveTool("height.crater") を呼び出します。Unity エディターでは RTSEditorContext.ActivateTool("height.crater") を使うこともできます。

コードからのセッションの操作

両方のエディターのブラシ、対称、ストロークは、すべて RTSEditSession を経由します。同じ API を使って、独自のフロントエンドを作成できます。たとえば、カスタムのペイント UI、プレイヤーの代わりにペイントするチュートリアル、レベルスクリプトなどです。

メンバー説明
BrushSettings Brush共有のブラシ:shape、customShape、radius、strength、hardness、useFalloffCurve、falloffCurve、rotation、randomRotation、spacing、jitter。コードから falloffCurve のキーを変更した後は、Brush.MarkDirty() を呼び出してください。ベイク済みのブラシマスクは、形状、硬さ、useFalloffCurve、customShape の変更は検出しますが、カーブのキーの変更は検出しません。
SymmetrySettings Symmetrymode(SymmetryMode)と center(正規化された値。0.5/0.5 = マップの中心)。GetPoints(localPos, mapWorldSize, mergeDistance, results) は、元の点とそのコピーを返します。
ScaleRadius(factor)、AddStrength(delta)[ ] と - = のショートカットと同じ操作です。値は有効な範囲に制限されます。
bool InvertModifierShift キーを押したままにするのと同じです。上げる代わりに下げ、ペイントする代わりに消去します。
BeginStroke(worldPoint)、UpdateStroke(worldPoint, deltaTime)、EndStroke()、CancelStroke()ボタンを押してから離すまでの 1 回のストローク。ボタンが押されている間は、毎フレーム UpdateStroke を呼び出してください(繰り返し動作するツールが機能し続けるよう、ポインターが動いていないときも呼び出します)。EndStroke は 1 アンドゥステップを追加し、CancelStroke はストロークを元に戻します。
Pick(worldPoint)アクティブなツールで、Ctrl+クリックと同じスポイト操作を実行します。
FindTool(id)、FindTool<T>()、GetTools(category)、SetActiveTool(id)、ActiveToolツールを検索して、アクティブにします。
ActiveToolChanged、StrokeEnded、UndoRedoPerformed、SelectionChangedUI 用のイベントです。それぞれ、アクティブなツールの変更、ストロークの完了(キャンセルされたストロークでは発生しません)、元に戻す・やり直しの実行、選択中の装飾オブジェクトの変更時に発生します。
using RTSTerrainStudio;
using UnityEngine;

// Paints with the built-in tools from your own code, for example from a custom UI.
// Call PressAt / DragTo / Release with world positions on the terrain (for example from terrain.Raycast).
public class SessionDriverExample : MonoBehaviour
{
    public RTSTerrain terrain;
    RTSEditSession session;

    void OnEnable()
    {
        // In a scene with the in-game editor you can use RTSRuntimeEditor.Session instead.
        session = new RTSEditSession();
        session.Terrain = terrain;

        // Brush and symmetry are shared by every tool of this session.
        session.Brush.radius = 8f;
        session.Brush.strength = 0.6f;
        session.Symmetry.mode = SymmetryMode.Rotate2;          // 1v1 point symmetry
        session.Symmetry.center = new Vector2(0.5f, 0.5f);     // map centre

        session.SetActiveTool("height.raise");
        session.StrokeEnded += () => Debug.Log("Stroke finished (one undo step)");
    }

    void OnDisable()
    {
        session?.Dispose();
        session = null;
    }

    public void PressAt(Vector3 worldPoint, bool lower)
    {
        session.InvertModifier = lower;      // like holding Shift
        session.BeginStroke(worldPoint);
    }

    // Call every frame while the button is held.
    public void DragTo(Vector3 worldPoint) => session.UpdateStroke(worldPoint, Time.deltaTime);

    public void Release() => session.EndStroke();

    public void Abort() => session.CancelStroke(); // reverts the running stroke

    public void UseLinearFalloff()
    {
        session.Brush.useFalloffCurve = true;
        session.Brush.falloffCurve = AnimationCurve.Linear(0f, 1f, 1f, 0f);
        // Curve keys are not detected automatically: rebuild the baked brush mask.
        session.Brush.MarkDirty();
    }
}

イベント

イベントシグネチャ発生タイミング
MapLoadedAction<RTSTerrain>新しいマップが割り当てられた後(読み込み、新規マップ、リサイズ、再構築)。マップを元に生成しているものは、ここですべて作り直してください。
MapUnloadingAction<RTSTerrain>現在のマップが置き換えられる前、または破棄される前。マップへの参照は、ここで解放してください。
MapChangedAction<RTSTerrain, MapChangeEventArgs>NotifyChanged / NotifyChangedAll が呼び出された直後で、メッシュが再構築される前。MapChangeEventArgs には Layers、CellRect、IsFullRefresh があります。ストローク中は頻繁に発生するため、ハンドラーの処理は軽くしてください。
ChangesProcessedAction<RTSTerrain, IReadOnlyList<DirtyRegion>>保留中の変更が処理された後。メッシュ、テクスチャ、通行判定は最新の状態になっています。各 DirtyRegion には layers と cellRect があります。
PathingChangedAction<RTSTerrain, RectInt>通行マップが(部分的に)再構築された後。矩形は通行セル単位です。PathingMap.Changed とは異なり、マップを再読み込みしても登録したハンドラーは保持されます。
TilesetChangedAction<RTSTerrain>タイルセットの参照、またはその内容が変更されたとき。
LibrariesChangedAction<RTSTerrain>植生ライブラリまたは装飾ライブラリの参照、あるいはその内容が変更されたとき。
RTSTerrain.TerrainEnabled / TerrainDisabledstatic Action<RTSTerrain>地形が有効または無効になったとき(レジストリの変更)。
RTSRuntimeEditorEvents.PlaytestChangedstatic Action<bool>ゲーム内エディターがテストプレイを開始または停止したとき。ゲームプレイ用のオブジェクトの生成と後片付けは、ここで行います。マップはテストプレイの後で復元されます。
RTSRuntimeEditorEvents.MapOpenedstatic Action<RTSTerrain>ゲーム内エディターがマップを開いたとき。
RTSEditSession:ActiveToolChanged、TerrainChanged、StrokeEnded、UndoRedoPerformed、SelectionChangedセッション(エディターまたはゲーム内エディター)の編集状態。
using System.Collections.Generic;
using RTSTerrainStudio;
using UnityEngine;

// Listens to all important terrain events. Put it on the RTS Terrain GameObject (or assign the terrain).
public class TerrainEventsExample : MonoBehaviour
{
    [SerializeField] RTSTerrain terrain;

    void OnEnable()
    {
        if (terrain == null) terrain = GetComponent<RTSTerrain>();
        if (terrain == null) return;
        terrain.MapLoaded += OnMapLoaded;               // new map assigned (load, new map, resize)
        terrain.MapChanged += OnMapChanged;             // immediately when NotifyChanged is called
        terrain.ChangesProcessed += OnChangesProcessed; // after meshes, textures and pathing are up to date
        terrain.PathingChanged += OnPathingChanged;     // after the pathing map was rebuilt
        terrain.TilesetChanged += OnTilesetChanged;     // tileset reference or content changed

        // The map may already be loaded before this component was enabled.
        if (terrain.Map != null) OnMapLoaded(terrain);
    }

    void OnDisable()
    {
        // Always unsubscribe: terrains survive scene changes in the editor and domain reloads.
        if (terrain == null) return;
        terrain.MapLoaded -= OnMapLoaded;
        terrain.MapChanged -= OnMapChanged;
        terrain.ChangesProcessed -= OnChangesProcessed;
        terrain.PathingChanged -= OnPathingChanged;
        terrain.TilesetChanged -= OnTilesetChanged;
    }

    void OnMapLoaded(RTSTerrain t)
    {
        // Rebuild everything your game derives from the map, e.g. spawn points from start locations.
        Debug.Log($"Map '{t.Map.Info.name}' loaded: {t.Map.Width} x {t.Map.Height} cells, " +
                  $"{t.Map.StartLocations.Count} start locations.");
    }

    void OnMapChanged(RTSTerrain t, MapChangeEventArgs e)
    {
        // Raised often during brush strokes: keep it cheap. Check which layers changed.
        if ((e.Layers & (MapLayers.Trees | MapLayers.Decor)) != 0)
            Debug.Log($"Objects changed in cells {e.CellRect} (full refresh: {e.IsFullRefresh})");
    }

    void OnChangesProcessed(RTSTerrain t, IReadOnlyList<DirtyRegion> regions)
    {
        // Meshes are rebuilt now, so GetHeight and Raycast return the new surface.
        foreach (DirtyRegion r in regions)
            if ((r.layers & MapLayers.Geometry) != 0)
                Debug.Log($"Ground changed in cells {r.cellRect}");
    }

    void OnPathingChanged(RTSTerrain t, RectInt pathingRect)
    {
        // The rectangle is in pathing cells (Map.PathingResolution per map cell). Re-plan paths here.
        Debug.Log($"Pathing changed in {pathingRect}");
    }

    void OnTilesetChanged(RTSTerrain t)
    {
        Debug.Log("Tileset is now " + (t.Tileset != null ? t.Tileset.name : "none"));
    }
}

ランタイムでの保存と読み込み、カスタムファイルセクション

マップは .rtsmap ファイルとして保存されます。Unity エディター(RTSMapAsset としてインポートされます)、ゲーム内エディター、Mirror のマップストリーミングで同じ形式が使われるため、プレイヤーが作ったマップはそのまま Unity で開くことができ、その逆も可能です。

メンバー説明
terrain.SaveToFile(path) / terrain.LoadFromFile(path)ランタイムでの保存と読み込み。どちらも CurrentFilePath を設定し、保存すると HasUnsavedChanges がクリアされます。
terrain.MapAsset、terrain.LoadFromAsset()プロジェクトにインポートされたマップを使用します(キャンペーンマップ、組み込みのスカーミッシュマップなど)。
terrain.SetMap(MapData)任意の MapData を表示します。地形がその所有権を引き継ぎ、以前のマップを破棄します。
RTSMapSerializer.Serialize(map) / Deserialize(bytes)マップをバイト列として扱います(ネットワーク、クラウドセーブ、MOD のダウンロード)。デシリアライズしたマップの所有権は呼び出し元にあります。
RTSMapSerializer.SaveToFile、LoadFromFile、Write(map, stream)、Read(stream)地形を使わずにファイルやストリームにアクセスします。
RTSMapSerializer.TryReadHeaderFromFile(path, out header)マップブラウザー向けに、小さなヘッダー(名前、作成者、説明、サイズ、セルサイズ、最大プレイヤー数、タイルセット ID、更新日時)だけを読み込みます。
RTSMapSerializer.DefaultMapsFolder、FileExtensionStorageRoot 内の Maps フォルダー(ゲーム内エディターの Maps Folder が設定されていない場合に使われます)と ".rtsmap"。
RTSMapSerializer.StorageRootセッションをまたいで保持されるファイル用のフォルダーで、Application.persistentDataPath です。Web ビルドでは、persistentDataPath がページのアドレスによって変わるため、ブラウザーのストレージ内にある、会社名と製品名に基づいた名前のフォルダーになります。
RTSWebFileSystem.Flush()、DownloadFile(...)、IsSupportedWeb ビルドで、変更したファイルをブラウザーのストレージに書き込んでリロード後も残るようにし、ブラウザーでファイルをダウンロードできるようにします。RTSMapSerializer.SaveToFile と terrain.SaveToFile は自動的にフラッシュを行います。独自に System.IO でファイルを書き込んだり削除したりした後は、Flush() を呼び出してください。ほかのプラットフォームでは、これらのメソッドは何もしません。詳しくは Web ビルド用のスクリプティング API を参照してください。
using System.Collections.Generic;
using System.IO;
using RTSTerrainStudio;
using UnityEngine;

// Saving, listing and loading maps at runtime (for example for your own map browser or a save game).
public static class MapFilesExample
{
    // The folder the in-game editor uses too: Maps in RTSMapSerializer.StorageRoot.
    static string Folder => RTSMapSerializer.DefaultMapsFolder;

    public static void Save(RTSTerrain terrain, string fileName)
    {
        // SaveToFile creates the folder when needed and remembers the path in terrain.CurrentFilePath.
        terrain.SaveToFile(Path.Combine(Folder, fileName + RTSMapSerializer.FileExtension));
    }

    public static void Load(RTSTerrain terrain, string fileName)
    {
        string path = Path.Combine(Folder, fileName + RTSMapSerializer.FileExtension);
        if (File.Exists(path)) terrain.LoadFromFile(path); // raises MapLoaded
    }

    // Lists all maps with their header (name, size, players) without loading the whole file.
    public static List<RTSMapHeader> ListMaps()
    {
        var result = new List<RTSMapHeader>();
        if (!Directory.Exists(Folder)) return result;
        foreach (string path in Directory.GetFiles(Folder, "*" + RTSMapSerializer.FileExtension))
            if (RTSMapSerializer.TryReadHeaderFromFile(path, out RTSMapHeader header))
                result.Add(header);
        return result;
    }

    // Maps can also travel as bytes (network, cloud save, mod download).
    public static byte[] ToBytes(RTSTerrain terrain) => RTSMapSerializer.Serialize(terrain.Map);

    public static void FromBytes(RTSTerrain terrain, byte[] bytes)
    {
        // The terrain takes ownership of the new MapData and disposes the previous map.
        terrain.SetMap(RTSMapSerializer.Deserialize(bytes));
    }

    // Loads a .rtsmap that was imported into the project (an RTSMapAsset), e.g. for a campaign.
    public static void LoadAsset(RTSTerrain terrain, RTSMapAsset asset)
    {
        // Assigning a different asset loads it immediately (when the terrain is enabled).
        // Assigning the same asset again does nothing, so reload it explicitly.
        if (terrain.MapAsset == asset) terrain.LoadFromAsset();
        else terrain.MapAsset = asset;
    }
}

ファイルレイアウトとカスタムセクション

"RTSM"            4 bytes magic
int32             format version (RTSMapSerializer.CurrentVersion = 1)
int32 + bytes     uncompressed header (RTSMapHeader)
deflate stream    sections: [int32 tag][int32 length][payload] ...
                  (built-in sections first, then the CustomSectionWriter sections)
                  int32 0  end marker

読み込み側は未知のセクションをスキップします。そのため、独自のデータ(トリガー、初期資源、クエストデータ)をマップファイルに保存しても、古いバージョンやほかのツールで引き続きマップを読み込めます。保存時にセクションを追加するには RTSMapSerializer.CustomSectionWriter を登録し、読み込み時に未知のセクションをすべて受け取るには RTSMapSerializer.CustomSectionReader を登録します。組み込みセクションと決して衝突しないよう、タグは 'X' で始まるものを使用してください。タグ 0 は予約済みで、セクションの終わりを示します。

CustomSectionReader は未知のセクションごとに呼び出されるため、各システムはそれぞれ固有の 'X…' タグを使用する必要があります。2 つの書き込み側が同じタグで異なるペイロードを出力すると、読み込み側は解釈できないペイロードを受け取り、マップの読み込みに失敗します。たとえば、カスタムセクションの StartingGoldSection は XGLD を使用しているため、以下の例では XGMD を使用しています。

using System.IO;
using System.Runtime.CompilerServices;
using RTSTerrainStudio;
using UnityEngine;

// Stores your own game data (here: starting gold and a victory text) inside the .rtsmap file.
// Older versions of the package and other games simply skip the unknown section.
public static class MapGameData
{
    public sealed class Data
    {
        public int startingGold = 500;
        public string victoryText = "";
    }

    // Section tag "XGMD" as a little-endian FourCC. Start your tags with 'X' so they never
    // collide with the built-in sections, and use a tag no other script of your project uses.
    const int Tag = 'X' | ('G' << 8) | ('M' << 16) | ('D' << 24);
    // Bump this when you change the payload layout, and keep reading the old layouts.
    const int PayloadVersion = 1;

    // One Data object per MapData instance. The table does not keep maps alive.
    static readonly ConditionalWeakTable<MapData, Data> s_Data = new ConditionalWeakTable<MapData, Data>();

    // Returns the game data of a map (created with defaults on first access).
    public static Data Get(MapData map) => s_Data.GetValue(map, _ => new Data());

    // Registers the hooks before any scene loads (play mode and builds) and, in the editor,
    // after every script reload, so maps saved from the RTS Terrain Studio window keep the data too.
#if UNITY_EDITOR
    [UnityEditor.InitializeOnLoadMethod]
#endif
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void Register()
    {
        // Remove first, so entering play mode without a domain reload never registers twice.
        RTSMapSerializer.CustomSectionWriter -= Write;
        RTSMapSerializer.CustomSectionWriter += Write;
        RTSMapSerializer.CustomSectionReader -= Read;
        RTSMapSerializer.CustomSectionReader += Read;
    }

    // Called while a map is saved: hand every section you want to write to addSection(tag, payload).
    static void Write(MapData map, System.Action<int, byte[]> addSection)
    {
        if (!s_Data.TryGetValue(map, out Data data)) return; // nothing set: write no section
        using var stream = new MemoryStream();
        using (var w = new BinaryWriter(stream))
        {
            w.Write(PayloadVersion);
            w.Write(data.startingGold);
            w.Write(data.victoryText ?? "");
        }
        addSection(Tag, stream.ToArray());
    }

    // Called for every section the serializer does not know. Ignore tags that are not yours,
    // other scripts may have registered readers too.
    static void Read(MapData map, int tag, byte[] payload)
    {
        if (tag != Tag) return;
        using var r = new BinaryReader(new MemoryStream(payload));
        int version = r.ReadInt32();
        var data = Get(map);
        data.startingGold = r.ReadInt32();
        if (version >= 1) data.victoryText = r.ReadString();
    }
}
この例のデータは 1 つの MapData オブジェクトに属します。新しいマップオブジェクトを作成する操作(読み込み、リサイズ、Clone)では、このデータは自動的には引き継がれません。ファイルから読み込み直すか、マップを複製またはリサイズするときに独自のコードでコピーしてください。

よくある質問(FAQ)

すべてがピンク(マゼンタ)で表示されます。原因は何ですか?

ピンクは、シェーダーを使用できなかったことを意味します。RTS Terrain Studio は、Universal Render Pipeline 専用の手書きシェーダーを使用しています。

  1. Tools ▸ RTS Terrain Studio ▸ Validate Project を開きます。Universal Render Pipeline is active チェックが緑色になっている必要があります。Fix All(またはそのチェックの修正ボタン)で URP アセットが割り当てられます。
  2. 同じウィンドウで Package shaders compile チェックを確認します。シェーダーにエラーがある場合は、コンソールにそのシェーダー名が表示されます。パッケージの Shaders フォルダーを再インポートしてください(右クリック ▸ Reimport)。
  3. 自分のプレハブ(木、岩、ユニット)だけがピンクの場合、それらのマテリアルはビルトインパイプライン用のシェーダーを使用しています。Unity のレンダーパイプラインコンバーターで変換するか、URP マテリアルを割り当ててください。

RTS Terrain を追加しましたが、何も表示されません。

  • 地形を選択し、そのインスペクターを確認します。マップが読み込まれていないと表示されている場合は、マップアセットを割り当てるか、新しいマップを作成するか(ウィンドウ ▸ Map タブ ▸ New Map)、New Map Wizard を使用します。
  • RTSTerrain コンポーネントが有効になっていることを確認してください。コンポーネントを無効にすると、そのマップはアンロードされます。
  • カメラを確認してください。チャンクは地形設定で指定したレイヤー(Chunk Layer)に配置されており、このレイヤーがカメラのカリングマスクに含まれている必要があります。シーンビューで F キーを押して地形を画面に収めます。セルサイズ 2 m で 128 セルのマップは、幅が 256 m になります。
  • タイルセットのない地形も見えなくなるわけではなく、ニュートラルなフォールバックカラーで描画されます。タイルセットを割り当てるか、Tools ▸ RTS Terrain Studio ▸ Create Demo Content を実行してデモのタイルセットを使用します。
  • 地形のインスペクターで Rebuild Terrain をクリックすると、すべてのチャンク、テクスチャ、通行マップが再作成されます。

ブラシがカーソルとは別の位置にペイントされます。

エディターは Unity の DPI を正しく考慮したシーンビューのレイを使用し、ブラシカーソルは地形シェーダーによって描画されます。そのため通常の構成では、ディスプレイの拡大率が 150% 以上の場合でも、カーソルとペイント位置は常に一致します。一致しない場合は、次の原因が考えられます。

  • 地形またはその親がスケーリングされているか、X または Z 軸周りに回転しています。使用されるのは位置と Y 軸回転だけです。地形のインスペクターはどちらの場合も警告を表示します。スケールを 1、X/Z 回転を 0 に戻し、スケーリングする代わりにマップのセルサイズを変更してください。
  • 地形が Material Override を使用しており、そのシェーダーがブラシカーソルを描画しません。この場合、正しい位置にペイントされますが、カーソルは表示されません。パッケージの地形シェーダーを使用するか、そのシェーダーのブラシカーソル用コードを組み込んでください。

戦場の霧が表示されません。

  • シーンには RTSFogOfWarManager がちょうど 1 つ必要です(GameObject ▸ RTS Terrain Studio ▸ Fog of War Manager)。
  • Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、Fog of War renderer feature を修正します。このレンダラー機能は画面上のすべてを暗くします。これがないと、パッケージ独自のシェーダー(地形、水、植生)にしか霧がかからず、自分のユニットやプロップには霧がかかりません。
  • 編集モードでは、マネージャーの Preview In Edit Mode を有効にします。
  • Reveal All はオフにする必要があります。Start Explored がオンだと、マップ全体が探索済みのように見えます(黒ではなく薄暗い表示)。
  • ユニットには、チームがマネージャーの Local Team と一致する RTSFogOfWarRevealer が必要です。ほかのチームの視界源では、ローカルチームの視界は開けません。そのため、ローカルチームの視界源がないと、マップは覆われたままになります(デフォルトでは黒)。

エディターでは問題ないのに、ビルドで植生(または水や地形)が表示されません。

  • Tools ▸ RTS Terrain Studio ▸ Validate Project を実行し、Shaders included in builds を修正します。パッケージは Resources 内のシェーダー参照アセットにシェーダーを保持しており、このアセットはプレイヤーをビルドする前に毎回自動で更新されます。このアセットを削除または移動した場合は、このチェックによって再作成されます。
  • GPU instancing on instanced prototypes を修正します。インスタンス描画される木、ディテール、装飾オブジェクトには、Enable GPU Instancing が有効なマテリアルが必要です。
  • マップがビルドに含まれていることを確認してください。ビルドに含まれるシーン内の地形に RTSMapAsset として割り当てられたマップは、自動的に含まれます。ディスクから読み込むマップファイルは、プレイヤーのマシン上に存在する必要があります(たとえば RTSMapSerializer.DefaultMapsFolder 内)。
  • シェーダーストリッピングや、ターゲットプラットフォーム用の別の URP アセット(品質設定)によって、機能が削除されることがあります。ビルドで使用される URP アセットの設定が、エディターで使用しているものと同じであることを確認してください。
  • WebGL 2.0 の Web ビルドでは、多くの機能を有効にした URP アセットを使用すると、1 回の描画で使用できるテクスチャの上限(16 個)を超え、地形が表示されなくなることがあります。詳しくはモバイル向けの注意点にある OpenGL ES と WebGL 2.0 の項目を参照してください。
  • ビルドでフォグが表示されない場合は、プレイヤービルドでのフォグを参照してください。

水が黒や灰色に見えるか、屈折しません。

  • 水シェーダーはシーンの深度とカラーを読み取ります。URP アセットで Depth Texture と Opaque Texture を有効にします:Tools ▸ RTS Terrain Studio ▸ Validate Project ▸ Depth and Opaque Texture enabled ▸ 修正。
  • URP アセットの Opaque Downsampling を None に設定します。ダウンサンプリングすると屈折がぼやけます。
  • カメラを確認してください。カメラの Depth Texture と Opaque Texture のオーバーライドが Off に設定されていてはいけません。水の描画は各カメラ自身の設定に従います。なお、シーンビューでは常に URP アセットの設定が使用されます。
  • Web ビルドの場合は、Web ビルドで使用される品質レベルの URP アセットを確認してください(Web quality levels have Depth and Opaque Texture チェックで確認できます。Project Validator の Web チェックを参照)。
  • 水の色はタイルセットの Water Profile で決まります。そこで浅水域と深水域の色を確認してください。

最高のパフォーマンスを得るにはどうすればよいですか?

  • Burst を有効にする必要があります(Jobs ▸ Burst ▸ Enable Compilation)。ブラシ、通行判定、戦場の霧、ジェネレーター、ミニマップは Burst ジョブで処理されます。
  • Chunk size(地形設定):16、32(デフォルト)、または 64 セル。チャンクが小さいほど編集中の再構築が速くなり、大きいほどゲーム中のドローコールが少なくなります。
  • Surface Subdivisions:1 にすると最少の三角形数でクラシック RTS らしい外観になり、2~4 にすると丘がより滑らかになります。
  • クエリ API(Raycast、GetHeight)だけを使用する場合は、Collider Mode を None にするとメッシュコライダーの生成を省略できます。
  • 木と装飾オブジェクトには Instanced スポーンモードを優先して使用してください。GameObject モードは、ゲームプレイでゲームオブジェクトとして必要なオブジェクト(伐採可能な木、スクリプト付きの装飾オブジェクト)にだけ使用します。
  • 性能の低いハードウェアでは、植生レンダラーの Detail Density Multiplier と Detail Distance Multiplier を下げます。
  • 戦場の霧:Resolution Per Cell を 1 にし、Update Interval を大きくすると負荷が下がります。
  • マップ検証ツールを実行します。木、装飾オブジェクト、GameObject モードのオブジェクト、ディテールレイヤーが多すぎる場合は、パフォーマンスに関する警告として一覧表示されます。

1 つのシーンで複数の地形を使用できますか?

はい。各 RTSTerrain は、それぞれ独自のマップ、タイルセット、ライブラリ、トランスフォームを持ちます。RTSTerrain.All ですべての地形を列挙でき、RTSTerrain.FindAt(position) で指定位置の下にある地形を取得でき、RTSTerrain.RaycastAll ですべての地形に対してレイキャストできます。エディターウィンドウでは、ヘッダーで編集対象の地形を選択します(シーン内で地形を選択しても切り替わります)。ゲーム内エディターでは Map ▸ Edited terrain を使用します。注意点:

  • アンドゥ履歴は編集対象の地形に属します。編集対象の地形を切り替えると、履歴は消去されます。
  • 戦場の霧マネージャーが対象とするのは 1 つの地形だけです(Terrain フィールドで指定した地形、空の場合は RTSTerrain.Main)。
  • A* では、地形ごとに独自のグラフ名を持つグラフビルダーが 1 つ必要です。NavMesh 連携では、地形ごとにビルダーが 1 つ必要です。

地形を移動・回転できますか?

はい。位置と Y 軸回転は、ランタイムでも完全にサポートされています。すべてのクエリ、ブラシ、植生、装飾オブジェクト、水、通行判定、戦場の霧、各連携がトランスフォームに追従します。X 軸または Z 軸周りの回転とスケールはサポートされていません(インスペクターに警告が表示されます)。マップを大きくするには、Map タブの Resize を使用するか、セルサイズを大きくします。

Input System と旧 Input Manager のどちらで動作しますか? 両方で動作しますか?

Project Settings ▸ Player ▸ Active Input Handling の 3 つの設定すべてで動作します。ランタイムコードは RTSInput を通じて入力を読み取ります。これは、Input System パッケージがインストールされていて有効な場合はそのパッケージを、それ以外の場合は旧 Input Manager を使用します。Tools ▸ RTS Terrain Studio ▸ Validate Project の Input handling チェックで、プロジェクトが使用している方式を確認できます。自分のゲームプレイコードでも RTSInput(PointerPosition、GetMouseButtonDown、GetKey(RTSKey) …)を使用すると、いずれの設定でも動作するようになります。プレイヤーのキーボードレイアウトに合わせたい文字キーのショートカットには GetKeyDownByLabel(RTSKey) を、[ や + などの記号キーには GetCharacterDown(char) を使用します。

ゲームやゲーム内エディターを Web ブラウザーで公開できますか?

はい。Web ビルド(WebGL 2.0)では、地形、植生、水、戦場の霧、ゲーム内エディター、サンプルが、マウスとキーボードで操作するデスクトップブラウザーで動作します。Tools ▸ RTS Terrain Studio ▸ Install WebGL Template で、ウィンドウ全体に表示するテンプレートをインストールします。設定、ブラウザーのストレージに保存されるマップ、ホスティングに関する注意点については、Web(WebGL)ビルドを参照してください。

パッケージを別のフォルダーに移動できますか?

はい。固定パスに依存している部分はなく、パッケージは自身の場所を基準にファイルを見つけます。

トラブルシューティング

このチェックリストを上から順に確認してください。ほとんどの問題は最初の 3 つの手順で解決します。

  1. コンソールを確認します。パッケージのメッセージは [RTS Terrain Studio] で始まり、関連するオブジェクトにリンクされています(メッセージをクリックするとそのオブジェクトが選択されます)。まずコンパイルエラーを修正してください。コンパイルに失敗するスクリプトが 1 つでもあると、ほかは何も更新されません。
  2. プロジェクトを検証します。Tools ▸ RTS Terrain Studio ▸ Validate Project は、URP、深度テクスチャと不透明テクスチャ、戦場の霧のレンダラー機能、ビルドで保持されるフォグモード、カラースペース、Burst/Collections/Mathematics、入力処理、シェーダーのコンパイル、ビルドに含まれるシェーダー、タイルセットのテクスチャ配列、GPU インスタンシング、タイルセットが設定された地形、マップファイルに保存される地形をチェックします。Web がアクティブなプラットフォームの場合は、Web 用の品質レベルと Web テンプレートもチェックします。Fix All をクリックし(事前にすべての変更点がダイアログに一覧表示されます)、次に Run Again をクリックします。
  3. 地形のインスペクターを確認します。マップは読み込まれていますか? タイルセットは割り当てられていますか? コンポーネントは有効ですか? スケールや回転に関する警告は出ていませんか? Rebuild Terrain をクリックします。
  4. マップを検証します。Map タブ ▸ Validation ▸ Validate Map で、存在しないプロトタイプ、壊れたペイント、到達できない開始地点、パフォーマンスの問題を検出できます。
  5. コンテンツを確認します。テクスチャ、植物、装飾オブジェクトが表示されない場合、たいていはタイルセットやライブラリが割り当てられていないか、マップのプロトタイプ ID がライブラリに存在しません。Tools ▸ RTS Terrain Studio ▸ Create Demo Content を再実行すると、デモアセットが復元されます。
  6. 連携を確認します。Mirror を削除した後に Integrations でコンパイルエラーが出る場合は、MIRROR シンボルがまだ定義されています(Mirror の削除)。Welcome ウィンドウで Installed と表示されているのに Active になっていない連携は、バージョンが要件を満たしていません。
  7. ドメインリロードなしの再生モードもサポートされています。2 回目の Play 以降に動作が変わる場合は、いったんドメインリロードを有効にして、自分のコードの静的な状態が原因かどうかを確認してください。
  8. それでも解決しない場合は、GameObject ▸ RTS Terrain Studio ▸ RTS Terrain で作成した地形とデモコンテンツを使って、新しいシーンで問題を再現してください。サポートを依頼するときは、Unity のバージョン、URP のバージョン、コンソールの出力、再現手順を添えてください。
症状考えられる原因参照先
マテリアルがピンクURP が有効でない、シェーダーエラー、自分のプレハブにビルトインのマテリアルを使用しているFAQ
地形が表示されないマップが読み込まれていない、コンポーネントが無効、チャンクレイヤーがカリングされているFAQ
ブラシの位置がカーソルとずれる地形のスケーリング、または X/Z 回転FAQ
戦場の霧が表示されないレンダラー機能がない、ローカルチームの視界源がない、Reveal All がオンFAQ
水が黒いDepth / Opaque Texture がオフFAQ
ビルドで植生が表示されないシェーダー参照、GPU インスタンシングFAQ
ユニットが移動しない通行マップがまだ構築されていない、経路の始点または終点が歩行不可、経路プロバイダーにグラフまたは NavMesh がないマップの検証、連携
ジェネレーターが配置する開始地点が少ないFlat Radius / Edge Margin に対して平坦な場所が足りないジェネレーター設定
インポートしたハイトマップが平坦になる、または起伏が極端になるコードで高さスケールが 0、RAW のサイズまたはバイトオーダーが誤っているハイトマップのインポートとエクスポート
Mirror の削除後に Mirror 関連のコンパイルエラーが出る古い MIRROR 定義が残っている連携の削除
RTS Terrain Studio requires URP ダイアログが表示されるURP 17.3 がインストールされていない、またはエディターが Unity 6.3 より古いUnity とレンダーパイプライン
Web ビルド:UI が極端に小さい、マップが消える、キー操作でブラウザーの機能が開く固定サイズのテンプレート、ブラウザーのストレージ、ブラウザーのショートカットWeb(WebGL)ビルド

変更履歴

バージョン 1.0.0 — 初回リリース

Unity 6.3 LTS(6000.3)と Universal Render Pipeline 17.3、および Burst、Collections、Mathematics が必要です。

マップモデル

  • クラシック RTS スタイルの、セルと角の頂点からなるグリッド。1 辺あたり 16~1024 セル(16 の倍数)、セルサイズは自由に設定可能。
  • プロシージャルに生成される丸みのある崖の壁面を備えた 16 段階の崖レベル。各レベルの上に重なる微細高さ。スロープ。タイルセットごとに 4 つの崖タイプ。
  • 高解像度スプラットマップによる、タイルセットごとに 16 個の地面レイヤー。高さベースのブレンド、オプションのトライプラナーマッピングとアンチタイリング。任意のテクスチャサイズと形式に対応。
  • 浅水域と深水域を備えた、頂点ごとの水。
  • 自動的に導出される通行マップ(歩行可能、建設可能、飛行可能、浅水域、深水域)と、ペイントによるオーバーライド。
  • リージョン、開始地点、マーカー、マップ情報、環境ライティングをマップ内に保存。

編集

  • Unity エディターとゲーム内エディターで共通のツールセット:高さ、崖、ペイント(ソフト、タイル、塗りつぶし、自動ペイント)、植生、装飾オブジェクト、水、通行判定、マーカー。
  • 円形、正方形、ひし形、テクスチャスタンプの形状と、減衰カーブ、回転、間隔、ジッターを備えたブラシ。対称ペイント(X/Z ミラーリング、4 方向、2 回・4 回回転対称)。
  • メモリ上限付きの元に戻す・やり直し機能。Unity の元に戻す機能と連携。
  • ドッキング可能な RTS Terrain Studio ウィンドウ、投影ブラシカーソルとオーバーレイによるシーンビューとの連携、すべてのアセット用のカスタムインスペクターとインポーター、Welcome ウィンドウ、New Map Wizard、Project Validator。
  • UI Toolkit で構築されたゲーム内エディター:ツールバー、ツールパネルとブラシパネル、パレット、マップブラウザー、ホットキー、テストプレイ。

マップツール

  • シードに基づくプロシージャルマップジェネレーター:6 つのスタイル、対称レイアウト、スロープ、湖と島、マップ境界、自動ペイント、植生、装飾オブジェクト、開始地点に対応。プリセットの保存が可能。
  • 16 ビット PNG と RAW のハイトマップのインポート(3 つのモード)とエクスポート、スプラット画像のエクスポート。
  • ミニマップベイカー、マップ検証ツール、アンカー指定によるリサイズ。
  • エディターとゲームで共通のバイナリ .rtsmap 形式(カスタムセクション対応)。

ランタイムシステム

  • Burst ジョブで構築される、チャンク分割された地形メッシュ。オプションのメッシュコライダーと、物理演算を使わないクエリ API。
  • 風とカリングに対応した GPU インスタンシングによる植生。木はインスタンスまたはゲームオブジェクトとして配置可能。
  • 深度に応じた色と屈折による水のレンダリング。任意のプレハブに対応した装飾マネージャー。
  • クラシック RTS の戦場の霧:チームごとの可視状態、探索済みエリアの記憶、崖と視界遮蔽物に遮られる視線、視界共有、ミニマップ対応。
  • クリアランスを考慮したユニットサイズと経路の平滑化を備えた、組み込みの Burst A* 経路探索エンジン。
  • サンプル:RTS カメラ、選択、隊形付きの移動命令、テストユニット、ミニマップ UI、デモシーン、生成されたデモコンテンツ。

プラットフォーム

  • Shader Model 3.5 を対象とするシェーダーにより、このパッケージは OpenGL ES 3.0 と Web ビルド(WebGL 2.0)でも動作。
  • Web ビルド:ブラウザーのストレージへのマップの保存、Download Map、Import Map、ドラッグ&ドロップ、未保存の変更がある状態でページを離れる前の警告、ブラウザーに配慮したキーボードショートカット、ウィンドウ全体に表示する Web テンプレート、Project Validator の Web チェック。
  • プレイヤービルドの前に、ビルドのテクスチャ圧縮形式に合わせてタイルセットのテクスチャ配列を自動で再構築。
  • URP 17.3 がない場合、このパッケージはコンパイルエラーを表示する代わりに非アクティブのままとなり、URP のインストール方法を案内。

連携

  • Mirror:キャッシュ付きのマップストリーミング、サーバー権威型のユニット、チームごとの戦場の霧、戦場の霧によるインタレストマネジメント。
  • A* Pathfinding Project Pro:通行マップから生成され差分更新されるグリッドグラフ、AIPath と RVO のユニット。
  • Unity AI Navigation:地形と通行マップから構築される NavMesh、差分更新、経路プロバイダー、NavMeshAgent モード。