Creator3.x Spine局部换装支持使用图集

对于Spine局部换装图集的支持,论坛里面的帖子大多都是关于Creator2.x的。方案上也是基于Creator2.x。Creator3.x Spine的局部换装只暴露了一个接口就是setSlotTexture 。无法直接使用论坛里面基于Creator2.x的方案。

我目前使用的方案是基于Creator2.x的方案修改而来,需要修改到原生引擎代码和Spine-Wasm的代码。

Creator2.x的方案:spine换装实战总结(基于2.4.7)| 社区征文

最终实现的局部换装代码示例

let texture = spriteFrame.texture as Texture2D;
let rect = spriteFrame.rect;
let originSize = spriteFrame.originalSize;
let originOffset = spriteFrame.offset;
let offset = new Vec2((originSize.width - rect.width) * 0.5 + originOffset.x, (originSize.height - rect.height) * 0.5 + originOffset.y);
let degrees = spriteFrame.rotated ? 270 : 0;
let texSize = new Size(texture.width, texture.height);

skeleton.setSlotTexture2(slotName, texture, texSize, rect, originSize, offset, degrees, true);

一、修改原生引擎换装实现。

我这里使用的是新增一个接口,新增了使用图集换装的接口

void setSlotTexture2(const std::string &slotName, cc::Texture2D *tex2d, cc::Size texSize, cc::Rect rect, cc::Size origSize, cc::Vec2 offset, uint32_t degrees, bool createAttachment);

核心代码和Creator2.x的方案基本一致

void SkeletonRenderer::setSlotTexture2(const std::string &slotName, cc::Texture2D *tex2d, cc::Size texSize, cc::Rect rect, cc::Size origSize, cc::Vec2 offset, uint32_t degrees, bool createAttachment) {
    if (!_skeleton) return;
    auto slot = _skeleton->findSlot(slotName.c_str());
    if (!slot) return;
    auto attachment = slot->getAttachment();
    if (!attachment) return;
    float u, v, u2, v2;
    float width = rect.width;
    float height = rect.height;
    float texW = texSize.width;
    float texH = texSize.height;
    if (degrees != 0) {
        u = rect.x / texW;
        v = rect.y / texH;
        u2 = (rect.x + height) / texW;
        v2 = (rect.y + width) / texH;
    } else {
        u = rect.x / texW;
        v = rect.y / texH;
        u2 = (rect.x + width) / texW;
        v2 = (rect.y + height) / texH;
    }

    if (createAttachment) {
        attachment = attachment->copy();
        slot->setAttachment(attachment);

        _skeleton->recordAttachment(slotName.c_str(), attachment);
    }
    AttachmentVertices *attachmentVertices = nullptr;
    if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) {
        auto region = static_cast<RegionAttachment *>(attachment);
        region->setRegionWidth(width);
        region->setRegionHeight(height);
        region->setRegionOriginalWidth(origSize.width);
        region->setRegionOriginalHeight(origSize.height);
        // region->setWidth(width);
        // region->setHeight(height);
        region->setRegionOffsetX(offset.x);
        region->setRegionOffsetY(offset.y);
        region->setUVs(u, v, u2, v2, degrees != 0);
        region->updateOffset();
        attachmentVertices = static_cast<AttachmentVertices *>(region->getRendererObject());
        if (createAttachment) {
            attachmentVertices = attachmentVertices->copy();
            region->setRendererObject(attachmentVertices);
        }
        V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts;
        const auto &UVs = region->getUVs();
        for (int i = 0, ii = 0; i < 4; ++i, ii += 2) {
            vertices[i].texCoord.u = UVs[ii];
            vertices[i].texCoord.v = UVs[ii + 1];
        }
    } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) {
        auto mesh = static_cast<MeshAttachment *>(attachment);
        mesh->setRegionWidth(width);
        mesh->setRegionHeight(height);
        mesh->setRegionOriginalWidth(origSize.width);
        mesh->setRegionOriginalHeight(origSize.height);
        mesh->setRegionOffsetX(offset.x);
        mesh->setRegionOffsetY(offset.y);
        // mesh->setWidth(width);
        // mesh->setHeight(height);
        mesh->setRegionU(u);
        mesh->setRegionV(v);
        mesh->setRegionU2(u2);
        mesh->setRegionV2(v2);
        mesh->setRegionRotate(degrees != 0);
        mesh->setRegionDegrees(degrees);
        mesh->updateUVs();
        attachmentVertices = static_cast<AttachmentVertices *>(mesh->getRendererObject());
        if (createAttachment) {
            attachmentVertices = attachmentVertices->copy();
            mesh->setRendererObject(attachmentVertices);
        }
        V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts;
        const auto &UVs = mesh->getUVs();
        for (size_t i = 0, ii = 0, nn = mesh->getWorldVerticesLength(); ii < nn; ++i, ii += 2) {
            vertices[i].texCoord.u = UVs[ii];
            vertices[i].texCoord.v = UVs[ii + 1];
        }
    }
    // if (!attachmentVertices) return;
    middleware::Texture2D *middlewareTexture = nullptr;
    for (auto &it : _slotTextureSet) {
        if (it->getRealTexture() == tex2d) {
            middlewareTexture = it;
            break;
        }
    }
    if (!middlewareTexture) {
        middlewareTexture = new middleware::Texture2D();
        middlewareTexture->addRef();
        middlewareTexture->setRealTexture(tex2d);
    }
    if (attachmentVertices->_texture) {
        attachmentVertices->_texture->release();
    }
    attachmentVertices->_texture = middlewareTexture;
}

