【VC++游戏开发】用C++来架构一个适合windows游戏编程的框架——取名为BCF

来源:互联网 发布:sql replaceinto 编辑:程序博客网 时间:2024/06/06 10:51

本文由BlueCoder编写   转载请说明出处:

http://blog.csdn.net/crocodile__/article/details/18375315

我的邮箱:bluecoder@yeah.net    欢迎大家和我交流编程心得

我的微博:BlueCoder_黎小华    欢迎光临^_^


一、序言

众所周知:

MFC适合桌面应用的开发,而不太适合windows游戏编程,因为它封装了很多我们游戏编程中所不需要的东西,这些东西在一定程度上都影响着GDI的效率,略显冗余了。但是MFC有丰富的类库,这在写代码时又能提供很大的方便……


再来看看Win32 SDK,接近底层,效率肯定好,但是却没有MFC那样的类库,写代码着实不太方便……


这样一想,我就有个问题了:在接下来的游戏效果模拟中,是继续使用MFC,还是专用Win32SDK呢?或者还有什么更好的方法?……

这么一想,嘿,一个idea就诞生了:用C++来自行架构一个适合windows游戏编程的框架,使它既能使用MFC类库效率又更好

这个框架如何架构呢?请继续阅读下面的内容吧^_^


二、架构的一个简易框架,MFC类库、效率都能兼顾

我的架构思路:

       1>将窗口创建过程(设计窗口类结构体实例、注册窗口类、创建窗口、显示窗口、消息循环)以及消息响应函数都封装在一个名为CCWindow的C++类中

       2>在Main.cpp源文件中就实现WinMain以及窗口过程


由于我的开源框架的源代码中有详尽的注释,F话就不多说了,直接贴上源代码:

StdAfx.h头文件:这是使用MFC类库所必须添加的头文件

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /*** 
  2. * 
  3. *   StdAfx.h 
  4. *   包含一些用到的类库所在的头文件 
  5. * 
  6. ***/  
  7.   
  8. #pragma once  
  9.   
  10. #define _AFXDLL//动态添加MFC类库  
  11. #include <SDKDDKVer.h>//定义最高版本windows  
  12.   
  13. #include <afxwin.h>       // MFC封装的核心组件和标准组件  
  14. #include <atlimage.h> //CImage类包含的头文件  
  15. #include <MMSystem.h> //播放媒体(音乐)所需包含的头文件  
  16. #pragma comment(lib, "winmm.lib")//添加媒体库  
  17.   
  18. #pragma comment(linker,"/manifestdependency:\"type='win32' \  
  19.                 name='Microsoft.Windows.Common-Controls' \  
  20.                 version='6.0.0.0' processorArchitecture='x86' \  
  21.                 publicKeyToken='6595b64144ccf1df' language='*'\"")  

CWindow.h头文件(CCWindow类的定义)

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #pragma once  
  2. #include"StdAfx.h"  
  3. #include<time.h>  
  4.   
  5. class CCWindow  
  6. {  
  7. //==================成员====================  
  8. private:  
  9.     WNDCLASS    m_wndclass; //窗口类结构体实例  
  10. public:  
  11.     HWND        m_hwnd;     //窗口句柄  
  12.     CImage      m_img;      //背景图片  
  13.     CRect       m_rect;     //窗口户区大小  
  14.   
  15. //=============窗口创建相关的成员函数=============  
  16. public:  
  17.     //设计窗口类  
  18.     bool InitWndClass(  
  19.                     HINSTANCE hInstance,    //实例句柄  
  20.                     WNDPROC   wpWndProc,    //窗口过程  
  21.                     LPCTSTR   lpWndName,    //窗口名称  
  22.                     LPCTSTR   lpIconPath);  //图标路径  
  23.   
  24.     //设计窗口类  
  25.     bool InitWndClass(WNDCLASS);  
  26.       
  27.     //注册窗口类  
  28.     ATOM RegisterWndClass();  
  29.   
  30.     //创建窗口(默认居中)  
  31.     void Create(  
  32.             LPCTSTR lpClassName,    //窗口类名称  
  33.             LPCTSTR lpWndName,  //窗口标题名称  
  34.             DWORD   dwStyle,        //窗口风格  
  35.             int     nWidth,         //窗口宽度  
  36.             int     nHeight);       //窗口高度  
  37.   
  38.     //显示窗口  
  39.     void Show(int);  
  40.   
  41.     //一般的消息循环  
  42.     int RunMsgLoop();  
  43.   
  44.     //更高效的消息循环  
  45.     int RunMsgLoop(void (*Display)(), int);  
  46.   
  47. //================消息响应函数================  
  48. public:  
  49.     //注:在这里添加需要响应的消息处理函数的声明  
  50.   
  51. public:  
  52.     //构造函数  
  53.     CCWindow(void);  
  54.     //析构函数  
  55.     ~CCWindow(void);  
  56. };  

