【三】仿微信飞机大战cocos2d-x3.0rc1

上一篇:【二】仿微信飞机大战cocos2d-x3.0rc1

今天的任务是:

1.我机子弹无限量发射

2.三种类型敌机的出现

3.敌机自己碰墙死掉

 

一、效果界面展示

 

暂时没有实现子弹打中敌机

 

二、工程解决方案

子弹层:BulletLayer文件

敌机层:EnemyLayer文件

敌机精灵数据:Enemy文件

 

三、子弹无限量供应

BulletLayer.h文件:

#ifndef __BULLET_LAYER_H__
#define __BULLET_LAYER_H__
#pragma once
#include "cocos2d.h"
#include "PlaneLayer.h"

USING_NS_CC;

class BulletLayer : public Layer
{
public:
	BulletLayer();
	~BulletLayer();
	virtual bool init();
	CREATE_FUNC(BulletLayer);

public:
	void BeginBulletShoot(float dt=0.0f);	// 开启子弹射击
	void StopBulletShoot();					// 停止子弹射击
	void addBullet(float dt);				// 添加子弹
	void removeBullet(Node* pNode);			// 移除子弹
};

#endif //__BULLET_LAYER_H__

结构和之前的layer一样,无非加了一些自己的方法。

流程:

1.想打飞机,则开启子弹开关  

2.发射子弹

3.某些情况移除子弹 

4.不想打飞机,就停止射击
 

我们可以在创建子弹层的时候,开启射击开关:

bool BulletLayer::init()
{
	if (!Layer::init())
	{
		return false;
	}

	BeginBulletShoot();
	return true;
}

 

射击开关实现如下:

void BulletLayer::BeginBulletShoot( float dt )
{
	this->schedule(schedule_selector(BulletLayer::addBullet), 0.2f, kRepeatForever, dt);
}

每隔0.2秒,就发射一颗子弹(调用addBullet),kRepeatForever.

 

子弹发射过程:

void BulletLayer::addBullet( float dt )
{
	// 子弹
	auto bullet = Sprite::createWithSpriteFrameName("bullet1.png");
	if (NULL == bullet)
	{
		return;
	}

	this->addChild(bullet);

	// 获得飞机的位置
	Point planePos = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
	Point bulletPos = Point(planePos.x + 2, planePos.y + PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getContentSize().height/2);
	bullet->setPosition(bulletPos);

	// 飞行长度 飞行就是超出窗体
	float flyLen = Director::getInstance()->getWinSize().height + bullet->getContentSize().height/2 - bulletPos.y;
	float flyVelocity = 320/1; //飞行速度
	float realFlyDuration = flyLen/flyVelocity; // 飞行时间
	auto actionMove=MoveTo::create(realFlyDuration,Point(bulletPos.x,Director::getInstance()->getWinSize().height+bullet->getContentSize().height/2));
	auto actionDone=CallFuncN::create(CC_CALLBACK_1(BulletLayer::removeBullet, this));

	Sequence* sequence=Sequence::create(actionMove,actionDone,NULL);
	bullet->runAction(sequence);
}

这里有个地方需要注意:PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition(); 这个地方,和我之前写的有点出入,因为那时候,我没考虑到飞机会被其他的模块调用。(飞机是在飞机头出现的,飞机又是可以移动的,所以子弹的位置需要根据飞机来定),这里我可以顺便给大家讲解下CREATE_FUNC宏。回到飞机层,下面展现下休整后的飞机层PlaneLayer.h头文件:

#pragma once
#include "cocos2d.h"
USING_NS_CC;

enum Enum_Plane
{
	AIRPLANE = 1,
};

class PlaneLayer : public Layer
{
public:
	PlaneLayer();

	~PlaneLayer();

	static PlaneLayer* create(); // 实现创建

	virtual bool init(); 

public:
	void checkBorder(float dt); // 边界检测

public:
	static PlaneLayer* sharedPlane; // 提供全局的指针 类似单例
	bool isAlive; // 飞机是否活着
};

少侠们,发现什么不同吗?yes,我提供出了一个static PlaneLayer* sharedPlane;全局的指针,这样的话,就可以被其他模块直接调用了。还有什么不同!没错~CREATE_FUNC(PlaneLayer)定义不见了。其实static PlaneLayer* create()和CREATE_FUNC(PlaneLayer)是一样的,只是之前被宏代替了,我们为什么不用CREATE_FUNC了呢?先给你们看下static PlaneLayer* create()的实现:

