SimpleCG小游戏开发系列(1)--扫雷

一、前言

        前面我们学习了SimpleCG的游戏开发框架,从本篇开始,我们用一系列小游戏的开发来加深对框架的了解.我们先以windows的经典游戏--扫雷,作为首个例子。游戏预览如下

二、框架搭建

        因为游戏程序的大体框架差不多,所以我们可以搭建一个通用的主程序。如下所示:

// GameMine.cpp : 定义控制台应用程序的入口点。
//

#include "../import/include/SimpleCG.h"


#define C_IMAGE_WIDTH			640
#define C_IMAGE_HEIGHT			480

#ifdef _DEBUG
#pragma comment(lib,"../import/lib/SimpleCG_MDd.lib")
#else
#ifdef _WIN64
#pragma comment(lib,"../import/lib/x64/MTRelease/SimpleCG_MT64.lib")
#else
#pragma comment(lib,"../import/lib/SimpleCG_MT.lib")
#endif
#endif

LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam);

//绘制游戏
void RenderGame()
{
	
}
//更新游戏状态
void UpdateGame( UINT nElapse )
{
	static int s_nLastTick = 0;
	if( ( nElapse - s_nLastTick )<120 )
		return;

	s_nLastTick = nElapse;

}
//按键消息响应函数
LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	switch( wParam )
	{
	case VK_SPACE:
		break;
	case VK_UP:
		break;
	case VK_DOWN:
		break;
	case VK_LEFT:
		break;
	case VK_RIGHT:
		break;
	}
	return 0;
}
LRESULT OnLButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	return false;
}
LRESULT OnLButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	return false;
}
LRESULT OnRButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	return false;
}
LRESULT OnRButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	return false;
}
LRESULT OnMouseMove( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	return false;
}
//初始化游戏
void InitGame(HWND hWnd)
{
	int i=0;
	int j=0;
	srand(GetTickCount());
	
	GetKeyboardInput()->onKeyDown		= OnKeyDown;
	GetMouseInput()->onMouseDownLeft	= OnLButtonDown;
	GetMouseInput()->onMouseDownRight	= OnRButtonDown;
	GetMouseInput()->onMouseUpLeft		= OnLButtonUp;
	GetMouseInput()->onMouseUpRight		= OnRButtonUp;
	GetMouseInput()->onMouseMove		= OnMouseMove;
}

int _tmain(int argc, _TCHAR* argv[])
{
	SCG_GameLoopInfo info;
	info.nFPS				= 60;
	info.nHeight			= C_IMAGE_BLOCK*g_nMapHeight+C_IMAGE_BLOCK;//C_IMAGE_HEIGHT;
	info.nWidth				= C_IMAGE_BLOCK*g_nMapWidth;//C_IMAGE_WIDTH;
	info.pFunDrawProcess	= RenderGame;
	info.pFunFrameUpdate	= UpdateGame;
	info.pFunInitGame		= InitGame;
	info.pFunInput			= NULL;

	if( !StartGameLoopEx( &info ))
		return 1;
	return 0;
}

        这个框架基本上就是前面文章所介绍的游戏开发框架。然后有了这个主框架,我们根据我们要开发的扫雷游戏,我们分别建立游戏逻辑的实现文件mine.cpp,以及头文件mine.h文件。在这两个文件中我们实现扫雷的相关逻辑,然后再把相关代码接入主程序即可。

三、游戏逻辑实现mine.cpp及mine.h文件

mine.h头文件


#ifndef _MINE_H
#define _MINE_H

