【问题标题】:Save generated SKTexture to file将生成的 SKTexture 保存到文件
【发布时间】:2015-12-18 12:40:45
【问题描述】:

我现在已针对以下问题提交了一个错误。任何有好的人 解决方法?

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

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

此代码在 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

鱼东更新:

这可能是一个更相关的例子,但假设场景由 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];

【问题讨论】:

  • 如果我错了,请纠正我:您想要结束场景(可能是场景的一部分)的“快照”,并将其用作按钮的纹理。所以你会考虑拍摄真实的快照而不是保存场景纹理。
  • 您要存储的缩略图是全屏内容还是屏幕的一部分?
  • 抱歉,我无法提供反馈。问题是比赛并不总是在屏幕上播放。极端情况下,比赛可以由两个 AI 玩家进行,完全在 CPU 中。屏幕上看不到比赛,但我还是想拍个快照。在更常见的情况下,我想在快照模式下制作场景的特殊变体,例如更大的字体。这只是对上述问题的回答,我也会评论你的回答。
  • 在 XCode 11.7 iPhone 8 模拟器 (iOS 13.7) 上测试。纹理 CGImage 是一个空的透明图像。纹理本身包含正确的图像。不知道是模拟器bug还是其他。

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


【解决方案1】:

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

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

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

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

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

您的代码中的 s1 和 dict1 也是如此:

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

dict1: { 对象 = "'(null)' (300 x 300)"; }

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

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

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

更新:

textureFromNode 在 iOS 9 中不能按预期工作,这很烦人。我试图通过反复试验来解决它,但最后没有运气。因此,我问你是否会考虑制作整个屏幕的快照并将其设置为缩略图。这是我今天取得的进展,希望你能从中得到启发。

我在didMoveToView 中创建了一个包含SKLabelNode 和SKSpriteNode 的场景。在我点击屏幕上的任意位置后,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。

【讨论】:

  • 谢谢。我会奖励你 Yudong,但我需要一个真正的解决方法。我真正的 SKScene 要复杂得多。我只是做了一个基本的例子。我的主要目标是生成一个场景的小型可存储图像,以供以后在精灵中使用。我可以看到 NSLog 返回 NULL,但 generateTexture 在 didMoveToView 的第 4 行返回某种有效纹理也是一个事实。这不是很奇怪吗?我的一般问题:如何快速存储非活动场景的缩放/裁剪快照以供以后用作精灵中的纹理?
  • @FredrikJohansson 第 4 行的代码显示了一个具有空纹理的有效精灵节点,我不知道 (300 x 300) 来自哪里。你能展示一些更复杂的例子来说明你在做什么吗?明天我会仔细看看你的问题。
  • 我已经更新了最后的问题。我的意思是,我的场景非常复杂,有一个 HUD 层、CTRL 层、世界层等,每个层都有很多精灵和东西。但我想场景中只需要一个精灵就可以显示问题。我什至无法存储只有一个精灵的场景纹理! ;) 感谢您的帮助。
  • 我会奖励你的,玉东。非常感谢!但我想您的解决方案需要将场景显示在屏幕上。那是对的吗?而且我猜你的感觉也是纹理的处理不正确?我已经向 Apple 报告了这种行为。让我们看看它是否被纠正。再次,非常感谢。
  • @FredrikJohansson 是的,我的解决方案需要所有内容都已经在屏幕上。如果您向我发送有关 Apple 反馈的消息,我会及时跟进您的问题。顺便说一句,我很期待你的比赛。祝你好运!
【解决方案2】:

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

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

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

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

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

  3. 当我尝试在 ios 9.2 上使用 SKTextures 序列化/反序列化节点时,我得到了白框而不是可见纹理。受 Apple SKTexture 文档的启发,该文档说:“纹理数据在以下情况下加载:

纹理对象的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。

我希望这对你有帮助。我想我在使用 SKTextures 时遇到的困难比大多数人都多,因为我的应用非常依赖它们。

【讨论】:

  • 如果你写博客、写书或任何东西,我会捐出冷钱。
  • Russel,我希望有一天能帮到你。非常感谢...我目前的计划是等待白色的9.2。也许它只是在那时起作用。希望如此。我会尝试更新帖子。
  • Russell:我已经用受您的代码启发的解决方法更新了这个问题。非常感谢!如您所见,我不必使用 UIImage。它适用于 iOS 9.1/XCode7/Metal。
猜你喜欢
  • 2019-05-07
  • 2012-04-30
  • 2014-05-17
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多