Spritekit - not loading @3x images from SKTextureAtlas
Asked Answered
C

5

13

Since my sample project was deleted (I thought this would be much easier to test), I will post some code and images to illustrate my point.

Here are sample images

2x image

my 3x image

My atlas setup:

enter image description here

My launch image setup:

enter image description here

The code where I add these sprites to my scene

override func didMoveToView(view: SKView) {
    let texture = SKTextureAtlas(named: "scenery")
    let test = SKSpriteNode(texture: texture.textureNamed("test"))
    test.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
    self.addChild(test)

}

These are my results:

iPhone 5 simulator:

enter image description here

iPhone 6 plus simulator:

enter image description here

I've tried changing the launch image to use the asset catalog. Then the iPhone 6 plus seems to upscale a 2x screen. It's still loading the 2x image, but scales it up.

I need it to load my 3x image and be to scale with my 2x image.

Gabuh's answer below pointed me in the right direction. Works on a new project. However, if I use his solution for my real SpriteKit project my 3x images don't get downscaled. They are 3x bigger than they should be.

Commodore answered 26/1, 2015 at 8:25 Comment(5)
Are you testing on a real iPhone 6+ ?? or in the simulator?,Montpellier
both. have you tried my project?Commodore
I removed the link to the project. The (suspected) source code with the issue should be in the question itself. SO users shouldn't need to download an entire project and start debugging/modifying it.Johnsen
alright... i've posted plenty of screenshots to illustrate my point. I was interested in posting a simple project because people are answering this and assuming it works without testing it out for themselves.Commodore
I will do testing as well but maybe you can test Xcode 6.2, according to the new answer below.Consuela
A
5

It seems to be working now if you are using a new way to create atlas. Important thing is that Deployment target should be >= 9.0 ...

Select Assets.xcassets and click to + sign to create a new sprite atlas:

enter image description here

Choose "New Sprite Atlas" option and add @2x and @3x assets :

enter image description here

Then in a code do this:

let sprite = SKSpriteNode(texture: SKTextureAtlas(named: "Sprites").textureNamed("test"))
sprite.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(sprite)

Hint:

If you are testing on Simulator, reset Simulator's content and settings to clear the cache before you try this.

Amygdaline answered 15/2, 2016 at 13:15 Comment(3)
@Siriss That's odd, because I am able to produce good results when deployment target is >= 9. I saw that this works for people from apple dev forum as well. It is like officially fixed thing.Amygdaline
Ok huh. I will recreate all of my texture atlases then and see if I can get it working.Appeal
Apple docs independantly describe "Creating a Sprite Atlas" and "Creating a Texture Atlas". I'm confused about the difference between the two. This answer shows how to create a Sprite Atlas within an Asset Catalog. Seems like best practice now is to just use Asset Catalogs and create Sprite Atlases within them. Is there no longer a need to create a Texture Atlas? @AppealCancellation
M
4

It seems a bug when Xcode generates the compiled atlas. If you check inside the package of your compiled app, you will see that Xcode is not creating the correct atlas names for the @3x images.

I've managed getting the @3x assets by creating atlases with the @3x name, and leaving the image without the suffix.

And you can check for the UIScreen.mainscreen().scale to decide the atlas name to use. Check the atlas name in the attached image, and the code inside getTextureAtlas enter image description here

