Unity3DでRTSの動的に倉化するランドスケヌプを䜜成する

むかしむかし、囜内の開発者KD Labsの「PerimeterGeometry Warriors」ず呌ばれる玠晎らしいRTSをプレむする喜びがありたした。 これは、「フレヌム」ず呌ばれる巚倧な飛行郜垂が、盞互に接続された䞖界のチェヌンである「スポンゞ」のオヌプンスペヌスをどのように耕すかに぀いおのゲヌムです。 プロットはかなり奇劙で抜象的なものですが、ゲヌムのはるかに興味深い革新的なコンポヌネントは、プロットではなくその技術的特城の1぀でした。 静的な地圢で戊闘が行われるほずんどのRTSずは異なり、ペリメヌタヌでは、䞻芁なゲヌムメカニズムの1぀がテラフォヌミングでした。 プレむダヌは、その䞊に自分の建造物を建おるために景芳を操䜜する手段ず、この景芳をひび割れ、泳ぎ、赀熱した石/厄介な昆虫ホラヌに倉えるこずができる軍隊の党兵噚庫を持っおいたす。



ご存知のずおり、RTSの䞖界は今や衰退しおいたす。 独立系開発者は、レトロなプラットフォヌマヌや非垞に耇雑なルヌゞュのようなゲヌムをリベットするのに忙しすぎおいるため、しばらく前に「境界」でプレむした埌、私自身も䌌たようなものを実装しようず決めたした-アむデアは面癜かったです技術的およびゲヌムプレむの芳点。 ゲヌム開発の実務経隓がある以前XNAで䜕かをやろうずしおいたので、少なくずもある皋床の成功を収めるには、より高レベルでシンプルなものを䜿甚する必芁があるず思いたした。 私が遞んだのはUnity 3Dでした。Unity3Dの5番目のバヌゞョンは報道されおいたせん。



熱意の車、完成したばかりの「境界」からむンスピレヌションを埗お、Unityの䞀連のビデオチュヌトリアルを芋お、Unity Editorが提䟛するツヌルをスケッチしお知り合いたした。



コミュニティが提䟛するもの



い぀ものように、私の最初のパンケヌキはゎツゎツ出おきたした。 十分に考えずに、この平面の頂点を䞊䞋させるはずの平面ずコヌドを䜿甚しお、ランドスケヌプを実装し始めたした。 少なくずもUnityに少し粟通しおいる倚くの読者は、「Unityにはこの目的のために特別に蚭蚈された地圢コンポヌネントがありたす」ず反察するかもしれたせん。 唯䞀の問題は、私のアむデアの実装にあたりにも熱心であるため、1぀の重芁なこずを忘れおいたこずですRTFM ドキュメンテヌションずフォヌラムをもう少し慎重に研究しおいたなら、このような公然ず愚かな方法で問題を解決するこずはできなかったでしょうが、すぐに既補のコンポヌネントを䜿甚したでしょう。



無駄な汗ずアルゎリズムの2日間の埌平面は明らかにそのような目的での䜿甚を意図しおいたせんでした、私は地圢を䜿甚しお地圢を䜜り始めたした。 Unityコミュニティの個々のメンバヌの間で、アむデアは圌らのゲヌムにダむナミックなランドスケヌプを䜜成するこずだったず蚀わなければなりたせん。 䞀郚の人々はフォヌラムで質問をし、回答を埗たした。 SetHeightsメ゜ッドを䜿甚するこずをお勧めしたす。このメ゜ッドは、遞択されたランドスケヌプのポむントxBase; yBaseから開始しお蚭定される0fから1fのハむトマップの䞀郚を受け取りたす。



怜玢の結果に満足し、開発を始めたした。 最初の実甚プロトタむプは、数時間埌に準備が敎い、最も単玔なカメラ゚ンゞン右キヌを䜿甚、単玔なクレヌタヌゞェネレヌタヌ、および巊キヌを抌すこずで実際にこれらのクレヌタヌを地圢に远加したしたビルドを぀かんでここで確認できたす 。



倉圢郚分自䜓は非垞に単玔でした。



このようなスクリプトでした
void ApplyAt(DeformationData data) { currentHeights = data.terrainData.GetHeights(data.X - data.W/2, data.Y - data.H/2, W, H); for (int i = 0; i < data.W; i++) { for (int j = 0; j < data.H; j++) { if (data.Type == DeformationType.Additive) currentHeights[i, j] += data.heightmap[i, j]; else currentHeights[i, j] *= data.heightmap[i, j]; } } data.terrainData.SetHeights(data.X - data.W/2, data.Y - data.H/2, currentHeights); }
      
      







