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.
Related
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"];
}
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.
I'm working on my first big application and have inadvertently driven myself into a panic from a small flaw in design. I've made a timer that counts at the touch of a button, and upon a second touch, transitions into a secondary timer with a 60 second countdown.
The problem lies in the fact that once I repeat this process, the (kRest-Pause) call is remembered. I'm looking to create a default countdown of 60 without the continuation of the time. Should I kill it in memory and create a new instance for each subsequent button press? Or is there a logic game that looks at the aggregate time and corrects with each new occurance?
I don't know how to approach this, I'm done attempting -if statements with returns as I have learned that's not how that works. Any help would be appreciated
EDIT: Sorry for the lack of clarity. I'm pretty green on programming of any caliber. So far, the main timer counts down from 10-0 then up from 0-120. In that 2 minute period, if the button is pressed again, the 0-120 count is paused for 60 seconds or until the button is pressed for the third time. If this 60-0 countdown reaches 0 or is interrupted, the initial 0-120 countup resumes its count. My issue is that if I push the button a fourth time, the 60-0 countdown is resumed from the moment of interruption without retaining a default of 60. This is why I named the post "creating defaults in Objective C". It's the wrong use of the word and way to broad, but it's what I could come up with.
kRest=60
-(void)increase{
if (mode==1){
count++;
int d = 10-count;
if (d==0){ timeLabel.text = #"Begin";
[self startPlaybackForPlayer: self.startTimerSound];}
else {timeLabel.text = [NSString stringWithFormat:#"%d", abs(d)];}
if(d<0){
//workign out
active = TRUE;
if (d <= -20) {
[self stopTimer];
}
}
else{
//no user interface
active = FALSE;
}
}
else{
pause++;
countdownLabel.text = [NSString stringWithFormat:#"%d!", (kRest-pause)];
NSLog(#"Paused at time %d", pause);
UIColor *textColor = nil;
if (pause % 2==0){
textColor = [UIColor yellowColor];
}
else{
textColor = [UIColor redColor];
}
timeLabel.textColor = textColor;
if ((kRest-pause)==0){
countdownLabel.text = [NSString stringWithFormat:#"%d!",pause];
mode=1;
pause=0;
[button setTitle:#"Stop" forState:UIControlStateNormal];
repCount++;
myRepCount.text = [NSString stringWithFormat:#"Rep Count: %d", repCount];
countdownLabel.text = #"";
}
}
}
if you are using a timer to access this counter then you should be able to update the counter that the timer uses. Just make sure you synchronize the object so you dont edit while you are reading.
here is an example of what I mean.
int counter = 0;
int limit = 60;
- (BOOL) incrementUntilReached{
#synchronized(self){
if (counter == limit) return YES;
counter++;
return NO;
}
}
- (void) resetTimer{
#synchronized(self){
counter = 0;
}
}
- (int) countsLeft {
#synchronized(self){
return limit - counter;
}
}
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.
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