I'm working on a SpriteKit game and trying to allow the user to customize the ship image in the game by colorizing it.

I added a category to UIImage that provides the implementation for adjustImage:hue:saturation:brightness. It uses two CIFilters to make the adjustments. I know that that code works because I tested it in another app using a UIImageView instead of an SKTexture.

Also note that CSHeroShipNode is a subclass of SKSpriteNode.

The problem that arises is that I get a bad access on the indicated line and I have no idea why. Debugging shows that the image and texture are both never nil anywhere in the process, even when passed into the second method.

The following code is from the CSHeroShipNode class.

+(instancetype)heroShip {
    UIImage *textureImage = [UIImage adjustImage:[UIImage imageNamed:@"heroShip_0001.png"] hue:0.0 saturation:1.0 brightness:0.0];
    SKTexture *heroTexture = [SKTexture textureWithImage:textureImage];
    CSHeroShipNode *hero = [CSHeroShipNode shipWithTexture:heroTexture size:CGSizeMake(64, 64)];
    return hero;
}

+(instancetype)shipWithTexture:(SKTexture *)texture size:(CGSize)size {
    /***** this next line causes it to crash *****/
    CSHeroShipNode *ship = [CSHeroShipNode spriteNodeWithTexture:texture size:size];

    ... // set other properties

    return ship;
}

And here is a backtrace after the crash.

* thread #1: tid = 0xb100f, 0x0000000102c2ee3d libsystem_platform.dylib`OSSpinLockLock + 7, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
frame #0: 0x0000000102c2ee3d libsystem_platform.dylib`OSSpinLockLock + 7
frame #1: 0x00000001012edaa1 SpriteKit`SKSpinLockSync(int*, void () block_pointer) + 30
frame #2: 0x00000001012b6ce7 SpriteKit`-[SKTexture loadImageData] + 221
frame #3: 0x00000001012b921f SpriteKit`-[SKTexture size] + 33
frame #4: 0x00000001012d766c SpriteKit`-[SKSpriteNode initWithTexture:] + 96
frame #5: 0x00000001012d77d8 SpriteKit`+[SKSpriteNode spriteNodeWithTexture:] + 76
frame #6: 0x00000001012d7857 SpriteKit`+[SKSpriteNode spriteNodeWithTexture:size:] + 77
frame #7: 0x0000000100006b98 Space`+[CSHeroShipNode shipWithTexture:size:](self=0x0000000100022300, _cmd=0x000000010001753b, texture=0x0000000116354ab0, size=CGSize at 0x00007fff5fbfc7d8) + 136 at CSHeroShipNode.m:27
frame #8: 0x0000000100006a3f Space`+[CSHeroShipNode heroShip](self=0x0000000100022300, _cmd=0x00000001000175c9) + 287 at CSHeroShipNode.m:22
frame #9: 0x0000000100010d3e Space`-[CSSurvivalGameLevel getHeroShip](self=0x0000000116341bf0, _cmd=0x0000000100017a14) + 46 at CSSurvivalGameLevel.m:33
frame #10: 0x00000001000152c5 Space`+[CSWorldNode worldNodeWithLevel:scene:](self=0x0000000100022a80, _cmd=0x0000000100017be7, level=0x0000000116341bf0, scene=0x000000011634d490) + 837 at CSWorldNode.m:24
frame #11: 0x000000010000b8d2 Space`-[CSGameScene initWithSize:level:](self=0x000000011634d490, _cmd=0x0000000100017b24, size=CGSize at 0x00007fff5fbfcc80, level=0x0000000116341bf0) + 1202 at CSGameScene.m:70
frame #12: 0x000000010000b3f1 Space`+[CSGameScene gameSceneWithSize:level:](self=0x0000000100022530, _cmd=0x0000000100017a64, size=CGSize at 0x00007fff5fbfcce0, level=0x0000000116341bf0) + 113 at CSGameScene.m:47
frame #13: 0x0000000100014e65 Space`-[CSMainMenuScene newGame](self=0x000000010c0154d0, _cmd=0x00000001000187c0) + 133 at CSMainMenuScene.m:44
frame #14: 0x00000001000056c1 Space`-[CSButtonNode callAction](self=0x000000010c02a280, _cmd=0x00000001000173ab) + 209 at CSButtonNode.m:41
frame #15: 0x00000001000057d1 Space`-[CSButtonNode pressEnded](self=0x000000010c02a280, _cmd=0x0000000100017413) + 129 at CSButtonNode.m:52
frame #16: 0x00000001000064a4 Space`-[CSButtonNode touchesEnded:withEvent:](self=0x000000010c02a280, _cmd=0x0000000100a00ce3, touches=0x000000011634cd50, event=0x000000010c501460) + 772 at CSButtonNode.m:81
frame #17: 0x00000001012c0df4 SpriteKit`-[SKView touchesEnded:withEvent:] + 540
frame #18: 0x00000001003a3c15 UIKit`-[UIWindow _sendTouchesForEvent:] + 701
frame #19: 0x00000001003a4633 UIKit`-[UIWindow sendEvent:] + 988
frame #20: 0x000000010037dfa2 UIKit`-[UIApplication sendEvent:] + 211
frame #21: 0x000000010036bd7f UIKit`_UIApplicationHandleEventQueue + 9549
frame #22: 0x0000000101a5fec1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #23: 0x0000000101a5f792 CoreFoundation`__CFRunLoopDoSources0 + 242
frame #24: 0x0000000101a7b61f CoreFoundation`__CFRunLoopRun + 767
frame #25: 0x0000000101a7af33 CoreFoundation`CFRunLoopRunSpecific + 467
frame #26: 0x0000000101f063a0 GraphicsServices`GSEventRunModal + 161
frame #27: 0x000000010036e043 UIKit`UIApplicationMain + 1010
frame #28: 0x0000000100016253 Space`main(argc=1, argv=0x00007fff5fbfed28) + 115 at main.m:16
frame #29: 0x0000000102c255fd libdyld.dylib`start + 1
frame #30: 0x0000000102c255fd libdyld.dylib`start + 1

