主頁 >  其他 > Unity UGUI原始碼決議

Unity UGUI原始碼決議

2021-07-21 15:11:43 其他

前言

這篇文章想寫的目的也是因為我面試遇到了面試官問我關于UGUI原理性的問題,雖然我看過,但是并沒有整理出完整的知識框架,導致描述的時候可能自己都是不清不楚的,自己說不清楚的東西,別人就等于你不會,每當學完一個東西的時候,應該會大體框架流程,具體實作細節有所了解,然后整理出來,以備日后查閱,人的記憶是有限的,如果不記下來,每次翻的都是別人的博客,這樣其實是一個很不好的習慣,所以決定整理出一些自己關于UGUI的了解,以上只是我的牢騷,下面開始今天的內容,


學習方法論與要點整理

在框架之前我們需要先思考,我們要從哪里入手來看這個框架,參考我最近經常聽到的一句話吧,你寫的這個東西有什么用呢,他解決了什么問題,這確實是需要值得思考的問題,我們為什么要用UGUI,他人給我們提供了什么東西,為我們解決了什么問題,那么從這個思路開始,我們來梳理這個框架,

學習方法論

我們要看一個框架,會發現這個框架的代碼實在是太多了,不同于我們我們平時寫模塊化的邏輯,點開一個腳本基本上只能看到很小部分框架業務邏輯實作,那么下面就推薦我看代碼的思路,

  • 用全宇宙最強編輯器生成UML圖,通過觀看框架結構介面定義,來實作對整個框架結構有個概覽,
  • 下載原始碼,嘗試寫代碼對單個功能點的邏輯進行斷點除錯,跟著斷點查看整個框架代碼的執行流程

舉個例子,假如你需要看Image的繪制流程,只需要寫個測驗代碼,對Image的color進行賦值,會觸發Canvas重繪,對Image進行更新,這時候就可以跟著斷點走進去看到整個邏輯執行順序

UGUI功能點

讓我們分析一個UI界面所需要的要點,拋開業務邏輯,那么一個UI框架需要為我們提供的是

  • UI要素的渲染
  • 可控的渲染層級排序
  • UI布局的自適應
  • 互動事件的檢測與回應
  • 面向功能性的UI組件

UGUI框架決議

UGUI框架結構概覽

框架UML圖如下, 點擊試試看能不能放大
在這里插入圖片描述
UGUI框架結構幾個核心的類如下:

  • UIBehaviour 抽象類 繼承MonoBehaviour,提供核心事件驅動
  • Graphic 抽象類 提供繪制介面,各種Dirty方法來注冊到更新列隊
  • EventSystem 事件中心,負責處理各種輸入,光線投射,以及發送事件
  • BaseInputModule 輸入模塊基類,負責發送各種輸入事件到GameObject
  • BaseRaycaster 光線投射基類
  • EventInterfaces 注意這是一個腳本,他定義了所有EventSystem事件方法介面

其他功能性的組件,以及布局我就不一一列舉了,具體請查看原始碼


渲染以及重繪流程

在unity中我們繪制一個圖形需要幾個基本要素,Mesh,Material,Texture,以下的幾個要素都被定義在了Graphic中,這些屬性都是被CanvasRenderer這個組件托管進行渲染的,我們可以再撰寫組件的時候對這些要素進行定義和替換,但是最后都需要賦值到CanvasRenderer中由Canvas進行渲染,

網格繪制

在unity中我們繪制一個圖形需要幾個基本要素,Mesh,Material,Texture,以下的幾個要素都被定義在了Graphic中,那么首先讓我們看一下UGUI原始碼的例子,看他是怎么對一個網格進行繪制的

using UnityEngine;
using UnityEngine.UI;
[ExecuteInEditMode]
// 這是一個簡易的基于UGUI的Image實作
public class SimpleImage : Graphic
{
    // 這里是 Graphic 可重寫的繪制網格的介面
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        // VertexHelper 這是UGUI一個用于構建網格的幫助類
        // 他可以用于填充頂點資料,設定三角面片資訊,并快取他們
        // 最后用這些資料填充并構建一個mesh

