使用VS开发桌面程序--贪吃蛇小程序

 

为了更好的理解Windows程序的运行机制,笔者当年尝试写了第一个程序--贪吃蛇,逐渐从事公司部分Windows开发工作(当然主业还是嵌入式开发啦)

前期准备:

  • 开发环境:Visual Studio 2015
  • 编程语言:C/C++

那我们一起开始VS开发吧。

  • 创建工程

【文件】->【新建】->【项目】选择Visual C++ 空项目(空项目让我们从头搭建,能有更深刻的理解)完成后,这时候在资源管理器一栏看到的也是什么也没有的。

  • 新建资源文件

这里我们需要添加Winmain文件作为程序开始的启动文件。新建cpp文件或h文件,鼠标右键源文件,【添加】->【新建项】

另外我们添加一个ico图标作为程序启动的图标,毕竟系统自带的实在太丑了。右键资源文件,【添加】->【资源】,如图然后导入想要的图标

这时候工程在资源文件将自动生成*.rc格式的文件,头文件将生成resource.h文件。这些文件适用于我们管理图片和相关控件使用的。这时候双击rc文件,我们给图标起个名称(不要默认名称,保证程序可读性)。

 

 

  • 创建Winmain函数和窗口消息处理函数

/**************************************************START OF FILE*****************************************************/


//@ Author : L17    Weichat:ForeverLiuYC

//@  Date : 2020 - 09 - 12



/*------------------------------------------------------------------------------------------------------------------
Includes
*/
#include "WinMain.h"
#include "resource.h"

//使用系统控件风格
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma comment( lib,"winmm.lib" )


/*------------------------------------------------------------------------------------------------------------------
Macros
*/
/*定义窗口结构体组合 WS_THICKFRAME:调整窗体大小 ,WS_MAXIMIZEBOX: 禁止最大化框*/
#define WS_MYSTYLE	((WS_OVERLAPPEDWINDOW^WS_MAXIMIZEBOX) | WS_CLIPCHILDREN)


/*------------------------------------------------------------------------------------------------------------------
Variables
*/
MSG Msg;              /*主窗口消息结构体*/
HWND MainFrameHwnd;   /*主窗口句柄*/

/*------------------------------------------------------------------------------------------------------------------
Functions
*/
LRESULT CALLBACK WinCallbackDeal(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);




/*
*****************************************************************************************
@ Brief  : WinMain 函数:系统启动的第一个程序

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	LPCWSTR szAppName = TEXT("SDK_TEST");
	WNDCLASS wndclass;
	ZeroMemory(&wndclass, sizeof(wndclass));                    //初始化结构
	wndclass.style = CS_HREDRAW | CS_VREDRAW;					//样式 | CS_DBLCLKS
	wndclass.lpfnWndProc = WinCallbackDeal;                             //指定窗口消息处理函数
	wndclass.cbClsExtra = 0;                                    //窗口类预留字节空间,这里为0字节
	wndclass.cbWndExtra = DLGWINDOWEXTRA;                       //窗口结构预留字节
	wndclass.hInstance = hInstance;                             //窗口类模块句柄
	wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_MAINFRAME));           //定义图标
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);				//鼠标光标
																//wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);		//设置背景色
	wndclass.hbrBackground = NULL;								//设置背景色,使用双缓冲时设置为 NULL
	wndclass.lpszMenuName = NULL;                               //窗口应用程序菜单 NULL
	wndclass.lpszClassName = szAppName;                         //窗口类别名称,对话框窗口和模板类名称相同

	/**注册窗口类**/
	if (!RegisterClass(&wndclass))//注册窗体类
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
		return 0;
	};


	/**创建窗口**/
	MainFrameHwnd = CreateWindow(szAppName,		// window class name
		TEXT("Hungry Snake"),			// window caption
		WS_MYSTYLE,						// window style
		CW_USEDEFAULT,					// initial x position
		CW_USEDEFAULT,					// initial y position
		CW_USEDEFAULT,					// initial x size
		CW_USEDEFAULT,					// initial y size
		NULL,							// parent window handle
		NULL,							// window menu handle
		hInstance,						// program instance handle
		NULL);


	/**显示窗口**/
	ShowWindow(MainFrameHwnd, SW_SHOW);	//显示窗口SW_HIDE  
	UpdateWindow(MainFrameHwnd);		//更新窗体


	/*消息循环处理*/
	while (GetMessage(&Msg, NULL, 0, 0) > 0)
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}


	return 0;
}