PlaneLayer* PlaneLayer::create()
{
	// 不需要手动释放create出来的对象
	PlaneLayer *pRet = new PlaneLayer();
	if (pRet && pRet->init())
	{
		pRet->autorelease(); //将它交由内存管理器进行内存释放管理
		sharedPlane = pRet; // 赋给指针
		return pRet;
	}
	else
	{
		CC_SAFE_DELETE(pRet); // 使用delete操作符删除一个C++对象pRet,如果pRet为NULL,则不进行操作
		return NULL;
	}
}

CREATE_FUNC转换过来,其实也是这样的,不信?那就对比一下,CREATE_FUNC宏:

#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
    __TYPE__ *pRet = new __TYPE__(); \
    if (pRet && pRet->init()) \
    { \
        pRet->autorelease(); \
        return pRet; \
    } \
    else \
    { \
        delete pRet; \
        pRet = NULL; \
        return NULL; \
    } \
}

是不是咯。其实他们很像,只是我们多了sharedPlane = pRet; 把我们生成的layer层提供出来给静态成员,方便外界模块的调用,所以我们才重新编写函数实现。把上面的代码替换之后,别忘了PlaneLayer.cpp在里写下PlaneLayer* PlaneLayer::sharedPlane = NULL;我们根据飞机当前的位置,很容易就算出了子弹此刻应该出现的位置,设置它:

Point planePos = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
	Point bulletPos = Point(planePos.x + 2, planePos.y + PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getContentSize().height/2);
	bullet->setPosition(bulletPos);

接着让子弹按X坐标不变,再定一个Y的终点坐标,这样飞机就会按固定直线,飞离你的飞机到指定位置,然后调用子弹消亡函数removeBullet()。

解释一下MoveTo::create(5,Point(100,100)):用5秒的时间移动到Point(100,100)处。

 

移除子弹:

子弹射的太远,有时候也不太好(你不销毁的话,会占用内存的,不用的,就释放掉)。

void BulletLayer::removeBullet( Node* pNode )
{
	if (NULL == pNode)
	{
		return;
	}

	Sprite* bullet=(Sprite*)pNode;
	this->removeChild(bullet,true);
}

在飞行到指定目的地,触发回调后,我们用本layer层直接removeChild。true是停止被移除精灵的所有动作。

 

停止射击:

void BulletLayer::StopBulletShoot()
{
	this->unschedule(schedule_selector(BulletLayer::addBullet));
}

 

是时候给让飞机打起来了!回到主场景GameScene下的GameLayer::init()。加入以下这段:

	// 开启子弹
	bulletLayer = BulletLayer::create();
	this->addChild(bulletLayer);

当然别忘记在GameScene.h中加头文件:

#ifndef __GAME_SCENE_H__
#define __GAME_SCENE_H__
#include "cocos2d.h"
#include "PlaneLayer.h"
#include "BulletLayer.h"
#include "EnemyLayer.h"

USING_NS_CC;

好吧,不小心把敌机的头文件也包含进来了。只好也把敌机也给实现了。

四、群魔乱舞的敌机

老规矩撒~继续摆出头文件EnemyLayer.h:

#pragma once
#include "cocos2d.h"
#include "EnemyDefine.h"
#include "Enemy.h"

USING_NS_CC;

class EnemyLayer : public Layer
{
public:
	EnemyLayer();
	~EnemyLayer();
	virtual bool init();
	CREATE_FUNC(EnemyLayer);

public:
	void addEnemy1(float dt); // 添加敌机1
	void addEnemy2(float dt); // 添加敌机2
	void addEnemy3(float dt); // 添加敌机3

	void removeEnemy(Node *pNode, EnemyType eType);// 移除敌机pNode, eType是敌机类型,本来想用来加相应特效的,不过算了
public:

};

我们赶快来看看init()的实现!!

bool EnemyLayer::init()
{
	if (!Layer::init())
	{
		return false;
	}

	// 敌机1被打爆动作帧
	cocos2d::Vector<SpriteFrame*> vecTemp;
	vecTemp.clear();
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));

	Animation *pAnimation1 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation1->setDelayPerUnit(0.1f);

	// 添加到AnimationCache,并且命名为Enemy1Blowup
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy1Blowup");

	this->schedule(schedule_selector(EnemyLayer::addEnemy1), 1.0f); // 每1秒出现一架敌机1

	// 敌机2被打爆动作帧
	vecTemp.clear();
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down4.png"));

	Animation *pAnimation2 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation2->setDelayPerUnit(0.1f);
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy2Blowup");
	this->schedule(schedule_selector(EnemyLayer::addEnemy2), 5.0f);

	// 敌机3被打爆动作帧
	vecTemp.clear();
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down4.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down5.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down6.png"));

	Animation *pAnimation3 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation3->setDelayPerUnit(0.1f);
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy3Blowup");
	this->schedule(schedule_selector(EnemyLayer::addEnemy3), 10.0f);

	return true;
}

 

