For my application, I am trying to implement a character who displays a walking animation when walking and displays a jumping animation when in the air.
Using Cocos2D, I've created a simple animation. The problem is that the I am using CCRepeatForever and I cannot seem to stop the animation or switch it once I set it off.
Steffen Itterheim's book discussed some animation in the chapter regarding sprites. The way he created an animation was to add multiple sprite frames to a CCAnimation object and then run it with CCRepeatForever.
I'm currently looking at two possiblities:
1) Create multiple sprites and add them to a CCArray and then loop through the multiple sprites.
2) Find an alternate solution to CCRepeatForever and find a way to stop the animation or switch to a different animation.
The problem with number 1 is that I cannot figure out a way to render a selective sprite to the stage. It seems to be that the only way to add a sprite to the screen is to use [self addChild:mySprite]; This is limiting and problematic if I want to switch between multiple sprites. Is there a way to selectively render a sprite?
The problem for number 2 is that there seems to be no alternate to CCRepeatForever and the other animation classes are not sufficient for running the animation.
Thank you!
Assuming you assign a tag to the action like the following.
CCSprite *sprite = [CCSprite spriteWithFile:#"image.png"];
CCRotateBy *spinAction = [CCRotateBy actionWithDuration:1 angle:90];
CCRepeatForever *spinForever = [CCRepeatForever actionWithAction:spinAction];
[spinForever setTag:ANIMATION_TAG];
[sprite runAction:spinForever];
you can stop the animation by calling
[sprite stopActionByTag:ANIMATION_TAG];
Related
I was wondering what the appropriate way was to resize an SKSpriteNode WITH its SKPhysicalBody in SpriteKit. At first I thought I could use SKActions to do resizeToX/Y and that would work. I tried doing this like:
SKAction *action = [SKAction resizeToY:whateverFloat completion:^{
SKPhysicalBody *newBody = [SKPhysicalBody bodyWithRectangleOfSize:spriteNode.size];
spriteNode.physicalBody = newBody;
}];
NOTE : I typed this from memory, its not the exact code, so please ignore syntax errors. I also tried moving around the variables just in case to see what happens. For example, I tried instantiating newBody outside the completion block. I tried assigning the newBody outside the completion block. I tried to update the categoryBitMask and all the same contactBitMasks again. Nothing works.
The closest I got it to working was when I didn't add a new physicalBody, the SKSpriteNode would resize, but its physicalBody would stay the same. If I add a new physicalBody, the whole SKSpriteNode disappears. I don't understand.
After searching online for a solution using SKAction, I found many different threads saying "SKActions don't work well with physics. Avoid mixing them." If I have to avoid mixing them, then how do I grow and shrink an SKSpriteNode the correct way and update its physical body while getting rid of its old one? The physics and collision logic from SpriteKits physics stuff is very important in my game.
I've been mucking around with sprite-kit but found out the only way to affect a sprites coordinate is with sprite.position, the issue here is I only want to affect one sprite coordinate, let's say y. How would I do this?
You should probably read up on SKActions:
SKAction *moveSprite = [SKAction moveToY:200 duration:0]; // or wherever...
[yourSpriteInstance runAction:moveSprite];
There is also a moveToX:duration:, moveTo:duration:, moveBy:duration: to mention a few of the options..
Like #sangony writes in the comments you can of course access the node's position directly through its position property:
sprite.position = CGPointMake(sprite.position.x, sprite.position.y + 200);
This will work just fine.
That being said, SKActions is the way to go about this in most real-life scenarios: E.g. if you want to have the sprite change its position after 3 seconds, then wait another 2 seconds before it begins rotating. It really is one of the cornerstones of the SpriteKit framework...
Working on an iOS game using SpriteKit. My background is made up of map tiles (essentially an infinite map, procedurally generated).
Our system is designed to manage "chunks" of the map, and we only load chunks near the player. Since SpriteKit requires we add SKSpriteNodes, we no longer have clean control over "unloading" sprites for chunks/tiles that are no longer near the player.
I realize SpriteKit won't actually render things off-screen, but it's going to kill performance if we can't remove sprites no longer needed, or check if a chunk/tile is already added.
Since SKNodes doesn't respond to isEqual:, I only see two ways to do this:
Give each sprite a name with their chunk/tile coordinate, and check this name each update
Maintain a separate array of loaded tiles and check that instead
Is there any easier way of checking/removing if a sprite has been added already? Maybe a partial string match on sprite name?
I'm not sure that using SpriteKit is the best solution (Xcode simulator seems to drag at 30fps, have yet to test on a real device). We originally built this game in Java and we're rendering our own textures - hence only what was loaded and could be fed into opengl manually.
-(void) renderToScene:(SKScene *)scene {
for( Chunk *chunk in loadedChunks ){
for( Tile *tile in [chunk getTiles] ){
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:tileTexture];
sprite.name = #"Tile";
sprite.position = CGPointMake(realX,realY);
[scene addChild:sprite];
}
}
}
What will in fact kill your framerate is frequently creating and removing nodes - you should have a pool of sprites that you re-use rather than recreate.
Just update the sprite's texture, position and other attributes when reusing one of those you no longer need. A common use case is to have enough tiles to span the entire screen plus one row and one column, so that when an entire row or column has moved outside the screen you can reposition it at the other side with new textures according to the map data.
If i'm understanding what you're asking correctly. You want to properly remove a node/sprite from the scene if its no longer within view.
You should just be able to call the [self removeFromParent] method to remove in whenever its outside the bounds of the screen. Remember you can call this method on any object as long as its a child.
For instance if i had character,
SKSpriteNode *character;
SKSpriteNode *sword;
//all the rest of the properties are up to you.
if my character picked up a sword in the game and after a certain time period the sword is no longer accessible. I would do:
[character addChild:sword];
which would add it to the character.
[sword removeFromParent];
when i no longer need the sword to be apart of its parent.
i just started using SpriteKit and i'm discovering the API. i have an object to move on screen when a button is pressed and held. so what i did is set an NSTimer while the user is still holding and keep calling the same method to move the object every 0.1 seconds. the object does move but while moving at some point it like doesn't show a smooth transition horizontally. so i used NSLog to look at the objects position and i released that it's subtracting 10 points rather than 0.5. here is the code:
SKAction *moveLeft = [SKAction moveToX:((CGFloat)[[self childNodeWithName:#"Hero"] frame].origin.x)-0.5 duration:0.1];
[[self childNodeWithName:#"Hero"] runAction:moveLeft];
how can i fix that? besides, i realised in the console that the coordinate is like 150.000000 not like 150 or 150.0. could that be the problem?
Another question for the ones who worked with SpriteKit before!!
in a gameplay scene and you want to add a button you add it in the scene class or in the view controller?
i found a solution, i'm using this right now:
SKAction *moveLeft = [SKAction moveByX:1 y:0 duration:0];
it gives more precise results.
It sounds like you are compounding SKAction tweens, setting a new one before the old finishes. It's also possible you are creating timers and not killing them ? Can't be sure without seeing the implementation.
One way to solve the problem is to set a SKAction in the desired direction when the touch begins, and then removing the action when the touch ends. So you will only ever have one SKAction running at a time.
Without seeing your code that creates the NSTimer and how it handles them it's hard to determine exactly what is going on, so I'm basing my answer on what information you have given us. This is one possible scenario.
I am trying out different looks of a little game I am writing to play with animation on iOS.
My goal is this to have a grid of tiles which based on gameplay changes the display of each tile to one of a set of images. I'd like each tile (up to 24x24) to flip around when its face changes. As the game progresses, more and more tiles need to be flipped at the same time. In the first implementation where I tried to flip them simultaneously the animation got very jerky.
I changed my approach to not flip them all at once, but just a few at a time, by scheduling the animation for each tile with a slightly increasing delay per tile, so that when say the 10th tile starts animating, the first one is already done. It takes little while longer for the whole process to finish, but also leads to a nice visual ripple-effect.
However, one problem remains: At the beginning of a game move, when the player picks a new color, it takes a few fractions of a second on the device, before the animation starts. This gets worse as the game progresses and more flips need to be scheduled per move, up to the point where the animation seems to hang and then completes almost instantly without any of the frames in between being actually discernible.
This is the code (in my UIView game grid subclass) that triggers the flipping of relevant tiles. (I removed an optimization that skips tiles, because it only matters in the early stages of the game).
float delay = 0.0f;
for (NSUInteger row=0; row<numRows; row++) {
for (NSUInteger col=0; col<numCols; col++) {
delay += 0.03f;
[self updateFlippingImageAtRow:row col:col delay:delay animated:YES];
}
}
The game grid view has an NSArray of tile subviews which are addressed using the row and col variables in the loop above.
updateFlippingImageAtRow:col:delay:animated is a method in my FlippingImageView (also a subclass of UIView) boils down to this (game logic omitted):
-(void)animateToShow:(UIImage*)image
duration:(NSTimeInterval)time
delay:(float)delay
completion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:time
delay:delay
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:self
cache:YES];
self.frontImage = image;
}
completion:completion
];
}
Works fine, however, I conclude from the Instruments measuring which tells me that my time is spent in the animation block, that as the game goes on and the number of tiles to flip goes up, that the number of animations that get scheduled at the very beginning of the operation is a problem, and that the system then tries to catch up by dropping frames.
Any suggestions how to improve the performance of this? The exact timing of the animation is not really important.
You can think about doing this with CoreAnimation and CALayers instead of UIViews. It is incredebly powerful and optimized framework.
It's not an easy thing, you'll have to recode at least some of your classes (view hierarchy and hit tests are the first things that come to my mind), but it's worth a try and it's rather painless process, because CALayer is very similar to UIView.
Next step is OpenGL. It definitely can operate several hundreds of objects in realtime, but it requires much more work to do.
You might want to try using CoreAnimation instead. One way to do the flip animation would be:
Create a CABasicAnimation that animates the first half of the flip (rotation along the y axis).
In the delegate method animationDidStop:finished: you set the new image and then create a new animation that animates the second half.
You simply apply the animation to the layer property of your view.
Note that you can use setValue:forKey: to "annotate" an animation (remember what object the animation is about). When you add an animation to a layer it gets copied, not retained, so you can't identify it by simply comparing pointer values.