I have a sprite with a given texture using:
joyStickRight = [SKSpriteNode spriteNodeWithImageNamed:#"joyStick.png"];
And I'd like to change it when the user touches and holds the sprite. When I detect the touch, I try changing the sprite texture by calling the same function with a different image:
joyStickRight = [SKSpriteNode spriteNodeWithImageNamed:#"joyStick_rollOver.png"];
But this does not seem to work. Nothing changes.
This for an iPad application. I am creating the on screen elements with SKSpriteNodes.
This is likely to be related to variable scope. I'm guessing you are trying to modify another instance of 'joyStickRight' SKSPriteNode. This second instance would not have been added to the scene and therefore not taking any effect.
The only way i found out to change textures ist to swap them via action like this:
SKTexture *texture1 = [SKTexture textureWithImageNamed:#"texture1"];
SKTexture *texture2 = [SKTexture textureWithImageNamed:#"texture2"];
SKAction *swapTextures = [SKAction repeatAction:[SKAction animateWithTextures:#[texture1,texture2] timePerFrame:0.1] count:3];
[node (in your case joyStickRight) runAction:swapTextures];
I still hope this is not the only solution, and also wonder why it does not work when i run it once!!!
Related
I'm using texture atlases in my Sprite Kit game. I'm creating SKTextureAtlas object and store it's textures in array for each animation. So when I need some animation on my hero I call animateWithTextures sending it the corresponding array. There are some lags when I start animations. Is there some way to start animation smoothly?
I am sure there are few ways to get around this. What you need to do is to preload an atlases before your gameplay actually start. Just show a loading screen at the beginning of the game and preload your atlases.
You may try with + preloadTextureAtlases:withCompletionHandler:
[SKTextureAtlas preloadTextureAtlases:textureAtlasesArray withCompletionHandler:^{ /*Game Start*/}];
Another way to implement resource loading before everything else (and keep everything in memory) is described here in Adventure game example
For more details about loading assets asynchronously take a peek into code which can be downloaded from the link above.
had the same problem and I solved it in my game by not using atlases. So try this example:
-(void)makePlayerAnimation:(SKSpriteNode *)player
{
SKTexture *texture1 = [SKTexture textureWithImageNamed:#"texture1.png"];
SKTexture *texture2 = [SKTexture textureWithImageNamed:#"texture2.png"];
SKTexture *texture3 = [SKTexture textureWithImageNamed:#"texture3.png"];
SKAction *animationTextures = [SKAction animateWithTextures:#[texture1, texture2, texture3] timePerFrame:0.1];
[player runAction:animationTextures];
}
When you wish to activate animation do this:
[self makePlayerAnimation:myNode];
or
[self makePlayerAnimation:self.myNode];
Just depends how you declared it.
If you need to run animation forever, you can just add line at the end of previous method:
SKAction *repeat = [SKAction repeatActionForever: animationTextures];
Hope this helps.
I am making a game and I noticed that during some scenes, my FPS kept dropping around the 55-60FPS area (using texture atlas). This drove me nuts so I decided to put all my assets to the Images.xcassets folder and voila, steady 60FPS.
I thought this was a fluke or that I was doing something wrong, so I decided to start a new project and perform some benchmarks...
Apple's documentation says that using texture atlas's will improve app performance. Basically, allowing your app to take advantage of batch rendering. However...
The Test (https://github.com/JRam13/JSGlitch):
- (void)runTest
{
SKAction *spawn = [SKAction runBlock:^{
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
sprite.position = CGPointMake(0, [self randomNumberBetweenMin:0 andMax:768]);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
[sprite runAction:move];
//notice I don't remove from parent (see test2 below)
[self addChild:sprite];
}
}];
SKAction *wait = [SKAction waitForDuration:.1];
SKAction *sequence = [SKAction sequence:#[spawn,wait]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
Results:
Tests repeatedly show that using the xcassets performed way better than the atlas counterpart in FPS. The atlas does seem to manage memory marginally better than the xcassets though.
Anybody know why these results show that images.xcassets has better performance than the atlas?
Some hypotheses I've come up with:
xcassets is just better optimized than atlasas.
atlasas are good at drawing lots of images in one draw pass, but have bigger overhead with repeated sprites. If true, this means that if your sprite appears multiple times on screen (which was the case in my original game), it is better to remove it from the atlas.
atlas sheets must be filled in order to optimize performance
Update
For this next test I went ahead and removed the sprite from parent after it goes offscreen. I also used 7 different images. We should see a huge performance gain using atlas due to the draw count but...
First, revise your test to match this :
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
float spawnY = arc4random() % 768;
sprite.position = CGPointMake(0, spawnY);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
// next three lines replace the runAction line for move
SKAction *remove = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:#[move, remove]];
[sprite runAction:sequence];
[self addChild:sprite];
}
Rerun your tests and you should notice that your framerate NEVER deteriorates as in your tests. Your tests were basically illustrating what happens when you never remove nodes, but keep creating new ones.
Next, add the following line to your ViewController when you set up your skview :
skView.showsDrawCount = YES;
This will allow you to see the draw count and properly understand where you are getting your performance boost with SKTextureAtlas.
Now, instead of having just one image , gather 3 images and modify your test by choosing a random one of those images each time it creates a node, you can do it something like this :
NSArray *imageNames = #[#"image-0", #"image-1", #"image-2"];
NSString *imageName = imageNames[arc4random() % imageNames.count];
In your code, create your sprite with that imageName each time through the loop. ie :
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:imageName];
In your SKTextureAtlas test, use that same imageName obviously to create each sprite.
Now... rerun your tests and take note of the draw count in each test.
This should give you a tangible example of what batch rendering is about with SKTextureAtlas.
It's no about rendering the same sprite image thousands of times.
It's about drawing many different sprites images in the same draw pass.
There is likely some overhead in getting this rendering optimization, but I think the draw count should be self explanatory as to why that overhead is moot when all things are considered.
Now, you can hypothesize some more :)
UPDATE
As mentioned in the comments, my post was to expose why your test was not a good test for the benefits of SKTextureAtlas and was flawed if looking to analyze it in a meaningful way. Your test was like testing for measles with a mumps test.
Below is a github project that I put together to pinpoint where an SKTextureAtlas is appropriate and indeed superior to xcassets.
atlas-comparison
Just run the project on your device and then tap to toggle between tests. You can tell when it's testing with SKTextureAtlas because the draw count will be 1 and the framerate will be 60fps.
I isolated what will be optimized with a SKTextureAtlas. It's a 60 frame animation and 1600 nodes playing that animation. I offset their start frames so that they all aren't on the same frame at the same time. I also kept everything uniform for both tests, so that it's a direct comparison.
It's not accurate for someone to think that using SKTextureAtlas will just optimize all rendering. It's optimization comes by reducing draw count via batch rendering. So, if your framerate slowdown is not something that can be improved via batch rendering, SKTexture atlas is the wrong tool for the job. right ?
Similar to pooling of objects, where you can gain optimization via not creating and killing your game objects constantly, but instead reusing them from a pool of objects. However if you are not constantly creating and killing objects in your game, pooling ain't gonna optimize your game.
Based on what I saw you describe as your game's issue in the discussion log , pooling is probably the right tool for the job in your case.
Since JSTileMap extends SKNode, you can use the API to move and animate your tilemap like any other node. However, I keep getting this weird effect/glitch...
Code:
_tiledMap = [JSTileMap mapNamed:#"Cloud.tmx"];
if (_tiledMap) {
[self addChild:_tiledMap];
}
_tiledMap.position = CGPointMake(800, 0);
SKAction *scrollLeft = [SKAction moveTo:CGPointMake(600, 0) duration:4];
SKAction *scrollRight = [SKAction moveTo:CGPointMake(700, 0) duration:4];
SKAction *sequence = [SKAction sequence:#[scrollLeft, scrollRight]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[_tiledMap runAction:repeat];
Results:
As you can see, whenever the JSTileMap changes direction, depending if its left or right, the image gets cropped or something, I can't explain it. This doesn't happen if the node itself is a SKSpriteNode. I added numbers to the background image for visual reference.
EDIT
Further tests reveal that moving the JSTileMap's position manually (_tiledMap.position.x = x+1) in the update loop, has the same effect. It crops the image/tile when it animates left, and returns to normal when it animates to the right.
I found a work-around. Apparently the problem is that the first tileset column itself is being cropped for some reason (if anyone figures this out please let me know). So the solution is to create a tilemap that is 2 tile units wider than what your original tilemap dimension is. For example, if your tiles are set to 32x32 (tilemap of 1024x768), you should generate a tilemap of 1088x768 instead and start drawing after the first column.
See image below.
It seems I was using an old/unmaintained version of JSTileMap. Slycrel's version of JSTileMap addresses this issue.
https://github.com/slycrel/JSTileMap
I am trying to mirror my SKSpriteNode using SKAction *mirrorDirection = [SKAction scaleXTo:-1 y:1 duration:0.0]; but every time I do, the sprite's physics body seems to get messed up and it drops off the screen. I have several objects (floors) beneath the sprite and it falls through all of them as if they are not there.
This only happens when I mirror the sprite. Anyone know how to fix this?
Update:
Reseting the physics body as one answer suggested unfortunately did not fix the problem. It appears that only the contact part of the physics body malfunctions. Gravity still has an effect as the little guy drops like a rock.
I also tried to reset by again self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
right after the self.xScale = -1; but this also did not fix the issue.
Add your mirror-able node as a child of some regular SKNode and set the physics body on this parent node instead of the negatively scaled node.
I don't know how to fix this but I would like to suggest one way you could create a mirrored sprite.
To achieve this set the x or y scale of your sprite node to -1. Then use the SKView method textureFromNode: to create a texture from this mirrored node.
You can then use this texture to create a new sprite node that is mirrored but doesn't require any negative scaling.
Don't use an SKAction, just set it directly to the SKSpriteNode's xScale property.
self.yourSprite.xScale = -1;
Roecrew is right about setting the xScale property directly. I would suggest you try this:
node.xScale = -1;
node.physicsBody = node.physicsBody;
You will need to 'reset' the physicsBody each time you change the xScale property.
The xScale issue with physicsBody is a bug in SpriteKit, but I was able to 'retain' the physicsBody using the second line.
I'm meeting the problem exactly like yours. I spent about 2 hours to figure it out.
Just init your physicsBody after you scaleX, i dont know why but i had correct this issue by doing this way.
walkRight = [SKAction sequence:#[resetDirection,[SKAction runBlock:^{
[self changePhysicsDirectionRight];
}],[SKAction repeatActionForever: walk]]];
walkLeft = [SKAction sequence:#[mirrorDirection,[SKAction runBlock:^{
[self changePhysicsDirectionLeft];
}],[SKAction repeatActionForever: walk]]];
walkRight and walkLeft is my action when changing direction, and resetDirection and mirrorDirection is exactly the action i used to scaleXTo:1 and scaleXTo:1
So after i scaleXTo i use a method call changePhysicsDirectionRight to re-init my physicBody like
- (void)changePhysicsDirectionRight{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width,self.size.height)];
self.physicsBody.categoryBitMask = guyCategory;
self.physicsBody.contactTestBitMask = 0;
self.physicsBody.collisionBitMask = 0;
}
Remember to re-assign all your category and everything like you init before.
I hope someone pro at spritekit can tell me the reason why ....
I am trying to create a conveyor belt effect using SpriteKit like so
MY first reflex would be to create a conveyor belt image bigger than the screen and then move it repeatedly forever with actions. But this does not seem ok because it is dependent on the screen size.
Is there any better way to do this ?
Also obviously I want to put things (which would move independently) on the conveyor belt so the node is an SKNode with a the child sprite node that is moving.
Update : I would like the conveyor belt to move "visually"; so the lines move in a direction giving the impression of movement.
Apply physicsBody to all those sprites which you need to move on the conveyor belt and set the affectedByGravity property as NO.
In this example, I am assuming that the spriteNode representing your conveyor belt is called conveyor. Also, all the sprite nodes which need to be moved are have the string "moveable" as their name property.
Then, in your -update: method,
-(void)update:(CFTimeInterval)currentTime
{
[self enumerateChildNodesWithName:#"moveable" usingBlock:^(SKNode *node, BOOL *stop{
if ([node intersectsNode:conveyor])
{
[node.physicsBody applyForce:CGVectorMake(-1, 0)];
//edit the vector to get the right force. This will be too fast.
}
}];
}
After this, just add the desired sprites on the correct positions and you will see them moving by themselves.
For the animation, it would be better to use an array of textures which you can loop on the sprite.
Alternatively, you can add and remove a series of small sprites with a sectional image and move them like you do the sprites which are travelling on the conveyor.
#akashg has pointed out a solution for moving objects across the conveyor belt, I am giving my answer as how to make the conveyor belt look as if it is moving
One suggestion and my initial intuition was to place a larger rectangle than the screen on the scene and move this repeatedly. Upon reflecting I think this is not a nice solution because if we would want to place a conveyor belt on the middle, in a way we see both it ends this would not be possible without an extra clipping mask.
The ideal solution would be to tile the SKTexture on the SKSpriteNode and just offset this texture; but this does not seem to be possible with Sprite Kit (no tile mechanisms).
So basically what I'm doing is creating subtextures from a texture that is like so [tile][tile](2 times a repeatable tile) and I just show these subtextures one after the other to create an animation.
Here is the code :
- (SKSpriteNode *) newConveyor
{
SKTexture *conveyorTexture = [SKTexture textureWithImageNamed:#"testTextureDouble"];
SKTexture *halfConveyorTexture = [SKTexture textureWithRect:CGRectMake(0.5, 0.0, 0.5, 1.0) inTexture:conveyorTexture];
SKSpriteNode *conveyor = [SKSpriteNode spriteNodeWithTexture:halfConveyorTexture size:CGSizeMake(conveyorTexture.size.width/2, conveyorTexture.size.height)];
NSArray *textureArray = [self horizontalTextureArrayForTxture:conveyorTexture];
SKAction *moveAction = [SKAction animateWithTextures:textureArray timePerFrame:0.01 resize:NO restore:YES];
[conveyor runAction:[SKAction repeatActionForever:moveAction]];
return conveyor;
}
- (NSArray *) horizontalTextureArrayForTxture : (SKTexture *) texture
{
CGFloat deltaOnePixel = 1.0 / texture.size.width;
int countSubtextures = texture.size.width / 2;
NSMutableArray *textureArray = [[NSMutableArray alloc] initWithCapacity:countSubtextures];
CGFloat offset = 0;
for (int i = 0; i < countSubtextures; i++)
{
offset = i * deltaOnePixel;
SKTexture *subTexture = [SKTexture textureWithRect:CGRectMake(offset, 0.0, 0.5, 1.0) inTexture:texture];
[textureArray addObject:subTexture];
}
return [NSArray arrayWithArray:textureArray];
}
Now this is still not ideal because it is necessary to make an image with 2 tiles manually. We can also edit a SKTexture with a CIFilter transform that could potentially be used to create this texture with 2 tiles.
Apart from this I think this solution is better because it does not depend on the size of the screen and is memory efficient; but in order for it to be used on the whole screen I would have to create more SKSpriteNode objects that share the same moveAction that I have used, since tiling is not possible with Sprite Kit according to this source :
How do you set a texture to tile in Sprite Kit.
I will try to update the code to make it possible to tile by using multiple SKSpriteNode objects.