#include "../import/include/SimpleCG.h"
#define C_IMAGE_BLOCK			20
enum ENUM_MAPTYPE
{
	  enumMAPTYPE_BACK
	, enumMAPTYPE_OPEN
	, enumMAPTYPE_BACKLIGHT
	, enumMAPTYPE_BACKDOWN
};
enum ENUM_MAPFLAG
{
	  enumMAPFLAG_NULL
	, enumMAPFLAG_FLAG
	, enumMAPFLAG_QUESTION
};
enum ENUM_MAPTEXT
{
	  enumMAPTEXT_NULL
	, enumMAPTEXT_NUMBER1
	, enumMAPTEXT_NUMBER2
	, enumMAPTEXT_NUMBER3
	, enumMAPTEXT_NUMBER4
	, enumMAPTEXT_NUMBER5
	, enumMAPTEXT_NUMBER6
	, enumMAPTEXT_NUMBER7
	, enumMAPTEXT_NUMBER8
	, enumMAPTEXT_NUMBER9
	, enumMAPTEXT_BOMB
};

extern int g_nMapWidth;
extern int g_nMapHeight;
//绘制内容
void Init( int nBomb, int nX, int nY  );
//计算炸弹数量
int CalBoombCount( int nX, int nY );
//绘制内容
void DrawMap( );
//绘制界面
void DrawUI(  );
//绘制单个方块,绝对坐标
void DrawBlockPos( int nX, int nY, int nType );
//绘制单个方块
void DrawBlock( int nX, int nY, int nType );
//是否在地图上
bool IsOnMap( int nX, int nY );
//坐标转化
int ScreenToMapX( int nX );
int ScreenToMapY( int nY );
//鼠标消息
LRESULT MapOnLButtonDown( WPARAM wParam, int nX, int nY );
LRESULT MapOnLButtonUp( WPARAM wParam, int nX, int nY );
LRESULT MapOnRButtonDown( WPARAM wParam, int nX, int nY );
LRESULT MapOnRButtonUp( WPARAM wParam, int nX, int nY );
LRESULT MapOnMouseMove( WPARAM wParam, int nX, int nY );
//打开操作
void DoOpen( int nX, int nY );
//设置标记
void SetFlag( int nX, int nY, int nFlag );
//设置标记
void AddFlag( int nX, int nY );
#endif

mine.cpp实现文件

/*===========================================================*\
|简介:	扫雷
|功能:	扫雷
|作者:	Bill
|主页:	http://simplecg.qqpet.com
|博客:	https://blog.csdn.net/b2b160
|贴吧:	https://tieba.baidu.com/f?kw=simplecg
|日期:	2023-09-15
\*===========================================================*/
#include "Mine.h"

int g_nMapWidth = 9;
int g_nMapHeight = 9;
int g_nMap[ 10 * 10] = {0};
int g_nMapText[ 10 * 10] = {0};
int g_nXMapPos = 0;
int g_nYMapPos = C_IMAGE_BLOCK;
int g_nBomb = 10;
int g_nLeftBomb = g_nBomb;

int g_nCurX = -1;
int g_nCurY = -1;

