Handling Double Tap in Cocos2d - objective-c

This question is too primitive, but I have been trying for the last 8 hours, and it is eating away my energy ( and confidence levels too :))
In my class, I have registered for Targeted touches.
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
I would like to check if the user is performing single tap or double tap. In ccTouchBegan method, first gets a touch with singleTap, then gets a touch with doubleTap. I found a very interesting solution for this one, in one of the Cocos2d Forums. ( I could not locate it now.. )
The solution goes like this.
switch (touch.tapCount) {
case 2:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(handleSingleTouch:) object:singleTouchLocation];
// Handle Double Touch here
break;
case 1:
self.singleTapLocation = ccp(location.x,location.y);
self.singleTouchLocation = touch;
[self performSelector:#selector(handleSingleTouch:) withObject:touch afterDelay:0.3
];
break;
}
Everything looks to be working fine. Essentially, you schedule a single touch handler with a delay and see if it is actually a double tap. If it is a double tap, then cancel singletap handler and perform logic for double tap.
But my problem is, In the single touch handler (handleSingleTouch), I am adding a CCSprite to the UI. This action is not working. No sprite gets added. (Although the method is called.). In fact this works, If I call the selector with no delay, but with delay, the sprite is not added.
I am not a Objective C expert, So, I apologize if the question is too primitive.
Edit 1: The original thread.
Edit 2: Posting handleSingleTouch.. only relevant code.
-(void) handleSingleTouch:(UITouch * ) touch {
CGPoint location = [touch locationInView: [touch view]];
CGPoint glLocation = [MainSceneLayer locationFromTouch:touch];
//Single tap
CCLOG(#"Single tap: Adding marker image");
if(zoomedOut) {
CGPoint globalCoordinates = [quadConvertor convertLocalToGlobal:location
inActiveQuadrant:activeQuadrant];
if( [self isTouchValidForSelectedItem:globalCoordinates] == Matched) {
[self addMarkerImages:glLocation];
} else if([self isTouchValidForSelectedItem:globalCoordinates] == NotMatched) {
[missedLabel stopAllActions];
for (ImageQuadrants* quad in quadrants) {
if(quad.quadrantNumber == activeQuadrant) {
missedLabel.position = ccp((quad.center.x * -1)+glLocation.x , (quad.center.y * -1)+glLocation.y);
//markers.position = ccp((quad.center.x * -1)+touchPosition.x , 320);
}
}
id blinkAction = [CCBlink actionWithDuration:1 blinks:3];
id makeInvible = [CCCallFunc actionWithTarget:self selector:#selector(makeInvisible:)];
id seq = [CCSequence actions:blinkAction, makeInvible, nil];
[missedLabel runAction:seq];
} else {
[alreadyAccountedLabel stopAllActions];
for (ImageQuadrants* quad in quadrants) {
if(quad.quadrantNumber == activeQuadrant) {
alreadyAccountedLabel.position = ccp((quad.center.x * -1)+glLocation.x , (quad.center.y * -1)+glLocation.y);
//markers.position = ccp((quad.center.x * -1)+touchPosition.x , 320);
}
}
id blinkAction = [CCBlink actionWithDuration:1 blinks:3];
id makeInvible = [CCCallFunc actionWithTarget:self selector:#selector(makeInvisible1:)];
id seq = [CCSequence actions:blinkAction, makeInvible, nil];
[alreadyAccountedLabel runAction:seq];
}
}
swipeStartPoint = [touch locationInView:touch.view];
}

i dont know if thats a typo in your question only but your delay method is all wrong. There is no withDelay: argument. it should look like this:
[self performSelector:#selector(handleSingleTouch:) withObject:touch afterDelay:0.1];
EDIT:
Since your problem is most probably with the touch getting lost. Try [touch retain] before calling the delayed method. Another way is changing the handleSingleTouch: method to take two floats x and y or a CCPoint as arguments instead of a UITouch. Then you create the floats before the delayed method and pass them in the delayed method. This way you will avoid a memory leak from retaining a touch as well since most probably you cant release it after calling the delayed method.
hope this helps

Related

Trigger audio files based on a method call or other variables

I am designing a game that changes scenes as the user is playing. The scenes change based on a NSTimer. I can ge everything to change accordingly in regards to graphics. Can even get background music to change.
Where I seem to be hitting a roadblock is changing the score sound and the sound for when the player collides with an object and dies.
As it it's right now I can cancel the sound for scene one but cannot get another one to play.
* on phone don't know how to hand code a code block on here*
If([_timer isValid]) {
//plays correct audio and does not continue when scene changes
}
Else if ([_timer == nil ]){
//suppose to play other sound but does nothing.
}
I would really prefer to call the different sounds based on if a method is called to change something along the lines of
Else if ([ _method1 == TRUE]) {
//play sound
}
Any advice in the right direction appreciated.
EDIT
- (void)didBeginContact:(SKPhysicsContact *)contact {
if( _moving.speed > 0 ) {
if( ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory ) {
// has contact with score entity
_score++;
_scoreLabelNode.text = [NSString stringWithFormat:#"%d", _score];
//Pick Up Egg
//Sound for Score
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"Point1" waitForCompletion: YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
I have been trying to structure an IF statement based on the background image because that changes with the sounds.
EDIT
_scoreSound = [SKAudioNode node];
SKAudioNode* teslaPoint = [SKAudioNode nodeWithFileNamed:#"Point1"];
[_scoreSound addChild:teslaPoint];
EDIT
THIS IS ADDED IN initwithSize:
_scoreSound = [SKNode node];
[self addChild:_scoreSound];
Then its called in the contacts
EDIT
//Sound for Score
if([_timer isValid]){
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"Point1" waitForCompletion:YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
}
if ([_bgImage isEqual: #"bgimage2.png"]){
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"grassypoint" waitForCompletion:YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
}

Objective-c : Set timeout / delay before function can be called again

I'm trying to make a cocos2D game in which every time the player touches the screen, the background scrolls to the left thus simulating moving forwards.
The "background" consists of 2 very long rectangular panels called pane1 and pane2 linked together in a chain. When the screen is touched, I use CCActionMoveTo to move both panes to the left, and when one pane is completely off the screen, I move it back around to the other side to create an infinite loop.
The problem is the background scrolling animation takes .2 seconds, and if the player just mashes the screen a lot, it messes up everything. And, sometimes these two panes experience different amounts of lag so that they desync.
How do I set a delay on a function so that it can only be called once per designated time period? In other words, I want to add a "cooldown" to the function that handles player touch.
This function is called every time the scene is touched:
- (void)playerMove {
CCActionMoveTo * actionMove1 = [CCActionEaseOut actionWithAction:
[CCActionMoveTo actionWithDuration:.2
position:ccp(pane1.position.x - 150, 0)]
rate: 1.5];
CCActionMoveTo * actionMove2 = [CCActionEaseOut actionWithAction:
[CCActionMoveTo actionWithDuration:.2
position:ccp(pane2.position.x - 150, 0)]
rate: 1.5];
CCActionCallFunc * actionCallGenerateTerrain = [CCActionCallFunc actionWithTarget: self selector:#selector(generateTerrain)];
counter++;
if(pane1InUse){
[pane1 runAction: [CCActionSequence actionWithArray:#[actionMove1, actionCallGenerateTerrain]]];
[pane2 runAction: actionMove2];
}
else
{
[pane1 runAction: actionMove1];
[pane2 runAction: [CCActionSequence actionWithArray:#[actionMove2, actionCallGenerateTerrain]]];
}
}
-(void) generateTerrain {
if (counter%8 == 0){
pane1InUse ^= YES;
CCLOG(#"%#", pane1InUse ? #"YES" : #"NO");
if (pane1InUse){
CCLOG(#"Generating Terrain 2 ...");
pane2.position = ccp(pane1.position.x+pane1.boundingBox.size.width, 0);
}
else{
CCLOG(#"Generating Terrain 1 ...");
pane1.position = ccp(pane2.position.x+pane2.boundingBox.size.width, 0);
}
}
}
Also you may have noticed I'm using this weird block of code because ideally I would like actionMove1 and actionMove2 to be executed simultaneously, and once they are both done, then execute actionCallGenerateTerrain, but I don't know how to implement that:
if(pane1InUse){
[pane1 runAction: [CCActionSequence actionWithArray:#[actionMove1, actionCallGenerateTerrain]]];
[pane2 runAction: actionMove2];
}
else
{
[pane1 runAction: actionMove1];
[pane2 runAction: [CCActionSequence actionWithArray:#[actionMove2, actionCallGenerateTerrain]]];
}
}
You probably want to put pane1 and pane2 in a ccNode , call it combinedPanes (this will make pane1 and pane2 always move in sync). Then perform a single action on combinedPane. Also add a boolean state property, call it moveEnabled;
id movePane = [CCActionMoveTo ... ]; // whatever you have for pane1 or pane2
id moveComplete = [CCActionBlock actionWithBlock:^{
[self generateTerrain];
self.moveEnabled = YES;
}];
self.moveEnabled = NO;
[movePane runAction:[CCActionSequence actions:movePane,moveComplete,nil]];
and use moveEnabled to allow/deny the touch processing that detected a touch and triggered this move code. During the move, this will drop touches and effectively block your hysterical user tapping like nuts.
-(void) playerMove {
if (self.moveEnabled){
//
// the rest of your logic
// ...
}
}
and in init (or if you detect this condition already, place it there).
self.moveEnabled = YES;
One simple way to prevent a function being called twice within a time period is to wrap it in an if statement like:
if ([[NSDate date] timeIntervalSinceDate:lastUpdated] > minDelay)
{
// call function here
lastUpdated = [NSDate date];
}
where lastUpdated is initialized using [NSDate date] and minDelay is the required minimum delay in seconds.

Showing a button when all 'enemy' ccsprites have been removed from scene

I am using SpriteBuilder to make a game. The objective is to destroy some CCSprites. I have 3 sprites on screen and are destroyed by another sprite, so the code must have something to do with when there are no more 'enemy' sprites remaining a next button must show. I have looked on the internet and are inexperienced with Cocos2D coding. Here is the code I have used to get rid of the 'enemy'
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair danald:(CCNode *)nodeA wildcard:(CCNode *)nodeB {
float energy = [pair totalKineticEnergy];
if (energy > 5000.f) {
[self danaldRemoved:nodeA];
}
}
If the object is hit with a certain speed it will call the method below
- (void)danaldRemoved:(CCNode *)Danald {
CCParticleSystem *explosion = (CCParticleSystem *)[CCBReader load:#"Explosion"];
explosion.autoRemoveOnFinish = TRUE;
explosion.position = Danald.position;
[Danald.parent addChild:explosion];
[Danald removeFromParent];
}
Thanks in an advanced, sorry if this question has been asked before but I cannot find it
Well I would suggest this method:
Create a variable where you store the number of sprites left. For example:
int spritesLeft;
And then initialize it to 0:
-(void) didLoadFromCCB{
//REST OF CODE
spritesLeft=3; //3 because you said there are only 3.
}
Now when you call danaldRemoved: method, just subtract 1 to spritesLeft, and check if spritesLeft is equal to 0. If it's true, just call your method to make a button appear:
- (void)danaldRemoved:(CCNode *)Danald {
spritesLeft--; //substract 1
CCParticleSystem *explosion = (CCParticleSystem *)[CCBReader load:#"Explosion"];
explosion.autoRemoveOnFinish = TRUE;
explosion.position = Danald.position;
[Danald.parent addChild:explosion];
[Danald removeFromParent];
//check if game is over.
if (spritesLeft == 0){
[self printButton];
}
}
Now create the method printButton, but before go to SpriteBuilder, create the button and place it where you want. Now uncheck 'Visible' value, and then go to code connections, and select 'Doc root var' (under custom class) and write a name for the button, for example: nextButton. At the selector value write: changeLevel and target: document root
Now declare it at the top of your .m file as you did with any other objects:
CCButton *nextButton;
Method for button (just set visibility ON)
-(void) printButton{
nextButton.visible = YES;
}
And now your method to change level:
-(void) changeLevel{
CCScene *nextLevel = [CCBReader loadAsScene:#"YOUR LEVEL"];
[[CCDirector sharedDirector] replaceScene:nextLevel];
}
Hope this helps!
EDIT: HOW TO DETECT WHEN A SPRITE GOES OFF THE SCREEN
As I said, create any kind of physic object in spritebuilder. For example, I use CCNodeColor. Then make it a rectangle and place it at left of the screen. Now go to physics, enable physics, polygon type and static. Now in connections, select doc root var and call it _leftNode. Now repeat with top,right and bottom and call them _topNode, etc.
Now go to code, declare your new nodes: CCNode *_leftNode; and so...
Now let's make a collision type:
_bottomNode.physicsBody.collisionType = #"_bound";
_leftNode.physicsBody.collisionType = #"_bound";
_rightNode.physicsBody.collisionType = #"_bound";
_topNode.physicsBody.collisionType = #"_bound";
And do the same with your sprite, but I think you have done that before. Let's make an example:
spritename.physicsBody.collisionType = #"_sprite";
So now implement the method:
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair _sprite:(CCNode *)nodeA _bound:(CCNode *)nodeB {
[_physicsNode removeChild:nodeA cleanup:YES];
}
And that's all.

cocos2d running sequences in order on different targets

So, i have a basic game scenerario:
Some unit moves close to an enemy unit, shoots projectile and then enemy unit's life is adjusted. My problem is that I am not sure how to schedule these three events to run one after another. If all these actions were done on the same target, then this would be super easy, but there are two different targets.
What would be best approach to do this?
Code looks like this:
Unit* unit = [self getActiveUnit];
Unit* enemy = [self getEnemyInRange:unit];
CGpoint A = unit.sprite.position;
CGPoint B = [self getPositionClose:enemy for:unit];
CCSequence* unitMove = [self generateUnitMoveFrom:A to:B];
Projectile* proj = [self generateProjectile];
CCSequence* projMove = [self generateProjMoveFrom:A to:B];
CCSequence* attackDone = [self generateAttackDoneFor:unit enemy:enemy];
// This is the part that i don't know how to do
// Execute these in order and sequentially, not at the same time
[unit.sprite runAction:unitMove];
[proj.sprite runAction:projMove];
[proj.sprite runAction:removeSprite];
[self runAction:attackDone];
What is the best approach to do this? Even using CCActionManager it still seems fairly complicated because i think i would have to add an extra call back between all these actions to resume scheduled actions for the next target.
Any ideas?
Thanks!
I would attempt to use CCSequence
[self runAction:[CCSequence actions:
[CCCallFuncO actionWithTarget:unit.sprite selector:#selector(runAction:) object:unitMove],
[CCCallFuncO actionWithTarget:proj.sprite selector:#selector(runAction:) object:projMove],
[CCCallFuncO actionWithTarget:proj.sprite selector:#selector(runAction:) object:removeSprite],
[CCCallFunc actionWithTarget:self selector:#selector(attackDone)],
nil]];
You may need this code for running Multiple CCAction ( each action for a different CCNode/CCSprite/... ) ; But starting All in sequence !
CCMoveBy *moveUpAction = CCMoveBy::create(0.5,ccp(0,400));
CCMoveBy *moveRightAction = CCMoveBy::create(1,ccp(300,0));
CCMoveBy *moveDownAction = CCMoveBy::create(1,ccp(0,-400));
CCTargetedAction *mm = CCTargetedAction::create(someNode_1,moveUpAction);
CCTargetedAction *rr = CCTargetedAction::create(someNode_2,moveRightAction);
CCTargetedAction *dd = CCTargetedAction::create(someNode_3,moveDownAction);
// Here we first run 'moveUpAction' on someNode_1.
// After finishing that Action we start 'moveRightAction' on someNode_2
// After finishing the Action, we start ...
CCSequence *targetedSeq = CCSequence::create(mm,rr,dd,NULL);
whateverNode->runAction(targetedSeq );
Agreed with James. Plus you could add in a delay if there was some action you wanted to give a little more time to before the others are run...
[CCDelayTime actionWithDuration:0.5 ];
you can create an array of your actions, then scedule some method to call them one by one. For example
- (void) playManyActionsOneByOne
{
// create some actions and add them to the
// mutable array m_actionsContainer
[self runNextActionInArray];
}
- (void) runNexActionInArray
{
if( [m_actionsContainer count > 0] )
{
id nextAction = [m_actionsContainer objectAtIndex:0];
id callback = [CCCallFunc actionWithTarget: self selector: #selector(runNextActionInArray)];
id sequence = [CCSequence actionOne: nextActon two: callback];
[neededNode runAction: sequence];
[m_actionsContainer removeObjectAtIndex:0];
}
}
it will run actions one by one and you even can add actions to your array while other actions aren't done yet.

.Net developer new to Objective-C. Need some critique and suggestions

I've created one of my first apps using Objective-C. Being a noob, there's a lot of stuff I want to do, but don't know how to apply it in Objective-C. Please take a look at the method below (which I created from scratch) and tell me what you would do to make it better. Obviously I've duplicated code across 2 UILabels, but I'd like to simplify that (I hate duplicating code) but I'm unaware what the best way to do it is. I just need suggestions which will help me better understand the right way to do stuff in Objective-C
timeText and dateText are of type UILabel
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (isRearranging)
{
NSLog(#"touchesMoved");
NSLog(#"touches=%#,event=%#",touches,event);
//TOUCH INFO
UITouch *touch = [[touches allObjects] objectAtIndex:0];
CGPoint currentLocation = [touch locationInView:touch.view];
CGPoint previousLocation = [touch previousLocationInView:touch.view];
//FRAME INFO
float timeHalfWidth = timeText.frame.size.width / 2;
float timeHalfHeight = timeText.frame.size.height / 2;
CGRect timeTextRect = CGRectMake(timeText.center.x - (timeHalfWidth), timeText.cener.y - (timeHalfHeight), timeText.frame.size.width, timeText.frame.size.height);
float dateHalfWidth = dateText.frame.size.width / 2;
float dateHalfHeight = dateText.frame.size.height / 2;
CGRect dateTextRect = CGRectMake(dateText.center.x - (dateHalfWidth), dateText.center.y - (dateHalfHeight), dateText.frame.size.width, dateText.frame.size.height);
//IF TIME TEXT
if(CGRectContainsPoint(timeTextRect,previousLocation))
{
CGPoint item = timeText.center;
CGPoint diff;
diff.x = previousLocation.x - item.x;
diff.y = previousLocation.y - item.y;
CGPoint newLoc;
newLoc.x = currentLocation.x - diff.x;
newLoc.y = currentLocation.y - diff.y;
if (newLoc.x<timeHalfWidth)
newLoc.x = timeHalfWidth;
if (newLoc.y<timeHalfHeight)
newLoc.y = timeHalfHeight;
[timeText setCenter:(newLoc)];
}
//IF DATE TEXT
if(CGRectContainsPoint(dateTextRect,previousLocation))
{
CGPoint item = dateText.center;
CGPoint diff;
diff.x = previousLocation.x - item.x;
diff.y = previousLocation.y - item.y;
CGPoint newLoc;
newLoc.x = currentLocation.x - diff.x;
newLoc.y = currentLocation.y - diff.y;
if (newLoc.x<dateHalfWidth)
newLoc.x = dateHalfWidth;
if (newLoc.y<dateHalfHeight)
newLoc.y = dateHalfHeight;
[dateText setCenter:(newLoc)];
}
}
touchMoved = YES;
}
Thanks so much for your help!
A first step, independent from the language you are working in, would be to follow DRY - most of your code is the same for both labels.
Then there is already functionality for hit-testing in the SDK, e.g. -hitTest:withEvent: or -pointInside:withEvent::
NSArray *labels = [NSArray arrayWithObjects:timeText, dateText, nil];
for (UILabel *label in labels) {
if ([label pointInside:previousLocation withEvent:nil]) {
[self relocateLabel:label];
break;
}
}
I will answer an additional question posed by asker in a comment. Quote:
What if I wanted to use mixed types in that array? i.e. a couple UILabel and a couple UIImageView. Is there a way to compare types or use generics? This is a poor guess/example for (NSObject *obj in objects) { if (label.type==UILabel) [self relocateLabel:obj]; else if (label.type==UIImageView) [self relocateImage:obj]; }
As Georg Fritzsche answered there, Objective-C messaging is dynamic. Object will be "queried" if it supports that message at runtime, and if so, it will execute the method associated with the message. Method/message name is called a "selector".
If you explicitly want to figure out object's class, you can do that as well.
if([view isKindOfClass:[UILabel class]])
{
// your code here
}
If you just want to figure out if the target object responds to a selector (that is, implements a method):
if([view respondsToSelector:#selector(relocateView:)])
{
// your code here
}
Selectors are derived from method names by omitting arguments themselves, leaving colons intact and appending everything closely. For example, if you had a message send (that is, method call): [thing moveTowardsObject:door movementType:XYZ_CRAWL], its selector would be: moveTowardsObject:movementType: and you'd get it using #selector(moveTowardsObject:movementType:).
In a loop such as what Georg posted, you typically want to just check if the target object responds to a selector, since otherwise an exception would be thrown, and Objective-C code rarely catches exceptions as part of a normal code flow (as opposed to what Python developers do).