/*
****************************************************************************************
@ Brief  : 消息处理函数,Windows事件处理都是基于消息的,所有事件都将在此回调函数中产生

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
LRESULT CALLBACK WinCallbackDeal(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_PAINT:

		return 0;

	case WM_KEYUP:

		return 0;

	case WM_KEYDOWN:

		return 0;

	case WM_DESTROY:

		return 0;

	case WM_MOUSEMOVE:

		return 0;

	case WM_SIZE:

		return 0;

	case WM_CREATE:

		return 0;

	case WM_TIMER:

		return 0;

	case WM_COMMAND:

		return 0;

		/*获得焦点*/
	case WM_SETFOCUS:

		return 0;

		/*正在失去焦点*/
	case WM_KILLFOCUS:

		return 0;

		/*鼠标左键按下消息*/
	case WM_LBUTTONDOWN:


		return 0;

		/*鼠标左键弹起消息*/
	case WM_LBUTTONUP:

		return 0;

	case WM_RBUTTONDOWN:

		return 0;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}



/****************************************************END OF FILE*****************************************************/

Winmain 函数我们可以直接理解成main函数就行,这里对窗口类做了完整设计,并显示了窗口 SW_SHOW

WinCallbackDeal 函数很重要的函数,这里也就是Windows常说的术语:消息队列。每个Windows应用程序开始执行后,系统都会为程序创建一个消息队列。这个消息队列用于存放该程序创建的窗口的消息。如鼠标按下抬起消息,键盘相关消息等等。大家可以自己试试,会有更多收获。

完成这两个基础函数我们先运行一下代码吧!!!桌边将显示该软件运行的窗口,于是我们可以在窗口中添加自己想要的控件图片或者动画了。

 

接下来我们开始,贪吃蛇的设计吧。这里我们直接创建几个cpp文件,绘制相关文件和游戏对象类(贪吃蛇)文件(源码将会在文章最后内容贴上下载路径)。

为了动画的美观性,这里我在网上找了一些图标用于贴图

程序在启动后的 WM_CREATE 消息中,将把这些图片加载到缓存中,用于接下来游戏背景的绘制

//消息队列中 WM_CREATE 事件处理

	case WM_CREATE:
		{	
			/*加载系统图片信息*/
			Deer_LoadImage();

			//创建显示双缓冲
			CreateHdcBuffer();

			//设置定时器更新窗口
			SetTimer(hwnd, 2, 1000, NULL);
		}
		return 0;

/*
*****************************************************************************************
@ Brief  : 透明相片转换

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
void ChangeImg(CImage* img)
{
	for (int i = 0; i<(*img).GetWidth(); ++i)
	{
		for (int j = 0; j<(*img).GetHeight(); ++j)
		{
			unsigned char* pucColor = (unsigned char*)(*img).GetPixelAddress(i, j);
			pucColor[0] = pucColor[0] * pucColor[3] / 255;
			pucColor[1] = pucColor[1] * pucColor[3] / 255;
			pucColor[2] = pucColor[2] * pucColor[3] / 255;
		}
	}
}

/*
*****************************************************************************************
@ Brief  : 加载项目所用到的图片资源

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
void Deer_LoadImage(void)
{
	img[IMG_Deer].Load(TEXT(".\\Png\\pander.png"));

	img[IMG_Bread].Load(TEXT(".\\Png\\bread.png"));
	ChangeImg(img + 1);

	img[IMG_Body].Load(TEXT(".\\Png\\Body1.png"));
	ChangeImg(img + 2);

	img[IMG_Bodyend].Load(TEXT(".\\Png\\end.png"));
	ChangeImg(img + 3);
}

接下来实现游戏人物: 贪吃蛇的行为和特点

/ *.h
/*---------------------------------------------------------------------------------------
Macros
*/
#define IMG_Deer      1
#define IMG_Bread     2
#define IMG_Body      3
#define IMG_Bodyend   4

#define RUN_UP       (1U)
#define RUN_DOWN     (2U)
#define RUN_LEFT     (3U)
#define RUN_RIGHT    (4U)

class CHungraySnake
{
public:
	CHungraySnake();
	~CHungraySnake();

public:
	void Deer_WinMainGUI(void);

private:
	void Run_station(void);   //运动轨迹计算
	void Food_Station(void);  //食物路劲显示

public:
	UINT8 runningDir;  //运动方向定义

	int headx, heady;  //表示蛇头出现的位置
	int bodySize;

	int foodx, foody;  //食物所在位置
};



/ *.cpp

CImage img[10];
//身体每个像素的所在位置
int sxarr[MAX_BODY] = { 4, 3, 2, 1, 0 };
int syarr[MAX_BODY] = { 0, 0, 0, 0, 0 };


