学习cocos2dx ver3.3

2015年03月18日 10:17 0 点赞 0 评论 更新于 2025-11-21 13:48

今天我们来学习 Cocos2d-x ver3.3 的 Sprite3DTest 示例。虽然与 Unity3D 相比,Cocos2d-x 对 3D 的支持只是皮毛,但它上手更容易,更适合像我这样没接触过 3D 开发的新手。如果 Cocos2d-x 能在性能和稳定性上进一步提升,对于那些习惯使用 Cocos2d-x 且有 3D 开发需求的老玩家来说,会是一个不错的选择。

下面我们来详细分析这个示例中可以学到的内容:

1. Sprite3D 基本创建方式

1.1 直接使用带有素材的 obj 文件(3DMax 对象)

Sprite3D::create("sprite3dTest/scene01.obj");

1.2 使用不带素材的对象,自行设置素材

auto sprite = Sprite3D::create("Sprite3DTest/boss1.obj");
sprite->setScale(3.f);
sprite->setTexture("Sprite3DTest/boss.png");

1.3 使用 EffectSprite3D 从 obj 文件和纹理创建

auto sprite = EffectSprite3D::createFromObjFileAndTexture("Sprite3DTest/boss1.obj", "Sprite3DTest/boss.png");

2. Sprite3D 拖拽操作

2.1 点击操作

onTouchBegan 中设置目标的透明度:

target->setOpacity(100);

onTouchEnded 中还原目标的透明度:

target->setOpacity(255);

2.2 移动操作

onTouchMoved 中更新目标的位置:

target->setPosition(target->getPosition() + touch->getDelta());

2.3 事件监听优先级

使用 addEventListenerWithSceneGraphPriority 根据 Sprite 的 Z 轴顺序决定事件调用先后。

3. Sprite3D 特效测试:Shader 的运用

将具体实现封装到 Effect3DOutline 中,用于展示一些 Shader 的运用示例。

4. 加载 3DMax 导出的文件

使用 fbx-conv.exe 进行文件转换,示例代码如下:

std::string fileName = "Sprite3DTest/orc.c3b";
auto sprite = EffectSprite3D::create(fileName);

5. Sprite3D 皮肤轮廓测试:Shader 特效在 c3b 文件中的运用

该部分主要展示了 Shader 特效在 c3b 格式文件中的应用。

6. 动画播放和切换:小乌龟示例

6.1 创建 Sprite 和 Action

同一个 c3b 文件既可以创建 Sprite 也可以创建 Action:

std::string fileName = "Sprite3DTest/tortoise.c3b";
auto sprite = Sprite3D::create(fileName);
sprite->setScale(0.1f);
auto s = Director::getInstance()->getWinSize();
sprite->setPosition(Vec2(s.width * 4.f / 5.f, s.height / 2.f));
addChild(sprite);
_sprite = sprite;
auto animation = Animation3D::create(fileName);

6.2 动画拆分

Animation 可以根据在 3DMax 中定义的时间进行拆分:

if (animation) {
auto animate = Animate3D::create(animation, 0.f, 1.933f);
_swim = RepeatForever::create(animate);
sprite->runAction(_swim);
_swim->retain();
_hurt = Animate3D::create(animation, 1.933f, 2.8f);
_hurt->retain();
_state = State::SWIMMING;
}

6.3 反向动作和转向

当乌龟游到尽头时,调用 reachEndCallBack,通过 reverse 获得相反的动作,并沿 Y 轴 180 度转向:

auto inverse = (MoveTo*)_moveAction->reverse();
auto rot = RotateBy::create(1.f, Vec3(0.f, 180.f, 0.f));

6.4 播放受伤动作

onTouchesEnded 中播放乌龟的受伤动作,由于无法实现受伤动作的结束回调,采用 _hurt->getDuration() 获得结束时间,并通过 renewCallBack 回到游泳动作:

auto delay = DelayTime::create(_hurt->getDuration() - Animate3D::getTransitionTime());
auto seq = Sequence::create(delay, CallFunc::create(CC_CALLBACK_0(Animate3DTest::renewCallBack, this)), nullptr);

这里有两个疑问:Animate3D::getTransitionTime() 的作用是什么?为什么使用 void Animate3DTest::update(float dt) 来维护中间状态 HURT_TO_SWIMMINGSWIMMING_TO_HURT

7. 附件测试:装备武器

获取某个骨骼(如在 3DMax 中定义的 Bip001 R Hand),并添加武器:

auto sp = Sprite3D::create("Sprite3DTest/axe.c3b");
sprite->getAttachNode("Bip001 R Hand")->addChild(sp);

点击后去掉附加节点:

_sprite->removeAllAttachNode();

8. 皮肤换装测试:动态更换材质

8.1 初始化标签

TTFConfig ttfConfig("fonts/arial.ttf", 20);
auto label1 = Label::createWithTTF(ttfConfig, "Hair");

8.2 定义材质名称

_girlPants[0] = "Girl_LowerBody01";
_girlPants[1] = "Girl_LowerBody02";
_girlUpperBody[0] = "Girl_UpperBody01";
_girlUpperBody[1] = "Girl_UpperBody02";
_girlShoes[0] = "Girl_Shoes01";
_girlShoes[1] = "Girl_Shoes02";
_girlHair[0] = "Girl_Hair01";
_girlHair[1] = "Girl_Hair02";

