using UnityEngine; using System.Collections; using System.Collections.Generic; public class WireDrawingSystem : MonoBehaviour { public enum DrawingState { Idle, // 空闲状态 SelectingStart, // 选择起点 SelectingEnd // 选择终点 } [Header("连线设置")] public Material wireMaterial; public Color wireColor = Color.red; public float wireWidth = 0.02f; [Header("吸附设置")] public bool enableSnapping = true; // 启用吸附功能 public float snapDistance = 1.0f; // 吸附距离 public LayerMask snapLayers = -1; // 可吸附的层 public bool snapToColliderCenter = true; // 吸附到碰撞体中心 public bool snapToTransformCenter = false; // 吸附到变换组件中心 public Color snapHighlightColor = Color.cyan; // 吸附高亮颜色 [Header("碰撞设置")] public bool requireColliderForConnection = true; // 要求物体必须有碰撞体才能连线 public bool addColliderToWires = false; // 是否为连线添加碰撞器 [Header("状态显示")] public DrawingState currentState = DrawingState.Idle; // 连线数据 private Vector3 startPoint; private Vector3 endPoint; private LineRenderer currentWire; private GameObject currentWireObject; // 吸附相关 private GameObject snapTarget; // 当前吸附目标 private Renderer snapTargetRenderer; // 吸附目标的渲染器 private Color snapTargetOriginalColor; // 吸附目标原始颜色 // 修复问题:记录鼠标点击的实际位置 private Vector3 actualMousePosition; /// /// 接线头样式 /// [Header("接线头样式")] public GameObject LineModel; [Header("连接点模型设置")] public GameObject connectionPointPrefab; // 连接点模型预制体 public float connectionPointScale = 0.1f; // 连接点模型缩放 public Material connectionPointMaterial; // 连接点材质 [Header("吸附点预览")] public GameObject snapPreviewPrefab; // 吸附点预览预制体 private GameObject currentSnapPreview; // 当前吸附点预览 private List connectionPoints = new List(); // 存储所有连接点 [Header("删除设置")] public KeyCode deleteKey = KeyCode.Mouse1; // 默认为鼠标右键 public float deleteDetectionRadius = 0.5f; // 删除检测半径 // 存储所有已创建的连线和连接点 private List allWires = new List(); private List allConnectionPoints = new List(); void Update() { HandleInput(); UpdateWirePreview(); // 在选择终点状态时检查吸附 if (currentState == DrawingState.SelectingEnd && enableSnapping) { CheckForSnapTargets(); UpdateSnapPreview(); } else { // 不在选择终点状态时,清除吸附点预览 ClearSnapPreview(); } } /// /// 在指定位置创建连接点模型 /// /// 连接点位置 /// 是否为起点 // 修改现有的创建连接点方法,添加记录功能 void CreateConnectionPoint(Vector3 position, bool isStartPoint) { if (connectionPointPrefab == null) { Debug.LogWarning("连接点预制体未设置,无法创建连接点模型"); return; } GameObject connectionPoint = Instantiate(connectionPointPrefab); connectionPoint.name = $"ConnectionPoint_{(isStartPoint ? "Start" : "End")}_{System.DateTime.Now:HHmmss}"; connectionPoint.transform.position = position; connectionPoint.transform.localScale = Vector3.one * connectionPointScale; // 设置材质 if (connectionPointMaterial != null) { Renderer renderer = connectionPoint.GetComponent(); if (renderer != null) { renderer.material = connectionPointMaterial; } } // 添加到连接点列表 allConnectionPoints.Add(connectionPoint); Debug.Log($"创建连接点: {position}, 类型: {(isStartPoint ? "起点" : "终点")}"); } /// /// 删除未完成的连接点(用于取消操作时) /// void RemoveUnfinishedConnectionPoints() { // 删除最近创建但未完成的连接点 if (allConnectionPoints.Count > 0) { // 查找未附加到连线的连接点 List toRemove = new List(); foreach (GameObject point in allConnectionPoints) { if (point != null && point.transform.parent == null) { toRemove.Add(point); } } // 删除这些连接点 foreach (GameObject point in toRemove) { allConnectionPoints.Remove(point); Destroy(point); } } } /// /// 清除所有连接点 /// public void ClearAllConnectionPoints() { foreach (GameObject point in connectionPoints) { if (point != null) Destroy(point); } connectionPoints.Clear(); } /// /// 删除所有连线和连接点 /// public void ClearAll() { // 删除所有连线 foreach (GameObject wire in allWires) { if (wire != null) Destroy(wire); } allWires.Clear(); // 删除所有连接点 foreach (GameObject point in allConnectionPoints) { if (point != null) Destroy(point); } allConnectionPoints.Clear(); // 清除吸附点预览 ClearSnapPreview(); Debug.Log("已清除所有连线和连接点"); } /// /// 将连接点附加到连线对象上 /// /// 连线对象 void AttachConnectionPointsToWire(GameObject wireObject) { // 查找最近创建的两个未附加的连接点 List unattachedPoints = new List(); foreach (GameObject point in allConnectionPoints) { if (point != null && point.transform.parent == null) { unattachedPoints.Add(point); } } // 将最近的两个连接点附加到连线 if (unattachedPoints.Count >= 2) { for (int i = unattachedPoints.Count - 1; i >= Mathf.Max(0, unattachedPoints.Count - 2); i--) { unattachedPoints[i].transform.SetParent(wireObject.transform); } } } void HandleInput() { if (Input.GetMouseButtonDown(0)) // 左键点击 { HandleMouseClick(); } if (Input.GetKeyDown(KeyCode.Escape))// 取消当前操作 { CancelDrawing(); } if (Input.GetMouseButtonDown(1)) { HandleDelete(); } } /// /// 处理删除操作 /// void HandleDelete() { // 如果正在绘制连线,先取消绘制 if (currentState != DrawingState.Idle) { CancelDrawing(); return; } // 清除吸附点预览 ClearSnapPreview(); // 查找鼠标位置附近的连线和连接点 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { // 检查是否点击到了连线或连接点 GameObject hitObject = hit.collider.gameObject; DeleteObjectIfWireOrConnection(hitObject); } else { // 如果没有击中任何物体,尝试删除最后一个创建的连线 DeleteLastWire(); } } /// /// 删除指定的连线或连接点 /// void DeleteObjectIfWireOrConnection(GameObject obj) { if (obj == null) return; // 检查是否是吸附预览 if (obj.name == "SnapPreview") { ClearSnapPreview(); return; } // 检查是否是连线 if (obj.name.StartsWith("Wire_") || obj.GetComponent() != null) { DeleteWire(obj); return; } // 检查是否是连接点 if (obj.name.StartsWith("ConnectionPoint_")) { DeleteConnectionPoint(obj); return; } // 检查是否是连线的一部分(子物体) if (obj.transform.parent != null) { GameObject parent = obj.transform.parent.gameObject; if (parent.name.StartsWith("Wire_") || parent.GetComponent() != null) { DeleteWire(parent); return; } } } /// /// 删除指定的连线 /// void DeleteWire(GameObject wireObject) { if (wireObject == null) return; // 删除连线的所有连接点(子物体) foreach (Transform child in wireObject.transform) { if (child.name.StartsWith("ConnectionPoint_")) { allConnectionPoints.Remove(child.gameObject); Destroy(child.gameObject); } } // 从列表中移除并销毁连线 allWires.Remove(wireObject); Destroy(wireObject); Debug.Log("删除连线: " + wireObject.name); // 清除吸附点预览 ClearSnapPreview(); } /// /// 删除指定的连接点 /// void DeleteConnectionPoint(GameObject connectionPoint) { if (connectionPoint == null) return; // 从列表中移除并销毁连接点 allConnectionPoints.Remove(connectionPoint); Destroy(connectionPoint); Debug.Log("删除连接点: " + connectionPoint.name); // 清除吸附点预览 ClearSnapPreview(); } /// /// 删除最后一个创建的连线 /// void DeleteLastWire() { if (allWires.Count > 0) { GameObject lastWire = allWires[allWires.Count - 1]; DeleteWire(lastWire); } else { Debug.Log("没有可删除的连线"); } } /// /// 删除最后一个连接点 /// void DeleteLastConnectionPoint() { if (allConnectionPoints.Count > 0) { GameObject lastPoint = allConnectionPoints[allConnectionPoints.Count - 1]; DeleteConnectionPoint(lastPoint); } else { Debug.Log("没有可删除的连接点"); } } void HandleMouseClick() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; // 获取鼠标的实际世界位置 actualMousePosition = GetMouseWorldPosition(); // 首先检查是否有吸附目标 if (snapTarget != null) { // 检查吸附目标是否有碰撞体 if (requireColliderForConnection && !HasCollider(snapTarget)) { Debug.LogWarning($"目标物体 {snapTarget.name} 没有碰撞体,无法连线"); return; } // 如果有吸附目标,使用吸附点的位置 Vector3 targetPoint = GetObjectCenter(snapTarget); switch (currentState) { case DrawingState.Idle: StartNewWire(targetPoint); break; case DrawingState.SelectingStart: break; case DrawingState.SelectingEnd: CompleteWire(targetPoint); break; } } else if (Physics.Raycast(ray, out hit, Mathf.Infinity, snapLayers)) { // 检查击中的物体是否有碰撞体 if (requireColliderForConnection && !HasCollider(hit.collider.gameObject)) { Debug.LogWarning($"目标物体 {hit.collider.gameObject.name} 没有碰撞体,无法连线"); return; } // 如果有击中物体,使用击中点的位置 Vector3 targetPoint = GetSnapPoint(hit); switch (currentState) { case DrawingState.Idle: StartNewWire(targetPoint); break; case DrawingState.SelectingStart: break; case DrawingState.SelectingEnd: CompleteWire(targetPoint); break; } } else { // 如果没有击中任何物体,且要求必须有碰撞体,则不允许连线 if (requireColliderForConnection) { Debug.LogWarning("没有找到带有碰撞体的物体,无法连线"); return; } // 如果没有击中任何物体,使用实际鼠标位置 switch (currentState) { case DrawingState.Idle: StartNewWire(actualMousePosition); break; case DrawingState.SelectingStart: break; case DrawingState.SelectingEnd: CompleteWire(actualMousePosition); break; } } } // 检查物体是否有碰撞体 bool HasCollider(GameObject obj) { if (obj == null) return false; // 检查所有类型的碰撞器 Collider collider3D = obj.GetComponent(); Collider2D collider2D = obj.GetComponent(); return (collider3D != null || collider2D != null); } void StartNewWire(Vector3 point) { startPoint = point; currentState = DrawingState.SelectingEnd; // 在这里添加:创建起点连接点模型 CreateConnectionPoint(point, true); // 创建连线预览 CreateWirePreview(); Debug.Log("选择起点: " + point); } void CompleteWire(Vector3 point) { endPoint = point; currentState = DrawingState.Idle; // 在这里添加:创建终点连接点模型 CreateConnectionPoint(point, false); // 完成连线 FinalizeWire(); Debug.Log("选择终点: " + point); Debug.Log($"连线完成: {startPoint} -> {endPoint}"); // 清除吸附高亮 ClearSnapHighlight(); } void CreateWirePreview() { currentWireObject = new GameObject("WirePreview"); currentWire = currentWireObject.AddComponent(); ConfigureLineRenderer(currentWire); currentWire.positionCount = 2; currentWire.SetPosition(0, startPoint); currentWire.SetPosition(1, startPoint); // 初始时终点与起点相同 } void UpdateWirePreview() { if (currentState == DrawingState.SelectingEnd && currentWire != null) { Vector3 targetPoint; // 如果有吸附目标,使用吸附点 if (snapTarget != null) { targetPoint = GetObjectCenter(snapTarget); } else { // 否则使用实际鼠标位置 targetPoint = GetMouseWorldPosition(); } currentWire.SetPosition(1, targetPoint); } } void CheckForSnapTargets() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; // 清除之前的吸附高亮 ClearSnapHighlight(); // 检查鼠标位置附近是否有可吸附的物体 if (Physics.Raycast(ray, out hit, Mathf.Infinity, snapLayers)) { GameObject hitObject = hit.collider.gameObject; // 如果要求碰撞体但物体没有碰撞体,跳过 if (requireColliderForConnection && !HasCollider(hitObject)) { return; } // 计算鼠标位置与物体中心的距离 Vector3 objectCenter = GetObjectCenter(hitObject); float distance = Vector3.Distance(GetMouseWorldPosition(), objectCenter); if (distance <= snapDistance) { // 设置吸附目标 snapTarget = hitObject; // 高亮显示可吸附的物体 HighlightSnapTarget(snapTarget); } } } Vector3 GetSnapPoint(RaycastHit hit) { if (!enableSnapping) return hit.point; GameObject hitObject = hit.collider.gameObject; Vector3 objectCenter = GetObjectCenter(hitObject); // 检查是否在吸附距离内 float distance = Vector3.Distance(hit.point, objectCenter); if (distance <= snapDistance) { return objectCenter; } return hit.point; } Vector3 GetObjectCenter(GameObject obj) { if (snapToColliderCenter) { Collider collider = obj.GetComponent(); if (collider != null) { return collider.bounds.center; } } if (snapToTransformCenter) { return obj.transform.position; } // 默认使用碰撞体中心 Collider defaultCollider = obj.GetComponent(); if (defaultCollider != null) { return defaultCollider.bounds.center; } // 如果都没有,使用变换位置 return obj.transform.position; } void HighlightSnapTarget(GameObject target) { snapTargetRenderer = target.GetComponent(); if (snapTargetRenderer != null) { snapTargetOriginalColor = snapTargetRenderer.material.color; snapTargetRenderer.material.color = snapHighlightColor; } } void ClearSnapHighlight() { if (snapTargetRenderer != null) { snapTargetRenderer.material.color = snapTargetOriginalColor; snapTargetRenderer = null; } snapTarget = null; } // 修改 FinalizeWire 方法,添加记录功能 void FinalizeWire() { if (currentWire != null) { currentWireObject.name = $"Wire_{System.DateTime.Now:yyyyMMddHHmmss}"; // 将连接点附加到连线对象 AttachConnectionPointsToWire(currentWireObject); // 添加到连线列表 allWires.Add(currentWireObject); // 可选:添加物理碰撞器 if (addColliderToWires) { AddWireCollider(currentWire); } // 保存连线数据 WireData data = currentWireObject.AddComponent(); data.startPoint = startPoint; data.endPoint = endPoint; data.creationTime = System.DateTime.Now; if (snapTarget != null) { data.snapEndObject = snapTarget; } } currentWire = null; currentWireObject = null; } // 修改 CancelDrawing 方法,确保正确清理 void CancelDrawing() { if (currentWireObject != null) { Destroy(currentWireObject); } // 删除未完成的连接点 RemoveUnfinishedConnectionPoints(); // 清除吸附点预览 ClearSnapPreview(); currentState = DrawingState.Idle; currentWire = null; currentWireObject = null; ClearSnapHighlight(); Debug.Log("取消连线操作"); } /// /// 更新吸附点预览模型 /// void UpdateSnapPreview() { if (snapPreviewPrefab == null) return; // 如果有吸附目标,显示预览 if (snapTarget != null) { if (currentSnapPreview == null) { currentSnapPreview = Instantiate(snapPreviewPrefab); currentSnapPreview.name = "SnapPreview"; currentSnapPreview.transform.localScale = Vector3.one * connectionPointScale * 0.8f; // 设置为半透明 Renderer renderer = currentSnapPreview.GetComponent(); if (renderer != null) { Color color = renderer.material.color; color.a = 0.5f; renderer.material.color = color; } } Vector3 snapPosition = GetObjectCenter(snapTarget); currentSnapPreview.transform.position = snapPosition; } else if (currentSnapPreview != null) { // 没有吸附目标时隐藏预览 ClearSnapPreview(); } } /// /// 清除吸附点预览 /// void ClearSnapPreview() { if (currentSnapPreview != null) { Destroy(currentSnapPreview); currentSnapPreview = null; } } void ConfigureLineRenderer(LineRenderer lr) { if (wireMaterial != null) { lr.material = wireMaterial; } else { // 使用默认材质 lr.material = new Material(Shader.Find("Sprites/Default")); } lr.startColor = wireColor; lr.endColor = wireColor; lr.startWidth = wireWidth; lr.endWidth = wireWidth; lr.useWorldSpace = true; } // 修复:改进的碰撞器添加方法 void AddWireCollider(LineRenderer lr) { if (lr.positionCount < 2) { Debug.LogWarning("连线点数不足,无法添加碰撞器"); return; } // 创建胶囊碰撞器而不是网格碰撞器,避免Convex Mesh错误 CreateCapsuleCollidersAlongLine(lr); } // 修复:使用胶囊碰撞器替代网格碰撞器 void CreateCapsuleCollidersAlongLine(LineRenderer lr) { // 获取连线上的所有点 Vector3[] positions = new Vector3[lr.positionCount]; lr.GetPositions(positions); // 为连线的每个线段创建胶囊碰撞器 for (int i = 0; i < positions.Length - 1; i++) { Vector3 start = positions[i]; Vector3 end = positions[i + 1]; // 计算线段长度和方向 float distance = Vector3.Distance(start, end); if (distance < 0.01f) continue; // 跳过太短的线段 Vector3 direction = (end - start).normalized; // 创建胶囊碰撞器 CapsuleCollider capsule = currentWireObject.AddComponent(); // 设置胶囊碰撞器的方向和位置 capsule.direction = 2; // Z轴方向 capsule.height = distance; capsule.radius = wireWidth / 2; // 计算胶囊的位置和旋转 capsule.center = start + direction * distance / 2; // 设置胶囊的方向 if (direction != Vector3.zero) { capsule.transform.rotation = Quaternion.LookRotation(direction); } capsule.isTrigger = true; } } // 修复:改进的鼠标位置获取方法 Vector3 GetMouseWorldPosition() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; // 优先使用吸附目标的位置 if (snapTarget != null) { return GetObjectCenter(snapTarget); } // 其次使用射线击中的位置 if (Physics.Raycast(ray, out hit, Mathf.Infinity, snapLayers)) { // 检查击中的物体是否有碰撞体 if (requireColliderForConnection && !HasCollider(hit.collider.gameObject)) { // 如果没有碰撞体,继续寻找其他点 return FindAlternativePosition(ray); } return GetSnapPoint(hit); } // 如果没有击中任何物体,使用默认平面 return FindAlternativePosition(ray); } // 寻找替代位置(当没有碰撞体时) Vector3 FindAlternativePosition(Ray ray) { // 尝试在默认平面上寻找位置 Plane defaultPlane = new Plane(Vector3.up, Vector3.zero); float enter; if (defaultPlane.Raycast(ray, out enter)) { return ray.GetPoint(enter); } // 最后使用射线上的一个点 return ray.origin + ray.direction * 10f; } // 公共方法:开始新连线 public void StartNewWire() { if (currentState != DrawingState.Idle) { CancelDrawing(); } currentState = DrawingState.SelectingStart; } // 公共方法:设置吸附目标(用于编程控制) public void SetSnapTarget(GameObject target) { if (enableSnapping && target != null) { // 检查目标是否有碰撞体 if (requireColliderForConnection && !HasCollider(target)) { Debug.LogWarning($"目标物体 {target.name} 没有碰撞体,无法设置为吸附目标"); return; } snapTarget = target; HighlightSnapTarget(target); } } // 公共方法:清除吸附目标 public void ClearSnapTarget() { ClearSnapHighlight(); } // 公共方法:检查物体是否可以连线 public bool CanConnectToObject(GameObject obj) { if (obj == null) return false; // 如果不需要碰撞体,任何物体都可以连线 if (!requireColliderForConnection) return true; // 检查物体是否有碰撞体 return HasCollider(obj); } }