Cocos2dx入门小游戏---Runner教程

简介:

本人初学cocos2dx,网上找了个例子,写一遍,自己领悟一翻,现在把自己的理解配上注释,

一步步深入理解这个小程序,希望可以帮助初始入门的朋友们。

首先建立一个Entity实体类,以便怪物类和玩家类可以继承:
#include "cocos2d.h"

/**
 * @brief 精灵实体类
 *
 * 绑定精灵、获取精灵
 */
class Entity : public cocos2d::CCNode {
public:
    /** @{
     * @name 构造函数、析构函数、
     */
    
    /// 构造函数
    Entity();
    
    /// 析构函数
    virtual ~Entity();
    
    /** @}
     */
    
    /** @{
     * @name getter、setter方法
     */
    
    /**
     * @brief 获取精灵
     */
    cocos2d::CCSprite *getSprite();
    
    /**
     * @brief 绑定精灵
     */
    void setSprite(cocos2d::CCSprite *pSprite);
    
    /** @}
     */
    
private:
    cocos2d::CCSprite *m_pSprite; ///< 精灵对象
};


#include "Entity.h"

USING_NS_CC;

Entity::Entity() : CCNode(), m_pSprite(NULL) {
    return;
}

Entity::~Entity() {
    // 使用自带的宏,方便安全
    CC_SAFE_RELEASE_NULL(m_pSprite);
    return;
}

CCSprite *Entity::getSprite() {
    return m_pSprite;
}

void Entity::setSprite(cocos2d::CCSprite *pSprite) {
    this->m_pSprite = pSprite;
    CC_SAFE_RETAIN(this->m_pSprite); // 防止被释放
    
    this->addChild(m_pSprite);
    return;
}


接下来,先创建一个有特效效果的文本显示类:

/**
 * @brief 文字飘动特效效果类
 */
class FlowTextLabel : public cocos2d::CCNode {
public:
    /** 
     * 构造函数 
     */
    FlowTextLabel();
    
    /**
     * 析构函数
     */
    virtual ~FlowTextLabel();
    
    /**
     * 初始化函数
     */
    virtual bool init();
    
    /**
     * 创建FlowTextLabel对象,自动释放对象
     */
    CREATE_FUNC(FlowTextLabel);
    
    /**
     * @brief 显示文本
     * @param pText 文本
     * @param pos 显示的位置
     */
    void showText(const char *pText, cocos2d::CCPoint pos);
    
    /**
     * @brief 隐藏文本
     */
    void hideText();
    
private:
    cocos2d::CCLabelTTF *m_pText; ///< 文本文字
};

#include "FlowTextLabel.h"

USING_NS_CC;

FlowTextLabel::FlowTextLabel() : m_pText(NULL) {
    CCNode::CCNode();
    return;
}

FlowTextLabel::~FlowTextLabel() {
    CC_SAFE_RELEASE_NULL(m_pText);
    CCNode::~CCNode();
    return;
}

bool FlowTextLabel::init() {
    bool bRet = false;
    do {
        CC_BREAK_IF(!CCNode::init());
        
        m_pText = CCLabelTTF::create("", "Arial", 30);
        CC_BREAK_IF(!m_pText);
        
        m_pText->setColor(ccc3(255, 0, 0));
        m_pText->setVisible(false);
        this->addChild(m_pText);
        
        bRet = true;
    } while (0);
    
    return bRet;
}

void FlowTextLabel::showText(const char *pText, CCPoint pos) {
    m_pText->setString(pText);
    m_pText->setPosition(pos);
    m_pText->setVisible(true);
    m_pText->setAnchorPoint(ccp(1, 0));
    
    // 先放大后缩小效果
    CCScaleTo *pScaleLarge = CCScaleTo::create(0.3f, 2.5f, 2.5f);
    CCScaleTo *pScaleSmall = CCScaleTo::create(0.4f, 0.5f, 0.5f);
    // 动作回调
    CCCallFunc *pCallFunc = CCCallFunc::create(this, callfunc_selector(FlowTextLabel::hideText));
    CCActionInterval *pActions = CCSequence::create(pScaleLarge, pScaleSmall, pCallFunc, NULL);
    this->runAction(pActions);
    return;
}

