I am using sprite kit and I have an SKLabelNode that I set up in my initWithSize method. It is declared as..
#property (nonatomic, strong) SKLabelNode *chargesToShowUponCompletion;
In the initWithSize method I create it this way..
_chargesToShowUponCompletion = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
_chargesToShowUponCompletion.fontSize = 40;
_chargesToShowUponCompletion.fontColor = [SKColor whiteColor];
_chargesToShowUponCompletion.position = CGPointMake(self.size.width/2, 1.5/3.0 * self.size.height);
_chargesToShowUponCompletion.zPosition = 20;
[self addChild:_chargesToShowUponCompletion];
[_chargesToShowUponCompletion setHidden:YES];
As you can see, I hide the LabelNode right after I have added it to the scene. I actually have several that I create like this, and I reveal them a different times. This one in particular needs to be shown for a bit, then disappear. So to accomplish that I have used the following.
[_chargesToShowUponCompletion performSelector:#selector(setHidden:) withObject:#YES afterDelay:3];
This works perfectly in iPhone 5, iPhone 4, and the simulator. However, when I run it on a 64 bit machine, or simulator, it does not work. It actually works as if I had setHidden withObject:#NO. I am figuring it has something to do with the 64 vs 32 bit iPhones, but I can not figure it out. I read Apple's doc about supporting both and I have my Architectures set for Standard. Is there some way I can get this selector to run on both types of machines? Also, the other LabelNodes that I create are hidden and shown at various times, they all work, but none of them deals with the selector. Any ideas? Thanks for the help!
So if any one is following this, or having the same problem, I solved it two different ways. The first one was to keep the #selector(setHidden) line of code. I replaced the #YES with #(1l) and this solved the problem of running on 5s.
[_chargesToShowUponCompletion performSelector:#selector(setHidden:) withObject:#(1l) afterDelay:3]; //change #YES to #(1l)
[_substanceToShowUponCompletion performSelector:#selector(setHidden:) withObject:#(1l) afterDelay:3];
UPDATE: I have heard it is bad to use performSelector in this manner. I would just go with the run block.
The other method was to make an SKAction run Block like this.
SKAction *action1 = [SKAction waitForDuration:3.0];
SKAction *action2 = [SKAction runBlock:^{
_chargesToShowUponCompletion.hidden = YES;}];
SKAction *action3 = [SKAction runBlock:^{
_substanceToShowUponCompletion.hidden = YES;}];
SKAction *seq1 = [SKAction sequence:#[action1, action2]];
SKAction *seq2 = [SKAction sequence:#[action1, action3]];
[_chargesToShowUponCompletion runAction:seq1];
[_substanceToShowUponCompletion runAction:seq2];
I ran that twice as well since I had two SKLabelnodes to hide. Hope this helps someone!!!!
Related
I have a sprite that moves from point A to B. Moving could cause the sprite to go around an object, which means the sprite will go from facing west to north to east.
I store the current facing direction in _facing. I use the A* pathfinding algorithm to build an NSArray of CGPoints. Then I walk the sprite through the points, while changing the texture from a static (standing still) texture to an animated texture. Ultimately this will be more complex, as the sprite "turns" from direction-to-direction, but while I'm learning Objective-C / SpriteKit I'm trying to keep it simple.
Anyways, the problem is that I need to change the animation from walking west/north/east as those points are hit, but when I queue up SKAction objects they of course run in sequence, which means when the "west" animation is playing the sprite isn't moving at all. How can I have some SKAction's run in parallel with another sequence of SKAction's? Are using blocks a potential solution?
Here's the code where I check if a "turn" is happening at the given point and respond accordingly:
// Set direction facing.
if (curCGPoint.x > prevCGPoint.x && [_facing isEqualToString:#"left"]) {
[self setDirectionFacing:#"right"];
// Add action to change direction.
SKAction *changeDir = [SKAction repeatActionForever:[SKAction animateWithTextures:walkRightFrames timePerFrame:0.1f resize:NO restore:YES]];
[walkActions addObject:changeDir];
} else if (curCGPoint.x < prevCGPoint.x && [_facing isEqualToString:#"right"]) {
[self setDirectionFacing:#"left"];
// Add action to change direction.
SKAction *changeDir = [SKAction repeatActionForever:[SKAction animateWithTextures:walkLeftFrames timePerFrame:0.1f resize:NO restore:YES]];
[walkActions addObject:changeDir];
}
// Build SKAction to walk hero to the current point.
SKAction *walkAction = [SKAction moveTo:curCGPoint duration:0.1];
// Add to our array of walk SKAction's.
[walkActions addObject:walkAction];
The previous code is in a for() where I run through all points – subsequently I do something like this to actually run the actions:
SKAction *sequence = [SKAction sequence:walkActions];
[_sprite runAction:sequence withKey:#"Move_Hero"];
Update: For those that run into this trying to do the same thing, you can use a combination of SKAction sequences, SKAction groups, and blocks (via SKAction:runBlock) to get your parallel and successive actions going. Using SKAction:runBlock you can fire off actions from specific "group" entries that should only run at that particular moment but should not block successive actions. This is a very flexible route to take.
Yes SKAction has a method called group:
[SKAction group:#[SKAction1, SKAction2, SKAction3]];
It runs a lot like sequence but all the SKActions inside the group array runs together.
For more details I advise reading the docs.
I have an SKSpriteNode and I want to change the texture on it when the user touches the screen. But cannot work out how to do so.
Creating and adding the head. (Declared in header).
head = [SKSpriteNode spriteNodeWithImageNamed:[NSString stringWithFormat:#"%#",face]];
head.position = CGPointMake(size.width / 2, size.height / 2);
[self addChild:head];
When a touch is detected, the below is run, but I cannot work out how to apply it to the SKSpritenode?!
SKAction* changeFace = [SKAction setTexture:[SKTexture textureWithImageNamed:[NSString stringWithFormat:#"%#",face]]];
[self runAction:changeFace];
I have tried the below also, but it does not seem to work...
head.texture = [SKTexture textureWithImageNamed:[NSString stringWithFormat:#"%#",face]];
Hope somebody is able to point me in the correct direction!
It looks like you are trying to run the action on the scene (or any other object than your sprite.) The second code should work however, but using the SKAction try this instead.
[head runAction:changeFace];
I have it working here, look at the code below:
1 - Create the SKSpriteNode
self.ninja = [SKSpriteNode spriteNodeWithImageNamed:#"ninja1"];
self.ninja.position = CGPointMake(self.ninja.size.width/2, self.frame.size.height/2);
[self addChild:self.ninja];
2 - Change the texture:
self.ninja.texture = [SKTexture textureWithImageNamed:#"ninja2"];
Obs: I change the texture in touchesBegan event, but this should work in any way you want to do.
I also experiencing this one, I was able to change texture but the the texture is stretch.
What is causing this?
Try This
SKAction*animation=[SKAction animateWithTextures:actions timePerFrame:0.1 resize:NO restore:YES];
I am implementing a UIview to contain a number of layers (about 9) to draw different elements of a graph in real time. I had previously implemented these as 9 different UIViews, and drew on them using the drawRect() function and it worked fine... but was very slow. From what I've been able to find online, it seems as if CALayers will be much faster. To make this change, I've subclassed CALayer, and only overriden the drawinContext: function. Here is the entirety of my CALayer.m file
#import "FFTLayer.h"
#implementation FFTLayer
#synthesize strokeColor,points,numPoints;
-(void)display{
[self drawInContext:UIGraphicsGetCurrentContext()];
NSLog(#"displaying!!");
[super display];
}
-(void)drawInContext:(CGContextRef)ctx
{
NSLog(#"drawing in context!");
CGContextSetStrokeColorWithColor(ctx, strokeColor);
CGContextStrokeLineSegments(ctx, points, numPoints*2);
}
#end
I have tried a bunch of different things. but so far, the only way that I've been able to get drawInContext: to be called is by calling it in the display() method, which seems wrong. Even when I do get drawInContext to be called, Xcode tells me that my drawing context is invalid. Here is the code that I'm using to try and tell FFTlayer to call itself.
context = UIGraphicsGetCurrentContext();
[self.layer addSublayer:fftLayer];
[fftLayer setPoints:fft_points];
[fftLayer drawInContext:context];
[fftLayer setNeedsDisplay];
[self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
Even after spending a few days reading about CALAyers online, I am still pretty confused about how to properly use them, and if I'm drawing this the most efficient/correct way. In case you can't tell from the "FFTLayer" class name, I'm an audio guy, not a graphics guy :)
Any help would be greatly appreciated!!
EDIT:
I set up all of my layers in the following way
//FFT Graph Layer
fftLayer = [[FFTLayer alloc] init];
fftLayer.backgroundColor = [UIColor clearColor].CGColor;
fftLayer.frame = self.layer.bounds;
[fftLayer setNumPoints:nP];
[fftLayer setStrokeColor:fft_strokeColor];
Did you import "FFTlayer.h" in your view subclass?
Try to add after layer init:
fftLayer.delegate = fftLayer;
As a test I made one layout that displays cells in a vertical line and another that displays them in a horizontal layout. When I call [collectionView setCollectionViewLayout:layout animated:YES]; it animates between the two positions very cleanly.
Now that I'd like to do is have all the views do a few spins, warps and flips around the screen (probably using CAKeyframeAnimations) before finally arriving at their destination, but I can't find a good place to hook this in.
I tried subclassing UICollectionViewLayoutAttributes to contain an animation property, then setting those animations in an overridden applyLayoutAttributes: method of the UICollectionViewCell I'm using. This works... EXCEPT it appears to happen only after the layout transition is complete. If I wanted to use this, I'd have to have the layout not change the current positions of the objects right away, only after the it reaches this apply attributes part of the code, and that seems like a lot of work...
Or I could subclass UICollectionView and override setCollectionViewLayout:animated:, but that also seems like a lot of state to keep around between layouts. Neither of these optins seems right, because there's such an easy way to animate additions/deletions of cells within a layout. I feel like there should be something similar for hooking into the animations between layouts.
Does anyone know the best way to get what I'm looking to accomplish?
#define degreesToRadians(x) (M_PI * (x) / 180.0)
UICollectionView *collectionView = self.viewController.collectionView;
HorizontalCollectionViewLayout *horizontalLayout = [HorizontalCollectionViewLayout new];
NSTimeInterval duration = 2;
[collectionView.visibleCells enumerateObjectsUsingBlock:^(UICollectionViewCell *cell, NSUInteger index, BOOL *stop)
{
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotationAnimation.toValue = #(degreesToRadians(360));
rotationAnimation.duration = duration;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[cell.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}];
[UIView animateWithDuration:duration
animations:^
{
collectionView.collectionViewLayout = horizontalLayout;
}];
I'm currently working on a game in which I add a CCSprite to a scene (an animated ghost), and have it move from a randomly generated point to another randomly generated point, fade out, and then be removed from the scene. When the player taps the ghost, 1 is added to the score.
Everything is working beautifully, but I want this action to be repeated until a condition is met (specifically, when the score reaches 50). I can't figure out the best way to do this. Every time I try to run a loop, it repeats infinitely and crashes the game.
What is the best way to achieve such a loop? I've heard mention of creating arrays for sprites, but there will only ever be one iteration of the ghost on the scene at one time. Are there any other ways of achieving this?
Ideally, what I want to achieve is: run the actions, wait for them to finish, and then run them again until the score is 50.
Here's my code:
-(void) ghostSpawner {
CGPoint endPoint = [self randomPointGenerator];
[self birthGhost];
CCSprite * Ghost = (CCSprite *) [self getChildByTag:2];
//...then fade him in
CCFiniteTimeAction *ghostBegin = [CCSequence actions:[CCDelayTime actionWithDuration:1],[CCTintTo actionWithDuration:0 red:0 green:0 blue:0],[CCTintTo actionWithDuration:.5 red:255 green:255 blue:255], nil];
//...move him over a given amount of time, then fade him out and DIE
CCFiniteTimeAction *ghostEnd = [CCSequence actions:[CCMoveTo actionWithDuration:2 position:endPoint],[CCTintTo actionWithDuration:1 red:0 green:0 blue:0],[CCCallFunc actionWithTarget:self selector:#selector(killGhost)], nil] ;
[Ghost runAction:[CCRepeatForever actionWithAction:[CCSequence actions:ghostBegin,ghostEnd, nil]]];
}
For generating the sprite:
-(void) birthGhost {
CGPoint spawnPoint = [self randomPointGenerator];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"ghostidle.plist"];
CCSprite * Ghost = [CCSprite spriteWithSpriteFrameName:#"Ghost0001.png"];
[Ghost setColor:ccc3(0,0,0)];
Ghost.anchorPoint = ccp(.5, .5);
Ghost.position = spawnPoint;
Ghost.anchorPoint = ccp(0.5f,0.5f);
Ghost.scale = .4;
Ghost.tag = 2;
[Ghost runAction:ghostIdleAnimation()];
[self addChild:Ghost z:0];
}
The 'ghostSpawner' function is called in my 'init' on the scene
[self ghostSpawner];
I hope this is enough. Any feedback would be awesome, since this is one of my first real iPhone projects. Thanks in advance!
Alright, here's how I would do it:
Send ghostSpawner to self on init (as you do now).
In ghostSpawner, take your sprite, reset it (change the opacity to 0, reset the position, whatever), then send it 2 runAction messages: one to fade in, the other a sequence to move to the end position, then send a killGhost message to self.
In killGhost, check whether the score is less than 50. If it is, send ghostSpawner to self again, otherwise do whatever you do when they have reached 50 points.
Your sprite creation code should be in init. You can just reuse the same sprite. Also, there's CCFadeIn you can use instead of CCTintTo.