Application源码分析

本文详细分析了Cocos游戏开发框架中的入口、ApplicationProtocol、Application、AppDelegate、GLContextAttrs、GLView等核心类,从源码层面探讨它们的作用及相互关系,为开发者提供深入理解Cocos框架的基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进行分析下cocos的入口,简单的学习下ApplicationProtocol,Application,AppDelegate,GLContextAttrs,GLView这几个类。



游戏的入口是从AppDelegate开始的,但是,ApplDelegate继承自Application,Application继承自ApplicationProtocol。他们的关系如下:



ApplicationProtocol:


这是个协议类,只是声明了一些抽象接口。他们每个方法的作用在上图中都说清楚了,就不上代码了。


Application:


代码:

.h

class CC_DLL Application : public ApplicationProtocol
{
public:
    /**
     * @js ctor
     */
    Application();
    /**
     * @js NA
     * @lua NA
     */
    virtual ~Application();

    /**
    @brief    Run the message loop.
    */
	/// 游戏的主循环都在这里来,包括逻辑,绘制,事件等
    int run();

    /**
    @brief    Get current applicaiton instance.
    @return Current application instance pointer.
	得到当前程序的实例
    */
    static Application* getInstance();

    /** @deprecated Use getInstance() instead */
	/// 废弃掉了
    CC_DEPRECATED_ATTRIBUTE static Application* sharedApplication();
    
    /* override functions */
	/// 设置动画间隔
    virtual void setAnimationInterval(double interval);
	/// 得到当前系统的语言
    virtual LanguageType getCurrentLanguage();
	/// 返回当前语言的ISO 639编码
	/// ISO 639:
	///ISO 639-1是国际标准化组织ISO 639语言编码标准的第一部分。它含有 136 个两字母的编码,用来标示世界上主要的语言。(百度的)
	virtual const char * getCurrentLanguageCode();
    
    /**
     @brief Get target platform
     */
	 /// 当前平台的标签
    virtual Platform getTargetPlatform();
    
    /**
     @brief Open url in default browser
     @param String with url to open.
     @return true if the resource located by the URL was successfully opened; otherwise false.
     */
	 /// 通过默认浏览器,打开网页
    virtual bool openURL(const std::string &url);

    /**
     *  Sets the Resource root path.
     *  @deprecated Please use FileUtils::getInstance()->setSearchPaths() instead.
     */
	 /// 设置资源的根目录
    CC_DEPRECATED_ATTRIBUTE void setResourceRootPath(const std::string& rootResDir);

    /** 
     *  Gets the Resource root path.
     *  @deprecated Please use FileUtils::getInstance()->getSearchPaths() instead. 
     */
	 /// 得到资源的根目录
    CC_DEPRECATED_ATTRIBUTE const std::string& getResourceRootPath(void);

	
	/// 设置启动脚本文件
    void setStartupScriptFilename(const std::string& startupScriptFile);
	/// 返回启动脚本文件名
    const std::string& getStartupScriptFilename(void)
    {
        return _startupScriptFilename;
    }

protected:
    HINSTANCE           _instance;
    HACCEL              _accelTable;
    LARGE_INTEGER       _animationInterval;
    std::string         _resourceRootPath;
    std::string         _startupScriptFilename;

    static Application * sm_pSharedApplication;/// 单例
};

.cpp

/**
@brief    This function change the PVRFrame show/hide setting in register.
@param  bEnable If true show the PVRFrame window, otherwise hide.
在注册表中写入PVRFrame的显示和隐藏设置
*/
static void PVRFrameEnableControlWindow(bool bEnable);

NS_CC_BEGIN

// sharedApplication pointer
Application * Application::sm_pSharedApplication = 0;

Application::Application()
: _instance(nullptr)
, _accelTable(nullptr)
{
    _instance    = GetModuleHandle(nullptr);
    _animationInterval.QuadPart = 0;
    CC_ASSERT(! sm_pSharedApplication);
    sm_pSharedApplication = this;
}

