SpriteKit SKTextureAtlas atlasWithDictionary method - ios

I'm not sure if I'm doing this wrong or if there's something broken with the "atlasWithDictionary" method.
This is how I used it:
NSArray* imageNames = #[ #"image1", #"image2", #"image3", #"image4"];
NSMutableDictionary* tempDict = [NSMutableDictionary dictionary];
for (int i = 0; i < imageNames.count; i++) {
UIImage* texture = [UIImage imageNamed:imageNames[i]];
[tempDict setObject:texture forKey:imageNames[i]];
}
SKTextureAtlas* atlasFullOfTextures = [SKTextureAtlas atlasWithDictionary:tempDict];
and then later in my code, whenever I would do
SKTexture* tex = [atlasFullOfTextures textureNamed:#"image1"];
I just get a nil object. I did a bit of troubleshooting and found that
NSArray* arrayOfNames = [atlasFullOfTextures textureNames];
returns an empty array.
Also, I know that the images are loading fine as I temporarily made the dictionary public and successfully made SKTextures from the UIImage objects.
Does anyone have any idea what's happening?

From the documentation:
The keys in the dictionary represent the names of the individual
textures. The associated object for each key can be:
An NSString object that contains a file system path to a file that contains the texture
An NSURL object that contains a file system path to a file that contains the texture
A UIImage object
An NSImage object
You are not providing a path/url to the image, you just use image names with no way of telling where they might be. You will probably also have to specify the file extension as Sprite Kit probably won't try to guess it. If these are textures obtained or created at runtime, they will not be in the bundle so you have to specify what the path to each texture is as well (usually in the appdata or documents folder).
If these images are in the bundle, you probably have to specify the bundle directory. But there's little reason not to have the atlas be created by Xcode at compile time in this case and then using atlasNamed:.

Related

Testing that a particular image was copied to pasteboard

I'm writing a test to verify a feature which copies an image to the pasteboard.
Here's the test, as I would prefer to write it:
// reset the paste board
UIPasteboard.generalPasteboard.image = nil; //<-- this explodes
XCTAssertNil(UIPasteboard.generalPasteboard.image);
// Grab some random existing image
UIImage *image = [UIImage imageNamed:#"some-image"];
MJKMyClass *myInstance = [[myInstance alloc] initWithImage:image];
[myInstance doSomethingThatCopiesImageToPasteboard]
XCTAssertNotNil(UIPasteboard.generalPasteboard.image);
This fails with:
failed: caught "NSInvalidArgumentException", "-[UIPasteboard setImage:]: Argument is not an object of type UIImage [(null)]"
Which is surprising, because, according to the UIPasteboard header, image is a nullable field.
#interface UIPasteboard(UIPasteboardDataExtensions)
<...>
#property(nullable,nonatomic,copy) UIImage *image __TVOS_PROHIBITED;
<...>
#end
I presume this means they are doing a runtime check on the argument, even though it is nullable.
Things I have tried:
comparing objects by id doesn't work because UIImage's are copied by the generalPastboard.image (every time you call UIPasteboard.generalPasteboard.image you can a different instance)
comparing by PNG representation might work, but seems gnarly.
comparing by image size has been my closest bet so far, but also seems roundabout.
You can clear the pasteboard without having to pass nil by using the items property:
UIPasteboard.generalPasteboard.items = #[];
Or in Swift:
UIPasteboard.generalPasteboard().items = []
For comparing UIImages, you might want to look at some other questions:
How to compare two UIImage objects
https://stackoverflow.com/search?q=uiimage+compare

Save generated SKTexture to file

I've now filed a bug for the issue below. Anyone with a good
workaround?
I try to save an SKTexture to file, and load it back again, but I don't succeed. The following code snippet can be copied to GameScene.m in the Xcode startup project.
I use textureFromNode in generateTexture, and that seems to be the root cause of my problem. If I use a texture from a sprite, the code works, and two spaceships are visible.
This code worked in iOS 8 but it stopped working in Xcode7 & iOS 9. I just want to verify that this is a bug before I file a bug report. My worry is that I do something wrong with NSKeyedArchiver.
It happens both in simulator and on device.
#import "GameScene.h"
#implementation GameScene
// Generates a texture
- (SKTexture *)generateTexture
{
SKScene *scene = [[SKScene alloc] initWithSize:CGSizeMake(100, 100)];
SKShapeNode *shapeNode = [SKShapeNode shapeNodeWithRectOfSize:CGSizeMake(50, 50)];
shapeNode.position = CGPointMake(50, 50);
shapeNode.strokeColor = SKColor.redColor;
shapeNode.lineWidth = 10;
[scene addChild:shapeNode];
SKTexture *texture = [self.view textureFromNode:scene];
//SKTexture *texture = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"].texture; // This works!
return texture;
}
// Just generate a path
- (NSString *)fullDocumentsPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *yourFileName = [documentsDirectory stringByAppendingPathComponent:#"fileName"];
return yourFileName;
}
- (void)didMoveToView:(SKView *)view
{
self.scaleMode = SKSceneScaleModeResizeFill;
// Verify that the generateTexture method indeed produces a valid texture.
SKSpriteNode *s1 = [SKSpriteNode spriteNodeWithTexture:[self generateTexture]];
s1.position = CGPointMake(100, 100);
[self addChild:s1];
// Start with saving the texture.
NSString *fullName = [self fullDocumentsPath];
NSError *error;
NSFileManager *fileMgr = [NSFileManager defaultManager];
if ([fileMgr fileExistsAtPath:fullName])
{
[fileMgr removeItemAtPath:fullName error:&error];
assert(error == nil);
}
NSDictionary *dict1 = [NSDictionary dictionaryWithObject:[self generateTexture] forKey:#"object"];
bool ok = [NSKeyedArchiver archiveRootObject:dict1 toFile:fullName];
assert(ok);
// Read back the texture and place it in a sprite. This sprite is not shown. Why?
NSData *data = [NSData dataWithContentsOfFile:fullName];
NSDictionary *dict2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
SKTexture *loadedTexture = [dict2 objectForKey:#"object"];
SKSpriteNode *s2= [SKSpriteNode spriteNodeWithTexture:loadedTexture];
NSLog(#"t(%f, %f)", loadedTexture.size.width, loadedTexture.size.height); // Size of sprite & texture is zero. Why?
s2.position = CGPointMake(200, 100);
[self addChild:s2];
}
#end
Update for Yudong:
This might be a more relevant example, but imagine that the scene consists of 4 layers, with lots of sprites. When the game play is over I want to store a thumbnail image of the end scene of the match. The image will be used as a texture on a button. Pressing that button will start a replay movie of the match. There will be lots of buttons with images of old games so I need to store each image on file.
-(SKTexture*)generateTexture
{
SKScene *scene = [[SKScene alloc] initWithSize:CGSizeMake(100, 100)];
SKSpriteNode *ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
ship.position = CGPointMake(50, 50);
[scene addChild:ship];
SKTexture *texture = [self.view textureFromNode:scene];
NSLog(#"texture: %#", texture);
return texture;
}
The solution/work around:
Inspired by Russells code I did the following. It works!
CGImageRef cgImg = texture.CGImage;
SKTexture *newText = [SKTexture textureWithCGImage:cgImg];
I've done a lot of experimenting/hacking with SKTextures. My game utilizes SKTextures. It is written in Swift. Specifically, I've had many problems with textureFromNode and textureFromNode:crop: and creating SKPhysicsBodies from textures. These methods worked fine in ios 8, but Apple completely broke them when they released ios 9.0. In ios 9.0, the textures were coming back as nil. Those nil textures broke SKPhysicsBodies from the textures.
I recently worked on serialization/deserialization of SKTextures.
Some key ideas/clues you might investigate are:
Run ios 9.2. Apple Staff mentioned a lot of issues have been fixed. https://forums.developer.apple.com/thread/17463 I've found ios 9.2 helps with SKTextures but didn't solve every issue especially the serialization issues.
Try PrefersOpenGL (set it to "YES" as a Boolean custom property in your config). Here is a post about PrefersOpenGL in the Apple Dev Forums by Apple Staff. https://forums.developer.apple.com/thread/19683 I've observed that ios 9.x seems to use Metal by default rather than OpenGL. I've found PrefersOpenGL helps with SKTexture issues but still doesn't make my SKShaders work (written in GLSL).
When I tried to serialize/deserialize nodes with SKTextures on ios 9.2, I got white boxes instead of visible textures. Inspired by Apple SKTexture docs that say, "The texture data is loaded when:
The size method on the texture object is called.
Another method is called that requires the texture’s size, such as creating a new SKSpriteNode object that uses the texture object.
One of the preload methods is called (See Preloading the Texture Data.)
The texture data is prepared for rendering when:
A sprite or particle that uses the texture is part of a node tree that is being rendered."
... I've hacked a workaround that creates a secondary texture from the CGImage() call:
// ios 9.2 workaround for white boxes on serialization
let img = texture!.CGImage()
let uimg = UIImage(CGImage: img)
let ntex = SKTexture(image: uimg)
let sprite = SKSpriteNode(texture: ntex, size: texture!.size())
So now my SKSpriteNodes created this way seem to serialize/deserialize fine. BTW, just invoking size() or creating an SKSpriteNode with the original texture does not seem to be enough to reify the texture into memory.
You didn't ask about textureFromNode:crop: but I'm adding observations anyway just in case it helps you: I've found this method in ios 8 worked (although the crop parameters were very tricky and seemed to require normalization with UIScreen.mainScreen().scale) In ios 9.0, this method didn't work at all (returned nil). In ios 9.2 this method now works (it now returns a non-nil texture) however subsequent creation of nodes from the texture do not need the size normalization. And furthermore, to make serialization/deserialization work, I found you ultimately have to do #3 above.
I hope this helps you. I imagine I've struggled more than most with SKTextures since my app is so dependent on them.
I tested your code in Xcode 7 and found texture returned in generateTexture was null. That's the reason why you can't load anything from the file, and you even haven't saved anything.
Try to use NSLog to log the description of your texture or sprite. E.g. add this line in generateTexture:
NSLog(#"texture: %#", texture);
What you will get in console:
texture: '(null)' (300 x 300)
And same for s1 and dict1 in your code:
s1: name:'(null)' texture:[ '(null)'
(300 x 300)] position:{100, 100} scale:{1.00, 1.00} size:{100, 100}
anchor:{0.5, 0.5} rotation:0.00
dict1: {
object = " '(null)' (300 x 300)"; }
You may do these tests on both iOS 8 and iOS 9 and you will probably get different results.
I'm not sure why you add the SKShapeNode to a scene and then save the texture from the scene. One workaround is to set texture for your SKShapeNode, and your code should work fine.
shapeNode.fillTexture = [SKTexture textureWithImageNamed:#"Spaceship"];
SKTexture *texture = shapeNode.fillTexture;
return texture;
Update:
It's quite annoying that textureFromNode doesn't works as expected in iOS 9. I tried to solve it by trial and error but no luck at last. Thus, I asked you if you would consider make a snapshot of the whole screen and set it as your thumbnail. Here's the progress I made today and hope you will get inspired from it.
I created a scene which contained SKLabelNode and SKSpriteNode in didMoveToView. After I clicked anywhere on screen, snapshot would be invoked and the down-scaled screenshot would be saved in the document folder. I used the code here.
- (UIImage *)snapshot
{
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0.5);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
Since the thumbnail is saved as UIImage therefore loading it back for the sprite's texture should be done easily. A sample project demonstrates the whole process and it works on both iOS 8 and 9.

Creating a Configuration File in Xcode?

new to Xcode. I have a static library creating a view, but I want to allow a new project to set the view frame in a configuration file that can change the frame size value in the static library. Some kind of global #define variable.
How do I do this in Xcode? I've looked into pbxuser files, pch, xconfigs, and plists but I'm totally lost as to where exactly I'm supposed to set this up.
You are supposed to pass the proper configuration (whether it will be view frame, color or any other parameters) when you're initializing class instance defined in the static library.
Don't overcomplicate things. You don't need any configuration file for that.
Lets say your config.plist file is under you project folder and added to your project :
NSString *configPath = [[NSBundle mainBundle] pathForResource:#"config" ofType:#"plist"];
NSDictionary *config = [NSDictionary dictionaryWithContentsOfFile:configPath];
float xStartPoint = [[config objectForKey:#"xStartPoint"] floatValue];
float yStartPoint = [[config objectForKey:#"yStartPoint"] floatValue];
float objectWidth = [[config objectForKey:#"objectWidth"] floatValue];
float objectHeight = [[config objectForKey:#"objectHeight"] floatValue];
UIView *exampleView = [[UIView alloc] initWithFrame:CGRectMake(xStartPoint, yStartPoint, objectWidth, objectHeight)];
[self.view addSubview:exampleView];
Example of the Plist file :
The .plist code
Create a plist and read the value on load. This would allow you to externally modify the plist file allowing for easy changes outside of Xcode (if that's his wish).
iPhone read/write .plist file

How to insert images through Open Gl in ios?

I have just started learning OpenGl, i have read about Rendering and texture image. If anyone can provide a simple example to insert image.
OpenGL itself doesn’t provide API to generate a texture from image file. We had to read image file and convert it to bitmap data to use texture in OpenGL before, but GLKTextureLoader class in GLKit enables us generating texture from image automatically.
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"mushroom" ofType:#"png"];
GLKTextureInfo* textureInfo =
[GLKTextureLoader textureWithContentsOfFile:filePath options:nil error:nil];
if (textureInfo) {
NSLog(#"Texture loaded successfully. name = %d size = (%d x %d)",
textureInfo.name, textureInfo.width, textureInfo.height);
}
return value of textureWithContentsOfFile:options:error: method is an instance of GLKTextureInfo class. A GLKTextureInfo object contains information about a texture such as width, height… There is a property named ‘name’ (documented as ‘glName’ and it should be misprint). We use value in this property to specify texture in GLKBaseEffect (Will be described later)
GLKTextureLoader is a class for loading textures, so we should release texture by our self. Call glDeleteTextures with value of ‘name’ property in GLKTextureInfo object.
Found this nice discussion in a blog. Hope this helps.. :)

Don't find Textures images into a SKTextureAtlas

I'm using spritekit on Xcode 5. I created a folder called images with ".atlas" extension and these images inside: bear1.png, bear2.png and bear3.png
During the simulation this SKTextureAtlas is founded by “[SKTextureAtlas atlasNamed:#"images”]” but when i try to recovery some image using “[ursoAtlas textureNamed:#"bear1"]" it doesn't find the image. I printed the variable temp and it returns "MissingResource.png" and numImages in the code is 0. i expected that numImages was 3.
SKTextureAtlas *ursoAtlas = [SKTextureAtlas atlasNamed:#"images"];
SKTexture *temp = [ursoAtlas textureNamed:#"bear1"];
NSLog(#"%#", temp);
int numImages = ursoAtlas.textureNames.count;
NSLog(#"%d", numImages);
Everything look fine.
Did you drag images.atlas into the project?
or you can try to create new test_image.atlas and drag into project.

Resources