void FlowTextLabel::hideText() {
    m_pText->setVisible(false);
    // 从父节点移除,并移除节点的动作和回调函数
    m_pText->removeFromParentAndCleanup(true);
    return;
}


接下来,创建一个继承于Entity的类,也就是玩家类:

#include "cocos2d.h"
#include "Entity.h"

/**
 * @brief 玩家类
 * 玩家动作:跳、受到攻击攻击伤害、吃掉金币
 */
class Player : public Entity {
public:
    ///
    /// 构造函数、析构函数、初始化函数、创建玩家函数
    Player();
    virtual ~Player();
    virtual bool init();
    CREATE_FUNC(Player);

    /**
     * @brief 玩家跳起
     */
    void jumpBegin();
    
    /**
     * @brief 玩家跳完毕
     */
    void jumpEnd();
    
    /**
     * @brief 玩家与怪物碰撞(玩家受到攻击伤害)
     */
    void hit();
    
    /**
     * @brief 获取得到的金币
     */
    int getMoney();
    
    /**
     * @brief 获取碰撞范围
     */
    cocos2d::CCRect getBoundingBox();
    
    /**
     * @brief 恢复数据到原始状态
     */
    void resetDataToNormal();
    
    /**
     * @brief 动作结束
     */
    void actionEnd();
    
private:
    bool m_bIsJumping; ///< 玩家是否正处于跳起状态
    int m_nMoney;      ///< 玩家获取的金币
};

#include "Player.h"
#include "FlowTextLabel.h"
#include "SimpleAudioEngine.h"

USING_NS_CC;
using namespace CocosDenshion;

Player::Player() : Entity(), m_bIsJumping(false), m_nMoney(0) {
    return;
}

Player::~Player() {
    return;
}

bool Player::init() {
    return true;
}

void Player::jumpBegin() {
    // 如果没有玩家或者如果玩家还在跳跃中,则不重复跳跃
    if (!getSprite() || m_bIsJumping) {
        return;
    }
    
    m_bIsJumping = true;
    
    // 创建动作
    // 1.秒内跳起200高度,只跳一次
    CCJumpBy *pJumpBy = CCJumpBy::create(1.0f, ccp(50, 0), 200, 1);
    // 这种也算是一种动作吧
    CCCallFunc *pCallFunc = CCCallFunc::create(this, callfunc_selector(Player::jumpEnd));
    // 瞬时动作
    CCActionInterval *pJumpActions = CCSequence::create(pJumpBy, pCallFunc, NULL);
    this->runAction(pJumpActions);
    return;
}

void Player::jumpEnd() {
    m_bIsJumping = false;
    return;
}

void Player::hit() {
    do {
        CC_BREAK_IF(!getSprite());
        
        // 获取金币特效提示
        FlowTextLabel *pFlowLabel = FlowTextLabel::create();
        CC_BREAK_IF(!pFlowLabel);
        this->addChild(pFlowLabel);
        pFlowLabel->showText("+15", getSprite()->getPosition());
        
        m_nMoney += 15;
        
        // 创建4种动作对象
        // 0.1秒内,x轴方向向左移动20,y方向不变
        CCMoveBy *pBackMoveBy = CCMoveBy::create(0.1, ccp(-20, 0));
        // 0.1秒内,x轴方向右向移动20,y方向不变
        CCMoveBy *pForwardMoveBy = CCMoveBy::create(0.1, ccp(20, 0));
        // 0.1秒内,x方向逆时间旋转5度,y方向顺时间旋转0度
        CCRotateBy *pBackRotateBy = CCRotateBy::create(0.1, -5.0, 0);
        // 0.1秒内,x方向顺时针旋转5度,y方向不变
        CCRotateBy *pForwardRotateBy = CCRotateBy::create(0.1, 5.0, 0);
        
        // 创建并行动作
        // 过程为:玩家0.1秒内先后退内20宽度,并0.1秒内逆时针旋转5度
        //        再0.1秒内向前移动20宽度,并0.1秒内顺时针旋转5度,回复到原来的状态
        CCSpawn *pBackActions = CCSpawn::create(pBackMoveBy, pBackRotateBy, NULL);
        CCSpawn *pForwardActions = CCSpawn::create(pForwardMoveBy, pForwardRotateBy, NULL);
        
        // 动作执行完毕时的回调
        CCCallFunc *pCallFunc = CCCallFunc::create(this, callfunc_selector(Player::actionEnd));
        CCActionInterval *pActions = CCSequence::create(pBackActions,
                                                        pForwardActions,
                                                        pCallFunc,
                                                        NULL);
        this->runAction(pActions);
        
        SimpleAudioEngine::sharedEngine()->playEffect("hitEffect.wav");

    } while (0);
    return;
}

