SpriteKit allow only one touch - ios

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 ];
}];
}
}

Related

Two touches in touchesBegan in objective c

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];
}
}
}

How can I make a functioning animated button?

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.

didBeginContact method not working as intended

I have two nodes and a boolean. Simple enough. When node A contacts Node B and the boolean is 0, nothing happens. However if the boolean is 1, Node A is removed through the didBeganContact method.
Extremely simple, however I have an annoying problem on when I want Node A removed.
Node B is a rectangle and node A is a square going in the middle of the rectangle, the boolean is called and turned into 1 when I tap and hold the Node B using the touchesBegan method. Now before Node A contacts Node B, I tap and hold Node B and when Node A contacts, its removed, but when Node A is already in the middle, and I tap Node B, nothing happens and I don't know why.
Rectangle Method
-(void)rectangle
{
SKSpriteNode *rectangle = [[SKSpriteNode alloc] init];
rectangle = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(75, 150)];
rectangle.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
rectangle.name = #"rect";
rectangle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rectangle.size];
rectangle.physicsBody.categoryBitMask = rectangleCategory;
rectangle.physicsBody.contactTestBitMask = fallingSquareCategory;
rectangle.physicsBody.collisionBitMask = 0;
[self addChild:rectangle];
}
touchesBeganMethod
-(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:#"rect"])
{
radBool = 1;
}
}
touchesEnded
-(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:#"rect"])
{
radBool = 0;
}
}
square Method
-(void)square
{
SKAction *move = [SKAction moveToY:CGRectGetMidY(self.frame) duration:1.75];
SKSpriteNode *fallingSquare = [[SKSpriteNode alloc] init];
fallingSquare = [SKSpriteNode spriteNodeWithColor:[UIColor yellowColor] size:CGSizeMake(75, 75)];
fallingSquare.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame));
fallingSquare.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fallingSquare.size];
fallingSquare.physicsBody.categoryBitMask = fallingSquareCategory;
fallingSquare.physicsBody.contactTestBitMask = rectangleCategory
fallingSquare.physicsBody.collisionBitMask = 0;
[self addChild:fallingSquare];
[fallingSquare runAction:move];
}
didBeginContact
static inline SKSpriteNode *nodeFromBody(SKPhysicsBody *body1, SKPhysicsBody *body2, uint32_t category) {
SKSpriteNode *node = nil;
if (body1.categoryBitMask & category) {
node = (SKSpriteNode *)body1.node;
}
else if (body2.categoryBitMask & category) {
node = (SKSpriteNode *)body2.node;
}
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
SKSpriteNode *R1 = nil;
SKSpriteNode *fallingS = nil;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
R1 = nodeFromBody(firstBody, secondBody, rectangleCategory);
fallingS = nodeFromBody(firstBody, secondBody, fallingSquareCategory);
if (R1 && fallingS && radBool == 1)
{
[fallingS removeFromParent];
}
}
I believe your issue is the "begin" part of didBeginContact. It only gets called the first time they contact and not every loop. Because the bool was not set to YES when they first contacted it will never be evaluated again.
I believe I ran into this issue once before and the solution was to create a new physical body when you touch it. This "should" trigger didBeginContact the next go around. You might also be able to change a property on the physical body, but if I recall correctly I didn't get that to work and had to init a new physical body.
For example try updating your touchesBegan with this
touchesBeganMethod
-(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:#"rect"])
{
radBool = 1;
node.physicsBody = nil;
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rectangle.size];
node.physicsBody.categoryBitMask = rectangleCategory;
node.physicsBody.contactTestBitMask = fallingSquareCategory;
node.physicsBody.collisionBitMask = 0;
}
}
Hope that works for you.

After rotating SKLabelNode it becomes difficult to get location in touchesBegan:

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];
}
}

Move a SKSpriteNode with a single tap, but move continously when long tap - Sprite Kit

I'm trying to change some SKActions of an existing Sprite Kit tutorial project, but I'm running into issues when it comes to movement. The Tutorial and GitHub project is here:
https://www.codefellows.org/blogs/simple-sprite-kit-game-tutorial-part1
https://github.com/megharastogi/GameTutorial
As you can see in the code below, each tap only moves the node once. How do I change it so that a long tap will move continuous move the node? I tried a few things like repeatActionForever, but that didn't work very well.
-(void)addShip
{
//initalizing spaceship node
ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
[ship setScale:0.5];
ship.zRotation = - M_PI / 2;
//Adding SpriteKit physicsBody for collision detection
ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.size];
ship.physicsBody.categoryBitMask = shipCategory;
ship.physicsBody.dynamic = YES;
ship.physicsBody.contactTestBitMask = obstacleCategory;
ship.physicsBody.collisionBitMask = 0;
ship.physicsBody.usesPreciseCollisionDetection = YES;
ship.name = #"ship";
ship.position = CGPointMake(120,160);
actionMoveUp = [SKAction moveByX:0 y:30 duration:.2];
actionMoveDown = [SKAction moveByX:0 y:-30 duration:.2];
[self addChild:ship];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
if(touchLocation.y >ship.position.y){
if(ship.position.y < 270){
[ship runAction:actionMoveUp];
}
}else{
if(ship.position.y > 50){
[ship runAction:actionMoveDown];
}
}
}
- (void)didMoveToView:(SKView *)view
{
UILongPressGestureRecognizer *tapper = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(tappedScreen:)];
tapper.minimumPressDuration = 0.1;
[view addGestureRecognizer:tapper];
}
- (void)tappedScreen:(UITapGestureRecognizer *)recognizer
{
float touchY = [self convertPointFromView:[recognizer locationInView:self.view]].y;
SKSpriteNode *ship = [self childNodeWithName:#"ship"];
if (recognizer.state == UIGestureRecognizerStateBegan) {
if(touchY >ship.position.y){
[ship runAction:[SKAction repeatActionForever:actionMoveUp] withKey:#"longTap"];
}else{
[ship runAction:[SKAction repeatActionForever:actionMoveDown] withKey:#"longTap"];
}
}
if (recognizer.state == UIGestureRecognizerStateEnded) {
[ship removeActionForKey:#"longTap"];
}
}
Add these two methods in your code.

Resources