根据Creator2.x 还需修改 MeshAttachment.cpp中一处关于旋转的修复

// MeshAttachment.cpp中的updateUVs方法
case 270: {
    float textureWidth = _regionHeight / (_regionU2 - _regionU);
    float textureHeight = _regionWidth / (_regionV2 - _regionV);
    u -= _regionOffsetY / textureWidth;
    v -= _regionOffsetX / textureHeight;
    width = _regionOriginalHeight / textureWidth;
    height = _regionOriginalWidth / textureHeight;
    for (i = 0; i < n; i += 2) {
        _uvs[i] = u + (1 - _regionUVs[i + 1]) * width;
        _uvs[i + 1] = v + _regionUVs[i] * height;
    }
    return;
}

二、修改js/TS增加新的换装接口。

  • 修改platforms/native/engine/jsb-spine-skeleton.js
// 增加setSlotTexture2方法
skeleton.setSlotTexture2 = function (slotName, tex2d, texSize, rect, originSize, offset, degrees, createNew) {
    if (this.isAnimationCached()) {
        console.error(`Cached mode can't change texture of slot`);
        return;
    }
    if (!this._nativeSkeleton) return;
    const slot = this.findSlot(slotName);
    if (!slot) {
        console.error(`No slot named:${slotName}`);
        return;
    }
    const createNewAttachment = createNew || false;
    this._nativeSkeleton.setSlotTexture2(slotName, tex2d, texSize, rect, originSize, offset, degrees, createNewAttachment);
};
  • 修改cc.d.ts
setSlotTexture2(slotName: string, tex2d: Texture2D, texSize: math.Size, rect: math.Rect, originSize: math.Size, offset: math.Vec2, degrees: number, createNew: boolean): void;

二、修改Spine的WASM实现支持图集换装

  • 修改native/cocos/editor-support/spine-wasm/spine-skeleton-instance新增以下方法
void resizeSlotRegion2(const spine::String &slotName, float width, float height, float rectWidth, float rectHeight, float rectX, float rectY, float originWidth, float originHeight, float offsetX, float offsetY, uint32_t degrees, bool createNew = false);

具体实现代码

void SpineSkeletonInstance::resizeSlotRegion(const spine::String &slotName, uint32_t width, uint32_t height, bool createNew) {
    if (!_skeleton) return;
    auto* slot = _skeleton->findSlot(slotName);
    if (!slot) return;
    auto*attachment = slot->getAttachment();
    if (!attachment) return;
    if (createNew) {
        attachment = attachment->copy();
        slot->setAttachment(attachment);
    }
    if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) {
        auto *region = static_cast<RegionAttachment *>(attachment);
        region->setRegionWidth(width);
        region->setRegionHeight(height);
        region->setRegionOriginalWidth(width);
        region->setRegionOriginalHeight(height);
        region->setWidth(width);
        region->setHeight(height);
        region->setUVs(0, 0, 1.0f, 1.0f, false);
        region->updateOffset();
        auto *attachmentVertices = static_cast<AttachmentVertices *>(region->getRendererObject());
        if (createNew) {
            attachmentVertices = attachmentVertices->copy();
            region->setRendererObject(attachmentVertices);
        }
        V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts;
        const auto &UVs = region->getUVs();
        for (int i = 0, ii = 0; i < 4; ++i, ii += 2) {
            vertices[i].texCoord.u = UVs[ii];
            vertices[i].texCoord.v = UVs[ii + 1];
        }
    } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) {
        auto *mesh = static_cast<MeshAttachment *>(attachment);
        mesh->setRegionWidth(width);
        mesh->setRegionHeight(height);
        mesh->setRegionOriginalWidth(width);
        mesh->setRegionOriginalHeight(height);
        mesh->setWidth(width);
        mesh->setHeight(height);
        mesh->setRegionU(0);
        mesh->setRegionV(0);
        mesh->setRegionU2(1.0f);
        mesh->setRegionV2(1.0f);
        mesh->setRegionRotate(true);
        mesh->setRegionDegrees(0);
        mesh->updateUVs();
        auto *attachmentVertices = static_cast<AttachmentVertices *>(mesh->getRendererObject());
        if (createNew) {
            attachmentVertices = attachmentVertices->copy();
            mesh->setRendererObject(attachmentVertices);
        }
        V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts;
        const auto &UVs = mesh->getUVs();
        for (size_t i = 0, ii = 0, nn = mesh->getWorldVerticesLength(); ii < nn; ++i, ii += 2) {
            vertices[i].texCoord.u = UVs[ii];
            vertices[i].texCoord.v = UVs[ii + 1];
        }
    }
}

  • 修改native/cocos/editor-support/spine-wasm/spine-type-export增加导出