void Player::resetDataToNormal() {
    m_bIsJumping = false;
    
    this->setPosition(ccp(200, 500 / 4));
    this->setScale(1.0f);
    this->setRotation(0);
    return;
}

int Player::getMoney() {
    return m_nMoney;
}

CCRect Player::getBoundingBox() {
    CCRect rect = CCRectZero;
    do {
        CC_BREAK_IF(!getSprite());
        
        // 获取玩家可视范围大小,用于检测玩家的碰撞范围
        CCSize spriteSize = getSprite()->getContentSize();
        // 获取玩家的中心位置
        CCPoint entityPos = this->getPosition();
        
        // 获取玩家左下角x坐标
        float leftBottomX = entityPos.x - spriteSize.width / 2;
        // 获取玩家左下角y坐标
        float leftBottomY = entityPos.y - spriteSize.height / 2;
        rect = CCRectMake(leftBottomX, leftBottomY, spriteSize.width, spriteSize.height);
        
    } while (0);
    return rect;
}

void Player::actionEnd() {
    this->setScale(1.0f);
    this->setRotation(0);
    return;
}

接下来,创建怪物类:

#include "cocos2d.h"
#include "Entity.h"
#include "Player.h"

/**
 * @brief 怪物类(也就是金币类),继承于实体类
 */
class Monster : public Entity {
public:
    /// 构造函数
    Monster();
    /// 析构函数
    virtual ~Monster();
    /// 初始化函数
    virtual bool init();
    /// 创建怪物对象,自动释放
    CREATE_FUNC(Monster);
    
    /**
     * @brief 显示怪物(金币)
     */
    void show();
    /**
     * @brief 隐藏怪物(金币)
     */
    void hide();
    /**
     * @brief 重置怪物数据(金币)
     */
    void reset();
    
    /**
     * @brief 怪物是否活着(金币)
     */
    bool isAlive();
    /**
     * @brief 怪物是否与指定玩家对象相碰撞(金币)
     * @param pPlayer 玩家对象
     */
    bool isCollideWithPlayer(Player *pPlayer);
    
private:
    bool m_bIsAlive; ///< 怪物是否还活着
};

#include "Monster.h"

USING_NS_CC;

Monster::Monster() : Entity(), m_bIsAlive(false) {
    return;
}

Monster::~Monster() {
    return;
}

bool Monster::init() {
    bool bRet = false;
    do {
        //
        
        bRet = true;
    } while (0);
    
    return bRet;
}

void Monster::show() {
    if (NULL != getSprite()) {
        this->setVisible(true);
        // 标记为活动状态
        m_bIsAlive = true;
    }
    return;
}

void Monster::hide() {
    do {
        CC_BREAK_IF(!getSprite());
        
        this->setVisible(false);
        reset();
        m_bIsAlive = false;
    } while (0);
    return;
}

void Monster::reset() {
    do {
        CC_BREAK_IF(!getSprite());
        
        // 范围在800 ~ 2000
        this->setPosition(ccp(800 + CCRANDOM_0_1() * 2000, 200 - CCRANDOM_0_1() * 100));
    } while (0);
    return;
}

bool Monster::isAlive() {
    return m_bIsAlive;
}

bool Monster::isCollideWithPlayer(Player *pPlayer) {
    bool bRet = false;
    do {
        CC_BREAK_IF(!pPlayer);
        
        // 获取玩家碰撞范围
        CCRect rect = pPlayer->getBoundingBox();
        // 获取怪物(金币)的中心位置
        CCPoint monsterPos = this->getPosition();
        
        bRet = rect.containsPoint(monsterPos);
    } while (0);
    return bRet;
}

创建怪物管理类:

#include "cocos2d.h"
#include "Player.h"

const int g_nMaxMonsterNumber = 10;

class MonsterManager : public cocos2d::CCNode {
public:
    MonsterManager();
    virtual ~MonsterManager();
    virtual bool init();
    CREATE_FUNC(MonsterManager);
    