Montpellier answered 26/1, 2015 at 9:2 Comment(12)
have you tried this in my project? I tried it, and it's still not picking up the 3x image.Commodore
this was great! totally worked on the sample project.. except when i copy this setup for my REAL spritekit project it's not downscaling the 3x images. theyre all 3x bigger than they should be! I can't tell what could be causing that. Any ideas? If I can figure that part out I'm home free.Commodore
I feel like this is something related to the project's settings. I keep trying to tell the difference between my projects but I really cant find it.Commodore
okay i got it.. in my other project under build settings i had set "Output Texture Atlas Format" I had RGBA8888_COMPRESSED selected. That didnt work. I had to set it to RGBA8888_PNG and clean project.Commodore
Is there a better fix for this yet?Appeal
Xcode 6.2 seems to read 3x and 2x from same folder. Testing now.Appeal
I reported this bug to Apple back then, they said they fixed it sometime during ioS9 betas. I just tested this out and it seems that atlases are regenerated OK now. However an iPhone 6 Plus does NOT use 3x images from an atlases even though they exist, it always used the 2x ones. Bummer.Consuela
this is not the correct way to handle retina graphics, all this is doing is loading images at 1x regardless of folder, the @#x plays no bearing since you are manually loading it in. That is why the graphics are not correctly scaled to the point systemSfax
@Sfax We were not saying that this was the correct way to handle retina images. It was just a workaround for a bug, if apple has adressed it, great for them. I have not tested it with Xcode 7.Montpellier
thats the thing, it is not even a work around, like I said, you are treating your -@3x images as -@1x with the file name -@3x. Since I no longer have Xcode 6 unfortunately I can't tell you what I did to get around this issue, I want to say it had to do with the actual size of the sprites not going correctly into the atlas, or maybe I used an external atlas packer software like TexturePacker, I know I have done both, this issue does not exist in 7 though. in 7 it will now properly split the image into 3 files for usSfax
I still get the same problem in Xcode 7. In IPhone 6+ is still loading the 2x image when I try to create the sprite from the SKTextureAtlas. It works well if I initialize the Sprite directly with his name. The workaround I posted still works for me, it not loads the images at 1x. At least in my project. (loading textures from the atlas, not by name)Montpellier
I had filed a bug report for this way back then and today I got a message from Apple "There are no plans to address this. We are now closing this report. If you have questions about the resolution, or if this is still a critical issue for you, then please update your bug report with that information. Please be sure to regularly check new Apple releases for any updates that might affect this issue." So it looks like it will not be getting fixed ever.Menedez
A
1

Xcode 6.2 now loads @3x and @2x images out of one atlas. It loads a 1x size (and seems to resize the image on its own) if you do not put @2x/@3x at the end of the image name.

Appeal answered 13/3, 2015 at 4:14 Comment(10)
Actually I just tried this but Xcode 6.2 still only loads a 2x texture even for iPhone 6 Plus. So Xcode 6.2 is still bugged. What's worse: I looked at the resulting atlas files in the .app: the 3x AND 1x images end up in the 1x file!!!Consuela
You are right!! I did not notice it was loading the @2x on my 6+ until you said that. This is really really screwed up.Appeal
@Appeal have you came up with something about this problem? This is really annoying bug... Or still the only solution is to make two atlases and based on scale factor to use required atlas accordingly ?Amygdaline
@Amygdaline I still check the screen scale as mentioned in the answer above. In my 2x atlas, I have At2x after my image name, in my 3x atlas, the images are just the image name. It loads the correct atlas and does not seem to scale the images.Appeal
@Appeal Thanks, I do the same, I was just hoping something has changed in the meanwhile.Amygdaline
I will today. I have been so frustrated with my SKPhysicsBodies being upside down, that I have not really looked into anything else.Appeal
No nothing has worked for me. I did just get a suggestion to recreate my art file in photoshop/illustrator, and make sure there are not gaps in the online layer. Apparently that has fixed these problems for some people. I didn't think there were gaps to begin with but I am going to try it anyway.Appeal
This is so frustrating... I am thinking of using @2x images and be done with it. I'm afraid the suggested solution above will not work when this issue is fixed and will make your sprites to not appear on the fixed ios version.Courtesan
Is this problem related to iPhone 6 Plus and iPhone 6 detection ? See from apple documentation: You use a launch XIB or storyboard file to indicate that your app runs on iPhone 6 Plus or iPhone 6.Taxi
I have all of that in place.Appeal
S
1

Not sure why this was never done, but here is the start of a workaround that is actually correct, but is a little slower unfortunately. Maybe someone can see some things to make it process faster

import Foundation
import SpriteKit

