Delays between a sprites position change - objective-c

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.

Related

How shall I make the timer count the AVAudioPlayer.duration correctly?

- (void)timerTickPlayer:(NSTimer*)timer
{
timeSec = 0;
timeMin = 0;
timeSec++;
int displayTime = [[NSNumber numberWithFloat:player.duration] intValue];
if (timeSec == displayTime)
{
timeSec = 0;
[timer invalidate];
}
NSString* playerTime = [NSString stringWithFormat:#"%2d",timeSec];
labelTime.text = playerTime;
}
Above is the method which I try to make the timer start counting form 00,
until it hits the duration.
But turns out the label will just show the duration for a moment, then change back to 00 again.
Where is the problem of my code/logic?
Here's the latest situation!
haha there's still a bug, seems to be getting solved.
With an AVPlayer, no need for a timer:
// timescale of 600 to report changes every 1/600th of a second (value taken from the doc of CMTimeScale)
CMTime interval = CMTimeMakeWithSeconds(1, 600)
__weak typeof(self) weakSelf = self;
self.timeObserver = [player addPeriodicTimeObserverForInterval:interval
queue:NULL
usingBlock:
^(CMTime time)
{
int timeSec = (int)CMTimeGetSeconds(time);
NSString *playerTime = [NSString stringWithFormat:#"%2d", timeSec];
weakSelf.labelTime.text = playerTime;
}];
Answering extra questions from ChiHsi Chung:
a weak self is required according to documentation of addPeriodicTimeObserverForInterval:queue:usingBlock::
Important
You should use a weak reference to self in the callback block to prevent creating a retain cycle.
you need to create your own timeObserver strong property of type id to store the return value of addPeriodicTimeObserverForInterval:queue:usingBlock:. No, there is no type for it, because according to definition, timeObserver should be:
An opaque object that you pass as the argument to removeTimeObserver: to cancel observation.

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.

Changing text field repeatedly in a loop only displays one final value [duplicate]

This question already has answers here:
NSTextField waits until the end of a loop to update
(4 answers)
Closed 9 years ago.
I wish to create a loop that increments an integer every ten seconds, and does this one hundred times. But when I use this code:
- (IBAction)loopTest:(id)sender {
}
- (IBAction)beginLoop:(id)sender {
for (i=0;i<100 ;i++ ) {
testingLoops++;
NSString *feed = [NSString stringWithFormat: #"%d", testingLoops];
self.feedLabel.stringValue = feed;
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(loopTest:) userInfo:nil repeats:NO];
}
}
the application just displays the integer as 100 straight away. I have it so that it runs the beginLoop when I press a button. What's going on?
Your statement:
[NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(loopTest:)
userInfo:nil
repeats:NO];
does not delay your loop - rather for every iteration of the loop it schedules a timer to call loopTest:, a method you've defined to do nothing.
To use a timer to delay a loop you need to schedule a method which performs the remainder of the loop. In other words a non-loop method which performs the equivalent of one iteration of your loop and then schedules a time to perform the remainder.
Following your approach, but switching to use an implicit timer provided by performSelector:withObject:afterDelay as it is more convenient here, this gives us:
- (IBAction)beginLoop:(id)sender
{
// start "loop"
// note we only pass the current index and not the limit or delay
// as there is no performSelector version which directly supports
// passing three values to the selector
[self doLoopIndex:#0];
}
- (void) doLoopIndex:(NSNumber *)objIndex
{
// extract int from NSNumber - we use the later as the argument type so we can use performSelector below
int index = objIndex.intValue;
// do the work of one iteration
NSString *feed = [NSString stringWithFormat: #"%d", index];
self.feedLabel.stringValue = feed;
// increment "loop" counter and schedule next iteration if needed
// note the use of #(index) to create an NSNumber as an object is required
index++;
if (index < 100)
[self performSelector:#selector(doLoopIndex:) withObject:#(index) afterDelay:1];
}
This is not the only way to achieve your goal. Using blocks and "Grand Central Dispatch" (GCD) is another and in this case has the advantage that passing three values: current index, limit and delay; is easier.
For example, a general loop with delay might be written as:
- (void) doLoop:(int)index // starting index
limit:(int)limit // limit
delay:(NSTimeInterval)delayInSeconds // delay each iteration
body:(void (^)(int))body // block for loop body
{
// invoke the body block
body(index);
// increment index and schedule next "iteration" if needed
index++;
if (index < 100)
{
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[self doLoop:index limit:limit delay:delayInSeconds body:body];
});
}
}
See the documentation for the details of the dispatch_X methods and types.
This might look more complicated, but that is because it is more general. Using the above your particular loop becomes just:
- (IBAction)beginLoop:(id)sender
{
[self doLoop:0 limit:100 delay:1 body:^(int index)
{
NSString *feed = [NSString stringWithFormat: #"%d", index];
self.feedLabel.stringValue = feed;
}];
}
Which of the above approaches, or using an explicit timer as your original code did, is appropriate for your use case is your choice - there is no single "right" answer.
HTH.
Try this
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, i * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
yourtextfield.text=[NSString stringWithFormat:#"%d", i];
});

How to Check Playback Time of Audioplayer increases by 2 seconds

I m trying to write a loop to check that whenever audio player.currenttime increases by 2 seconds then it should execute update view method
- (void)myTimerMethod{
NSLog(#"myTimerMethod is Called");
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(checkPlaybackTime:)
userInfo:nil
repeats:YES];
}
- (void)checkPlaybackTime:(NSTimer *)theTimer
{
float seconds = audioplayer.currenttime;
NSLog(#"Cur: %f",audioPlayer.currentTime );
if (seconds = seconds + 2){
[self update view];
}
- (void)UpdateView{
if (index < [textArray count])
{
self.textView.text = [self.textArray objectAtIndex:index];
self.imageView.image = [self.imagesArray objectAtIndex:index];
index++;
}else{
index = 0;
}
}
what is the correct way to write if audio player.currenttimer increases by 2 seconds then do this.
NSLog for current time always shows 0.00. Why is that. It should increase as the audioplayer is playing.
Thanks for help.
What i understood from your given explanation that you want to increment the time-interval something like this
Timer calls after 0.55
Timer calls after 0.60
Timer calls after 0.65
Timer calls after 0.70
& so on.
If that is what you are looking to do. Then i think you can do this way that by changing repeats:YES to repeats:NO so that the timer doesn't repeat, and then in onTimer, just start a new timer with a longer interval.
You need a variable to hold your interval so that you can make it a bit longer each time through onTimer.
Also, you probably don't need to retain the timer anymore, as it will only fire once, and when it does, you'll get a new timer.
float gap = 0.50;
[NSTimer scheduledTimerWithTimeInterval:gap target:self selector:#selector(onTimer) userInfo:nil repeats:NO];
-(void) onTimer {
gap = gap + .05;
[NSTimer scheduledTimerWithTimeInterval:gap target:self selector:#selector(onTimer) userInfo:nil repeats:NO];
}
Hope this helps you
First, try using your float "seconds" in your NSLog rather than the current time.
NSLog(#"Cur: %f", seconds);
current time is not a float, it's an NSTimer object so you would have to use %# in your NSLog text so
NSLog(#"Cur: %#",audioPlayer.currentTime );
Should work as well.
Assuming your audioPlayer is set up correctly, if you're looking for when the timer is at 2 seconds, your if statement will be
if(seconds == 2){
[self update view];
}
if you're looking for each time the timer hits an even number, i.e. 2, 4, 6, etc. your if statement will be
if(seconds % 2 == 0){
[self update view];
}
The % in an if statement is the modulo sign: http://www.cprogramming.com/tutorial/modulus.html
Also, your current if statement is assigning rather than checking the seconds variable. To check it, you need == not =. However, your current if statement will never be true since you're checking a variable by itself + 2. To put this another way, if seconds equals 2, your if statement is asking if 2 == (2+2) or if it it is 4, it's asking if 2 == (4+2). This statement cannot validate as true.
Hope this helps!

Using two NSTimer

I'm creating this app that converts text to Morse code and then flash it out using the iPhone's flashlight. I have used string replacement to convert the content of a NSString to Morse code. I have found a script that turns the iPhone's flashlight on and off, with adjustable intervals using NSTimer. But I can't figure out how to add two different intervals, one for the morse "." and one for the morse "-". Can anyone help me?
- (void)viewDidLoad {
[super viewDidLoad];
int spaceTime;
spaceTime = 1;
int flashTimePrik;
flashTimePrik = 5;
strobeIsOn = NO;
strobeActivated = NO;
strobeFlashOn = NO;
flashController = [[FlashController alloc] init];
self.strobeTimer = [
NSTimer
scheduledTimerWithTimeInterval:spaceTime
target:self
selector:#selector(strobeTimerCallback:)
userInfo:nil
repeats:YES
];
self.strobeFlashTimer = [
NSTimer scheduledTimerWithTimeInterval:flashTimePrik
target:self
selector:#selector(strobeFlashTimerCallback:)
userInfo:nil
repeats:YES
];
}
- (void)strobeTimerCallback:(id)sender {
if (strobeActivated) {
strobeIsOn = !strobeIsOn;
strobeFlashOn = YES;
} else {
strobeFlashOn = NO;
}
}
- (void)strobeFlashTimerCallback:(id)sender {
if (strobeFlashOn) {
strobeFlashOn = !strobeFlashOn;
[self startStopStrobe:strobeIsOn];
} else {
[self startStopStrobe:NO];
}
}
Just use one timer, set the time interval based on the dot, dash or space interval. For "A" which is dot space dash
Turn on the light and set that timer to the dot interval.
When the timer fires, turn the light off and set the timer to the space interval.
When the timer fires, turn the light on and set the timer to the dash interval.
When the timer fires, turn the light off.
Use a bajillion timers. All single fire mode. Want an short call call to flash short. Then in the timer call back, create another timer for the next dash or dot. When there are no more signals to transmit in the array, you are done. Code is approximate....
- (void)lightTimerOffCallback:(id)sender {
turnLIGHTOFF
[NSTimer scheduledTimer:intervalbeforeStartingNextChar... selector(#startNextDotOrFlash) repeat NO]
}
- (void)startNextDotOrFlash:(id)sender {
if (there is a new dot or dash to do)
intervalToLeaveThisLightOn = 1.0 : 0.1 ? isDot;
turnLIGHTON
[NSTimer scheduledTimer:intervalToLeaveThisLightOn... selector(#lightTimerOffCallback) repeat NO]
}
Don't need the timer in an iVar.