.function("resizeSlotRegion", &SpineSkeletonInstance::resizeSlotRegion)
.function("resizeSlotRegion2", &SpineSkeletonInstance::resizeSlotRegion2)
  • 重新编译spine-wasm

这个网上有相关帖子,论坛里面也有,不行也可以问AI

编译spine wasm

三、修改Spine的TS层支持图集换装

  • 修改cocos/spine/skeleton.ts
public setSlotTexture2(slotName: string, tex2d: Texture2D, texSize: Size, rect: Rect, originSize: Size, offset:Vec2, degrees: number, createNew: boolean): void {
    if (this.isAnimationCached()) {
        error(`Cached mode can't change texture of slot`);
        return;
    }
    const slot = this.findSlot(slotName);
    if (!slot) {
        error(`No slot named:${slotName}`);
        return;
    }
    
    const createNewAttachment = createNew || false;
    this._instance!.resizeSlotRegion2(slotName, texSize.width, texSize.height, rect.width, rect.height, rect.x, rect.y, originSize.width, originSize.height, offset.x, offset.y, degrees, createNewAttachment);
    if (!this._slotTextures) this._slotTextures = new Map<number, Texture2D>();
    let textureID = 0;
    this._slotTextures.forEach((value, key) => {
        if (value === tex2d) textureID = key;
    });
    if (textureID === 0) {
        textureID = ++_slotTextureID;
        this._slotTextures.set(textureID, tex2d);
    }
    this._instance!.setSlotTexture(slotName, textureID);
}
  • 修改cocos/spine/lib/skeleton-core.d.ts
resizeSlotRegion(slotName: string, width: number, height: number, createNew: boolean);
resizeSlotRegion2(slotName: string, texWidth: number, texHeight: number, rectWidth: number, rectHeight: number, rectX: number, rectY: number, originWidth: number, originHeight: number, offsetX: number, offsetY: number, degrees: number, createNew: boolean);

四、修改引擎适配js代码,增加图集换装支持

  • 修改bin/adapter/engine-adapter.js
skeleton.setSlotTexture2 = function (slotName, tex2d, texSize, rect, originSize, offset, degrees, createNew) {
    if (this.isAnimationCached()) {
        console.error(`Cached mode can't change texture of slot`);
        return;
    }
    if (!this._nativeSkeleton) return;
    const slot = this.findSlot(slotName);
    if (!slot) {
        console.error(`No slot named:${slotName}`);
        return;
    }
    const createNewAttachment = createNew || false;
    this._nativeSkeleton.setSlotTexture2(slotName, tex2d, texSize, rect, originSize, offset, degrees, createNewAttachment);
};
  • 修改bin/adapter/engine-adapter.min.js
skeleton.setSlotTexture2 = function (slotName, tex2d, texSize, rect, originSize, offset, degrees, createNew) {
    if (this.isAnimationCached()) {
        console.error(`Cached mode can't change texture of slot`);
        return;
    }
    if (!this._nativeSkeleton) return;
    const slot = this.findSlot(slotName);
    if (!slot) {
        console.error(`No slot named:${slotName}`);
        return;
    }
    const createNewAttachment = createNew || false;
    this._nativeSkeleton.setSlotTexture2(slotName, tex2d, texSize, rect, originSize, offset, degrees, createNewAttachment);
};

五、删除项目temp、library文件夹,重新编译项目

  • 重新编译引擎,重启项目
  • 如果发现有问题也可以删除引擎目录bin/.cache文件夹,重新打开项目

我在我的机器上是实验成果的,各位坛友感兴趣可以尝试一下,有问题欢迎交流,另外附上我修改的完整文件。基于Creator3.8.5

我修改的文件完整内容:
engine.zip (927.3 KB)

6赞

这里也希望各位大神帮忙看下这个方案的缺陷和不足,是否有改进之处