int g_nCurDownX = -1;
int g_nCurDownY = -1;
int g_nOpended = 0;
UINT g_nStartTime = 0;
UINT g_nGameRunning = 0;
//绘制内容
void Init( int nBomb, int nX, int nY )
{
	int i=0;
	int j=0;
	memset(g_nMap,0, sizeof(g_nMapText));
	memset(g_nMapText,0, sizeof(g_nMapText));
	int nTotal = nX*nY;
	if( nBomb>nTotal )
		nBomb = nTotal;
	g_nBomb = nBomb;
	g_nLeftBomb = g_nBomb;

	g_nMapWidth = nX;
	g_nMapHeight = nY;
	g_nOpended = 0;
	for(i=0;i<nBomb;++i)
	{
		int nBombIndex = rand() % (nX*nY);
		if( g_nMapText[nBombIndex]!=enumMAPTEXT_BOMB)
		{
			g_nMapText[nBombIndex]=enumMAPTEXT_BOMB;
		}
		else
		{
			int nStop = nBombIndex++;
			while( nBombIndex != nStop  )
			{
				if( g_nMapText[nBombIndex]!=enumMAPTEXT_BOMB )
				{
					g_nMapText[nBombIndex]=enumMAPTEXT_BOMB;
					break;
				}
				++nBombIndex;
				if(nBombIndex>=nTotal )
					nBombIndex = 0;
			}
		}
			
	}
	for(j=0;j<g_nMapHeight;++j)
	{
		for(i=0;i<g_nMapWidth;++i)
		{
			g_nMap[j*g_nMapWidth+i]=enumMAPTYPE_BACK;
			if(g_nMapText[j*g_nMapWidth+i] != enumMAPTEXT_BOMB )
				g_nMapText[j*g_nMapWidth+i]=CalBoombCount(i,j);
		}
	}
	g_nStartTime = GetTickCount();
	g_nGameRunning = TRUE;
}
//计算炸弹数量
int IsBoomb( int nX, int nY )
{
	if( nX<0 || nX>=g_nMapWidth )
		return 0;
	if( nY<0 || nY>=g_nMapHeight )
		return 0;
	if( g_nMapText[nY*g_nMapWidth+nX]==enumMAPTEXT_BOMB )
		return 1;
	return 0;
}
//计算炸弹数量
int CalBoombCount( int nX, int nY )
{
	int nCount = 0;
	if( IsBoomb(nX-1,nY-1 ) )
		++nCount;
	if( IsBoomb(nX,nY-1 ) )
		++nCount;
	if( IsBoomb(nX+1,nY-1 ) )
		++nCount;
	if( IsBoomb(nX-1,nY ) )
		++nCount;
	if( IsBoomb(nX+1,nY ) )
		++nCount;
	if( IsBoomb(nX-1,nY+1 ) )
		++nCount;
	if( IsBoomb(nX,nY+1 ) )
		++nCount;
	if( IsBoomb(nX+1,nY+1 ) )
		++nCount;
	return nCount;
}
//绘制内容
void DrawOpen( int nX, int nY )
{
	static COLORREF s_nColor[3]={RGB(200,0x0,0x0),RGB(0x0,0x0,200),RGB(22,122,11)};
	int nXPos = g_nXMapPos + nX*C_IMAGE_BLOCK;
	int nYPos = g_nYMapPos + nY*C_IMAGE_BLOCK;

	settextcolor(s_nColor[g_nMapText[nY*g_nMapWidth+nX]%3]);
	if( g_nMapText[nY*g_nMapWidth+nX]==enumMAPTEXT_BOMB)
		printfRectEx( nXPos, nYPos, C_IMAGE_BLOCK, C_IMAGE_BLOCK, DT_CENTER, _T("X"));
	else if(g_nMapText[nY*g_nMapWidth+nX]>0)
		printfRectEx( nXPos, nYPos, C_IMAGE_BLOCK, C_IMAGE_BLOCK, DT_CENTER, _T("%d"), g_nMapText[nY*g_nMapWidth+nX]);
}
//绘制内容
void DrawMap(  )
{
	int i=0;
	int j=0;
	for(j=0;j<g_nMapHeight;++j)
	{
		for(i=0;i<g_nMapWidth;++i)
		{
			switch( g_nMap[j*g_nMapWidth+i] & 0xFFFF )
			{
			case enumMAPTYPE_OPEN:
				DrawBlock( i, j, g_nMap[j*g_nMapWidth+i] );
				DrawOpen( i, j );
				break;
			case enumMAPTYPE_BACK:
				if( g_nCurDownX==i && g_nCurDownY==j )
					DrawBlock( i, j, enumMAPTYPE_BACKDOWN);
				else if( g_nCurX==i && g_nCurY==j )
					DrawBlock( i, j, enumMAPTYPE_BACKLIGHT | (g_nMap[j*g_nMapWidth+i]&0xFFFF0000) );
				else
					DrawBlock( i, j, g_nMap[j*g_nMapWidth+i] );
				break;
			}
			
		}
	}
}
//绘制界面
void DrawUI(  )
{
	int nOld;
	if( FALSE == g_nGameRunning )
		return;
	UINT nTime = GetTickCount()-g_nStartTime;
	clearrectangle(g_nXMapPos, g_nYMapPos-C_IMAGE_BLOCK, g_nXMapPos + C_IMAGE_BLOCK*g_nMapWidth, g_nYMapPos);
	nOld = settextcolor(SCG_RGB(0,0,0xFF));
	printfRectEx( g_nXMapPos, g_nYMapPos-C_IMAGE_BLOCK, C_IMAGE_BLOCK*g_nMapWidth, C_IMAGE_BLOCK, DT_VCENTER, _T("时间:%d.%d"), nTime/1000,nTime%1000);
	printfRectEx( g_nXMapPos, g_nYMapPos-C_IMAGE_BLOCK, C_IMAGE_BLOCK*g_nMapWidth, C_IMAGE_BLOCK, DT_RIGHT|DT_VCENTER, _T("炸:%d"), g_nLeftBomb);
	settextcolor(nOld);
}
//绘制单个方块,绝对坐标
void DrawBlockPos( int nX, int nY, int nType )
{
	int nFlag = (nType>>16) & 0xFFFF;
	switch( nType & 0xFFFF )
	{
	case enumMAPTYPE_OPEN:
		setlinewidth(1);
		setfillcolor(RGB(194,194,194));
		setlinecolor(RGB(125,125,125));
		fillrectangle( nX, nY, nX + C_IMAGE_BLOCK, nY + C_IMAGE_BLOCK);
		break;
	case enumMAPTYPE_BACK:
		setlinewidth(3);
		setfillcolor(RGB(194,194,194));
		setlinecolor(RGB(125,125,125));
		fillrectangle( nX+1, nY+1, nX + C_IMAGE_BLOCK-1, nY + C_IMAGE_BLOCK-1);
		setlinecolor(RGB(231,231,231));
		line(nX+2, nY+1, nX-3 + C_IMAGE_BLOCK,nY+1);
		line(nX+1, nY+2, nX+1,nY-3 + C_IMAGE_BLOCK);
		break;
	case enumMAPTYPE_BACKDOWN:
		setlinewidth(1);
		setfillcolor(RGB(194,194,194));
		setlinecolor(RGB(125,125,125));
		fillrectangle( nX, nY, nX + C_IMAGE_BLOCK, nY + C_IMAGE_BLOCK);
		break;
	case enumMAPTYPE_BACKLIGHT:
		setlinewidth(3);
		setfillcolor(RGB(221,221,221));
		setlinecolor(RGB(125,125,125));
		fillrectangle( nX+1, nY+1, nX + C_IMAGE_BLOCK-1, nY + C_IMAGE_BLOCK-1);
		setlinecolor(RGB(231,231,231));
		line(nX+2, nY+1, nX-3 + C_IMAGE_BLOCK,nY+1);
		line(nX+1, nY+2, nX+1,nY-3 + C_IMAGE_BLOCK);
		break;
	}
	if( nFlag )
	{
		switch( nFlag )
		{
		case enumMAPFLAG_FLAG:
			{
				POINT pt[]={{nX+14,nY+2},{nX+6,nY+12},{nX+14,nY+12}};
				
				setlinewidth(1);
				setfillcolor(RGB(255,0,0));
				solidpolygon( pt, sizeof(pt)/sizeof(pt[0]) );
				setfillcolor(0);
				solidrectangle( nX+13, nY+4, nX + 16, nY + C_IMAGE_BLOCK-1);
			}
			break;
		case enumMAPFLAG_QUESTION:
			settextcolor(0);
			printfRectEx( nX, nY, C_IMAGE_BLOCK, C_IMAGE_BLOCK, DT_CENTER, _T("?"));
			break;
		}
	}
}
//绘制单个方块,地图坐标
void DrawBlock( int nX, int nY, int nType )
{
	int nXPos = g_nXMapPos + nX*C_IMAGE_BLOCK;
	int nYPos = g_nYMapPos + nY*C_IMAGE_BLOCK;
	DrawBlockPos(nXPos, nYPos, nType);
}
//是否在地图上
bool IsOnMap( int nX, int nY )
{
	if(nX>=g_nXMapPos && nX<g_nXMapPos + g_nMapWidth*C_IMAGE_BLOCK && nY>=g_nYMapPos && nY<g_nYMapPos + g_nMapHeight*C_IMAGE_BLOCK )
		return true;
	return false;
}
//坐标转化
int ScreenToMapX( int nX )
{
	int nRet = -1;
	if(nX>=g_nXMapPos && nX<g_nXMapPos + g_nMapWidth*C_IMAGE_BLOCK )
	{
		nRet =( nX-g_nXMapPos)/C_IMAGE_BLOCK;
	}
	return nRet;
}
int ScreenToMapY( int nY )
{
	int nRet = -1;
	if(nY>=g_nYMapPos && nY<g_nYMapPos + g_nMapHeight*C_IMAGE_BLOCK )
	{
		nRet =( nY-g_nYMapPos)/C_IMAGE_BLOCK;
	}
	return nRet;
}
//鼠标消息
LRESULT MapOnLButtonDown( WPARAM wParam, int nX, int nY )
{
	int nMapX = ScreenToMapX(nX);
	int nMapY = ScreenToMapY(nY);
	if( g_nCurDownX != nMapX || g_nCurDownY != nMapY )
	{ 
		g_nCurDownX = nMapX;
		g_nCurDownY = nMapY;
		return true;
	}
	return false;
}
LRESULT MapOnLButtonUp( WPARAM wParam, int nX, int nY )
{
	if( g_nCurDownX >=0 && g_nCurDownX<g_nMapWidth && g_nCurDownY >=0 && g_nCurDownY<g_nMapHeight)
	{
		DoOpen( g_nCurDownX, g_nCurDownY );
		g_nCurDownX = -1;
		g_nCurDownY = -1;
		return true;
	}
	return false;
}
LRESULT MapOnRButtonDown( WPARAM wParam, int nX, int nY )
{
	return false;
}
LRESULT MapOnRButtonUp( WPARAM wParam, int nX, int nY )
{
	int nMapX = ScreenToMapX(nX);
	int nMapY = ScreenToMapY(nY);
	AddFlag( nMapX, nMapY );
	return false;
}
LRESULT MapOnMouseMove( WPARAM wParam, int nX, int nY )
{
	int nMapX = ScreenToMapX(nX);
	int nMapY = ScreenToMapY(nY);

	if( g_nCurX != nMapX || g_nCurY != nMapY )
	{
		g_nCurX = nMapX;
		g_nCurY = nMapY;
		return true;
	}
	return false;
}
void MarkOpen(int nX, int nY)
{
	g_nMap[nY*g_nMapWidth+nX] = enumMAPTYPE_OPEN;
	++g_nOpended;
	if(g_nOpended>=g_nMapWidth*g_nMapHeight-g_nBomb)
	{
		TCHAR pText[256];
		UINT nTime = GetTickCount()-g_nStartTime;
		wsprintf(pText, _T("You Win!用时%d.%d秒!是否重新开始?"), nTime/1000,nTime%1000);
		if( MessageBox(NULL,pText,_T(""),MB_YESNO) == IDYES )
			Init(g_nBomb, g_nMapWidth, g_nMapHeight);
		else
			g_nGameRunning = FALSE;
	}
}
//打开操作
void DoOpenLoop( int nX, int nY )
{
	if(g_nMap[nY*g_nMapWidth+nX] == enumMAPTYPE_OPEN)
		return;
	MarkOpen(nX, nY);
	int i=0;
	int j=0;
	for(j=-1;j<=1;++j)
	{
		for(i=-1;i<=1;++i)
		{
			if((i+nX)<0||(i+nX)>=g_nMapWidth)
				continue;
			if((j+nY)<0||(j+nY)>=g_nMapHeight)
				continue;
			if(g_nMap[(j+nY)*g_nMapWidth+(i+nX)] == enumMAPTYPE_BACK)
			{
				if(g_nMapText[(j+nY)*g_nMapWidth+(i+nX)] == enumMAPTEXT_NULL)
				{
					DoOpenLoop( i+nX, j+nY );
				}
				else if(g_nMapText[(j+nY)*g_nMapWidth+(i+nX)] != enumMAPTEXT_BOMB)
				{
					MarkOpen(i+nX, (j+nY));
				}
			}

		}
	}
}
void DoOpen( int nX, int nY )
{
	if(g_nMapText[nY*g_nMapWidth+nX] == enumMAPTEXT_BOMB )
	{
		g_nMap[nY*g_nMapWidth+nX] = enumMAPTYPE_OPEN;
		if( MessageBox(NULL,_T("游戏结束,是否重新开始?"),_T(""),MB_YESNO) == IDYES )
		{
			Init(g_nBomb, g_nMapWidth, g_nMapHeight);
			return;
		}
		else
		{
			g_nGameRunning = FALSE;
			return;
		}
	}
	else if(g_nMapText[nY*g_nMapWidth+nX] == enumMAPTEXT_NULL)
	{
		DoOpenLoop( nX, nY );
	}
	if(g_nMap[nY*g_nMapWidth+nX] != enumMAPTYPE_OPEN )
	{
		MarkOpen(nX, nY);
	}
}
//设置标记
void SetFlag( int nX, int nY, int nFlag )
{
	int nOld = g_nMap[nY*g_nMapWidth+nX];
	g_nMap[nY*g_nMapWidth+nX] = (nOld & 0xFFFF) | (nFlag<<16);
}
//设置标记
void AddFlag( int nX, int nY )
{
	if((g_nMap[nY*g_nMapWidth+nX] &0xFFFF)==enumMAPTYPE_OPEN)
		return;
	int nOld = g_nMap[nY*g_nMapWidth+nX];
	int nOldFlag = (nOld>>16)&0xFFFF;
	++nOldFlag;
	if(nOldFlag == enumMAPFLAG_FLAG )
		--g_nLeftBomb;
	else if(nOldFlag == enumMAPFLAG_QUESTION )
		++g_nLeftBomb;
	if(nOldFlag>enumMAPFLAG_QUESTION)
	{
		nOldFlag = 0;
	}
	g_nMap[nY*g_nMapWidth+nX] = (nOld & 0xFFFF) | (nOldFlag<<16);
}