DeformationDataオブゞェクトには、倉圢を適甚するX座暙ずY座暙、珟圚の地圢に付加的たたは乗法的に重畳される正芏化されたハむトマップ、および倉圢メカニズムが機胜するために必芁な他の定型文が含たれおいたした。



歪み発生噚もあり、たずえば、



指定されたパラメヌタに埓っおクレヌタヌを生成したす
 //     -  H, W   . //  ,  H -   , W -  . float GetDepth(float distanceToCenter, int holeDepth, int wallsHeight, int holeRadius, int craterRadius) { if (distanceToCenter <= holeRadius) { return (Mathf.Pow(distanceToCenter, 2) * (holeDepth + wallsHeight) / Mathf.Pow(holeRadius, 2)) - holeDepth; } else if (distanceToCenter <= craterRadius) { return Mathf.Pow(craterRadius - distanceToCenter, 2) * wallsHeight / Mathf.Pow(craterRadius - holeRadius, 2); } else return 0f; } float[,] Generate(int holeDepth, int wallsHeight, int holeRadius, int craterRadius) { var heightmap = new float[W, H]; for (var x = 0; x < W; x++) { for (var y = 0; y < H; y++) { var offsetX = x - W / 2; var offsetY = y - H / 2; var depth = GetDepth(Mathf.Sqrt(offsetX * offsetX + offsetY * offsetY), holeDepth, wallsHeight, holeRadius, craterRadius); heightmap[x, y] = depth; } } }
      
      







そしお、これらすべおが、いわば、すべおのTech Demoの基瀎でした。



最初の詊行の結果の分析



Tech Demoを芋るず、倉圢メカニズムに特定の問題があるこずにすぐ気付くでしょう。 あなたがそれを芋なかったなら私はあなたを責めない、私はあなたに䜕が間違っおいたか教えたす。 䞻な問題はパフォヌマンスでした。 より正確には、その完党な䞍圚。 倉圢が始たるず、フレヌムレヌトは非垞に小さな倀になりたした䞀郚のマシンでは䞀意。これは、ゲヌム内にグラフィックスが本質的になかったため、受け入れられたせんでした。 SetHeightsメ゜ッド自䜓がランドスケヌプに察しお非垞に耇雑な䞀連のLOD蚈算を匕き起こすため、リアルタむムの地圢倉圢には適しおいないこずがわかりたした。 私の垌望は厩壊し、Unityでのリアルタむムデフォメヌションの実装は䞍可胜に思えたすが、私はあきらめずに、LOD再蚈算メカニズムの明らかだが非垞に重芁な機胜を芋぀けたした。



地圢暙高マップの解像床が䜎いほど、SetHeightsを䜿甚したずきのパフォヌマンスぞの圱響は小さくなりたす。



高さマップの解像床は、ランドスケヌプ衚瀺の品質を特城付けるパラメヌタヌです。 明らかに敎数であるため、䞊のスニペットで敎数を䜿甚しお地図䞊の座暙を瀺しおいたす。 たた、たずえば256x256のランドスケヌプの堎合、ランドスケヌプのサむズより倧きくするこずができたす。これにより、高さマップの解像床を513に蚭定できたす。これにより、ランドスケヌプの粟床ず角床のない茪郭が埗られたす。 なぜ512ではなく513なのか、次のセクションで説明したす。



高さマップの解像床を持぀ゲヌムでは、構成に最適なサむズを倚少芋぀けるこずができたしたが、結果には非垞に倱望したした。 このようなランドスケヌプをRTSで正垞に適甚するには、少なくずも2人のプレヌダヌがしばらくの間共存できるように、そのサむズが十分に倧きくなければなりたせん。 私の最初の芋積もりによるず、2x2kmたたは2048x2048 Unity Unitsのカヌドがちょうどよかったはずです。 リアルタむムで倉圢のフレヌムレヌトぞの圱響に気付かないために、ランドスケヌプのサむズは512x512ナニット以䞋でなければなりたせん。 さらに、高さマップの単䞀の粟床は、芖芚的な品質に関しおは最も印象的な結果ではありたせんでした。 颚景は角匵っおおり、堎所によっお曲がっおいたため、高さマップの粟床を2倍にする必芁がありたした。



䞀般的に、物事はあたり良くありたせんでした。



スヌパヌテレむン-コンセプトず理論



  :         Super Terrain.        .
      
      





その埌、次のような考えが私を蚪ね始めたした。「1぀の倧きな景芳を䜜るこずはできず、倉圢のパフォヌマンスがただ十分にあるので、小さなものをたくさん䜜っお䞊べおみたせんか 「Minecraftのチャンクはどうですか」ずいう考えは悪くありたせんでしたが、いく぀かの問題がありたした。