8.3 隐藏备选部分

初始化时隐藏备选的部分,点击切换时再交换显示:

auto girlPants = sprite->getMeshByName(_girlPants[1]);
if (girlPants) {
girlPants->setVisible(false);
}

9. 包围盒与 3D 模型碰撞的实现

9.1 使用 decltype 获取 auto 类型

auto obbSize = _obb.size();
for (decltype(obbSize) i = 0; i < obbSize; i++)

9.2 触摸点转换到 3D 场景

使用 Ray 将触摸点转换到 3D 场景中,判断是否点击到目标:

Ray ray;
calculateRayByLocationInView(&ray, location);

疑问:Ray 的射出方向是否会沿着相机的正中心发射?

9.3 绘制 3D 边框

#include "DrawNode3D.h"
DrawNode3D* _drawDebug;
_drawDebug->clear();
Mat4 mat = _sprite->getNodeToWorldTransform();
mat.getRightVector(&_obbt._xAxis);
_obbt._xAxis.normalize();
mat.getUpVector(&_obbt._yAxis);
_obbt._yAxis.normalize();
mat.getForwardVector(&_obbt._zAxis);
_obbt._zAxis.normalize();
_obbt._center = _sprite->getPosition3D();
Vec3 corners[8] = {};
_obbt.getCorners(corners);
_drawDebug->drawCube(corners, Color4F(0, 1, 1, 1));

9.4 独立的 OBB

OBB 可以不依赖 Sprite 独立存在:

void Sprite3DWithOBBPerfromanceTest::addNewOBBWithCoords(Vec2 p) {
Vec3 extents = Vec3(10, 10, 10);
AABB aabb(-extents, extents);
auto obb = OBB(aabb);
obb._center = Vec3(p.x, p.y, 0);
_obb.push_back(obb);
}

9.5 碰撞判断

碰撞判断非常简单,只需一行代码:

_obbt.intersects(_obb);

10. 3D 模型的镜像

sprite->setScaleX(-5); // 在素材上转换
sprite->setCullFace(GL_FRONT); // 在 mesh 上转换

11. 相机测试示例

11.1 相机添加和观察条件

相机可以被添加到任何地方,如果一个 SpriteLayer 想要被相机观察到,必须包含其标志(效果也会作用到其子节点):

auto sprite = Sprite::create("myFile.png");
sprite->setCameraMask(CameraFlag::USER1);
auto camera = Camera::createPerspective(60, winSize.width / winSize.height, 1, 1000);
camera->setCameraFlag(CameraFlag::USER1);
scene->addChild(camera);

11.2 相机缩放

在点击标签时设置标志位 _bZoomOut_bZoomIn,重新设置相机的位置即可实现缩放:

Vec3 lookDir = _camera->getPosition3D() - _sprite3D->getPosition3D();
Vec3 cameraPos = _camera->getPosition3D();
if (lookDir.length() <= 300) {
cameraPos += lookDir.getNormalized();
_camera->setPosition3D(cameraPos);
}

11.3 相机左右旋转

实际上就是沿着 Y 轴左右旋转:

Vec3 rotation3D = _camera->getRotation3D();
rotation3D.y += 1;
_camera->setRotation3D(rotation3D);

11.4 视角切换

使用 _cameraType 记录当前相机类型,包括第一人称和第三人称视角:

enum class CameraType {
FreeCamera = 0,
FirstCamera = 1,
ThirdCamera = 2
};
// 第一人称视角
Vec3 newFaceDir;
_sprite3D->getWorldToNodeTransform().getForwardVector(&newFaceDir);
newFaceDir.normalize();
_camera->setPosition3D(Vec3(0, 35, 0) + _sprite3D->getPosition3D());
_camera->lookAt(_sprite3D->getPosition3D() + newFaceDir * 50, Vec3(0, 1, 0));
// 第三人称视角
_camera->setPosition3D(Vec3(0, 130, 130) + _sprite3D->getPosition3D());
_camera->lookAt(_sprite3D->getPosition3D(), Vec3(0, 1, 0));

11.5 相机跟随角色移动

每一帧都需要更新相机的状态:

void Camera3DTestDemo::updateCamera(float fDelta) {
// 先计算出角色需要移动多少
Vec3 newFaceDir = _targetPos - curPos;
newFaceDir.y = 0.0f;
newFaceDir.normalize();
Vec3 offset = newFaceDir * 25.0f * elapsedTime;
// 相机随之移动
Vec3 cameraPos = _camera->getPosition3D();
cameraPos.x += offset.x;
cameraPos.z += offset.z;
_camera->setPosition3D(cameraPos);
}

常用总结

  1. 3D 纬度的朝向设置sprite->setRotation3D(Vec3(0, 180, 0));
  2. 添加特效sprite->setEffect();
  3. 获取某个骨骼sprite->getAttachNode("Bip001 R Hand");
  4. 获取材质sprite->getMeshByName(_girlPants[1]);
  5. 获取向量的标准化形式Vec2 getNormalized() const;Vec3 Vec3::getNormalized() const;,若为零向量,返回 (0, 0),也可以理解为获取一个向量的单位向量。

作者信息

feifeila

feifeila

共发布了 3994 篇文章