四、逻辑接入主程序

在主程序中做以下几个修改,就将扫雷的逻辑接入主程序并将画面展现出来,同时接收用户的输入操作并反应。首先当然是包含逻辑头文件Mine.h

#include "Mine.h"

然后绘制画面

//绘制游戏
void RenderGame()
{
	setbackmode(enumBKM_TRANSPARENT);
	DrawMap();
	setbackmode(enumBKM_OPAQUE);
	//绘制界面
	DrawUI(  );
}

我们没有在更新函数中做任何更新,所以不需要改变UpdateGame

然后在鼠标输入操作中,我们需要判断是否在地图上并根据输入进行状态改变


LRESULT OnLButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	if( IsOnMap(nX,nY))
		return MapOnLButtonDown( wParam, nX, nY );
	return false;
}
LRESULT OnLButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	if( IsOnMap(nX,nY))
		return MapOnLButtonUp( wParam, nX, nY );
	return false;
}
LRESULT OnRButtonDown( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	if( IsOnMap(nX,nY))
		return MapOnRButtonDown( wParam, nX, nY );
	return false;
}
LRESULT OnRButtonUp( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	if( IsOnMap(nX,nY))
		return MapOnRButtonUp( wParam, nX, nY );
	return false;
}
LRESULT OnMouseMove( HWND hWnd, WPARAM wParam, int nX, int nY )
{
	if( IsOnMap(nX,nY))
		return MapOnMouseMove( wParam, nX, nY );
	return false;
}

最后在初始化阶段进行游戏初始化即可,此处初始化10个炸弹,9x9地图

Init( 10, 9, 9 );

四、代码下载

所有代码可在以下地址察看或下载,

gamemine · master · b2b160 / SimpleCG_Demo · GitCode

编译此程序需安装SimpleCG库,安装方法如下: 

SimpleCG库安装方法

如果只想执行程序可在如下地址下载

扫雷exe压缩文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

b2b160

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值