I was wondering how to call a method after a CCAction is finished (I would also like to call it every 2.5 seconds after it is first called). I have a sprite that goes to a random position, and I want it to run this method (a shooting bullet one) after it is done moving to its random position. So far the method is called when it is still moving. Can anyone help?
Here is the enemy create method:
(void)enemy1{
gjk= arc4random()%6;
enemy1 = [CCSprite spriteWithFile:#"enemy1.png"];
int d = arc4random()%480+480;
int o = arc4random()%320+320;
x = arc4random()%480;
if( x <= 480 && x>= 460){
x=x-100;
}
if(x <= 100){
x = x+50;
}
y = arc4random()%320;
if(y <=320 && y >= 290){
y = y-100;
}
if(y < 100){
y = y + 100;
}
enemy1.position = ccp(o,d);
[enemy1 runAction:[CCMoveTo actionWithDuration:3 position: ccp(x,y)]];
CCRotateBy *rotation = [CCRotateBy actionWithDuration:20 angle:1080];
CCRepeatForever * repeatforever = [CCRepeatForever actionWithAction:rotation];
[enemy1 runAction:repeatforever];
[self addChild:enemy1];
[enemy addObject :enemy1];
}
And the method for shooting a projectile:
(void)projectileShooting:(ccTime)dt {
projcount++;
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
}
}
The shooting method is called in the init method by the code, so it is called every 2.5 seconds.
[self schedule:#selector(projectileShooting:) interval:2.5];
I know I tried to make the shooting happen by making it so that is shoots when the y position is < 320, but it is still moving when it passes the position of 320.
You can make a sequence of actions and at the end of sequence you can give a callback function which will be executed at the end. Something like this
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(methodToRunAfterAction)],
nil]];
Adding on to Tayyab's answer, you can simply start the actual scheduling of your projectile firing within the startShooting method (or whatever you want to call it) which is fired at the end of your move action:
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(startShooting)],
nil]];
where startShooting is defined as follows:
- (void) startShooting
{
// start scheduling your projectile to fire every 2.5 seconds
[self schedule:#selector(projectileShooting:) interval:2.5];
}
Related
I am new to the community, so let me know if my question is unclear. I am trying to make a choice reaction exercise on the iPAD. There are two images that should appear in random sequence on the left and right of the screen, and the user will respond by tapping a button that corresponds to the position of the appeared image. Here's the problem, I tried to get the two images to appear at random order using the following way:
- (void) viewDidAppear:(BOOL)animated
{
for(int n = 1; n <= 20; n = n + 1)
{
int r = arc4random() % 2;
NSLog(#"%i", r);
if(r==1)
{
[self greenCircleAppear:nil finished:nil context: nil];
}
else
{
[self redCircleAppear:nil finished:nil context: nil];
}
}
}
However, 20 random numbers get generated while only 1 set of animation is run. Is there a way to let the animation finish running in each loop before the next loop begins? Any help is appreciated, thanks in advance!
When you say "only one set of animation is run" I'm assuming that means greenCircleAppear and redCircleAppear begin the sequence of images appearing and the user pressing a button. If that's the case, I'd recommend not using a for loop in viewDidAppear but instead have viewDidAppear initialize the current state and call a method that presents the next animation. When the animation is finished, have it call the method that presents the next animation. Something along these lines:
Add this to the interface:
#interface ViewController ()
#property NSInteger currentIteration;
#end
This is in the implementation:
- (void)viewDidAppear:(BOOL)animated {
self.currentIteration = 0;
[self showNextAnimation];
}
- (void)greenCircleAppear:(id)arg1 finished:(id)arg2 context:(id)arg3 {
//perform animation
NSLog(#"green");
[self showNextAnimation];
}
- (void)redCircleAppear:(id)arg1 finished:(id)arg2 context:(id)arg3 {
//perform animation
NSLog(#"red");
[self showNextAnimation];
}
- (void)showNextAnimation {
self.currentIteration = self.currentIteration + 1;
if (self.currentIteration <= 20) { //you should replace '20' with a constant
int r = arc4random() % 2;
NSLog(#"%i", r);
if(r==1)
{
[self greenCircleAppear:nil finished:nil context: nil];
}
else
{
[self redCircleAppear:nil finished:nil context: nil];
}
}
else {
//do what needs to be done after the last animation
}
}
I have a problem with my cocos2d game. I am trying to delete a projectile shot by an enemy every 5 seconds (each projectile is supposed to have a lifetime of 5 seconds), but I cannot figure out how to do it. I get the error of
Assertion failure in -[CCTimer initWithTarget:selector:interval:]
Here is my code:
-(void)projectileShooting:(ccTime)dt
{
[self schedule:#selector(projectileShooting:) interval:2.5];
projcount++;
if([proj count] <= 15 ){
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
for(CCSprite *projectile in proj){
[self schedule:#selector (deleteprojectile:projectile:) interval:5];
}
}
}
}
for(CCSprite *enem2 in enemytwo)
{
if( [proj count] <= 15){
CCSprite * projectilebull = [CCSprite spriteWithFile:#"Projectile.png"];
CGPoint MyVector = ccpSub(enem2.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enem2.contentSize.width/2+10);
MyVector = ccpMult(MyVector,-1);
projectilebull.position = ccpAdd(enem2.position, MyVector);
[self addChild:projectilebull];
[proj addObject:projectilebull];
for(CCSprite *projectile in proj){
}
}
}
}
-(void)deleteprojectile:(CCSprite *)protime:(ccTime)dt{
NSMutableArray *timepro = [[NSMutableArray alloc]init];
[timepro addObject:protime];
for(CCSprite *objecttime in timepro){
[proj removeObject:objecttime];
[self removeChild:objecttime cleanup:YES];
}
}
It's a bit of a hack but this is what I use in my program, until I find a more elegant solution. I have a method in my game layer that I call to remove a node from its parent, like so:
-(void)removeNode:(CCNode*)node {
[node removeFromParentAndCleanup:YES];
}
And when I want to schedule a node for deletion after a delay, I call it like this:
[self performSelector:#selector(removeNode:) withObject:node afterDelay:delay];
Simple, and it works.
Change argument name in your selector to protime instead of projectile. The selector must match the signature defined in you object's class definition.
Your selector was not defined properly and probably the Timer is checking if the object implements the given selector.
I did not have time to test it so thanks to #RamyAlZuhouri for confirming.
Right now I have a NSArray with x amount of coordinates inside; I have a sprite that needs to go to each of the points to go along the selected path. I tried using a for loop, but that does it in such quick succession that it appears to just teleport to the final destination. I've tried a with selectors but I can't get those to work either. Anyone know how to do this?
you have to use an NSTimer
#implementation whatever
{
int count;
NSTimer *myTimer;
CCSprite *mySprite;
NSArray *locationArray;
}
and then start the timer fromsomewhere...
count=0
//1.0 is one second, so change it to however long you want to wait between position changes
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(movealong) userInfo:nil repeats:YES];
And then it calls this until it goes through all the object
-(void)movealong{
//assuming array is full of CGPoints...
//mySprite.position = [locationArray objectAtIndex:count];
//try this then
CGPoint newPoint = [locationArray objectAtIndex:count];
mySprite.position = ccp(newPoint.x,newPoint.y);
//I think maybe THIS is what you need to do to make it work...
//if you want it to not jump directly there
//you can also use CCMoveTo
/*// under actionWithDuration: put the same amount of time or less of what you had
// under scheduledTimerWithTimeInterval in your NSTimer
CCFiniteTimeAction *newPos = [CCMoveTo actionWithDuration:1.0 position:[locationArray objectAtIndex:count]];
[mySprite runAction:newPos];
*/
count++;
if(count >= locationArray.count){
[myTimer invalidate];
myTimer = nil;
}
}
Create a method that will place your sprite to position that is at some index (for example, _i) in the positions array. And at the end of this method call it again with delay, using CCDelayTime and CCCallFunc actions in sequence. And do not forget to increment index. Smth like
// somewhere in code to start move
_i = 0;
[self setNewPosition];
// method that will set the next position
- (void) setNewPosition
{
sprite.position = // get position at index _i from your array here
_i++;
BOOL needSetNextPosition = // check if _i is inside bounds of your positions array
if( needSetNextPosition )
{
id delay = [CCDelayTime actionWithDuration: delayBetweenUpdate];
id callback = [CCCallFunc actionWithTarget: self selector: #selector(setNewPosition)];
id sequence = [CCSequence actionOne: delay two: callback];
[self runAction = sequence];
}
}
This is just example, but I hope, you can adapt it for your needs.
I have a ccLayer where I'm trying to make it rain.
On init i schedule the following:
[self schedule:#selector(throwRain) interval:0.1f];
And here is the rest of the code:
-(void) throwRain {
CCSprite *gota;
for (int i = 1; i <= 6; i++){
gota = [CCSprite spriteWithFile:#"4_gota.png"];
gota.position = ccp(arc4random() % 768, 1060);
gota.scale = (arc4random () % 25 + 50.0f) / 100.0f;
gota.rotation = 35 ;
[self addChild:gota z:arc4random() % 5 + 7];
[gota runAction:[CCSequence actions:[CCEaseRateAction actionWithAction:[CCMoveTo actionWithDuration:3.0f + (arc4random() % 200) / 100.0f position:ccp(gota.position.x, 0)] rate:3] , [CCCallFunc actionWithTarget:self selector:#selector(spriteDone:)], nil]];
}
}
-(void) spriteDone:(id)sender {
[self removeChild:sender cleanup:YES];
}
However, the drops gets to the bottom and just stays there, and never gets removed. Any idea?
Thanks.
Try changing from CCCallFunc to CCCallFuncN. The 'N' stands for Node, and will pass the Node that is performing the action to the selector.
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