SKSpriteNode removeFromParent in touchesEnded - sprite-kit

I have an issue removing an SKSpriteNode from it's parent when a touch is detected on that SKSpriteNode. Here is the code:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint sceneLocation = [touch locationInNode:self];
if ([_tableDoneButton containsPoint:sceneLocation]) {
NSLog(#"here");
[self removeItemsFromView];
_headerNode.portLabel.text = [IPGameManager sharedGameData].world.player.port.name;
[_portNode show];
}
SKNode *nodeInPort = [_portNode nodeAtPoint:[touch locationInNode:_portNode]];
if ([nodeInPort.name isEqualToString:#"marketButton"]) {
[self showMarket];
}
}
}
In the showMarket function, a 'Done' SKSpriteNode is added so the user can leave the market when he/she wants to. As you can see above, if the _tableDoneButton contains the touch location, it should call the removeItemsFromView function which contains [_tableDoneButton removeFromParent];. Do I have to do something special when removing nodes this way?
Another issue: It seems to get removed ~1 second later. What exactly happens when you run [node removeFromParent]? If I set the SKSpriteNode to nil after I call [self removeItemsFromView] it works fine. But I shouldn't have to do that?

Related

SpriteKit Touches - containsPoint

I'm having an issue where, within my touchesBegan method, I'm not getting back what I think I should.
I'm testing for a hit within a specific node. I've tried several methods, and none work. I've created a work-around, but would love to know if this is a bug or if I'm doing something wrong.
Here's the code:
Standard touchesBegan method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
SKSpriteNode *infoPanelNode = (SKSpriteNode *)[self childNodeWithName:#"infoPanelNode"];
UITouch *touch = [touches anyObject];
if(touch){
//e.g. infoPanelNode position:{508, 23} size:{446, 265.5} (also of note - infoPanelNode is a child of self)
//This solution works
CGPoint location = [touch locationInNode:self];
//location == (x=77, y=170)
bool withinInfoPanelNode = [self myPtInNode:infoPanelNode inPoint:location];
// withinInfoPanelNode == false (CORRECT)
//This one doesn't return the same result - returns true when hit is not in the cell
CGPoint infoLocation = [touch locationInNode:infoPanelNode];
//infoLocation == (x=-862, y=294)
bool withinInfoPanelNodeBuiltInResult = [infoPanelNode containsPoint:infoLocation];
// withinInfoPanelNodeBuiltInResult == true (WRONG)
// This one doesn't work either - returns an array with the infoPanelNode in it, even though the hit point and node location are the same shown above
// NSArray *nodes = [self nodesAtPoint:location];
// for (SKNode *node in nodes) {
// if(node==infoPanelNode)
// withinInfoPanelNode = true;
// }
//
//Code omitted - doing something with the withinInfoPanelNode now
}
My custom hit test code:
-(bool) myPtInNode:(SKSpriteNode *)node inPoint:(CGPoint)inPoint {
if(node.position.x < inPoint.x && (node.position.x+node.size.width) > inPoint.x){
if(node.position.y < inPoint.y && (node.position.y+node.size.height) > inPoint.y){
return true;
}
}
return false;
}
Anyone see what's going wrong here?
Thanks,
kg
I'm not sure exactly how SKCropNode will work, but in general, in order for containsPoint: to detect touches within it you need to give it a point relative to its parent node. The following code should work. Note the addition of .parent when calling locationInNode:
CGPoint infoLocation = [touch locationInNode:infoPanelNode.parent];
BOOL withinInfoPanelNodeBuiltInResult = [infoPanelNode containsPoint:infoLocation];
Solved this problem and wanted to update everyone.
It turns out that with this specific infoPanelNode (an SKSpriteNode), I have a child node within it that is an SKCropNode. This node then crops out a much larger node (obviously it's a child of the crop node) so only a small portion is viewable (allowing for scrolling to portions of that node). Unfortunately, the call to containsPoint apparently combines the boundaries of all child nodes with the receiving node's boundaries to use as the boundary test rect. This would be understandable if it would respect the SKCropNode's boundaries of IT'S children, but apparently, it doesn't so you have to roll your own if you have this type of setup.

iOS Loses Touches?

I can't find anything to explain lost UITouch events. If you smash your full hand on the screen enough times, the number of touchesBegan will be different than the number of touchesEnded! I think the only way to actually know about these orphaned touches will be to reference them myself and keep track of how long they haven't moved.
Sample code:
int touchesStarted = 0;
int touchesFinished = 0;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
touchesStarted += touches.count;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchesFinished += touches.count;
NSLog(#"%d / %d", touchesStarted, touchesFinished);
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
Don't forget about touchesCancelled: UIResponder reference
Editing in response to the poster's update:
Each touch object provides what phase it is in:
typedef enum {
UITouchPhaseBegan,
UITouchPhaseMoved,
UITouchPhaseStationary,
UITouchPhaseEnded,
UITouchPhaseCancelled,
} UITouchPhase;
I believe that if a touch starts and ends in the same touch event set, -touchesBegan:withEvent: will be called but will contain touches which have ended or cancelled.
You should change your counting code, then, to look like this:
int touchesStarted = 0;
int touchesFinished = 0;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self customTouchHandler:touches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self customTouchHandler:touches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self customTouchHandler:touches];
}
- (void)customTouchHandler:(NSSet *)touches
{
for(UITouch* touch in touches){
if(touch.phase == UITouchPhaseBegan)
touchesStarted++;
if(touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled)
touchesFinished++;
}
NSLog(#"%d / %d", touchesStarted, touchesFinished);
}
Every single touch event will go through both phases of started and finished/cancelled, and so your counts should match up as soon as your fingers are off the screen.
One thing to remember... You may receive some touches that have multiple taps. Don't forget to take tapCount into account.
However, if you still have the problem, you can consider all touches from the event, though it presents some other management issues...
HACK ALERT
I have coded the following HACK to work around this. Sometimes the touchesEnded does not get called, BUT, the touches show up as part of all the touches in the event.
Note, that you can now process the same "canceled" or "ended" touch multiple times. If that is a problem, you have to keep your own state of "pending" touches, and remove them when done.
Yeah, it all is pretty bad, but I don't know how to overcome this issue without a similar hack. The basic solution is to look at all the touches in each event, and process them based on their phase, calling the appropriate ended/canceled when they are seen.
- (void) touchesEndedOrCancelled:(NSSet *)touches
{
__block NSMutableSet *ended = nil;
__block NSMutableSet *canceled = nil;
[touches enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
if (touch.phase == UITouchPhaseEnded) {
if (!ended) ended = [NSSet setWithObject:touch];
else [ended addObject:touch];
} else if (touch.phase == UITouchPhaseCancelled) {
if (!canceled) canceled = [NSSet setWithObject:touch];
else [canceled addObject:touch];
}
}];
if (ended) [self touchesEnded:ended withEvent:nil];
if (canceled) [self touchesCancelled:canceled withEvent:nil];
}
Then, call it at the end of touchesBegan and touchesMoved...
[self touchesEndedOrCancelled:event.allTouches];
For this to work, touchesEnded/Canceled needs to not choke on a nil event. Also, the "other" needs to be handled. In touchesEnded...
[self touchesCancelled:[event.allTouches objectsPassingTest:^BOOL(UITouch *touch, BOOL *stop) {
return touch.phase == UITouchPhaseCancelled;
}] withEvent:nil];
and in touchesCanceled...
[self touchesEnded:[event.allTouches objectsPassingTest:^BOOL(UITouch *touch, BOOL *stop) {
return touch.phase == UITouchPhaseEnded;
}] withEvent:nil];
You may need to provide more detail but....
If you run your finger off the edge of the screen this event will show touch started and touch moved but no end as it didn't actually end (lift finger). This could be your problem, that the event didn't happen. Plus there is a limit to the amount of touches in multi touch if you press say 10 times its then up to the system to determine what events are really and what are false, it seems like you are using the hardware incorrectly.

iPhone, Cocos2D - moving sprite left/right while touching screen

I'm new to Objective C and app development so please go easy on me!
I'm trying to make a basic game and need to move a sprite left or right continuously while the user's finger is on the screen - left side to go left, right to go right...
I'm trying to use update to repeat movements of a few pixels every 1/60th second. So far, this is what I have (and sorry about the formatting):
#import "GameplayLayer.h"
#implementation GameplayLayer
-(id)init {
self = [super init];
if (self != nil) {
CGSize screenSize = [CCDirector sharedDirector].winSize;
// enable touches
self.isTouchEnabled = YES;
blobSprite = [CCSprite spriteWithFile:#"blob.png"];
[blobSprite setPosition: CGPointMake(screenSize.width/2, screenSize.height*0.17f)];
ball = [CCSprite spriteWithFile:#"ball.png"];
[ball setPosition:CGPointMake(10, screenSize.height*0.75f)];
[self addChild:blobSprite];
[self addChild:ball];
[self schedule:#selector(update) interval:1.0f/60.0f];
}
return self;
}
-(void) update:(ccTime)dt{
if (_tapDownLeft == YES){
blobSprite.position.x==blobSprite.position.x-5;
}
if (_tapDownRight == YES){
blobSprite.position.x==blobSprite.position.x+5;
}
}
-(void) ccTouchesBegan:(UITouch*)touch withEvent: (UIEvent *)event{
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
if (touchLocation.x > 400) {
if ((blobSprite.position.x+10)<460){
_tapDownRight = YES;
}
}
if (touchLocation.x < 200) {
if ((blobSprite.position.x-10>20)){
_tapDownLeft = YES;
}
}
else {
_tapDownLeft = NO;
_tapDownRight = NO;
}
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
_tapDownLeft = NO;
_tapDownRight = NO;
}
-(void) registerWithTouchDispatcher{
[[CCTouchDispatcher sharedDispatcher]addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
#end
Am I on the right lines with this? At the moment it's giving me 'expression result unused' in update. Could anyone tell me what I'm missing? Any help would be greatly appreciated.
Thanks,
Patrick
i see a few things here:
not certain your selector will call update : #selector(update:)
I would not rely on dt being either exactly 1/60th of a second, nor being constant. I would favor defining a speed constant (in points per second) and compute the deltaX in points based on the desired speed and dt, at each update cycle.
I dont see a 'registerWithTouchDispatcher' call (i try to place them in onEnter and onExit) methods.
Somewhere in there, make certain you remove your children (either in dealloc, or better in a local cleanup method (dont forget to invoke [super cleanup]).
Remove the argument in the update function

(cocos2d sneaky input) when setting joystick position with touchLocation, sprite won't move

i want to let user choose where the joystick should be. i.e., when user touch at one location, the joystick will appear there and ready to use and will remove when finger released.
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self getChildByTag:kTagJoyStick] == nil) {
[self addJoystickWithPosition:[Helper locationFromTouches:touches]];
}
}
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self getChildByTag:kTagJoyStick] != nil) {
[self removeChildByTag:kTagJoyStick cleanup:YES];
}
}
-(void) ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self ccTouchesEnded:touches withEvent:event];
}
(do nothing in ccTouchesMoved method)
the update methods for joystick is:
-(void) sneakyUpdate {
if ([self getChildByTag:kTagJoyStick] != nil) {
if (joystick.velocity.x < 0) {
[self controlLeft];
}
else if (joystick.velocity.x > 0) {
[self controlRight];
}
else {
[self controlStop];
}
}
else {
[self controlStop];
}
}
but the result is, the joystick will appear and auto remove. but my sprite won't move. ( i set the break point, the sneakyUpdate method did get called. but the joystick.velocity is always 0. (and the thumbSprite didn't follow our finger.
please help me.
update:
and it turns out that i have to use 2 fingers (one for touch once and let the joystick show up, move my finger away, and then use another finger to control the joystick)
I'm not 100% sure, but I think you should use ccTouchBegan instead ccTouchesBegan, because sneakyJoystick classes use ccTouchBegan/Moved/Ended/Cancelled. Also, there are for a single touch, that is what you want.
I hope it works!
It looks like the problem is in your joystick class. Every joystick implementation I've seen uses the ccTouchesBegan method to activate the joystick, then in the ccTouchesMoved method, it makes sure its activated before using it. The problem I am seeing is that you create and add the joystick AFTER the touches began method, meaning your joystick never 'activates'. One way of bypassing this is to do all of the joystick's ccTouchesBegan functions in the method that creates the joystick, and 'activate' it from there by passing a reference to the touch that will be using it.

