I have a button set up in a scene like this:
self.playButton = [SKSpriteNode spriteWithImageNamed:#"playbutton.png"];
self.playButton.size = CGSizeMake(100,100);
self.playButton.position = CGPointMake(CGRectGetMidX(self.frame), 100);
[self addChild:self.playButton];
Then in touchesBegan:
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if([self.playButton containsPoint:location]){
self.playButton.texture = [SKTexture textureWithImageNamed:#"playbuttonpressed.png"];
[self.playButton runAction: [self touchButtonAction]];
}
Then in touchesEnded:
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if([self.playButton containsPoint:location]){
self.playButton.texture = [SKTexture textureWithImageNamed:#"playbutton.png"];
[self.view presentScene:self.nextScene transition:self.sceneTransition];
}
Then in touchButtonAction:
SKAction *toSmall = [SKAction scaleBy:0.8 duration:0.1];
return toSmall;
Here's the problem: When I tap the button, touchesEnded gets called before the action is finished, which causes for an incomplete animation which doesn't look good (it seems laggy). How can I make a button that finishes the animation before transitioning?
There are a couple of ways of accomplishing what you want. One of those is using blocks.
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if([self.playButton containsPoint:location]){
SKAction *block0 = [SKAction runBlock:^{
self.playButton.texture = [SKTexture textureWithImageNamed:#"playbuttonpressed.png"];
[self.playButton runAction: [self touchButtonAction]];
}];
SKAction *wait0 = [SKAction waitForDuration:1.0]; // <- however long it takes for the animation to complete
SKAction *block1 = [SKAction runBlock:^{
self.playButton.texture = [SKTexture textureWithImageNamed:#"playbutton.png"];
[self.view presentScene:self.nextScene transaction:self.sceneTransaction];
}];
[self runAction:[SKAction sequence:#[block0, wait0, block1]]];
}
For more information on using blocks, you can read the Apple Blocks Programming Guide.
Related
I have two touches in method "touchesBegan" they both starts at the same time, but i want to second touch to wait while first touch end. How can i achieve that? Here are what I have:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location =[touch locationInView:self.view];
SKNode *touchedNode = [self nodeAtPoint:location];
if(touchedNode && [touchedNode.name isEqual:#"tapButton"]){
[self runAction:[SKAction playSoundFileNamed:#"buttons.wav" waitForCompletion:NO]];
[self createBackLn];
[self createBottom];
[self ballCreate];
[tapButton removeFromParent];
}
if (groupSprite.children.count < 3) {
CGPoint touchlocation = [touch locationInNode:self];
CGPoint location = CGPointMake(touchlocation.x, IS_IPAD()?108: 75);
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"board"];
if(IS_IPAD()){
sprite.xScale = 0.5;
sprite.yScale = 0.7;
}else{
sprite.xScale = 0.3;
sprite.yScale = 0.5;
}
sprite.position = location;
sprite.zPosition = 2;
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size];
sprite.physicsBody.dynamic = NO;
sprite.physicsBody.categoryBitMask = boardCategory;
sprite.physicsBody.contactTestBitMask = ballCategory | secballCategory;
SKAction *delay = [SKAction waitForDuration:IS_IPAD()?0.6:0.4];
SKAction *remove = [SKAction removeFromParent];
SKAction *actionSequence = [SKAction sequence:#[delay,remove]];
[sprite runAction:actionSequence];
[groupSprite addChild:sprite];
}
}
}
Everytime a touch is made, a node is added and will move to another node. I want to avoid that you can touch the node many times, and the node will been added as many times as you click. It should be only added once, and after the SKAction is done, you can touch the node again.
In my Code below, I tried it with userInteractionEnabled. But after the 'Zauberer' is added the third time, it recognizes no touches anymore.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.userInteractionEnabled = NO;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"Zauberer"]){
Wurfstein = [SKSpriteNode spriteNodeWithImageNamed:#"Wurfstein.png"];
Wurfstein.position = CGPointMake(Mensch.position.x, Mensch.position.y);
Wurfstein.zPosition = 1;
Wurfstein.scale = 0.6;
Wurfstein.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:5];
Wurfstein.physicsBody.dynamic = NO;
Wurfstein.physicsBody.allowsRotation = NO;
Wurfstein.physicsBody.usesPreciseCollisionDetection = YES;
Wurfstein.physicsBody.restitution = 0;
Wurfstein.physicsBody.categoryBitMask = SteinCategory ;
Wurfstein.physicsBody.collisionBitMask = ZaubererCategory;
Wurfstein.physicsBody.contactTestBitMask = ZaubererCategory;
SKAction *action = [SKAction moveTo:Zauberer.position duration:0.5];
SKAction *remove = [SKAction removeFromParent];
[self addChild:Wurfstein];
[Wurfstein runAction:[SKAction sequence:#[action,remove]]completion:^{
self.userInteractionEnabled = YES;
[Zauberer removeFromParent];
[self performSelector:#selector(Zauberer) withObject:nil afterDelay:5.0 ];
}];
}
}
Ok, I got it. If you first touch anywhere else on the screen, and than on the 'Zauberer' node, it wouldn't react. You need to put the self.userInteractionEnabled = NO; in the if sentence, to avoid the problem.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"Zauberer"]){
self.userInteractionEnabled = NO;
Wurfstein = [SKSpriteNode spriteNodeWithImageNamed:#"Wurfstein.png"];
Wurfstein.position = CGPointMake(Mensch.position.x, Mensch.position.y);
Wurfstein.zPosition = 1;
Wurfstein.scale = 0.6;
Wurfstein.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:5];
Wurfstein.physicsBody.dynamic = NO;
Wurfstein.physicsBody.allowsRotation = NO;
Wurfstein.physicsBody.usesPreciseCollisionDetection = YES;
Wurfstein.physicsBody.restitution = 0;
Wurfstein.physicsBody.categoryBitMask = SteinCategory ;
Wurfstein.physicsBody.collisionBitMask = ZaubererCategory;
Wurfstein.physicsBody.contactTestBitMask = ZaubererCategory;
SKAction *action = [SKAction moveTo:Zauberer.position duration:0.5];
SKAction *remove = [SKAction removeFromParent];
[self addChild:Wurfstein];
[Wurfstein runAction:[SKAction sequence:#[action,remove]] completion:^{
self.userInteractionEnabled = YES;
[Zauberer removeFromParent];
[self performSelector:#selector(Zauberer) withObject:nil afterDelay:4.0 ];
}];
}
}
In a Sprite-Kit game I want to animate Buttons when they were pressed. Right now the code reacts directly and to not wait till the animation is executed. Furthermore I want the button to animate back when the user wipes his finger out of the button.
Here my code:
-(void)addStartButton:(CGSize)size {
self.start = [SKSpriteNode spriteNodeWithImageNamed:#"startButton1"];
self.start.position = CGPointMake(size.width/2, size.height/2);
self.start.name = #"start";
[self addChild:self.start];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"start"]) {
self.start.texture = [SKTexture textureWithImageNamed:#"startButton2"];
MyScene *myScene = [MyScene sceneWithSize:self.size];
[self.view presentScene:myScene transition:[SKTransition pushWithDirection:SKTransitionDirectionLeft duration:0.5]];
}
}
This will give you 2 second delay before switching to another scene:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"start"]) {
SKAction *changeTexture = [SKAction runBlock:^{
self.start.texture = [SKTexture textureWithImageNamed:#"startButton2"];
}];
SKAction *wait = [SKAction waitForDuration:2.f];
SKAction *presentAnotherScene = [SKAction runBlock:^{
MyScene *myScene = [MyScene sceneWithSize:self.size];
[self.view presentScene:myScene transition:[SKTransition pushWithDirection:SKTransitionDirectionLeft duration:0.5]];
}];
[self runAction:[SKAction sequence:#[changeTexture,wait,presentAnotherScene]]];
}
}
Furthermore I want the button to animate back when the user wipes his finger out of the button.
This seems pointless, since you are transitioning to another scene when user presses the button.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"start"]) {
startButton.texture = [SKTexture textureWithImageNamed:#"startButton2"];
}else{startButton.texture = [SKTexture textureWithImageNamed:#"startButton1"];}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"start"]) {
startButton.texture = [SKTexture textureWithImageNamed:#"startButton2"];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"start"]) {
//Start Button Actions
}
I have an SKLabel Node setup as a button in my app and it works fine. However, when I rotate the SKLabelNode with this code my touchesBegan method no longer executes correctly and it becomes very difficult to have the SKLabelNode touches register correctly:
[_menuButton runAction:[SKAction sequence:#[[SKAction rotateByAngle:M_PI duration:.2],
[SKAction moveByX:0 y:-15 duration:.2]]]];
Here is my touches began method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
CGPoint positionInScene = [touch locationInNode:self];
[self selectNodeForTouch:positionInScene];
if ([node.name isEqualToString:menuButtonName]) {
//Touched Menu Button
[self animateMenuAndShow:YES];
}
}
EDIT: One additional piece of information is that I'm using SKTEffects which requires that instead of using self.scene as my base I'm using _worldLayer to add all elements in the scene to. Is it possible that this is throwing off my touch calculations? And if so how can I correct this?
self.scaleMode = SKSceneScaleModeResizeFill;
self.anchorPoint = CGPointMake(0.5, 0.5);
// The origin of the pivot node must be the center of the screen.
_worldPivot = [SKNode node];
[self addChild:_worldPivot];
// Create the world layer. This is the only node that is added directly
// to the pivot node. If you have a HUD layer you would add that directly
// to the scene and make it sit above the world layer.
_worldLayer = [SKNode node];
_worldLayer.position = self.frame.origin;
[_worldPivot addChild:_worldLayer];
Also after following sangony's advice here is my updated touchesBegan: method which works well at first, but runs into the same problem after rotating the label by M_PI
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:_worldLayer];
CGPoint locationInMenu = [touch locationInNode:_menuBackground];
if (CGRectContainsPoint(_menuButton.frame, location)) {
//Touched Menu Button
if (!self.menuVisible) {
[self animateMenuAndShow:YES];
}
else
{
[self animateMenuAndShow:NO];
}
}
}
EDIT2:
Here is my animateMenuAndShow: method
-(void)animateMenuAndShow:(BOOL)show
{
if (show == YES) {
// self.paused = YES;
self.menuVisible = !self.menuVisible;
[self createMenu];
SKAction *showMenuAction = [SKAction sequence:#[
[SKAction moveTo:CGPointMake(self.size.width / 2, self.size.height/2) duration:0.3],
[SKAction runBlock:^{
[self jelly:self.menuBackground];
}],
[SKAction waitForDuration:1.5]
]];
[self.menuBackground runAction: showMenuAction completion:^{
self.paused = YES;
}];
if (_flipped == YES) {
[self.menuBackground runAction:[SKAction sequence:#[
[SKAction moveByX:0 y:0 duration:.2],[SKAction rotateByAngle:M_PI duration:.2]]]];// [SKAction rotateByAngle:M_PI duration:.2]
}
}
else
{
self.paused = NO;
SKAction *removeMenuAction = [SKAction sequence:#[
[SKAction moveTo:CGPointMake(self.size.width / 2, -self.size.height + 300) duration:0.3],
]];
[self.menuBackground runAction:removeMenuAction completion:^{
[self.menuBackground removeFromParent];
}];
self.menuVisible = !self.menuVisible;
}
}
Try this touchesBegan instead:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
if (CGRectContainsPoint(myLabel.frame, touchLocation))
{
[self animateMenuAndShow:YES];
}
}
Im using Spritekit to create a game for iOS 7. The game has circles moving across the screen using this code
_enemy = [SKSpriteNode spriteNodeWithImageNamed:#"greeny"];
_enemy.position = CGPointMake(CGRectGetMidX(self.frame)+300,
CGRectGetMidY(self.frame));
_enemy.size = CGSizeMake(70, 70);
_enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:35];
_enemy.physicsBody.mass = 0;
_enemy.physicsBody.categoryBitMask = Collisiongreen;
[_enemies addObject:_enemy];
NSLog(#"Enemies%lu",(unsigned long)_enemies.count);
[self addChild:_enemy];
[_enemy runAction:[SKAction moveByX:-900 y:0 duration:4]];
[self runAction:[SKAction playSoundFileNamed:#"Spawn.wav" waitForCompletion:NO]];
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:1.4],
[SKAction performSelector:#selector(move)
onTarget:self],
]]];
I would like to have it that when the user touches one of the objects moving across the screen that they can move it with their finger either up or down (thats why I'm using touches moved)
The code i have set up right now is
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(_dead)
return;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if(CGRectContainsPoint(_enemy.frame, location)){
[_enemy runAction:[SKAction moveTo:[[touches anyObject] locationInNode:self] duration:0.01]];
}
My problem is that if i touch it the object will move a few pixels but will stop right after. How can I get it to move with my finger and if i let go it will continue moving left like programed before? Thanks!!
So the trick here is that in the touchesMoved method, you want the enemy's postion set to the location of your touch. Use the touchesEnded method to execute a method that makes the enemy continue in the direction of your last touch. This is a rough non-tested example.
You need to store the difference of the current location with the previous location.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(_dead)
return;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if(CGRectContainsPoint(_enemy.frame, location)){
someBool = False;
[_enemy setPosition:location];
changeInX = location.x - lastX;
changeInY = location.y - lastY;
lastX = location.x;
lastY = location.y;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
someBool = True;
}
- (void)update:(NSTimeInterval)currentTime
{
...
if (someBool) {
enemy.position = CGPointMake((enemy.position.x + changeInX), (enemy.position.y + changeInY));
}
...
}