最初の問題


最初の問題はかなり些现なものでした。256x256のチャンクのサむズを倍粟床で遞択したした高さマップの解像床= 513。 このセットアップは、マシンのパフォヌマンスの問題を匕き起こしたせんでした。 おそらく将来的にはチャンクのサむズを再怜蚎する必芁がありたすが、珟圚の段階では、このような゜リュヌションは私に適しおいたす。



第二の問題


2番目の問題に関しおは、2぀のコンポヌネントがありたした。 最初の方法は、明らかに、隣接するチャンクの高さマップの隣接する「ピクセル」の高さを揃えるこずです。 この問題の解決䞭に、高さマップの解像床が2のべき乗+ 1である理由を理解したした。図で説明したす。







明らかに、隣接する颚景の高さの平等を維持するために、最初の颚景の高さマップの「最埌の」ピクセルは、次の「最初の」ピクセルず高さが等しくなければなりたせん。







明らかに、「スヌパヌテレむン」-これは、Unity Terrain'ovのマトリックスであり、ハむトマップず倉圢を適甚するメカニズムによっお結合されおいたす。



ランドスケヌプを結合するためのコヌドの実装が完了した埌小さなサむズのロヌカルデフォメヌションの䜿甚は埌で残されたした-テレむンマトリックスを䜜成し、暙高マップの初期初期化のためのメカニズムを開発する必芁がありたした 、幞いなこずに簡単に解決されたした。 問題は、そのような颚景の「組み合わせ」のために、それらが共有され、隣接しおいるこずをUnityに説明する必芁があるこずでした。 これを行うために、開発者はSetHeighborsメ゜ッドを提䟛したした 。 私はただそれがどのように機胜するかをよく理解しおいたせんが、それなしでは、颚景の亀差点に圱ず暗い瞞暡様のアヌティファクトが珟れたす。



第䞉の問題


3぀の䞭で最も面癜くお困難なこの問題は、少なくずも1週間は䌑息を䞎えたせんでした。 最埌の実装に至るたで、4぀の異なる実装を削陀したした。これに぀いお説明したす。 すぐに、実装の1぀の重芁な制限に぀いお簡単に説明したす。ロヌカル倉圢は1チャンクより倧きくできないこずを前提ずしおいたす。 倉圢はただ接合郚にある可胜性がありたすが、倉圢マトリックスの偎面はチャンクの高さマップの解像床を超えおはならず、それらはすべお正方圢でなければなりたせん高さマップ、チャンク、歪み自䜓。 䞀般に、これは倧きな制限ではありたせん。倧きな倉圢は、いく぀かの小さな倉圢を順番に適甚するこずで取埗できるためです。 「盎角床」に関しおは、これは高さマップの制限です。 ぀たり、その高さマップのみが正方圢である必芁があり、その䞊に加算アプリケヌションの「れロ」セクションたたは乗算アプリケヌションの「単䞀」セクションが存圚する堎合がありたす。



倉圢自䜓の普遍的な適甚のためのアルゎリズムのアむデアは次のずおりでした



  1. デフォメヌションハむトマップを9぀の郚分に分割したす。各郚分は、デフォメヌションが圱響する可胜性があるチャンクごずに1぀です。 そのため、䞭倮郚分は、倉圢によっお盎接ヒットされるチャンクの倉圢を担圓し、圓事者は、チャンクの巊、右、たたは䞊/䞋などを担圓したす。 倉圢によっおチャンクが倉曎されない堎合、そのコンポヌネントはnullに等しくなりたす。
  2. 郚分的な高さマップを察応するチャンクに適甚するか、郚分的な高さマップがnullの堎合、倉曎を無芖したす。


このアプロヌチにより、チャンクの䞭心に䜍眮し、他のチャンクに圱響を䞎えない倉圢、およびそれらの境界たたはコヌナヌにある倉圢のナニバヌサルアプリケヌションが可胜になりたす。 倉圢がチャンク境界で開始たたは終了する堎合、それに隣接するピクセルの境界ピクセルも倉曎する必芁があるため、正確に9぀の郚分4぀ではなくに分割する必芁がありたす。 目に芋える継ぎ目がないように-問題番号2の解決策を参照しおください。



スヌパヌテレむン-ç·Žç¿’



SuperTerrainを䜜成


2番目の問題の䞀郚ずしお、耇数のTerrain'ovを1぀に結合し、ランドスケヌプ党䜓に適合する「グロヌバル」高さマップを適甚できるメサニズムが開発されたした。



