When I pause the game and wait some time, then resume the game, an alien ship is immediately added. How do I make it so that the 'timer' doesn't continue once the game is paused?
My Code:
#implementation GamePlayScene
-(void) didMoveToView:(SKView *)view {
...
self.addAlienTimeInterval = [Util randomWithMin:10 max:25];
self.timeSinceLastAlien = 0;
...
}
-(void) update:(NSTimeInterval)currentTime{
if (!_isGamePaused){
if (self.lastUpdateTimeInterval){
self.timeSinceLastAlien += currentTime - self.lastUpdateTimeInterval;
}
if (self.timeSinceLastAlien > self.addAlienTimeInterval && !self.gameOver){
[self addAlienShip];
self.timeSinceLastAlien = 0;
}
self.lastUpdateTimeInterval = currentTime;
...
}
}
#end
Your update: process isn't assigning lastUpdateTimeInterval while the "paused" condition is true, so when you resume, timeSinceLastAlien is augmented based on the lastUpdateTimeInterval that was seen way back when you first hit the pause button.
You need to keep updating the lastUpdateTimeInterval property while _isGamePaused is true in order for the interval currentTime - self.lastUpdateTimeInterval to reflect un-paused game time.
Related
I am building a Sprite Kit game where the player shoots a particle whenever the screen is pressed. How can I limit the touch inputs (let's say 2 recognized touch inputs per second) so that the player can't just quickly tap the screen to get unlimited shots?
Another solution:
Create a BOOL (I prefer to work with properties myself, so):
#property (nonatomic, assign) BOOL touchEnabled;
Set it to YES in the scene's init. Then it is fairly simple from thereon:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.touchEnabled){
self.touchEnabled = NO;
[self shootParticle];
[self performSelector:#selector(enableTouch) withObject:nil afterDelay:2.0];
}
...
- (void)shootParticle{
// whatever...
}
- (void)enableTouch{
self.touchEnabled = YES;
}
One of many possibilities:
#interface YourSceneName (){
int _amountBullets; //increase every time you shot and just shoot when _fireStop = NO
BOOL _fireStop; // init as NO at start
BOOL _needStartTime; // init as YES at start
CFTimeInterval _startTime;
}
#end
-(void)update:(CFTimeInterval)currentTime {
//set starttime
if(_needStartTime){
_startTime = currentTime;
_needStartTime = NO;
}
//timeinterval if 2 seconds, renew everything
if(currentTime - _startTime > 2){
_startTime = currentTime;
_amountBullets = 0
_fireStop = NO;
}
//set firestop to yes, method should be executed
if(_amountBullets = 2){
_fireStop = YES;
}
}
I am new in SpriteKit but this should work. I bet there are better possibilities. Also i havenĀ“t test the code. It shell show you the logic of how you could do it, hope I could help.
Here is a great Tutorial for working with time in SpriteKit.
I'm trying to create a simple countdown timer app for myself. So far I've figured out how to create the countdown timers with a stop/reset action on them for a single button I've got attached to the timer.
However, I would like to add multiple timers to the same page and I'm not really sure how to do about making extra calls for the timers. Each of the timers would have it's own number to count down from (7 minutes for one, 3 minutes for the other, etc). These are set intervals that the user is not able to change. Google hasn't really worked out for me on how to do this so I'm hoping someone can at least guide me in the right direction. Below is my code snippets:
ViewController.h
#interface ViewController : UIViewController {
IBOutlet UILabel *firstCountdownLabel;
NSTimer *firstCountdownTimer;
bool timerActive;
int secondsCount;
}
- (IBAction)start:(id)sender;
- (void)timerRun;
#end
ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (void) timerRun {
secondsCount = secondsCount - 1;
int minutes = secondsCount / 60;
int seconds = secondsCount - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
firstCountdownLabel.text = timerOutput;
if (secondsCount == 0) {
[firstCountdownTimer invalidate];
firstCountdownTimer = nil;
}
}
//- (void) setTimer {
- (IBAction)start:(id)sender {
secondsCount = 420;
if (timerActive == NO) {
timerActive = YES;
self->firstCountdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
else {
timerActive=NO;
[self->firstCountdownTimer invalidate];
self->firstCountdownTimer = nil;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// [self setTimer];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Google doesn't help in showing you how to implement original application ideas.
If you want multiple timers, define multiple timer instance variables:
#interface ViewController : UIViewController
{
IBOutlet UILabel *timer1Label;
IBOutlet UILabel *timer2Label;
IBOutlet UILabel *timer3Label;
NSTimer *timer1;
NSTimer *timer2;
NSTimer *timer3;
int timer1Count;
int timer2Count;
int timer3Count;
bool timer1Active;
bool timer2Active;
bool timer3Active;
}
Then create a separate IBAction for each button that starts each of the timers:
- (IBAction)startTimer1:(id)sender
{
timer1Count = 420;
if (timer1Active == NO)
{
timer1Active = YES;
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timer1Run:)
userInfo:nil
repeats:YES];
}
else
{
timer1Active=NO;
[timer1 invalidate];
timer1 = nil;
}
}
- (void) timer1Run: (NSTimer*) timer
{
timer1Count -= 1;
int minutes = timer1Count / 60;
int seconds = timer1Count - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
timer1Label = timerOutput;
if (timer1Count == 0) {
[timer2 invalidate];
timer2 = nil;
}
}
Duplicate the above code for each timer, using "timer2" and "timer3" in place of "timer1". Change the time counts for each one to the desired values. (I changed the names from "firstTimer" to "timer1" because it's easier to edit the code to support multiple timers that way.
I did not write 3 versions of each method for you because you need to figure this out rather than copy & pasting in code that you don't understand.
It would be possible, and require less code, to use the same IBAction method for all your start timer buttons, and have the code check the tag on the button to decide which timer to start.
The code might look like this:
- (IBAction)startTimer1:(id)sender
{
int tag = [sender tag];
switch (tag)
{
case 1: //timer 1
//Put code to start timer 1 here
break;
case 2: //timer 2
//put code to start timer 2 here
break;
}
}
But that might be a bit over your head at the moment.
By the way, forget you ever saw the "self->variable" syntax. it is slower and more error-prone than just referring to the instance variable directly. using object->variable syntax also allows you to access the instance variables of other objects, which is bad practice. You should always use properties to access the instance variables of objects other than yourself.
Also, the timer method should take a single parameter, a timer. I corrected the timer method in the above code.
Create a class as YourTimer with few properties like
NSString *timerLabel;
NSTimer *timer;
NSInteger timerCounter;
Now create an array of YourTimer objects. Then you can access it easily.
This will be modular, maintainable and reusable code, as may be later on you need one more identifier to be with all timers, hence wrap them in one class and use it.
I am making a stopwatch, but I only have the start button working. When the start button is pressed it enters a loop:
- (void)stopwatch
{
NSInteger hourInt = [hourLabel.text intValue];
NSInteger minuteInt = [minuteLabel.text intValue];
NSInteger secondInt = [secondLabel.text intValue];
if (secondInt == 59) {
secondInt = 0;
if (minuteInt == 59) {
minuteInt = 0;
if (hourInt == 23) {
hourInt = 0;
} else {
hourInt += 1;
}
} else {
minuteInt += 1;
}
} else {
secondInt += 1;
}
NSString *hourString = [NSString stringWithFormat:#"%02d", hourInt];
NSString *minuteString = [NSString stringWithFormat:#"%02d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%02d", secondInt];
hourLabel.text = hourString;
minuteLabel.text = minuteString;
secondLabel.text = secondString;
CGRect hourFrame = self->hourBar.frame;
CGRect minuteFrame = self->minuteBar.frame;
CGRect secondFrame = self->secondBar.frame;
if ((NSInteger)hourFrame.size.height != hourInt) { // check if we need to modify
hourFrame.origin.y -= ((hourInt * 10.0) - hourFrame.size.height);
hourFrame.size.height = (hourInt * 10.0);
self->hourBar.frame = hourFrame;
}
if ((NSInteger)minuteFrame.size.height != minuteInt) { // check if we need to modify
minuteFrame.origin.y -= ((minuteInt * 4.0) - minuteFrame.size.height);
minuteFrame.size.height = (minuteInt * 4.0);
self->minuteBar.frame = minuteFrame;
}
if ((NSInteger)secondFrame.size.height != secondInt) { // check if we need to modify
secondFrame.origin.y -= ((secondInt * 4.0) - secondFrame.size.height);
secondFrame.size.height = (secondInt * 4.0);
self->secondBar.frame = secondFrame;
}
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:NO];
}
When the stop button is pressed, I want this loop to be paused so that when the user presses start, it resumes the stopwatch.
When the reset button is pressed, I want the loop to stop and reset back to 0.
If you could make your answer as simple as possible, that would be really great because I'm only a beginner!
You should take the timer out of your 'loop' method and make it repeating. It should be the thing that is driving the stopwatch, not that fact that you have started your 'loop'. Then, you can stop the timer when you need to and restart it later. You can also google to find out the correct way to pause the timer (you need to change the timer to start at a specified fire date to be 100% accurate, but just knowing how many seconds are left may be enough for your case).
Based on your previous question it sounds like you have moved your timer outside of your stopwatch method. Remember, timers and loops are two very different things.
You can stop an NSTimer by calling invalidate on it. Please read Apple's documentation for more details.
One way that might not be completely thread safe but I just tested it and it works is to have a variable that is set to true when you click start and false when you click stop.
For example, in my .h, I added #property (nonatomic) BOOL go;, then as the first line of the method you provided, add an if check that looks like this:
- (void)stopwatch {
if (!self.go) return;
//..do the rest of your stopwatch code
//the timer call
}
then my start and stop button calls look like:
- (void)start {
if (!self.go) { // Do not call the stopwatch method again if it is already going.
self.go = YES;
[self stopwatch];
}
}
- (void)stop {
self.go = NO;
}
Loop is the wrong construct for this type of operation. For better control, use NSTimer.
A relevant example can be found here.
EDIT:
My above answer was based on prior version of your question.
So yes, the NSTimer should be the controlling thing for your loop, not part of the loop.
General implementation:
Start should only mark timer starting. Also note the current time from system clock. (this is optional though)
Timer function should change value of the time variable, and compare it against the stopwatch set value. If time elapsed == set value, stop the timer using invalidate.
Stop should interrupt the timer and stop it, and also the elapsed time value should be set to 0. Time Variable should be reset too, if you do not want to reuse this same time value again.
I need to organize rendering loop in my app by a specific way (there is a reasons).
Let's say I have
Sprite *sprite = [[Sprite alloc] initWithContentsOfFile:#"sprite.png"]; // some sprite
while (true) {
[Graphics update];
sprite.x = (sprite.x + 1) % 1024 // moving sprite by one pixel each frame
[Input update];
[self update];
}
There Graphics.update should render one frame and delay execution (not rendering) until next frame
#interface Graphics () {
static BOOL _frozen;
static int _count;
static int _frameCount;
static float _frameRate;
static double _lastFrameTimestamp;
}
#end
#implementation Graphics
+ (void)initialize {
_frozen = NO
_count = 0
_frameCount = 0
_frameRate = 30.0
_lastFrameTimestamp = CACurrentMediaTime()
}
+ (void)freeze {
_frozen = YES;
}
+ (void)update {
if _frozen
return
end
Video.nextFrame // some OpenGL ES magic to render next frame
_count++
now = CACurrentMediaTime()
waitTill = _lastFrameTimestamp + _count * (1.0 / _frameRate)
if now <= waitTill
sleep(waitTill - now)
else
_count = 0
_lastFrameTimestamp = CACurrentMediaTime()
end
_frameCount++
}
#end
Somehow it works and sprite is moving. But when I go to home applicationWillResignActive isn't called and when I go back to app there is black screen and after some time app crashes.
Here is the thing I try to port: https://bitbucket.org/lukas/openrgss/src/7d9228cc281207fe00a99f63b507198ea2596ead/src/graphics.c (Graphics_update function)
You can try using Core Animation DisplayLink instead of a while loop. That's how it's usually done in the graphics frameworks. The currentRunLoop calls your update method every 1/60 seconds.
You should remove the sleep call in your update if using NSRunLoop.
CADisplayLink *displayLink;
// Set your update method
displayLink = [CADisplayLink displayLinkWithTarget:[Graphics class]
selector:#selector(update)];
// Set fps to device refresh rate (60)
[displayLink setFrameInterval:1.0f];
// Add your display link to current run loop
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// Stop updating
[displayLink invalidate];
The last line stops the execution, so do not call that until you're done with your loop.
If you are using Sparrow, this is how you should be handling it:
SPSprite *sprite = [[SPSprite alloc] initWithContentsOfFile:#"sprite.png"]; // some sprite
[self addEventListener:#selector(enterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
-(void)enterFrame:(SPEnterFrameEvent*)e {
[Graphics update];
sprite.x += 1; // moving sprite by one pixel each frame
[Input update];
[self update];
}
Sparrow manages the game loop for you using an SP_ENTER_FRAME_EVENT. It will be called every time it's time to render again. Roughly 30 times per second (though it can be configured).
I just want to run a certain method for 20 seconds when user presses start button and stop it by it's own rather than using a button to do it. But it should also trigger when ever the user press start button after 20 seconds in next turn.
How can i use NStimer to implement this?
Sorry for not posting any codes
Thanks in advance!
// Assume ARC - otherwise do the proper retains/releases
{
NSTimer *timer; // keep a reference around so you can cancel the timer if you need to
NSDate *timerDate;
}
- (IBAction)buttonPressed:(id)sender
{
// disable button til the time has passed
[sender setEnabled:NO];
// make sure its diabled til we're done here
timerDate = [NSDate date];
NSTimeInterval interval = 1; // 1 gets you a callback every second. If you just wnat to wait 20 seconds change it
// schedule it
timer = [NSTimer ]scheduledTimerWithTimeInterval:time target:self selector:#selector(myTimeRoutine:) userInfo:nil repeats:YES];
}
- (void)myTimeRoutine:(NSTimer *)timer
{
// do something ...
NSTimeInterval interval = -[timerDate timeIntervalSinceNow]; // return value will be negative, so change sign
if(interval >= 20) {
// I'm done now
myButton.enabled = YES; // so it can be tapped again
[timer cancel]; // dont want to get any more messages
timer = nil; // ARC way to release it
timerDate = nil;
}
}