【Visual C++】游戏开发笔记二十六 DirectX 11各组件的介绍第一个DirectX 11 Demo的创建
這節筆記主要討論DirectX 11現有的組件構成,隨著DirectX各個版本的更新被時代拋棄的一些組件以及第一個圍繞著DirectX 11 的Demo創建全過程。
一、DirectX11的現有組件
?
DirectX的API被分為頗多組件,每一組件都展現了系統不同方面的功能。其中的每一套API都能被獨立的使用,因此我們可以按照所需有選擇地自由添加我們游戲中需要的功能。在最新版本的DirectX中,不少組件迎來了更新,如Direct3D。其他的組件保持著現有的功能,當然,也有一些被棄用的組件面臨著被移除的命運。值得一提的是,DirectX中的各個內部組件可以獨立進行新功能的升級。
下面進行分別介紹:
?
1.Direct2D
Direct2D用于Win32應用程序中2D圖形的繪制。Direct2D善于高性能矢量圖形的渲染。
?
2. DirectWrite
?
DirectWrite用于Direct2D應用程序中的字體和文字渲染。
?
3 . DXGI
DXGI(DirectX Graphics Infrastructure)即DirectX圖形基礎,用于
Direct3D交換鏈(swap chains)和枚舉設備適配器(enumeration of deviceadapters)的創建。
?
4 .Direct3D
?
Direct3D用于DirectX中所有的與3D圖形相關的操作。Direct3D可謂DirectX中的大牌級API,受到了微軟最多的關懷與多次針對性的更新,它也最多被世人所熟知。這個專欄后續內容的大部分的篇幅將集中于講解Direct3D上。
?
5. XAudio2
?
XAudio2 是一款底層的音頻處理API,以前為XDK(Xbox Development Kit)的一部分,目前,隸屬于DIRECTXSDK。XAudio2替換了DirectSound。初始版本的XAudio用于的第一代Xbox游戲機。
?
6. XACT3
?
XACT3是一款建立在XAudio2之上的更高級別的音頻處理API。XACT3允許開發者在他們的應用中使用跨平臺音頻創作工具。若開發者希望從底層控制音頻系統或者希望創建自己的類型于XACT3的更高級別的音頻系統,可以運用XAdio2來完成各項功能。XACT3,我們已經討論過,是一款非常容易上手的游戲音頻制作工具。
?
7. XInput
?
XInput是XDK和DirectX SDK的負責輸入功能的 API,用于處理Xbox 360控制器輸入?;旧先魏慰梢栽赬box 360上可以使用的命令,都可以在PC上使用,而XInput就是幫助我們在這些設備上進行輸入相關操作的API。這些設備不僅包含Xbox手柄,也包含了其他很多設備。需要說明的是,XInput 是之前版本DirectInput的一個補充,但XInput不支持鍵盤和鼠標等PC最常見的輸入設備,所以涉及到鍵盤及鼠標消息的處理,還是需要使用DirectInput。
?
??? 注:XAudio是只能用于Xbox游戲機的音效API。 XAudio2,其繼任者,可用于Xbox游戲機和基于Windows的PC。
?
8 . XNAMath
?
新出現的XNA Math 不是一款API,而是是一個數學庫,進行電子游戲中常見運算的優化工作。XNA Math采用了SIMD (Single Instruction Multiple Data單指令多數據)來執行多個操作的單指令調用。XNA Math庫適用于基于Windows的PC以及Xbox 360。相關內容我們將在后續內容繼續講解。
?
注:XNA GameStudio為一款基于DirectX的游戲開發工具,可供我們使用C#和.NET編程語言來為Xbox360與Windows PC平臺編寫游戲。而XNA Math是一款DirectX SDK中數學庫的名字,可獨立于XNA Game Studio單獨使用。我們使用XNA Math不必下載XNA Game Studio SDK。
?
9 . DirectCompute
?
DirectCompute是一款DIRECTX 11中新加入的API,作用為支持GPU的通用多線程計算(general purpose multi threading computing)。GPU具有并行處理多任務的能力,如物理,視頻壓縮與視頻解壓,音頻處理等。并非所有的任務都適合GPU來完成,但是對于這些,由GPU來完成的可能性是巨大的(but for those that are, the possibilities are tremendous)。想了解DirectCompute的更多信息,可以查閱相關資料。
?
10. DirectSetup
當我們完成游戲開發的時候,我們理所當然地需要把完成品呈現給用戶。DirectSetup給我們提供了在用戶的電腦上安裝最新版本的DirectX的功能。DirectSetup也具有檢查已經安裝了的DirectX版本號的功能。
?
11.Windows Games Explorer
?
WindowsGames Explorer(游戲資源管理器)是Windows Vista與Windows 7中的新特性,可供游戲開發者在他們的操作系統上展示(測試)他們的游戲。比如游戲的顯示,標題,評級,描述,特定區域的風格框,評分內容(如M為成人,T為青少年等),游戲數據統計和通知,家長控制等。DirectX SDK中提供了大量如何使用自己的游戲的游戲資源管理器的教程,這對于發行一款游戲是非常有用的。下面的圖1是淺墨電腦上的Windows7游戲資源管理器的界面截圖
圖1 ?Windows 7中游戲資源管理器的示例
?
12. DirectInput
DirectInput是一款偵測鍵盤、鼠標和操作桿輸入的API。目前Xinput用于所有的游戲控制器。對于鍵盤和鼠標我們可以使用Win32函數或者使用DirectInput,后續內容將對DirectInput展開講解。根據DirectX SDK,DirectInput將繼續保留目前的形式,直到它被新的技術所取代。
二、已過時DirectX組件
?
開門見山吧,下面這些組件已經過氣,或者已被DirectX SDK移除:
?
1 . DirectDraw
DirectDraw曾經用于2D圖形的渲染,目前我們可以運用Direct2D或者Direct3D來進行2D圖形的繪制。在DirectX 8中,DirectDraw與Direct3D進行了合并,并改名為DirectX Graphics。
注:在早期版本的DIRECTX中,2D圖形繪制功能由DirectDraw完成。因為DirectDraw不再被更新,我們最好在Direct3D和Direct2D中完成圖形的繪制。
?
2 . DirectPlay
?
DirectPlay用于網絡游戲的網絡功能配置,基于UDP(UserDatagram Protocol)協議,并擔任更高級別的抽象層網絡通信。目前這款API被移除于DIRECTX SDK之外,以便于PC與Xbox360平臺上Windows Live中游戲的更好的整合。
?
3. DirectShow
?
DirectShow是一款用于多媒體渲染和錄音的API。DirectShow能夠播放常見的視頻文件,并提供DVD視頻導航菜單等功能。當前DirectShow為Windows SDK的一部分,而不再隸屬于DirectX SDK。此外,Windows Vista與Windows 7用戶可以使用的微軟媒體中心(Microsoft’s MediaFoundations),也是Windows SDK的一部分。在電子游戲中,若需要顯示切換CG的場景與視頻文件, DirectShow也可以派上用場。
4. DirectMusic
?
作用為在應用程序中播放音頻內容的DirectMusic ,在DirectX 7及后續版本中被移除。DirectMusic提供了與底層音頻與硬件溝通的渠道,在眾多的DirectX相關教程中叱咤風云多年。
現今,在游戲和多媒體應用相關的音頻操作中,我們使用XAudio2(底層)或XACT3(高層)來替代服役了多年的,頗具疲態的DirectMusic。
5. DirectSound
?
DirectSound是DirectX中另一款被廢置的底層音頻API,現今被XAudio2取代。
三、第一個DirectX Demo
?
學習DirectX的最好方式就是勤動手,從頭開始一步一步創建簡單的demo應用程序。在接下來連續的幾篇文章中,我們將一起通過一步步簡單的操作,創建我們的第一個DirectX應用程序。隨著學習的深入,我們將對D3D從開始設置到結束過程有一個堅實的認識。
?
?
Ⅰ.工程的創建
?
首先我們需要創建一個工程。
創建一個空白的Win32 窗口新工程請執行以下步驟:
?
1.???? 打開Visual Studio 2010,從菜單中依次選擇 【文件】->【新建】->【工程】
(【File】 > 【New】 > 【Project】),這時會彈出一個新建工程的對話框,如下圖 2
圖2
彈出如下窗口:
?
圖3
?
2.???? 我們以“BlankWindowDemo”作為工程名,然后在工程模板列表中選擇“EmptyProject”,完成后,點擊OK按鈕,如圖4
?
圖4
?
3.點擊“完成”按鈕。
?
一個新的空工程就創建完畢了。下一步就是在工程中添加模板代碼了。
?
Ⅱ.代碼的書寫
?
完成上面的步驟,Visual Studio 2010已經為我們創建好一個空的工程了,下一步就是加入源代碼來初始化主程序的窗口了。我們以添加一個空白的Cpp文件到工程中開始,這個文件將稱為我們的main源程序文件,我們將它命名為main.cpp。創建main.cpp文件的步驟如下:
⑴???? 在Visual Studio的資源管理器中右擊源文件文件夾,然后在彈出的對話框中選擇【Add New Item】,如圖5
?
圖5
⑵??? 在彈出的對話框中選擇C++源文件類型,命名為main.cpp。如圖6
?
⑶點擊“OK”完成。
?
?
?
?
在工程中創建好main.cpp源文件后,我們便可以在其中添加完成Win32空白窗口工作的源代碼。一旦我們加入了主程序入口點的代碼,我們就可以初始化D3D 11然后利用D3D渲染窗口畫布。
?
代碼講解之一:主函數入口點
main.cpp需要注意的第一個要點是包含必要的Win32頭文件和入口點函數。我們應該知道,Win32應用程序的入口點是WinMain函數。目前為止,我們只需要在源文件頂部包括Windows.h頭文件即可。在代碼段1中可以看到空的WinMain函數和main.cpp的開始部分。
?
代碼段1:Blank Win32 Window Demo書寫步驟之一:wWinMain 函數框架
?
[cpp] view plaincopyprint?
?
在上面的代碼中我們可以看到用wWinMain替換了一般情況下會采用的WinMain。兩者的區別是wWinMain用來處理Unicode類型的變量,特別是它的第三個參數cmdLine,而WinMain會執行Unicode和ANSI之間的轉換。而這樣的轉換可能導致缺少一個Unicode字符串中的字符。所以運用wWinMain能讓我們正確的處理被傳遞給應用程序的Unicode類型的參數。
(w)WinMain函數有四個參數。這些參數被定義為如下:
▲HINSTANCE hInstance.應用程序的當前句柄實例
▲HINSTANCEprevInstance. 應用程序的之前句柄實例。MSDN文檔中表明這個參數恒為NULL。由于此參數恒為NULL,如果我們需要找到一個判斷之前應用程序的實例是否運行的方法,文檔中建議我們可以利用CreateMutex函數創建一個唯一命名的互斥變量(mutex)。若該互斥變量被創建了,CreateMutex函數將返回ERROR_ALREADY_EXISTS。
▲LPSTR cmdLine (orLPWSTR in Unicode). 應用程序不包括程序名的命令行指令。
這使得我們可以將命令行指令傳遞給應用程序,比如在命令提示行中利用命令行字符串提供的快捷操作。
▲int cmdShow. 一個窗口如何被顯示的特殊的ID,可取很多已有完善的代碼支持的ID。具體取值見下表:
?
| ID值名稱 | 描述 |
| SW_HIDE | 隱藏此窗口,激活其他窗口 |
| SW_SHOW | 以當前的尺寸和位置激活和顯示該窗口 |
| SW_SHOWNA | 以當前的狀態顯示該窗口,并保持激活狀態 |
| SW_SHOWNOACTIVATE | 用最近的尺寸和大小顯示一個窗口,并保持激活狀態 |
| SW_SHOWNORMAL | 激活和顯示一個窗口,如果這個窗口是最小化或者最大化,系統將它重置為原始尺寸和位置(與SW_RESORE相同) |
?
?
? ??
?
在窗口創建過程中顯示窗口的時候,需要使用show ID命令,后面中我們會具體講解。
代碼講解之二:窗口的初始化
?
如果沒有進行窗口創建過程的話,即使應用程序運行了,也不會在屏幕上顯示出來。所以下一步我們的任務是創建Win32窗口。首先,我們注冊窗口類,然后創建窗口本身。如代碼段2中所述。應用程序必須在系統中注冊它的窗口。
?
代碼段2:Blank Win32 Window Demo書寫步驟之二:窗口的注冊和創建
?
[cpp] view plaincopyprint?
?
Win32宏UNREFERENCED_PARAMETER用于避免變量定義后沒在函數體中使用的編譯警告。雖然這在技術上來講是毫無必要的,但是作為養成良好的編程習慣,我們在組建源代碼時,要力爭零warning。由于這樣的宏實際上什么都沒有做,Visual Studio的編譯器在編譯時,會將其忽視的,就像忽視喜聞樂見的注釋一樣。
在兩句UNREFERENCED_PARAMETER宏之后,我們用WINDCLASSEX定義了一個窗口類,即用wndClass實例化了WINDCLASSEX。這個窗口類中包含很多Win32窗口的很多屬性,比如窗口圖標,窗口菜單,窗口的應用實例,光標的樣式等等。WNDCLASSEX包含在頭文件Winuser.h中,而Winuser.h又包含在windows.h中。即我們只需在頭文件中包含Windows.h即可。我們可以在windows.h源代碼中找到如下描述:
?
[cpp] view plaincopyprint?
?
WNDCLASSEX結構體中的參數詳細剖析如下:
?
▲cbSize.結構體的字節數大小
▲style.第二個成員變量style指定這一類型窗口的樣式,常用的樣式如下:
| ID值名稱 | 描述 |
| CS_HREDRAW | 當窗口水平方向上的寬度發生變化時,將重新繪制整個窗口。當窗口發生重繪時,窗口中的文字和圖形將被擦除。如果沒有指定這一樣式,那么在水平方向上調整窗口寬度時,將不會重繪窗口。 ? |
| CS_VREDRAW | 當窗口垂直方向上的高度發生變化時,將重新繪制整個窗口。如果沒有指定這一樣式,那么在垂直方向上調整窗口高度時,將不會重繪窗口。 ? |
| CS_NOCLOSE | 禁用系統菜單的Close命令,這將導致窗口沒有關閉按鈕。 ? |
| CS_DBLCLKS | 當用戶在窗口中雙擊鼠標時,向窗口過程發送鼠標雙擊消息。 ? |
▲lpfnWndProc. 第二個成員變量lpfnWndProc是一個函數指針,指向窗口過程函數,窗口過程函數是一個回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外一方調用的,用于對該事件或條件進行響應。回調函數實現的機制是:
?
?
(1)定義一個回調函數。
?
(2)提供函數實現的一方在初始化的時候,將回調函數的函數指針注冊給調用者。
?
(3)當特定的事件或條件發生的時候,調用者使用函數指針調用回調函數對事件進行處理。
?
針對Windows的消息處理機制,窗口過程函數被調用的過程如下:
?
(1)在設計窗口類的時候,將窗口過程函數的地址賦值給lpfnWndProc成員變量。
?
(2)調用RegsiterClass(&wndclass)注冊窗口類,那么系統就有了我們所編寫的窗口過程函數的地址。
?
(3)當應用程序接收到某一窗口的消息時,調用DispatchMessage(&msg)將消息回傳給系統。系統則利用先前注冊窗口類時得到的函數指針,調用窗口過程函數對消息進行處理。
?
一個Windows程序可以包含多個窗口過程函數,一個窗口過程總是與某一個特定的窗口類相關聯(通過WNDCLASS結構體中的lpfnWndProc成員變量指定),基于該窗口過程。
?
lpfnWndProc成員變量的類型是WNDPROC,我們在VC++開發環境中使用goto definition功能,可以看到WNDPROC的定義:
?
typedef LRESULT (CALLBACK*WNDPROC)(HWND, UINT, WPARAM, LPARAM);
?
在這里又出現了兩個新的數據類型LRESULT和CALLBACK,再次使用goto definition,可以看到它們實際上是long和__stdcall。
?
從WNDPROC的定義可以知道,WNDPROC實際上是函數指針類型。
?
注意:WNDPROC被定義為指向窗口過程函數的指針類型,窗口過程函數的格式必須與WNDPROC相同。
?
?
?
?知識點 ?在函數調用過程中,會使用棧。__stdcall與__cdecl是兩種不同的函數調用約定,定義了函數參數入棧的順序,由調用函數還是被調用函數將參數彈出棧,以及產生函數修飾名的方法。關于這兩個調用約定的詳細信息,大家可參看MSDN。對于參數個數可變的函數,例如printf,使用的是__cdecl調用約定,Win32的API函數都遵循__stdcall調用約定。在VC++開發環境中,默認的編譯選項是__cdecl,對于那些需要__stdcall調用約定的函數,在聲明時必須顯式地加上__stdcall。在Windows程序中,回調函數必須遵循__stdcall調用約定,所以我們在聲明回調函數時要使用CALLBACK。使用CALLBACK而不是__stdcall的原因是為了告訴我們這是一個回調函數。注意,在Windows 98和Windows 2000下,聲明窗口過程函數時,即使不使用CALLBACK也不會出錯,但在Windows NT4.0下,則會出錯。
?
▲cbClsExtra. 類附加內存。
▲cbWndExtra. 窗口附加內存。
▲hInstance. 指定包含窗口過程的程序的實例句柄。
▲hIcon. 指定窗口類的圖標句柄。
▲hCursor. 指定窗口類的光標句柄。
▲hbrBackground. 指定窗口類的背景畫刷句柄。
▲lpszMenuName. 一個以空終止的字符串,指定菜單資源的名字。
▲lpszClassName. 一個以空終止的字符串,指定窗口類的名字。
▲hIconSm. 指定窗口類的小圖標句柄。(就像在任務欄右下角托盤中應用程序顯示的小圖標)
大部分的Win32應用程序的窗口特性我們是不做考慮的,如創建一個菜單(在編輯區之外,我們一般不會在游戲中創建一個Win32菜單)。這些成員變量我們在這里一般設為0。
隨著WNDCLASSEX結構體的創建,我們可以將它用RegisterClassEx( )調用,來注冊一個窗口。RegisterClassEx()必須在我們創建窗口之前調用,且調用時需要用到窗口類的地址作為變量。若函數返回值為0,則表示注冊失敗,這時,我們需要檢查窗口相關值的合法性。
下一步就是創建實際的窗口了。首先我們調用AdjustWindowRect()函數來根據我們設定的尺寸和風格來計算窗口的尺寸。窗口的類型取決于我們需要的真實尺寸。如果我們需要Win32應用程序,就會有像標題欄這樣的非客戶區,一個環繞著應用程序的邊框,等等。如果我們要創建一個特殊的窗口尺寸,我們需要牢記在心的是,應用程序既有客戶區,也有非客戶區。
?
AdjustWindowRect函數首先用利用一個矩形定義左下角,右下角,左上角,右上角窗口區域的坐標。左上角的屬性代表了窗口的起始位置,結合右下角則可以反應窗口的寬度和高度。AdjustWindowRect函數中也專門有一個布爾類型的標明窗口類型的變量,指示窗口是否擁有菜單欄,而有無菜單欄影響著非客戶區。
下一步我們調用Win32函數CreateWindow來創建我們的窗口。在下表中我們講解它的一個變體CreateWindowA。兩者的主要的區別是CreateWindowA接受ANSI編碼類型的字符串變量,而CreateWindows接受Unicode編碼類型的字符串變量。若要使用Unicode類型,我們可以用L”XX”將我們的Unicode類型字符串括起來,而其中“XX”處填寫Unicode字符串的內容。
?
Win32函數CreateWindowA的變量如下:
?
▲ lpClassName (可選)?? 窗口類名(和窗口類結構體一致)
▲ lpWindowName (可選)? 窗口標題欄文本
▲ dwStyle 窗口類型標識
▲ X—窗口的水平位置。
▲ Y—窗口的豎直位置。
▲ nWidth—窗口寬度
▲ hHeight—窗口高度
▲ hWndParent (可選)—父窗口句柄的一個句柄 (若此新窗口為彈出窗口或者子窗口,本變量為為可選).
▲ hMenu (可選)—窗口菜單的資源句柄
▲ hInstance (可選)—應用程序實例ID (wWinMain的第一個變量).
▲ lpParam (optional)—通過窗口過程回調函數的lpParam參數,傳遞給窗口數據
(在窗口回調過程中深入講解).
CreateWindow(A)的返回值是一個空句柄。如果CreateWindow(A)創建成功,我們可以調用Win32函數ShowWindow來顯示窗口,ShowWindow()函數需要使用ShowWindow函數返回的窗口句柄,以及cmdShow參數(wWinMain的最后一個變量)。
當窗口被創建后,應用程序就能開始執行它的工作了。Win32 GUI應用程序是基于事件的應用程序。這就意味著當事件發生時,應用程序得到通知,然后進行相關的響應。
?
?
?
這樣的消息響應機制持續到應用程序退出。例如,當Microsoft Word運行后,一個“創建(Create)”事件被激發,應用程序便開始加載。當用戶點擊工具欄上與菜單上等的按鈕的時候,一個事件就被觸發,發送到應用程序處進行處理。如果打開文件按鈕的鼠標點擊事件被觸發,然后一個對話框會顯示出來,使用戶可以直觀地選擇需要打開的文件。許多應用程序都是基于事件而運行與工作的。
在電子游戲中,應用程序是實時的,這意味著很多事件與行為的發生與否并不是應用程序一直在執行很多任務的。若用戶按下游戲控制器的按鈕,通常會在游戲循環的更新步驟中被檢測到,然后游戲程序進行對應的響應。如果沒有事件發生,游戲程序依然會通過渲染當前的游戲狀態而運行(例如,渲染菜單,電影,游戲地圖等),并執行邏輯更新,查找和響應網絡數據,進行音頻播放等等。
?
無論是實時還是基于事件的程序,都會在啟動運行后一直處于運行的狀態,直到用戶決定退出。在這里介紹應用程序循環的概念。應用程序循環我們解釋為,是一個持續的無限的循環,直到用戶進行了相關打破這種循環的操作??梢酝ㄟ^接受到一個WM_QUIT(Win32運用程序的退出消息)消息。在應用程序中點擊“退出”來關閉該程序。(例如,在游戲運行時按下Esc鍵也許會出現一個暫停的畫面,但不應該是退出,除非我們去那樣設計),或者設計任何其他方式的退出。在此專欄系列的后續內容,我們將演示只有當用戶按Esc鍵,或者單擊窗口右上角的“X”關閉按鈕的時候,應用程序才會退出。
這是一個我們之后中會用到的應用循環的例子
?
代碼段3:Blank Win32 Window Demo書寫步驟之三—— 應用循環
[cpp] view plaincopyprint?
其中static_cast<>運算符是用于進行強制類型轉換的C++運算。
?
?
?
?
代碼講解之三:窗口回調過程
這里就直接上代碼吧。
代碼段4:Blank Win32 Window Demo書寫步驟之四:窗口回調過程
[cpp] view plaincopyprint?
?
窗口過程函數返回了LRESULT類型,運用了CALLBACK類型。函數本身可以被隨意命名,我們在這里采用WndProc。
?
?
?代碼講解之四:完整的源代碼
將以上幾個部分的講解串聯起來,加之細節上的修改,就得到了Blank Win32 Window Demo的完整源代碼:
?
[cpp] view plaincopyprint?
對代碼進行編譯運行,就可以得到一個空白的窗口,如下圖。這個Demo放在這里講很有必要,可以作為我們之后講解的程序的模板,后面我們會接著創建很多Demo,都是在這個最基礎的Demo之上添加相關代碼即可。
程序運行后得到如下窗口:
圖7
本節筆記到這里就結束了。
本篇文章配套的源代碼請點擊這里下載: 【Visual C++】Note_Code_26
感謝一直支持【Visual C++】游戲開發筆記系列專欄的朋友們。
【Visual C++】游戲開發 系列文章才剛剛展開一點而已,因為游戲世界實在是太博大精深了~
但我們不能著急,得慢慢打好基礎。做學問最忌好高騖遠,不是嗎?
?
淺墨希望看到大家的留言,希望與大家共同交流,希望得到睿智的評論(即使是批評)。
你們的支持是我寫下去的動力~
?
精通游戲開發的路還很長很長,非常希望能和大家一起交流,共同學習,共同進步。
大家看過后覺得值得一看的話,可以頂一下這篇文章,你們的支持是我繼續寫下去的動力~
如果文章中有什么疏漏的地方,也請大家指正。也希望大家可以多留言來和我探討相關的問題。
最后,謝謝你們一直的支持~~~
???????????????????????????????????????????????
?
????????????????????????????????????????????????? ——————————淺墨于2012年6月24日
總結
以上是生活随笔為你收集整理的【Visual C++】游戏开发笔记二十六 DirectX 11各组件的介绍第一个DirectX 11 Demo的创建的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(3170):react-hel
- 下一篇: 面对 this 指向丢失,尤雨溪在 Vu