        // 構建頂點資料
        // 此處兩個Vector2結構體的資料來源為當前Gameobject的Rectransform
        // 因為RectTransform的資料是基于布局自適應的
        Vector2 corner1 = Vector2.zero;
        Vector2 corner2 = Vector2.zero;
        corner1.x = 0f;
        corner1.y = 0f;
        corner2.x = 1f;
        corner2.y = 1f;
        corner1.x -= rectTransform.pivot.x;
        corner1.y -= rectTransform.pivot.y;
        corner2.x -= rectTransform.pivot.x;
        corner2.y -= rectTransform.pivot.y;
        corner1.x *= rectTransform.rect.width;
        corner1.y *= rectTransform.rect.height;
        corner2.x *= rectTransform.rect.width;
        corner2.y *= rectTransform.rect.height;
        vh.Clear();
        UIVertex vert = UIVertex.simpleVert;
        vert.position = new Vector2(corner1.x, corner1.y);
        vert.color = color;
        vh.AddVert(vert);
        vert.position = new Vector2(corner1.x, corner2.y);
        vert.color = color;
        vh.AddVert(vert);
        vert.position = new Vector2(corner2.x, corner2.y);
        vert.color = color;
        vh.AddVert(vert);
        vert.position = new Vector2(corner2.x, corner1.y);
        vert.color = color;
        vh.AddVert(vert);

        // 此處指定了三角面片的繪制順序 順時針方向
        // 0 (0,0) 頂點在陣列中的index(x坐標,y坐標)
        //
        // 1 (0,1)   >    2 (1,1) 
        //    ...........
        //    .       . .
        // ^  .    .    .
        //    . .       .
        //    ...........
        // 0 (0,0)  <     3 (1,1)
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(2, 3, 0);
    }
}

通過以上的例子應該對網格繪制原理有所了解,并且也能通過官方例子來實作一個簡易的Image繪制,

材質

在UGUI的Image中,就算我們沒有給他其特定的材質,Image還是能夠進行正常的渲染,Unity為我們設定了一個默認的材質定義,他的位置在Sahder面板中的UI/Default,以下靜態方法被定義在Canvas組件中

    public static Material GetDefaultCanvasMaterial();
    public static Material GetDefaultCanvasTextMaterial();
    public static Material GetETC1SupportedCanvasMaterial();
重繪流程

由于Canvas沒有開源代碼,沒辦法看到底層的渲染流程,只能通過調整引數來控制他的渲染邏輯,下面講一下主要的幾個控制引數,

RenderMode

  • ScreenSpace-Overlay 螢屏空間并覆寫在螢屏上,也就是說,當前渲染層級永遠在最上層
  • ScreenSpace-Camera 螢屏空間并由Camera來控制渲染,這個多了一層Camera套娃,可以來控制渲染和層級
  • WorldSpace 世界空間,這個渲染是三維空間的,可以用z軸進行渲染排序

PixelPerfect 強制于像素對齊,無特殊需要建議關閉,會影響性能

SortOrder 在ScreenSpace-Overlay模式下用于控制Canvas的渲染順序

SortingLayer 世界空間下畫布的渲染層

OrderInLayer 畫布在當前渲染層下的渲染順序

AdditionalShaderChannels 頂點資料的遮罩

那么下面我們再來說一下UGUI的重繪流程,在UGUI中渲染的核心就是Canvas組件和CanvasRenderer組件,Graphic只是為了填充渲染的必要元素,他并不直接參與繪制,那么讓我們看一下他是怎么觸發整個Canvas重新繪制的,這是Canvas中重繪的關鍵事件

    public static event WillRenderCanvases willRenderCanvases;

那么讓我們思考一下,什么情況下需要觸發重新繪制,就是當前Canvas下需要繪制的物體屬性變更的時候,一共有以下幾個點,

  • 材質變更
  • 貼圖變更
  • Mesh資訊變更
  • 位置變更
  • 顯示與隱藏