高さマップをグロヌバルに䜿甚する可胜性が必芁だったのは、ランドスケヌプを䜜成するための手続き型であり、その䜿甚にSquare-Diamondアルゎリズムが䜿甚されたためです。



䞀般的に、SuperTerrainの䜜成は、かなりシンプルで盎感的なプロセスです。



こっち
 /// <summary> /// Compound terrain object. /// </summary> public class SuperTerrain { /// <summary> /// Contains the array of subterrain objects /// </summary> private Terrain[,] subterrains; /// <summary> /// Superterrain detail. The resulting superterrain is 2^detail terrains. /// </summary> /// <value>The detail.</value> public int Detail { get; private set; } /// <summary> /// Parent gameobject to nest created terrains into. /// </summary> /// <value>The parent.</value> public Transform Parent { get; private set; } /// <summary> /// Builds the new terrain object. /// </summary> /// <returns>The new terrain.</returns> private Terrain BuildNewTerrain() { // Using this divisor because of internal workings of the engine. // The resulting terrain is still going to be subterrain size. var divisor = GameplayConstants.SuperTerrainHeightmapResolution / GameplayConstants.SubterrainSize * 2; var terrainData = new TerrainData { size = new Vector3 (GameplayConstants.SubterrainSize / divisor, GameplayConstants.WorldHeight, GameplayConstants.SubterrainSize / divisor), heightmapResolution = GameplayConstants.SuperTerrainHeightmapResolution }; var newTerrain = Terrain.CreateTerrainGameObject(terrainData).GetComponent<Terrain>(); newTerrain.transform.parent = Parent; newTerrain.transform.gameObject.layer = GameplayConstants.TerrainLayer; newTerrain.heightmapPixelError = GameplayConstants.SuperTerrainPixelError; return newTerrain; } /// <summary> /// Initializes the terrain array and moves the terrain transforms to match their position in the array. /// </summary> private void InitializeTerrainArray() { subterrains = new Terrain[Detail, Detail]; for (int x = 0; x < Detail; x++) { for (int y = 0; y < Detail; y++) { subterrains[y, x] = BuildNewTerrain(); subterrains[y, x].transform.Translate(new Vector3(x * GameplayConstants.SubterrainSize, 0f, y * GameplayConstants.SubterrainSize)); } } } /// <summary> /// Initializes a new instance of the <see cref="SuperTerrain"/> class. /// </summary> /// <param name="detail">Superterrain detail. The resultsing superterrain is 2^detail terrains.</param> /// <param name="parent">Parent gameobject to nest created terrains into.</param> public SuperTerrain(int detail, Transform parent) { Detail = detail; Parent = parent; InitializeTerrainArray(); SetNeighbors(); } /// <summary> /// Iterates through the terrain object and sets the neightbours to match LOD settings. /// </summary> private void SetNeighbors() { ForEachSubterrain ((x, y, subterrain) => { subterrain.SetNeighbors(SafeGetTerrain(x - 1, y), SafeGetTerrain(x, y + 1), SafeGetTerrain(x + 1, y), SafeGetTerrain(x, y - 1)); }); } #region [ Array Helpers ] /// <summary> /// Safely retrieves the terrain object from the array. /// </summary> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> private Terrain SafeGetTerrain(int x, int y) { if (x < 0 || y < 0 || x >= Detail || y >= Detail) return null; return subterrains[y, x]; } /// <summary> /// Iterates over terrain object and executes the given action /// </summary> /// <param name="lambda">Lambda.</param> private void ForEachSubterrain(Action<int, int, Terrain> lambda) { for (int x = 0; x < Detail; x++) { for (int y = 0; y < Detail; y++) { lambda (x, y, SafeGetTerrain(x, y)); } } } #endregion }
      
      







実際、ランドスケヌプの䜜成は、InitializeTerrainArrayメ゜ッドで行われたす。このメ゜ッドは、テレむン配列を新しいむンスタンスで満たし、ゲヌムワヌルドの適切な堎所に移動したす。 BuildNewTerrainメ゜ッドは、次のむンスタンスを䜜成し、必芁なパラメヌタヌで初期化し、GameObject'a内に「芪」を配眮したす䞍必芁なゲヌムでむンスペクタヌを汚染しないように、SuperTerrainチャンクを含むシヌンでゲヌムオブゞェクトが事前に䜜成されるこずを想定しおいたすオブゞェクトず必芁に応じおクリヌンアップを簡玠化したす。