//构造函数,初始化相关变量
CHungraySnake::CHungraySnake()
{
	runningDir = RUN_RIGHT;

	//定义蛇头位置
	headx = 4, heady = 0;

	//身体长度
	bodySize = 3;

	//食物所在位置
	foodx = 8, foody = 2;  
}

//析构函数,该类结束
CHungraySnake::~CHungraySnake()
{

}

/*
*****************************************************************************************
@ Brief  : 身体搭建

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
void CHungraySnake::Deer_WinMainGUI(void)
{
	int i;

	if (iSPauseGame == false)
	{
		Run_station();    //计算运动轨迹
	}

	Food_Station();   //更新食物路径

	img[IMG_Deer].Draw(hdcBuffer, sxarr[0] * 20, syarr[0] * 20);    //定义头部

	for (i = 1; i<bodySize; i++)
	{
		//img[IMG_Body].Draw(hdcBuffer, sxarr[s] * 20+1, syarr[s] * 20+1);
		GUI_DrawFramelessRectangle(hdcBuffer, sxarr[i] * 20 + 2, syarr[i] * 20 + 2, sxarr[i] * 20 + 18, syarr[i] * 20 + 18, RGB(0XFF, 0XA5, 0X00));
	}
	//img[IMG_Bodyend].Draw(hdcBuffer, sxarr[SnakeSize-1] * 20 + 1, syarr[SnakeSize-1] * 20 + 1);
}

/*
*****************************************************************************************
@ Brief  : 食物位置显示

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
void CHungraySnake::Food_Station(void)
{
	img[IMG_Bread].Draw(hdcBuffer, foodx * 20, foody * 20);      //食物
}

/*
*****************************************************************************************
@ Brief  : 运动解析,更新位置

@ Param  : None

@ Return : None

@ Author : L17

@  Date  : 2020 - 09 - 12
*****************************************************************************************
*/
void CHungraySnake::Run_station(void)
{
	int snakelen;

	for (snakelen = bodySize; snakelen >= 0; snakelen--)
	{
		syarr[snakelen + 1] = syarr[snakelen];
		sxarr[snakelen + 1] = sxarr[snakelen];
	}

	if (runningDir == RUN_UP)
	{   
		heady--;
		syarr[0] = heady;
	}
	else if (runningDir == RUN_DOWN)  
	{
		heady++;
		syarr[0] = heady;

	}
	else if (runningDir == RUN_LEFT)  
	{
		headx--;
		sxarr[0] = headx;
	}
	else if (runningDir == RUN_RIGHT)  
	{
		headx++;
		sxarr[0] = headx;
	}

	//--------------当吃到食物---------------
	if (sxarr[0] == foodx && syarr[0] == foody)  //蛇头到达食物位置
	{
		//随机食物的位置		
		srand(time(NULL));

		while (TRUE)
		{
			foodx = rand() % 100 + 1;
			foody = rand() % 100 + 1;

			/*防止食物位置超出窗口*/
			if ((foodx < 50) && (foody < 30))
			{
				break;
			}
		}

		bodySize++;

		if (bodySize == MAX_BODY) bodySize = MAX_BODY - 1;  //防止数组越界
	}
}

接下来我们将在消息队列中按键上下左右键来实现蛇的方向控制,空格键实现暂停:

		case WM_KEYDOWN:
			switch (wParam)
			{
			case VK_ESCAPE:
				DestroyWindow(hwnd);
				break;
			case VK_UP:
				if (myHungrySnake.runningDir != RUN_DOWN)
				{
					myHungrySnake.runningDir = RUN_UP;
				}
				break;
			case VK_DOWN:
				if (myHungrySnake.runningDir != RUN_UP)
				{
					myHungrySnake.runningDir = RUN_DOWN;
				}
				break;
			case VK_LEFT:
				if (myHungrySnake.runningDir != RUN_RIGHT)
				{
					myHungrySnake.runningDir = RUN_LEFT;
				}
				break;
			case VK_RIGHT:
				if (myHungrySnake.runningDir != RUN_LEFT)
				{
					myHungrySnake.runningDir = RUN_RIGHT;
				}
				break;

			case VK_SPACE:   //按下space键  暂停游戏
				iSPauseGame = (iSPauseGame == true) ? false : true;
				break;
			}
			return 0;