    // 更新绘制
    void update(float delta);
    
    /**
     * @brief 设置玩家
     * @param pPlayer 玩家对象指针
     */
    void setPlayer(Player *pPlayer);
    
private:
    /**
     * @brief 创建怪物(金币)
     */
    void createMonsters();
    
private:
    cocos2d::CCArray *m_pArrMonsters; ///< 怪物对象数组
    Player *m_pPlayer;                ///< 玩家对象
};

#include "MonsterManager.h"
#include "Monster.h"

USING_NS_CC;

MonsterManager::MonsterManager()
: CCNode(),
  m_pArrMonsters(NULL),
  m_pPlayer(NULL) {
    return;
}

MonsterManager::~MonsterManager() {
    CC_SAFE_DELETE_ARRAY(m_pArrMonsters);
    CC_SAFE_RELEASE_NULL(m_pPlayer);
    return;
}

bool MonsterManager::init() {
    bool bRet = false;
    do {
        createMonsters();
        // 启用update
        this->scheduleUpdate();
        
        bRet = true;
    } while (0);
    return bRet;
}

void MonsterManager::createMonsters() {
    m_pArrMonsters = CCArray::create();
    CC_SAFE_RETAIN(m_pArrMonsters); // 防止数组被释放
    
    Monster *pMonster = NULL;
    CCSprite *pSprite = NULL;
    
    for (int i = 0; i < g_nMaxMonsterNumber; i++) {
        pMonster = Monster::create();
        CC_BREAK_IF(!pMonster);
        
        pSprite = CCSprite::create("monster.png");
        CC_BREAK_IF(!pSprite);
        
        pMonster->setSprite(pSprite);
        pMonster->reset();
        
        this->addChild(pMonster); // 将怪物添加到管理器中
        m_pArrMonsters->addObject(pMonster);
    }
    return;
}

void MonsterManager::update(float delta) {
    CCObject *pObject = NULL;
    Monster *pMonster = NULL;
    
    CCARRAY_FOREACH(m_pArrMonsters, pObject) {
        pMonster = dynamic_cast<Monster *>(pObject);
        // 如果怪物是否处于活动状态
        if (pMonster->isAlive()) {
            // 左移3个宽度
            pMonster->setPositionX(pMonster->getPositionX() - 3);
            // 若到达屏幕最左,超出屏幕就隐藏
            if (pMonster->getPositionX() < 0) {
                pMonster->hide();
            } else if (pMonster->isCollideWithPlayer(m_pPlayer)) {
                m_pPlayer->hit();
                pMonster->hide();
            }
        } else { // 非活动状态
            pMonster->show();
        }
    }
    return;
}

void MonsterManager::setPlayer(Player *pPlayer) {
    this->m_pPlayer = pPlayer;
    CC_SAFE_RETAIN(m_pPlayer);
    return;
}

最后,就是需要创建一个游戏场景:

#include "cocos2d.h"
#include "Player.h"
#include "cocos-ext.h"

USING_NS_CC;
using namespace extension;

/**
 * @brief 游戏运行场景
 */
class GameRunningScene : public CCLayer {
public:
    GameRunningScene();
    virtual ~GameRunningScene();
    virtual bool init();
    static CCScene *scene();
    CREATE_FUNC(GameRunningScene);
    
    virtual void update(float delta);
    
private:
    void initBackground();  ///< 初始化关卡背景
    void createJumpButton();///< 创建跳跃按钮
    void jumpEvent(CCObject *pSender, CCControlEvent event); // 响应点击事件
    void createScoreLabel();///< 分数
    void createTimeSlider();///< 创建时间条
    
    CCSprite *m_pBgSprite1;         ///< 背景1
    CCSprite *m_pBgSprite2;         ///< 背景2
    
    Player *m_pPlayer;              ///< 玩家
    CCLabelTTF *m_pScoreLabel;      ///< 分类标签
    CCControlSlider *m_pTimeSlider; ///< 时间条
    
    int m_nScore;       ///< 得分
    int m_nCurrentTime; ///< 当前时间
};

#include "GameRunningScene.h"
#include "MonsterManager.h"
#include "SimpleAudioEngine.h"

using namespace CocosDenshion;