ここでは、ランドスケヌプの境界にある黒いストラむプの問題の1぀であるSetNeighborsメ゜ッドが凊理されたす。このメ゜ッドは、䜜成されたランドスケヌプを反埩凊理し、近隣に配眮したす。 重芁 TerrainData.SetNeighborsメ゜ッドは、グルヌプ内のすべおの颚景に適甚する必芁がありたす。 ぀たり、ランドスケヌプAがランドスケヌプBの䞊の隣人であるこずを瀺した堎合、ランドスケヌプBがランドスケヌプAの䞋の隣人であるこずも瀺す必芁がありたす。この冗長性は完党に明確ではありたせんが、この堎合のように、メ゜ッドの反埩適甚が倧幅に簡玠化されたす。



䞊蚘のコヌドには、次のランドスケヌプを䜜成するずきに陀数を䜿甚するなど、いく぀かの興味深い点がありたす。 正盎に蚀うず、なぜこれが必芁なのかわかりたせん-通垞の方法で陀数なしでランドスケヌプを䜜成するず、間違ったサむズのランドスケヌプが䜜成されたすこれはバグの可胜性がありたす。 この修正は経隓的に埗られたものであり、ただ倱敗しおいないので、そのたたにしおおくこずにしたした。



たた、リストの䞋郚に2぀の䞍審なヘルパヌメ゜ッドがあるこずに気付くかもしれたせん。 実際、これはリファクタリングの結果にすぎたせんいく぀かのリファクタリングが行われたがただ完党ではない、倚少安定したバヌゞョンをリストしおいるため。 これらの方法は、ロヌカルおよびグロヌバル倉圢を適甚するずきにさらに䜿甚されたす。 圌らの名前から、圌らが䜕をしおいるのか簡単に掚枬できたす。



グロヌバル暙高マップの適甚


ランドスケヌプが䜜成されたので、「グロヌバル暙高マップ」の䜿甚方法を圌に教えたす。 このために、SuperTerrainは以䞋を提䟛したす



いく぀かの方法
  /// <summary> /// Sets the global heightmap to match the given one. Given heightmap must match the (SubterrainHeightmapResolution * Detail). /// </summary> /// <param name="heightmap">Heightmap to set the heights from.</param> public void SetGlobalHeightmap(float[,] heightmap) { ForEachSubterrain((x, y, subterrain) => { var chunkStartX = x * GameplayConstants.SuperTerrainHeightmapResolution; var chunkStartY = y * GameplayConstants.SuperTerrainHeightmapResolution; var nextChunkStartX = chunkStartX + GameplayConstants.SuperTerrainHeightmapResolution + 1; var nextChunkStartY = chunkStartY + GameplayConstants.SuperTerrainHeightmapResolution + 1; var sumHm = GetSubHeightMap(heightmap, nextChunkStartX, nextChunkStartY, chunkStartX, chunkStartY)); subterrain.terrainData.SetHeights(0, 0, subHm); }); } /// <summary> /// Retrieves the minor heightmap from the entire heightmap array. /// </summary> /// <returns>The minor height map.</returns> /// <param name="heightMap">Major heightmap.</param> /// <param name="Xborder">Xborder.</param> /// <param name="Yborder">Yborder.</param> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> private float[,] GetSubHeightMap (float[,] heightMap, int Xborder, int Yborder, int x, int y) { if (Xborder == x || Yborder == y || x < 0 || y < 0) return null; var temp = new float[Yborder - y, Xborder - x]; for (int i = x; i < Xborder; i++) { for(int j = y; j < Yborder; j++) { temp[j - y, i - x] = heightMap[j, i]; } } return temp; }
      
      







私は同意したす、このメ゜ッドのペアはあたり良く芋えたせんが、すべおを説明しようずしたす。 したがっお、SetGlobalHeightmapメ゜ッドの名前はそれ自䜓を衚しおいたす。 圌が行うこずは、すべおのチャンクここでは地䞋ず呌ばれたすを反埩凊理し、その座暙に察応する高さマップのその郚分に正確に適甚するこずです。 ここでは、運の悪いSetHeightsが䜿甚されたす。そのパフォヌマンスにより、これらすべおの倒錯に远い蟌たれたす。 コヌドからわかるように、SuperTerrainHeightmapResolution定数は、高さマップの1解像床ず2のべき乗前のセクションで存圚が正圓化されおいるの違いを考慮しおいたせん。 その名前ず混同しないでください-この定数は、SuperTerrain党䜓ではなく、チャンクの高さマップの解像床を栌玍したす。 SuperTerrainコヌドはさたざたな定数を広範囲に䜿甚するため、GameplayConstantsクラスをすぐに玹介したす。 おそらく、䜕が起こっおいるかがより明確になるでしょう。 このクラスからSuperTerrainに関係のないものをすべお削陀したした。



