【Unity3D】点选物体、框选物体、绘制外边框

您所在的位置:网站首页 unity键盘游戏 【Unity3D】点选物体、框选物体、绘制外边框

【Unity3D】点选物体、框选物体、绘制外边框

#【Unity3D】点选物体、框选物体、绘制外边框| 来源: 网络整理| 查看: 265

1 需求描述

​ 绘制物体外框线条盒子 中介绍了绘制物体外框长方体的方法,本文将介绍物体投影到屏幕上的二维外框绘制方法。

点选物体:点击物体,可以选中物体,按住 Ctrl 追加选中,选中的物体设置为红色。 框选物体:拖拽鼠标,屏幕上会出现滑动框,滑动框内的物体会被选中,选中的物体设置为红色。 绘制外边框:给选中的物体绘制外边框(选中框)。

​ 滑动框效果如下:

img

​ 选中边框效果如下:

img

​ 本文完整代码见→ Unity3D点选物体、框选物体、绘制外边框。

2 需求实现 2.1 场景搭建

​ 1)场景对象

img

​ 说明:Plane 的 Layer 设置为 Plane (值为 6)。

​ 2)滑动框

​ 拖拽鼠标时,屏幕上会出现滑动框。SlideBox 对象用于显示滑动框,其 Image 组件中,Source Image 设置为红色滑动框 Sprite,去掉 Raycast Target 勾选,Image Type 设置为 Sliced;RectTransform 组件中,Pivot 设置为 (0, 0),Width、Height 都设置为 0。参数设置如下:

img

​ 滑动框图片如下:

img

​ 说明:滑动框图片是 png 格式,中间部分都是半透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,Sprite Mode 设置为 Multiple,并且需要在 Sprite Editor 中 编辑 border(九宫格格式),如下:

img

​ 2)选框

​ 选中物体后,选中的物体边界会显示外边框(选框)。SelectBox 对象用于显示选框,其 Image 组件中,Source Image 设置为黄色外框 Sprite,去掉 Raycast Target 勾选,Image Type 设置为 Sliced;RectTransform 组件中,Pivot 设置为 (0, 0),Width、Height 都设置为 0。参数设置如下:

img

​ 外框图片如下:

img

​ 说明:外框图片是 png 格式,并且除了黄色边角,其他部分都是透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,Sprite Mode 设置为 Multiple,并且需要在 Sprite Editor 中 编辑 border(九宫格格式),如下:

img

2.2 代码

​ EventDetector.cs