CWindow.cpp(CCWindow类的成员函数实现)

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include "CWindow.h"  
  2.   
  3.   
  4. CCWindow::CCWindow(void)  
  5. {  
  6. }  
  7.   
  8. CCWindow::~CCWindow(void)  
  9. {  
  10. }  
  11.   
  12. /*------------------------------ 
  13.     *功能:设计窗口类 
  14.     *@hInstance:    实例句柄 
  15.     *@WndProc:      窗口过程 
  16.     *@WndName:      窗口名称 
  17.     *@IconPath:     图标路径 
  18.  -----------------------------*/  
  19. bool CCWindow::InitWndClass(HINSTANCE hInstance,//实例句柄  
  20.                             WNDPROC WndProc,    //窗口过程  
  21.                             LPCTSTR WndName,    //窗口名称  
  22.                             LPCTSTR IconPath)   //图标路径  
  23. {  
  24.     ZeroMemory(&m_wndclass, sizeof(WNDCLASS));  
  25.   
  26.     m_wndclass.style            = CS_HREDRAW | CS_VREDRAW;  
  27.     m_wndclass.lpfnWndProc      = WndProc;  
  28.     m_wndclass.hInstance        = hInstance;  
  29.     m_wndclass.cbClsExtra       = 0;  
  30.     m_wndclass.cbWndExtra       = 0;  
  31.     m_wndclass.hIcon            =   
  32.         static_cast<HICON>(LoadImage(NULL, IconPath, IMAGE_ICON,  
  33.                         0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE));  
  34.     m_wndclass.hCursor          = ::LoadCursor(NULL, IDC_ARROW);  
  35.     m_wndclass.hbrBackground    =   
  36.         static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));  
  37.     m_wndclass.lpszMenuName     = NULL;  
  38.     m_wndclass.lpszClassName    = WndName;  
  39.   
  40.     return true;  
  41. }  
  42.   
  43. /*-------------------------------------- 
  44.                 设计窗口类 
  45. --------------------------------------*/  
  46. bool CCWindow::InitWndClass(WNDCLASS wndclass)  
  47. {  
  48.     m_wndclass = wndclass;  
  49.   
  50.     return true;  
  51. }  
  52.   
  53. /*-------------------------------------- 
  54.                注册窗口类 
  55. --------------------------------------*/  
  56. ATOM CCWindow::RegisterWndClass()  
  57. {  
  58.     return RegisterClass(&m_wndclass);  
  59. }  
  60.   
  61. /*-------------------------------------- 
  62.                 创建窗口 
  63. --------------------------------------*/  
  64. void CCWindow::Create(  
  65.         LPCTSTR lpClassName,  
  66.         LPCTSTR lpWindowName,  
  67.         DWORD dwStyle,  
  68.         int nWidth,  
  69.         int nHeight)  
  70. {  
  71.     //获取屏幕宽度和高度  
  72.     int screenW = GetSystemMetrics(SM_CXSCREEN);  
  73.     int screenH = GetSystemMetrics(SM_CYSCREEN);  
  74.   
  75.     //创建并居中显示窗口  
  76.     m_hwnd = CreateWindow(lpClassName, lpWindowName, dwStyle,  
  77.                  (screenW-nWidth)/2, (screenH-nHeight)/2,  
  78.                  nWidth, nHeight, NULL, NULL,  
  79.                  m_wndclass.hInstance, NULL);  
  80. }  
  81.   
  82. /*-------------------------------------- 
  83.                 显示窗口 
  84. --------------------------------------*/  
  85. void CCWindow::Show(int nCmdShow)  
  86. {  
  87.     ShowWindow(m_hwnd, nCmdShow);  
  88. }  
  89.   
  90. //UpdateWindow(...)更新窗口(可以省略)  
  91.   
  92. /*-------------------------------------- 
  93.             一般的消息循环 
  94.              GetMessage() 
  95. --------------------------------------*/  
  96. int CCWindow::RunMsgLoop()  
  97. {  
  98.     MSG msg;  
  99.     ZeroMemory(&msg, sizeof(MSG));  
  100.   
  101.     while(GetMessage(&msg, NULL, 0, 0))  
  102.     {  
  103.         TranslateMessage(&msg);  
  104.         DispatchMessage(&msg);  
  105.     }  
  106.   
  107.     return msg.wParam;  
  108. }  
  109.   
  110. /*-------------------------------------- 
  111.         消息循环(更好的消息循环) 
  112.             PeekMessage() 
  113. --------------------------------------*/  
  114. int CCWindow::RunMsgLoop(void (*Display)(), int interval)  
  115. {  
  116.     MSG msg;  
  117.     ZeroMemory(&msg, sizeof(MSG));  
  118.   
  119.     //获取运行到此处时的时间  
  120.     int last = GetTickCount();  
  121.   
  122.     //如果不是退出消息  
  123.     while(msg.message != WM_QUIT)  
  124.     {  
  125.         //如果有消息  
  126.         if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))  
  127.         {  
  128.             TranslateMessage(&msg);  
  129.             DispatchMessage(&msg);  
  130.         }  
  131.         //否则, 空闲的时候执行响应函数(大多数是绘制函数)  
  132.         else  
  133.         {  
  134.             //如果窗口客户区大小不为0就是显示(有可能窗口是在最小化)  
  135.             if(m_rect.Width() &&   
  136.                m_rect.Height())  
  137.             {  
  138.                 Display();  
  139.                 Sleep(interval);  
  140.             }  
  141.         }  
  142.     }  
  143.   
  144.     return msg.wParam;  
  145. }  
  146.   
  147. //----------------------------------------------------------  
  148. //                      消息响应函数  
  149. //----------------------------------------------------------  
  150. //注:在这里添加需要响应的消息处理函数的实现  

