美文网首页Unity3D
【Unity3D】点选物体、框选物体、绘制外边框

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

作者: LittleFatSheep | 来源:发表于2023-03-18 10:20 被阅读0次

    1 需求描述

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

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

    滑动框效果如下:

    选中边框效果如下:

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

    2 需求实现

    2.1 场景搭建

    1)场景对象

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

    2)滑动框

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

    滑动框图片如下:

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

    2)选框

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

    外框图片如下:

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

    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.EndCtrlScroll;
        }
     
        public bool IsBeginDrag() { // 是否是开始拖拽类型事件
            MyEventType type = EventType();
            return type == MyEventType.BeginDrag || type == MyEventType.BeginCtrlDrag || type == MyEventType.BeginAltDrag;
        }
     
        public float Scroll() { // 鼠标滑轮滑动刻度
            if (HasScrollEvent()) {
                return scroll;
            }
            return 0;
        }
     
        private bool IsClickEvent(MyEventType type) { // 是否是点击事件
            return type >= MyEventType.ClickDown && type <= MyEventType.CtrlClickUp;
        }
     
        private bool IsDragEvent(MyEventType type) { // 是否是拖拽事件
            return type >= MyEventType.BeginDrag && type <= MyEventType.EndAltDrag;
        }
     
        private bool IsScrollEvent(MyEventType type) { // 是否是滑轮事件
            return type >= MyEventType.BeginScroll && type <= MyEventType.EndCtrlScroll;
        }
     
        private bool IsBeginEvent(MyEventType type) { // 是否是开始类型事件
            return type == MyEventType.ClickDown
                || type == MyEventType.BeginDrag
                || type == MyEventType.BeginCtrlDrag
                || type == MyEventType.BeginAltDrag
                || type == MyEventType.BeginScroll
                || type == MyEventType.BeginCtrlScroll;
        }
     
        private bool HasCtrlKey(MyEventType type) { // 是否有Ctrl按键事件
            return type >= MyEventType.CtrlClickDown && type <= MyEventType.CtrlClickUp
                || type >= MyEventType.BeginCtrlDrag && type <= MyEventType.EndCtrlDrag
                || type >= MyEventType.BeginCtrlScroll && type <= MyEventType.EndCtrlScroll;
        }
     
        private bool HasAltKey(MyEventType type) { // 是否有Alt按键事件
            return type >= MyEventType.BeginAltDrag && type <= MyEventType.EndAltDrag;
        }
     
        private void ContinueKeyType() { // 保持按键事件
            if (HasCtrlKey(lastEventType)) {
                AddKeyType("Ctrl");
            } else if (HasAltKey(lastEventType)) {
                AddKeyType("Alt");
            }
        }
     
        private void AddKeyType(string key) { // 添加按键事件
            if ("Ctrl".Equals(key)) {
                if (eventType == MyEventType.ClickDown) { // 点击事件
                    eventType = MyEventType.CtrlClickDown;
                } else if (eventType == MyEventType.Click) {
                    eventType = MyEventType.CtrlClick;
                } else if (eventType == MyEventType.ClickUp) {
                    eventType = MyEventType.CtrlClickUp;
                } else if (eventType == MyEventType.BeginDrag) { // 拖拽事件
                    eventType = MyEventType.BeginCtrlDrag;
                } else if (eventType == MyEventType.Drag) {
                    eventType = MyEventType.CtrlDrag;
                } else if (eventType == MyEventType.EndDrag) {
                    eventType = MyEventType.EndCtrlDrag;
                } else if (eventType == MyEventType.BeginScroll) { // 滑轮事件
                    eventType = MyEventType.BeginCtrlScroll;
                } else if (eventType == MyEventType.Scroll) {
                    eventType = MyEventType.CtrlScroll;
                } else if (eventType == MyEventType.EndScroll) {
                    eventType = MyEventType.EndCtrlScroll;
                }
            } else if ("Alt".Equals(key)) {
                if (eventType == MyEventType.BeginDrag) { // 拖拽事件
                    eventType = MyEventType.BeginAltDrag;
                } else if (eventType == MyEventType.Drag) {
                    eventType = MyEventType.AltDrag;
                } else if (eventType == MyEventType.EndDrag) {
                    eventType = MyEventType.EndAltDrag;
                }
            }
        }
    }
     
    public enum MyEventType { // 事件类型
        None = 0,
        ClickDown = 1,
        Click = 2,
        ClickUp = 3,
        CtrlClickDown = 4,
        CtrlClick = 5,
        CtrlClickUp = 6,
        BeginDrag = 10,
        Drag = 11,
        EndDrag = 12,
        BeginCtrlDrag = 13,
        CtrlDrag = 14,
        EndCtrlDrag = 15,
        BeginAltDrag = 16,
        AltDrag = 17,
        EndAltDrag = 18,
        BeginScroll = 20,
        Scroll = 21,
        EndScroll = 22,
        BeginCtrlScroll = 23,
        CtrlScroll = 24,
        EndCtrlScroll = 25
    }
    

    说明: EventDetector 脚本组件挂在相机下,用于统一管理事件。点选物体(ClickUp / Ctrl + ClickUp)、滑动选框(Drag)、场景变换(Ctrl + Drag / Alt + Drag)都有鼠标事件,这些事件相互冲突,不便于在每个类里都去捕获鼠标和键盘事件,因此需要 EventDetector 统一管理事件。

    ClickSelect.cs

    using System.Collections.Generic;
    using UnityEngine;
    
    public class ClickSelect : MonoBehaviour { // 点选物体
        private EventDetector eventDetector; // 鼠标事件检测器
        private List<Transform> targets; // 选中的游戏对象
        private List<Transform> loseFocus; // 失焦的游戏对象
        private RaycastHit hit; // 碰撞信息
    
        private void Awake() {
            targets = new List<Transform>();
            loseFocus = new List<Transform>();
            eventDetector = Camera.main.GetComponent<EventDetector>();
            GameObject.Find("Work").GetComponent<SlideSelect>().targetsChangedHandler += SetTargets;
        }
    
        private void Update() {
            if (eventDetector.EventType() == MyEventType.ClickUp || eventDetector.EventType() == MyEventType.CtrlClickUp) {
                Transform hitTrans = GetHitTrans();
                if (hitTrans == null || hitTrans.gameObject.layer == LayerMask.NameToLayer("Plane")) { // 未选中物体或点到地面, 全部取消选中
                    targets.ForEach(obj => 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<Renderer>().material.color = Color.gray;
            }
            foreach(var item in targets) {
                item.GetComponent<Renderer>().material.color = Color.red;
            }
            loseFocus.Clear();
        }
    
        private void SetTargets(List<Transform> 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<List<Transform>> targetsChangedHandler; // 框选目标改变时的处理器
        private EventDetector eventDetector; // 鼠标事件检测器
        private RectTransform slideTrans; // 滑动选框
        private Vector3 preMousePos; // 鼠标滑动前的位置
        private Transform work; // 需要检测是否被框选的物体根对象
        private List<Transform> targets; // 框选的目标对象
    
        private void Awake() {
            slideTrans = GameObject.Find("Canvas/SlideBox").GetComponent<RectTransform>();
            work = GameObject.Find("Work").transform;
            eventDetector = Camera.main.GetComponent<EventDetector>();
        }
    
        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<Transform> 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<Transform> 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<MeshFilter>() == null || transform.GetComponent<MeshFilter>().mesh == null) {
                return null;
            }
            Vector3[] vertices = transform.GetComponent<MeshFilter>().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<Transform> action) { // 对所有子对象执行活动
            if (transform == null || action == null) {
                return;
            }
            Transform[] children = transform.GetComponentsInChildren<Transform>();
            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<Transform> targets; // 选中的游戏对象
    
        private RectPainter() {
            selectTrans = GameObject.Find("Canvas/SelectBox").transform as RectTransform;
            Camera.main.GetComponent<SceneController>().camChangedHandler += DrawRect;
        }
    
        public static RectPainter GetInstance() { // 获取单例
            if (instance == null) {
                instance = new RectPainter();
            }
            return instance;
        }
    
        public static void DrawRect(List<Transform> targets) { // 绘制被选中物体的外边框(包含子对象)
            if (instance != null) {
                instance.targets = targets;
                Rect rect = RectDetector.GetRect(targets);
                instance.DrawRect(rect);
            }
        }
    
        public static void DrawCurrRect(List<Transform> targets) { // 绘制被选中物体的外边框(不包含子对象)
            if (instance != null) {
                instance.targets = targets;
                Rect rect = RectDetector.GetCurrRect(targets);
                instance.DrawRect(rect);
            }
        }
    
        public static List<Transform> DrawRect(Transform root, Rect rect) { // 绘制root下面的在rect内的物体的外边框
            if (instance != null) {
                instance.targets = new List<Transform>();
                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 <= 0 || rect1.height <= 0 || rect2.width <= 0 || rect2.height <= 0) {
                return false;
            }
            if (rect2.xMin < rect1.xMin || rect2.yMin < rect1.yMin || rect2.xMax > rect1.xMax || rect2.yMax > rect1.yMax) {
                return false;
            }
            return true;
        }
    
        private static void ForAllChildren(Transform transform, Action<Transform> action) { // 对所有子对象执行活动
            if (transform == null || action == null) {
                return;
            }
            Transform[] children = transform.GetComponentsInChildren<Transform>();
            for(int i = 0; i < children.Length; i++) {
                action(children[i]);
            }
        }
    }
    

    SceneController.cs

    using System;
    using UnityEngine;
     
    public class SceneController : MonoBehaviour {
        private EventDetector eventDetector; // 鼠标事件检测器
        public Action camChangedHandler; // 相机改变处理器
        private Transform cam; // 相机
        private float nearPlan; // 近平面
        private Vector3 preMousePos; // 上一帧的鼠标坐标
     
        private void Awake() {
            cam = Camera.main.transform;
            nearPlan = Camera.main.nearClipPlane;
            eventDetector = cam.GetComponent<EventDetector>();
        }
     
        private void Update() { // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)
            if (eventDetector.HasCtrlScrollEvent()) { // 缩放场景
                ScaleScene(eventDetector.Scroll());
            } else if (eventDetector.IsBeginDrag()) {
                preMousePos = Input.mousePosition;
            } else if (eventDetector.HasDragEvent()) {
                Vector3 offset = Input.mousePosition - preMousePos;
                if (eventDetector.EventType() == MyEventType.CtrlDrag) { // 移动场景
                    MoveScene(offset);
                } else if (eventDetector.EventType() == MyEventType.AltDrag) { // 旋转场景
                    RotateScene(offset);
                }
                preMousePos = Input.mousePosition;
            }
        }
     
        private void ScaleScene(float scroll) { // 缩放场景
            cam.position += cam.forward * scroll;
            camChangedHandler?.Invoke();
        }
     
        private void MoveScene(Vector3 offset) { // 平移场景
            cam.position -= (cam.right * offset.x / 100 + cam.up * offset.y / 100);
            camChangedHandler?.Invoke();
        }
     
        private void RotateScene(Vector3 offset) { // 旋转场景
            Vector3 rotateCenter = GetRotateCenter(0);
            cam.RotateAround(rotateCenter, Vector3.up, offset.x / 3); // 水平拖拽分量
            cam.LookAt(rotateCenter);
            cam.RotateAround(rotateCenter, -cam.right, offset.y / 5); // 竖直拖拽分量
            camChangedHandler?.Invoke();
        }
     
        private Vector3 GetRotateCenter(float planeY) { // 获取旋转中心
            if (Mathf.Abs(cam.forward.y) < Vector3.kEpsilon || Mathf.Abs(cam.position.y) < float.Epsilon)
            {
                return cam.position + cam.forward * (nearPlan + 1 / nearPlan);
            }
            float t = (planeY - cam.position.y) / cam.forward.y;
            float x = cam.position.x + t * cam.forward.x;
            float z = cam.position.z + t * cam.forward.z;
            return new Vector3(x, planeY, z);
        }
    }
    

    说明: SceneController 脚本组件挂在相机下,用于平移、旋转、缩放场景,其原理见→缩放、平移、旋转场景

    3 运行效果

    点选效果如下:

    框选效果如下:

    声明:本文转自点选物体、框选物体、绘制外边框

    相关文章

      网友评论

        本文标题:【Unity3D】点选物体、框选物体、绘制外边框

        本文链接:https://www.haomeiwen.com/subject/qnpohdtx.html