-
-
Save todorok1/503911a7370439fc83dc52859c5ccb0d to your computer and use it in GitHub Desktop.
エディタ上でパーリンノイズによる地形を生成するサンプル
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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