Application::~Application()
{
    CC_ASSERT(this == sm_pSharedApplication);
    sm_pSharedApplication = nullptr;
}

int Application::run()
{
	//设置PVRFrame不可用
    PVRFrameEnableControlWindow(false);

    // Main message loop:
    LARGE_INTEGER nLast;
    LARGE_INTEGER nNow;

    QueryPerformanceCounter(&nLast);
	//初始化OpenGL的背景属性
    initGLContextAttrs();

    // Initialize instance and cocos2d.
	//初始化单例和cocos2d,方法中初始化了导演类和场景
    if (!applicationDidFinishLaunching())
    {
        return 1;
    }

    auto director = Director::getInstance();//初始化后才能调用
    auto glview = director->getOpenGLView();

    // Retain glview to avoid glview being released in the while loop
	//保持glview避免在while循环中glview被释放
    glview->retain();
	//如过窗口还没关闭,那么,执行这个循环
    while(!glview->windowShouldClose())
    {
		//查询性能计数器
		//QueryPerformanceCounter来查询定时器的计数值,如果硬件里有定时器,
		//它就会启动这个定时器,并且不断获取定时器的值,这样的定时器精度,就跟硬件时钟的晶振一样精确的。
        QueryPerformanceCounter(&nNow);
        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
        {
            nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
            //主循环,有对Director的销毁,重启和绘制操作
            director->mainLoop();
			//检测输入事件
            glview->pollEvents();
        }
        else
        {
            Sleep(1);
        }
    }

    // Director should still do a cleanup if the window was closed manually.
	//导演需要执行一次清除,如果窗口手动关闭
	//检测是否还有窗口,如果是直接关闭窗口,那么执行这个操作,如果是 director->end();那么不执行这个操作
    if (glview->isOpenGLReady())
    {
        director->end();
        director->mainLoop();
        director = nullptr;
    }
    glview->release();
    return 0;
}

void Application::setAnimationInterval(double interval)
{
    LARGE_INTEGER nFreq;
    QueryPerformanceFrequency(&nFreq);
    _animationInterval.QuadPart = (LONGLONG)(interval * nFreq.QuadPart);
}

//////////////////////////////////////////////////////////////////////////
// static member function  静态函数(类函数)
//////////////////////////////////////////////////////////////////////////
Application* Application::getInstance()
{
    CC_ASSERT(sm_pSharedApplication);
    return sm_pSharedApplication;
}

// @deprecated Use getInstance() instead
Application* Application::sharedApplication()
{
    return Application::getInstance();
}

LanguageType Application::getCurrentLanguage()
{
    LanguageType ret = LanguageType::ENGLISH;
    
    LCID localeID = GetUserDefaultLCID();
    unsigned short primaryLanguageID = localeID & 0xFF;
    /// 各种语言
    switch (primaryLanguageID)
    {
        case LANG_CHINESE:
            ret = LanguageType::CHINESE;
            break;
        case LANG_ENGLISH:
            ret = LanguageType::ENGLISH;
            break;
        .............
            break;
    }
    
    return ret;
}

const char * Application::getCurrentLanguageCode()
{
	LANGID lid = GetUserDefaultUILanguage();
	const LCID locale_id = MAKELCID(lid, SORT_DEFAULT);
	static char code[3] = { 0 };
	GetLocaleInfoA(locale_id, LOCALE_SISO639LANGNAME, code, sizeof(code));
	code[2] = '\0';
	return code;
}

Application::Platform Application::getTargetPlatform()
{
    return Platform::OS_WINDOWS;
}

bool Application::openURL(const std::string &url)
{
    WCHAR *temp = new WCHAR[url.size() + 1];
    int wchars_num = MultiByteToWideChar(CP_UTF8, 0, url.c_str(), url.size() + 1, temp, url.size() + 1);
    HINSTANCE r = ShellExecuteW(NULL, L"open", temp, NULL, NULL, SW_SHOWNORMAL);
    delete[] temp;
    return (size_t)r>32;
}

