Skip to content

Instantly share code, notes, and snippets.

@todorok1
Last active January 20, 2021 06:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save todorok1/503911a7370439fc83dc52859c5ccb0d to your computer and use it in GitHub Desktop.
Save todorok1/503911a7370439fc83dc52859c5ccb0d to your computer and use it in GitHub Desktop.
エディタ上でパーリンノイズによる地形を生成するサンプル
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
/// <Summary>
/// エディタ上でフィールドを生成するクラスです。
/// </Summary>
[CustomEditor(typeof(FieldGenerator))]
public class FieldGeneratorEditor : Editor
{
public override void OnInspectorGUI()
{
var generator = (FieldGenerator) target;
DrawDefaultInspector();
if (GUILayout.Button("フィールドの生成"))
{
GenerateFieldOnEditor(generator);
}
EditorGUILayout.Space();
if (GUILayout.Button("フィールドパーツの削除"))
{
RemoveFieldParts(generator);
}
EditorGUILayout.Space();
if (GUILayout.Button("フィールドメッシュの結合"))
{
CombineFieldParts(generator);
}
}
/// <Summary>
/// エディタ上でフィールド用のオブジェクトを生成するメソッドです。
/// </Summary>
void GenerateFieldOnEditor(FieldGenerator generator)
{
for (float z = 0f; z < generator.fieldSizeZ; z++)
{
for (float x = 0f; x < generator.fieldSizeX; x++)
{
// パーリンノイズの座標を指定して値を取得します。
float xValue = generator.xOrigin + x * generator.scale;
float yValue = generator.yOrigin + z * generator.scale;
float perlinValue = Mathf.PerlinNoise(xValue, yValue);
float height = generator.fieldHeight * perlinValue;
// 位置のVector3を作成してオブジェクトをインスタンス化します。
Vector3 pos = new Vector3(x, height, z);
InstantiateFieldParts(pos, generator);
}
}
}
/// <Summary>
/// オブジェクトをインスタンス化するメソッドです。
/// </Summary>
void InstantiateFieldParts(Vector3 pos, FieldGenerator generator)
{
// オブジェクトをインスタンス化します。
GameObject obj = Instantiate(generator.fieldParts, Vector3.zero, Quaternion.identity);
// オブジェクトのTransformを設定します。
obj.transform.SetParent(generator.fieldParent);
obj.transform.localPosition = pos;
// マテリアルをランダムにセットします。(検証用の処理)
int matIndex = Random.Range(0, generator.matList.Count);
obj.GetComponent<MeshRenderer>().material = generator.matList[matIndex];
}
/// <Summary>
/// フィールドのパーツを削除するメソッドです。
/// </Summary>
void RemoveFieldParts(FieldGenerator generator)
{
for (int i = generator.fieldParent.childCount - 1; i >= 0; i--)
{
DestroyImmediate(generator.fieldParent.GetChild(i).gameObject);
}
}
/// <Summary>
/// フィールドのパーツを結合するメソッドです。
/// </Summary>
void CombineFieldParts(FieldGenerator generator)
{
// 地形オブジェクトのMeshFilterへの参照を配列として保持します。
MeshFilter[] meshFilters = generator.fieldParent.GetComponentsInChildren<MeshFilter>();
MeshRenderer[] meshRenderers = generator.fieldParent.GetComponentsInChildren<MeshRenderer>();
// MeshFilterとMeshRendererの数が合っていない場合は処理を抜けます。
if (meshFilters.Length != meshRenderers.Length)
{
return;
}
// 結合したオブジェクトを格納するオブジェクトを作成します。
GameObject combineParent = new GameObject();
combineParent.name = "CombineParent";
combineParent.transform.position = Vector3.zero;
// 結合済みのオブジェクトを格納するオブジェクトを作成します。
GameObject combinedObjects = new GameObject();
// 子オブジェクトのメッシュをマテリアルごとにグループ分けします。
Dictionary<string, Material> matNameDict = new Dictionary<string, Material>();
Dictionary<string, List<MeshFilter>> matFilterDict = new Dictionary<string, List<MeshFilter>>();
for (int i = 0; i < meshFilters.Length; i++)
{
Material mat = meshRenderers[i].sharedMaterial;
string matName = mat.name;
// 辞書のキーにマテリアルが登録されていない場合はMeshFilterのリストを追加します。
if (!matFilterDict.ContainsKey(matName))
{
List<MeshFilter> filterList = new List<MeshFilter>();
matFilterDict.Add(matName, filterList);
matNameDict.Add(matName, mat);
}
matFilterDict[matName].Add(meshFilters[i]);
}
// グループ分けしたマテリアルごとにオブジェクトを作成し、メッシュを結合します。
foreach (KeyValuePair<string, List<MeshFilter>> pair in matFilterDict)
{
// 結合したメッシュを表示するゲームオブジェクトを作成します。
GameObject obj = CreateMeshObj(pair.Key, combineParent);
obj.transform.SetAsFirstSibling();
// MeshFilterとMeshRendererをアタッチします。
MeshFilter combinedMeshFilter = CheckComponent<MeshFilter>(obj);
MeshRenderer combinedMeshRenderer = CheckComponent<MeshRenderer>(obj);
// 結合するメッシュの配列を作成します。
List<MeshFilter> filterList = pair.Value;
CombineInstance[] combine = new CombineInstance[filterList.Count];
// 結合するメッシュの情報をCombineInstanceに追加していきます。
for (int i = 0; i < filterList.Count; i++)
{
combine[i].mesh = filterList[i].sharedMesh;
combine[i].transform = filterList[i].transform.localToWorldMatrix;
filterList[i].gameObject.transform.SetParent(combinedObjects.transform);
filterList[i].gameObject.SetActive(false);
// 進捗を表示します。
if (i % 10 == 0)
{
float progress = (float) (i + 1) / filterList.Count;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
i.ToString() + "項目(" + (progress * 100).ToString("F2") + "%)",
progress
);
}
}
EditorUtility.ClearProgressBar();
// 結合処理の進捗を表示します。
int step = 0;
int maxStep = 6;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
"メッシュを結合しています。",
(float) step / maxStep
);
// 作成したゲームオブジェクトにセットします。
combinedMeshFilter.sharedMesh = new Mesh();
combinedMeshFilter.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
combinedMeshFilter.sharedMesh.CombineMeshes(combine);
combinedMeshFilter.sharedMesh.name = pair.Key;
step++;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
"UVを生成しています。",
(float) step / maxStep
);
// 結合したメッシュでUV2を自動生成します。
Unwrapping.GenerateSecondaryUVSet(combinedMeshFilter.sharedMesh);
step++;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
"メッシュをアセットとして保存しています。",
(float) step / maxStep
);
// 結合したメッシュをアセットとして保存します。
SaveMeshAsAsset(combinedMeshFilter.sharedMesh);
step++;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
"結合したメッシュにマテリアルをセットしています。",
(float) step / maxStep
);
// 結合したメッシュにマテリアルをセットします。
combinedMeshRenderer.sharedMaterial = matNameDict[pair.Key];
step++;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
"結合したメッシュをコライダーにセットしています。",
(float) step / maxStep
);
// 結合したメッシュをコライダーにセットします。
MeshCollider meshCol = CheckComponent<MeshCollider>(obj);
meshCol.sharedMesh = combinedMeshFilter.sharedMesh;
step++;
EditorUtility.DisplayProgressBar(
"メッシュの結合中(" + pair.Key + ")",
"結合したメッシュを表示します。",
(float) step / maxStep
);
// 親オブジェクトを表示します。
generator.fieldParent.gameObject.SetActive(true);
EditorUtility.ClearProgressBar();
}
// 結合済みのオブジェクトを削除します。
DestroyImmediate(combinedObjects);
}
/// <Summary>
/// 結合したメッシュを表示するGameObjectを作成します。
/// </Summary>
GameObject CreateMeshObj(string matName, GameObject combineParent)
{
GameObject obj = new GameObject();
obj.name = $"CombinedMesh_{matName}";
obj.transform.SetParent(combineParent.transform);
obj.transform.localPosition = Vector3.zero;
return obj;
}
/// <Summary>
/// 指定されたコンポーネントへの参照を取得します。
/// コンポーネントがない場合はアタッチします。
/// </Summary>
T CheckComponent<T>(GameObject obj) where T : Component
{
// 型パラメータで指定したコンポーネントへの参照を取得します。
var targetComp = obj.GetComponent<T>();
if (targetComp == null)
{
targetComp = obj.AddComponent<T>();
}
return targetComp;
}
/// <Summary>
/// 結合したメッシュをアセットとして保存します。
/// 保存先は現在開いているシーン名のフォルダです。
/// </Summary>
void SaveMeshAsAsset(Mesh saveMesh)
{
// 現在開いているシーンのパスを取得します。
string scenePath = EditorSceneManager.GetActiveScene().path;
// フォルダの存在を確認します。
string folderPath = scenePath.Replace(".unity", "");
bool existFolder = AssetDatabase.IsValidFolder(folderPath);
// フォルダがない場合は作成します。
if (!existFolder)
{
string sceneName = EditorSceneManager.GetActiveScene().name;
string parentFolder = folderPath.Substring(0, folderPath.LastIndexOf(sceneName) - 1);
string folderName = sceneName;
AssetDatabase.CreateFolder(parentFolder, folderName);
}
// メッシュを保存します。
string meshPath = $"{folderPath}/{saveMesh.name}.asset";
var asset = (Mesh)AssetDatabase.LoadAssetAtPath(meshPath, typeof(Mesh));
if (asset == null)
{
AssetDatabase.CreateAsset(saveMesh, meshPath);
}
else
{
EditorUtility.CopySerialized(saveMesh, asset);
AssetDatabase.SaveAssets();
}
AssetDatabase.Refresh();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment