Well I was tinkering around with it and I got something working but as you said it's not particularly straight forward. The problem as you stated is the atlas doesn't know which textures the skin needs and to get at the skin you need the atlas. So basically what I did was added some functionality to create the atlas but with atlasPages that have null textures/render objects. But when the game goes through the old path it loads all the textures
UCLASS(BlueprintType, ClassGroup=(Spine))
class SPINEPLUGIN_API USpineAtlasAsset: public UObject {
.
.
spine::Atlas* GetAtlasWithoutLoading();
void LoadPages(const TArray<spine::AtlasPage*>& PagesToLoad);
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<TSoftObjectPtr<UTexture2D>> atlasPages;
UPROPERTY(Transient)
TArray<UTexture2D*> cachedAtlasPages;
.
.
}
Atlas* USpineAtlasAsset::GetAtlas () {
if (!atlas) {
if (atlas) {
delete atlas;
atlas = nullptr;
}
std::string t = TCHAR_TO_UTF8(*rawData);
atlas = new (__FILE__, __LINE__) Atlas(t.c_str(), strlen(t.c_str()), "", nullptr);
cachedAtlasPages.SetNumZeroed(atlasPages.Num());
Vector<AtlasPage*> &pages = atlas->getPages();
for (size_t i = 0, n = pages.size(), j = 0; i < n; i++) {
AtlasPage* page = pages[i];
if (atlasPages.Num() > 0 && atlasPages.Num() > (int32)i)
{
cachedAtlasPages[j] = atlasPages[j].LoadSynchronous();
page->setRendererObject(cachedAtlasPages[j]);
++j;
}
}
}
return this->atlas;
}
Atlas* USpineAtlasAsset::GetAtlasWithoutLoading() {
if (!atlas) {
if (atlas) {
delete atlas;
atlas = nullptr;
}
std::string t = TCHAR_TO_UTF8(*rawData);
atlas = new (__FILE__, __LINE__) Atlas(t.c_str(), strlen(t.c_str()), "", nullptr);
}
return this->atlas;
}
void USpineAtlasAsset::LoadPages(const TArray<spine::AtlasPage*>& PagesToLoad)
{
Vector<spine::AtlasPage*> &pages = atlas->getPages();
for (auto& pageToLoad : PagesToLoad)
{
const int32 pageIndex = pages.indexOf(pageToLoad);
if (pageIndex >= 0)
{
cachedAtlasPages[pageIndex] = atlasPages[pageIndex].LoadSynchronous();
pageToLoad->setRendererObject(cachedAtlasPages[pageIndex]);
}
}
}
void USpineAtlasAsset::PostLoad()
{
Super::PostLoad();
cachedAtlasPages.SetNumZeroed(atlasPages.Num());
}
Set up the skeleton with that that atlas. Set the skin to the appropriate skin. Then get the required pages that the skin uses and load up the texture for those.
TArray<AtlasPage*> USpineSkeletonAnimationComponent::GetRequiredAtlasPages()
{
TArray<AtlasPage*> requiredAtlasPages;
CheckState();
if (skeleton)
{
Skin* skin = skeleton->getSkin();
if (skin)
{
Skin::AttachmentMap::Entries entries = skin->getAttachments();
while (entries.hasNext())
{
Skin::AttachmentMap::Entry entry = entries.next();
if (entry._attachment->getRTTI().isExactly(RegionAttachment::rtti))
{
RegionAttachment* regionAttachment = static_cast<RegionAttachment*>(entry._attachment);
AtlasRegion* attachmentAtlasRegion = static_cast<AtlasRegion*>(regionAttachment->getRendererObject());
requiredAtlasPages.AddUnique(attachmentAtlasRegion->page);
}
else if(entry._attachment->getRTTI().isExactly(MeshAttachment::rtti))
{
MeshAttachment* regionAttachment = static_cast<MeshAttachment*>(entry._attachment);
AtlasRegion* attachmentAtlasRegion = static_cast<AtlasRegion*>(regionAttachment->getRendererObject());
requiredAtlasPages.AddUnique(attachmentAtlasRegion->page);
}
}
}
}
return requiredAtlasPages;
}
We have our own actor class for spine actors and I just go through the new path before spine has a chance to go through the old code path.
USpineAtlasAsset& spineAtlas = mustDeref(streamableManager.LoadSynchronous<USpineAtlasAsset>(characterData.CharacterVisualTemplate.SpineAtlas));
spineAtlas.GetAtlasWithoutLoading();
SpineSkeletonAnimComp->Atlas = &spineAtlas;
USpineSkeletonDataAsset& spineSkeletonData = mustDeref(streamableManager.LoadSynchronous<USpineSkeletonDataAsset>(characterData.CharacterVisualTemplate.SpineSkeletonData));
SpineSkeletonAnimComp->SkeletonData = &spineSkeletonData;
SpineSkeletonAnimComp->SetSkin(characterData.CharacterVisualTemplate.SkinName);
TArray<spine::AtlasPage*> requiredAtlasPages = SpineSkeletonAnimComp->GetRequiredAtlasPages();
spineAtlas.LoadPages(requiredAtlasPages);
There were a few other changes I had to make as some things access the atlasPages directly which I had to make use the cached version.