我们将图形显示放在消息队列:WM_PAINT 中绘制,其中我们将会用到著名的 BitBlt 绘制函数。图形的定时绘制通过定时器(消息队列:WM_TIMER)固定触发:InvalidateRect 函数,该函数将会触发一次 WM_PAINT 事件。

		case WM_PAINT:
		{
			if (hwnd == MainFrameHwnd)
			{
				hdc = BeginPaint(hwnd, &ps);

				myHungrySnake.Deer_WinMainGUI();   //显示位置

				BitBlt(hdc, 0, 0, WIN_XCLIENT, WIN_YCLIENT, hdcBuffer, 0, 0, SRCCOPY);
				EndPaint(hwnd, &ps);
			}
		}
		return 0;

		case WM_TIMER:
		{
			if (LOWORD(wParam) == 1)
			{
				InvalidateRect(hwnd, NULL, FALSE);  //触发一次WinPan
				GUI_DrawFramelessRectangle(hdcBuffer, 0, 0, WIN_XCLIENT, WIN_YCLIENT, RGB(0XFF, 0XFF, 0XFF));
			}
			else if (LOWORD(wParam) == 2)
			{
				KillTimer(hwnd, 2);
				SetTimer(hwnd, 1, 100, NULL);

				GUI_DrawFramelessRectangle(hdcBuffer, 0, 0, WIN_XCLIENT, WIN_YCLIENT, RGB(0XFF, 0XFF, 0XFF));

				ShowWindow(MainFrameHwnd, SW_SHOW);	//显示窗口		
			}
		}
		return 0;

 

最后我们必须记住的一点,如果程序结束,我们应该释放相关内存,在消息队列:WM_DESTROY 中实现相关内存释放

		case WM_DESTROY:
			DeleteHdcBuffer();
			PostQuitMessage(0);
			return 0;

好了我们一起来玩玩贪吃蛇吧!!!

