UpdateLayeredWindow實(shí)現(xiàn)任意異形窗口使用詳解
引言
前面提到,我們可以用SetWindowRgn或SetLayeredWindowAttributes實(shí)現(xiàn)不規(guī)則以及半透明的效果
對(duì)于SetWindowRgn,它通過(guò)一個(gè)Rgn來(lái)設(shè)置區(qū)域,這個(gè)Rgn一般可以從圖片中讀取,在這張圖片中,將不需要顯示的區(qū)域標(biāo)記為一種特殊的顏色,這里有個(gè)問(wèn)題,必須保證這種顏色沒(méi)有被正常的區(qū)域使用,否則會(huì)被誤傷。
為了解決這個(gè)問(wèn)題,可以考慮用兩張圖片,增加一張單色的掩碼圖,這種方案帶來(lái)了額外的管理開銷。SetWindowRgn的好處是效率較高,對(duì)于大部分自繪的皮膚,一般只有四個(gè)角落有一些不規(guī)則,所以用SetWindowRgn是最好的選擇。
- SetLayeredWindowAttributes可以將特定的窗口設(shè)置為某種透明度,也可以用它來(lái)過(guò)濾某種顏色,匹配的顏色會(huì)變成全透明。也就是類似于SetWindowRgn的效果。
- SetLayeredWindowAttributes直接從DC中獲得顏色,所以你需要事先繪制DC。
- SetLayeredWindowAttributes過(guò)濾顏色后,相關(guān)區(qū)域雖然不可見(jiàn),但是不可見(jiàn)的區(qū)域可以放置子窗口,這點(diǎn)和SetWindowRgn有所區(qū)別。此外若子窗口刷新不及時(shí)或其他原因,那么父窗口因?yàn)镾etLayeredWindowAttributes被隱藏的DC顏色將被浮出水面。
- UpdateLayeredWindow直接根據(jù)DC中的Alpha通道來(lái)實(shí)現(xiàn)透明效果,它很好的處理了和背景的Alpha Blend的問(wèn)題,所以完美的解決了SetWindowRgn的鋸齒問(wèn)題。
Sample
template<typename T,bool DLG> class ImageFrameT{ public: BEGIN_MSG_MAP(ImageFrameT) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLBttonDown) END_MSG_MAP() ImageFrameT():m_res_(NULL),m_move_flag_(false){} virtual ~ImageFrameT(){} LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){ bHandled = FALSE; // 通過(guò)參數(shù)獲得資源句柄 LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam; if(lpCreateStruct && lpCreateStruct->lpCreateParams) m_res_ = (CImage*)(lpCreateStruct->lpCreateParams); ATLASSERT(m_res_); if(!DLG) this->InitSelf(); return 0; } LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){ bHandled = FALSE; if(lParam){ // 通過(guò)參數(shù)獲得資源句柄 m_res_ = (CImage*)lParam; } if(DLG) this->InitSelf(); return 0; } LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ // OnPaint不作任何事,轉(zhuǎn)到UpdateLayeredWindow處理 T* pT = static_cast<T*>(this); CPaintDC dc_(pT->m_hWnd); return TRUE; } LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ // 屏蔽背景繪制 return TRUE; } LRESULT OnLBttonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ // 是否支持整窗口拖動(dòng) T* pT = static_cast<T*>(this); if(this->m_move_flag_) pT->PostMessage(WM_SYSCOMMAND,0xF012,0); else bHandled = FALSE; return 0; } void SetRes(CImage* res){ // 設(shè)置資源 ATLASSERT(res); if(res) this->m_res_ = res; } void SetMoveFlag(bool flag){ // 設(shè)置是否可拖動(dòng) this->m_move_flag_ = flag; } private: void InitSelf(){ ATLASSERT(m_res_); if(m_res_){ T* pT = static_cast<T*>(this); // 設(shè)置屬性WS_EX_LAYERED LONG lWindowLong = ::GetWindowLong(pT->m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED; ::SetWindowLong(pT->m_hWnd, GWL_EXSTYLE, lWindowLong); // 設(shè)置屬性WS_POPUP lWindowLong = ::GetWindowLong(pT->m_hWnd, GWL_STYLE) | WS_POPUP; // 去掉一堆其他屬性 lWindowLong &= ~WS_CHILD; lWindowLong &= ~WS_BORDER; lWindowLong &= ~WS_CAPTION; lWindowLong &= ~WS_SYSMENU; ::SetWindowLong(pT->m_hWnd, GWL_STYLE, lWindowLong); pT->SetWindowPos(HWND_BOTTOM,0,0, m_res_->GetWidth(),m_res_->GetHeight(), SWP_NOMOVE | SWP_NOOWNERZORDER); CClientDC dc_(pT->m_hWnd); CDC mem_dc_; mem_dc_.CreateCompatibleDC(dc_); CBitmap mem_bitmap_; mem_bitmap_.CreateCompatibleBitmap(dc_, m_res_->GetWidth(), m_res_->GetHeight()); mem_dc_.SelectBitmap(mem_bitmap_); m_res_->Draw(mem_dc_,0,0); BLENDFUNCTION pb_; pb_.AlphaFormat = 1; pb_.BlendOp = 0; pb_.BlendFlags =0; pb_.SourceConstantAlpha = 0xFF; CPoint pt_(0,0); CSize size_(m_res_->GetWidth(),m_res_->GetHeight()); ::UpdateLayeredWindow(pT->m_hWnd,dc_,&pt_,&size_,mem_dc_,&pt_,0,&pb_,ULW_ALPHA ); pT->CenterWindow(NULL); } } protected: CImage* m_res_; bool m_move_flag_; };
class CAboutDlg : public CDialogImpl<CAboutDlg>, public ImageFrameT<CAboutDlg,true> { typedef ImageFrameT<CAboutDlg,true> BaseClass; public: enum { IDD = IDD_DIALOG1 }; BEGIN_MSG_MAP(CAboutDlg) CHAIN_MSG_MAP(BaseClass) MESSAGE_HANDLER(WM_RBUTTONDOWN, OnClose) END_MSG_MAP() LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { EndDialog(0); return 0; } };
CImage bitmap_bg_; BOOL ret_ = File2CImageAndImplAlpha(&bitmap_bg_,_T("res/bk.png")); ATLASSERT(ret_); CAboutDlg dlg_; dlg_.SetMoveFlag(true); dlg_.DoModal(this->m_hSubWindow_,(LPARAM)(&bitmap_bg_));
放置子控件
若只需要最一個(gè)簡(jiǎn)單的窗口,那么上面的代碼可以完成要求。UpdateLayeredWindow有一個(gè)問(wèn)題,那就是它上面不能放置任何子窗口,放置上去的任何窗口都不可見(jiàn)。為了解決這個(gè)問(wèn)題,一種簡(jiǎn)單的辦法是自繪,在單個(gè)窗口中模擬各種消息。
若不想搞復(fù)雜,有一種變通的辦法,那就是在上面放置一個(gè)非子窗口,這種子窗口大小位置保持和它一致,同時(shí)這個(gè)子窗口用SetLayeredWindowAttributes搞成全透明,接下來(lái)我們將所有子控件放到這個(gè)全透明的子窗口即可。
Sample
#define CHAIN_MSG_MAP_ALT_MEMBER_EX(theChainMember, msgMapID, msg) \ { \ if(uMsg == msg && \ theChainMember &&\ theChainMember->ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, msgMapID)) \ return TRUE; \ } // 放置在ImageFrameT上的子窗口 // 這個(gè)類主要處理消息的轉(zhuǎn)發(fā),通過(guò)WM_CREATE獲得最頂層窗口的指針 // 并存儲(chǔ)在m_message_變量中,然后使用CHAIN_MSG_MAP_ALT_MEMBER_EX // 將它子窗口發(fā)給它的WM_COMMAND,WM_NOTIFY轉(zhuǎn)發(fā)過(guò)去 template<typename T,typename Y> class SubWindowT{ public: BEGIN_MSG_MAP(SubWindowT) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) // 將這個(gè)不可見(jiàn)的窗口的WM_COMMAND和WM_NOTIFY消息 // 轉(zhuǎn)發(fā)給最頂層的窗口 CHAIN_MSG_MAP_ALT_MEMBER_EX(m_message_,1,WM_COMMAND) CHAIN_MSG_MAP_ALT_MEMBER_EX(m_message_,1,WM_NOTIFY) END_MSG_MAP() SubWindowT():m_message_(NULL){}; LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){ bHandled = FALSE; T* pT = static_cast<T*>(this); LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam; // 初始化m_message_ if(lpCreateStruct && lpCreateStruct->lpCreateParams) m_message_ = (Y*)(lpCreateStruct->lpCreateParams); ATLASSERT(m_message_); // 設(shè)置屬性WS_POPUP LONG lWindowLong = ::GetWindowLong(pT->m_hWnd, GWL_STYLE) | WS_POPUP; // 去掉一堆其他屬性 lWindowLong &= ~WS_CHILD; lWindowLong &= ~WS_BORDER; lWindowLong &= ~WS_CAPTION; lWindowLong &= ~WS_SYSMENU; ::SetWindowLong(pT->m_hWnd, GWL_STYLE, lWindowLong); return 0; } LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ // 屏蔽背景消息 return TRUE; } // 這個(gè)指針指向最頂層的窗口,用它來(lái)將緊貼這頂層窗口的不可見(jiàn)窗口的消息 // 轉(zhuǎn)發(fā)給m_message_ // 注意宏CHAIN_MSG_MAP_ALT_MEMBER_EX Y* m_message_; }; // 這個(gè)類在SubWindowT的基礎(chǔ)上實(shí)現(xiàn)了全透明的效果 // 利用SetLayeredWindowAttributes可以對(duì)某種特定的顏色實(shí)現(xiàn)全透明過(guò)濾的特性 template<typename Y> class SubWindow1 : public CWindowImpl<SubWindow1<Y>,CWindow>, public SubWindowT<SubWindow1<Y>,Y>{ typedef SubWindowT<SubWindow1<Y>,Y> BaseClass; public: BEGIN_MSG_MAP(SubWindow1) CHAIN_MSG_MAP(BaseClass) //連接基類的消息處理邏輯,并優(yōu)先處理 MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_PAINT, OnPaint) REFLECT_NOTIFICATIONS() END_MSG_MAP() LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){ bHandled = FALSE; // 設(shè)置屬性WS_EX_LAYERED LONG lWindowLong = ::GetWindowLong(this->m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED; lWindowLong &= ~WS_EX_TRANSPARENT; ::SetWindowLong(this->m_hWnd, GWL_EXSTYLE, lWindowLong); // 在OnPaint里面將整個(gè)窗口刷成RGB(255,0,255) // 在這里將此顏色過(guò)濾(編程全透明) ::SetLayeredWindowAttributes(this->m_hWnd,RGB(255,0,255),0,LWA_COLORKEY); return 0; } LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ // 在OnPaint里面將整個(gè)窗口刷成RGB(255,0,255) // 以便讓SetLayeredWindowAttributes過(guò)濾 CPaintDC dc_(this->m_hWnd); CRect client_rect_; this->GetClientRect(client_rect_); dc_.FillSolidRect(client_rect_,RGB(255,0,255)); return TRUE; } }; template<typename T,bool DLG,template<typename X> class SUB_WINDOW > class ImageFrameExT : public ImageFrameT<T,DLG>{ typedef ImageFrameT<T,DLG> BaseClass; public: BEGIN_MSG_MAP(ImageFrameExT) CHAIN_MSG_MAP(BaseClass) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_MOVE, OnMove) END_MSG_MAP() ImageFrameExT(){ m_sub_rect_.SetRect(0,0,0,0); } LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){ bHandled = FALSE; T* pT = static_cast<T*>(this); if(!DLG){ // 一并創(chuàng)建子窗口 // 注意它將pT傳給了最后一個(gè)參數(shù),這個(gè)在 // OnCreate傳給了SubWindowT m_hSubWindow_.Create(pT->m_hWnd,NULL,NULL, WS_VISIBLE,0,0U,pT); } return 0; } LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; T* pT = static_cast<T*>(this); if(DLG){ // 一并創(chuàng)建子窗口 // 注意這里沒(méi)有WS_CHILD m_hSubWindow_.Create(pT->m_hWnd,NULL,NULL, WS_VISIBLE,0,0U,pT); } return 0; } LRESULT OnMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ // 動(dòng)態(tài)更新子窗口 bHandled = FALSE; T* pT = static_cast<T*>(this); if(m_hSubWindow_.IsWindow()){ CRect win_rect_; pT->GetWindowRect(win_rect_); if(m_sub_rect_.IsRectNull()){ m_hSubWindow_.MoveWindow(win_rect_); }else{ CRect tmp_ = m_sub_rect_; tmp_.OffsetRect(win_rect_.TopLeft()); m_hSubWindow_.MoveWindow(tmp_); } } return 0; } SUB_WINDOW<T>* SubWindow(){ // 獲得子窗口 return &m_hSubWindow_; } void SetSubRect(const CRect& rect){ // 可以設(shè)置讓子窗口位于那塊區(qū)域,而不一定要占滿整屏 m_sub_rect_ = rect; } protected: SUB_WINDOW<T> m_hSubWindow_; CRect m_sub_rect_; };
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public ImageFrameExT<CMainFrame,false,SubWindow1 > { typedef ImageFrameExT<CMainFrame,false,SubWindow1 > BaseClass; public: DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) BEGIN_MSG_MAP(CMainFrame) //REFLECT_NOTIFICATIONS() CHAIN_MSG_MAP(BaseClass) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) ALT_MSG_MAP(1) COMMAND_CODE_HANDLER(BN_CLICKED,OnClick) END_MSG_MAP() LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { m_close_.Create(this->m_hSubWindow_, CRect(200,200,50 + 200,26 + 200), _T("Close"), WS_CHILD | WS_VISIBLE); m_help_.Create(this->m_hSubWindow_, CRect(260,200,50 + 260,26 + 200), _T("Help"), WS_CHILD | WS_VISIBLE); m_help2_.Create(this->m_hSubWindow_, CRect(320,200,50 + 320,26 + 200), _T("Help2"), WS_CHILD | WS_VISIBLE); return 0; } LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { // 解決wtl不能關(guān)閉ws_popup的bug PostQuitMessage(0); bHandled = FALSE; return 1; } LRESULT OnClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled){ if(m_close_ == hWndCtl){ this->PostMessage(WM_CLOSE); }else if(m_help_ == hWndCtl){ }else if(m_help2_ == hWndCtl){ } return 0; } CButton m_close_; CButton m_help_; CButton m_help2_; };
其他問(wèn)題
若上面放置普通的矩形控件,并且不支持透明,那么啥問(wèn)題都沒(méi)有,然后若要實(shí)現(xiàn)不規(guī)則或者透明控件,那么全透明窗口被過(guò)濾的顏色將被顯示出來(lái)
一個(gè)變通的辦法就是將需要填充子控件的區(qū)域摳出來(lái),并交給這個(gè)子窗口來(lái)畫(這就要求放置控件區(qū)域的地方?jīng)]有半透明,大部分需求都只是希望能夠處理簡(jiǎn)單的異形,并且消除鋸齒)。
// 將父窗口中間摳出來(lái),交給子窗口來(lái)畫 template<typename Y> class SubWindow2 : public CWindowImpl<SubWindow2<Y>,CWindow>, public SubWindowT<SubWindow2<Y>,Y>{ typedef SubWindowT<SubWindow2<Y>,Y> BaseClass; public: BEGIN_MSG_MAP(SubWindow2) CHAIN_MSG_MAP(BaseClass) MESSAGE_HANDLER(WM_PAINT, OnPaint) REFLECT_NOTIFICATIONS() END_MSG_MAP() LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ CPaintDC dc_(this->m_hWnd); ATLASSERT(m_res_); if(m_res_) m_res_->Draw(dc_,0,0); return TRUE; } inline void SetRes(CImage* res){ ATLASSERT(res); if(res) this->m_res_ = res; } private: CImage* m_res_; };
class CAboutDlgEx : public CDialogImpl<CAboutDlgEx>, public ImageFrameExT<CAboutDlgEx,true,SubWindow2> { typedef ImageFrameExT<CAboutDlgEx,true,SubWindow2> BaseClass; public: enum { IDD = IDD_DIALOG1 }; BEGIN_MSG_MAP(CAboutDlgEx) CHAIN_MSG_MAP(BaseClass) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_RBUTTONDOWN, OnClose) ALT_MSG_MAP(1) END_MSG_MAP() LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ bool ret_ = m_res_.InitFromFile( _T("res/imgbtn.png"), _T("res/imgbtn_h.png"), _T("res/imgbtn_p.png"), _T("res/imgbtn_d.png")); ATLASSERT(ret_); m_btn_.Create(this->m_hSubWindow_,NULL,NULL,0,0,0U,&m_res_); m_btn_.MoveWindow(120,120,0,0,TRUE); return 0; } LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ EndDialog(0); return 0; } ImageButton<ButtonRes,true> m_btn_; ButtonRes m_res_; };
經(jīng)測(cè)試,這種方案在拖動(dòng)時(shí),兩個(gè)窗口交接處有明顯的刷新不一致存在。
Bug
在一些非32位圖像模式下,該函數(shù)會(huì)有些問(wèn)題,解決辦法就是強(qiáng)制創(chuàng)建32位的Bitmap
CBitmap bitmap; BITMAPINFOHEADER bmih; bmih.biSize = sizeof (BITMAPINFOHEADER) ; bmih.biWidth = 384 ; bmih.biHeight = 256 ; bmih.biPlanes = 1 ; bmih.biBitCount = 32 ; //注意32位 bmih.biCompression = BI_RGB ; bmih.biSizeImage = 0 ; bmih.biXPelsPerMeter = 0 ; bmih.biYPelsPerMeter = 0 ; bmih.biClrUsed = 0 ; bmih.biClrImportant = 0 ; bitmap.CreateDIBitmap(dc,&bmih);
為了避免繪圖匯到左上角,需要在調(diào)用UpdateLayeredWindow時(shí),明確指定左上角坐標(biāo)
CRect rect_; GetWindowRect(rect_); CPoint topleft(rect_.left,rect_.top); CPoint pt_(0,0); CSize size_(m_res_->GetWidth(),m_res_->GetHeight()); ::UpdateLayeredWindow(pT->m_hWnd,dc_,&topleft,&size_,mem_dc_,&pt_,0,&pb_,ULW_ALPHA );
以上就是UpdateLayeredWindow實(shí)現(xiàn)任意異形窗口使用詳解的詳細(xì)內(nèi)容,更多關(guān)于UpdateLayeredWindow 異形窗口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#實(shí)現(xiàn)定時(shí)關(guān)機(jī)小應(yīng)用
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)定時(shí)關(guān)機(jī)小應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07C#使用FileSystemWatcher控件實(shí)現(xiàn)的文件監(jiān)控功能示例
這篇文章主要介紹了C#使用FileSystemWatcher控件實(shí)現(xiàn)的文件監(jiān)控功能,結(jié)合實(shí)例形式分析了C# FileSystemWatcher組件的功能及監(jiān)控文件更改情況的具體使用技巧,需要的朋友可以參考下2017-08-08WPF使用DrawingContext實(shí)現(xiàn)二維繪圖
這篇文章介紹了WPF使用DrawingContext實(shí)現(xiàn)二維繪圖的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06如何通過(guò)IL了解C#類的構(gòu)造函數(shù)淺析
這篇文章主要給大家介紹了關(guān)于如何通過(guò)IL了解C#類的構(gòu)造函數(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02C#利用ScriptControl動(dòng)態(tài)執(zhí)行JS和VBS腳本
C#中利用ScriptControl動(dòng)態(tài)執(zhí)行JS和VBS腳本的實(shí)現(xiàn)方法,需要的朋友可以參考下2013-04-04Unity3D使用GL實(shí)現(xiàn)圖案解鎖功能
這篇文章主要為大家詳細(xì)介紹了Unity3D使用GL實(shí)現(xiàn)圖案解鎖功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03