ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Cocos2d-x横版跑酷游戏开发实战:从零构建“萝莉快跑”

Cocos2d-x横版跑酷游戏开发实战:从零构建“萝莉快跑”

1. 项目概述与核心思路

“萝莉快跑”这个项目,一听名字就知道是个典型的横版跑酷游戏。这类游戏的核心玩法简单直接——控制角色在一条无限延伸的跑道上,通过跳跃、下滑等动作躲避障碍,同时尽可能收集金币或道具,跑得越远分数越高。听起来简单,但要把手感做顺滑、节奏做爽快,里面的门道可不少。我这次选择用 Cocos2d-x 这个老牌引擎来开发,一方面是因为它跨平台能力极强,一套代码能轻松发布到 iOS、Android 甚至 Windows 上,对于想快速验证玩法的独立开发者或者小团队来说非常友好;另一方面,Cocos2d-x 在 2D 游戏开发领域积累了大量的社区资源和成熟方案,很多坑前人已经帮你踩过了。

这个项目的核心目标,不仅仅是实现一个能跑能跳的 demo,而是要打造一个具备完整游戏循环、手感优秀、有一定扩展性的横版跑酷游戏。这意味着我们需要处理好几个关键模块:首先是角色的控制与动画,要让“萝莉”的跑、跳、下滑等动作流畅自然;其次是关卡与障碍物的生成逻辑,如何做到既随机又有节奏感,让玩家不会觉得单调;然后是游戏的核心循环,包括分数计算、生命值管理、游戏状态切换(开始、进行中、结束);最后是视听效果的打磨,比如音效、粒子特效和 UI 交互。整个开发过程,我会从最基础的场景搭建开始,一步步把这些模块串联起来,过程中会分享很多从实际编码中总结出来的技巧和避坑指南。

2. 开发环境搭建与项目初始化

2.1 Cocos2d-x 引擎选择与环境配置

Cocos2d-x 发展到今天,版本已经相当多了。对于新项目,我强烈推荐使用 Cocos2d-x 4.0 或更新版本。4.x 版本对渲染器进行了重构,性能有显著提升,同时更好地支持了现代 C++ 特性,代码写起来更舒服。官方提供了 Cocos Creator 这个编辑器,但对于习惯纯代码开发或者需要更深度定制的开发者,使用命令行工具创建和构建 C++ 项目依然是主流且高效的方式。

第一步是下载引擎。不要去官网找那些零散的下载链接,直接用 Git 克隆官方仓库是最稳妥的,能确保拿到最新的稳定代码和示例。打开终端(Windows 用 PowerShell 或 CMD,Mac/Linux 用 Terminal),执行以下命令:

git clone https://github.com/cocos2d/cocos2d-x.git cd cocos2d-x git checkout v4.0 # 切换到4.0稳定分支,你也可以用最新的分支

克隆完成后,需要安装 Python 依赖。Cocos2d-x 的构建系统依赖 Python 2.7 或 3.x,以及一些 Python 模块。在引擎根目录下,通常会有requirements.txt文件,用 pip 安装即可:pip install -r requirements.txt。这里有个坑需要注意,如果你的系统同时有 Python2 和 Python3,要确保 pip 命令对应的是正确的 Python 版本,否则后续的创建项目脚本可能会报错。一个简单的检查方法是运行python --versionpip --version,看它们是否来自同一个 Python 安装。

接下来是设置环境变量。你需要把 Cocos2d-x 的根目录路径,以及其tools/cocos2d-console/bin目录添加到系统的 PATH 环境变量中。这样,你就可以在任意位置使用cocos这个命令行工具了。在 Windows 上,可以通过“系统属性 -> 高级 -> 环境变量”来设置;在 Mac/Linux 上,可以修改~/.bash_profile~/.zshrc文件,添加类似export PATH=/path/to/cocos2d-x/tools/cocos2d-console/bin:$PATH的语句,然后执行source ~/.zshrc使其生效。

注意:很多新手在这一步会遇到“cocos命令未找到”的问题。除了检查PATH,还要确保你关闭并重新打开了终端窗口,或者在新开的终端标签页中操作,因为环境变量的更改不会自动应用到已经打开的终端会话中。

2.2 创建“萝莉快跑”项目骨架

环境配置好后,就可以创建我们的游戏项目了。使用cocos new命令,指定项目名称、包名、使用的编程语言和引擎路径:

cocos new LoliRun -p com.yourcompany.lolirun -l cpp -d /path/to/your/projects

解释一下这几个参数:

  • LoliRun:项目名称,也是生成的文件夹名。
  • -p com.yourcompany.lolirun:包名(Package Name),遵循 Java 包名的反向域名格式,这在后续部署到移动设备时非常重要,必须是唯一的。
  • -l cpp:指定使用 C++ 语言。Cocos2d-x 也支持 Lua 和 JavaScript,但 C++ 性能最好,适合对性能要求较高的跑酷游戏。
  • -d /path/to/your/projects:指定项目创建的目录。

命令执行成功后,你会得到一个标准的 Cocos2d-x 项目结构。主要目录和作用如下:

  • Classes/:存放所有的 C++ 源代码文件,这是我们主要工作的地方。
  • Resources/:存放所有的资源文件,如图片(.png, .jpg)、音效(.mp3, .wav)、字体、配置文件等。
  • proj.android/proj.ios/:分别是 Android 和 iOS 平台的特定工程文件。
  • CMakeLists.txt:用于跨平台编译的 CMake 配置文件。

创建完项目,先别急着写代码。我建议先编译运行一下,确保基础环境没问题。进入项目根目录,对于桌面平台(如 Mac 或 Windows),可以这样编译运行:

cd LoliRun cocos run -p mac # 如果是Mac # 或 cocos run -p win32 # 如果是Windows

如果一切顺利,你应该能看到一个默认的“Hello World”场景窗口弹出来。这证明你的引擎、项目配置和编译链都是正常的。这个默认场景位于Classes/AppDelegate.cppClasses/HelloWorldScene.cpp中,我们接下来就会把它改造成我们的游戏主场景。

3. 游戏核心场景与角色构建

3.1 设计游戏主场景与基础图层

一个典型的横版跑酷游戏场景,从视觉上可以分解为几个层次:最底层是背景(可能有多层滚动背景以营造景深效果),中间是跑道和障碍物,最上层是角色、UI(分数、生命值)和特效。在 Cocos2d-x 中,我们使用SceneLayer来组织这些元素。

首先,我们重命名或替换掉默认的HelloWorldScene。我创建了一个新的GameScene类。在GameScene.h中,我定义了场景的几个核心图层:

