将生成的SKTexture保存到文件中

时间:2015-09-21 17:49:43

标签: ios objective-c sprite-kit ios9 sktexture

  

我现在已经为下面的问题提交了一个错误。任何人都有好的   解决办法:

我尝试将SKTexture保存到文件中,然后重新加载,但我没有成功。以下代码段可以复制到Xcode启动项目中的 GameScene.m

我在textureFromNode中使用generateTexture,这似乎是我问题的根本原因。如果我使用精灵中的纹理,则代码可以工作,并且可以看到两个太空飞船。

此代码在iOS 8中有效,但它在Xcode7& iOS 9.我只想在提交错误报告之前验证这是一个错误。我担心的是我对NSKeyedArchiver做错了。

它发生在模拟器和设备上。

#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
  

Yudong更新:

这可能是一个更相关的例子,但想象一下场景由4层组成,有很多精灵。当游戏结束时,我想存储比赛结束场景的缩略图。该图像将用作按钮上的纹理。按下该按钮将开始比赛的重播电影。将有许多按钮与旧游戏的图像,所以我需要将每个图像存储在文件中。

-(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;
}
  

解决方案/解决方法:

受到Russells代码的启发,我做了以下工作。它有效!

CGImageRef  cgImg = texture.CGImage;
SKTexture *newText = [SKTexture textureWithCGImage:cgImg];

2 个答案:

答案 0 :(得分:4)

我已经使用SKTextures进行了大量的实验/黑客攻击。我的游戏使用SKTextures。它是用Swift编写的。具体来说,我在textureFromNode和textureFromNode:crop:和纹理创建SKPhysicsBodies方面遇到了很多问题。这些方法在ios 8中运行良好,但Apple在发布ios 9.0时完全破坏了它们。在ios 9.0中,纹理以零形式返回。那些零纹理从纹理中打破了SKPhysicsBodies。

我最近致力于SKTextures的序列化/反序列化。

您可能会调查的一些关键想法/线索是:

  1. 运行ios 9.2。 Apple员工提到很多问题已得到修复。 https://forums.developer.apple.com/thread/17463我发现ios 9.2对SKTextures有帮助,但没有解决每个问题,尤其是序列化问题。

  2. 尝试PrefersOpenGL(将其设置为" YES"作为配置中的布尔自定义属性)。以下是Apple Staff的Apple Dev论坛中关于PrefersOpenGL的帖子。 https://forums.developer.apple.com/thread/19683我观察到ios 9.x似乎默认使用Metal而不是OpenGL。我发现PrefersOpenGL有助于解决SKTexture问题,但仍然无法使我的SKShaders工作(用GLSL编写)。

  3. 当我尝试在ios 9.2上使用SKTextures序列化/反序列化节点时,我得到的是白色方框而不是可见纹理。灵感来自苹果SKTexture文档说,"纹理数据在以下情况下加载:

  4. 调用纹理对象上的size方法。

    调用另一个需要纹理大小的方法,例如创建一个使用纹理对象的新SKSpriteNode对象。

    调用其中一个预加载方法(请参阅预加载纹理数据。)

    纹理数据准备在以下情况下渲染:

    使用纹理的精灵或粒子是正在渲染的节点树的一部分。"

    ...我已经破解了一个从CGImage()调用创建辅助纹理的变通方法:

            // 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())
    

    所以现在我用这种方式创建的SKSpriteNodes似乎很好地序列化/反序列化。顺便说一句,只是调用size()或用原始纹理创建一个SKSpriteNode似乎不足以将纹理重新化为内存。

    1. 你没有问过textureFromNode:crop:但是我还是在添加观察结果以防它对你有所帮助:我发现这个方法在ios 8中有效(尽管作物参数非常好)棘手,似乎需要使用UIScreen.mainScreen()。scale进行规范化。在ios 9.0中,这种方法根本不起作用(返回nil)。在ios 9.2中,此方法现在可以工作(它现在返回一个非零纹理)但是从纹理中后续创建节点不需要大小规范化。此外,为了使序列化/反序列化工作,我发现你最终必须做上面的#3。
    2. 我希望这会对你有所帮助。我想,由于我的应用程序非常依赖于它,因此我使用SKTextures比大多数人更挣扎。

答案 1 :(得分:2)

我在Xcode 7中测试了您的代码,发现texture中返回的generateTexture为空。这就是为什么你无法从文件中加载任何东西的原因,你甚至还没有保存任何东西。

尝试使用NSLog记录纹理或精灵的描述。例如。在generateTexture中添加此行:

NSLog(@"texture: %@", texture);

您将在控制台中获得什么:

  

纹理:'(null)' (300 x 300)

代码中s1dict1的内容相同:

  

s1:name:'(null)'纹理:['(null)'   (300 x 300)]位置:{100,100}比例:{1.00,1.00}大小:{100,100}   锚:{0.5,0.5}旋转:0.00

     

dict1:{       object =" '(空)' (300 x 300)&#34 ;; }

您可以在iOS 8和iOS 9上进行这些测试,您可能会得到不同的结果。

我不确定为什么要将SKShapeNode添加到scene,然后从scene保存纹理。一种解决方法是为SKShapeNode设置纹理,您的代码应该可以正常工作。

shapeNode.fillTexture = [SKTexture textureWithImageNamed:@"Spaceship"];
SKTexture *texture = shapeNode.fillTexture;
return texture;

<强>更新

令人讨厌的是,textureFromNode在iOS 9中没有像预期的那样发挥作用。我试图通过反复试验来解决它,但最后没有运气。因此,我问你是否考虑制作整个屏幕的快照并将其设置为缩略图。这是我今天取得的进步,希望你能从中获得灵感。

我在SKLabelNode中创建了一个包含SKSpriteNodedidMoveToView的场景。点击屏幕上的任意位置后,将调用snapshot,并将缩小的屏幕截图保存在文档文件夹中。我使用了代码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;
}

由于缩略图保存为UIImage,因此应该轻松地将其加载回精灵的纹理。 A sample project演示了整个过程,它适用于iOS 8和9。