Main.h头文件(主程序头文件)

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /*** 
  2. * 
  3. *   Main.h 
  4. *   主程序所需包含的头文件、宏定义、声明等 
  5. * 
  6. ***/  
  7.   
  8. #pragma once  
  9. #include "CWindow.h"  
  10.   
  11. #define WNDNAME "【VC++游戏开发】窗口名称"//窗口名称  
  12. #define WNDWIDTH 800//窗口宽度  
  13. #define WNDHEIGHT 600//窗口高度  
  14.   
  15. //窗口关联对象:全局对象  
  16. CCWindow wnd;  
  17.   
  18. //窗口过程  
  19. LRESULT CALLBACK WndProc(HWNDUINTWPARAMLPARAM);  

Main.cpp(主程序代码:负责WinMain、WndProc)

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;">#include "Main.h"  
  2.   
  3. /* 
  4.     显示函数:绘制随机位置、大小、颜色的矩形 
  5.     注:由于调用很频繁,故设为内联函数 
  6. */  
  7. inline void Display()  
  8. {  
  9.     //做响应的操作  
  10. }  
  11.   
  12. //  
  13. //==============WinMain=======================  
  14. //  
  15. int WINAPI WinMain(HINSTANCE hInstance,  
  16.                    HINSTANCE hPrevInstance,  
  17.                    LPSTR lpCmdLine,  
  18.                    int nCmdShow)  
  19. {  
  20.     //设计窗口类  
  21.     CString iconPath = "";//图标的路径(这里没有, 需自己设定)  
  22.     wnd.InitWndClass(hInstance, WndProc, WNDNAME, iconPath);  
  23.   
  24.     //注册窗口类  
  25.     if(!wnd.RegisterWndClass())  
  26.     {  
  27.         ::AfxMessageBox("RegisterWndClass() Failed");  
  28.   
  29.         return 0;  
  30.     }  
  31.   
  32.     DWORD style = WS_OVERLAPPEDWINDOW &  
  33.                 ~(WS_THICKFRAME | WS_MAXIMIZEBOX);  
  34.   
  35.     //创建窗口并居中窗口  
  36.     wnd.Create(WNDNAME, WNDNAME, style,  
  37.                WNDWIDTH, WNDHEIGHT);  
  38.   
  39.     //显示窗口  
  40.     wnd.Show(nCmdShow);  
  41.   
  42.     /*进入消息循环 
  43.         1. 使用更好的消息循环 如:wnd.RunMsgLoop(Display, 100) 
  44.         2. 使用一般的消息循环 如:wnd.RunMsgLoop() 
  45.     */  
  46.     return wnd.RunMsgLoop();  
  47. }  
  48.   
  49. //  
  50. //================窗口过程:处理消息=================  
  51. //  
  52. LRESULT CALLBACK WndProc(  
  53.                 HWND hwnd,  
  54.                 UINT msg,  
  55.                 WPARAM wParam,  
  56.                 LPARAM lParam)  
  57. {  
  58.     /* 
  59.     在这里添加消息映射代码(switch-case语句) 
  60.  
  61.     如: 
  62.         switch(msg) 
  63.         { 
  64.         case WM_CREATE: 
  65.             wnd.OnCreate(); //窗口建立消息:进行一些初始化操作 
  66.             return 0; 
  67.         } 
  68.     */  
  69.   
  70.     return DefWindowProc(hwnd, msg, wParam, lParam);  
  71. }</span>  