void Application::setResourceRootPath(const std::string& rootResDir)
{
    _resourceRootPath = rootResDir;
    std::replace(_resourceRootPath.begin(), _resourceRootPath.end(), '\\', '/');
    if (_resourceRootPath[_resourceRootPath.length() - 1] != '/')
    {
        _resourceRootPath += '/';
    }
    FileUtils* pFileUtils = FileUtils::getInstance();
    std::vector<std::string> searchPaths = pFileUtils->getSearchPaths();
    searchPaths.insert(searchPaths.begin(), _resourceRootPath);
    pFileUtils->setSearchPaths(searchPaths);
}

const std::string& Application::getResourceRootPath(void)
{
    return _resourceRootPath;
}

void Application::setStartupScriptFilename(const std::string& startupScriptFile)
{
    _startupScriptFilename = startupScriptFile;
    std::replace(_startupScriptFilename.begin(), _startupScriptFilename.end(), '\\', '/');
}

NS_CC_END

//////////////////////////////////////////////////////////////////////////
// Local function  局部函数
//////////////////////////////////////////////////////////////////////////
static void PVRFrameEnableControlWindow(bool bEnable)
{
    HKEY hKey = 0;

    // Open PVRFrame control key, if not exist create it.
	// 打开PVRFrame的控制键,如果没有则创建它
    if(ERROR_SUCCESS != RegCreateKeyExW(HKEY_CURRENT_USER,
        L"Software\\Imagination Technologies\\PVRVFRame\\STARTUP\\",
        0,
        0,
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        nullptr))
    {
        return;
    }

    const WCHAR* wszValue = L"hide_gui";
    const WCHAR* wszNewData = (bEnable) ? L"NO" : L"YES";
    WCHAR wszOldData[256] = {0};
    DWORD   dwSize = sizeof(wszOldData);
    LSTATUS status = RegQueryValueExW(hKey, wszValue, 0, nullptr, (LPBYTE)wszOldData, &dwSize);
    if (ERROR_FILE_NOT_FOUND == status              // the key not exist
        || (ERROR_SUCCESS == status                 // or the hide_gui value is exist
        && 0 != wcscmp(wszNewData, wszOldData)))    // but new data and old data not equal
    {
        dwSize = sizeof(WCHAR) * (wcslen(wszNewData) + 1);
        RegSetValueEx(hKey, wszValue, 0, REG_SZ, (const BYTE *)wszNewData, dwSize);
    }

    RegCloseKey(hKey);
}

GLView:


代码:

.h

/** There are some Resolution Policy for Adapt to the screen. */
//这里是一些关于屏幕适配的策略
enum class ResolutionPolicy
{
    /** The entire application is visible in the specified area without trying to preserve the original aspect ratio.
     * Distortion can occur, and the application may appear stretched or compressed.
	 *整个程序是可见的在规定区域,不尝试去保持原始的比例。
	 *将会有失真,程序(界面)或许会被拉伸或压缩
     */
    EXACT_FIT,
    /** The entire application fills the specified area, without distortion but possibly with some cropping,
     * while maintaining the original aspect ratio of the application.
	 *整个程序将会填充规定区域,不会有失真但是可能会有一些裁切,当保持程序原始比例的时候。
     */
    NO_BORDER,
    /** The entire application is visible in the specified area without distortion while maintaining the original
     * aspect ratio of the application. Borders can appear on two sides of the application.
	 *整个程序将会被显示没有失真当保持程序原有比例的时候。但是,黑边可能会在程序的左右两侧或上下两侧出现。
     */
    SHOW_ALL,
    /** The application takes the height of the design resolution size and modifies the width of the internal
     * canvas so that it fits the aspect ratio of the device.
     * No distortion will occur however you must make sure your application works on different
     * aspect ratios.
	 *程序根据设计分辨率的高适应内部画布的宽,所以,他能适应设备的设置比例。
	 *
     */
    FIXED_HEIGHT,
    /** The application takes the width of the design resolution size and modifies the height of the internal
     * canvas so that it fits the aspect ratio of the device.
     * No distortion will occur however you must make sure your application works on different
     * aspect ratios.
     */
    FIXED_WIDTH,