我们就拿出敌机1来解释吧。其他其实都一样:

创建一个容器Vector:cocos2d::Vector<SpriteFrame*> vecTemp。这个Vector存储的只能是继承了Ref的对象,Node,Sprite等都继承了Ref。这个vecTemp.clear()。每次清理一下,我才放心。

然后就是把爆炸后的四个状态对象加入到Vector中去:

	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));

 

接着就是让Animation把这些图像缓存起来,并且设置成0.1秒的间隔显示时间。

	Animation *pAnimation1 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation1->setDelayPerUnit(0.1f);

 

我们缓存起来之后,怎么取出来用呢?就用到了下面的这种形式:

	// 添加到AnimationCache,并且命名为Enemy1Blowup
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy1Blowup");

 

好了让我们回归到如何出现敌机上来:

把第一架飞机设置成一秒一架的速率出现(有人说会不会太快了,你子弹0.2秒一个,还不许人家敌机1秒一次吗?):

this->schedule(schedule_selector(EnemyLayer::addEnemy1), 1.0f); // 每1秒出现一架敌机1

 

添加敌机void EnemyLayer::addEnemy1( float dt ):

void EnemyLayer::addEnemy1( float dt )
{
	EnemySprite *pEnemySprite = EnemySprite::create();
	pEnemySprite->setEnemyByType(Enemy1);
	this->addChild(pEnemySprite);

	// 设置运动轨迹 以及到终点时调用的函数
	auto actionMove=MoveTo::create(3.0f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
	auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy1));
	Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
	pEnemySprite->runAction(sequence);
}

EnemySprite是等下我们要讲的,就先不管这个家伙,我们只要知道EnemySprite它继承了Node。这样我们就可以让EnemySprite加入到EnemyLayer中去。记住我们是让EnemyLayer层里面加入多个以及多种肆无忌惮的敌机,然后射死它们。另外pEnemySprite->getPositionX()与0 - pEnemySprite->getSprite()->getContentSize().height/2这两个是说敌机的X位置不变,Y的位置移动到负的敌机高度的一半。

addEnemy2,addEnemy3都一样,只是把枚举改一下就可以了。

加入敌机1后,我们设置敌机的飞行轨迹actionMove,以及飞行完以后,需要擦屁股的函数EnemyLayer::removeEnemy。因为CC_CALLBACK_1可以额外带一个参数,所以我就带了个Enemy1,this是必带的。这里就实现了,敌机出现,以及敌机消亡的过程。那么敌机是怎么产生的呢?因为有我机,必需拿敌机来衬托我机的强大以及持久呗。好了,不废话,让我们看下敌机真正产生的过程:

/***********************************************

  因为敌方飞机具有生命值等特性,需要附加属性

************************************************/
#include "cocos2d.h"
#include "EnemyDefine.h"

USING_NS_CC;


class EnemySprite : public Node
{
public:
	EnemySprite();
	~EnemySprite();
	virtual bool init();
	CREATE_FUNC(EnemySprite);

public:
	void setEnemyByType(EnemyType enType);
	Sprite* getSprite();

private:
	Sprite *pEnemySprite;
	int nLife;
};

我们这里的飞机有3种,每种的生命值都不一样,所以我们多了个nLife表示敌机生命值。而且我们还需要生成敌机,所以多了个Sprite。再让EnemySprite 继承 Node,这样就可以让敌机层加入这个Node,Node它自己也会addchild(Sprite)的。Layer中加入了Node,Node里面又加入了Sprite。这个你们应该能懂吧。

让我们看下init()的实现:

bool EnemySprite::init()
{
	bool pRet = true;
	if (!Node::init())
	{
		pRet = false;
	}

	return pRet;
}

我擦。居然什么都没有,创建敌机的方法跑哪里去了!!

看下之前被我们抛弃的那三行:

	EnemySprite *pEnemySprite = EnemySprite::create();
	pEnemySprite->setEnemyByType(Enemy2);
	this->addChild(pEnemySprite);

用到了setEnemyByType函数,原来是通过敌机类型来产生不同类型的敌机的。

