429 lines
12 KiB
C#
429 lines
12 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
using Sirenix.OdinInspector;
|
||
using TGS;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.Serialization;
|
||
|
||
//笔刷类型
|
||
public enum EBrushType
|
||
{
|
||
None = 0,
|
||
Tree,
|
||
Building,
|
||
Stuff,
|
||
}
|
||
|
||
//物品分布方式
|
||
public enum ESceneObjectLayout
|
||
{
|
||
None = 0,
|
||
RegularPolygon = 1,
|
||
Random = 2,
|
||
}
|
||
|
||
/// <summary>
|
||
/// 笔刷工具,每一个笔刷绑定了一个Prefab和一个SceneObjectRenderInfo
|
||
/// 每一个笔刷就对应了一个具体外貌的游戏物体(如绿色的松树和白色的松树就需要两个笔刷)
|
||
/// 需要继承自该类完成自己类型笔刷的逻辑
|
||
/// </summary>
|
||
public abstract partial class SceneBrush : ScriptableObject
|
||
{
|
||
#region Const
|
||
|
||
protected const string SCENE_BRUSH_GENERATED_PREFAB_PATH = "Assets/Art/Scene/SceneBrush/Brush/Generated/Prefabs";
|
||
|
||
protected const string SCENE_BRUSH_GENERATED_MATERIAL_PATH =
|
||
"Assets/Art/Scene/SceneBrush/Brush/Generated/Materials";
|
||
|
||
protected const string SCENE_TRANSPARENT_SHADER = "Assets/Code/Shaders/Scene/NLD_Scene_Transparent.shader";
|
||
protected const string SCENE_OPAQUE_SHADER = "Assets/Code/Shaders/Scene/NLD_Scene_Opaque.shader";
|
||
|
||
public const string BRUSH_UNDO_NAME = "Brush Undo";
|
||
public const string BRUSH_DRAW_UNDO_NAME = "Brush Draw Undo";
|
||
|
||
//public const string ART_NAME = "Art";
|
||
|
||
public static readonly string[] EBrushTypeStrings = new[]
|
||
{
|
||
"None",
|
||
"树",
|
||
"建筑物",
|
||
"其他物件",
|
||
};
|
||
|
||
// private Transform _art;
|
||
//
|
||
// public Transform Art
|
||
// {
|
||
// get
|
||
// {
|
||
// if (_art == null)
|
||
// {
|
||
// var scene = _sceneManager.gameObject.scene;
|
||
// var roots = scene.GetRootGameObjects();
|
||
// var artGo = roots.FirstOrDefault(r => r.name == ART_NAME);
|
||
// if (artGo == null)
|
||
// {
|
||
// artGo = ObjectFactory.CreateGameObject(scene, HideFlags.None, ART_NAME);
|
||
// }
|
||
//
|
||
// _art = artGo.transform;
|
||
// }
|
||
//
|
||
// return _art;
|
||
// }
|
||
// }
|
||
|
||
#endregion
|
||
|
||
#region Fields And Properties
|
||
|
||
[Sirenix.OdinInspector.ReadOnly]
|
||
public GameObject prefab;
|
||
|
||
[Sirenix.OdinInspector.ReadOnly]
|
||
public SceneObjectRenderInfo renderInfo;
|
||
|
||
[HideInInspector]
|
||
public bool useTGS;
|
||
|
||
[HideInInspector]
|
||
public float radius = 1;
|
||
|
||
[HideInInspector]
|
||
public ESceneObjectLayout layout = ESceneObjectLayout.RegularPolygon;
|
||
|
||
[HideInInspector]
|
||
public int objCount = 1;
|
||
|
||
[HideInInspector]
|
||
public float scale = 1;
|
||
|
||
[HideInInspector]
|
||
public float yAngle;
|
||
|
||
[HideInInspector]
|
||
public bool isEraser;
|
||
|
||
[HideInInspector]
|
||
public bool eraseSameType;
|
||
|
||
public string remarkName;
|
||
|
||
[FormerlySerializedAs("checkOverlap")]
|
||
[HideInInspector]
|
||
public bool canOverlap;
|
||
|
||
private NLDSceneManager _sceneManager;
|
||
|
||
private TerrainGridSystem _tgs;
|
||
|
||
private Vector3 _lastDrawPosition;
|
||
|
||
public virtual bool supportTransparent
|
||
{
|
||
get
|
||
{
|
||
//如果gameobject上所有的材质的shader都是SCENE_OPAQUE_SHADER才支持半透
|
||
if (prefab == null)
|
||
return false;
|
||
var msrs = prefab.GetComponentsInChildren<MeshRenderer>();
|
||
bool result = true;
|
||
foreach (var msr in msrs)
|
||
{
|
||
foreach (var mat in msr.sharedMaterials)
|
||
{
|
||
if (mat != null && AssetDatabase.GetAssetPath(mat.shader) == SCENE_OPAQUE_SHADER)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
result = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
|
||
public abstract string RootName { get; }
|
||
|
||
public virtual bool SupportSurface => prefab != null && prefab.GetComponentsInChildren<MeshRenderer>().Length == 1;
|
||
|
||
public abstract EBrushType brushType { get; }
|
||
|
||
public bool Valid => prefab != null && renderInfo != null;
|
||
|
||
public ESceneObjectType SceneObjectType => SceneBrushHelper.BrushType2SceneObjectType(brushType);
|
||
|
||
#endregion
|
||
|
||
#region MainLife
|
||
|
||
public void OnSelected(NLDSceneManager sceneManager, TerrainGridSystem tgs)
|
||
{
|
||
_sceneManager = sceneManager;
|
||
_tgs = tgs;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基类的该函数只能对应大部分的场景物件(如树木,石头等),建筑等特殊的需要重写该函数
|
||
/// </summary>
|
||
/// <param name="newPrefab"></param>
|
||
public virtual void OnPrefabChanged(GameObject newPrefab)
|
||
{
|
||
if (newPrefab == null)
|
||
{
|
||
prefab = null;
|
||
return;
|
||
}
|
||
|
||
//预处理一下资源
|
||
if (!PreProcessPrefab(newPrefab))
|
||
return;
|
||
var savedBrush = SceneBrushHelper.GetSceneBrushByPrefab(newPrefab);
|
||
if (savedBrush != null)
|
||
{
|
||
//如果当前预制体已经与其他笔刷绑定了
|
||
if (!OnPrefabRepeat(ref newPrefab, savedBrush))
|
||
return;
|
||
}
|
||
|
||
prefab = newPrefab;
|
||
//后处理资源
|
||
PostProcessPrefab();
|
||
}
|
||
|
||
//预处理资源,如树是从TreeCreator制作的,但Unity已经不支持了,需要我们手动移除无用组件和材质并设置正确值
|
||
protected virtual bool PreProcessPrefab(GameObject k_prefab)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
protected virtual bool OnPrefabRepeat(ref GameObject k_prefab, SceneBrush savedBrush)
|
||
{
|
||
var clonedPrefab = ClonePrefab(k_prefab);
|
||
k_prefab = clonedPrefab;
|
||
return true;
|
||
}
|
||
|
||
//后处理资源,包括生成半透明材质,记录材质和网格信息
|
||
protected virtual void PostProcessPrefab()
|
||
{
|
||
//应用当前不透明材质到renderInfo上
|
||
ApplyOpaqueMaterial();
|
||
//创建透明材质并应用到renderInfo上
|
||
if (supportTransparent)
|
||
CreateTransparentMaterialAndApply();
|
||
//应用网格
|
||
ApplyMesh();
|
||
}
|
||
|
||
protected GameObject ClonePrefab(GameObject k_prefab)
|
||
{
|
||
if (k_prefab == null || !PrefabUtility.IsPartOfPrefabAsset(k_prefab))
|
||
{
|
||
Debug.LogError("克隆对象失败!");
|
||
return null;
|
||
}
|
||
|
||
var instance = PrefabUtility.InstantiatePrefab(k_prefab) as GameObject;
|
||
if (instance == null)
|
||
return null;
|
||
PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
|
||
var newPath =
|
||
EditorUtil.GetAssetUniquePath($"{SCENE_BRUSH_GENERATED_PREFAB_PATH}/{name}.prefab", out var assetName);
|
||
instance.name = assetName;
|
||
PrefabUtility.SaveAsPrefabAsset(instance, newPath);
|
||
DestroyImmediate(instance);
|
||
var newPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(newPath);
|
||
//深拷贝材质
|
||
var mats = MaterialUtil.ReplaceMaterialOnGameObject(newPrefab);
|
||
if (mats == null)
|
||
return null;
|
||
foreach (var mat in mats)
|
||
{
|
||
EditorUtil.CreateAnUniqueAsset(mat, $"{SCENE_BRUSH_GENERATED_MATERIAL_PATH}/{name}_opaque.mat");
|
||
}
|
||
|
||
AssetDatabase.Refresh();
|
||
return newPrefab;
|
||
}
|
||
|
||
protected void ApplyOpaqueMaterial()
|
||
{
|
||
var meshrenderers = prefab.GetComponentsInChildren<MeshRenderer>();
|
||
List<Material> mats = new();
|
||
foreach (var meshRenderer in meshrenderers)
|
||
{
|
||
foreach (var mat in meshRenderer.sharedMaterials)
|
||
{
|
||
mats.Add(mat);
|
||
}
|
||
}
|
||
|
||
renderInfo.opaqueMaterial = mats;
|
||
}
|
||
|
||
protected void CreateTransparentMaterialAndApply()
|
||
{
|
||
if (prefab == null)
|
||
return;
|
||
var shader = AssetDatabase.LoadAssetAtPath<Shader>(SCENE_TRANSPARENT_SHADER);
|
||
var mats = MaterialUtil.CreateMaterialWithShaderOnGameObject(prefab, shader);
|
||
if (mats == null)
|
||
return;
|
||
foreach (var mat in mats)
|
||
{
|
||
mat.enableInstancing = true;
|
||
EditorUtil.CreateAnUniqueAsset(mat, $"{SCENE_BRUSH_GENERATED_MATERIAL_PATH}/{name}_transparent.mat");
|
||
}
|
||
|
||
renderInfo.transparentMaterial = mats;
|
||
}
|
||
|
||
protected void ApplyMesh()
|
||
{
|
||
if (prefab == null)
|
||
return;
|
||
var meshFilters = prefab.GetComponentsInChildren<MeshFilter>();
|
||
List<Mesh> result = new();
|
||
foreach (var mf in meshFilters)
|
||
{
|
||
result.Add(mf.sharedMesh);
|
||
}
|
||
|
||
renderInfo.meshes = result;
|
||
}
|
||
|
||
protected virtual void OnMouseDownInScene(Vector3 mouseWorldPosition)
|
||
{
|
||
if (!isEraser)
|
||
DrawStandard(mouseWorldPosition);
|
||
else
|
||
{
|
||
EraseStandard(mouseWorldPosition);
|
||
}
|
||
|
||
_lastDrawPosition = mouseWorldPosition;
|
||
}
|
||
|
||
protected virtual void OnMouseDragInScene(Vector3 mouseWorldPosition, float dragDistance)
|
||
{
|
||
if (dragDistance >= radius)
|
||
{
|
||
if (!isEraser)
|
||
DrawStandard(mouseWorldPosition);
|
||
else
|
||
EraseStandard(mouseWorldPosition);
|
||
|
||
_lastDrawPosition = mouseWorldPosition;
|
||
}
|
||
}
|
||
|
||
protected virtual void OnMouseUpInScene(Vector3 mouseWorldPosition)
|
||
{
|
||
}
|
||
|
||
public virtual void OnDelete()
|
||
{
|
||
if (Valid)
|
||
{
|
||
foreach (var mat in renderInfo.transparentMaterial)
|
||
{
|
||
if (AssetDatabase.IsSubAsset(mat))
|
||
{
|
||
AssetDatabase.RemoveObjectFromAsset(mat);
|
||
}
|
||
else
|
||
{
|
||
//Object.DestroyImmediate(mat, true);
|
||
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(mat));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
var path = AssetDatabase.GetAssetPath(renderInfo);
|
||
//DestroyImmediate(renderInfo, true);
|
||
AssetDatabase.DeleteAsset(path);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Other
|
||
|
||
protected void DrawStandard(Vector3 position)
|
||
{
|
||
var locations = StandardDrawCalculate(position);
|
||
if (locations != null)
|
||
{
|
||
foreach (var location in locations)
|
||
{
|
||
DrawASceneObject(location.position, location.rotation);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected void EraseStandard(Vector3 position)
|
||
{
|
||
var sObjs = StandardEraseCalculate(position);
|
||
foreach (var sObj in sObjs)
|
||
{
|
||
EraseASceneObject(sObj);
|
||
}
|
||
}
|
||
|
||
protected void DrawASceneObject(Vector3 position, Quaternion rotation)
|
||
{
|
||
var instance = PrefabUtility.InstantiatePrefab(prefab, GetTypeRoot()) as GameObject;
|
||
if (instance != null)
|
||
{
|
||
instance.transform.position = position;
|
||
instance.transform.rotation = rotation;
|
||
instance.transform.localScale *= scale;
|
||
var sceneObj = instance.AddComponent<SceneObject>();
|
||
sceneObj.renderInfo = renderInfo;
|
||
sceneObj.sceneObjectType = SceneObjectType;
|
||
Undo.RegisterCompleteObjectUndo(_sceneManager, BRUSH_DRAW_UNDO_NAME);
|
||
_sceneManager.sceneObjects.Add(sceneObj);
|
||
Undo.RegisterCreatedObjectUndo(instance, BRUSH_DRAW_UNDO_NAME);
|
||
EditorSceneManager.MarkSceneDirty(instance.scene);
|
||
EditorUtility.SetDirty(_sceneManager);
|
||
}
|
||
}
|
||
|
||
protected void EraseASceneObject(SceneObject sceneObject)
|
||
{
|
||
if (sceneObject != null)
|
||
{
|
||
Undo.RegisterCompleteObjectUndo(_sceneManager, BRUSH_DRAW_UNDO_NAME);
|
||
_sceneManager.sceneObjects.Remove(sceneObject);
|
||
Undo.DestroyObjectImmediate(sceneObject.gameObject);
|
||
EditorUtility.SetDirty(_sceneManager);
|
||
}
|
||
}
|
||
|
||
private Transform GetTypeRoot()
|
||
{
|
||
var trans = _sceneManager.Art.Find(RootName);
|
||
if (trans == null)
|
||
{
|
||
GameObject go = new GameObject();
|
||
go.name = RootName;
|
||
go.transform.parent = _sceneManager.Art;
|
||
trans = go.transform;
|
||
}
|
||
|
||
return trans;
|
||
}
|
||
|
||
#endregion
|
||
} |