下面,我简单地介绍一下这个框架的使用方法:

1>创建一个空项目(我这里是VS2010)

2>将这3个头文件以及2个cpp源文件添加到创建好的项目中

3>在 CWindow.h中添加消息响应函数(CCWindow类的成员函数)的声明

如:(名称还是遵循MFC的命名方式)

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //================消息响应函数================  
  2. public:  
  3.     //注:在这里添加需要响应的消息处理函数的声明  
  4.     void OnCreate();;  
4>CWindow.cpp中实现这些消息响应函数

5>在Main.cpp的窗口过程函数中调用响应的消息响应函数

如:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. switch(msg)  
  2. {  
  3. case WM_CREATE:  
  4.     wnd.OnCreate(); //窗口建立消息:进行一些初始化操作  
  5.     return 0;  
  6. }  


我还是为这个框架取一个"艺名儿",名为:BCF

BC:我的csdn博客ID缩写——BlueCoder

F:frame——框架的意思

合起来就是BlueCoder的框架,当然,大家都可以用,只要你愿意,我也乐意^_^


三、一个简单的实例,教你熟悉这个框架

我做了一个简单的实例,来帮助大家熟悉这个框架


这个实例呢,就是简单的贴图、贴文字,不过你会看到有趣的部分:我使用了诸如CPaintDC、CFont、CString、CImage、CRect等MFC类库。对,没错,这就是这个框架的一大特点:可以使用MFC类库,这就方便了我们写代码


由于没什么复杂的原理,我就按照步骤贴出核心代码哈:

(1)项目、头文件、源文件已经相继处理好了

(2)在CCWindow类中添加如下的消息响应成员函数:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //================消息响应函数================  
  2. //注:在这里添加需要响应的消息处理函数  
  3. public:  
  4.     //处理WM_CREATE消息  
  5.     void OnCreate();  
  6.   
  7.     //处理WM_SIZE消息  
  8.     void OnSize();  
  9.       
  10.     //处理WM_PAINT消息  
  11.     void OnPaint();  
  12.       
  13.     //处理WM_KEYDOWN消息  
  14.     void OnKeyDown(WPARAM);  
  15.       
  16.     //处理WM_DESTROY消息  
  17.     void OnDestroy();  

