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);
}
}