using UnityEngine; public class EventDetector : MonoBehaviour { // 事件检测器 private MyEventType eventType = MyEventType.None; // 事件类型 private MyEventType lastEventType = MyEventType.None; // 上次事件类型 private float scroll; // 滑轮滑动刻度 private bool detecting; // 事件检测中 private Vector3 clickDownMousePos; // 鼠标按下时的坐标 private const float dragThreshold = 1; // 识别为拖拽的鼠标偏移 private void Update() { detecting = true; DetectMouseEvent(); DetectScrollEvent(); UpgradeMouseEvent(); detecting = false; lastEventType = eventType; } private void DetectMouseEvent() { // 检测鼠标事件 if (Input.GetMouseButtonDown(0)) { // Click Down eventType = MyEventType.ClickDown; clickDownMousePos = Input.mousePosition; } else if (Input.GetMouseButtonUp(0)) { if (IsDragEvent(eventType)) { // End Drag eventType = MyEventType.EndDrag; } else { // Click Up eventType = MyEventType.ClickUp; } } else if (Input.GetMouseButton(0)) { if (IsDragEvent(eventType)) { // Drag eventType = MyEventType.Drag; } else if (Vector3.Distance(clickDownMousePos, Input.mousePosition) > dragThreshold) { // Begin Drag eventType = MyEventType.BeginDrag; } else { // Click eventType = MyEventType.Click; } } else { eventType = MyEventType.None; } } private void DetectScrollEvent() { // 检测滑轮事件 if (eventType != MyEventType.None && (!IsBeginEvent(eventType) || lastEventType != MyEventType.None && !IsScrollEvent(lastEventType))) { scroll = 0; return; } float temScroll = Input.GetAxis("Mouse ScrollWheel"); if (Mathf.Abs(scroll) < float.Epsilon && Mathf.Abs(temScroll) > float.Epsilon) { // Begin Scroll eventType = MyEventType.BeginScroll; scroll = temScroll; } else if (Mathf.Abs(scroll) > float.Epsilon && Mathf.Abs(temScroll) < float.Epsilon) { // End Scroll eventType = MyEventType.EndScroll; scroll = temScroll; } else if (Mathf.Abs(temScroll) > float.Epsilon) { // Scroll eventType = MyEventType.Scroll; scroll = temScroll; } else { scroll = 0; } } private void UpgradeMouseEvent() { // 升级鼠标事件(关联键盘事件) if (eventType == MyEventType.None) { return; } if (IsBeginEvent(eventType)) { if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) { AddKeyType("Ctrl"); } else if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) { AddKeyType("Alt"); } } else { ContinueKeyType(); // 保持按键事件 } } public MyEventType EventType() { // 事件类型 if (detecting) { return lastEventType; } return eventType; } public bool HasClickEvent() { // 是否有点击事件 MyEventType type = EventType(); return IsClickEvent(type); } public bool HasDragEvent() { // 是否有拖拽事件 MyEventType type = EventType(); return IsDragEvent(type); } public bool HasScrollEvent() { // 是否有滑轮事件 MyEventType type = EventType(); return IsScrollEvent(type); } public bool HasCtrlScrollEvent() { // 是否有Ctrl滑轮事件 MyEventType type = EventType(); return type >= MyEventType.BeginCtrlScroll && type = MyEventType.ClickDown && type = MyEventType.BeginDrag && type = MyEventType.BeginScroll && type = MyEventType.CtrlClickDown && type = MyEventType.BeginCtrlDrag && type = MyEventType.BeginCtrlScroll && type = MyEventType.BeginAltDrag && type loseFocus.Add(obj)); targets.Clear(); } else if (eventDetector.EventType() == MyEventType.CtrlClickUp) { if (targets.Contains(hitTrans)) { // Ctrl重复选中, 取消选中 loseFocus.Add(hitTrans); targets.Remove(hitTrans); } else { // Ctrl追加选中 targets.Add(hitTrans); } } else { // 单选 targets.ForEach(trans => loseFocus.Add(trans)); loseFocus.Remove(hitTrans); targets.Clear(); targets.Add(hitTrans); } UpdateSelectColor(); RectPainter.DrawRect(targets); } } private void UpdateSelectColor() { // 更新选中的物体颜色 foreach(var item in loseFocus) { item.GetComponent().material.color = Color.gray; } foreach(var item in targets) { item.GetComponent().material.color = Color.red; } loseFocus.Clear(); } private void SetTargets(List targets) { // 框选时触发 this.targets.ForEach(trans => loseFocus.Add(trans)); if (targets == null) { this.targets.Clear(); } else { this.targets = targets; this.targets.ForEach(trans => loseFocus.Remove(trans)); } UpdateSelectColor(); } private Transform GetHitTrans() { // 获取屏幕射线碰撞的物体 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out hit)) { return hit.transform; } return null; } }

​ 说明:ClickSelect 脚本组件挂在 Work 对象下,用于点选物体。

​ SlideSelect.cs

using System; using System.Collections.Generic; using UnityEngine; public class SlideSelect : MonoBehaviour { // 滑动框选物体 public Action targetsChangedHandler; // 框选目标改变时的处理器 private EventDetector eventDetector; // 鼠标事件检测器 private RectTransform slideTrans; // 滑动选框 private Vector3 preMousePos; // 鼠标滑动前的位置 private Transform work; // 需要检测是否被框选的物体根对象 private List targets; // 框选的目标对象 private void Awake() { slideTrans = GameObject.Find("Canvas/SlideBox").GetComponent(); work = GameObject.Find("Work").transform; eventDetector = Camera.main.GetComponent(); } private void Update() { if (eventDetector.EventType() == MyEventType.BeginDrag) { preMousePos = Input.mousePosition; } else if (eventDetector.EventType() == MyEventType.EndDrag) { Rect rect = slideTrans.rect; rect.position = slideTrans.position; targets = RectPainter.DrawRect(work, rect); targetsChangedHandler?.Invoke(targets); ClearRect(); } else if (eventDetector.EventType() == MyEventType.Drag) { DrawRect(); } } private void DrawRect() { // 绘制滑动选框 float minX = Mathf.Min(Input.mousePosition.x, preMousePos.x); float minY = Mathf.Min(Input.mousePosition.y, preMousePos.y); slideTrans.position = new Vector3(minX, minY, 0); Vector3 delta = Input.mousePosition - preMousePos; slideTrans.sizeDelta = new Vector2(Mathf.Abs(delta.x), Mathf.Abs(delta.y)); } private void ClearRect() { // 清除滑动选框 slideTrans.sizeDelta = Vector2.zero; } }

​ 说明:SlideSelect 脚本组件挂在 Work 对象下,用于滑动框选物体。

​ RectDetector.cs

using System; using System.Collections.Generic; using UnityEngine; public class RectDetector { // 边框检测器 public static Rect GetRect(List targets) { // 获取物体的外边框(包含子对象) if (targets != null && targets.Count > 0) { Rect[] rects = new Rect[targets.Count]; for (int i = 0; i < targets.Count; i++) { rects[i] = GetRect(targets[i]); } return GetRect(rects); } return new Rect(); } public static Rect GetCurrRect(List targets) { // 获取物体的外边框(不包含子对象) if (targets != null && targets.Count > 0) { Rect[] rects = new Rect[targets.Count]; for (int i = 0; i < targets.Count; i++) { rects[i] = GetCurrRect(targets[i]); } return GetRect(rects); } return new Rect(); } public static Rect GetRect(Transform transform) { // 获取物体外边框(包含子物体) Rect rect = GetInitRect(); ForAllChildren(transform, trans => { Rect rect1 = GetCurrRect(trans); rect.xMin = Mathf.Min(rect.xMin, rect1.xMin); rect.yMin = Mathf.Min(rect.yMin, rect1.yMin); rect.xMax = Mathf.Max(rect.xMax, rect1.xMax); rect.yMax = Mathf.Max(rect.yMax, rect1.yMax); }); return rect; } public static Rect GetCurrRect(Transform transform) { // 获取物体外边框(不包含子对象) Rect rect = GetInitRect(); Vector3[] vertices = GetVertices(transform); if (vertices != null && vertices.Length > 0) { for (int i = 0; i < vertices.Length; i++) { Vector3 screenPos = Camera.main.WorldToScreenPoint(vertices[i]); rect.xMin = Mathf.Min(rect.xMin, screenPos.x); rect.yMin = Mathf.Min(rect.yMin, screenPos.y); rect.xMax = Mathf.Max(rect.xMax, screenPos.x); rect.yMax = Mathf.Max(rect.yMax, screenPos.y); } } return rect; } private static Rect GetRect(Rect[] rects) { // 合并一组边框 if (rects == null || rects.Length == 0) { return new Rect(); } Rect rect = rects[0]; for (int i = 1; i < rects.Length; i++) { rect.xMin = Mathf.Min(rect.xMin, rects[i].xMin); rect.yMin = Mathf.Min(rect.yMin, rects[i].yMin); rect.xMax = Mathf.Max(rect.xMax, rects[i].xMax); rect.yMax = Mathf.Max(rect.yMax, rects[i].yMax); } return rect; } private static Rect GetInitRect() { // 获取初始的边框 Rect rect = new Rect(); rect.xMin = float.MaxValue; rect.yMin = float.MaxValue; rect.xMax = float.MinValue; rect.yMax = float.MinValue; return rect; } private static Vector3[] GetVertices(Transform transform) { // 获取网格顶点的世界坐标 if (transform.GetComponent() == null || transform.GetComponent().mesh == null) { return null; } Vector3[] vertices = transform.GetComponent().mesh.vertices; for (int i = 0; i < vertices.Length; i++) { vertices[i] = transform.TransformPoint(vertices[i]); } return vertices; } private static void ForAllChildren(Transform transform, Action action) { // 对所有子对象执行活动 if (transform == null || action == null) { return; } Transform[] children = transform.GetComponentsInChildren(); for(int i = 0; i < children.Length; i++) { action(children[i]); } } }

​ 说明:RectDetector 通过遍历 mesh 的所有顶点,并将其投射到屏幕上,以计算出物体的屏幕选框大小。

​ RectPainter.cs

using System; using System.Collections.Generic; using UnityEngine; public class RectPainter { // 矩形选框渲染器 private const float border = 5; // 矩形选框的边界宽度 private static RectPainter instance; // 单例 private RectTransform selectTrans; // 选框 private List targets; // 选中的游戏对象 private RectPainter() { selectTrans = GameObject.Find("Canvas/SelectBox").transform as RectTransform; Camera.main.GetComponent().camChangedHandler += DrawRect; } public static RectPainter GetInstance() { // 获取单例 if (instance == null) { instance = new RectPainter(); } return instance; } public static void DrawRect(List targets) { // 绘制被选中物体的外边框(包含子对象) if (instance != null) { instance.targets = targets; Rect rect = RectDetector.GetRect(targets); instance.DrawRect(rect); } } public static void DrawCurrRect(List targets) { // 绘制被选中物体的外边框(不包含子对象) if (instance != null) { instance.targets = targets; Rect rect = RectDetector.GetCurrRect(targets); instance.DrawRect(rect); } } public static List DrawRect(Transform root, Rect rect) { // 绘制root下面的在rect内的物体的外边框 if (instance != null) { instance.targets = new List(); ForAllChildren(root, trans => { Rect rect1 = RectDetector.GetCurrRect(trans); if (ContainsRect(rect, rect1)) { instance.targets.Add(trans); } }); instance.DrawCurrRect(); return instance.targets; } return null; } private void DrawRect() { // 绘制边框(包含子对象) Rect rect = RectDetector.GetRect(targets); DrawRect(rect); } private void DrawCurrRect() { // 绘制边框(不包含子对象) Rect rect = RectDetector.GetCurrRect(targets); DrawRect(rect); } private void DrawRect(Rect rect) { // 绘制边框 selectTrans.position = new Vector3(rect.x - border, rect.y - border, 0); selectTrans.sizeDelta = new Vector2(rect.width + border * 2, rect.height + border * 2); } private static bool ContainsRect(Rect rect1, Rect rect2) { // 判断rect1是否包含rect2 if (rect1.width


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3