    UNKNOWN,
};

/** @struct GLContextAttrs 
 *
 * There are six opengl Context Attrs. 
 *有OpenGL的6个背景属性
 */
struct GLContextAttrs
{
    int redBits;
    int greenBits;
    int blueBits;
    int alphaBits;
    int depthBits;
    int stencilBits;
};

NS_CC_BEGIN

/**
 * @addtogroup core
 * @{
 */
/**
 * @brief By GLView you can operate the frame information of EGL view through some function.
 *通过GLView你可以通过一些函数操作EGL试图的帧信息
 *EGL详解:
 *http://www.cnblogs.com/wanqieddy/archive/2011/11/24/2261457.html
 */
class CC_DLL GLView : public Ref
{
public:
    /**
     * @js ctor
     */
    GLView();
    /**
     * @js NA
     * @lua NA
     */
    virtual ~GLView();

    /** Force destroying EGL view, subclass must implement this method. */
	//强制销毁EGL视图,子类必须实现这个接口
    virtual void end() = 0;

    /** Get whether opengl render system is ready, subclass must implement this method. */
	//opengl 渲染系统是不是已经准备好,子类必须实现这个接口
    virtual bool isOpenGLReady() = 0;

    /** Exchanges the front and back buffers, subclass must implement this method. */
	//交换前后缓冲区,子类必须实现这个接口
    virtual void swapBuffers() = 0;

    /** Open or close IME keyboard , subclass must implement this method. 
     *开启或者关闭IME键盘
	 *(IME:Input Method Editors中文名称输入法编辑器。它是一种程序,能使用户用 101 
	 *键的标准键盘输入亚洲语言中数以千计的字符。IME 由将键击转换为拼音和表意字符的
	 *引擎和通常用于表意字的字典组成。当用户输入键击时,IME 引擎会尝试确定应将键击
	 *转换成哪个(哪些)字符。),
	 *子类必须实现这个接口.
     * @param open Open or close IME keyboard.
     */
    virtual void setIMEKeyboardState(bool open) = 0;

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
    virtual void setIMEKeyboardState(bool open, std::string str) = 0;
#endif
    
    /** When the window is closed, it will return false if the platforms is Ios or Android.
     * If the platforms is windows or Mac,it will return true.
     *在Ios和安卓平台当窗口被关闭的时候,这个方法将返回false,
	 *在Windows或者MAC平台,这个方法将返回true
     * @return In ios and android it will return false,if in windows or Mac it will return true.
     */
    virtual bool windowShouldClose() { return false; };

    /** Static method and member so that we can modify it on all platforms before create OpenGL context. 
     *静态成员和方法,我们可以修改它在所有的平台上在生成OpenGL背景前
     * @param glContextAttrs The OpenGL context attrs.
     */
    static void setGLContextAttrs(GLContextAttrs& glContextAttrs);
    
    /** Return the OpenGL context attrs. 
     *返回OpenGL背景属性
     * @return Return the OpenGL context attrs.
     */
    static GLContextAttrs getGLContextAttrs();
    
    /** The OpenGL context attrs. */
	//OpenGL背景属性
    static GLContextAttrs _glContextAttrs;

    /** @deprecated
     * Polls input events. Subclass must implement methods if platform
     * does not provide event callbacks.
	 *已经废弃,
	 *检查输入事件,子类必须实现这个接口如果平台不支持事件回调
     */
    CC_DEPRECATED_ATTRIBUTE virtual void pollInputEvents();
    
    /** Polls the events. */
	//检查事件
	//检查输入事件,子类必须实现这个接口如果平台不支持事件回调
    virtual void pollEvents();

    /**
     * Get the frame size of EGL view.
     * In general, it returns the screen size since the EGL view is a fullscreen view.
     *得到EGL视图的框架尺寸。
	 *通常来说,如果EGL试图是全屏视图,将返回屏幕的尺寸
     * @return The frame size of EGL view.
     */
    virtual const Size& getFrameSize() const;

