Хочу поделиться полезным инструментом для создания мультитеррейнов непосредственно в Unity.
Синтаксис:
Используется javascript
import UnityEngine.GUILayout;
import UnityEditor.EditorGUILayout;
enum Direction {Across, Down}
class Stitch extends ScriptableWizard {
static var across : int;
static var down : int;
static var tWidth : int;
static var tHeight : int;
static var totalTerrains : int;
static var terrains : Object[];
static var stitchWidth : int;
static var message : String;
static var terrainRes : int;
static var lineTex : Texture2D;
@MenuItem ("Terrain/Stitch...")
static function CreateWizard () {
if (lineTex == null) { // across/down etc. defined here, so closing and re-opening wizard doesn't reset vars
across = down = tWidth = tHeight = 2;
stitchWidth = 10;
SetNumberOfTerrains();
lineTex = EditorGUIUtility.whiteTexture;
}
message = "";
ScriptableWizard.DisplayWizard("Stitch Terrains", Stitch);
}
function OnGUI () {
BeginHorizontal(Width(220));
BeginVertical();
BeginHorizontal(Width(190));
Label("Number of terrains across:");
across = Mathf.Max(IntField(across, Width(30)), 1);
EndHorizontal();
BeginHorizontal(Width(190));
Label("Number of terrains down:");
down = Mathf.Max(IntField(down, Width(30)), 1);
EndHorizontal();
EndVertical();
BeginVertical();
Space(12);
if (Button("Apply")) {
tWidth = across;
tHeight = down;
SetNumberOfTerrains();
}
EndVertical();
EndHorizontal();
Space(16);
var counter = 0;
for (h = 0; h < tHeight; h++) {
BeginHorizontal();
Space(12);
for (w = 0; w < tWidth; w++) {
terrains[counter] = ObjectField(terrains[counter++], Terrain, Width(112));
Space(5);
}
EndHorizontal();
Space(10);
}
DrawGrid(Color.black, 1);
DrawGrid(Color.white, 0);
Space(10);
BeginHorizontal();
Label("Stitch width: " + stitchWidth, Width(90));
stitchWidth = HorizontalSlider(stitchWidth, 1, 100);
EndHorizontal();
Space(2);
Label(message);
Space(2);
BeginHorizontal();
if (Button("Clear")) {
SetNumberOfTerrains();
}
if (Button("Stitch")) {
StitchTerrains();
}
EndHorizontal();
}
private function DrawGrid (color : Color, offset : int) {
GUI.color = color;
for (i = 0; i < tHeight+1; i++) {
GUI.DrawTexture(Rect(5 + offset, 63 + offset + 28*i, 121*tWidth, 1), lineTex);
}
for (i = 0; i < tWidth+1; i++) {
GUI.DrawTexture(Rect(5 + offset + 121*i, 63 + offset, 1, 28*tHeight + 1), lineTex);
}
}
static function SetNumberOfTerrains () {
terrains = new Object[tWidth * tHeight];
totalTerrains = tWidth * tHeight;
message = "";
}
static function StitchTerrains () {
for (t in terrains) {
if (t == null) {
message = "All terrain slots must have a terrain assigned";
return;
}
}
terrainRes = terrains[0].terrainData.heightmapWidth;
if (terrains[0].terrainData.heightmapHeight != terrainRes) {
message = "Heightmap width and height must be the same";
return;
}
for (t in terrains) {
if (t.terrainData.heightmapWidth != terrainRes || t.terrainData.heightmapHeight != terrainRes) {
message = "All heightmaps must be the same resolution";
return;
}
}
if (tWidth == 1 && tHeight == 1) {
message = "Only one terrain specified...nothing to stitch";
return;
}
for (t in terrains) {
Undo.RegisterUndo(t.terrainData, "Stitch");
}
stitchWidth = Mathf.Clamp(stitchWidth, 1, (terrainRes-1)/2);
var counter = 0;
var total = tHeight*(tWidth-1) + (tHeight-1)*tWidth;
for (h = 0; h < tHeight; h++) {
for (w = 0; w < tWidth-1; w++) {
EditorUtility.DisplayProgressBar("Stitching...", "", Mathf.InverseLerp(0, total, ++counter));
BlendData (terrains[h*tWidth + w], terrains[h*tWidth + w + 1], Direction.Across);
}
}
for (h = 0; h < tHeight-1; h++) {
for (w = 0; w < tWidth; w++) {
EditorUtility.DisplayProgressBar("Stitching...", "", Mathf.InverseLerp(0, total, ++counter));
BlendData (terrains[h*tWidth + w], terrains[(h+1)*tWidth + w], Direction.Down);
}
}
message = "Terrains stitched successfully";
EditorUtility.ClearProgressBar();
}
static function BlendData (terrain1 : Terrain, terrain2 : Terrain, thisDirection : Direction) {
var heightmapData = terrain1.terrainData.GetHeights(0, 0, terrainRes, terrainRes);
var heightmapData2 = terrain2.terrainData.GetHeights(0, 0, terrainRes, terrainRes);
var pos = terrainRes-1;
if (thisDirection == Direction.Across) {
for (i = 0; i < terrainRes; i++) {
for (j = 1; j < stitchWidth; j++) {
var mix = Mathf.Lerp(heightmapData[i, pos-j], heightmapData2[i, j], .5);
if (j == 1) {
heightmapData[i, pos] = mix;
heightmapData2[i, 0] = mix;
}
var t = Mathf.SmoothStep(0.0, 1.0, Mathf.InverseLerp(1, stitchWidth-1, j));
heightmapData[i, pos-j] = Mathf.Lerp(mix, heightmapData[i, pos-j], t);
heightmapData2[i, j] = Mathf.Lerp(mix, heightmapData2[i, j], t);
}
}
}
else {
for (i = 0; i < terrainRes; i++) {
for (j = 1; j < stitchWidth; j++) {
mix = Mathf.Lerp(heightmapData2[pos-j, i], heightmapData[j, i], .5);
if (j == 1) {
heightmapData2[pos, i] = mix;
heightmapData[0, i] = mix;
}
t = Mathf.SmoothStep(0.0, 1.0, Mathf.InverseLerp(1, stitchWidth-1, j));
heightmapData2[pos-j, i] = Mathf.Lerp(mix, heightmapData2[pos-j, i], t);
heightmapData[j, i] = Mathf.Lerp(mix, heightmapData[j, i], t);
}
}
}
terrain1.terrainData.SetHeights(0, 0, heightmapData);
terrain2.terrainData.SetHeights(0, 0, heightmapData2);
}
}
import UnityEditor.EditorGUILayout;
enum Direction {Across, Down}
class Stitch extends ScriptableWizard {
static var across : int;
static var down : int;
static var tWidth : int;
static var tHeight : int;
static var totalTerrains : int;
static var terrains : Object[];
static var stitchWidth : int;
static var message : String;
static var terrainRes : int;
static var lineTex : Texture2D;
@MenuItem ("Terrain/Stitch...")
static function CreateWizard () {
if (lineTex == null) { // across/down etc. defined here, so closing and re-opening wizard doesn't reset vars
across = down = tWidth = tHeight = 2;
stitchWidth = 10;
SetNumberOfTerrains();
lineTex = EditorGUIUtility.whiteTexture;
}
message = "";
ScriptableWizard.DisplayWizard("Stitch Terrains", Stitch);
}
function OnGUI () {
BeginHorizontal(Width(220));
BeginVertical();
BeginHorizontal(Width(190));
Label("Number of terrains across:");
across = Mathf.Max(IntField(across, Width(30)), 1);
EndHorizontal();
BeginHorizontal(Width(190));
Label("Number of terrains down:");
down = Mathf.Max(IntField(down, Width(30)), 1);
EndHorizontal();
EndVertical();
BeginVertical();
Space(12);
if (Button("Apply")) {
tWidth = across;
tHeight = down;
SetNumberOfTerrains();
}
EndVertical();
EndHorizontal();
Space(16);
var counter = 0;
for (h = 0; h < tHeight; h++) {
BeginHorizontal();
Space(12);
for (w = 0; w < tWidth; w++) {
terrains[counter] = ObjectField(terrains[counter++], Terrain, Width(112));
Space(5);
}
EndHorizontal();
Space(10);
}
DrawGrid(Color.black, 1);
DrawGrid(Color.white, 0);
Space(10);
BeginHorizontal();
Label("Stitch width: " + stitchWidth, Width(90));
stitchWidth = HorizontalSlider(stitchWidth, 1, 100);
EndHorizontal();
Space(2);
Label(message);
Space(2);
BeginHorizontal();
if (Button("Clear")) {
SetNumberOfTerrains();
}
if (Button("Stitch")) {
StitchTerrains();
}
EndHorizontal();
}
private function DrawGrid (color : Color, offset : int) {
GUI.color = color;
for (i = 0; i < tHeight+1; i++) {
GUI.DrawTexture(Rect(5 + offset, 63 + offset + 28*i, 121*tWidth, 1), lineTex);
}
for (i = 0; i < tWidth+1; i++) {
GUI.DrawTexture(Rect(5 + offset + 121*i, 63 + offset, 1, 28*tHeight + 1), lineTex);
}
}
static function SetNumberOfTerrains () {
terrains = new Object[tWidth * tHeight];
totalTerrains = tWidth * tHeight;
message = "";
}
static function StitchTerrains () {
for (t in terrains) {
if (t == null) {
message = "All terrain slots must have a terrain assigned";
return;
}
}
terrainRes = terrains[0].terrainData.heightmapWidth;
if (terrains[0].terrainData.heightmapHeight != terrainRes) {
message = "Heightmap width and height must be the same";
return;
}
for (t in terrains) {
if (t.terrainData.heightmapWidth != terrainRes || t.terrainData.heightmapHeight != terrainRes) {
message = "All heightmaps must be the same resolution";
return;
}
}
if (tWidth == 1 && tHeight == 1) {
message = "Only one terrain specified...nothing to stitch";
return;
}
for (t in terrains) {
Undo.RegisterUndo(t.terrainData, "Stitch");
}
stitchWidth = Mathf.Clamp(stitchWidth, 1, (terrainRes-1)/2);
var counter = 0;
var total = tHeight*(tWidth-1) + (tHeight-1)*tWidth;
for (h = 0; h < tHeight; h++) {
for (w = 0; w < tWidth-1; w++) {
EditorUtility.DisplayProgressBar("Stitching...", "", Mathf.InverseLerp(0, total, ++counter));
BlendData (terrains[h*tWidth + w], terrains[h*tWidth + w + 1], Direction.Across);
}
}
for (h = 0; h < tHeight-1; h++) {
for (w = 0; w < tWidth; w++) {
EditorUtility.DisplayProgressBar("Stitching...", "", Mathf.InverseLerp(0, total, ++counter));
BlendData (terrains[h*tWidth + w], terrains[(h+1)*tWidth + w], Direction.Down);
}
}
message = "Terrains stitched successfully";
EditorUtility.ClearProgressBar();
}
static function BlendData (terrain1 : Terrain, terrain2 : Terrain, thisDirection : Direction) {
var heightmapData = terrain1.terrainData.GetHeights(0, 0, terrainRes, terrainRes);
var heightmapData2 = terrain2.terrainData.GetHeights(0, 0, terrainRes, terrainRes);
var pos = terrainRes-1;
if (thisDirection == Direction.Across) {
for (i = 0; i < terrainRes; i++) {
for (j = 1; j < stitchWidth; j++) {
var mix = Mathf.Lerp(heightmapData[i, pos-j], heightmapData2[i, j], .5);
if (j == 1) {
heightmapData[i, pos] = mix;
heightmapData2[i, 0] = mix;
}
var t = Mathf.SmoothStep(0.0, 1.0, Mathf.InverseLerp(1, stitchWidth-1, j));
heightmapData[i, pos-j] = Mathf.Lerp(mix, heightmapData[i, pos-j], t);
heightmapData2[i, j] = Mathf.Lerp(mix, heightmapData2[i, j], t);
}
}
}
else {
for (i = 0; i < terrainRes; i++) {
for (j = 1; j < stitchWidth; j++) {
mix = Mathf.Lerp(heightmapData2[pos-j, i], heightmapData[j, i], .5);
if (j == 1) {
heightmapData2[pos, i] = mix;
heightmapData[0, i] = mix;
}
t = Mathf.SmoothStep(0.0, 1.0, Mathf.InverseLerp(1, stitchWidth-1, j));
heightmapData2[pos-j, i] = Mathf.Lerp(mix, heightmapData2[pos-j, i], t);
heightmapData[j, i] = Mathf.Lerp(mix, heightmapData[j, i], t);
}
}
}
terrain1.terrainData.SetHeights(0, 0, heightmapData);
terrain2.terrainData.SetHeights(0, 0, heightmapData2);
}
}
Способ применения: создаем, к примеру, 4 террейна, настраиваем высоты, расставляем в порядке:
1 2
3 4
Открываем Terrain - Stitch, перетаскиваем террейны в том же порядке в окно Stitch Terrains и жмем Stitch, вуаля