(3)这些消息响应函数的实现:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //----------------------------------------------------------  
  2. //                      消息响应函数  
  3. //----------------------------------------------------------  
  4.   
  5. //窗口建立消息:进行一些初始化操作  
  6. void CCWindow::OnCreate()  
  7. {  
  8.     m_img.Load("res\\bg.jpg");  
  9.   
  10.     if(m_img.IsNull())  
  11.     {  
  12.         AfxMessageBox("背景图片加载失败!");  
  13.         exit(0);  
  14.     }  
  15.   
  16.     mciSendString("open res\\bgm.mp3 alias bgm", 0, 0, 0);  
  17.     mciSendString("play bgm repeat", 0, 0, 0);  
  18. }  
  19.   
  20. //窗口size消息:获取窗口大小  
  21. void CCWindow::OnSize()  
  22. {  
  23.     ::GetClientRect(m_hwnd, m_rect);  
  24. }  
  25.   
  26. //窗口Paint消息:绘制窗口客户区  
  27. void CCWindow::OnPaint()  
  28. {  
  29.     CPaintDC dc(CWnd::FromHandle(m_hwnd));  
  30.   
  31.     //绘制背景图片  
  32.     dc.SetStretchBltMode(COLORONCOLOR);  
  33.     m_img.StretchBlt(dc, 0, 0, m_rect.Width(), m_rect.Height(),  
  34.         0, 0, m_img.GetWidth(), m_img.GetHeight(), SRCCOPY);  
  35.   
  36.     //绘制文字  
  37.     CFont font;  
  38.     font.CreatePointFont(150, "微软雅黑");  
  39.     dc.SelectObject(font);  
  40.   
  41.     CString str1 = "给自己的梦想一次破茧而出的机会,创造属于自己的幸福";  
  42.     CString str2 = "BlueCoder(黎小华)";  
  43.   
  44.     dc.SetBkMode(TRANSPARENT);  
  45.     dc.SetTextColor(RGB(163, 21, 21));  
  46.     dc.TextOut(130, 220, str1);  
  47.   
  48.     dc.SetTextColor(RGB(21, 155, 230));  
  49.     dc.TextOut(500, 300, str2);  
  50. }  
  51.   
  52. //按键消息:按下Esc键退出程序  
  53. void CCWindow::OnKeyDown(WPARAM wParam)  
  54. {  
  55.     if(wParam == VK_ESCAPE)  
  56.     {  
  57.         DestroyWindow(m_hwnd);  
  58.     }  
  59. }  
  60.   
  61. //窗口销毁消息:释放内存  
  62. void CCWindow::OnDestroy()  
  63. {  
  64.     m_img.Destroy();  
  65.   
  66.     mciSendString("close bgm", 0, 0, 0);  
  67.   
  68.     PostQuitMessage(0);  
  69. }  

(4)在Main.cpp中的窗口过程中调用响应的消息成员函数:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //窗口过程:处理消息  
  2. LRESULT CALLBACK WndProc(  
  3.                 HWND hwnd,  
  4.                 UINT msg,  
  5.                 WPARAM wParam,  
  6.                 LPARAM lParam)  
  7. {  
  8.     switch(msg)  
  9.     {  
  10.     case WM_CREATE:  
  11.         wnd.OnCreate(); //窗口建立消息:进行一些初始化操作  
  12.         return 0;  
  13.   
  14.     case WM_SIZE:  
  15.         wnd.OnSize();   //窗口size消息:获取窗口大小  
  16.         return 0;  
  17.   
  18.     case WM_KEYDOWN:    //按键消息  
  19.         wnd.OnKeyDown(wParam);  
  20.         return 0;  
  21.   
  22.     case WM_PAINT:      //窗口Paint消息:绘制窗口客户区  
  23.         wnd.OnPaint();  
  24.         return 0;  
  25.   
  26.     case WM_DESTROY:    //窗口销毁消息:释放内存  
  27.         wnd.OnDestroy();  
  28.         return 0;  
  29.     }  
  30.   
  31.     return DefWindowProc(hwnd, msg, wParam, lParam);  
  32. }  

注:此时,我们使用的是一般的消息循环