GameRunningScene::GameRunningScene()
: m_pBgSprite1(NULL),
  m_pBgSprite2(NULL),
  m_nCurrentTime(0),
  m_pTimeSlider(NULL),
  m_pPlayer(NULL),
  m_pScoreLabel(NULL),
  m_nScore(0) {
    CCLayer::CCLayer();
    
    return;
}

GameRunningScene::~GameRunningScene() {
    CC_SAFE_RELEASE_NULL(m_pBgSprite1);
    CC_SAFE_RELEASE_NULL(m_pBgSprite2);
    CC_SAFE_RELEASE_NULL(m_pPlayer);
    CC_SAFE_RELEASE_NULL(m_pScoreLabel);
    CC_SAFE_RELEASE_NULL(m_pTimeSlider);
    
    CCLayer::~CCLayer();
    return;
}

CCScene *GameRunningScene::scene() {
    CCScene *pScene = NULL;
    do {
        pScene = CCScene::create();
        CC_BREAK_IF(!pScene);
        
        GameRunningScene *pLayer = GameRunningScene::create();
        CC_BREAK_IF(!pLayer);
        
        pScene->addChild(pLayer, 1);
    } while (0);
    
    return pScene;
}

bool GameRunningScene::init() {
    bool bRet = false;
    do {
        CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
        
        CCSprite *pSprite = CCSprite::create("sprite.png");
        m_pPlayer = Player::create();
        CC_BREAK_IF(!m_pPlayer);
        m_pPlayer->setSprite(pSprite);
        m_pPlayer->setPosition(ccp(200, visibleSize.height / 4));
        this->addChild(m_pPlayer, 1);
        
        // 初始化背景图片
        initBackground();
        
        // 创建按钮
        createJumpButton();
        
        // 设置启用CCNode的update函数,游戏会在每一帧调用update函数
        this->scheduleUpdate();
        
        // 怪物管理器
        MonsterManager *pMonsterManager = MonsterManager::create();
        CC_BREAK_IF(!pMonsterManager);
        pMonsterManager->setPlayer(m_pPlayer);
        this->addChild(pMonsterManager, 4);
        
        // 创建分数标签
        createScoreLabel();
        
        // 创建时间条
        createTimeSlider();
        
        SimpleAudioEngine::sharedEngine()->playBackgroundMusic("bgMusic.wav", true);
        
        bRet = true;
    } while (0);
    
    return bRet;
}

void GameRunningScene::initBackground() {
    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    
    m_pBgSprite1 = CCSprite::create("tollgateBG.jpg");
    m_pBgSprite1->setPosition(ccp(visibleSize.width / 2, visibleSize.height / 2));
    this->addChild(m_pBgSprite1, 0);
    
    m_pBgSprite2 = CCSprite::create("tollgateBG.jpg");
    m_pBgSprite2->setPosition(ccp(visibleSize.width / 2 * 3, visibleSize.height / 2));
    m_pBgSprite2->setFlipX(true);
    this->addChild(m_pBgSprite2, 0);
    
    return;
}

void GameRunningScene::update(float delta) {
    // 地图大小
    CCSize mapSize = m_pBgSprite1->getContentSize();
    
    // 地图1和地图2的X坐标
    int posX1 = m_pBgSprite1->getPositionX();
    int posX2 = m_pBgSprite2->getPositionX();
    
    int nMapScrollSpeed = 5; // 地图滚动的速度
    posX1 -= nMapScrollSpeed;
    posX2 -= nMapScrollSpeed;
    
    // 创建无限循环
    if (posX1 <= -mapSize.width / 2) {
        posX1 = mapSize.width + mapSize.width / 2;
        posX2 = mapSize.width / 2;
    }
    
    if (posX2 <= -mapSize.width / 2) {
        posX1 = mapSize.width / 2;
        posX2 = mapSize.width + mapSize.width / 2;
    }
    
    m_pBgSprite1->setPositionX(posX1);
    m_pBgSprite2->setPositionX(posX2);
    
    // 得分
    m_nScore = m_pPlayer->getMoney();
    m_pScoreLabel->setString(CCString::createWithFormat("Score: %d", m_nScore)->getCString());
    m_pTimeSlider->setValue(--m_nCurrentTime);
    
    return;
}

