How to call method only once on first contact? - sprite-kit

I have an issue where I have a game over/stop function:
Hero.m
+(id)hero//The hero
{
JBHero *_hero = [JBHero spriteNodeWithTexture:heroTexture1];
_hero.name = #"hero";
return _hero;
}
- (void)stop
{
self.physicsBody.contactTestBitMask = button2Category;
/*Setting contact to the only node I want contact with when gameover is called*/
self.physicsBody.allowsRotation = YES;
[self removeAllActions];
NSLog(#"STOP");
}
GameScene.m
-(void)gameOver
{
self.isGameOver = YES;
[hero stop];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
if (! self.isStarted){
[self start];//Start method
self->hero.physicsBody.dynamic = YES;
}
else if (self.isGameOver){
[self clear];//Reset scene
}
else [hero jump];
}
that calls the stop function on every single collision the hero has with the obstacles. I only want it to call the stop function on the very first contact the hero has with the obstacles and ignore the following contacts.
Any help would be great.

Related

Spritekit pause and resume SKSpriteNode's action by touchesBegan

I have a SKSpriteNode with a repeated movement, let me call it RunAction A, e.g goes up - down. Now I want to make a movement, letz call it RunAction B, left - middle - right - middle, by touchesBegan.
And after finishing RunAction B, RunAction A should resume. It should start an the position, where RunAction B did start and did stop.
If I use (attention pseudo lang.)
[sprite RunAction A]
[sprite setPaused: True]
[sprite RunAction B]
[sprite setPaused: False]
I can see, sprite has never paused!
Is it possible, to let the sprite let resume the action where it stops before?
Thanks
#import "GameScene.h"
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 65;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
NSString *myParticlePath = [[NSBundle mainBundle] pathForResource:#"fireflies" ofType:#"sks"];
SKEmitterNode *myParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticle];
self.physicsWorld.gravity=CGVectorMake(0.0, -9);
self.physicsBody=[SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.linearDamping=0.0;
SKSpriteNode *rectNode=[SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(50, 50)];
rectNode.name=#"rect";
rectNode.position=CGPointMake(300, 300);
[self addChild:rectNode];
[self moveAction];
}
-(SKAction*)groupAction:(CGPoint)points
{
SKAction *A=[SKAction moveTo:CGPointMake(points.x+100, points.y+100) duration:1];
SKAction *B=[SKAction moveTo:CGPointMake(500, 100) duration:1];
SKAction *C=[SKAction moveTo:CGPointMake(100, 100) duration:1];
SKAction *s=[SKAction sequence:#[A,B,C]];
return s;
}
-(void)moveRect:(SKNode*)node
{
[node runAction:[SKAction repeatActionForever:[self groupAction:node.position]] withKey:#"moveAction"];
}
-(void)moveAction
{
[self enumerateChildNodesWithName:#"rect" usingBlock:^(SKNode *node, BOOL *stop) {
[self moveRect:node];
}];
}
-(void)clearAction
{
[self enumerateChildNodesWithName:#"rect" usingBlock:^(SKNode *node, BOOL *stop) {
[node removeActionForKey:#"moveAction"];
}];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
if(!_stop)
{
[self clearAction];
_stop=TRUE;
}
else
{
[self moveAction];
_stop=FALSE;
}
/*
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.physicsBody.allowsRotation=FALSE;
sprite.physicsBody=[SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(sprite.size.width, sprite.size.height)];
sprite.physicsBody.friction=0.0;
sprite.physicsBody.linearDamping=0.0;
sprite.physicsBody.restitution=1.0;
sprite.xScale = 0.5;
sprite.yScale = 0.5;
sprite.position = location;
[self addChild:sprite];
*/
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
check out this example i just used a single rectangle but you can use as many object you want be sure that different action group contain a different name so its easy for you to delete and pause a action when user touch screen and apply and new group of action on another object when a action is deleted or paused.
every action has a complete handler in sprite kit so you can easily get when an action is finished or completed by using below sentence
//action A completed do whatever you want's to do when action is finished
[sprite runAction: A completion:^{
[sprite setPaused: True]
}];

how to implement a reverse countdown and then call a method

I am developing a 2d game in which on my game screen I have to implement a reverse timer from 30 sec till 0 sec and if a player does not move his character he will win otherwise he will lose and the game will be over.
This is my init method:
-(id)init
{
if(self==[super init])
{
self.isTouchEnabled=YES;
self.isAccelerometerEnabled=YES;
CGSize size=[[CCDirector sharedDirector] winSize];
screenwidth=size.width;
screenheight=size.height;
west_pic=[CCSprite spriteWithFile:#"west_pic.png"];
west_pic.anchorPoint=ccp(1, 1);
west_pic.scaleX=1.4;
west_pic.position=ccp(size.width, size.height);
[self addChild:west_pic];
label1=[CCLabelTTF labelWithString:#"Level One" fontName:#"Arial" fontSize:20];
label1.position=ccp(size.width/3.8,size.height/1.2);
[self addChild:label1];
label2=[CCLabelTTF labelWithString:#"Lives :" fontName:#"ArcadeClassic" fontSize:18];
label2.position=ccp(size.width/1.5,size.height/8.2);
[self addChild:label2];
player=[CCSprite spriteWithFile:#"player.png"];
player.position=ccp(size.width/1.7, size.height/2);
player.scale=0.2;
player.tag=2;
player.anchorPoint=ccp(1, 0);
[self addChild:player];
[self schedule:#selector(updateplayer:) interval:1.0f/60.0f];
}
return self;
}
for example, you can have an instance with number of seconds, that player must not to move character, then you must have ssome event methods to know when the character begins and stops movement, create updatable label(if you want to show the rest of the time to player) and schedule timer, which will decrease number of seconds. smth like this
- (void) onEnter
{
[super onEnter];
// if player character not move on start
[self schedule:#selector(reduceRestTime) interval:1.f];
}
- (void) onExit
{
[self unscheduleAllSelectors];
[super onExit];
}
- (void) onCharacterStop
{
m_restTime = // your time left. in your case, 30 sec
// if you have time label
[self updateRestTimeLabel];
[self schedule:#selector(reduceRestTime) interval:1.f];
}
- (void) onCharacterBeginMovement
{
[self unscheduleSelector:#selector(reduceRestTime)];
}
- (void) reduceRestTime
{
m_restTime--;
// if you have time label
[self updateRestTimeLabel];
if( m_timeLeft == 0 )
{
[self unscheduleSelector:#selector(reduceRestTime)];
// your win code
}
}
- (void) updateRestTimeLabel
{
[m_timeLabel setString:[NSString stringWithFormat:#"Time left: %d", m_timeLeft]];
}

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

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.