讓我們看一下UGUI原始碼這個簡單的例子,

    // 設定頂點顏色
    public virtual Color color 
    { 
        get 
        { 
            return m_Color; 
        } 
        set 
        { 
            if (SetPropertyUtility.SetColor(ref m_Color, value)) 
            SetVerticesDirty(); 
        } 
    }

    // 設定材質
    public virtual Material material
    {
        get
        {
            return (m_Material != null) ? m_Material : defaultMaterial;
        }
        set
        {
            if (m_Material == value)
                return;

            m_Material = value;
            SetMaterialDirty();
        }
    }

    // 材質變化 將自身注冊到重繪串列中
    public virtual void SetMaterialDirty()
    {
        if (!IsActive())
            return;

        m_MaterialDirty = true;
        CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

        if (m_OnDirtyMaterialCallback != null)
            m_OnDirtyMaterialCallback();
    }

    // 頂點變化 將自身注冊到重繪串列中
    public virtual void SetVerticesDirty()
    {
        if (!IsActive())
            return;

        m_VertsDirty = true;
        CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

        if (m_OnDirtyVertsCallback != null)
            m_OnDirtyVertsCallback();
    }

以上的方法都是在Graphic類中,一個用于一個頂點顏色,也就是頂點資訊改變,一個用于設定材質,也就是材質改變,相對應的每個需要渲染的屬性都有其屬性設定器,我們可以看到他在set的時候呼叫了一個相對應的Dirty的方法,他會在方法中把自身注冊進CanvasUpdateRegistry這個類中,這個類是重繪的關鍵,讓我們看一下這個類主要實作了什么邏輯,以下列舉幾個關鍵方法

    public class CanvasUpdateRegistry
    {
        // 構造方法,將PerformUpdate方法注冊進Canvas即將渲染前會呼叫的事件
        protected CanvasUpdateRegistry()
        {
            Canvas.willRenderCanvases += PerformUpdate;
        }

        // 將組件注冊進布局重建的串列
        public static void RegisterCanvasElementForLayoutRebuild(ICanvasElement element)
        {
            instance.InternalRegisterCanvasElementForLayoutRebuild(element);
        }

        // 將組件注冊進渲染重建的串列
        public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
        {
            instance.InternalRegisterCanvasElementForGraphicRebuild(element);
        }

        // 更新布局與渲染的方法
        private void PerformUpdate()
        {
            // 由于篇幅限制這里只貼了更新Graphic的部分,實際上還有布局更新的部分,請自行查閱原始碼
            var graphicRebuildQueueCount = m_GraphicRebuildQueue.Count;
            for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
            {
                UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
                for (var k = 0; k < graphicRebuildQueueCount; k++)
                {
                    try
                    {
                        var element = m_GraphicRebuildQueue[k];
                        if (ObjectValidForUpdate(element))
                        //這個是關鍵方法,他根據Canvas更新的流程來選擇不同的繪制方法,
                        // 來重建Graphic
                            element.Rebuild((CanvasUpdate)i);
                    }
                    catch (Exception e)
                    {
                        Debug.LogException(e, m_GraphicRebuildQueue[k].transform);
                    }
                }
                UnityEngine.Profiling.Profiler.EndSample();
            }

            for (int i = 0; i < graphicRebuildQueueCount; ++i)
                m_GraphicRebuildQueue[i].GraphicUpdateComplete();

            m_GraphicRebuildQueue.Clear();
            m_PerformingGraphicUpdate = false;
            UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Render);
        }
    }

Tips:先描述一下 ICanvasElement 這個類的定義,這個類是顧名思義他就是Canvas元素,而Canvas主要負責繪制,所以他是一個所有需要帶繪制屬性組件的基類,這個類中定義了 Rebuild 方法,可以在這個方法對主要元素進行重新繪制,順便貼上在 Graphic 中的ReBuild實作

    // 核心的重繪方法
    public virtual void Rebuild(CanvasUpdate update)
    {
        if (canvasRenderer == null || canvasRenderer.cull)
            return;

        // 根據自定義的更新流程來更新頂點和材質
        switch (update)
        {
            case CanvasUpdate.PreRender:
                if (m_VertsDirty)
                {
                    UpdateGeometry();
                    m_VertsDirty = false;
                }
                if (m_MaterialDirty)
                {
                    UpdateMaterial();
                    m_MaterialDirty = false;
                }
                break;
        }
    }

