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

news2024/11/17 4:23:09

1 需求描述

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

        滑动框效果如下:

        选中边框效果如下: 

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

2 需求实现

2.1 场景搭建

        1)场景对象

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

        2)滑动框

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

        2)选框

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

         外框图片如下:

         说明:外框图片是 png 格式,并且除了黄色边角,其他部分都是透明的,图片导入 Unity 后,需要修改 Texture Type 为 Sprite,并且需要在 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 Awake() {
        RectPainter.GetInstance();
    }

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

        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.EventType() == MyEventType.CtrlScroll) { // 缩放场景
            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 运行效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue中自定义指令是什么?有哪些应用场景?

一、什么是指令 开始之前先学习一下指令系统这个词 指令系统是计算机硬件的语言系统&#xff0c;也叫机器语言&#xff0c;它是系统程序员看到的计算机的主要属性。因此指令系统表征了计算机的基本功能决定了机器所要求的能力 在vue中提供了一套为数据驱动视图更为方便的操作&…

【C语言进阶】qsort函数详解以及它的模拟实现

目录一、qsort函数介绍二、qsort函数参数介绍2.1&#xff1a;void* base2.2&#xff1a;size_t num2.3&#xff1a;size_t size2.4&#xff1a;int(* compar)(const void *,const void *)三、实际应用3.1&#xff1a;利用qsort函数对整型数组排序3.2&#xff1a;利用qsort函数对…

2023年1月9日:fastadmin在列表操作列区域添加按钮及控制已有按钮显示

列表操作列区域添加按钮 buttons: [{name: detail,title: __(详情),classname: btn btn-xs btn-primary btn-dialog,icon: fa fa-list,url: audit/detail,callback: function (data) {Layer.alert("接收到回传数据&#xff1a;" JSON.stringify(data), {title: &q…

【nvivo11plus教程】01_nvivo介绍、案例与批注

1、查看nvivo版本2、nvivo是如何支持质性研究的3、nvivo的项目介绍4、建立nvivo项目(1)建立项目(2)文件夹(3)新建分类(4)建立备忘录5、案例(1)建立案例(2)案例节点分类的变量设置(3)归类案例6、批注7、备忘录链接1、查看nvivo版本 2、nvivo是如何支持质性研究的 是一个迭代的过…

MATLAB算法实战应用案例精讲-【数据分析】时许异常检测

前言 时间序列异常检测的目的就是在时间序列中寻找不符合常见规律的异常点,无论是在学术界还是工业界这都是一个非常重要的问题。企业的运维场景中有海量的运维指标数据,如果单纯依靠人力来发现并定位异常,将是十分低效的,所以如果可以开发一个智能运维系统对于异常波动自…

MAC地址

目录MAC地址广播信道的数据链路层必须使用地址&#xff08;MAC&#xff09;IEEE 802局域网的MAC地址格式IEEE 802局域网的MAC地址发送顺序单播MAC地址举例广播MAC地址举例多播MAC地址举例MAC地址 使用点对点信道的数据链路层不需要使用地址使用广播信道的数据链路层必须使用地址…

加解密与HTTPS(6)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e;随着成本的下降&#xff0c;主流网站都已经开始使用HTTPS了。但有了可信机构颁发的证书&#xff0c;网站就真的绝对安全了吗&#xff1f;以之前出现过的上大学被冒…

多任务系统概述

一个例子&#xff1a; int Main(void) { TargetInit(); //初始化目标板OSInit(); //初始化操作系统 OSTaskCreate(Task0,&StackTask0[StackSizeTask0 - 1],PrioTask0); // 创建一个任务Uart_Printf("Ready to start OS\n"); OSStart(); //运行操作系统return 0…

AtCoder Beginner Contest 284(A~E)

比赛名称&#xff1a;AtCoder Beginner Contest 284 比赛链接&#xff1a;AtCoder Beginner Contest 284 A - Sequence of Strings 输入若干字符串&#xff0c;再把这些字符串按输入顺序倒序输出 #include <bits/stdc.h> using namespace std; signed main() {ios::sy…

年终盘点(二)丨2022计讯物联荣誉资质大盘点

峥嵘岁月&#xff0c;奋力前行。2022年&#xff0c;计讯物联积极发扬实干精神&#xff0c;聚力做强做精做专物联网产业&#xff0c;全面助力数字化转型升级&#xff0c;以硬核的实力揽获多项殊荣。 每一项荣誉的背后是计讯领导的的正确指导与全力支持&#xff0c;更是全体计讯人…

综合项目 旅游网【2. 优化servlet】没有指定的js文件读不到文件 错误

优化servlet目的减少Servlet的数量&#xff0c;现在是一个功能一个Servlet&#xff0c;将其优化为一个模块一个Servlet&#xff0c;相当于在数据库中一张表对应一个Servlet&#xff0c;在Servlet中提供不同的方法&#xff0c;完成用户的请求。如何解决测试时控制台中文乱码&…

VS2019+Opencv3.4+Win10配置详解

一.下载opencv 官网&#xff1a;Releases - OpenCV 不同版本vs对应不同版本的opencv,其中高版本vs可以配置低版本vc&#xff0c;低版本不能配置高版本vc。 windows系统直接下载Windows版本就可以&#xff08;下载的文件是一个exe文件&#xff0c;运行相当于解压缩&#xff0…

1143汉诺塔

题目描述汉诺塔问题是这样的&#xff1a;有3根柱子A,B,C&#xff0c;其中A柱上有64个盘子&#xff0c;盘子大小不等&#xff0c;大的在下&#xff0c;小的在上。要求把这64个盘子从A柱移到C柱上&#xff0c;在移动过程中可以借助B柱&#xff0c;每次只允许移动一个盘子&#xf…

什么是 Java 泛型?怎样使用 Java 泛型?

目录 1、为什么使用泛型&#xff1f; 2、什么是泛型类&#xff1f;如何定义一个泛型类&#xff1f; 泛型的命名约定 3、什么是泛型方法&#xff1f;如何定义一个泛型方法&#xff1f; 4、什么是有界类型参数&#xff1f;如何定义有界类型参数&#xff1f; &#xff08;1&…

Maven高级-私服

分模块合作开发 9.2)Nexus Nexus是Sonatype公司的一款maven私服产品 下载地址&#xff1a;https://help.sonatype.com/repomanager3/download Nexus*安装、启动与配置** 启动服务器&#xff08;命令行启动&#xff09; nexus.exe /run nexus访问服务器&#xff08;默认端口…

linux安装部署vsftpd

yum直接安装yum -y install vsftpd ftp 创建新用户&#xff1a;ftpd更新ftpd密码&#xff1a;echo "123456" |passwd --stdin ftpd创建ftp目录&#xff1a;mkdir -p /home/ftpd/test授权&#xff1a;chown -R ftpd:ftpd /home/ftpd/testchmod 777 -R /home/ftpd/test…

某程序员哀叹:最近阳的人越来越多,面对员工们纷纷倒下,公司领导公然宣称“发烧请病假不等于在家睡大觉,再不回复工作就滚蛋”...

最近阳的人越来越多&#xff0c;面对员工们纷纷倒下&#xff0c;有的公司通情达理&#xff0c;有的公司却开始“不当人”了。一位网友曝光公司领导在群里所有人&#xff0c;称“发烧请病假不意味着在家睡大觉&#xff0c;啥也不管&#xff0c;联系不上&#xff0c;安排不予响应…

【SAP Hana】SAP HANA SQL 基础教程

SAP HANA SQL 基础教程1、SQL 标准简介2、HANA STUDIO 的安装3、HANA STUDIO 的设置4、HANA SQL 基础教程&#xff08;1&#xff09;查看表数据&#xff08;2&#xff09;查看表结构&#xff08;3&#xff09;SELECT&#xff08;4&#xff09;WHERE&#xff08;5&#xff09;WH…

B站直播带货,带货直播数据如何查看?

随着时代发展&#xff0c;直播电商带货也是越来越火&#xff0c;在这个直播带货火热期&#xff0c;B站也是当仁不让的加入到直播带货行业中&#xff0c;在今年双11中&#xff0c;B站第一次参加双十一直播电商混战&#xff0c;但是并未像其他电商平台一般&#xff0c;趁双十一流…

【自学Python】Python浮点型(float)

Python浮点型(float) Python浮点型(float)教程 Python 浮点型数值用于保存带小数点的数值。Python 的浮点数有两种表示形式&#xff0c;即十进制形式和科学计数法形式。 Python浮点型(float)详解 十进制形式 Python 最常见的浮点型数就是十进制形式的浮点型数。Python 中的…