    /**
     * Set the frame size of EGL view.
     *设置EGL试图的框架尺寸
     * @param width The width of the fram size.
     * @param height The height of the fram size.
     */
    virtual void setFrameSize(float width, float height);

    /** Set zoom factor for frame. This methods are for
     * debugging big resolution (e.g.new ipad) app on desktop.
     * 设置框架的缩放系数。这个方法是为了调试大分辨率的桌面程序
     * @param zoomFactor The zoom factor for frame.
     */
    virtual void setFrameZoomFactor(float zoomFactor) {}
    
    /** Get zoom factor for frame. This methods are for
     * debugging big resolution (e.g.new ipad) app on desktop.
     *得到框架的缩放系数。这个方法是为了调试大分辨率的桌面程序
     * @return The zoom factor for frame.
     */
    virtual float getFrameZoomFactor() const { return 1.0; }
    
    /**
     * Hide or Show the mouse cursor if there is one.
     *显示或隐藏鼠标的光标,如果有鼠标的话。
     * @param isVisible Hide or Show the mouse cursor if there is one.
     */
    virtual void setCursorVisible(bool isVisible) {}

    /** Get retina factor.
     *得到视网膜屏的系数
     * @return The retina factor.
     */
    virtual int getRetinaFactor() const { return 1; }

    /** Only works on ios platform. Set Content Scale of the Factor. */
	//只在IOS平台有效,设置内容的缩放系数
    virtual bool setContentScaleFactor(float scaleFactor) { return false; }
    
    /** Only works on ios platform. Get Content Scale of the Factor. */
	/// 得到内容的缩放系数,只在IOS平台有效
    virtual float getContentScaleFactor() const { return 1.0; }
    
    /** Returns whether or not the view is in Retina Display mode.
     *判断当前的视图是不是视网膜显示模式
     * @return Returns whether or not the view is in Retina Display mode.
     */
    virtual bool isRetinaDisplay() const { return false; }
 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    virtual void* getEAGLView() const { return nullptr; }
#endif /* (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) */

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)
	virtual Size getRenerTargetSize() const = 0;
	virtual const Mat4& getOrientationMatrix() const = 0;
	virtual const Mat4& getReverseOrientationMatrix() const = 0;