接著上面說,我們可以看到這個類主要做了這幾件事情,在構造的時候將更新方法注冊進 Canvas.willRenderCanvases 中,這個事件是會在Canvas更新前被呼叫,然后可以利用 RegisterCanvasElementForGraphicRebuild 這個方法將需要繪制的元素注冊進待更新的佇列,然后在Canvas即將渲染時會執行這個類的更新方法 PerformUpdate,這時候他會根據自定義的CanvasUpdate更新流程作為引數,呼叫每個element的Rebuild函式

總結:我們可以看上片段邏輯已經實作了一個繪制的基本要素和觸發重繪的條件,形成了一個完整的倍訓,
核心就是依賴的Canvas即將要渲染前的事件,來構造這樣一段邏輯,主要是實作了觀察者模式,算是一個比較高效率的實作,所以在我們做專案的UGUI優化的時候也是主要觀察這個willRenderCanvases,被注冊的次數,盡量用Canvas來隔開不必要的重繪次數,當然這需要和Drawcall之間進行衡量,找出性能的瓶頸點來進行優化,

事件觸發以及回應

在看之前首先讓我們思考,如果讓我們撰寫一個事件系統的話,我們會怎么實作這樣一個功能,
很明顯,事件就是一個觀察者模式,他有一個事件中心(EventSystem),以及眾多的觀察者(例: Button),當然我們也需要對事件進行型別區分,比如說 OnClick, OnDrag,很明顯他們實作的功能職責不同,既然我們要區別這些事件型別,那么我們必然需要一個用戶輸入的手勢資料(例:PointerEventData)用來判斷區分不同的事件觸發,既然已經整理清楚思路了,就讓我們看一下UGUI中模塊的定義,

模塊定義

在這里插入圖片描述

他主要根據檔案目錄分了以下幾個模塊,輸入資料模塊(EventData), 輸入模塊(InputModules), 射線模塊(Raycaster), UI元素模塊(UIElement)

事件系結

讓我們先看一段我們平時在代碼中會撰寫的方法觸發邏輯,這里主要利用了Button,

    void Start()
    {
        _button = GetComponent<Button>();
        _button.onClick.AddListener(func);
    }

這個方法主要是把 func 托管給了Button這個組件,然后來進行事件的觸發,那么讓我們看看Button里面做了什么把,

    public class Button : Selectable, IPointerClickHandler, ISubmitHandler
    {
        public class ButtonClickedEvent : UnityEvent {}

        private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();

        // 方法系結在這個事件中
        public ButtonClickedEvent onClick
        {
            get { return m_OnClick; }
            set { m_OnClick = value; }
        }

        private void Press()
        {
            if (!IsActive() || !IsInteractable())
                return;

            UISystemProfilerApi.AddMarker("Button.onClick", this);
            m_OnClick.Invoke();
        }

        // 這里是主要的觸發邏輯,實作了 IPointerClickHandler 介面
        public virtual void OnPointerClick(PointerEventData eventData)
        {
            if (eventData.button != PointerEventData.InputButton.Left)
                return;

            Press();
        }
    }

    // Click事件的介面
    public interface IPointerClickHandler : IEventSystemHandler
    {
        void OnPointerClick(PointerEventData eventData);
    }

我們可以發現組件想要觸發事件主要依賴于實作UGUI的介面,然后框架會呼叫介面中的方法來觸發組件的邏輯
能觸發這種不同型別的事件的介面有很多,他們都被定義在了 EventInterfaces 這個腳本中,所有的介面都繼承了 IEventSystemHandler 這個事件觸發的基類,

事件檢測以及觸發

