How to update the score without generating countless nodes? - ios

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.

Related

Can't change "int" in Objective C

I have had a very weird experience with Objective C and xCode these last couple of days. I'm now turning to you guys for some quick help.
I'm simply trying to set up and int, call in damage(the amount of damage this object is supposed to do) and increase it if a void function is called.
-(void) increaseDagage{
damage = damage + 100;
NSLog(#"%f", damage);
}
I have tried setting the int damage up as a int and also as
#property (nonatomic, assign) float damage;
The problem is that when I print "damage" it hasn't increased...
I also have a function that returns the amount of damage this object does and it returns the wrong value.
I can't figure out why this isn't doing what I want...
I also have an int called health, which is basically the same thing and works fine.
Here's the full class if you want to see that too,
//
// Character.m
// TD
//
// Created by Victor Appelgren on 2014-12-23.
// Copyright (c) 2014 Victor Appelgren. All rights reserved.
//
#import "Character.h"
#import "constants.h"
#import "Level.h"
#import "GameViewController.h"
#interface Character (){
SKSpriteNode *character;
float health;
float maxHealth;
Level *level;
GameViewController *gVC;
BOOL dead;
int damage;
}
//#property (nonatomic, assign) float damage;
#end
#implementation Character
-(id) init {
if (self = [super init]){
damage = 100;
dead = NO;
// Load health
maxHealth = 200;
health = 200.0;
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:character.frame.size.width / 2];
[self addChild:character];
}
return self;
}
-(void) doDamageType:(int)type{ // here a type is passed in to tell how much damage is being done.
if (type == 1){
health = health - 20;
} else if (type == 2){
health = health - 40;
}
if (health <= 0){
[self removeFromParent];
dead = YES;
}
}
-(void) increaseDagage{
damage = damage + 100.0;
// NSLog(#"%f", _damage);
}
-(int) returnDamage{
return damage;
}
-(BOOL) returnDead{
return dead;
}
-(float) returnHealth{
return health;
}
-(float) returnMaxHealth{
return maxHealth;
}
#end
Here's the output I'm getting,
2015-02-22 01:28:27.722 TD[279:43180] 200.000000
2015-02-22 01:28:30.327 TD[279:43180] 200
2015-02-22 01:28:30.496 TD[279:43180] 200
2015-02-22 01:28:30.644 TD[279:43180] 200
2015-02-22 01:28:30.809 TD[279:43180] 200
first is initial value and the rest is from when the function is being called
What is wrong....
I might be missing something here but can't seem to find the problem
Thanks for the help
The problem is that you've created two different damage variables -- one in your .h and another in your .m. The one in your .h is public and accessible from your .m using self.damage. The one you've declared in your .m is private and accessible simply using damage. So the problem here is that you're accessing the public version of your damage from the other class, but you're actually manipulating the private version within your class.
So I'd recommend changing the following methods and property declarations of your .m as follows:
// ***Remove the private declaration of damage***
#interface Character (){
SKSpriteNode *character;
float health;
float maxHealth;
Level *level;
GameViewController *gVC;
BOOL dead;
}
#end
#implementation Character
// ***And add "self." before each instance of damage***
-(id) init {
if (self = [super init]){
self.damage = 100;
dead = NO;
// Load health
maxHealth = 200;
health = 200.0;
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:character.frame.size.width / 2];
[self addChild:character];
}
return self;
}
-(void) increaseDagage{
self.damage = self.damage + 100.0;
// NSLog(#"%f", self.damage);
}
-(int) returnDamage{
return self.damage;
}
#end
You have a spelling error in your method name : increaseDagage
I am guessing this should be increaseDamage
You also seem to be creating your own setters and getters. I would read up on the usage of properties.
Several things jump out:
Your method is called increaseDagage. Do you get any error message in Xcode's console when you call it?
You are not telling us whether you are getting any error or warning messages from the code. You should be getting some.
You keep talking about ints, but the log statement and property you define are floats.
You are not #synthesizeing your float damage property. So that means that it will create its own _damage instance variable to store the value and not use your damage instance variable (which would be the wrong type anyway).
You are logging _damage, not damage.
In short, the code, as written, mixes up two completely different things.

SKLabelNode when added to class throws attempted to add nil node error