void GameRunningScene::createJumpButton() {
    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    
    do {
        // 按钮标题
        CCLabelTTF *pJumpLabel = CCLabelTTF::create("Jump", "Arial", 35);
        CC_BREAK_IF(!pJumpLabel);
        
        // 按钮状态图片
        CCScale9Sprite *pJumpNormalSprite = CCScale9Sprite::create("button.png");
        CC_BREAK_IF(!pJumpNormalSprite);
        
        CCScale9Sprite *pJumpLightSprite = CCScale9Sprite::create("buttonHighlighted.png");
        CC_BREAK_IF(!pJumpLightSprite);
        
        // 创建按钮
        CCControlButton *pJumpButton = CCControlButton::create(pJumpLabel, pJumpNormalSprite);
        CC_BREAK_IF(!pJumpButton);
        
        pJumpButton->setPosition(visibleSize.width - 80, 50);
        pJumpButton->setBackgroundSpriteForState(pJumpLightSprite, CCControlStateHighlighted);
        pJumpButton->addTargetWithActionForControlEvents(this,
                                                         cccontrol_selector(GameRunningScene::jumpEvent),
                                                         CCControlEventTouchDown);
        this->addChild(pJumpButton);
    } while (0);
    
    return;
}

void GameRunningScene::jumpEvent(cocos2d::CCObject *pSender, CCControlEvent event) {
    m_pPlayer->jumpBegin();
    return;
}

void GameRunningScene::createScoreLabel() {
    m_nScore = m_pPlayer->getMoney();
    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    
    m_pScoreLabel = CCLabelTTF::create(
              CCString::createWithFormat("Score: %d", m_nScore)->getCString(), "Arial", 35);
    m_pScoreLabel->setAnchorPoint(ccp(0, 1));
    m_pScoreLabel->setPosition(ccp(0, visibleSize.height));
    
    this->addChild(m_pScoreLabel);
    return;
}

void GameRunningScene::createTimeSlider() {
    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    m_nCurrentTime = 10000;
    m_pTimeSlider = CCControlSlider::create(CCSprite::create("background.png"),
                                            CCSprite::create("progress.png"),
                                            CCSprite::create("sliderThumb.png"));
    m_pTimeSlider->setPosition(
            ccp(m_pTimeSlider->getContentSize().width / 2,
            visibleSize.height - m_pTimeSlider->getContentSize().height - m_pScoreLabel->getContentSize().height
            ));
    m_pTimeSlider->setTouchEnabled(false);
    m_pTimeSlider->setMaximumValue(10000);
    m_pTimeSlider->setMinimumValue(0);
    m_pTimeSlider->setValue(m_nCurrentTime);
    this->addChild(m_pTimeSlider, 3);
    
    return;
}



目录
相关文章
|
3月前
AutoJS4.1.0实战教程 ---七猫免费小说
AutoJS4.1.0实战教程 ---七猫免费小说
39 0
|
3月前
AutoJS4.1.0实战教程 ---快音
AutoJS4.1.0实战教程 ---快音
38 0
|
3月前
AutoJS4.1.0实战教程 ---彩蛋视频
AutoJS4.1.0实战教程 ---彩蛋视频
21 0
|
3月前
AutoJS4.1.0实战教程 ---步多多
AutoJS4.1.0实战教程 ---步多多
13 0
|
3月前
AutoJs4.1.0实战教程---抖音极速版
AutoJs4.1.0实战教程---抖音极速版
76 0
|
3月前
|
JavaScript 数据安全/隐私保护 Android开发
AutoJs4.1.0实战教程---源码大放送
AutoJs4.1.0实战教程---源码大放送
48 0
|
3月前
AutoJS4.1.0实战教程 ---火火视频极速版
AutoJS4.1.0实战教程 ---火火视频极速版
24 5
|
3月前
AutoJs4.1.0实战教程---抖音极速版点赞加关注代码
AutoJs4.1.0实战教程---抖音极速版点赞加关注代码
46 0
|
3月前
AutoJs Pro 7.0.4-1 实战教程---史上最全抖音极速版
AutoJs Pro 7.0.4-1 实战教程---史上最全抖音极速版
57 0
|
3月前
AutoJs Pro 7.0.4-1 实战教程---史上最全快音短视频
AutoJs Pro 7.0.4-1 实战教程---史上最全快音短视频
20 0