我們要觸發事件,必然需要一個核心驅動來獲取我們每幀的手勢輸入,我們先看一下事件觸發的核心驅動部分邏輯,

    // 這是掛載在場景中的核心驅動模塊
    public class EventSystem : UIBehaviour
    {
        // 輸入模塊
        private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();
        // 當前的輸入模塊
        private BaseInputModule m_CurrentInputModule;
        // 事件中心串列,實際上當前場景只允許有一個
        private  static List<EventSystem> m_EventSystems = new List<EventSystem>();

        // 每幀驅動輸入模塊的邏輯
        protected virtual void Update()
        {
            // 核心邏輯,截取部分,具體的查閱原始碼
            if (current != this)
                return;
            // 更新驅動輸入模塊
            TickModules();

            if (!changedModule && m_CurrentInputModule != null)
                // 這個模塊方法里面會構造當前的輸入資料
                m_CurrentInputModule.Process();

            // 可以看到驅動執行事件在處理輸入資料之前,這說明我們的輸入資料會在這一幀被構造,在下一幀被執行
        }

        // 更新驅動輸入模塊
        private void TickModules()
        {
            var systemInputModulesCount = m_SystemInputModules.Count;
            for (var i = 0; i < systemInputModulesCount; i++)
            {
                if (m_SystemInputModules[i] != null)
                    // 這里驅動對應的輸入模塊了
                    m_SystemInputModules[i].UpdateModule();
            }
        }
    }

以上邏輯可以看到EventSystem模塊主要負責和驅動相應的輸入模塊,具體實作要延遲到各個輸入模塊中,要注意的點是,輸入資料會在這一幀被構造,在下一幀被執行, 那么我們以 TouchInputModule 為例,來看看他實作了什么功能,

    // 這是一個觸摸的輸入模塊
    public class TouchInputModule : PointerInputModule
    {
        // 定義了輸入的資料
        private PointerEventData m_InputPointerEvent;

        // 每幀更新的方法,執行當前的輸入事件
        public override void UpdateModule()
        {
            if (!eventSystem.isFocused)
            {
                if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
                    // 這是核心邏輯,這里執行了對應的資料的事件,
                    ExecuteEvents.Execute(m_InputPointerEvent.pointerDrag, m_InputPointerEvent, ExecuteEvents.endDragHandler);

                m_InputPointerEvent = null;
            }

            m_LastMousePosition = m_MousePosition;
            m_MousePosition = input.mousePosition;
        }

        // 構造當前的輸入資料
        public override void Process()
        {
            if (UseFakeInput())
                FakeTouches();
            else
                ProcessTouchEvents();
        }

        // 構造當前的輸入資料
        private void ProcessTouchEvents()
        {
            for (int i = 0; i < input.touchCount; ++i)
            {
                // 這里終于能看見呼叫Unity的API來通過GetTouch來獲取當前的觸摸點
                Touch touch = input.GetTouch(i);

                if (touch.type == TouchType.Indirect)
                    continue;

                bool released;
                bool pressed;
                var pointer = GetTouchPointerEventData(touch, out pressed, out released);

                // 在這個方法里面會對當前的 m_InputPointerEvent 進行賦值
                // 這樣當前幀的輸入資訊的構造好了
                ProcessTouchPress(pointer, pressed, released);

                if (!released)
                {
                    ProcessMove(pointer);
                    ProcessDrag(pointer);
                }
                else
                    RemovePointerData(pointer);
            }
        }
    }

