QQ登录

只需一步,快速开始

 找回密码
 定下契约(新注册)

QQ登录

只需一步,快速开始

查看: 2683|回复: 5
收起左侧

[原创] 草(三)移动端可用的草海,编辑器刷草工具(完结)

[复制链接]

Tech Artist

Rank: 16

UID
60078
宝石
0 粒
金币
29 枚
节操
3 斤
灵石
0 块
精力
49 ℃
发表于 2021-10-20 14:04:32 | 显示全部楼层 |阅读模式

你这样只看不注册,真的大丈夫?~

您需要 登录 才可以下载或查看,没有账号?定下契约(新注册)

x
[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]

世界工人

心之所向

Rank: 16

UID
1
宝石
30 粒
金币
3713 枚
节操
1271 斤
灵石
2 块
精力
12635 ℃

sex lady精力射线真の绅士

发表于 2021-10-20 14:17:32 | 显示全部楼层
前排支持大佬
回复 支持 反对

使用道具 举报

翘楚 Outstanding

Rank: 6Rank: 6Rank: 6

UID
60029
宝石
17 粒
金币
920 枚
节操
83 斤
灵石
0 块
精力
2018 ℃
发表于 2021-10-25 10:46:38 | 显示全部楼层
妈耶!一觉醒来多了几个分区
回复 支持 反对

使用道具 举报

版主

什么都没有\("▔□▔)/

Rank: 32Rank: 32

宝石
138 粒
金币
21721 枚
节操
3481 斤
灵石
9 块
精力
5528 ℃

善恶天使真の绅士Hot Blue!sex lady一击必杀宙光普照精力射线木下秀吉猩红之心赤月游丝宝石达人---大丈夫!!!Ruby RoseArchar黑岩之炎万华镜禁术BRS暴走

发表于 2021-10-25 23:58:38 | 显示全部楼层
好硬,给我牙崩掉了
回复 支持 反对

使用道具 举报

版主

时序者

Rank: 32Rank: 32

宝石
59 粒
金币
6420 枚
节操
143 斤
灵石
2 块
精力
13800 ℃

黑岩之炎猩红之心mikuBRS暴走

发表于 2021-10-26 19:40:29 来自手机 | 显示全部楼层
支持大佬
回复 支持 反对

使用道具 举报

翘楚 Outstanding

Rank: 6Rank: 6Rank: 6

UID
60029
宝石
17 粒
金币
920 枚
节操
83 斤
灵石
0 块
精力
2018 ℃
发表于 2021-10-28 14:52:10 | 显示全部楼层
人生重来名 发表于 2021-10-25 23:58
好硬,给我牙崩掉了

妈耶??
回复 支持 1 反对 0

使用道具 举报

本版积分规则

    切换繁體
    Archiver|手机版|小黑屋|

GMT+8, 2024-11-23 19:15 , Processed in 0.136201 second(s), 91 queries .

沪ICP备2021020632号-1

快速回复 返回顶部 返回列表