|
[md]## 刷草功能
> 到目前为止草的位置都是根据指定的模型Mesh顶点位置定的。编辑器内的刷草工具还是很有必要的。至少需要添加、删除和修改。
1. 添加实现逻辑
- 鼠标指向的位置投射射线
- 根据射线打中的位置作为判定范围,并根据设定参数如颜色、笔刷大小、密度、宽度和高度生成相应的数据添加到数组中储存
2. 移除实现逻辑
- 鼠标指向的位置投射射线
- 根据射线打中的位置,与储存的所有数据进行对比,得到根据笔刷大小作为判定条件筛选出需要移除的草,并对相应数据进行摘除
3. 编辑实现逻辑
- 鼠标指向的位置投射射线
- 根据射线打中的位置,与储存的所有数据进行对比,得到根据笔刷大小和衰减范围作为判定条件筛选出需要编辑的草,并对相应数据进行修改
直接上代码,带有注释
***GrassPainter.cs***
```csharp
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[RequireComponent(typeof(MeshFilter))]
[ExecuteInEditMode]
public class GrassPainter : MonoBehaviour {
//生成Mesh用于储存刷的点
public Mesh mesh;
//仅储存到场景中
MeshFilter filter;
//笔刷颜色,顶点色的基础色
public Color AdjustedColor;
//限制刷草的上限
[Range(1, 600000)]
public int grassLimit = 50000;
//保存上一个位置,用于计算距离
private Vector3 lastPosition = Vector3.zero;
//当前笔刷模式0添加 1移除 2修改
public int toolbarInt = 0;
//修改模式下模式 0颜色 1长度和宽度 2颜色、长度和宽度
public int toolbarIntEdit = 0;
//缓存顶点位置
[SerializeField]
List<Vector3> positions = new List<Vector3>();
//缓存顶点颜色
[SerializeField]
List<Color> colors = new List<Color>();
//缓存顶点下标
[SerializeField]
List<int> indicies = new List<int>();
//缓存顶点法线
[SerializeField]
List<Vector3> normals = new List<Vector3>();
//缓存宽度长度
[SerializeField]
List<Vector2> length = new List<Vector2>();
//下标临时变量,当前放置草堆点的数量
public int i = 0;
//草宽度乘数
public float sizeWidth = 1f;
//草高度乘数
public float sizeLength = 1f;
//草密度
public float density = 1f;
//限制刷草时地表面的竖直法线方向,0仅能在完全竖直的法线表面上刷,1无限制
public float normalLimit = 1;
//随机颜色偏移值
public float rangeR, rangeG, rangeB;
//表面检测层级,区分两层可用碰撞体遮挡,避免刷错
public LayerMask hitMask = 1;
//刷草地表面检测层级
public LayerMask paintMask = 1;
//笔刷大小
public float brushSize;
//笔刷渐变范围大小
public float brushFalloffSize;
//修改的灵敏度,越大越不灵敏,跳过的帧数
public float Flow;
private int flowTimer;
//鼠标位置
Vector3 mousePos;
//射线检测打到的位置,用于Editor显示
[HideInInspector]
public Vector3 hitPosGizmo;
//射线检测打到的位置,用于工具功能
Vector3 hitPos;
//射线打到的法线方向
[HideInInspector]
public Vector3 hitNormal;
//设置进Mesh的工具数组
int[] indi;
#if UNITY_EDITOR
//处理事件,当处理Scene窗口时,执行工具功能
void OnFocus() {
SceneView.duringSceneGui -= this.OnScene;
SceneView.duringSceneGui += this.OnScene;
}
void OnDestroy() {
SceneView.duringSceneGui -= this.OnScene;
}
private void OnEnable() {
filter = GetComponent<MeshFilter>();
SceneView.duringSceneGui += this.OnScene;
}
//清空数据
public void ClearMesh() {
i = 0;
positions = new List<Vector3>();
indicies = new List<int>();
colors = new List<Color>();
normals = new List<Vector3>();
length = new List<Vector2>();
}
void OnScene(SceneView scene) {
// 仅允许painter的物体被选中时才执行
if ((Selection.Contains(gameObject))) {
//获取输入事件
Event e = Event.current;
//射线击中结构体
RaycastHit terrainHit;
//鼠标位置
mousePos = e.mousePosition;
//转换屏幕原点坐标到左下角
float ppp = EditorGUIUtility.pixelsPerPoint;
mousePos.y = scene.camera.pixelHeight - mousePos.y * ppp;
mousePos.x *= ppp;
// 用于Editor显示的射线
Ray rayGizmo = scene.camera.ScreenPointToRay(mousePos);
RaycastHit hitGizmo;
if (Physics.Raycast(rayGizmo, out hitGizmo, 200f, hitMask.value)) {
hitPosGizmo = hitGizmo.point;
}
// 判断是否按下Contrl | Alt | shift | Command键
bool isModifiedHold = e.control || e.alt || e.shift || e.command;
// 添加
// 按下功能键+鼠标右键 进入添加模式
bool isAdding = isModifiedHold && e.type == EventType.MouseDrag &&
e.button == 1 && toolbarInt == 0;
if (isAdding) {
// 密度循环
for (int k = 0; k < density; k++) {
// 笔刷大小
float t = 2f * Mathf.PI * Random.Range(0f, brushSize);
float u = Random.Range(0f, brushSize) + Random.Range(0f, brushSize);
float r = (u > 1 ? 2 - u : u);
Vector3 origin = Vector3.zero;
// 除了第一个,其他随机放置
if (k != 0) {
origin.x += r * Mathf.Cos(t);
origin.y += r * Mathf.Sin(t);
}
else {
origin = Vector3.zero;
}
// 给射线添加随机偏移
Ray ray = scene.camera.ScreenPointToRay(mousePos);
ray.origin += origin;
// 射线检测,草数量限制,法线限制
if (Physics.Raycast(ray, out terrainHit, 200f, hitMask.value) &&
i < grassLimit && terrainHit.normal.y <= (1 + normalLimit) &&
terrainHit.normal.y >= (1 - normalLimit)) {
//是否在可刷草的区域
if ((paintMask.value & (1 << terrainHit.transform.gameObject.layer)) > 0) {
hitPos = terrainHit.point;
hitNormal = terrainHit.normal;
if (k != 0) {
var grassPosition = hitPos;
//减去物体位置,grassPosition 为模型空间数据
grassPosition -= this.transform.position;
positions.Add((grassPosition));
indicies.Add(i);
//传到uv中使用,宽度和高度的乘数
length.Add(new Vector2(sizeWidth, sizeLength));
// 颜色随机
colors.Add(
new Color(AdjustedColor.r + (Random.Range(0, 1.0f) * rangeR),
AdjustedColor.g + (Random.Range(0, 1.0f) * rangeG),
AdjustedColor.b + (Random.Range(0, 1.0f) * rangeB), 1));
//法线,与地面垂直
normals.Add(terrainHit.normal);
i++;
}
else {
// 只有刷动才添加,否则仅在一个点停留不添加
if (Vector3.Distance(terrainHit.point, lastPosition) > brushSize) {
var grassPosition = hitPos;
grassPosition -= this.transform.position;
positions.Add((grassPosition));
indicies.Add(i);
length.Add(new Vector2(sizeWidth, sizeLength));
colors.Add(
new Color(AdjustedColor.r + (Random.Range(0, 1.0f) * rangeR),
AdjustedColor.g + (Random.Range(0, 1.0f) * rangeG),
AdjustedColor.b + (Random.Range(0, 1.0f) * rangeB), 1));
normals.Add(terrainHit.normal);
i++;
if (origin == Vector3.zero) {
lastPosition = hitPos;
}
}
}
}
}
}
//消耗掉事件
e.Use();
}
// 移除
// 按下功能键+鼠标右键 进入移除模式
bool isRemoving = isModifiedHold && e.type == EventType.MouseDrag &&
e.button == 1 && toolbarInt == 1;
if (isRemoving) {
Ray ray = scene.camera.ScreenPointToRay(mousePos);
if (Physics.Raycast(ray, out terrainHit, 200f, hitMask.value)) {
hitPos = terrainHit.point;
hitPosGizmo = hitPos;
hitNormal = terrainHit.normal;
//检查所有位置与当前刷子位置
for (int j = 0; j < positions.Count; j++) {
Vector3 pos = positions[j];
pos += this.transform.position;
float dist = Vector3.Distance(terrainHit.point, pos);
// 在刷子范围内则移除
if (dist <= brushSize) {
positions.RemoveAt(j);
colors.RemoveAt(j);
normals.RemoveAt(j);
length.RemoveAt(j);
indicies.RemoveAt(j);
i--;
//重排下标
for (int i = 0; i < indicies.Count; i++) {
indicies = i;
}
}
}
}
e.Use();
}
// 编辑
// 按下功能键+鼠标右键 进入编辑模式
bool isEditing = isModifiedHold && e.type == EventType.MouseDrag &&
e.button == 1 && toolbarInt == 2;
if (isEditing) {
Ray ray = scene.camera.ScreenPointToRay(mousePos);
if (Physics.Raycast(ray, out terrainHit, 200f, hitMask.value)) {
hitPos = terrainHit.point;
hitPosGizmo = hitPos;
hitNormal = terrainHit.normal;
//检查所有位置与当前刷子位置
for (int j = 0; j < positions.Count; j++) {
Vector3 pos = positions[j];
pos += this.transform.position;
float dist = Vector3.Distance(terrainHit.point, pos);
// 在刷子范围内则应用当前设置
if (dist <= brushSize) {
brushFalloffSize = Mathf.Clamp(brushFalloffSize, 0, brushSize);
float falloff = Mathf.Clamp01((dist - brushFalloffSize) /
(brushSize - brushFalloffSize));
Color OrigColor = colors[j];
Color newCol = (
new Color(AdjustedColor.r + (Random.Range(0, 1.0f) * rangeR),
AdjustedColor.g + (Random.Range(0, 1.0f) * rangeG),
AdjustedColor.b + (Random.Range(0, 1.0f) * rangeB), 1));
Vector2 origLength = length[j];
Vector2 newLength = new Vector2(sizeWidth, sizeLength); ;
flowTimer++;
if (flowTimer > Flow) {
if (toolbarIntEdit == 0 || toolbarIntEdit == 2) {
colors[j] = Color.Lerp(newCol, OrigColor, falloff);
}
if (toolbarIntEdit == 1 || toolbarIntEdit == 2) {
length[j] = Vector2.Lerp(newLength, origLength, falloff);
}
flowTimer = 0;
}
}
}
}
e.Use();
}
// 设置所有信息到Mesh中
mesh = new Mesh();
mesh.SetVertices(positions);
indi = indicies.ToArray();
// 以点模式储存
mesh.SetIndices(indi, MeshTopology.Points, 0);
mesh.SetUVs(0, length);
mesh.SetColors(colors);
mesh.SetNormals(normals);
filter.mesh = mesh;
}
}
#endif
}
```
## Editor
***GrassPainterEditor.cs*** 需要放在Editor文件夹
```csharp
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
[CustomEditor(typeof(GrassPainter))]
[InitializeOnLoad]
public class GrassPainterEditor : Editor {
GrassPainter grassPainter;
readonly string[] toolbarStrings = { "Add", "Remove", "Edit" };
readonly string[] toolbarStringsEdit = { "Edit Colors", "Edit Length/Width", "Both" };
private void OnEnable() {
grassPainter = (GrassPainter)target;
}
void OnSceneGUI() {
//默认添加草
Handles.color = Color.cyan;
Handles.DrawWireDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushSize);
Handles.color = new Color(0, 0.5f, 0.5f, 0.4f);
Handles.DrawSolidDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushSize);
//移除
if (grassPainter.toolbarInt == 1) {
Handles.color = Color.red;
Handles.DrawWireDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushSize);
Handles.color = new Color(0.5f, 0f, 0f, 0.4f);
Handles.DrawSolidDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushSize);
}
//修改
if (grassPainter.toolbarInt == 2) {
Handles.color = Color.yellow;
Handles.DrawWireDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushSize);
Handles.color = new Color(0.5f, 0.5f, 0f, 0.4f);
Handles.DrawSolidDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushSize);
// falloff
Handles.color = Color.yellow;
Handles.DrawWireDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
grassPainter.brushFalloffSize);
Handles.color = new Color(0.5f, 0.5f, 0f, 0.4f);
Handles.DrawSolidDisc(grassPainter.hitPosGizmo, grassPainter.hitNormal,
Mathf.Clamp(grassPainter.brushFalloffSize, 0, grassPainter.brushSize));
}
}
public override void OnInspectorGUI() {
EditorGUILayout.LabelField("Grass Limit", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(grassPainter.i.ToString() + "/", EditorStyles.label);
grassPainter.grassLimit = EditorGUILayout.IntField(grassPainter.grassLimit);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Hit Settings", EditorStyles.boldLabel);
LayerMask tempMask = EditorGUILayout.MaskField("Hit Mask",
InternalEditorUtility.LayerMaskToConcatenatedLayersMask(grassPainter.hitMask),
InternalEditorUtility.layers);
grassPainter.hitMask = InternalEditorUtility.ConcatenatedLayersMaskToLayerMask(tempMask);
LayerMask tempMask2 = EditorGUILayout.MaskField("Painting Mask",
InternalEditorUtility.LayerMaskToConcatenatedLayersMask(grassPainter.paintMask),
InternalEditorUtility.layers);
grassPainter.paintMask = InternalEditorUtility.ConcatenatedLayersMaskToLayerMask(tempMask2);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Paint Status (Right-Mouse Button to paint)",
EditorStyles.boldLabel);
grassPainter.toolbarInt = GUILayout.Toolbar(grassPainter.toolbarInt, toolbarStrings);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Brush Settings", EditorStyles.boldLabel);
grassPainter.brushSize = EditorGUILayout.Slider("Brush Size",
grassPainter.brushSize, 0.1f, 10f);
//添加
if (grassPainter.toolbarInt == 0) {
grassPainter.normalLimit = EditorGUILayout.Slider("Normal Limit",
grassPainter.normalLimit, 0f, 1f);
grassPainter.density = EditorGUILayout.Slider("Density", grassPainter.density, 0.1f, 10f);
}
//修改
if (grassPainter.toolbarInt == 2) {
grassPainter.toolbarIntEdit = GUILayout.Toolbar(grassPainter.toolbarIntEdit,
toolbarStringsEdit);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Soft Falloff Settings", EditorStyles.boldLabel);
grassPainter.brushFalloffSize = EditorGUILayout.Slider("Brush Falloff Size",
grassPainter.brushFalloffSize, 0.1f, 10f);
grassPainter.Flow = EditorGUILayout.Slider("Brush Flow", grassPainter.Flow, 1, 20f);
}
//添加或修改
if (grassPainter.toolbarInt == 0 || grassPainter.toolbarInt == 2) {
EditorGUILayout.Space();
EditorGUILayout.LabelField("Width and Length ", EditorStyles.boldLabel);
grassPainter.sizeWidth = EditorGUILayout.Slider("Grass Width",
grassPainter.sizeWidth, 0f, 2f);
grassPainter.sizeLength = EditorGUILayout.Slider("Grass Length",
grassPainter.sizeLength, 0f, 2f);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Color", EditorStyles.boldLabel);
grassPainter.AdjustedColor = EditorGUILayout.ColorField("Brush Color",
grassPainter.AdjustedColor);
EditorGUILayout.LabelField("Random Color Variation", EditorStyles.boldLabel);
grassPainter.rangeR = EditorGUILayout.Slider("Red", grassPainter.rangeR, 0f, 1f);
grassPainter.rangeG = EditorGUILayout.Slider("Green", grassPainter.rangeG, 0f, 1f);
grassPainter.rangeB = EditorGUILayout.Slider("Blue", grassPainter.rangeB, 0f, 1f);
}
//清空
if (GUILayout.Button("Clear Mesh")) {
if (EditorUtility.DisplayDialog("Clear Painted Mesh?",
"Are you sure you want to clear the mesh?", "Clear", "Don't Clear")) {
grassPainter.ClearMesh();
}
}
}
}
```
***效果***
![grassBrush](https://cdn.laojiong.site/grassBrush.gif)
[/md] |
|