How to call a method only once during -(void)touchesMoved?

I am using - (void) touchesMoved to do stuff when ever I enter a specific frame, in this case the area of a button.
My problem is, I only want it to do stuff when I enter the frame - not when I am moving my finger inside the frame.
Does anyone know how I can call my methods only once while I am inside the frame, and still allow me to call it once again if I re-enter it in the same touchMove.
Thank you.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self.view] anyObject];
CGPoint location = [touch locationInView:touch.view];
if(CGRectContainsPoint(p1.frame, location))
{
//I only want the below to me called
// once while I am inside this frame
[self pP01];
[p1 setHighlighted:YES];
}else {
[p1 setHighlighted:NO];
}
}
You can use some attribute to check if the code was already called when you were entering specific area. It looks like highlighted state of p1 object (not sure what it is) may be appropriate for that:
if(CGRectContainsPoint(p1.frame, location))
{
if (!p1.isHighlighted){ // We entered the area but have not run highlighting code yet
//I only want the below to me called
// once while I am inside this frame
[self pP01];
[p1 setHighlighted:YES];
}
}else { // We left the area - so we'll call highlighting code when we enter next time
[p1 setHighlighted:NO];
}
Simply add a BOOL that you check in touchesMoved and reset in touchesEnded
if( CGRectContainsPoint([p1 frame],[touch locationInView:self.view])) {
NSLog (#"Touch Moved over p1");
if (!p14.isHighlighted) {
[self action: p1];
p1.highlighted = YES;
}
}else {
p1.highlighted = NO;
}
try using a UIButton and use the 'touch drag enter' connection in Interface Builder.