unity 陶艺制作模拟
using UnityEngine;
using System.Collections.Generic;/// ============================================================================
/// 陶艺制作器 - PotteryMaker.cs
/// ============================================================================
///
/// 【功能概述】
/// 专业的3D陶器建模工具,支持实时交互式陶器制作和编辑
///
/// 【主要特性】
/// ✅ 智能网格生成 - 自动创建底部、外壁、顶部、内壁和内底网格
/// ✅ 双模式操作 - 左键扩大陶器,右键缩小陶器
/// ✅ 高度补偿系统 - 壁扩大时自动变矮,壁缩小时自动变高
/// ✅ 实时碰撞器 - 动态更新网格碰撞器,支持触发器模式
/// ✅ 调试可视化 - 显示影响范围和操作目标点
/// ✅ 多输入支持 - 同时支持鼠标和触控输入
///
/// 【操作指南】
/// 1. 左键拖拽:扩大陶器形状
/// 2. 右键拖拽:缩小陶器形状
/// 3. 垂直拖拽:调整整体高度
/// 4. 参数调整:在Inspector中修改模型参数
///
/// 【快捷键】
/// • 左键 + 水平拖拽:半径调整
/// • 右键 + 水平拖拽:反向半径调整
/// • 任何键 + 垂直拖拽:高度调整
///
/// 【注意事项】
/// • 确保已分配陶艺材质
/// • 碰撞器默认启用,可根据需要调整
/// • 调试信息可在编辑器中查看操作效果
///
/// ============================================================================/// <summary>
/// 陶艺制作器 - 用于创建和编辑3D陶器模型
/// 功能包括:网格生成、交互变形、碰撞器管理、调试可视化
/// </summary>
public class PotteryMaker : MonoBehaviour
{[Header("模型参数")][Tooltip("细节数量(水平分段),值越高模型越平滑")]public int Details = 24; // 细节数量(水平分段)[Tooltip("层数(垂直分段),值越高模型高度方向细节越多")]public int LayerCount = 20; // 层数(垂直分段)[Tooltip("每层的高度,控制陶器的高度精度")]public float LayerHeight = 0.1f; // 层高[Tooltip("陶器的初始外径尺寸")]public float Radius = 1.0f; // 外径尺寸[Tooltip("陶器壁的厚度")]public float Thickness = 0.2f; // 壁厚度[Tooltip("陶器允许的最小半径")]public float MinRadius = 0.5f; // 最小半径[Tooltip("陶器允许的最大半径")]public float MaxRadius = 2.0f; // 最大半径[Header("交互参数")][Tooltip("操作影响的层数,值越大影响范围越广")]public int InfluenceLayer = 3; // 操作影响的层数[Tooltip("操作力度,控制变形速度")]public float TouchPower = 0.01f; // 操作力度[Tooltip("左键操作力度乘数")]public float LeftClickPowerMultiplier = 1.0f;[Tooltip("右键操作力度乘数")]public float RightClickPowerMultiplier = -1.0f;[Header("渲染设置")][Tooltip("陶艺材质,用于渲染陶器表面")]public Material PotteryMaterial; // 陶艺材质[Tooltip("射线检测层掩码,用于精确点击检测")]public LayerMask PotteryLayerMask = -1; // 射线检测层掩码[Header("碰撞器设置")][Tooltip("是否启用碰撞器更新")]public bool EnableCollider = true;[Tooltip("碰撞器是否为触发器")]public bool ColliderIsTrigger = true;[Header("调试设置")][Tooltip("是否显示调试信息")]public bool ShowDebugInfo = true;[Tooltip("影响范围可视化颜色")]public Color InfluenceRangeColor = Color.green;[Tooltip("目标点标记颜色")]public Color TargetPointColor = Color.red;[Header("高度补偿设置")][Tooltip("高度补偿强度,控制壁变化时的高度调整幅度")]public float HeightCompensationStrength = 0.1f;[Tooltip("最大高度补偿量,限制高度变化幅度")]public float MaxHeightCompensation = 0.05f;[Tooltip("最小允许高度,防止陶器变得过扁")]public float MinAllowedHeight = 0.1f;// 内部变量private Mesh theMesh;private MeshFilter meshFilter;private MeshRenderer meshRenderer;private MeshCollider meshCollider;private bool isShaping = false;private Vector3 targetWorldPos;private Vector3 lastScreenPos;private int SplitIndex; // 区分内外表面的顶点索引private bool isRightClick = false; // 标记是否为右键操作// 计算属性private float SqrMaxOuterRadius => MaxRadius * MaxRadius;private float SqrMinOuterRadius => MinRadius * MinRadius;private float SqrMaxInnerRadius => (MaxRadius - Thickness) * (MaxRadius - Thickness);private float SqrMinInnerRadius => (MinRadius - Thickness) * (MinRadius - Thickness);/// <summary>/// 初始化函数,在游戏开始时调用/// </summary>void Start(){InitializeMesh();GeneratePottery();if (EnableCollider){AddCollider();}Debug.Log("陶艺制作器初始化完成!");Debug.Log("使用说明:左键扩大,右键缩小,垂直拖拽调整高度");}/// <summary>/// 每帧更新函数,处理用户输入/// </summary>void Update(){HandleInput();// 在编辑器中绘制调试信息if (ShowDebugInfo && Application.isEditor){DebugDrawInfluenceRange();}}/// <summary>/// 在场景视图中绘制Gizmos/// </summary>private void OnDrawGizmos(){if (ShowDebugInfo && Application.isPlaying){// 绘制影响范围Gizmos.color = InfluenceRangeColor;Gizmos.DrawWireSphere(targetWorldPos, InfluenceLayer * LayerHeight);// 绘制目标点十字标记Gizmos.color = TargetPointColor;float crossSize = 0.1f;Gizmos.DrawLine(targetWorldPos - Vector3.right * crossSize, targetWorldPos + Vector3.right * crossSize);Gizmos.DrawLine(targetWorldPos - Vector3.up * crossSize, targetWorldPos + Vector3.up * crossSize);Gizmos.DrawLine(targetWorldPos - Vector3.forward * crossSize, targetWorldPos + Vector3.forward * crossSize);}}/// <summary>/// 初始化网格组件/// </summary>private void InitializeMesh(){meshFilter = gameObject.GetComponent<MeshFilter>();if (meshFilter == null)meshFilter = gameObject.AddComponent<MeshFilter>();meshRenderer = gameObject.GetComponent<MeshRenderer>();if (meshRenderer == null)meshRenderer = gameObject.AddComponent<MeshRenderer>();// 检查材质是否已赋值,如果没有则创建默认材质if (PotteryMaterial == null){Debug.LogWarning("陶艺材质未赋值,正在创建默认材质...");CreateDefaultMaterial();}meshRenderer.material = PotteryMaterial;theMesh = new Mesh();theMesh.name = "Pottery";meshFilter.mesh = theMesh;}/// <summary>/// 创建默认材质(当用户未指定材质时使用)/// </summary>private void CreateDefaultMaterial(){// 创建默认的陶艺材质Shader standardShader = Shader.Find("Standard");if (standardShader != null){PotteryMaterial = new Material(standardShader);PotteryMaterial.name = "DefaultPotteryMaterial";PotteryMaterial.color = new Color(0.8f, 0.6f, 0.4f, 1.0f); // 陶土色PotteryMaterial.SetFloat("_Glossiness", 0.3f); // 设置光泽度Debug.Log("已创建默认陶艺材质: " + PotteryMaterial.name);}else{Debug.LogError("无法找到Standard着色器,请确保项目中包含Standard着色器!");// 创建最简单的材质作为备选PotteryMaterial = new Material(Shader.Find("Diffuse"));if (PotteryMaterial != null){PotteryMaterial.color = Color.gray;Debug.LogWarning("使用Diffuse着色器作为备选材质");}else{Debug.LogError("无法创建任何材质,渲染可能不正常!");}}}/// <summary>/// 添加网格碰撞器/// </summary>public void AddCollider(){// 移除旧的碰撞器MeshCollider[] oldColliders = GetComponents<MeshCollider>();foreach (var collider in oldColliders){DestroyImmediate(collider);}// 添加新的网格碰撞器meshCollider = gameObject.AddComponent<MeshCollider>();meshCollider.sharedMesh = theMesh;meshCollider.convex = false; // 非凸碰撞器,适合复杂形状meshCollider.isTrigger = ColliderIsTrigger; // 设置为触发器避免物理交互冲突Debug.Log("陶器碰撞器已添加,触发器模式: " + ColliderIsTrigger);}/// <summary>/// 更新碰撞器以匹配当前网格/// </summary>public void UpdateCollider(){if (meshCollider != null && EnableCollider){meshCollider.sharedMesh = null; // 先清空引用meshCollider.sharedMesh = theMesh; // 重新赋值以更新碰撞器}}/// <summary>/// 生成陶器网格/// </summary>private void GeneratePottery(){List<Vector3> vertices = new List<Vector3>();List<int> triangles = new List<int>();List<Vector2> uvs = new List<Vector2>();float deltaAngle = 2 * Mathf.PI / Details;// 生成各部分CreateBottom(deltaAngle, vertices, triangles, uvs);CreateOuter(deltaAngle, vertices, triangles, uvs);CreateTop(deltaAngle, vertices, triangles, uvs);CreateInner(deltaAngle, vertices, triangles, uvs);CreateInnerBottom(deltaAngle, vertices, triangles, uvs);// 设置网格数据theMesh.vertices = vertices.ToArray();theMesh.triangles = triangles.ToArray();theMesh.uv = uvs.ToArray();theMesh.RecalculateBounds();theMesh.RecalculateNormals();SmoothNormals();// 记录内外表面的分界点SplitIndex = Details + 1 + (LayerCount + 1) * (Details + 1);Debug.Log("陶器生成完成,顶点数: " + vertices.Count + ", 三角形数: " + triangles.Count / 3);}/// <summary>/// 创建底部网格/// </summary>private void CreateBottom(float deltaAngle, List<Vector3> vertices, List<int> triangles, List<Vector2> uvs){// 外底中心点作为整个模型的第一个顶点vertices.Add(Vector3.zero);uvs.Add(new Vector2(0.25f, 0.25f));int index = 1;for (int i = 0; i < Details; ++i){float angle = i * deltaAngle;float cosAngle = Mathf.Cos(angle);float sinAngle = Mathf.Sin(angle);Vector3 v = new Vector3(Radius * cosAngle, 0, Radius * sinAngle);vertices.Add(v);// 添加三角形triangles.Add(index);triangles.Add((index >= Details) ? 1 : index + 1);triangles.Add(0);// 计算UVVector2 u = new Vector2(0.25f + 0.25f * cosAngle, 0.25f + 0.25f * sinAngle);uvs.Add(u);++index;}}/// <summary>/// 创建外壁网格/// </summary>private void CreateOuter(float deltaAngle, List<Vector3> vertices, List<int> triangles, List<Vector2> uvs){for (int layer = 0; layer <= LayerCount; ++layer){float height = layer * LayerHeight;int vIndex = vertices.Count;int lastIndex = vIndex - Details - 1;int vIndexAddOne = vIndex + 1;int lastIndexAddOne = lastIndex + 1;float v = ((float)layer / LayerCount) * 0.4f + 0.5f;for (int i = 0; i <= Details; ++i){float angle = (i == Details) ? 0 : i * deltaAngle;float cosAngle = Mathf.Cos(angle);float sinAngle = Mathf.Sin(angle);Vector3 vo = new Vector3(Radius * cosAngle, height, Radius * sinAngle);vertices.Add(vo);if (layer > 0 && i < Details){triangles.Add(vIndex + i);triangles.Add(vIndexAddOne + i);triangles.Add(lastIndex + i);triangles.Add(lastIndex + i);triangles.Add(vIndexAddOne + i);triangles.Add(lastIndexAddOne + i);}float u = (float)i / Details;Vector2 uv = new Vector2(u, v);uvs.Add(uv);}}}/// <summary>/// 创建顶部网格/// </summary>private void CreateTop(float deltaAngle, List<Vector3> vertices, List<int> triangles, List<Vector2> uvs){float inner = Radius - Thickness;for (int h = 0; h < 2; ++h){float height = (LayerCount - h) * LayerHeight;int vIndex = vertices.Count;int lastIndex = vIndex - Details - 1;int vIndexAddOne = vIndex + 1;int lastIndexAddOne = lastIndex + 1;float v = 0.95f + h * 0.05f;for (int i = 0; i <= Details; ++i){float angle = (i == Details) ? 0 : i * deltaAngle;float cosAngle = Mathf.Cos(angle);float sinAngle = Mathf.Sin(angle);Vector3 vo = new Vector3(inner * cosAngle, height, inner * sinAngle);vertices.Add(vo);if (i < Details){triangles.Add(vIndex + i);triangles.Add(vIndexAddOne + i);triangles.Add(lastIndex + i);triangles.Add(lastIndex + i);triangles.Add(vIndexAddOne + i);triangles.Add(lastIndexAddOne + i);}float u = (float)i / Details;Vector2 uv = new Vector2(u, v);uvs.Add(uv);}}}/// <summary>/// 创建内壁网格/// </summary>private void CreateInner(float deltaAngle, List<Vector3> vertices, List<int> triangles, List<Vector2> uvs){float inner = Radius - Thickness;int count = LayerCount - 1;for (int layer = 0; layer < count; ++layer){float height = (LayerCount - layer - 1) * LayerHeight;int vIndex = vertices.Count;int lastIndex = vIndex - Details - 1;int vIndexAddOne = vIndex + 1;int lastIndexAddOne = lastIndex + 1;float v = 0.5f - ((float)layer / count) * 0.5f;for (int i = 0; i <= Details; ++i){float angle = (i == Details) ? 0 : i * deltaAngle;float cosAngle = Mathf.Cos(angle);float sinAngle = Mathf.Sin(angle);Vector3 vo = new Vector3(inner * cosAngle, height, inner * sinAngle);vertices.Add(vo);if (layer > 0 && i < Details){triangles.Add(vIndex + i);triangles.Add(vIndexAddOne + i);triangles.Add(lastIndex + i);triangles.Add(lastIndex + i);triangles.Add(vIndexAddOne + i);triangles.Add(lastIndexAddOne + i);}float u = (float)i / Details * 0.5f + 0.5f;Vector2 uv = new Vector2(u, v);uvs.Add(uv);}}}/// <summary>/// 创建内底网格/// </summary>private void CreateInnerBottom(float deltaAngle, List<Vector3> vertices, List<int> triangles, List<Vector2> uvs){int index = vertices.Count;float inner = Radius - Thickness;for (int i = 0; i < Details; ++i){float angle = i * deltaAngle;float cosAngle = Mathf.Cos(angle);float sinAngle = Mathf.Sin(angle);Vector3 v = new Vector3(inner * cosAngle, LayerHeight, inner * sinAngle);vertices.Add(v);triangles.Add(index + Details);triangles.Add((i >= Details - 1) ? index : index + i + 1);triangles.Add(index + i);Vector2 u = new Vector2(0.75f + 0.25f * cosAngle, 0.25f + 0.25f * sinAngle);uvs.Add(u);}vertices.Add(new Vector3(0, LayerHeight, 0));uvs.Add(new Vector2(0.75f, 0.25f));}/// <summary>/// 处理用户输入/// </summary>private void HandleInput(){// 检测鼠标右键按下if (Input.GetMouseButtonDown(1)){isRightClick = true;lastScreenPos = Input.mousePosition;if (Physics.Raycast(Camera.main.ScreenPointToRay(lastScreenPos), out RaycastHit hit, 1000f, PotteryLayerMask)){// 验证点击的是当前游戏对象if (hit.collider.gameObject == this.gameObject){targetWorldPos = hit.point;isShaping = true;Debug.Log("右键操作开始 - 缩小模式");}}else{isShaping = false;}}else if (Input.GetMouseButtonUp(1)){isRightClick = false;isShaping = false;}// 检测鼠标左键按下if (Input.GetMouseButtonDown(0)){isRightClick = false;lastScreenPos = Input.mousePosition;if (Physics.Raycast(Camera.main.ScreenPointToRay(lastScreenPos), out RaycastHit hit, 1000f, PotteryLayerMask)){// 验证点击的是当前游戏对象if (hit.collider.gameObject == this.gameObject){targetWorldPos = hit.point;isShaping = true;Debug.Log("左键操作开始 - 扩大模式");}}else{isShaping = false;}}else if (Input.GetMouseButtonUp(0)){isShaping = false;}// 处理鼠标移动操作if (isShaping && (Input.GetMouseButton(0) || Input.GetMouseButton(1))){Vector3 currPos = Input.mousePosition;ShapeIt(currPos - lastScreenPos);lastScreenPos = currPos;}// 如果检测到触控用触控,否则用鼠标if (Input.touchCount == 1){Touch touch = Input.GetTouch(0);if (touch.phase == TouchPhase.Began){if (Physics.Raycast(Camera.main.ScreenPointToRay(touch.position), out RaycastHit hit, 1000f, PotteryLayerMask)){// 验证点击的是当前游戏对象if (hit.collider.gameObject == this.gameObject){targetWorldPos = hit.point;isShaping = true;}}else{isShaping = false;}}else if (isShaping && (touch.phase == TouchPhase.Moved)){ShapeIt(touch.deltaPosition);}else if (touch.phase == TouchPhase.Ended){isShaping = false;}}}/// <summary>/// 形状变形处理/// </summary>/// <param name="deltaPos">鼠标/触控移动增量</param>private void ShapeIt(Vector3 deltaPos){bool bHorizontal = false;bool bVertical = false;float dirRate = 0, scale = 0;if (Mathf.Abs(deltaPos.x) > 0.01f){// 根据左右键选择操作方向float powerMultiplier = isRightClick ? RightClickPowerMultiplier : LeftClickPowerMultiplier;dirRate = IsInRight() ? Mathf.Sign(deltaPos.x) : -Mathf.Sign(deltaPos.x);dirRate *= powerMultiplier;bHorizontal = true;}if (Mathf.Abs(deltaPos.y) > 0.02f){scale = Mathf.Sign(deltaPos.y) * 0.001f;bVertical = true;}if (bHorizontal){Vector3 targetPos = transform.InverseTransformPoint(targetWorldPos);Vector3[] vertices = theMesh.vertices;int maxVerticesIndex = vertices.Length - 1;float Range = InfluenceLayer * LayerHeight;// 记录原始平均高度,用于高度补偿float originalAverageHeight = CalculateAverageHeight(vertices);for (int i = 1; i < maxVerticesIndex; ++i){float max, min;if (i < SplitIndex){max = SqrMaxOuterRadius;min = SqrMinOuterRadius;}else{max = SqrMaxInnerRadius;min = SqrMinInnerRadius;}float dis = Mathf.Abs(targetPos.y - vertices[i].y);if (dis < Range){Vector3 dir = vertices[i];dir.y = 0;if ((dirRate > 0 && dir.sqrMagnitude < max) || (dirRate < 0 && dir.sqrMagnitude > min))vertices[i] += dir.normalized * dirRate * TouchPower * (1f - dis / Range);}}// 应用高度补偿:壁扩大时变矮,壁缩小时变高ApplyHeightCompensation(vertices, originalAverageHeight, dirRate);theMesh.vertices = vertices;theMesh.RecalculateBounds();theMesh.RecalculateNormals();SmoothNormals();// 更新碰撞器UpdateCollider();}if (bVertical){Vector3 sc = transform.localScale;sc.y = Mathf.Clamp(sc.y + scale, 0.5f, 1.5f);transform.localScale = sc;}}/// <summary>/// 计算网格的平均高度/// </summary>private float CalculateAverageHeight(Vector3[] vertices){float totalHeight = 0f;int count = 0;for (int i = 1; i < vertices.Length; i++){// 只计算外壁和内壁的顶点,排除底部中心点if (vertices[i].y > 0){totalHeight += vertices[i].y;count++;}}return count > 0 ? totalHeight / count : 0f;}/// <summary>/// 应用高度补偿:壁扩大时变矮,壁缩小时变高/// </summary>private void ApplyHeightCompensation(Vector3[] vertices, float originalAverageHeight, float dirRate){// 计算高度变化量(壁扩大时高度降低,壁缩小时高度增加)float heightChange = -dirRate * HeightCompensationStrength; // 负号表示反向关系// 限制高度变化幅度heightChange = Mathf.Clamp(heightChange, -MaxHeightCompensation, MaxHeightCompensation);// 应用高度补偿到所有相关顶点for (int i = 1; i < vertices.Length; i++){// 只调整外壁和内壁的顶点高度,保持底部不变if (vertices[i].y > 0){vertices[i].y += heightChange;// 确保高度不会低于最小值vertices[i].y = Mathf.Max(vertices[i].y, MinAllowedHeight);}}if (ShowDebugInfo){Debug.Log($"高度补偿应用: 方向={dirRate}, 变化量={heightChange:F3}, 强度={HeightCompensationStrength}");}}/// <summary>/// 判断目标点是否在右侧/// </summary>/// <returns>true如果在右侧,否则false</returns>private bool IsInRight(){Transform camera = Camera.main.transform;Vector3 target = camera.InverseTransformPoint(targetWorldPos);Vector3 pottery = camera.InverseTransformPoint(transform.position);return target.x > pottery.x;}/// <summary>/// 平滑法线/// </summary>private void SmoothNormals(){Vector3[] normals = theMesh.normals;int step = Details + 1;int start = step;int end = start + (LayerCount + 3) * step;for (int i = start; i < end; i += step){int index1 = i;int index2 = i + Details;Vector3 normal = ((normals[index1] + normals[index2]) / 2f).normalized;normals[index1] = normal;normals[index2] = normal;}theMesh.normals = normals;}/// <summary>/// 调试绘制影响范围/// </summary>private void DebugDrawInfluenceRange(){if (isShaping){// 绘制影响范围球体DebugDrawWireSphere(targetWorldPos, InfluenceLayer * LayerHeight, InfluenceRangeColor);// 绘制目标点十字标记DebugDrawCross(targetWorldPos, 0.1f, TargetPointColor);// 显示操作信息string operationType = isRightClick ? "缩小" : "扩大";Debug.Log($"当前操作: {operationType}, 影响半径: {InfluenceLayer * LayerHeight:F2}");}}/// <summary>/// 调试绘制线框球体/// </summary>private void DebugDrawWireSphere(Vector3 center, float radius, Color color){int segments = 16;float angleIncrement = 360f / segments;for (int i = 0; i < segments; i++){float angle = i * angleIncrement * Mathf.Deg2Rad;float nextAngle = (i + 1) * angleIncrement * Mathf.Deg2Rad;Vector3 start = center + new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius;Vector3 end = center + new Vector3(Mathf.Cos(nextAngle), 0, Mathf.Sin(nextAngle)) * radius;Debug.DrawLine(start, end, color);start = center + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;end = center + new Vector3(Mathf.Cos(nextAngle), Mathf.Sin(nextAngle), 0) * radius;Debug.DrawLine(start, end, color);start = center + new Vector3(0, Mathf.Cos(angle), Mathf.Sin(angle)) * radius;end = center + new Vector3(0, Mathf.Cos(nextAngle), Mathf.Sin(nextAngle)) * radius;Debug.DrawLine(start, end, color);}}/// <summary>/// 调试绘制十字标记/// </summary>private void DebugDrawCross(Vector3 center, float size, Color color){Debug.DrawLine(center - Vector3.right * size, center + Vector3.right * size, color);Debug.DrawLine(center - Vector3.up * size, center + Vector3.up * size, color);Debug.DrawLine(center - Vector3.forward * size, center + Vector3.forward * size, color);}/// <summary>/// 调试用:显示法线/// </summary>private void ShowNormals(){Vector3[] vertices = theMesh.vertices;Vector3[] normals = theMesh.normals;for (int i = 0; i < vertices.Length; i++){Debug.DrawRay(transform.TransformPoint(vertices[i]), transform.TransformDirection(normals[i]) * 0.1f, Color.red);}}/// <summary>/// 重新生成陶器(公开方法,可用于重置)/// </summary>public void RegeneratePottery(){GeneratePottery();if (EnableCollider){UpdateCollider();}}/// <summary>/// 设置陶器参数并重新生成/// </summary>public void SetParameters(int details, int layerCount, float radius, float thickness){Details = details;LayerCount = layerCount;Radius = radius;Thickness = thickness;RegeneratePottery();}
}
参考:https://blog.csdn.net/sdhexu/article/details/117991357?spm=1001.2014.3001.5502