热血传奇登录界面那道城门是从 ChrSel.wzl / ChrSel.wzx 里抽出来的序列帧(背景索引22,开门帧索引24~32,共9帧,原图约495×361),原理就是 Cocos2d-x 的 SpriteFrameCache 加载图集 plist + Animation::createWithSpriteFrames 组帧 + Animate 驱动 Sprite 播放,锚点锁左下角贴回原坐标,点击或延时触发一次不循环开门动作 。下面按 3.x API 写可直接抄进 HelloWorldScene 的完整链路。
一、素材准备与透明处理
1. 用 WzlWzxExtractor / 传奇资源浏览器 从 ChrSel.wzx 抽出 24~32 帧 PNG;
2. 传奇里 RGB(0,0,0) 代表透明,抽图后最右一列常留黑线,PS 删掉最右 1px 再存,避免播动画时竖黑边 ;
3. 第一帧门几乎闭合,可裁掉做 8 帧,TexturePacker 打图集 door.plist + door.png,帧名 door_00.png ~ door_07.png;
4. 资源扔 Resources 目录,Android 放 assets,Win32 放调试目录根。
二、场景初始化与背景铺设
bool HelloWorld::init()
{
if (!Scene::init()) return false;
auto visibleSize = Director::getInstance()->getVisibleSize();
auto origin = Director::getInstance()->getVisibleOrigin();
// 登录背景
auto bg = Sprite::create("background.png");
bg->setPosition(Vec2(visibleSize.width / 2 + origin.x,
visibleSize.height / 2 + origin.y));
this->addChild(bg, 0);
// 预加载门图集
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("door.plist");
return true;
}
三、核心开门方法(锚点+坐标还原)
传奇门帧原点在图片左下角,必须 setAnchorPoint(Vec2(0,0)),位置按原界面贴(示例 153,143,按自己背景尺寸换算):
void HelloWorld::playOpenDoor()
{
static Sprite* door = nullptr;
if (door == nullptr)
{
door = Sprite::create();
door->setAnchorPoint(Vec2(0, 0)); // 左下角锚点,对齐传奇原图基点
door->setPosition(Vec2(153, 143)); // 门在背景上的绝对位置
this->addChild(door, 1);
}
Animation* animation = AnimationCache::getInstance()->getAnimation("openDoor");
if (animation == nullptr)
{
const int frameCount = 8; // 24~31 共8帧可用
Vector<SpriteFrame*> frames;
auto* cache = SpriteFrameCache::getInstance();
for (int i = 0; i < frameCount; ++i)
{
std::string key = StringUtils::format("door_%02d.png", i);
SpriteFrame* f = cache->getSpriteFrameByName(key);
if (f) frames.pushBack(f);
}
animation = Animation::createWithSpriteFrames(frames, 1.5f / frames.size());
animation->setRestoreOriginalFrame(false); // 播完停最后一帧(门保持开)
animation->setLoops(1);
AnimationCache::getInstance()->addAnimation(animation, "openDoor");
}
door->stopAllActions();
door->runAction(Animate::create(animation));
}
delayPerUnit 用 1.5f / 帧数 ≈ 0.1875s/帧,总时长 1.5s,贴近官服厚重推门节奏;setRestoreOriginalFrame(false) 让门开完不回弹 。
四、触发方式(鼠标点击 / 延时自动)
点击触发:
auto mouseListener = EventListenerMouse::create();
mouseListener->onMouseDown = [&](EventMouse* e) { this->playOpenDoor(); };
_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);
进场景自动开:
this->runAction(Sequence::create(DelayTime::create(0.6f),
CallFunc::create([&](){ playOpenDoor(); }), nullptr));
五、2.x 旧版写法对照(CC 前缀)
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("door.plist");
CCArray* arr = CCArray::create();
for (int i=0;i<8;i++){
const char* n = CCString::createWithFormat("door_%02d.png",i)->getCString();
arr->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(n));
}
CCAnimation* anim = CCAnimation::createWithSpriteFrames(arr, 1.5f/8);
anim->setRestoreOriginalFrame(false);
doorSprite->runAction(CCAnimate::create(anim));
3.x 把 CCArray 换 Vector<SpriteFrame*>,sharedXXX 换 getInstance,其余一致 。
六、常见歪点排查
• 门播出来位置偏:锚点没设(0,0),传奇帧基点左下,默认(0.5,0.5)必错位;
- 黑竖线:抽图没删 RGB(0,0,0) 最右列,plist 里 trim 没开;
• 只闪一帧:frames 为空,plist 帧名和代码 format 不一致;
• 门播完弹回闭合:setRestoreOriginalFrame 没设 false;
- 想循环演示:setLoops(-1) 或 RepeatForever::create(Animate::create(anim));
• 想关门反向:把 frames 反转再建 Animation,或 Sequence::create(openAnim, openAnim->reverse(), nullptr) ;
- 大图内存吃紧:TexturePacker 压 8bit、裁冗余像素、首帧去重,495×361 原图能砍掉近三成 。
七、扩成游戏内城门/机关门
把 door 坐标改成地图物体世界坐标,绑定碰撞盒;角色走到触发距离 → playOpenDoor();门动画播完设物理 body 失效让角色穿过;关门用 reverse 动画 + 重置碰撞。序列帧机制不变,只换触发源和坐标空间。
整套就是 SpriteFrameCache 解 plist → 取帧组 Animation → Animate 绑 Sprite → 锚点左下还原传奇基点 → 单次非循环播放,黑线透明和首帧去重是抽 ChrSel 资源时必做的两步。
Cocos2d-x精灵帧动画还原热血传奇登录城门开启效果完整实现
来源:
作者:
点击:

