How to remove a CCnode with a touch? - objective-c

I need help I have tried to remove a ccnode that constantly is respawning at different locations and adding them to an array to get control of which sprites are on screen, but the thing is that I can't get to remove them. It detects the touches but doesn't get removed any ideas? Here is the code I'm using to get rid of the node.
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToUI:location];
for (CCNode *sprite in _spritesOnScreen) {
if (CGPointEqualToPoint(sprite.position, location)) {
[_spritesOnScreen removeObject:sprite];
[self removeChild:sprite cleanup:YES];
}
}
}

Allow me to offer you a slightly different approach. Subclass CCNode to CCAppleNode and within the CCAppleNode.m file detect touches and call removeFromParent on touchBegan. This way the CCAppleNode class is taking up the responsibility of removing itself from parent when its touched, taking away this responsibility from your main game scene.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
[self removeFromParentAndCleanup:YES];
[super touchBegan:touch withEvent:event];
}

Related

Sprite with userInteractionEnabled set to YES does not receive touches when covered by normal sprites

I put a sprite A (subclassed to receive hits (userInteractionEnabled to YES)), and then a normal sprite B that does not take hits on top of that (userInteractionEnabled default NO), completely covering sprite A.
Tapping on sprite B, I assume that sprite A would get the touch but nothing happens. The part from docs about the matter is below.
I feel something is unclear here because it seems still that sprite B receives the touch but throws it away. OR, spriteA is removed from possible touch receiver because it's not visible.
From the docs:
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Nodes/Nodes.html#//apple_ref/doc/uid/TP40013043-CH3-SW7
For a node to be considered during hit-testing, its
userInteractionEnabled property must be set to YES. The default value
is NO for any node except a scene node. A node that wants to receive
events needs to implement the appropriate responder methods from its
parent class (UIResponder on iOS and NSResponder on OS X). This is one
of the few places where you must implement platform-specific code in
Sprite Kit
Anyway to fix this?
As long as something's userInteractionEnabled is NO, it shouldn't interfere with other touch receivers.
Update: Even setting sprite B's alpha to 0.2, making sprite A very visible, will not make sprite A touchable. Sprite B just totally "swallows" the touch, despite being not enabled for interaction.
Here is my solution until Apple updates SpriteKit with correct behaviour, or someone acctually figures out how to use it like we want.
https://gist.github.com/bobmoff/7110052
Add the file to your project and import it in your Prefix header. Now touches should work the way intended.
My solution doesn't require isKindOfClass sort of things...
In your SKScene interface:
#property (nonatomic, strong) SKNode* touchTargetNode;
// this will be the target node for touchesCancelled, touchesMoved, touchesEnded
In your SKScene implementation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
NSArray* nodes = [self nodesAtPoint:[touch locationInNode:self]];
self.touchTargetNode = nil;
for( SKNode* node in [nodes reverseObjectEnumerator] )
{
if( [node conformsToProtocol:#protocol(CSTouchableNode)] )
{
SKNode<CSTouchableNode>* touchable = (SKNode<CSTouchableNode>*)node;
if( touchable.wantsTouchEvents )
{
self.touchTargetNode = node;
[node touchesBegan:touches
withEvent:event];
break;
}
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.touchTargetNode touchesCancelled:touches
withEvent:event];
self.touchTargetNode = nil;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.touchTargetNode touchesMoved:touches
withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.touchTargetNode touchesEnded:touches
withEvent:event];
self.touchTargetNode = nil;
}
You will need to define the CSTouchableNode protocol... call it what you wish :)
#protocol CSTouchableNode <NSObject>
- (BOOL) wantsTouchEvents;
- (void) setWantsTouchEvents:(BOOL)wantsTouchEvents;
#end
For your SKNodes that you want to be touchable, they need to conform to the CSTouchableNode protocol. You will need to add something like below to your classes as required.
#property (nonatomic, assign) BOOL wantsTouchEvents;
Doing this, tapping on nodes works as I would expect it to work. And it shouldn't break when Apple fixes the "userInteractionEnabled" bug. And yes, it is a bug. A dumb bug. Silly Apple.
Updated
There is another bug in SpriteKit... the z order of nodes is weird. I have seen strange orderings of nodes... thus I tend to force a zPosition for nodes/scenes that need it.
I've updated my SKScene implementation to sorting the nodes based on zPosition.
nodes = [nodes sortedArrayUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"zPosition" ascending:false]]];
for( SKNode* node in nodes )
{
...
}
Here is an example for getting touches etc..
The Scene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
HeroSprite *newHero = [HeroSprite new];
newHero.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:newHero];
SKSpriteNode *overLapSprite = [SKSpriteNode spriteNodeWithColor:[UIColor orangeColor] size:CGSizeMake(70, 70)];
overLapSprite.position = CGPointMake(CGRectGetMidX(self.frame) + 30, CGRectGetMidY(self.frame));
overLapSprite.name = #"overlap";
[self addChild:overLapSprite];
}
return self;
}
-(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:#"heroNode"]) {
NSLog(#"Scene detect hit on hero");
}
if ([node.name isEqualToString:#"overlap"]) {
NSLog(#"overlap detect hit");
}
// go through all the nodes at the point
NSArray *allNodes = [self nodesAtPoint:location];
for (SKNode *aNode in allNodes) {
NSLog(#"Loop; node name = %#", aNode.name);
}
}
The Subclassed sprite / Hero
- (id) init {
if (self = [super init]) {
[self setUpHeroDetails];
self.userInteractionEnabled = YES;
}
return self;
}
-(void) setUpHeroDetails
{
self.name = #"heroNode";
SKSpriteNode *heroImage = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
[self addChild:heroImage];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint locationA = [touch locationInNode:self];
CGPoint locationB = [touch locationInNode:self.parent];
NSLog(#"Hero got Hit at, %# %#", NSStringFromCGPoint(locationA), NSStringFromCGPoint(locationB));
}
#end
Tried many things, but the touch doesn't go through the overlapping sprite. I guess you can use the scene class to detect the touch via looping through the nodes then directly call that node.
I had this problem when a particle emitter covered a button...
I think this could be a design choice, but certainly not very flexible. I prefer to handle objects that can overlap (like game objects) in Sprite Kit at Scene level with nodeAtPoint: combined with isKindOfClass: methods. For objects like buttons that usually do not overlap and are placed in a different layer (like overlays and buttons), I handle their user interaction inside their classes.
I would like my touch events to "bubble up" the nodes tree as it happens in Sparrow Framework, but i fear it is more a feature request than a bug report.
P.S.: By handling touches at scene level you can easily touch and move nodes even if they are completely covered by non-movable and non-touchable nodes!
Solution with fewer lines of code. Remember to set userInteractionEnabled in every node you want to receive touch.
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer {
CGPoint touchLocation = self.positionInScene;
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
NSArray *nodes = [self nodesAtPoint:touchLocation];
for (SKSpriteNode *node in [nodes reverseObjectEnumerator]) {
NSLog(#"Node in touch array: %#. Touch enabled: %hhd", node.name, node.isUserInteractionEnabled);
if (node.isUserInteractionEnabled == 1)
touchedNode = node;
}
NSLog(#"Newly selected node: %#", touchedNode);
}

Dragging UIImages to the selected area

Newbie obj-c question.
My task is to make visually represented test for iPad. In view I have three UIImages (with images of answers to test question) what will dragged to area for answer. If selected right image, then it needs to stay in this area, if not - it goes back into the start position. If user stop dragging not in area for answer it also needs to goes back to the start position.
I tried to implement this: http://www.cocoacontrols.com/platforms/ios/controls/tkdragview but it's too hard for me because I am a super newbie.
I implemented simple dragging of images by PanRecognizer, so every from three images dragging
-(IBAction)controlPan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
If I think in right way, I need to set CGPoint's with start and destination coordinates and then to customize Pan Gestures method? If it's not right, in what way can I do this?
You can consider this three method, maybe these are easier for you:
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
Detailed demo for – touchesBegan:withEvent: is:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
UITouch *touched = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touched.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
UITouch *touched = [[event allTouches] anyObject];
// Here I suppose self.imageView contains the image user is dragging.
CGPoint location = [touch locationInView:touched.view];
// Here the check condition is the image user dragging is absolutely in the answer area.
if (CGRectContainsRect(self.answerArea.frame, self.imageView.frame)) {
// set transition to another UIViewController
} else {
self.imageView.center = location; // Here I suppose you just move the image with user's finger.
}
}
Here is the Apple's doc for this UIResponder Class Reference

Why won't my Cocos2d test app fire "touchesBegan" events?

In my app delegate, I made sure I have the line:
[glView setMultipleTouchEnabled: YES];
And I have a simple layer meant only to figure out how multi touch works. The .mm file looks like:
#import "TestLayer.h"
#implementation TestLayer
-(id) init
{
if( (self=[super init])) {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
return self;
}
-(void) draw{
[super draw];
glColor4f(1.0, 0.0, 0.0, 0.35);
glLineWidth(6.0f);
ccDrawCircle(ccp(500,500), 250,CC_DEGREES_TO_RADIANS(360), 60,YES);
}
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"got some touches");
}
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"some touches moved.");
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
NSLog(#"a touch began");
return FALSE;
}
#end
When I touch the screen, I always see "a touch began", but no matter how I touch it (simulator or actual device), I never see "some touches moved" or "got some touches".
Is there something further I need to do to make multi touch work?
Specifically, I'm just trying to do basic pinch-to-zoom functionality... I heard there is some sort of gesture recognizer for iPhone...does it work for Coco2ds? Would it work even if I can't get simple multi touch events to fire?
UIGestureRecognizers absolutely work for Cocos2D, I personally used them, you just need to add them to the correct view by using:
[[[CCDirector sharedDirector] openGLView] addGestureRecognizer:myGestureRecognizer];
Regarding your touches, I guess you enabled them for the scene you are working in?
scene.isTouchEnabled = YES;
In any case you shouldn't use the addTargetDelegate method, take a look here
add self.isTouchEnabled = YES; to your init
and for the gesture recognizers look at the other answer

ccTouchesBegan vs ccTouchBegan - Touch Detection & SIGABRT crash

I'm currently doing an application where I'm trying to detect touch-positions of the user.
I changed from ccTouchBegan to ccTouchesBegan in the process of implementing the "detect touch-position" function.
But I can't get it to work. I changed from ccTouchBegan to ccTouchesBegan:
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
instead of using:
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
When i did this the whole thing crashes when I click the screen. Generating a SIGABRT error higlighting:
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
NSAssert(NO, #"Layer#ccTouchBegan override me");
return YES;
}
#endif
#end
So my questions are:
Why do you think it crashes?
What's the difference between ccTouchBegan & ccTouchesBegan? Multi touch abilities?
For further help, this is my code:
-(id) init
{
if( (self=[super init])) {
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
self.isTouchEnabled = YES;
// Set up background
background = [CCSprite spriteWithFile:#"Image.png"];
background.scaleX = 1;
background.scaleY = 1;
background.position = ccp(0,0);
[self addChild:background];
[[CCTouchDispatcher sharedDispatcher]addTargetedDelegate:self
priority:0
swallowsTouches:YES];
// Preload sound effect
soundFile = [SimpleAudioEngine sharedEngine];
if (soundFile != nil) {
[soundFile preloadBackgroundMusic:#"sound.wav"];
}
}
return self;
}
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
NSLog(#"ccTouchesBegan");
// Sets the sound variable to YES
ifOne = YES;
prevPos = [touch locationInView:[touch self]];
prevPos = [[CCDirector sharedDirector] convertToGL:[touch locationInView:touch.self]];
[self schedule:#selector(timerUpdate:) interval:0.1];
//return YES;
}
It's a nice feature in cocos2d which lets you swallow touches in cases that you want to handle only a single touch event.
Try adding this function to your class:
- (void) registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES];
}
ccTouchesBegan happens the second you tap the screen
ccTouchesEnded happens the second you let go after tapping on the screen
and instead of
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
instead of using:
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
try using
-(void)ccTouchesBegan:(UITouch *)...
or
-(void)ccTouchesBegan:(NSSet *)...
or
-(BOOL)ccTouchesBegan:(NSSet *)...
your problem may just be invalid data types or some crap like that, my advice is just try switching the types of touches around.
I would give more info but you didn't provide alot of information to work with, so this is the best I can do.

touches in touch event handling in UIView vs UIViewControllers

So, I'm capturing multiple touches and determining the number and where each of the touches occurred and I'm seeing different behavior between a UIView UIViewController.
Basically, the event handler is coded below: (in this case the touchesEnded, but it really doesn't matter). What happens in the View, is that I get the entire collection of touches, but in the controller, I get only one touch at a time, that is there is never a collection of touches, but the TouchesEnded gets executed for each touch that occurred in the same moment.
I add the subviews as identically as possible...
Can someone 'spain to me what's going on? Why doesn't the controller version give me the collection?
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
int total = 0;
for (UITouch *touch in touches)
{
for(UIView *myView in [contentView subviews])
{
if (CGRectContainsPoint([myView frame], [touch locationInView:self.view]))
total++; // this is always 1 for the UIViewController
// but I get the full number within the UIView version
}
}
}
I suspect you are getting the touches but that the views are different. Check that contentView is the same and has the same subviews.
I am not sure that I am right but this thing always work for me.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
If(touch.view==your view)
{
NSLog("do your task here");
}
}