EDIT: Here is the whole CSHeroShipNode class.

#import "CSHeroShipNode.h"

#import "CSGameScene.h"
#import "CSPhaserNode.h"

@implementation CSHeroShipNode {
    SKEmitterNode *exhaust;
}

+(instancetype)heroShip {
    UIImage *textureImage = [UIImage adjustImage:[UIImage imageNamed:@"heroShip_0001.png"] hue:0.0 saturation:1.0 brightness:0.0];
    SKTexture *heroTexture = [SKTexture textureWithImage:textureImage];
    CSHeroShipNode *hero = [CSHeroShipNode shipWithTexture:heroTexture size:CGSizeMake(64, 64)];
    return hero;
}

+(instancetype)shipWithTexture:(SKTexture *)texture size:(CGSize)size {
    CSHeroShipNode *ship = [CSHeroShipNode spriteNodeWithTexture:texture size:size];

    ship.name = @"Hero";
    ship.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:size.width / 2];
    ship.physicsBody.categoryBitMask = CSPhysicsBodyCollisionTypeHeroShip;
    ship.physicsBody.collisionBitMask = CSPhysicsBodyCollisionTypeWorld;
    ship.physicsBody.contactTestBitMask = CSPhysicsBodyCollisionTypeEnemyPhaser | CSPhysicsBodyCollisionTypeEnemyShip | CSPhysicsBodyCollisionTypeObject;

    ship->exhaust = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"Exhaust" ofType:@"sks"]];
    CGPoint exhaustPosition = CGPointMake(ship.position.x, ship.position.y - ship.size.height / 2 + 7);
    ship->exhaust.position = exhaustPosition;
    [ship addChild:ship->exhaust];

    // set the initial health
    ship.health = 200;

    ship.weaponCharge = 100;
    ship.weaponChargeRate = 100;

    return ship;
}

-(void)fireToLocation:(CGPoint)location atTime:(CFTimeInterval)gameTime {
    // delay phaser firing due to recharge time
    if (self.weaponCharge == 100) {
        lastFireTime = gameTime;
        self.weaponCharge = 0;

        float xOffset = location.x - self.position.x;
        float yOffset = location.y - self.position.y;
        float angle = atan2f(yOffset, xOffset);

        CSPhaserNode *phaser = [[CSPhaserNode alloc] initWithPosition:self.position angle:angle time:gameTime];
        phaser.parentShip = self;
        [((CSGameScene *)self.scene).world addChild:phaser];
    }
}

-(void)takeDamage:(NSUInteger)damage {
    self.health -= damage;
}

-(void)die {
    // make sure that health is 0
    self.health = 0;
    // create an explosion for the ship
    SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"ShipExplosion" ofType:@"sks"]];
    explosion.position = self.position;
    [((CSGameScene *)self.scene).world addChild:explosion];
    [self removeFromParent];
}

-(void)update:(CFTimeInterval)gameTime {
    [super update:gameTime];
}


@end
有帮助吗?

解决方案

It turns out that this is a bug in the SpriteKit code (see this answer to a very similar question). "Following the lldb trace, I would guess it's a memory management problem with how it allocates the image data buffers when using filters."

I ended up using a CIContext in my adjustImage:hue:saturation:brightness: method to create a CGImageRef and then returned that. So the code in CSHeroShipNode ends up looking like this

+(instancetype)heroShip {
    CGImageRef textureImage = [UIImage adjustImage:[UIImage imageNamed:@"heroShip_0001.png"] hue:0.0 saturation:1.0 brightness:0.0];
    SKTexture *heroTexture = [SKTexture textureWithCGImage:textureImage];
    CSHeroShipNode *hero = [CSHeroShipNode shipWithTexture:heroTexture size:CGSizeMake(64, 64)];
    return hero;
}

While this workaround isn't terrible I still find it annoying that Apple's code has issues like this. Hopefully they'll fix it soon.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top