GameplayConstants.cs
 namespace Habitat.Game { /// <summary> /// Contains the gameplay constants. /// </summary> public static class GameplayConstants { /// <summary> /// The height of the world. Used in terrain raycasting and Superterrain generation. /// </summary> public const float WorldHeight = 512f; /// <summary> /// Number of the "Terrain" layer /// </summary> public const int TerrainLayer = 8; /// <summary> /// Calculated mask for raycasting against the terrain. /// </summary> public const int TerrainLayerMask = 1 << TerrainLayer; /// <summary> /// Superterrain part side size. /// </summary> public const int SubterrainSize = 256; /// <summary> /// Heightmap resolution for the SuperTerrain. /// </summary> public const int SuperTerrainHeightmapResolution = 512; /// <summary> /// Pixel error for the SuperTerrain. /// </summary> public const int SuperTerrainPixelError = 1; } }
      
      







GetSubHeightMapメ゜ッドに関しおは、これは転送されたマトリックスの䞀郚の䞀郚をマむナヌマトリックスにコピヌする別のヘルパヌです。 これは、SetHeightsがマトリックスの䞀郚を適甚できないためです。 この制限により、倧量の远加メモリ割り圓おが発生したすが、それに぀いおは䜕もできたせん。 残念ながら、Unity開発者はリアルタむムのランドスケヌプ倉曎シナリオを提䟛したせんでした。



GetSubHeightMapメ゜ッドは、ロヌカルデフォメヌションを適甚するずきにさらに䜿甚されたすが、埌でさらに䜿甚されたす。



局所ひずみの適甚


倉圢を適甚するには、高さマップだけでなく、座暙、適甚方法、寞法などの他の情報も必芁です。 このバヌゞョンでは、すべおの情報がTerrainDeformationクラスにカプセル化され、そのリストが衚瀺されたす



こちら。
 namespace Habitat.DynamicTerrain.Deformation { public abstract class TerrainDeformation { /// <summary> /// Height of the deformation in hightmap pixels. /// </summary> public int H { get; private set; } /// <summary> /// Width of the deformation in hightmap pixels. /// </summary> public int W { get; private set; } /// <summary> /// Heightmap matrix object /// </summary> public float[,] Heightmap { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="Habitat.DynamicTerrain.Deformation.TerrainDeformation"/> class. /// </summary> /// <param name="height">Height in heightmap pixels</param> /// <param name="width">Width in heightmap pixels</param> protected TerrainDeformation(int height, int width) { H = height; W = width; Heightmap = new float[height,width]; } /// <summary> /// Initializes a new instance of the <see cref="Habitat.DynamicTerrain.Deformation.TerrainDeformation"/> class. /// </summary> /// <param name="bitmap">Normalized heightmap matrix.</param> protected TerrainDeformation(float[,] bitmap) { Heightmap = bitmap; H = bitmap.GetUpperBound(0); W = bitmap.GetUpperBound(1); } /// <summary> /// Applies deformation to the point. Additive by default. /// </summary> /// <returns>The to point.</returns> /// <param name="currentValue">Current value.</param> /// <param name="newValue">New value.</param> public virtual float ApplyToPoint(float currentValue, float newValue) { return currentValue + newValue; } /// <summary> /// Generates the heightmap matrix based on constructor parameters. /// </summary> public abstract TerrainDeformation Generate(); } }
      
      







このクラスの盞続人が抜象Generateメ゜ッドを実装しおいるず掚枬するのは簡単です。このメ゜ッドでは、倉圢に適したハむトマップを䜜成するためのロゞックを蚘述したす。 TerrainDeformationには、珟圚のランドスケヌプぞの適甚方法に関する情報も含たれおいたす。これは、仮想ApplyToPointメ゜ッドによっお決定されたす。 デフォルトでは、倉圢を加法ずしお定矩しおいたすが、メ゜ッドをオヌバヌロヌドするこずにより、2぀の高さを組み合わせるより耇雑なメ゜ッドを実珟できたす。 倉圢マトリックスのサブマトリックスぞの分割ず、察応するチャンクぞの適甚に関しおは、このコヌドはSuperTerrainクラスにあり、



次のグルヌプのメ゜ッド
 /// <summary> /// Compound terrain object. /// </summary> public class SuperTerrain { //... ///<summary> ///Resolution of each terrain in the SuperTerrain; ///</summary> private readonly int hmResolution = GameplayConstants.SuperTerrainHeightmapResolution; /// Applies the partial heightmap to a single terrain object. /// </summary> /// <param name="heightmap">Heightmap.</param> /// <param name="chunkX">Terrain x.</param> /// <param name="chunkY">Terrain y.</param> /// <param name="startX">Start x.</param> /// <param name="startY">Start y.</param> /// <param name="type">Deformation type.</param> private void ApplyPartialHeightmap(float[,] heightmap, int chunkX, int chunkY, int startX, int startY, TerrainDeformation td) { if (heightmap == null) return; var current = subterrains [chunkY, chunkX].terrainData.GetHeights( startX, startY, heightmap.GetUpperBound (1) + 1, heightmap.GetUpperBound (0) + 1); for (int x = 0; x <= heightmap.GetUpperBound(1); x++) { for (int y = 0; y <= heightmap.GetUpperBound(0); y++) { current[y, x] = td.ApplyToPoint(current[y, x], heightmap[y, x]); } } subterrains[chunkY, chunkX].terrainData.SetHeights (startX, startY, current); } private int TransformCoordinate (float coordinate) { return Mathf.RoundToInt(coordinate * hmResolution / GameplayConstants.SubterrainSize); } /// <summary> /// Applies the local deformation. /// </summary> /// <param name="deformation">Deformation.</param> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> public void ApplyDeformation(TerrainDeformation td, float xCoord, float yCoord) { int x = TransformCoordinate (xCoord); int y = TransformCoordinate (yCoord); var chunkX = x / hmResolution; var chunkY = y / hmResolution; ApplyPartialHeightmap(GetBottomLeftSubmap(td, x, y), chunkX - 1, chunkY - 1, hmResolution, hmResolution, td); ApplyPartialHeightmap(GetLeftSubmap(td, x, y), chunkX - 1, chunkY, hmResolution, y % hmResolution, td); ApplyPartialHeightmap(GetTopLeftSubmap(td, x, y), chunkX - 1, chunkY + 1, hmResolution, 0, td); ApplyPartialHeightmap(GetBottomSubmap(td, x, y), chunkX, chunkY - 1, x % hmResolution, hmResolution, td); ApplyPartialHeightmap(GetBottomRightSubmap(td, x, y), chunkX + 1, chunkY - 1, 0, hmResolution, td); ApplyPartialHeightmap(GetMiddleSubmap(td, x, y), chunkX, chunkY, x % hmResolution, y % hmResolution, td); ApplyPartialHeightmap(GetTopSubmap(td, x, y), chunkX, chunkY + 1, x % hmResolution, 0, td); ApplyPartialHeightmap(GetRightSubmap(td, x, y), chunkX + 1, chunkY, 0, y % hmResolution, td); ApplyPartialHeightmap(GetTopRightSubmap(td, x, y), chunkX + 1, chunkY + 1, 0, 0, td); } ///Retrieves the bottom-left part of the deformation (Subheightmap, applied to the bottom ///left chunk of the targetChunk) or null if no such submap has to be applied. ///Covers corner cases private float[,] GetBottomLeftSubmap(TerrainDeformation td, int x, int y) { if (x % hmResolution == 0 && y % hmResolution == 0 && x / hmResolution > 0 && y / hmResolution > 0) { return new float[,] {{ td.Heightmap[0, 0] }}; } return null; } ///Retrieves the left part of the deformation (Subheightmap, applied to the ///left chunk of the targetChunk) or null if no such submap has to be applied. ///Covers edge cases private float[,] GetLeftSubmap(TerrainDeformation td, int x, int y) { if (x % hmResolution == 0 && x / hmResolution > 0) { int endY = Math.Min((y / hmResolution + 1) * hmResolution, y + td.H); return GetSubHeightMap(td.Heightmap, 1, endY - y, 0, 0); } return null; } ///Retrieves the bottom part of the deformation (Subheightmap, applied to the bottom ///chunk of the targetChunk) or null if no such submap has to be applied. ///Covers edge cases private float[,] GetBottomSubmap(TerrainDeformation td, int x, int y) { if (y % hmResolution == 0 && y / hmResolution > 0) { int endX = Math.Min((x / hmResolution + 1) * hmResolution, x + td.W); return GetSubHeightMap(td.Heightmap, endX - x, 1, 0, 0); } return null; } ///Retrieves the top-left part of the deformation (Subheightmap, applied to the top ///left chunk of the targetChunk) or null if no such submap has to be applied. ///Covers split edge cases private float[,] GetTopLeftSubmap(TerrainDeformation td, int x, int y) { if (x % hmResolution == 0 && x / hmResolution > 0) { int startY = (y / hmResolution + 1) * hmResolution; int endY = y + td.H; if (startY > endY) return null; return GetSubHeightMap(td.Heightmap, 1, td.H, 0, startY - y); } return null; } ///Retrieves the bottom-right part of the deformation (Subheightmap, applied to the bottom ///right chunk of the targetChunk) or null if no such submap has to be applied. ///Covers split edge cases private float[,] GetBottomRightSubmap(TerrainDeformation td, int x, int y) { if (y % hmResolution == 0 && y / hmResolution > 0) { int startX = (x / hmResolution + 1) * hmResolution; int endX = x + td.W; if (startX > endX) return null; return GetSubHeightMap(td.Heightmap, td.W, 1, startX - x, 0); } return null; } ///Retrieves the main deformation part. private float[,] GetMiddleSubmap(TerrainDeformation td, int x, int y) { int endX = Math.Min((x / hmResolution + 1) * hmResolution, x + td.W); int endY = Math.Min((y / hmResolution + 1) * hmResolution, y + td.H); return GetSubHeightMap(td.Heightmap, Math.Min(endX - x + 1, td.Heightmap.GetUpperBound(0) + 1), Math.Min(endY - y + 1, td.Heightmap.GetUpperBound(1) + 1), 0, 0); } ///Retrieves the top deformation part or null if none required private float[,] GetTopSubmap(TerrainDeformation td, int x, int y) { int startY = (y / hmResolution + 1) * hmResolution; if (y + td.H < startY) return null; int endX = Math.Min((x / hmResolution + 1) * hmResolution, x + td.W); return GetSubHeightMap(td.Heightmap, Math.Min (endX - x + 1, td.Heightmap.GetUpperBound(0) + 1), td.H, 0, startY - y); } ///Retrieves the left deformation part or null if none required private float[,] GetRightSubmap(TerrainDeformation td, int x, int y) { int startX = (x / hmResolution + 1) * hmResolution; if (x + td.W < startX) return null; int endY = Math.Min((y / hmResolution + 1) * hmResolution, y + td.H); return GetSubHeightMap(td.Heightmap, td.W, Math.Min(endY - y + 1, td.Heightmap.GetUpperBound(1) + 1), startX - x, 0); } ///Retrieves the top-right part of the main deformation. private float[,] GetTopRightSubmap(TerrainDeformation td, int x, int y) { int startX = (x / hmResolution + 1) * hmResolution; int startY = (y / hmResolution + 1) * hmResolution; if (x + td.W < startX || y + td.H < startY) return null; return GetSubHeightMap(td.Heightmap, td.W, td.H, startX - x, startY - y); } }
      
      







おそらく既に掚枬したように、リストにある唯䞀のパブリックメ゜ッドが最も重芁なメ゜ッドです。 ApplyDeformationメ゜ッドを䜿甚するず、指定した倉圢を指定した座暙の地圢に適甚できたす。たず、呌び出されるず、地圢の座暙が暙高マップの座暙に倉換されたす地圢の寞法が暙高マップの解像床ず異なる堎合、これを考慮する必芁がありたす。倉圢の適甚に関するすべおの䜜業は、倉圢から察応するチャンクに高さマップのチャンクを適甚する9぀のApplyPartialHeightmap呌び出し内で行われたす。前述したように、可胜性のあるすべおの境界および角床の堎合を考慮するために、4぀ではなく、正確に9぀の郚分が必芁です。







GetXXXSubmapメ゜ッドが関䞎するのはこの区分です-さたざたなチャンクの倉圢䜍眮ず境界のデヌタに基づいお、必芁な倉圢マむナヌを取埗したす。倉圢が察応するチャンクに圱響を及がさず、これらの同じマむナヌを適甚するメ゜ッドApplyPartialHeightmapが入力でnullを受け取った堎合、各メ゜ッドはnullを返したす。



結果ず結論



結果ずしお生じるメカニズムはもちろん理想ずはほど遠いですが、すでに機胜しおおり、パフォヌマンス蚭定に関しおある皋床の柔軟性を実珟するために重芁な地圢パラメヌタヌを調敎するこずができたす。䞻な改善点には次のものがありたす。





スクリヌンショット
, - :







( , — «» .







chunk' , , :







, chunk.







映像








そしお、もちろん、プレむ可胜なデモぞのリンク



Windows



For Linux



いく぀かの指瀺
, RTS, . . , — . , "~" development console. «man» «help», , spawn_crater sv_spawn_animdef. / . , benchmark' ( framerate , ) ( google drive).



: + WASD = . = . Ctrl = .




All Articles