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).
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 am working on an iOS 7+ app and would like to animated change the content of an UILabel. I do not want do do any graphical animation like fade out old content / fade in new content. Thus all the standard animation features iOS offers like Layer animations or animation blocks cannot be uses (at least I think so).
Assume the UILabel shows some meter values like "200 V" and this text should be changed to "400 V". The text should not just jump from "200 V" to "400 V" but should be counted up using some easing function: "200 V", "220 V", "240 V"... "390 V", "395 V" ... "400 V"
In Android could easily be solved using a ValueAnimator:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setInterpolation(new EaseOutInterpolator());
animation.setDuration(2500);
animation.setStartDelay(500);
animation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpate(ValueAnimator animator) {
float currentValue = animator.getAnimatedValue.floatValue();
label1.setText(String.format("%.2", fromValue1 + ((toValue1 - fromValue1) * currentValue)));
label2.setText(String.format("%.2", fromValue2 + ((toValue2 - fromValue2) * currentValue)));
...
}
});
animation.start();
Is there such thing in iOS as well? I found different solution for this but they are all pretty old (2010/11) and all end up implementing this behavior manually using NSTimer and own easing functions.
It is out of question that one can implement this on his own, but this would be quite cumbersome and not very elegant. So: Is there something build in iOS to solve this or are there at least convenient third party implementation available?
Thank you very much!
Since I found no tailored solution for this I created my own: A simple Animator Class which handles the Easing:
// MyValueAnimation.h
typedef void (^MyAnimationBlock)(double animationValue);
#interface MyValueAnimation : NSObject
- (void)startAnimation:(MyAnimationBlock)animationBlock runtime:(NSUInteger)runtime delay:(NSUInteger)delay;
#end
// MyValueAnimation.m
#import "MyValueAnimation.h"
// Number of seconds between each animation step
#define kStepSize 0.05
#interface MyValueAnimation () {
NSTimer *timer;
NSUInteger totalRunTime; // Total duration of the animation (delay not included)
NSUInteger currentRuntime; // Time the animation is already running
MyAnimationBlock animationBlock;
}
#end
#implementation MyValueAnimation
- (void)startAnimation:(MyAnimationBlock)block runtime:(NSUInteger)runtime delay:(NSUInteger)delay {
if (timer != nil)
[timer invalidate];
timer = nil;
totalRunTime = runtime;
animationBlock = block;
currentRuntime = 0;
if (block != nil) {
if (delay > 0) {
// Wait to delay the start. Convert delay from millis to seconds
double delaySeconds = (double)delay / 1000.0;
timer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds target:self selector:#selector(delayTick:) userInfo:nil repeats:false];
} else {
// Run the animation
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
}
}
- (void)delayTick:(NSTimer *)delayTimer {
// End of delay -> run animation
[delayTimer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
- (void)animationTick:(NSTimer *)animationTimer {
NSUInteger step = 1000 * kStepSize; // step size/length in milli seconds
currentRuntime += step;
double progress = MIN((double)currentRuntime / (double)totalRunTime, 1.0);
// Progress is a value between 0 and 1. The easing function maps this
// to the animationValue which is than used inside the animationBlock
// to calculate the current value of the animiation
double animationValue = [self customEaseOut:progress];
if (animationBlock != nil)
animationBlock(animationValue);
if (progress >= 1.0) {
// Animation complete
[timer invalidate];
timer = nil;
}
}
- (double)customEaseOut:(double)t {
// Use any easing function you like to animate your values...
// http://rechneronline.de/function-graphs/
// http://sol.gfxile.net/interpolation/
return (1 - pow(1-t, 2));
}
#end
// =============================================================
// Some code using the animation
- (void)animateValueFrom:(double)fromValue to:(double)toValue {
if (valueAnimation == nil)
valueAnimation = [[MyValueAnimation alloc] init];
MyAnimationBlock animationBlock = ^(double animationValue) {
double currentValue = fromValue + ((toValue - fromValue) * animationValue);
someLabel.text = [NSString stringWithFormat:"%dV", currentValue];
};
[valueAnimation startAnimation:animationBlock runtime:1500 delay:500];
}
Maybe not the prettiest solution but it works :-)
I know a solution but its for fast iteration because final iterations can jump through value (looks not beautifully if there is a slow search), but the decision simple and short, can not the most beautiful from a architecture(but it can be corrected if it is necessary) realisation.
- (void)someAction
{
[self animateValue:0 toValue:1111 withStep:7 andIntervalSpeed:5];
}
-(void)animateValue:(int)value toValue:(int)toValue withStep:(int)step andIntervalSpeed:(int64_t)intervalSpeed
{
self.currentValue = value; // #property (nonatomic) int currentValue;
NSUInteger numberofIteration = (toValue - value)/step;
int64_t interval = 0.0;
for (NSUInteger i = 0; i < numberofIteration; i++) {
dispatch_time_t start = DISPATCH_TIME_NOW;
interval += intervalSpeed;
dispatch_after(dispatch_time(start, interval * USEC_PER_SEC), dispatch_get_main_queue(), ^{
if (((toValue - value)%step != 0) && (i == (numberofIteration-1)))
{
self.currentValue = toValue;
}
else
{
self.currentValue+= step;
}
NSLog(#"%d",self.currentValue);
self.someLabel.text = [NSString stringWithFormat:#"%d",self.currentValue];
});
}
}
I and many others have a issue mathematically causing a function to be called more and more often. My goal is to call the code inside the if statement more and more often. The function is called every .01 seconds. I would like the first time it runs is at 1 second, then faster and faster until it holds off at about .3. I need to know what to put in the SOMETHING.
The Function is called every .01 seconds by a NSTimer.
The code is:
-(IBAction)redmaker:(id)sender{
refreshrate = refreshrate+1;
if(SOMETHING){
int rand =arc4random() %65;
UIButton *button = (UIButton *)[self.view viewWithTag:rand];
button.backgroundColor = [UIColor colorWithRed:255 green:0 blue:0 alpha:1];
button.enabled = YES;
deathtimes[rand] = 10;
rate = rate+1;
refreshrate = 0;
}
You should use an NSTimer to call your method. Define an NSTimer in your header file.
Class.h
NSTimer *timer;
double interval;
Class.m
//put the following two lines in viewDidLoad, or some other method
interval = 1.0
timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(redmarker:) userInfo:nil repeats:NO];
-----
//put this at the bottom of your if statement
if (interval > 0.3)
{
[timer invalidate];
//change this value to something greater to call the method faster
interval -= 0.05;
timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(redmarker:) userInfo:nil repeats:NO];
}
You may experience an issue that causes your game to slow down. If that occurs, then it is possible that the main thread is unable to handle the timer, the buttons, and other actions all at the same time. You will want to look into using Grand Central Dispatch.
Repeating timers always use the same time interval. You can't change it.
If you want a timer on a decreasing interval, create a non-repating timer that triggers a new timer with the same selector each time it fires. Use an instance variable to hold the interval, and subtract some amount from the interval value each time it fires.
As for your "if (SOMETHING)", nobody else can tell you the conditions in your code that would decide what to do.
Can't you use Grand Central Dispatch with a recursive method like this:
#import "ViewController.h"
#interface ViewController ()
{
CGFloat fireTime;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
fireTime = 1.0;
// initial call to method
[self foo];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)foo
{
NSLog(#"Hello at, timer fired off after %lf", fireTime);
if (fireTime > 0.3)
{
// decrement timer
fireTime -= 0.1;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(fireTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self foo];
});
}
#end
I'm currently taking my first shaky steps in ios development. I'm trying to animate a free falling ball. I have a button connected to an IBAction, and and UIImageView containing an image of my ball.
Inside my action, I have a while loop that's timed using NSTimeInterval, and based on the time it takes it calculates a new position until the ball reaches 'the ground'(Yes, I realize this is probably the least optimal way to do it. But I'm having a hard time grasping the syntax (and therein my problem probably lays), the optimisation will have to come later). From what I can understand, NSTimeInterval returns the elapsed time in seconds, so even though it will increment incredibly small steps, it should work. But I may have a serious case of brain fart.
So far so good. But when I tap the button, the ball moves straight from it's starting point to it's finishing point without an animation.
-(IBAction)doshit:(id)sender{
int G = 10;
CGPoint center = [myImage center];
NSDate *startTime = [NSDate date];
NSTimeInterval T;
T = fabs([startTime timeIntervalSinceNow]);
while (center.y<480)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:T];
center.y=center.y+((G*T*T)/2);
[myImage setCenter:center];
T = fabs([startTime timeIntervalSinceNow]);
[UIView commitAnimations];
}
}
I welcome all suggestions! =)
One way to do it is to use a CADisplayLink to provide the timing -- it is tied to the display refresh rate of 1/60 of a second. If you 're using auto layout (which is on by default now), it is better to animate a constraint, rather then set a frame. So, this example uses a button at the top of the screen, whose constraint to the top of the view is connected to the IBOutlet topCon.
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (weak,nonatomic) IBOutlet NSLayoutConstraint *topCon;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(startDisplayLink) withObject:nil afterDelay:2];
}
- (void)startDisplayLink {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
static BOOL first = YES;
static double startTime = 0;
if (first) {
startTime = displayLink.timestamp;
}
first = NO;
double T = (double)displayLink.timestamp - startTime;
self.topCon.constant += ((10 * T * T)/2);
if (self.topCon.constant > 420) {
[self stopDisplayLink];
}
}
As Carl notes, you cannot perform animations by repeatedly changing things in the middle of a method call. See What is the most robust way to force a UIView to redraw? for more discussion on that.
As you may suspect, not only is this non-optimal, it actively fights iOS. iOS has many easy-to-use techniques for performing smooth animations without resorting to timers of any kind. Here is one simple approach:
- (IBAction)dropAnimate:(id)sender {
[UIView animateWithDuration:3 animations:^{
self.circleView.center = CGPointMake(100, 300);
}];
}
In the animations block, you change the thing you want to change to the final value (myImage.center in your case). UIKit will sample the current value and the final value, and figure out a path to get you there in the time you requested. For a full, runnable example with a few other features (like chained animations), see the example code from iOS:PTL Chapter 9.
The above code will use the default timing function. Once you understand that, you can move on to customizing the timing function. See Animation Pacing in the Animation Types and Timing Programming Guide. Also How to create custom easing function with Core Animation? and Parametric acceleration curves in Core Animation. But I would get your head around simple, default animations first.
I want to implement startup loader in my app. It should be like this: after startup splash screen, user will watch simple animataion and in meanwhile app preload sound effects, background music, sprite images, spritesheets and so on. Current implementation:
- (id)init {
if((self = [super init])) {
// Some other setup ...
CGRect rect;
rect = waveSprite.textureRect;
waveInitialTexRectOrigin = rect.origin;
rect.size.width = 91;
waveSprite.textureRect = rect;
assetFilenames = [[NSArray alloc] initWithObjects:
// images
#"background.png",
// spritesheets
#"sprites.plist",
// fonts
#"main.png",
// sound effects
#"button.wav",
nil];
assetCounter = 0;
[self loadAsset];
}
return self;
}
- (void)update:(ccTime)dt {
CGRect rect;
rect = waveSprite.textureRect;
rect.origin.x += dt*kLoaderWaveSpeed;
while (rect.origin.x > waveInitialTexRectOrigin.x + kLoaderWavePeriod) {
rect.origin.x -= kLoaderWavePeriod;
}
waveSprite.textureRect = rect;
}
#pragma mark Private
- (void)loadAsset {
// CCLOG(#"loadAsset");
NSString *filename = [assetFilenames objectAtIndex:assetCounter];
CCLOG(#"loading %#", filename);
NSString *ext = [filename pathExtension];
if ([ext doesMatchRegStringExp:#"[png|jpg]"]) {
[[CCTextureCache sharedTextureCache] addImage:filename];
} else if ([ext isEqualToString:#"plist"]) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:filename];
} else if ([ext doesMatchRegStringExp:#"[caf|wav|mp3]"]) {
[[SimpleAudioEngine sharedEngine] preloadEffect:filename];
}
assetCounter++;
if (assetCounter < [assetFilenames count]) {
[self performSelector:#selector(loadAsset) withObject:self afterDelay:0.1f];
} else {
[self performSelector:#selector(loadingComplete) withObject:self afterDelay:0.2];
}
But the animation is SO abrupt.
UPD I've already tried
[self performSelectorInBackground: withObject:]
but it didn't seem to work (hung on loading first asset). Maybe I should try better in this direction.
UPD2 Smooth = not abrupt, without delays and flicker. fps doesn't matter, 20 fps quite OK
I'm not experienced with CC2D, but here's what I used in my ViewController to show a smooth animated loading indicator:
//Set up and run your loading scene/indicator here
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load your scene
dispatch_async( dispatch_get_main_queue(), ^{
//This fires when the loading is complete
//Show menu and destroy the loader scene/indicator here
});
});
I guess it's worth a try.
You should not be using performSelectorInBackground anymore, it is not deprecated but it might be soon, with GCD now in the picture.
What I would try is run the animation in the main thread since it is the only one that can do UI updates. And spawn one or more threads using GCD to load all your assets.
I would use concurrent dispatch queues to load everything in parallel and simply dismiss the animation when this is done.
You can get complete information about concurrent dispatch queues here:
Concurrency Programming Guide
Hope that helps.
That is because you are doing everything on the same thread. Use a separate thread for the loader.
Hope this helps.
Cheers!
EDIT : Take a look at this.