I'm following along this Spritekit tutorial http://code.tutsplus.com/tutorials/ios-sdk-build-a-facts-game-interface-creation--mobile-20764
And they suggest to add a timer's text in the viewDidLoad Method. The code that is given in the tutorial looks like this:
_timerLevel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
_timerLevel.text = #"30";
_timerLevel.fontSize = 70;
_timerLevel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)+350);
[self addChild:_timerLevel];
Once I run the line [self addChild:_timerLevel] the error 'attempted to add nile node error to parent' is thrown.
And _timerLevel is declared in the interface as
#property (nonatomic,weak) SKLabelNode* timerLevel;
The scene is initalised like so:
-(id) initWithSize:(CGSize)size inLevel:(NSInteger)level withPlayerLives:(int)lives {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.35 green:0.25 blue:0.5 alpha:1.0];
defaults = [NSUserDefaults standardUserDefaults];
playerLives = lives;
playerLevel = level;
maximumTime = 30;
}
return self;
}
Is the error telling me that the _timerLevel hasn't been initialised and is therefore nil? How do I initialise it correctly? Many thanks
Either remove the weak keyword:
#property (nonatomic) SKLabelNode* timerLevel;
Or assign the label to a local variable first:
SKLabelNode* timerLevel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
_timerLevel = timerLevel;
The problem with the use of weak here is that after initializing the label, there is no strong reference holding on to it unless you either assign it to a local variable or remove the weak keyword. Otherwise the label is initialized, returned, and immediately deallocates and set to nil even before the next line executes.
you don't want the SKLabelNode code in the ViewDidLoad function of the view controller. You want to move it in the didMoveToView function of the SKScene you created.

Nil label with spritebuilder, can't figure out how to fix it

I am making an iOS game with sprite builder. And I designed it around a board game style. I want the user to press the play button that triggers the play method. It then generates a random number that should be displayed on a label. The label and button are on the gameplay scene. The game play scene is a subclass of CCNode. The code is on the Gameplay class that is a subclass of CCNode. I have found out that the label is nil. How do I make it not nil? The code connection for my label is doc root var assigned to _randNumLabel. My gameplay code connection is assigned Gameplay. This is my log after I open the scene and click the button:
2014-06-09 17:20:12.565 Sunk[6037:60b] CCBReader: Couldn't find member variable: _randNumLabel
2014-06-09 17:20:12.567 Sunk[6037:60b] CCBReader: Couldn't find member variable: _affectLabel
2014-06-09 17:20:19.513 Sunk[6037:60b] Nil`
Ignore the _affectLabel as it will get fixed if _randNumLabel gets fixed.
#import "Gameplay.h"
#implementation Gameplay
CCLabelTTF *_randNumLabel;
- (void)play {
if (_randNumLabel == nil)
{
CCLOG(#"Nil");
}
if (_randNumLabel !=nil)
{
CCLOG(#"play button pressed!");
int max = 6;
int randNumber = (arc4random() % max) + 1; // Generates a number between 1-6.
CCLOG(#"Random Number %d", randNumber);
_randNumLabel.string = [NSString stringWithFormat:#"Number: %d", randNumber];
}
}
- (void)update:(CCTime)delta {
}
#end
You need to declare your instance variables using curly brackets, i.e.:
#implementation Gameplay
{
CCLabelTTF *_randNumLabel;
}
- (void)play {
// rest of your code
...
As a personal preference I would use a private property instead of an instance variable, e.g.
#interface Gameplay ()
#property (nonatomic, strong) CCLabelTTF *randNumLabel;
#end
#implementation Gameplay
- (void)play {
// rest of your code
...

Sprite kit - naming repeated objects

Using apple spritekit, how can I uniquely identify nodes if, say 20 nodes, they have same art work and are to be placed randomly on the screen? Is there a faster way like in cocos2d there is a"tag" function to identify?
I'm not the most proficient with sprites, but is this something you're looking for?
Save:
NSArray * nodesArray; // some array of nodes.
for (int x = 0; x < nodesArray.count; x++) {
SKNode * node = nodesArray[x];
node.name = [NSString stringWithFormat:#"%i", x];
}
Retrieve:
int nodeToRetrieveTag = 2; // or whatever
SKNode* nodeToRetrieve = [self.scene childNodeWithName:[NSString stringWithFormat:#"%i", nodeToRetrieveTag]];
I'm usually using the property name:
SKNode *myNode = [[SKNode alloc]init];
myNode.name = #"uniqueName";
[self addChild:myNode];
In the SKScene, to recovery the node you can do:
[self childNodeWithName:#"uniqueName"]; // self is SKScene
If for some reason you don't want to use name, you can always subclass one SKNode and add your personal unique identifier:
MySpriteNode.h
#interface MySpriteNode : SKSpriteNode
#property (nonatomic, strong) NSString *personalIdentifier;
#end
and
MySpriteNode.m
#import "MySpriteNode.h"
#implementation MySpriteNode
#end
with this second option you can:
for (MySpriteNode *sprite in [self children]) personalIdentifier
{
if ([sprite.personalIdentifier isEqualToString:#"something"])
{
//do something
}
}
EDIT 1:
Try to follow these tutorials, I really think they are great. I learned a lot with them:
An iOS 7 Sprite Kit Game Tutorial
Sprite Kit Programming Guide
Sprite Kit Tutorial for Beginners
Are you looking for enumerateChildNodesWithName:usingBlock: method?

SpriteKit: Making a character move in a world

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]]];
}

Resources