程序下载地址: https://download.csdn.net/download/weixin_38426553/12839201

  • 30
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是用 C++ 和 cocos2d-x 开发贪吃蛇的代码示例: 首先,我们需要创建一个 Snake 类,用于管理贪吃蛇的运动和状态: ```c++ class Snake : public cocos2d::Node { public: virtual bool init() override; CREATE_FUNC(Snake); void move(); // 移动 void turn(Direction direction); // 转向 bool isDead() const; // 是否死亡 private: std::vector<cocos2d::Sprite*> m_body; // 身体 Direction m_direction; // 运动方向 bool m_isDead; // 是否死亡 int m_gridWidth; // 网格宽度 }; ``` 在 Snake 类中,我们定义了以下成员变量: - m_body:存储贪吃蛇身体的精灵数组; - m_direction:表示当前的运动方向; - m_isDead:表示是否已经死亡; - m_gridWidth:网格宽度,即每个格子的大小。 接下来,我们需要实现 Snake 类的成员函数: ```c++ bool Snake::init() { if (!Node::init()) { return false; } // 初始化 m_direction = Direction::RIGHT; m_isDead = false; m_gridWidth = 10; // 添加蛇头 auto head = cocos2d::Sprite::create("snake_head.png"); head->setPosition(cocos2d::Vec2(0, 0)); addChild(head); m_body.push_back(head); // 添加蛇身 for (int i = 1; i < 4; i++) { auto body = cocos2d::Sprite::create("snake_body.png"); body->setPosition(cocos2d::Vec2(-i * m_gridWidth, 0)); addChild(body); m_body.push_back(body); } return true; } void Snake::move() { if (m_isDead) { return; } // 移动身体 for (int i = m_body.size() - 1; i > 0; i--) { auto prevPos = m_body[i - 1]->getPosition(); m_body[i]->setPosition(prevPos); } // 移动头部 auto headPos = m_body[0]->getPosition(); switch (m_direction) { case Direction::UP: headPos.y += m_gridWidth; break; case Direction::DOWN: headPos.y -= m_gridWidth; break; case Direction::LEFT: headPos.x -= m_gridWidth; break; case Direction::RIGHT: headPos.x += m_gridWidth; break; } m_body[0]->setPosition(headPos); } void Snake::turn(Direction direction) { if (m_isDead) { return; } if ((int)direction + (int)m_direction == 0) { return; } m_direction = direction; } bool Snake::isDead() const { return m_isDead; } ``` 在 init 函数中,我们初始化了贪吃蛇的状态,并添加了蛇头和蛇身。 在 move 函数中,我们首先移动了蛇身,然后根据当前的运动方向移动了蛇头。 在 turn 函数中,我们检查了新的方向是否与当前方向相反,如果是,则不做任何操作。 在 isDead 函数中,我们简单地返回 m_isDead 的值。 接下来,我们需要创建一个 GameLayer 类,用于管理游戏的逻辑和界面: ```c++ class GameLayer : public cocos2d::Layer { public: virtual bool init() override; CREATE_FUNC(GameLayer); void update(float delta) override; // 帧更新函数 private: void createFood(); // 创建食物 bool checkCollision(); // 检查碰撞 Snake* m_snake; // 贪吃蛇 cocos2d::Sprite* m_food; // 食物 int m_gridWidth; // 网格宽度 float m_interval; // 更新间隔 }; ``` 在 GameLayer 类中,我们定义了以下成员变量: - m_snake:贪吃蛇对象; - m_food:食物对象; - m_gridWidth:网格宽度,即每个格子的大小; - m_interval:更新间隔,即每隔多少秒更新一次贪吃蛇的状态。 接下来,我们需要实现 GameLayer 类的成员函数: ```c++ bool GameLayer::init() { if (!Layer::init()) { return false; } // 初始化 m_gridWidth = 10; m_interval = 0.1f; // 添加贪吃蛇 m_snake = Snake::create(); m_snake->setPosition(cocos2d::Vec2(0, 0)); addChild(m_snake); // 创建食物 createFood(); // 注册键盘事件 auto listener = cocos2d::EventListenerKeyboard::create(); listener->onKeyPressed = [=](cocos2d::EventKeyboard::KeyCode keyCode, cocos2d::Event* event) { switch (keyCode) { case cocos2d::EventKeyboard::KeyCode::KEY_UP_ARROW: m_snake->turn(Direction::UP); break; case cocos2d::EventKeyboard::KeyCode::KEY_DOWN_ARROW: m_snake->turn(Direction::DOWN); break; case cocos2d::EventKeyboard::KeyCode::KEY_LEFT_ARROW: m_snake->turn(Direction::LEFT); break; case cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_ARROW: m_snake->turn(Direction::RIGHT); break; default: break; } }; _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); // 开始更新 scheduleUpdate(); return true; } void GameLayer::update(float delta) { m_interval -= delta; if (m_interval <= 0) { m_snake->move(); // 检查碰撞 if (checkCollision()) { m_snake->stopAllActions(); m_snake->setScale(1.0f); m_snake->runAction(cocos2d::Blink::create(2, 5)); m_snake->setIsDead(true); return; } // 检查是否吃到食物 auto headPos = m_snake->getPosition(); auto foodPos = m_food->getPosition(); if (headPos.distance(foodPos) < m_gridWidth) { m_food->removeFromParent(); createFood(); auto body = cocos2d::Sprite::create("snake_body.png"); body->setPosition(m_snake->getChildByName("tail")->getPosition()); m_snake->addChild(body); } m_interval = 0.1f; } } void GameLayer::createFood() { // 随机创建食物 auto visibleSize = cocos2d::Director::getInstance()->getVisibleSize(); auto origin = cocos2d::Director::getInstance()->getVisibleOrigin(); int x = (int)origin.x + m_gridWidth + rand() % ((int)visibleSize.width - 2 * m_gridWidth); int y = (int)origin.y + m_gridWidth + rand() % ((int)visibleSize.height - 2 * m_gridWidth); m_food = cocos2d::Sprite::create("snake_food.png"); m_food->setPosition(cocos2d::Vec2(x, y)); addChild(m_food); } bool GameLayer::checkCollision() { // 检查是否碰到边界 auto visibleSize = cocos2d::Director::getInstance()->getVisibleSize(); auto origin = cocos2d::Director::getInstance()->getVisibleOrigin(); auto headPos = m_snake->getPosition(); if (headPos.x < origin.x || headPos.y < origin.y || headPos.x > origin.x + visibleSize.width || headPos.y > origin.y + visibleSize.height) { return true; } // 检查是否碰到自己 for (int i = 1; i < m_snake->getChildrenCount() - 1; i++) { auto bodyPos = m_snake->getChildByTag(i)->getPosition(); if (headPos.distance(bodyPos) < m_gridWidth) { return true; } } return false; } ``` 在 init 函数中,我们初始化了游戏状态,并添加了贪吃蛇和食物。此外,我们还注册了键盘事件,并开始更新。 在 update 函数中,我们首先检查更新间隔是否已经到达,然后移动贪吃蛇,并检查是否碰到边界或自己。如果碰到了,则设置贪吃蛇为死亡状态,并返回。如果贪吃蛇吃到了食物,则创建一个新的身体部分,并重新生成食物。最后,重置更新间隔。 在 createFood 函数中,我们随机生成一个食物,并添加到场景中。 在 checkCollision 函数中,我们检查贪吃蛇是否碰到了边界或自己。如果碰到了,则返回 true,否则返回 false。 最后,在场景中添加 GameLayer 类的实例,即可运行贪吃蛇游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值