class GameScene : public cocos2d::Scene { public: static cocos2d::Scene* createScene(); virtual bool init(); CREATE_FUNC(GameScene); private: cocos2d::Layer* _backgroundLayer; // 背景层 cocos2d::Layer* _gameLayer; // 游戏层(跑道、障碍物、角色) cocos2d::Layer* _uiLayer; // UI层 void initBackground(); // 初始化背景 void initGameElements(); // 初始化游戏元素 void initUI(); // 初始化用户界面 };

init()方法中,我们依次创建并添加这些图层。这里有一个重要的细节:图层的添加顺序决定了它们的绘制顺序(后添加的在上层)。所以_backgroundLayer最先添加,_uiLayer最后添加,确保UI永远在最前面。

背景的实现通常采用“视差滚动”技术来增强动感。我们准备两张可以无缝衔接的背景图(比如天空和远山)。在initBackground()中,我们将每张背景图创建两个精灵(Sprite),并排排列。然后在每帧的更新回调里,让这些背景以不同的速度向左移动(模拟摄像机跟随角色向右移动),当某张图完全移出屏幕左侧时,立即将其位置重置到队列的最右侧,从而实现无限循环滚动。远山的移动速度比近处的天空慢一些,就能产生景深效果。

void GameScene::initBackground() { _backgroundLayer = Layer::create(); this->addChild(_backgroundLayer); // 创建天空背景(快速滚动) auto skySprite1 = Sprite::create("background/sky.png"); auto skySprite2 = Sprite::create("background/sky.png"); skySprite1->setAnchorPoint(Vec2::ZERO); skySprite2->setAnchorPoint(Vec2::ZERO); skySprite1->setPosition(0, 0); skySprite2->setPosition(skySprite1->getContentSize().width, 0); _backgroundLayer->addChild(skySprite1); _backgroundLayer->addChild(skySprite2); _skySprites.pushBack(skySprite1); _skySprites.pushBack(skySprite2); // 类似地创建远山背景(慢速滚动) // ... }

3.2 实现“萝莉”角色与控制

角色是游戏的核心。我们需要一个Player类来封装角色的所有属性和行为。这个类继承自cocos2d::Sprite,以便于显示和进行物理碰撞检测。

首先,定义角色的状态。一个跑酷角色通常有几种状态:奔跑(IDLE/RUN)、跳跃(JUMP)、下滑(SLIDE)、受伤(HURT)和死亡(DEAD)。我们用枚举来管理:

class Player : public cocos2d::Sprite { public: enum class State { RUNNING, JUMPING, SLIDING, HURT, DEAD }; // ... 其他方法和属性 private: State _currentState; float _velocityY; // Y轴速度,用于跳跃计算 bool _isOnGround; // 是否在地面上 // ... };

角色的动画使用 Cocos2d-x 的AnimationAnimate动作来实现。我们需要为每种状态准备一组帧动画。例如,奔跑动画可能是由8张连续图片组成的。在Player的初始化方法中,我们预先创建这些动画并缓存起来,避免在游戏运行时重复创建,影响性能。

bool Player::init() { if (!Sprite::initWithFile("player/run_1.png")) { // 用第一帧初始化 return false; } // 创建奔跑动画 Vector<SpriteFrame*> runFrames; for (int i = 1; i <= 8; ++i) { auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName( StringUtils::format("run_%d.png", i)); if (frame) { runFrames.pushBack(frame); } } auto runAnimation = Animation::createWithSpriteFrames(runFrames, 0.1f); // 每帧0.1秒 AnimationCache::getInstance()->addAnimation(runAnimation, "run"); // 类似地创建跳跃、下滑动画... _currentState = State::RUNNING; this->runAction(RepeatForever::create(Animate::create(runAnimation))); return true; }

接下来是最关键的控制逻辑。我们通过监听键盘事件(桌面调试)和触摸事件(移动设备)来控制角色。对于跑酷游戏,常见的操作是:点击屏幕左侧区域跳跃,点击右侧区域下滑。我们在GameSceneinit()方法中设置触摸监听器:

auto touchListener = EventListenerTouchOneByOne::create(); touchListener->onTouchBegan = [this](Touch* touch, Event* event) -> bool { auto location = touch->getLocation(); // 假设屏幕宽度为 visibleSize.width if (location.x < visibleSize.width / 2) { // 点击左半屏,触发跳跃 _player->jump(); } else { // 点击右半屏,触发下滑 _player->slide(); } return true; }; _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

Player::jump()方法中,我们需要处理跳跃的物理逻辑。一个简单的模拟方法是给角色一个向上的初始速度,然后在每帧更新中施加一个向下的重力加速度,直到角色落回地面。

void Player::jump() { if (_currentState != State::RUNNING && _currentState != State::SLIDING) { return; // 只有在奔跑或下滑结束时才能起跳 } _currentState = State::JUMPING; _velocityY = 450.0f; // 初始跳跃速度(像素/秒) _isOnGround = false; // 切换为跳跃动画 this->stopAllActions(); this->runAction(_jumpAnimateAction); }

然后在Playerupdate(float dt)方法中(这个方法需要被定时调度),我们根据状态更新角色的位置:

void Player::update(float dt) { switch (_currentState) { case State::JUMPING: _velocityY -= GRAVITY * dt; // GRAVITY 是重力加速度,比如 980.0f this->setPositionY(this->getPositionY() + _velocityY * dt); // 检测是否落地 if (this->getPositionY() <= _groundHeight) { this->setPositionY(_groundHeight); _isOnGround = true; _currentState = State::RUNNING; // 切换回奔跑动画 this->stopAllActions(); this->runAction(_runAnimateAction); } break; // ... 处理其他状态 } }

实操心得:跳跃手感是跑酷游戏的核心。_velocityYGRAVITY的数值需要反复调试。数值太大会让跳跃感觉“飘”,太小则感觉“沉”。一个技巧是参考现实世界的物理比例,但通常需要夸张一些。可以先设定一个目标跳跃高度(比如200像素)和跳跃持续时间(比如0.6秒),然后反推出合适的初速度和重力值。另外,允许玩家在跳跃途中再次点击进行二段跳,是提升操作爽快感的常见设计,可以在jump()方法中加入对_isDoubleJumpUsed标志位的判断来实现。

4. 关卡逻辑与障碍物系统

4.1 设计可扩展的障碍物与道具

一个有趣的跑酷游戏,障碍物和道具的种类必须丰富。我们可以设计几种基础类型:

  1. 地面障碍:矮墙、钉子板。需要跳跃越过。
  2. 空中障碍:摆锤、飞鸟。需要把握时机跳跃或下滑通过。
  3. 陷阱:地刺、落穴。需要提前预判。
  4. 道具:金币(加分)、护盾(短暂无敌)、磁铁(自动吸金币)、冲刺(加速)。

为了实现这种多样性,我们最好采用面向对象的设计,定义一个基类GameObject,然后派生出ObstacleItem等子类。每个游戏对象都应该有类型标识、碰撞体积、分数价值等属性,以及spawn(),update(),collideWithPlayer()等方法。

class GameObject : public cocos2d::Node { public: enum class Type { OBSTACLE_LOW, // 低障碍 OBSTACLE_HIGH, // 高障碍 TRAP, COIN, SHIELD, MAGNET, SPEED_UP }; virtual void spawn(float startX); // 在指定X坐标生成 virtual void update(float dt); // 每帧更新(移动) virtual bool checkCollision(Player* player); // 检测与玩家碰撞 // ... protected: Type _type; cocos2d::Rect _collisionBox; int _scoreValue; };

GameScene中,我们维护一个游戏对象池(Vector<GameObject*>)。对象池是游戏开发中一个非常重要的优化技巧。与其不停地newdelete对象(这会产生内存碎片并影响性能),不如在游戏初始化时就创建一定数量的对象放入池中,需要时从池中取出并激活,不用时放回池中并隐藏。对于跑酷游戏这种对象频繁生成和消失的场景,对象池能极大提升性能。

4.2 实现无限关卡与动态难度

跑酷游戏的关卡本质上是障碍物和道具的序列。我们不可能手动设计无限长的序列,因此需要一套算法来动态生成。一个经典的方法是使用“节拍”和“模板”。

首先,我们定义游戏的基本“节拍”或“单元”。例如,每 100 像素可以看作一个单元。在每个单元上,我们可以放置一种或多种游戏对象,也可以什么都不放。我们可以预先设计一些“模板”,每个模板描述了在连续几个单元内如何放置对象。例如,一个“简单跳跃”模板可能是在第1个单元放一个矮障碍,第3个单元放一个金币。

struct SpawnTemplate { std::string name; int lengthInUnits; // 模板占几个单元 std::vector<std::pair<int, GameObject::Type>> placements; // (单元偏移量, 对象类型) int weight; // 模板被随机选中的权重 };

在游戏运行时,我们维护一个“已生成距离”和“当前关卡长度”。在每帧更新中,检查屏幕最右侧之外还有多少“预备区域”(比如200像素)。如果预备区域空了,就根据当前游戏难度(比如玩家跑了多远、当前分数)从模板库中随机选取一个模板,计算其实际像素位置,然后从对象池中取出对应的游戏对象,调用其spawn()方法放置在场景中,并将其加入活动对象列表。

动态难度可以通过调整模板库的权重来实现。游戏开始时,“简单”模板的权重大,“复杂”模板权重小。随着玩家跑动距离增加,我们逐渐降低简单模板的权重,提高复杂模板的权重,甚至引入新的、更难的模板。同时,生成对象的间隔(即单元长度)也可以逐渐缩短,让障碍物出现得更密集。

void GameScene::spawnNextSegment() { float currentDifficulty = calculateDifficulty(); // 根据距离、分数计算难度系数 SpawnTemplate* chosenTemplate = chooseTemplateByWeight(currentDifficulty); float spawnStartX = _lastSpawnX; for (auto& placement : chosenTemplate->placements) { float objX = spawnStartX + placement.first * UNIT_LENGTH; GameObject* obj = _objectPool.getObject(placement.second); // 从对象池获取 if (obj) { obj->spawn(objX); _activeObjects.pushBack(obj); _gameLayer->addChild(obj); } } _lastSpawnX += chosenTemplate->lengthInUnits * UNIT_LENGTH; }

注意事项:动态生成的关卡一定要保证“可玩性”。这意味着生成的障碍物序列必须给玩家合理的反应时间和通过路径。不能生成“死局”,比如两个必须跳跃的障碍物之间距离小于角色跳跃跨度,导致无论如何都会撞上。在模板设计时就要考虑这一点,或者可以在生成后增加一个“可行性校验”步骤,对极端不合理的组合进行微调。

5. 碰撞检测、分数与游戏状态管理

5.1 高效的碰撞检测实现

碰撞检测是游戏逻辑的基石。对于2D横版游戏,我们通常使用轴对齐包围盒(AABB)进行粗略检测,因为它计算速度快。Cocos2d-x 的Rect类提供了intersectsRect()方法可以方便地判断两个矩形是否相交。

我们在GameObject基类中定义了一个_collisionBox成员变量,它表示对象相对于其自身锚点的碰撞区域。在Player类中也定义一个碰撞框。每一帧,在GameScene的更新函数中,我们遍历所有活动的游戏对象,计算它们在世界坐标系下的碰撞框,并与玩家的碰撞框进行检测。

void GameScene::checkCollisions(float dt) { auto playerBox = _player->getCollisionBox(); // 将玩家碰撞框转换到世界坐标系 playerBox.origin += _player->getPosition(); for (auto obj : _activeObjects) { auto objBox = obj->getCollisionBox(); objBox.origin += obj->getPosition(); if (playerBox.intersectsRect(objBox)) { // 发生碰撞 handleCollision(_player, obj); // 通常碰撞后该对象应被回收 recycleObject(obj); break; // 一帧内通常只处理一次碰撞 } } }

但是,逐帧遍历所有对象进行两两检测,当对象数量很多时(比如上百个金币),性能会成为问题。这里就需要引入空间划分优化。对于横版跑酷这种对象基本沿X轴线性分布的游戏,一个简单有效的优化是使用“标记-清理”法。我们给每个游戏对象增加一个_isActive标记。在更新对象位置时,如果发现对象的X坐标已经远小于屏幕左边界(即完全移出屏幕),就将其标记为不活跃,并从活动列表移到待回收列表。这样,碰撞检测只需要遍历活动列表,而这个列表的长度会被控制在屏幕内对象数量的规模,通常不会很大。

另一个优化是针对不同碰撞类型的分层检测。比如,玩家与金币的碰撞(收集)和与障碍物的碰撞(受伤)可以分开处理。我们可以先快速检测可能与玩家发生“危险碰撞”的对象(如障碍物、陷阱),这些对象数量较少。如果没发生危险碰撞,再检测“收集类”对象(如金币)。这可以通过给对象设置碰撞层级(Collision Layer)来实现。

5.2 构建完整的游戏循环与UI反馈

游戏需要清晰的状态管理:开始前(READY)、进行中(PLAYING)、暂停(PAUSED)、结束(GAME_OVER)。我们在GameScene中用一个枚举变量_gameState来记录当前状态。状态的切换会触发一系列动作,比如从 READY 进入 PLAYING 时,开始滚动背景、开始生成障碍物、启动分数计时等。

分数系统是驱动玩家不断挑战的核心。我们的分数可以由几部分组成:

  • 距离分:每跑过一定像素(如10像素)加1分。这通过一个定时器或根据背景滚动速度来累加。
  • 金币分:每收集一个金币加固定分数(如100分)。
  • 连击分/表现分:连续收集金币而不碰到障碍,可以获得递增的额外奖励分数。

分数需要实时显示在屏幕上。我们在_uiLayer上添加一个Label对象来显示分数。为了性能,不要每帧都更新 Label 的字符串(特别是分数很高时,数字转换有开销)。可以设置一个分数变量_currentScore,一个显示分数变量_displayScore。在每帧更新中,让_displayScore以一定的速度向_currentScore平滑过渡(插值),然后每隔几帧或当分数变化较大时再更新 Label 的文本。

void GameScene::updateScore(float dt) { // 增加距离分 _currentScore += _scrollSpeed * dt * SCORE_PER_PIXEL; // 平滑显示分数 _displayScore = lerp(_displayScore, _currentScore, 0.1f); // lerp是线性插值函数 if (fabs(_displayScore - _lastDisplayScore) > 10) { // 分数变化超过10再更新文本 _scoreLabel->setString(StringUtils::format("Score: %d", (int)_displayScore)); _lastDisplayScore = _displayScore; } }

生命值(或护盾)系统增加了游戏的容错率和策略性。我们可以给玩家设定初始生命值为3。每次与障碍物发生碰撞,生命值减1。当生命值为0时,游戏结束。如果玩家获得了“护盾”道具,则进入短暂的无敌状态,期间碰撞不会扣减生命值。无敌状态可以用一个定时器和一个视觉特效(比如角色半透明闪烁)来表现。

游戏结束的逻辑需要处理好。当玩家生命值为0或掉入深渊时,触发gameOver()方法。这个方法里需要做几件事:

  1. 将游戏状态改为 GAME_OVER。
  2. 停止所有背景滚动、对象生成和自动分数增加。
  3. 播放角色死亡动画和音效。
  4. 弹出游戏结束界面,显示本次最终分数、历史最高分,并提供“重新开始”和“返回主菜单”按钮。

重新开始游戏时,切忌简单地重新加载整个场景,因为这样会有黑屏延迟。更好的做法是重置所有游戏数据:将玩家位置、生命值、分数归零;回收所有活跃的游戏对象回对象池;重置关卡生成器的种子或位置;然后将游戏状态切回 READY 或直接切回 PLAYING。这样体验会更加流畅。

6. 性能优化与发布准备

6.1 渲染与内存优化实战

当游戏内容逐渐丰富,特效和对象增多时,性能问题就会浮现。Cocos2d-x 提供了强大的性能分析工具Profiler,在调试模式下,可以通过Director::getInstance()->setDisplayStats(true);在屏幕角落显示帧率(FPS)、绘制调用次数(Draw Calls)等信息。Draw Calls 是衡量渲染性能的关键指标,次数越少越好。

优化 Draw Calls 的核心是“合批”(Batching)。对于大量静态或变化不频繁的精灵(比如背景、远处的装饰物),如果它们使用同一张纹理图集(Texture Atlas),Cocos2d-x 的渲染器会自动将它们合并到一个 Draw Call 中。因此,将游戏中的小图片打包成图集是必须的步骤。可以使用 TexturePacker 等工具来打包,生成一个.plist文件(描述信息)和一个.png文件(合并后的图片)。在游戏启动时,用SpriteFrameCache::getInstance()->addSpriteFramesWithFile("game.plist");加载图集,之后创建精灵时使用帧名而非单独的文件名。

// 优化前:每个精灵单独加载文件,产生多个Draw Call auto sprite1 = Sprite::create("coin1.png"); auto sprite2 = Sprite::create("coin2.png"); // 优化后:使用图集,多个精灵可能共享一个Draw Call SpriteFrameCache::getInstance()->addSpriteFramesWithFile("game-assets.plist"); auto sprite1 = Sprite::createWithSpriteFrameName("coin_01.png"); auto sprite2 = Sprite::createWithSpriteFrameName("coin_02.png");

对象池我们已经提过了,它对性能提升至关重要。特别是对于频繁创建销毁的对象,如子弹、特效、金币。另一个内存优化点是纹理的延迟加载和及时卸载。不要在游戏一开始就加载所有资源,而是根据场景需要加载。当切换场景(比如从游戏场景回到主菜单)时,要记得卸载不再使用的纹理和精灵帧,调用TextureCache::getInstance()->removeUnusedTextures();SpriteFrameCache::getInstance()->removeUnusedSpriteFrames();

对于移动设备,还要注意电量消耗。减少不必要的每帧逻辑。如果某个节点暂时不需要更新,可以调用node->unscheduleUpdate();来停止它的更新回调。对于远离屏幕、对游戏没有影响的对象(比如已经滚到屏幕左边很远的背景板),可以直接将其设置为不可见setVisible(false),这样渲染器会跳过它。

6.2 多平台适配与打包发布

Cocos2d-x 最大的优势之一就是跨平台。我们的代码在桌面(Windows/Mac)上调试完成后,需要编译到真机(iOS/Android)上进行测试和发布。

对于 Android 平台,进入项目的proj.android目录,使用 Gradle 进行编译。首先确保你的系统已经配置好了 Android SDK 和 NDK(版本需要与proj.android/app/build.cfggradle.properties中的配置匹配)。然后在项目根目录或proj.android目录下执行:

cocos compile -p android --android-studio

--android-studio参数会生成一个可用于 Android Studio 打开的工程,方便进行更详细的配置和调试。编译成功后,会在proj.android/app/build/outputs/apk/目录下生成 APK 文件。在将 APK 安装到真机前,需要处理签名问题。调试版本可以使用调试密钥,发布版本则必须使用你自己生成的密钥库进行签名。

对于 iOS 平台,进入proj.ios目录,你会看到一个.xcodeproj文件。用 Xcode 打开它。你需要有一个有效的 Apple 开发者账号,并在 Xcode 中设置好你的团队(Team)和 Bundle Identifier。在真机上运行前,需要在苹果开发者网站为你的设备创建配置文件(Provisioning Profile)。相比 Android,iOS 的打包流程对证书和配置的依赖更强,需要仔细按照苹果官方的步骤进行。

避坑指南:跨平台开发时,要特别注意文件路径和大小写问题。Windows 不区分大小写,而 Linux 和 Mac 是区分的。所有资源文件的引用,在代码中最好使用统一的小写,并且使用相对路径。另外,移动设备的屏幕尺寸和长宽比千差万别,我们的游戏需要做多分辨率适配。不要在代码中写死位置坐标(如setPosition(400, 300)),而应该使用相对坐标。Cocos2d-x 提供了Director::getInstance()->getVisibleSize()getVisibleOrigin()来获取可视区域的大小和起点。所有 UI 元素和游戏布局都应该基于这两个值来计算位置,例如将角色放在屏幕水平中央、底部向上 20% 的位置:player->setPosition(visibleSize.width/2, visibleSize.height*0.2)。对于背景图等资源,可能需要准备不同分辨率的版本(如 sd, hd, xhd),并通过FileUtils的搜索路径机制来自动加载合适尺寸的图片。

返回列表