Oh my, sorry for the pain this caused. I've opened an issue here and will push this tomorrow.
Loading Spine Sprite at runtime
Thank you Mario I really appreciate feedback and the raising of a ticket.
I have checked the code out to am trying to get the build working some kinks still with the intention of working though loading from file.
Is it expected that you can load from file sources not only from project resources?
Hello I have completed the changes needed to load spine sprites from file. The path error was only one issue there is also the problem that the Resource loader for the images doesn't load images from path only from resources. So Image::load was needed.
This does however introduce a new concern that each load will import the image from the drive as there is no access to the Resource Cache. I overcame this in GDScript for now. It would be better for this to be handled in the loader by at least keeping a WeakRef to any images and reuse them should they already exist.
Is there any way i could ask for the c++ changes to go into the base code. I am happy to meet your development standards just let me know what I need to improve or change. It would just be simpler for me in the long run if I don't have to make this modification each time there is a new version.
Changed to SpinAtlasResource.cpp
GodotSpineTextureLoader::fix_path
This was changed to resolve the error I mentioned earlier.
static String fix_path(const String &path) {
if (path.size() > 5 && path[4] == '/' && path[5] == '/') return path;
const String prefix = "res:/";
auto i = path.find(prefix);
if (i < 0) return path;
auto sub_str_pos = i + prefix.size() - 1;
auto res = path.substr(sub_str_pos);
if (!EMPTY(res)) {
if (res[0] != '/') {
return prefix + "/" + res;
} else {
return prefix + res;
}
}
return path;
}
GodotSpineTextureLoader::load
This had to change to facilitate loading the texture from disk. I only implemented the load for version 4.
void load(spine::AtlasPage &page, const spine::String &path) override {
Error error = OK;
auto fixed_path = fix_path(String(path.buffer()));
#if VERSION_MAJOR > 3
const String prefix = "res:/";
auto i = fixed_path.find(prefix);
Ref<Texture2D> texture;
if (i < 0) {
Ref<Image> image=Image::load_from_file(fixed_path);
texture = ImageTexture::create_from_image(image);
} else {
texture = ResourceLoader::load(fixed_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error);
}
#else
Ref<Texture> texture = ResourceLoader::load(fixed_path, "", false, &error);
#endif
if (error != OK || !texture.is_valid()) {
ERR_PRINT(vformat("Can't load texture: \"%s\"", String(path.buffer())));
auto renderer_object = memnew(SpineRendererObject);
renderer_object->texture = Ref<Texture>(nullptr);
renderer_object->normal_map = Ref<Texture>(nullptr);
page.texture = (void *) renderer_object;
return;
}
textures->append(texture);
auto renderer_object = memnew(SpineRendererObject);
renderer_object->texture = texture;
renderer_object->normal_map = Ref<Texture>(nullptr);
String new_path = vformat("%s/%s_%s", fixed_path.get_base_dir(), normal_map_prefix, fixed_path.get_file());
if (ResourceLoader::exists(new_path)) {
Ref<Texture> normal_map = ResourceLoader::load(new_path);
normal_maps->append(normal_map);
renderer_object->normal_map = normal_map;
}
SpineSkeletonFileResource::_bind_methods
Another gap was that load_from_file for skeleton was not bound to GDScript so it could not be called this is necessary to allow loading from disk.
void SpineSkeletonFileResource::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_from_file", "path"), &SpineSkeletonFileResource::load_from_file);
ADD_SIGNAL(MethodInfo("skeleton_file_changed"));
}
New GDScript to load from disk
To overcome the issues of loading multiple atlases and skeletons into the system I built a helper class that will cache a WeakRef to both. This means if they are still in the scene then they will not be loaded again but they should not stay in ram any longer then that.
extends Node
class_name SpineSpriteFileLoader
static var known_atlasses={}
static var known_skeletons={}
static func _load_spine_atlas(atlas_path:String)->SpineAtlasResource:
var atlas_res:SpineAtlasResource=null
if known_atlasses.has(atlas_path) and known_atlasses[atlas_path].get_ref() != null:
atlas_res=known_atlasses[atlas_path].get_ref()
else:
atlas_res=SpineAtlasResource.new()
var error:Error=atlas_res.load_from_atlas_file(atlas_path)
if error != OK:
printerr("Failure loading atlas@"+atlas_path,error)
known_atlasses[atlas_path]=weakref(atlas_res)
return atlas_res
static func _load_spine_skeleton(skeleton_json_path:String)->SpineSkeletonFileResource:
var skeleton_file_res:SpineSkeletonFileResource=null
if known_skeletons.has(skeleton_json_path) and known_skeletons[skeleton_json_path].get_ref() != null:
skeleton_file_res=known_skeletons[skeleton_json_path].get_ref()
else:
skeleton_file_res=SpineSkeletonFileResource.new()
var error:Error=skeleton_file_res.load_from_file(skeleton_json_path)
if error != OK:
printerr("Failure loading json-spine! ",error)
known_skeletons[skeleton_json_path]=weakref(skeleton_file_res)
return skeleton_file_res
static func load_spine_sprite(atlas_path:String,skeletonm_json_path:String)->SpineSprite:
var atlas_res:SpineAtlasResource=_load_spine_atlas(atlas_path)
var skeleton_file_res:SpineSkeletonFileResource=_load_spine_skeleton(skeletonm_json_path)
var skeleton_data_res:SpineSkeletonDataResource=SpineSkeletonDataResource.new()
skeleton_data_res.skeleton_file_res=skeleton_file_res
skeleton_data_res.atlas_res=atlas_res
var sprite:SpineSprite=SpineSprite.new()
sprite.skeleton_data_res=skeleton_data_res
return sprite
Best Regards
Travis Bulford
Hello,
Is this something you will still be able to merge into the code base?
Not for the upcoming Spine 4.2 release which we'll likely publish this week. However, if you folks can send a PR, I can see if I can get it into the next patch release. It will need quite some testing on my end, as it does modify a crucial code path.
I encountered the same issue. Could you please provide guidance on how to load a texture but only for Godot version 3.5.3, as the necessary methods are not present in the Godot *.cpp files?
Ref<Image> image=Image::load_from_file(fixed_path);
texture = ImageTexture::create_from_image(image);
I have figured out the image loading part, now I just need to turn it into a texture.
old:
Ref<Image> image=Image::load_from_file(fixed_path);
new:
Ref<Image> image;
image.instance();
ImageLoader::load_image(fixed_path, image);
I was able to make a similar modification for version 3.5.3, and it works!!
const String prefix = "res:/";
auto i = fixed_path.find(prefix);
Ref<ImageTexture> texture;
if (i < 0) {
Ref<Image> image;
image.instance();
image->load(fixed_path);
texture.instance();
texture->create_from_image(image);
} else {
texture = ResourceLoader::load(fixed_path, "", false, &error);
}
Fix:
const String prefix = "res:/";
auto i = fixed_path.find(prefix);
Ref<Texture> texture;
if (i < 0) {
Ref<Image> image;
image.instance();
image->load(fixed_path);
Ref<ImageTexture> image_texture;
image_texture.instance();
image_texture->create_from_image(image);
texture = image_texture;
} else {
texture = ResourceLoader::load(fixed_path, "", false, &error);
}
Fix GDScript for Godot 3.x
extends Node
class_name SpineSpriteFileLoader
const known_atlasses = {}
const known_skeletons = {}
static func _load_spine_atlas(atlas_path:String) -> SpineAtlasResource:
var atlas_res:SpineAtlasResource = null
if known_atlasses.has(atlas_path) and known_atlasses[atlas_path].get_ref() != null:
atlas_res = known_atlasses[atlas_path].get_ref()
else:
atlas_res = SpineAtlasResource.new()
var error = atlas_res.load_from_atlas_file(atlas_path)
if error != OK:
printerr("Failure loading atlas@"+atlas_path,error)
known_atlasses[atlas_path] = weakref(atlas_res)
return atlas_res
static func _load_spine_skeleton(skeleton_json_path:String) -> SpineSkeletonFileResource:
var skeleton_file_res:SpineSkeletonFileResource = null
if known_skeletons.has(skeleton_json_path) and known_skeletons[skeleton_json_path].get_ref() != null:
skeleton_file_res = known_skeletons[skeleton_json_path].get_ref()
else:
skeleton_file_res = SpineSkeletonFileResource.new()
var error = skeleton_file_res.load_from_file(skeleton_json_path)
if error != OK:
printerr("Failure loading json-spine! ",error)
known_skeletons[skeleton_json_path] = weakref(skeleton_file_res)
return skeleton_file_res
static func load_spine_sprite(atlas_path:String, skeletonm_json_path:String) -> SpineSprite:
var atlas_res:SpineAtlasResource = _load_spine_atlas(atlas_path)
var skeleton_file_res:SpineSkeletonFileResource=_load_spine_skeleton(skeletonm_json_path)
var skeleton_data_res:SpineSkeletonDataResource = SpineSkeletonDataResource.new()
skeleton_data_res.skeleton_file_res = skeleton_file_res
skeleton_data_res.atlas_res = atlas_res
var sprite:SpineSprite = SpineSprite.new()
sprite.skeleton_data_res = skeleton_data_res
return sprite
Thanks for the investigation. I'll be adding both of your suggestions to our build this week.
Thanks Mario. I know you asked for a pull request. I can still do that but if you can manage without one I would appreciate it as I have never done one before and was still reading up.
No need for a pull request, I'll manage to do it without one.
I've noticed some strange behavior, most likely it's an error that needs to be fixed, but unfortunately, I haven't been able to find a solution for it. I'm writing about it in this thread because it's related to it. If we have a scene with a spine object that was loaded from the file system rather than from the game's resources, then upon reloading, the spine object will be empty. If you have a quick solution for this, I would greatly appreciate it.
I'm afraid I do not have a quick solution. Can you please provide me with an example project to reproduce the issue? Just a scene + gdscript + assets would do.