#endif
    /**
     * Get the visible area size of opengl viewport.
     *得到OpenGL视窗的可视区域
     * @return The visible area size of opengl viewport.
     */
    virtual Size getVisibleSize() const;

    /**
     * Get the visible origin point of opengl viewport.
     *得到OpenGL视窗的可视原点
     * @return The visible origin point of opengl viewport.
     */
    virtual Vec2 getVisibleOrigin() const;

    /**
     * Get the visible rectangle of opengl viewport.
     *得到OpenGL视窗的可视矩形
     * @return The visible rectangle of opengl viewport.
     */
    virtual Rect getVisibleRect() const;

    /**
     * Set the design resolution size.
     * @param width Design resolution width.
     * @param height Design resolution height.
     * @param resolutionPolicy The resolution policy desired, you may choose:
     *                         [1] EXACT_FIT Fill screen by stretch-to-fit: if the design resolution ratio of width to height is different from the screen resolution ratio, your game view will be stretched.
     *                         [2] NO_BORDER Full screen without black border: if the design resolution ratio of width to height is different from the screen resolution ratio, two areas of your game view will be cut.
     *                         [3] SHOW_ALL  Full screen with black border: if the design resolution ratio of width to height is different from the screen resolution ratio, two black borders will be shown.
     *设置设计分辨率的尺寸和分辨率策略
	 */
    virtual void setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy);

    /** Get design resolution size.
     *  Default resolution size is the same as 'getFrameSize'.
     *返回设计分辨率,默认的尺寸与getFrameSize的结果一样
     * @return The design resolution size.
     */
    virtual const Size&  getDesignResolutionSize() const;

    /**
     * Set opengl view port rectangle with points.
     *通过点设置OpenGL视窗的矩形
     * @param x Set the points of x.
     * @param y Set the points of y.
     * @param w Set the width of  the view port
     * @param h Set the Height of the view port.
     */
    virtual void setViewPortInPoints(float x , float y , float w , float h);

    /**
     * Set Scissor rectangle with points.
     *通过点设置裁切矩形
     * @param x Set the points of x.
     * @param y Set the points of y.
     * @param w Set the width of  the view port
     * @param h Set the Height of the view port.
     */
    virtual void setScissorInPoints(float x , float y , float w , float h);

    /**
     * Get whether GL_SCISSOR_TEST is enable.
     *裁切是否可用
     * @return Whether GL_SCISSOR_TEST is enable.
     */
    virtual bool isScissorEnabled();

    /**
     * Get the current scissor rectangle.
     *返回当前裁切矩形
     * @return The current scissor rectangle.
     */
    virtual Rect getScissorRect() const;

    /** Set the view name. 
     *设置视图的名字
     * @param viewname A string will be set to the view as name.
     */
    virtual void setViewName(const std::string& viewname);
    
    /** Get the view name.
     *返回视图的名字
     * @return The view name.
     */
    const std::string& getViewName() const;

    /** Touch events are handled by default; if you want to customize your handlers, please override this function.
     *点击事件被自动管理,如果你想定制自己的管理器,请重载这个方法
     * @param num The number of touch.
     * @param ids The identity of the touch.
     * @param xs The points of x.
     * @param ys The points of y.
     */
    virtual void handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[]);
    
    /** Touch events are handled by default; if you want to customize your handlers, please override this function.
     *
     * @param num The number of touch.
     * @param ids The identity of the touch.
     * @param xs The points of x.
     * @param ys The points of y.
     */
    virtual void handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[]);
    
    /** Touch events are handled by default; if you want to customize your handlers, please override this function.
     *
     * @param num The number of touch.
     * @param ids The identity of the touch.
     * @param xs The points of x.
     * @param ys The points of y.
     */
    virtual void handleTouchesEnd(int num, intptr_t ids[], float xs[], float ys[]);
    
    /** Touch events are handled by default; if you want to customize your handlers, please override this function.
     *
     * @param num The number of touch.
     * @param ids The identity of the touch.
     * @param xs The points of x.
     * @param ys The points of y.
     */
    virtual void handleTouchesCancel(int num, intptr_t ids[], float xs[], float ys[]);

    /**
     * Get the opengl view port rectangle.
     *得到OpenGL视窗的矩形
     * @return Return the opengl view port rectangle.
     */
    const Rect& getViewPortRect() const;
    
    /**
     * Get list of all active touches.
     *得到所有激活的点击事件
     * @return A list of all active touches.
     */
    std::vector<Touch*> getAllTouches() const;

    /**
     * Get scale factor of the horizontal direction.
     *得到水平方向的缩放系数
     * @return Scale factor of the horizontal direction.
     */
    float getScaleX() const;

    /**
     * Get scale factor of the vertical direction.
     *得到竖直方向的缩放系数
     * @return Scale factor of the vertical direction.
     */
    float getScaleY() const;

    /** Returns the current Resolution policy.
     *返回当前的屏幕适应策略
     * @return The current Resolution policy.
     */
    ResolutionPolicy getResolutionPolicy() const { return _resolutionPolicy; }

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    virtual HWND getWin32Window() = 0;
#endif /* (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) */

#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
    virtual id getCocoaWindow() = 0;
#endif /* (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) */
    
protected:
    void updateDesignResolutionSize();
    
    void handleTouchesOfEndOrCancel(EventTouch::EventCode eventCode, int num, intptr_t ids[], float xs[], float ys[]);

    // real screen size
	//屏幕的真实尺寸
    Size _screenSize;
    // resolution size, it is the size appropriate for the app resources.
	//设计分辨率尺寸
    Size _designResolutionSize;
    // the view port size
	//视窗尺寸
    Rect _viewPortRect;
    // the view name
	//视窗名字
    std::string _viewName;

    float _scaleX;
    float _scaleY;
    ResolutionPolicy _resolutionPolicy;
};



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值