看着这些代码,你是否觉得和我原先的MFC代码以及风格很相似呢?呵呵:)


ok,来看看运行效果吧:




有心的朋友可能已经发现在我封装的CCWindow类中有两个消息循环成员函数:

1>RunMsgLoop()  ——  一般的消息循环:GetMessage

2>RunMsgLoop(void (*Display)(), int) ——  更有效的消息循环:PeekMessage

注:1、如果不熟悉这GetMessage、PeekMessage两个API函数的区别,请查阅MSDN

       2、给不明白的朋友一个提示: void (*Display)()  这是函数指针的声明的格式


这个一般的消息循环的实例已经在刚刚这个实例中用到,下面就用第二个:更有效的消息循环

这个更有效的消息循环有两个好处:(1)效率好  (2)我们可以省略计时器的设定


1、所谓效率好,就是程序能利用其空闲的时候——没有消息处理的情况下,来执行一些操作(例如:贴图==)

2、由于利用的是程序没有消息路由的空闲时间来处理一些操作,即只要没有消息,我们就能执行自己想要的代码,那么我们便可以通过Sleep这个函数来模拟计时器的效果——让这些操作的执行能有一个有序的时间间隔


那么这个有效的消息循环,如何使用呢?

你需要自行写一个函数void Display(),然后让这个消息循环执行这个显示函数就ok了


还是来看看代码吧:(我实现的功能,是借用P先生《Windows程序设计》中的一个例子,很simple:不断绘制一些随机位置、大小、颜色的矩形)

注:我并没有使用计时器,Sleep()函数模拟了计时器的作用效果

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /*-------------------------------------- 
  2.         消息循环(更好的消息循环) 
  3.             PeekMessage() 
  4.         @Display:执行函数 
  5.         @interval:函数执行的时间间隔 
  6. --------------------------------------*/  
  7. int CCWindow::RunMsgLoop(void (*Display)(), int interval)  
  8. {  
  9.     MSG msg;  
  10.     ZeroMemory(&msg, sizeof(MSG));  
  11.   
  12.     //获取运行到此处时的时间  
  13.     int last = GetTickCount();  
  14.   
  15.     //如果不是退出消息  
  16.     while(msg.message != WM_QUIT)  
  17.     {  
  18.         //如果有消息  
  19.         if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))  
  20.         {  
  21.             TranslateMessage(&msg);  
  22.             DispatchMessage(&msg);  
  23.         }  
  24.         //否则, 空闲的时候执行响应函数(大多数是绘制函数)  
  25.         else  
  26.         {  
  27.             //如果窗口客户区大小不为0就是显示(有可能窗口是在最小化)  
  28.             if(m_rect.Width() &&   
  29.                m_rect.Height())  
  30.             {  
  31.                 Display();//执行函数  
  32.                 Sleep(interval);//当前线程睡眠一会儿  
  33.             }  
  34.         }  
  35.     }  
  36.   
  37.     return msg.wParam;  
  38. }  

在WinMain调用这个更有效的消息循环:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /*进入消息循环 
  2.     1. 使用更好的消息循环wnd.RunMsgLoop(Display, 100) 
  3.     2. 使用一般的消息循环wnd.RunMsgLoop() 
  4. */  
  5. return wnd.RunMsgLoop(Display, 100);  


实现效果:


可见一些随机的矩形在不断地绘制,还是很漂亮吧:》



四、你想要的开源源代码

等待多时的开源框架BCF的源代码总算能开始下载了:

开源框架BCF源码以及相关实例



希望大家帮我多测试一下BCF哈,有什么bug请及时告诉我,我将尽快纠正

另外,还欢迎大家和我交流,留下你想说的话(不喜勿碰^_^),因为你们的支持是我继续努力开源的动力:)


……

……


一不留神,时间飞逝,现在都凌晨1点多了——但愿我夜以继日的无私奉献,能帮助到和我有共同兴趣爱好的有志青年,让我们一起踏上美好的游戏编程之旅,扬帆起航,实现我们共同的梦想……


电脑屏幕前的你,晚安,BlueCoder也晚安,BCF,加油,哈哈……




0 0
原创粉丝点击