I'm creating a game with sprite-kit and my question is if there is a simple way to execute an action after a certain time since level start. I know i can define a timer a count time having an if statement with my period of time, but i think sprite-kit should have a timer for scene or something like that. But i haven't found any info about this.
Any idea ?
If you're trying to use an SKAction when you start the game you can do the following.
SKAction *timer = [SKAction waitForDuration: 10];
SKAction *someAction = //put some action here;
SKAction *finalAction = [SKAction sequence:#[timer, someAction]];
With that, "someAction" will be executed right after 10 seconds.
Related
I've made a game in Xcode 6, using several NSTimers for different things, like a scorer timer, countdown timer, and to move my objects around. The problem is that sometimes (it seems like) the NSTimers stop for like half a second which makes it look like it lags. Example: When the character is moving, it stops for a tiny second and then continues to move. It happens so fast, but it is noticable, and it annoys me so much. I want it to be completely smooth. Any help would be appreciated!
A couple of thoughts:
If you're having a small delay in the timer processing, the most likely issue is that you have something blocking the main queue. Take a careful look at your code and see if you can find anything that could block the main queue.
You can actually use Instruments to find places in your app where the thread might be blocked. If I recall correctly, WWDC 2112 video Building Concurrent User Interfaces on iOS shows the trick with Instruments to find where your app is blocked. It's a bit dated, but the techniques for finding where the main thread blocks still apply.
It's unlikely, but you might want to consider checking the run loop modes that your timer is running on. For example, the default:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
This can pause during certain animations. You might consider using a broader array of run loop modes, e.g.:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
This results in a timer that is less susceptible to certain delays during particular types of animations. It just depends upon what else your app is doing when you see the delay in the user interface.
When using a timer to update animations, better than a NSTimer is a CADisplayLink. For example, define a few properties:
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (nonatomic) CFTimeInterval startTime;
Then you can write code to start and stop the display link:
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
self.startTime = CACurrentMediaTime();
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
CFTimeInterval elapsed = CACurrentMediaTime() - self.startTime;
// update your UI, not on the basis of "this is called x times per second",
// but rather, on the basis that `elapsed` seconds have passed
}
The key in good animation code is that you don't just assume that your routine will be called at a specified frequency, but rather that you update the UI based upon the number of elapsed seconds. This way, a slow device that drops a few frames and a fast device will yield the same animation, the latter would just be a little smoother than the former.
The merits of display links, though, are discussed briefly in WWDC 2014 video - Building Interruptible and Responsive Interactions. There are other longer discussions of the topic that are eluding me at this point, but this might be a good place to get introduced to the topic (even though the vast majority of that video is on other topics).
You may want to try a high resolution timer, like Timer dispatch sources. It looks a bit scary at first, but actually quite easy to use. Sample code (with comments)
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue , dispatch_block_t block) {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer) {
// Use dispatch_time instead of dispatch_walltime if the interval is small
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void MyCreateTimer()
{
dispatch_source_t aTimer = CreateDispatchTimer(30 * NSEC_PER_SEC, 1 * NSEC_PER_SEC, dispatch_get_main_queue(), ^{
NSLog(#"Timer fired!");
});
// Keep a reference if you want to, say, stop it somewhere in the future
}
EDIT:
In XCode, if you type dispatch in the editor, it will suggest a snippet called dispatch_source timer - GCD: Dispatch Source (Timer), which will generate the template code for the timer.
I want some operation executed every second. I do not want ticks to be accurate between every two seconds. But for a long term, I want them exact for average.
For example, the ticks may happen in time sequence like this:
0.999, 2.005, 2.995, 3.950, 5.012, ... 999.01, 1000.011, ...
Functions may not executed exactly per second, but in a long term, there would be no (or little enough) error in average.
A simple way is using NSTimer. But there is a problem: If CPU ratio is high, the NSTimer fires with a tinny delay. For example, it may fires as a sequence like this:
1.001, 2.002, 3.004, 4.005, 5.006, ... 999.999, 1001.001, ...
As time elapses, the time error may go more and more greater.
Therefore, is there any way to capture any time elapse event in Xcode? Something like "NSTimeNotificationSecondPassed" or ...?
I have develop a method to achieve my goal:
Create an thread Like This:
- (void)_threadHandleTick:(id)arg
{
while(...)
{
#autoreleasepool
{
[self sleepToNextSecond];
... /* Do what ever you like or raise a noification */
}
}
}
Then the - sleepToNextSecond method:
- (void)sleepToNextSecond
{
NSDateFormatter *microSecondFormat = [[NSDateFormatter alloc] init];
[microSecondFormat setDateFormat:#"SSSSSS"];
NSString *microSecondString = [microSecondFormat stringFromDate:[NSDate date]];
useconds_t microSeconds = 1000000 - ((useconds_t)[microSecondString integerValue]);
usleep(microSeconds);
}
Done.
I need some help with xcode...
I need to show a value on label with [label1 setIntValue: someInt] wait some secs and do again [label1 setIntValue: otherInt]. I tried with sleep() but the ui stucks and only the second int is shown. What I need to do? Thank you very much!
What you need to do is to set up a timer. After setting the first value on the label, use this:
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:1 target: self selector:#selector(timerEnded) userInfo: nil repeats:NO];
In this case you're waiting 1 second before triggering timerEnded. So, after this, create the timerEnded method. This is the method that gets called after 1 second.
-(void)timerEnded{
//set value to label
}
I'm very new to Xcode/programming and trying to modify existing code
I'm having a small problem where I have an amount of objects (enemies) on the screen at one particular time and cannot redefine their value. I set my enemies to begin with 3 on the screen.
My objective is to change the amount of enemies based on the current score.
I've attached snippets of the code below.
int numberOfEnemies;
if (self.score>=0) {
numberOfEnemies = 3
}
else if (self.score>=100) {
numberOfEnemies = 4
}
// Setup array
enemyArray = [[NSMutableArray alloc] init];
for(int i = 0; i < numberOfEnemies; i++) {
[enemyArray addObject:[SpriteHelpers setupAnimatedSprite:self.view numFrames:3
withFilePrefix:#"enemyicon" withDuration:((CGFloat)(arc4random()%2)/3 + 0.5)
ofType:#"png" withValue:0]];
}
enemyView = [enemyArray objectAtIndex:0];
What do I need to do to parse the new value of numberOfEnemies into the array when my score updates?
I'm going to move our conversation into an answer since I don't want it to get too long winded, and I can easily edit and expand on this.
So far, we've established that the reason that you're having issues is that you execute the above code in the viewDidLoad function, which will run at least once when the application is first started. The problem with this is as you've found out, that you arent getting a chance to see a new score, and then update the number of enemies.
I know that game update loops for iOS are usually done in the following structure, but I would recommend finding a tutorial online to get what may be a more efficient/correct way to do it.
From your current structure, I would take the code you have above and create a new function out of it:
-(void) updateDifficulty:(NSTimer *)gameTimer
{
//This can be the code you have above for now
}
Afterwards, inside of your viewDidLoad, I would put the following code:
-(void) viewDidLoad:
{
gameTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateDifficulty:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
}
What that does is it declares a timer that will keep track of the game time, and with how it was declared, every 1 second it will call the updateDifficulty method. This is the general structure that you want, but again I would highly suggest you check out a game tutorial from Ray Wenderlich for example.
Hope that helps!
I have a computational process that takes quite a bit of time to perform so a UIActivityIndicatorView seems appropriate. I have a button to initiate the computation.
I've tried putting the command [calcActivity startAnimating]; at the beginning of the computation in an IBAction and [calcActivity stopAnimating]; at the end of the computation but nothing shows.
Next, I created a new IBAction to contain the starting and stopping with a call to the computation IBAction and a dummy for loop just to give the startAnimating a little chance to get started between the two. This doesn't work either.
The skeletal code looks like this:
- (IBAction)computeNow:(id)sender {
[calcActivity startAnimating];
for (int i=0; i<1000; ++i) { }
[self calcStats];
[calcActivity stopAnimating];
}
- (IBAction)calcStats {
// do lots of calculations here
return;
}
Ok, as I commented, you should never performe complex calculations in your main thread. It not only leads to situations like yours, your app might also be rejected from the store.
Now, the reason for the UIActivityIndicatorView not being updated is, that the UI doesn't actually update itself e.g. when you call [calcActivity startAnimating]; Instead, it gets updated after your code ran through. In your case, that means that startAnimating and stopAnimating are getting called at once, so nothing really happens.
So, the 'easy' solution: Start a new thread, using either this techniques or, probably better, GCD.
Thanks for the nudge, Phlibbo. I'm new to this game and appreciate all the help. I didn't comprehend all the info on the links you provided, but it did prod me to search further for examples. I found one that works well. The IBAction 'computeNow' is triggered by the calculation button. The code now looks like this:
- (IBAction)computeNow {
[calcActivity startAnimating];
[self performSelector:#selector(calcStats) withObject:nil afterDelay:0];
return;
}
- (void) calcStats {
// Lots of tedious calculations
[calcActivity stopAnimating];
}