public class Atlas: SKTextureAtlas
{
    var textures = [String:(texture:SKTexture,image:UIImage)]();
    public convenience init(named name: String)
    {
        self.init()
        let scale = CGFloat(UIScreen().scale);
        let path = "\(name).atlasc/\(name)";
        let atlasContent = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource(path, ofType: "plist")!);

        let content = atlasContent!["images"] as! [[String:AnyObject]];
        let info = content[Int(scale) - 1] ;
        let imagepath = "\(name).atlasc/\((info["path"] as! String!).stringByReplacingOccurrencesOfString(".png", withString: ""))";
        let imgDataProvider = CGDataProviderCreateWithCFData(NSData(contentsOfFile:  NSBundle.mainBundle().pathForResource(imagepath, ofType: "png")!));
        let cgimage = CGImageCreateWithPNGDataProvider(imgDataProvider, nil, true, .RenderingIntentDefault);

        let subimages = info["subimages"] as! [[String:AnyObject]];
        for subimage in subimages
        {
            let spriteSourceSize = CGSizeFromString(subimage["spriteSourceSize"] as! String);
            let size = CGSizeApplyAffineTransform(spriteSourceSize, CGAffineTransformMakeScale(1/scale,1/scale));



            let isFullyOpaque = subimage["isFullyOpaque"] as! Bool;
            let spriteOffset = CGPointFromString((subimage["spriteOffset"] as! String));
            let textureRect = CGRectFromString((subimage["textureRect"] as! String));
            let textureRectSize = CGSizeApplyAffineTransform(textureRect.size, CGAffineTransformMakeScale(1/scale,1/scale));




            let name = (subimage["name"] as! String).stringByReplacingOccurrencesOfString("@3x.png", withString: "");
            let textureRotated = subimage["textureRotated"] as! Bool;
            let smallImage = CGImageCreateWithImageInRect(cgimage, textureRect);



            UIGraphicsBeginImageContextWithOptions(size, isFullyOpaque, scale);
            let context = UIGraphicsGetCurrentContext();
            CGContextSaveGState(context);
            CGContextSetShouldAntialias(context,false);
            CGContextSetAllowsAntialiasing( context ,false);
            CGContextSetInterpolationQuality(context , CGInterpolationQuality.None)

            if(textureRotated)
            {
                CGContextTranslateCTM(context, (size.width)/2, (size.height)/2);
                  CGContextScaleCTM(context, 1, -1);
                  CGContextRotateCTM(context,CGFloat(M_PI_2));
                  CGContextTranslateCTM(context, 0, ((size.height - textureRectSize.height)));
                CGContextTranslateCTM(context, -((size.height)/2), -((size.width)/2));
                CGContextTranslateCTM(context, spriteOffset.y/scale, -spriteOffset.x/scale);

            }
            else
            {
                //Set to center of image to flip correctly
                CGContextTranslateCTM(context, (size.width)/2, (size.height)/2);
                    CGContextScaleCTM(context, 1, -1);
                CGContextTranslateCTM(context, -((size.width)/2), -((size.height)/2));
                CGContextTranslateCTM(context, spriteOffset.x/scale, spriteOffset.y/scale);

            }

            CGContextDrawImage(context,CGRect(origin: CGPoint.zero,size: textureRectSize), smallImage);
            let image = UIGraphicsGetImageFromCurrentImageContext();
            let texture = SKTexture(image: image);
            textures[name] = (texture:texture,image:image);
            CGContextRestoreGState(context);
            UIGraphicsEndImageContext();
        }
    }
    override public func textureNamed(name: String) -> SKTexture {
        return textures[name]!.texture;
    }
    public func imageNamed(name: String) -> UIImage {
        return textures[name]!.image;
    }
}
Sfax answered 4/3, 2016 at 9:16 Comment(0)
S
0

This bug is still unsolved. By using just @2x images the visual of the app gets broken. Instead choose the right image by looking screen scale.

textureName = [UIScreen mainScreen].scale > 2.9 ? @"awesome@3x" : @"awesome";
Sou answered 16/11, 2015 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.