- Düzenlendi
Lots and lots of skins that need to be manually edited
Hello,
We're having trouble with our Unity WebGL project - we need to use clothes, and a lot of them (projecting 150 sets in total) with separate parts for tops, bottoms, legs, shoes etc, and we really, really need them to be downloadable separately and on request for our WebGL projector to make the game load fast/not keep everything in RAM. We tried to use skins as a clothing system, but we were having a lot of problems since our textures are very large (4096x4096) and keeping them all in the editor requires more VRAM than is available, making the editor crash and become unstable. There is also the problem of keeping every skin in separate bundles so they can be downloaded on request from the server, not everything at once - so we really need to be able to separate the "naked body" and each skin into separate files.
We have conceived of a bit of a "hack" where we keep multiple copies of the same skeleton "with clothes" and, on the runtime side, we substitute parts of the skeleton with the skeleton-in-clothing. This solved the editor problems since the animator can just work on one copy of the skeleton and add the skin on a copy and worked well as a bundling solution, but this solution makes it impossible to mix skins - we need to be able to have multiple items attached and able to have them "one on top of the other" (so, say, a sweater on top of a T-shirt with the T-shirt visible) and skins solve that, but with a solution like that we'd need multiple "mixed" bundles and that's just impractical.
TL;DR We need to have an advanced clothing system in which:
- we can have lots and lots of clothes (150+)
- clothes can be on top of one another (say sweater on top of a T-shirt)
- multiple attachment points (legs, top, jewellery slots etc)
- clothes will be loaded dynamically, as to only load what's needed
- clothes will not Z-clash
- will not require massive amount of RAM/VRAM in the editor
Does anyone know, perhaps, of a solution for working in the editor with multiple large skins without overflowing VRAM or of keeping skins separate and attachable in a way that doesn't require it to be loaded into the editor?
What you likely want is something users have been calling "Mix and Match" and it's what people use for equips/character customization.
I've documented part of the process here, specific to Spine-Unity: https://github.com/pharan/spine-unity-docs/blob/master/Mix-and-Match.md
But basically, it lets you create attachments, and atlases, at runtime so you don't have to pre-pack them while using Spine.
In the editor, you would animate the skeletons with template images, and all the actual clothes and equips would be imported in Unity by converting UnityEngine.Sprites into usable Spine Attachments, and you can choose to use only the ones you need and load them however you want, from disk, preload, download from the internet or whatever. At that point, memory/texture loading and management is yours to handle.
Feel free to give us feedback on what's not clear or what confused you in that doc.
Is there any chance whatsoever that support for this kind of customization, "mix and match", to be added to the Cocos2d-x runtimes (C and CPP)? Our development team is in the same situation at the moment, and we are evaluating several options in order to achieve something similar to what the OP mentioned.
We want to be able to create attachments that use custom textures, add them to a dynamically created skin, and then apply that skin to the skeleton data.
The textures will come from individual files (PNGs), and perhaps even sprite sheets if required.
This is already possible in the C/C++ runtimes as well. The C# code here https://github.com/pharan/spine-unity-docs/blob/master/spine-unity-mix-and-match.md#runtime-skins can be directly translated to the corresponding C/C++ API. You will have to make sure to dispose of your custom skin at the right time manually of course.
The at-runtime-repacking is also possible, however, I'm not aware of a runtime texture packer for cocos2d-x. We do not include our own packer, either in the C#/Unity runtime or the C/C++ runtimes.
- Düzenlendi
Thank you for the response badlogic. Shortly after I made the initial post I dove right into translating the C# code across to Cocos2d-x, and while it's now compiling, no texture is being displayed for the specific attachment I've created using a texture, so I'm hunting down the reason why.
I'm not overly concerned with texture-repacking, at least not for this project, so I didn't port over that section of code.
One question though, in the C# Unity code, if GetRemappedClone() is called with premultiplyAlpha set to false, then for this line:
var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(false);
The condition result ends up being atlasRegion = sprite.ToAtlasRegion(false);
Now, the extension method ToAtlasRegion that gets called is:
internal static AtlasRegion ToAtlasRegion (this Sprite s, bool isolatedTexture = false) {
var region = new AtlasRegion();
region.name = s.name;
region.index = -1;
region.rotate = s.packed && s.packingRotation != SpritePackingRotation.None;
// World space units
Bounds bounds = s.bounds;
Vector2 boundsMin = bounds.min, boundsMax = bounds.max;
// Texture space/pixel units
Rect spineRect = s.rect.SpineUnityFlipRect(s.texture.height);
region.width = (int)spineRect.width;
region.originalWidth = (int)spineRect.width;
region.height = (int)spineRect.height;
region.originalHeight = (int)spineRect.height;
region.offsetX = spineRect.width * (0.5f - InverseLerp(boundsMin.x, boundsMax.x, 0));
region.offsetY = spineRect.height * (0.5f - InverseLerp(boundsMin.y, boundsMax.y, 0));
if (isolatedTexture) {
region.u = 0;
region.v = 1;
region.u2 = 1;
region.v2 = 0;
region.x = 0;
region.y = 0;
} else {
Texture2D tex = s.texture;
Rect uvRect = TextureRectToUVRect(s.textureRect, tex.width, tex.height);
region.u = uvRect.xMin;
region.v = uvRect.yMax;
region.u2 = uvRect.xMax;
region.v2 = uvRect.yMin;
region.x = (int)spineRect.x;
region.y = (int)spineRect.y;
}
return region;
}
What I don't quite understand is the region being returned has no reference to the texture being used in the sprite, yet all the other ToAtlasRegion() methods do store a reference to the texture/material in the region.atlas field. Does this method actually work as intended? I'm in the process of downloading Unity3D to actually test it, because it seems incorrect.
In GetRemappedClone(), the line
var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(false);
should perhaps be
var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(sourceMaterial);
This allows the texture to be displayed properly if premultiplyAlpha is false.
Note that a lot of the Spine-Unity code tries to respect the Unity types, hence you have stuff like Material, and Sprite, and Vector2, and Rect.
This assembly-internal method is called by the other overloads which do the mapping of a Material object (which holds a reference to the texture, and to the shader program) to the AtlasRegion object.
Thanks for reporting that though!
Thanks for the info. I initially thought the internal method was the issue, until I noticed that the wrong method was being called in GetRemappedClone(), and now it makes more sense. As a result of that find and other Cocos2d-x specific bits of code, I ended up getting the C++ port working, so we're one step closer to deciding if Spine is the right tool for our requirements.