Okay, this is an odd one - and the question has been asked before in various guises and always closed out with the opinion that a) it won't be accurate and b) that it will get less accurate with time.
I understand this - but I'm doing an experiment to see whether that accuracy can be boosted at all by taking into account other evidential sources (for example, if a map has been plotted in advance then the direction of travel from the compass combined with the route could provide another evidence source).
The problem is that my code is clearly rubbish - so I'd welcome your opinion. I suspect that this might be a brown paper bag error!
My ViewController.h looks like this:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIAccelerometerDelegate> {
UIAccelerometer *accelerometer;
long last_speed;
long distance_travelled;
long lastAccel;
long long lastTime;
IBOutlet UITextField* speedView;
IBOutlet UITextField* distanceView;
}
#end
And my ViewController.m looks like this:
#import "ViewController.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
lastAccel = 0;
last_speed = 0;
distance_travelled = 0;
lastTime = (long)(NSTimeInterval)([NSDate.date timeIntervalSince1970] * 1000.0);
accelerometer = UIAccelerometer.sharedAccelerometer;
accelerometer.updateInterval = 0.1;
accelerometer.delegate = self;
}
- (void)accelerometer:(UIAccelerometer *)meter
didAccelerate:(UIAcceleration *)acceleration
{
long long currentTime = (long)(NSTimeInterval)([NSDate.date timeIntervalSince1970] * 1000.0);
long long deltaTime = currentTime - lastTime;
lastTime = currentTime;
long accel_x = acceleration.x;
long accel_y = acceleration.y;
long accel_z = acceleration.z;
long integrated_acceleration = sqrtl(accel_x*accel_x + accel_y*accel_y + accel_z*accel_z);
long average_acceleration = (lastAccel + integrated_acceleration) / 2;
long speed = last_speed + (average_acceleration * deltaTime);
long average_speed = (speed + last_speed) / 2;
distance_travelled = distance_travelled + (average_speed * deltaTime);
last_speed = speed;
lastAccel = integrated_acceleration;
[speedView setText:[NSString stringWithFormat:#"%ld", speed]];
[distanceView setText:[NSString stringWithFormat:#"%ld", distance_travelled]];
}
#end
When the code is run, the speed and the distance keep going up continually, and deceleration (me slowing down) is never taken into account - so even when I stop, speed and distance keep going up.
Even by the standards of 'this will be a bit inaccurate' that's taking it too far!
All thoughts and opinions gratefully received (well almost - I know that it won't be accurate!)
You can't calculate Speed using UIAccelerometer.
Please find the link for more clarification : How to calculate speed using accelerometer in ios
Related
I am creating a simple drum machine. This function controls the time between each sample that is played (thus controlling the tempo of the drum machine). I need to control the tempo with a slider, so I'm hoping to be able to control the 'time duration until next step' value with this if possible. However, when I have tried to do this, it tells me "time is part of NSDate"
-(void)run
{
#autoreleasepool
{
// get current time
NSDate* time = [NSDate date];
// keeping going around the while loop if the sequencer is running
while (self.running)
{
// sleep until the next step is due
[NSThread sleepUntilDate:time];
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// time duration until next step
time = [time dateByAddingTimeInterval:0.5];
}
// exit thread
[NSThread exit];
}
}
This tells me NSTimeInterval is an incompatable type
// time duration until next step
time = [time dateByAddingTimeInterval: self.tempoControls];
Here is where the slider is declared
.m
- (IBAction)sliderMoved:(UISlider *)sender
{
AppDelegate* app = [[UIApplication sharedApplication] delegate];
if (sender == self.tempoSlider)
{
PAEControl* tempoControl = app.tempoControls[app.editIndex];
tempoControl.value = self.tempoSlider.value;
}
}
.h
#interface DetailController : UIViewController
#property (weak, nonatomic) IBOutlet UISlider *tempoSlider;
- (IBAction)sliderMoved:(UISlider *)sender;
Any help would me much appriciated, thanks in advance.
It looks like self.tempoControls is an array of PAEControl objects. The method named dateByAddingTimeInterval: needs an argument of type NSTimeInterval (aka double). It looks like you're trying to pass in this array instead.
Try changing this line -
time = [time dateByAddingTimeInterval: self.tempoControls];
To maybe this -
PAEControl* tempoControl = self.tempoControls[self.editIndex];
time = [time dateByAddingTimeInterval: (NSTimeInterval)tempoControl.value];
On another note, if this is all running on the main thread, be aware that you are blocking it and the UI will become very unresponsive.
This problem is driving me crazy and I actually need some help.
I have to implement a particle filter in iOS and I started from a working code in Java.
The algorithm is very close to which is described in the course "Artificial Intelligence for Robotics" of Thrun on Udacity (https://www.udacity.com/course/artificial-intelligence-for-robotics--cs373)
The Java implementation has this behavior: I move the robot, the particles get closer and closer to the robot and then they follow it with a very low error. This is the expected behavior, I think. Changing number of particles, number of landmarks and noise, I get better or worse results.
The iOS implementation behavior is quite different: I move the robot, the particles get closer to the robot but after few iterations they start moving far from the robot. Initially the mean value is still close to the robot but then it jumps far.
Changing number of particles, number of landmarks and noise, I get to almost the same result.
I double checked the iOS code and apparently it's correct.
I suspected that the problem was related to random number generation and/or gaussian random generation.
I have changed my code to use exactly the code that Java library uses (http://docs.oracle.com/javase/7/docs/api/java/util/Random.html#nextGaussian()) getting the same results.
I moved the gaussian random generator to a singleton in order to share the same generator across all the particles, but nothing changed.
I have tried to change drand48() with ((double)arc4random() / (double)UINT32_MAX) getting the same results.
I have no other ideas.
I am not asking you to debug my code but please, give me any advice to sort this problem out.
EDIT 1
Maybe these pictures may help
From this step particles get far from the robot
EDIT 2
This is my Particle class:
#interface Particle ()
#property (nonatomic, strong) MyPoint *point;
#property double orientation;
#property NSInteger worldWidth;
#property NSInteger worldHeight;
#property double probability;
#property double forwardNoise;
#property double turnNoise;
#property double senseNoise;
#property (nonatomic,strong) Utils *utils;
#end
#implementation Particle
-(instancetype)initWithWorldWidth:(float)worldWidth worldHeight:(float)worldHeight {
self = [super init];
if (self) {
_worldWidth = worldWidth;
_worldHeight = worldHeight;
_point = [[MyPoint alloc] initWithX:drand48() * _worldWidth
Y:drand48() * _worldHeight];
_orientation = drand48() * 2. * M_PI;
_forwardNoise = 0;
_turnNoise = 0;
_senseNoise = 0;
_utils = [Utils sharedInstance];
}
return self;
}
-(void)setPosition:(MyPoint *)p orientation:(float)orientation probability:(double)probability {
_point.x = p.x;
_point.y = p.y;
_orientation = orientation;
_probability = probability;
}
-(void)setNoise:(double)forwardNoise turnNoise:(double)turnNoise senseNoise:(double)senseNoise {
_forwardNoise = forwardNoise;
_turnNoise = turnNoise;
_senseNoise = senseNoise;
}
-(MyPoint *)getPosition {
return _point;
}
-(double)getOrientation {
return _orientation;
}
-(double)getProbability {
return _probability;
}
-(double)getForwardNoise {
return _forwardNoise;
}
-(double)getTurnNoise {
return _turnNoise;
}
-(double)getSenseNoise {
return _senseNoise;
}
-(NSArray<NSNumber *>*)sense:(NSArray<Landmark*>*)landmarks {
NSMutableArray<NSNumber*> *measures = [[NSMutableArray alloc] init];
for(int i=0; i<landmarks.count; i++) {
Landmark *landmark = landmarks[i];
double distance = [Utils distanceBetweenP1:_point andP2:landmark];
double measure = distance + [_utils box_muller:0 :1.] * _senseNoise;
[measures addObject:[NSNumber numberWithDouble:measure]];
}
return measures;
}
-(void)moveForward:(double)forward withTurn:(double)turn {
//NSLog(#"---- Move ---- forward: %f ---- %f",forward,turn);
double a1 = [_utils box_muller:0. :1.];
//NSLog(#"\ta1=%.8f",a1);
_orientation = _orientation + turn + a1 * _turnNoise;
_orientation = [Utils circle:_orientation :2*M_PI];
double a2 = [_utils box_muller:0. :1.];
//NSLog(#"\ta2=%.8f",a2);
double dist = forward + a2 * _forwardNoise;
_point.x += cos(_orientation) * dist;
_point.y += sin(_orientation) * dist;
_point.x = [Utils circle:_point.x :_worldWidth];
_point.y = [Utils circle:_point.y :_worldHeight];
}
-(double)measurementProb:(NSArray<NSNumber *> *)measurements landmarks:(NSArray<Landmark *>*)landmarks {
double prob = 1.0;
for(int i=0; i<measurements.count; i++) {
Landmark *landmark = landmarks[i];
double measurement = [measurements[i] doubleValue];
double dist = [Utils distanceBetweenP1:_point andP2:landmark];
prob *= [Utils gaussian:dist :_senseNoise :measurement];
}
_probability = prob;
return prob;
}
This is my Particle filter:
#import "ParticleFilter.h"
#interface ParticleFilter ()
#property (nonatomic,strong) NSMutableArray<Particle *> *particles;
#property (nonatomic,strong) NSArray<Landmark *> *landmarks;
#property NSInteger worldWidth;
#property NSInteger worldHeight;
#end
#implementation ParticleFilter
-(instancetype)initWithLandmarks:(NSArray<Landmark*>*)landmarks numberOfParticles:(NSInteger)numberOfParticles worldWidth:(float)worldWidth worldHeight:(float)worldHeight {
self = [super init];
if (self) {
_worldWidth = worldWidth;
_worldHeight = worldHeight;
_particles = [[NSMutableArray alloc] init];
for (int i = 0; i < numberOfParticles; i++) {
[_particles addObject:[[Particle alloc] initWithWorldWidth:worldWidth worldHeight:worldHeight]];
}
_landmarks = [NSArray arrayWithArray:landmarks];
}
return self;
}
-(void)setNoise:(double)forwardNoise turnNoise:(double)turnNoise senseNoise:(double)senseNoise {
for (Particle *p in _particles) {
[p setNoise:forwardNoise turnNoise:turnNoise senseNoise:senseNoise];
}
}
-(void)moveForward:(double)forward withTurn:(double)turn {
for (Particle *p in _particles) {
[p moveForward:forward withTurn:turn];
}
}
-(void)resample:(NSArray<NSNumber *>*)measurements {
NSMutableArray<Particle *> *newParticles = [[NSMutableArray alloc] init];
for (Particle *p in _particles) {
[p measurementProb:measurements landmarks:_landmarks];
}
double B = 0;
Particle *bestParticle = [self getBestParticle];
NSInteger index = drand48() * _particles.count;
for (int i = 0; i < _particles.count; i++) {
B += drand48() * 2. * [bestParticle getProbability];
while (B > [_particles[index] getProbability]) {
B -= [_particles[index] getProbability];
index = [self circle:index+1 :_particles.count];
}
[newParticles addObject:_particles[index]];
}
[_particles removeAllObjects];
[_particles addObjectsFromArray:newParticles];
}
-(NSInteger)circle:(NSInteger) num :(NSInteger)length {
while(num > length - 1)
num -= length;
while(num < 0)
num += length;
return num;
}
-(Particle *)getAverageParticle {
Particle *p = [[Particle alloc] initWithWorldWidth:_worldWidth worldHeight:_worldHeight];
double x = 0;
double y = 0;
double orient = 0;
double prob = 0;
for(int i=0; i<_particles.count; i++) {
x += [_particles[i] getPosition].x;
y += [_particles[i] getPosition].y;
orient += [_particles[i] getOrientation];
prob += [_particles[i] getProbability];
}
x /= _particles.count;
y /= _particles.count;
orient /= _particles.count;
prob /= _particles.count;
[p setPosition:[[MyPoint alloc] initWithX:x Y:y]
orientation:orient
probability:prob];
[p setNoise:[_particles[0] getForwardNoise]
turnNoise:[_particles[0] getTurnNoise]
senseNoise:[_particles[0] getSenseNoise]];
return p;
}
Each movement is:
[_robot moveForward:2. withTurn:0];
[_particleFilter moveForward:2. withTurn:0];
NSLog(#"%#",_particleFilter);
NSLog(#"Mean %#",[_particleFilter getAverageParticle]);
NSArray<NSNumber*> *measurements = [_robot sense:_landmarks];
[_particleFilter resample:measurements];
NSLog(#"%#",_particleFilter);
NSLog(#"Mean %#",[_particleFilter getAverageParticle]);
NSLog(#"Robot %#",_robot);
NSLog(#"Estimated Robot %#",[_particleFilter getAverageParticle]);
NSLog(#"Best Robot %#",[_particleFilter getBestParticle]);
Here the code involving random numbers
#import "Utils.h"
#interface Utils ()
#property BOOL haveNextNextGaussian;
#property double nextNextGaussian;
#end
#implementation Utils
+ (instancetype) sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[super alloc] initInstance];
});
return sharedInstance;
}
-(instancetype)initInstance {
self = [super init];
if (self) {
srand48(arc4random());
}
return self;
}
+(double)distanceBetweenP1:(MyPoint *)p1 andP2:(MyPoint *)p2 {
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
+(double)gaussian:(double)mu :(double)sigma :(double)x {
return exp(-(pow(mu - x, 2.)) / pow(sigma, 2.) / 2.0) / sqrt(2.0 * M_PI * pow(sigma, 2.));
}
-(double)box_muller:(double)m :(double)s {
if (_haveNextNextGaussian) {
_haveNextNextGaussian = NO;
return _nextNextGaussian;
} else {
double v1, v2, s;
do {
v1 = 2 * drand48() - 1; // between -1.0 and 1.0
v2 = 2 * drand48() - 1; // between -1.0 and 1.0
s = v1 * v1 + v2 * v2;
}
while (s >= 1 || s == 0);
double multiplier = sqrt(-2 * log(s)/s);
_nextNextGaussian = v2 * multiplier;
_haveNextNextGaussian = YES;
return v1 * multiplier;
}
}
+(double)circle:(double) num :(double)length {
while(num > length - 1)
num -= length;
while(num < 0)
num += length;
return num;
}
#end
This answer was posted as a "how do I find the problem with x" before the OP posted any code.
With code to look at, this may no longer be relevant, particularly since OP asserts that all steps listed below have been followed.
This is a vague answer, but it's a vague question..
It's not possible for us to give any advice or guidance here since the only piece of information really available to the reader is that you suspect that the problem lies in the random number generation.
This yor folt?
To answer this question as a reader, we're basically comparing the chances of a stranger (YOU) on the internet having made a mistake VS the chances of a mistake being made by a team of ios developers, and having that mistake go unnoticed. Unfortunately we have to put our bets on the mistake being your fault.
PROGRAMMER'S RULE OF THUMB: Test your code before blaming someone else's code. If you know the person you are about to blame, test it again. If the person you are about to blame sees you every day, test it a third time. If you are within punching distance of the person you are about to blame, test it once more.
Problem Solving (AKA How to Find and Fix Your Pooch Up)
The first step in solving any coding problem is identifying the problem.
Rather than haphazardly looking at your code for errors again and again (StackOverflow Users Agree(c) you will never find it):
Create some unit tests to verify your code expectations and make sure that you're doing things right. If you are, then..
If the units of code that you've tested are all behaving correctly, check that they behave correctly when combined. Create tests that check chains of operations. The longer the chains, the more of the end to end functionality of your app you will be testing. Still not working? You may consider...
Remote debug your application while it is running to see exactly what is going on. Can't get remote debug to work? You're making this hard, but...
Log a small set of data from your app. Create a test to run your data set through your code. Verify expectations. You still can't get it to work? Then try...
Post your question on StackOverflow, with relevant code and data set.
Basically TEST, TEST, TEST
Every test made will save you time in the long run if your app is in active development (very mature and stable code will benefit less).
I am developing a game for iOS with Obj-C and Apple's SpriteKit framework. My primary testing device is an iPad2 (model #MC773X/A running iOS8.1). My game runs smoothly on this device except for a single "stutter" around 60-120s in. At this time the app lags for around 150-300ms / 10-20 frames.
I have tested the same game app on an iPhone6+ (model #MGAJ2X/A running iOS8.4) which doesn't seem to have this problem.
I originally thought it had something to do with my game apps resource management, and then later maybe the debugging link itself... so I created the following testing app. It is simply a blank screen, that logs dt's above 20ms. It has an option to log to the screen (using SKLabelNodes) for testing without a debugging link.
This simple testing app shows the same stutter on the iPad2 without fail at around the 60s mark (built in debug/release, with/without debugging). The iPhone 6+ doesn't show the stuttering behaviour.
Starting with a new SpriteKit game template for iOS (I am using Xcode 6.4 but I saw the same bug with an earlier version - Xcode 6.something):
In GameViewController.m:
//scene.scaleMode = SKSceneScaleModeAspectFill;
scene.scaleMode = SKSceneScaleModeResizeFill;
Replace GameScene.m with:
#import "GameScene.h"
#define LOG_TO_SCREEN
static int dtLogCounter = 1;
#ifdef LOG_TO_SCREEN
static const int dtLogLabelNum = 10; //5;
#endif
#interface GameScene ()
#property (nonatomic) CFTimeInterval prevTime;
#property (nonatomic) CFTimeInterval startTime;
#ifdef LOG_TO_SCREEN
#property (strong, nonatomic) SKLabelNode *timeLabel;
#property (strong, nonatomic) NSArray *dtLogLabels;
#endif
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view
{
#ifdef LOG_TO_SCREEN
CGFloat screenWidth = view.bounds.size.width;
_timeLabel = [SKLabelNode labelNodeWithFontNamed:#"Courier"];
_timeLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeRight;
_timeLabel.fontSize = 16.0;
_timeLabel.position = CGPointMake(screenWidth - 7.0, 28.0);
[self addChild:_timeLabel];
NSMutableArray *dtLogLabels = [NSMutableArray arrayWithCapacity:dtLogLabelNum];
for (int i = 0; i < dtLogLabelNum; i++)
{
SKLabelNode *dtLogLabel = [SKLabelNode labelNodeWithFontNamed:#"Courier"];
dtLogLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeRight;
dtLogLabel.fontSize = 16.0;
dtLogLabel.position = CGPointMake(screenWidth - 7.0, 100.0 + 25*i);
[self addChild:dtLogLabel];
[dtLogLabels addObject:dtLogLabel];
}
_dtLogLabels = [NSArray arrayWithArray:dtLogLabels];
#endif
_prevTime = _startTime = CACurrentMediaTime();
}
-(void)update:(CFTimeInterval)currentTime
{
CFTimeInterval dt = currentTime - _prevTime;
_prevTime = currentTime;
CFTimeInterval gameTime = currentTime - _startTime;
#ifdef LOG_TO_SCREEN
_timeLabel.text = [NSString stringWithFormat:#"t:%.1f(c)", gameTime];
#endif
if (dt > 20e-3)
{
NSString *logStr = [NSString stringWithFormat:#"#%d:%.1f:%.0f ", dtLogCounter++, gameTime, dt*1000];
#ifdef LOG_TO_SCREEN
for (int i = dtLogLabelNum-1; i > 0; i--)
{
SKLabelNode *dtLogLabel = [_dtLogLabels objectAtIndex:i];
SKLabelNode *prevLabel = [_dtLogLabels objectAtIndex:i-1];
dtLogLabel.text = prevLabel.text;
}
SKLabelNode *firstDtLogLabel = [_dtLogLabels objectAtIndex:0];
firstDtLogLabel.text = logStr;
#else
NSLog(#"%#", logStr);
#endif
}
}
#end
I am getting comfortable with using the time profiler instrument but in this case I don't think it is particularly helpful. It shows no activity for the duration of the stutter.
(image1)
(Unfortunately I'm a brand new poster so can't inline images)
It seems like it is a system process happening at this time which is causing the app to stutter. The activity monitor instrument shows a peak at this time but I don't really know how to interpret the data / nothing obvious stands out.
(image2)
The system usage instrument shows no activity in the I/O activity track.
The file activity instrument doesn't seem to be available.
My question is: how should I approach debugging this issue? Which instrument could I use, and how?
Confirmation that others see this issue on this/other hardware would be appreciated as would suggestions to it's cause/resolution.
Thanks
If you haven't already, I suggest you read Apple's Analyzing CPU Usage in Your App. If that does not yield any positive results for you, peppering your code with NSLog statements can be another way to try to pinpoint the code section involved with this issue.
Do keep in mind that you are probably not seeing the stutter in the iPhone 6+ due to its faster CPU. The iPad 2 is somewhat dated and runs much slower. This itself is a small clue to your issue.
I have this game when a sprite touches the sides, you get a point. I figured out the code to increment the score each time the sprite touches the side. I get the correct output through the NSLog message.
But, When I try the same code by changing the code from NSLog to SKLabelNode, I get a more than a thousand nodes (which I think will affect the performance and slow the game).Also, when the score gets incremented, it overlaps the old score rather than just increasing the score. I have added the code in the update with frame.
Heres the code:
- (void)printScore {
NSString *text = [NSString stringWithFormat:#"%ld",(long)userScore];
SKLabelNode *score = [SKLabelNode labelNodeWithText:text];
score.fontName = #"chalkduster";
score.fontSize = 45;
score.fontColor = [SKColor whiteColor];
score.position = CGPointMake(CGRectGetMidX(self.frame) + 175 ,CGRectGetMidY(self.frame) + 350);
[self addChild:score];
}
-(void)update:(CFTimeInterval)currentTime {
[self printScore];
}
How do I fix this so that the score gets updated without so many sprites being added?
Sorry if this is a really dumb question, I am noob.
Add just a single property:
#property (nonatomic, strong) SKLabelNode *scoreLabel;
And create it just one time in viewDidLoad o an init method:
_scoreLabel = [SKLabelNode initWithFontNamed:#"chalkduster"];
_scoreLabel.fontSize = 45;
_scoreLabel.fontColor = [SKColor whiteColor];
_scoreLabel.position = CGPointMake(CGRectGetMidX(self.frame) + 175 ,CGRectGetMidY(self.frame) + 350);
[self addChild:_scoreLabel];
Then call the printScore method only when the sprite touches de sides, not on each frame like you are doing in the update method:
- (void)printScore
{
NSString *text = [NSString stringWithFormat:#"%ld",(long)userScore];
self.scoreLabel.text = text;
}
I hope this helps.
The reason you get so many nodes is that you are constantly adding new labels to the scene. Notice that in your printScore method you are create new SKLabelNode instances and then adding that to the scene.
In order to correct that, you will want to maintain a reference to a single SKLabelNode and then update the text of that.
Also, you may want to move the call to printScore to only be called when the actual scoring event happens, as opposed to update which is called for every frame. I'm of course assuming you do not have a scoring event happen every frame.
Your printScore method creates a new SKLabelNode every time it is called and you are calling it 60 times a second via your update method. You should set a BOOL to notify you of the need to call your printScoreMethod only when there is a need to do so (as in the score has changed).
create a global boolean and only add the label the first time:
BOOL firsttime = YES;
- (void)printScore {
NSString *text = [NSString stringWithFormat:#"%ld",(long)userScore];
SKLabelNode *score = [SKLabelNode labelNodeWithText:text];
if( firsttime )
{
score.fontName = #"chalkduster";
score.fontSize = 45;
score.fontColor = [SKColor whiteColor];
score.position = CGPointMake(CGRectGetMidX(self.frame) + 175, CGRectGetMidY(self.frame) + 350);
[self addChild:score];
firsttime = NO;
}
}
-(void)update:(CFTimeInterval)currentTime {
[self printScore];
}
There are a few ways to approach this. I prefer an approach which hides as much of the underlying implementation. Ideally, I would actually create a class which represents the score HUD. From a "rest of the app code" perspective, the other game code's contract with the HUD is through a score property which reflects the score.
Some will argue it is one label and it seems like a hassle. But if you change how it is implemented (for example, say it becomes a score with a fill bar or perhaps you have special effects which accompany a change in score), you will find it makes it super easy to deal with. Or what if you suddenly have a 2 player game and both players track score? You can easily add a new score by adding a new instance of your the score class.
Here is a simple example of what I mean.
ScoreHUD.h
#interface ScoreHUD : NSObject
#property (nonatomic, assign) NSInteger score;
- (instancetype)initWithScene:(SKScene *)scene x:(CGFloat)x y:(CGFloat)y;
#end
ScoreHUD.m
#interface ScoreHUD()
#property (nonatomic, strong) SKLabelNode *scoreLabel;
#end
#implementation ScoreHUD
- (instancetype)initWithScene:(SKScene *)scene x:(CGFloat)x y:(CGFloat)y
{
self = [super init];
if (self) {
_scoreLabel = [SKLabelNode initWithFontNamed:#"chalkduster"];
_scoreLabel.fontSize = 45;
_scoreLabel.fontColor = [SKColor whiteColor];
_scoreLabel.position = CGPointMake(x, y);
[scene addChild:_scoreLabel];
// Setting initial label value to the default value of the score
_scoreLabel.text = [#(_score) stringValue];
}
return self;
}
- (void)setScore:(NSInteger)score
{
if (_score != score) {
_score = score;
self.scoreLabel.text = [#(score) stringValue];
}
}
#end
Note that I haven't tested the code or checked to see if it would compile.
So to use this, you would create an instance of this in your scene like:
// Property in the scene
#property (nonatomic, strong) ScoreHUD *score;
// Initialize where you need to
self.score = [[ScoreHUD alloc] initWithScene:self x:CGRectGetMidX(self.frame) + 175 y:CGRectGetMidY(self.frame) + 350];
You could do this in your scene's initWithSize.
When the score changes, you simply do something like:
self.score.score += 1;
You can always wrap that in another method if it looks nasty to you.
There is a drawback to this method. Say your score get's updated a lot during a frame. This means your text would be set multiple times per frame. Depending on the type of game you have, this may or may not be a concern. There are ways to still have this basic setup and work around it. But for the vast majority of games, it will not be an issue.
i am trying to understand how you can make a huge world, and have a character move inside the world, and then it moves the visible part of the world with the moving character. Like the Mario game.
I read apples guide here: https://developer.apple.com/library/IOs/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Actions/Actions.html#//apple_ref/doc/uid/TP40013043-CH4-SW32
on "centering the scene on a node" but i really couldn't understand it at all. I tried implementing it, but it did not work at all.
So, anyone able to help me here? A full example with some good comments on would be great.
Hope my question makes sense, thanks on advance everyone!
They try to talk with you "Your game world is a node and what you need to do only move the world node with your character".
Game "Adventure" - the example game for Sprite Kit will help you a lot to create a huge world like that.
https://developer.apple.com/LIBRARY/IOS/documentation/GraphicsAnimation/Conceptual/CodeExplainedAdventure/AdventureArchitecture/AdventureArchitecture.html
=> There are example Code of this game in documentary.
Besides, you can create "Tile map" to do the same.
I use a simple class AGMovingNode, inherited from SKNode as a background layer.
It's not perfect, but at least you can start from where.
Here is a code of the class:
AGMovingNode.h:
#import <SpriteKit/SpriteKit.h>
#interface AGMovingNode : SKNode
#property float pointsPerSecondSpeed;
- (instancetype)initWithPointsPerSecondSpeed:(float)pointsPerSecondSpeed;
- (void)update:(NSTimeInterval)currentTime paused:(BOOL)paused;
#end
AGMovingNode.m:
#import "AGMovingNode.h"
#implementation AGMovingNode
{
NSTimeInterval _lastUpdateTime;
NSTimeInterval _deltaTime;
}
- (instancetype)initWithPointsPerSecondSpeed:(float)pointsPerSecondSpeed {
if (self = [super init]) {
self.pointsPerSecondSpeed = pointsPerSecondSpeed;
}
return self;
}
- (void)update:(NSTimeInterval)currentTime paused:(BOOL)paused {
if (paused) {
_lastUpdateTime = 0;
return;
}
//To compute velocity we need delta time to multiply by points per second
if (_lastUpdateTime) {
_deltaTime = currentTime - _lastUpdateTime;
} else {
_deltaTime = 0;
}
_lastUpdateTime = currentTime;
CGPoint bgVelocity = CGPointMake(-self.pointsPerSecondSpeed, 0.0);
CGPoint amtToMove = CGPointMake(bgVelocity.x * _deltaTime, bgVelocity.y * _deltaTime);
self.position = CGPointMake(self.position.x+amtToMove.x, self.position.y+amtToMove.y);
}
#end
I found it in one of tutorials (can't remember which one right now) and slightly modified it.
I found it best if you use SKActions to move objects around.
What i do is have a method to reposition my character also with sprite animations (when he is standing and when he is walking):
- (void)moveUser:(CGPoint)position {
SKSpriteNode *user = (SKSpriteNode*)[self childNodeWithName:#"userSprite"];
// ... some logic to load correct atlas
NSArray *animationFrames = ...
SKAction *animation = [SKAction animateWithTextures:animationFrames timePerFrame:0.05f numberOfFrames:animationFrames.size];
SKAction *moveToPositionAction = [SKAction moveTo:position duration:0.4f];
[userNode runAction:[SKAction group:#[animation,moveToPositionAction]]];
}