void EnemySprite::setEnemyByType( EnemyType enType )
{
	switch (enType)
	{
	case Enemy1:
		pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1.png"));
		nLife = ENEMY1_MAXLIFE;
		break;
	case Enemy2:
		pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2.png"));
		nLife = ENEMY2_MAXLIFE;
		break;
	case Enemy3:
		pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n1.png"));
		nLife = ENEMY3_MAXLIFE;
		break;
	case Enemy4:
		pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n2.png"));
		nLife = ENEMY3_MAXLIFE;
		break;
	default:
		return;
		break;
	}

	this->addChild(pEnemySprite);
	Size winSize = Director::getInstance()->getWinSize();
	Size enemySize = pEnemySprite->getContentSize();
	int minX=enemySize.width/2;
	int maxX=winSize.width - enemySize.width/2;
	int rangeX=maxX-minX;
	int actualX=(rand()%rangeX) + minX;

	// 设置敌机Node方位 Node包含Sprite
	this->setPosition(Point(actualX, winSize.height + enemySize.height/2));
}

我们通过不同类型生成不同的敌机,并且赋予不同的生命。然后加入到Node里面去。接着就是设置这个Node的方位。ok,到这里,我们已经把敌机也产生了。最后剩下一个,就是我们让Node把精灵提供出来Sprite* EnemySprite::getSprite():

Sprite* EnemySprite::getSprite()
{
	return pEnemySprite;
}

 

篇幅太长了,我提供了头文件,而且几乎每个方法都实现了一遍。

这里就提供一下EnemyLayer.cpp:

#include "EnemyLayer.h"

EnemyLayer::EnemyLayer()
{

}

EnemyLayer::~EnemyLayer()
{

}

bool EnemyLayer::init()
{
	if (!Layer::init())
	{
		return false;
	}

	// 敌机1被打爆动作帧
	cocos2d::Vector<SpriteFrame*> vecTemp;
	vecTemp.clear();
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));

	Animation *pAnimation1 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation1->setDelayPerUnit(0.1f);

	// 添加到AnimationCache,并且命名为Enemy1Blowup
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy1Blowup");

	this->schedule(schedule_selector(EnemyLayer::addEnemy1), 1.0f); // 每1秒出现一架敌机1

	// 敌机2被打爆动作帧
	vecTemp.clear();
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down4.png"));

	Animation *pAnimation2 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation2->setDelayPerUnit(0.1f);
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy2Blowup");
	this->schedule(schedule_selector(EnemyLayer::addEnemy2), 5.0f);

	// 敌机3被打爆动作帧
	vecTemp.clear();
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down1.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down2.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down3.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down4.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down5.png"));
	vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down6.png"));

	Animation *pAnimation3 = Animation::createWithSpriteFrames(vecTemp);
	pAnimation3->setDelayPerUnit(0.1f);
	AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy3Blowup");
	this->schedule(schedule_selector(EnemyLayer::addEnemy3), 10.0f);

	return true;
}

void EnemyLayer::addEnemy1( float dt )
{
	EnemySprite *pEnemySprite = EnemySprite::create();
	pEnemySprite->setEnemyByType(Enemy1);
	this->addChild(pEnemySprite);

	// 设置运动轨迹 以及到终点时调用的函数
	auto actionMove=MoveTo::create(3.0f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
	auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy1));
	Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
	pEnemySprite->runAction(sequence);
}

void EnemyLayer::addEnemy2( float dt )
{
	EnemySprite *pEnemySprite = EnemySprite::create();
	pEnemySprite->setEnemyByType(Enemy2);
	this->addChild(pEnemySprite);

	// 设置运动轨迹 以及到终点时调用的函数
	auto actionMove=MoveTo::create(4.5f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
		auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy2));
	Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
	pEnemySprite->runAction(sequence);
}

void EnemyLayer::addEnemy3( float dt )
{
	EnemySprite *pEnemySprite = EnemySprite::create();
	pEnemySprite->setEnemyByType(Enemy3);
	this->addChild(pEnemySprite);

	// 设置运动轨迹 以及到终点时调用的函数
	auto actionMove=MoveTo::create(6.0f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
	auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy3));
	Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
	pEnemySprite->runAction(sequence);
}

void EnemyLayer::removeEnemy( Node *pNode , EnemyType eType)
{
	EnemySprite* enemy=(EnemySprite*)pNode;
	if (enemy!=NULL)
	{
		this->removeChild(enemy,true);
	}
}



今天周五!!回家,明天搬到公司附近住~~




 

 

 

 

 

 




 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值