我們可以看到這個輸入模塊每幀構造當前的輸入資料以及每幀驅動模塊進行事件的分發,并且他呼叫了核心的方法 ExecuteEvents.Execute(),這個方法會負責觸發對應介面的方法,讓我們看一下這個方法的定義

    // 這里把需要執行的方法定義在了外部
    public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData);

    private static readonly EventFunction<IPointerEnterHandler> s_PointerEnterHandler = Execute;

    private static void Execute(IPointerEnterHandler handler, BaseEventData eventData)
    {
        handler.OnPointerEnter(ValidateEventData<PointerEventData>(eventData));
    }

    public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
    {
        // 這個方法主要實作了收集gameObject上所有的 IEventSystemHandler 組件
        var internalHandlers = ListPool<IEventSystemHandler>.Get();
        GetEventList<T>(target, internalHandlers);

        var internalHandlersCount = internalHandlers.Count;
        for (var i = 0; i < internalHandlersCount; i++)
        {
            T arg;
            try
            {
                arg = (T)internalHandlers[i];
            }
            catch (Exception e)
            {
                var temp = internalHandlers[i];
                Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e));
                continue;
            }

            try
            {
                // 執行對應的方法以及傳入資料引數
                functor(arg, eventData);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        var handlerCount = internalHandlers.Count;
        ListPool<IEventSystemHandler>.Release(internalHandlers);
        return handlerCount > 0;
    }

    // 這個方法主要實作了收集gameObject上所有的 IEventSystemHandler 組件
    private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
    {
        // Debug.LogWarning("GetEventList<" + typeof(T).Name + ">");
        if (results == null)
            throw new ArgumentException("Results array is null", "results");

        if (go == null || !go.activeInHierarchy)
            return;

        var components = ListPool<Component>.Get();
        go.GetComponents(components);

        var componentsCount = components.Count;
        for (var i = 0; i < componentsCount; i++)
        {
            if (!ShouldSendToComponent<T>(components[i]))
                continue;

            // Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T)));
            results.Add(components[i] as IEventSystemHandler);
        }
        ListPool<Component>.Release(components);
        // Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">");
    }

主要需要分析的是Execute這個方法的 functor 引數,他實作的功能就是相對應需要執行的方法如OnPointerEnter,定義在了外部,通過傳參的方法來執行對應的方法,

引數還有一個gameObject,這是一個必須的引數,這個gameObject在構建輸入資料的時候已經被賦值,并隨著輸入資料進行傳遞,讓我們來看一下UGUI是怎么獲取當前需要接受這個事件的gameObject的,

    // 所有輸出模塊的基類
    public abstract class BaseInputModule : UIBehaviour
    {
        // 接受事件檢測的物件串列
        protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>();
    }

    public class EventSystem : UIBehaviour
    {
        // 這個方法呼叫了會獲取所有需要檢測的組件串列
        public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
        {
            raycastResults.Clear();
            var modules = RaycasterManager.GetRaycasters();
            var modulesCount = modules.Count;
            for (int i = 0; i < modulesCount; ++i)
            {
                var module = modules[i];
                if (module == null || !module.IsActive())
                    continue;

                module.Raycast(eventData, raycastResults);
            }

            raycastResults.Sort(s_RaycastComparer);
        }
    }

RaycastAll方法會獲取所有需要回應檢測的串列,并對其進行排序,然后在構造輸入資料的時候獲取第一個被檢測的物件,這個物件就是那個GameObject,其他具體檢測邏輯請參考Canvas的渲染模式以及原始碼

對于Graphic物件來說,我們有兩種方式控制其射線的檢測邏輯,一種是繼承并重寫Graphic.Raycast方法,還有一種就是讓子類同時繼承 ICanvasRaycastFilter 介面, 實作其介面方法 IsRaycastLocationValid 來進行來實作過濾,


總結

UGUI的邏輯已經比較完善了,源框架代碼也很規范,希望這篇文章可以幫助大家快速的通讀UGUI的原始碼相關邏輯,共同進步,哦耶~

好像少了一個部分還沒說就是關于自適應布局邏輯及更新這塊,還有蒙板邏輯,其他部分留著下次再補充吧

布局組件這塊已經更新好啦,>>>>>> 戳這里 Unity UGUI自適應布局系統詳解<<<<<<

那么今天就教程就到這里結束了,如果覺得我說的有用的話就在我的>>>>>> 戳這里 github專案<<<<<<點上一個小小的star吧,咖啡就不用請我喝了,屑屑(比心)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/289402.html

標籤:其他

上一篇:猜數字游戲——純前端實作(純小白也能看懂&CV直接拿走直接玩)

下一篇:Unity iOS本地推送解決方案?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more