ND_SimulationAutomaticControl/Assets/Scripts/Line/WireDrawingSystem.cs

900 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 接线头样式
/// </summary>
[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<GameObject> connectionPoints = new List<GameObject>(); // 存储所有连接点
[Header("删除设置")]
public KeyCode deleteKey = KeyCode.Mouse1; // 默认为鼠标右键
public float deleteDetectionRadius = 0.5f; // 删除检测半径
// 存储所有已创建的连线和连接点
private List<GameObject> allWires = new List<GameObject>();
private List<GameObject> allConnectionPoints = new List<GameObject>();
void Update()
{
HandleInput();
UpdateWirePreview();
// 在选择终点状态时检查吸附
if (currentState == DrawingState.SelectingEnd && enableSnapping)
{
CheckForSnapTargets();
UpdateSnapPreview();
}
else
{
// 不在选择终点状态时,清除吸附点预览
ClearSnapPreview();
}
}
/// <summary>
/// 在指定位置创建连接点模型
/// </summary>
/// <param name="position">连接点位置</param>
/// <param name="isStartPoint">是否为起点</param>
// 修改现有的创建连接点方法,添加记录功能
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<Renderer>();
if (renderer != null)
{
renderer.material = connectionPointMaterial;
}
}
// 添加到连接点列表
allConnectionPoints.Add(connectionPoint);
Debug.Log($"创建连接点: {position}, 类型: {(isStartPoint ? "" : "")}");
}
/// <summary>
/// 删除未完成的连接点(用于取消操作时)
/// </summary>
void RemoveUnfinishedConnectionPoints()
{
// 删除最近创建但未完成的连接点
if (allConnectionPoints.Count > 0)
{
// 查找未附加到连线的连接点
List<GameObject> toRemove = new List<GameObject>();
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);
}
}
}
/// <summary>
/// 清除所有连接点
/// </summary>
public void ClearAllConnectionPoints()
{
foreach (GameObject point in connectionPoints)
{
if (point != null)
Destroy(point);
}
connectionPoints.Clear();
}
/// <summary>
/// 删除所有连线和连接点
/// </summary>
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("已清除所有连线和连接点");
}
/// <summary>
/// 将连接点附加到连线对象上
/// </summary>
/// <param name="wireObject">连线对象</param>
void AttachConnectionPointsToWire(GameObject wireObject)
{
// 查找最近创建的两个未附加的连接点
List<GameObject> unattachedPoints = new List<GameObject>();
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();
}
}
/// <summary>
/// 处理删除操作
/// </summary>
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();
}
}
/// <summary>
/// 删除指定的连线或连接点
/// </summary>
void DeleteObjectIfWireOrConnection(GameObject obj)
{
if (obj == null) return;
// 检查是否是吸附预览
if (obj.name == "SnapPreview")
{
ClearSnapPreview();
return;
}
// 检查是否是连线
if (obj.name.StartsWith("Wire_") || obj.GetComponent<LineRenderer>() != 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<LineRenderer>() != null)
{
DeleteWire(parent);
return;
}
}
}
/// <summary>
/// 删除指定的连线
/// </summary>
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();
}
/// <summary>
/// 删除指定的连接点
/// </summary>
void DeleteConnectionPoint(GameObject connectionPoint)
{
if (connectionPoint == null) return;
// 从列表中移除并销毁连接点
allConnectionPoints.Remove(connectionPoint);
Destroy(connectionPoint);
Debug.Log("删除连接点: " + connectionPoint.name);
// 清除吸附点预览
ClearSnapPreview();
}
/// <summary>
/// 删除最后一个创建的连线
/// </summary>
void DeleteLastWire()
{
if (allWires.Count > 0)
{
GameObject lastWire = allWires[allWires.Count - 1];
DeleteWire(lastWire);
}
else
{
Debug.Log("没有可删除的连线");
}
}
/// <summary>
/// 删除最后一个连接点
/// </summary>
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<Collider>();
Collider2D collider2D = obj.GetComponent<Collider2D>();
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<LineRenderer>();
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<Collider>();
if (collider != null)
{
return collider.bounds.center;
}
}
if (snapToTransformCenter)
{
return obj.transform.position;
}
// 默认使用碰撞体中心
Collider defaultCollider = obj.GetComponent<Collider>();
if (defaultCollider != null)
{
return defaultCollider.bounds.center;
}
// 如果都没有,使用变换位置
return obj.transform.position;
}
void HighlightSnapTarget(GameObject target)
{
snapTargetRenderer = target.GetComponent<Renderer>();
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<WireData>();
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("取消连线操作");
}
/// <summary>
/// 更新吸附点预览模型
/// </summary>
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<Renderer>();
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();
}
}
/// <summary>
/// 清除吸附点预览
/// </summary>
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<CapsuleCollider>();
// 设置胶囊碰撞器的方向和位置
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);
}
}