SpriteKit: Making a character move in a world - ios

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

Related

How to update the score without generating countless nodes?

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.

Store in memory NSArray with pictures or SKAction

i don't completely understand the best choice in sprite kit animation;
1) Apple in "Adventure" example use this methodology, they store in memory animation as pictures in nsarray :
static NSArray *sSharedTouchAnimationFrames = nil;
- (NSArray *)touchAnimationFrames {
return sSharedTouchAnimationFrames;
}
- (void)runAnimation
{
if (self.isAnimated) {
[self resolveRequestedAnimation];
}
}
- (void)resolveRequestedAnimation
{
/* Determine the animation we want to play. */
NSString *animationKey = nil;
NSArray *animationFrames = nil;
VZAnimationState animationState = self.requestedAnimation;
switch (animationState) {
default:
case VZAnimationStateTouch:
animationKey = #"anim_touch";
animationFrames = [self touchAnimationFrames];
break;
case VZAnimationStateUntouch:
animationKey = #"anim_untouch";
animationFrames = [self untouchAnimationFrames];
break;
}
if (animationKey) {
[self fireAnimationForState:animationState usingTextures:animationFrames withKey:animationKey];
}
self.requestedAnimation = VZAnimationStateIdle;
}
- (void)fireAnimationForState:(VZAnimationState)animationState usingTextures:(NSArray *)frames withKey:(NSString *)key
{
SKAction *animAction = [self actionForKey:key];
if (animAction || [frames count] < 1) {
return; /* we already have a running animation or there aren't any frames to animate */
}
[self runAction:[SKAction sequence:#[
[SKAction animateWithTextures:frames timePerFrame:self.animationSpeed resize:YES restore:NO],
/* [SKAction runBlock:^{
[self animationHasCompleted:animationState];
}]*/]] withKey:key];
}
I appreciate this methodology, but i can't understand. Is storing SKAction in memory not better choice and use animation always like this ?
[self runAction:action];
without making always new SKAction;
[SKAction animateWithTextures:frames timePerFrame:self.animationSpeed resize:YES restore:NO]
Storing SKTextures in an NSArray is the recommended methodology to be used for animation. I can't say that I found SpriteKit's documentation to be lacking on the subject either, as SKAction has the method animateWithTextures.
You could indeed have a SKAction that has a given animation defined by a given NSArray, and maybe store those in a NSMutableDictionary with an animation name for a key. However this methodology I see above just has the animateTextures line of code once in their fireAnimationState method, and you can pass parameters to it such as the animationState and a key.
I think you would need to take more than a surface look at their methodology to determine why they chose to go this route. You can see that the animationState was being utilized upon completion of the animation and likely triggered something else.
[self runAction:action] is indeed simple, however it's also easy to see that it's not in any way managing an animationState or key, which I have to assume they decided their game needed to do.
Also keep in mind that their methodology likely has a higher level where in a given player class it's calling a flexible method for enacting an animation for a given game entity and doing other things besides just changing the animation. So in their high level coding they might be doing something more like this :
[self runAnimation:#"jump"];
or even
[self jump];
And because they have designed a low level system that manages animation states and adds keys for a given management design, they don't ever have to code the long line that you are pointing out except once in that method you see above.
A few reasons I have found it useful to store the NSArray of frames and not storing wrapped in a SKAction is because I sometimes want to manipulate the start frame of the animation or run the animation at a different speed. But you may or may not have the need to manipulate those aspects of your animation.

Double notification on contact in spriteKit

I seem to get a double notification when my SKSpriteNode hits has contact with the worldCategory, how come is this? This creates problem when i want to run an action when it touches the worldCategory, since the action is being triggered
Here is my bitmask in the InitWithSize method
mover.physicsBody.categoryBitMask = birdCategory;
bottom.physicsBody.categoryBitMask = worldCategory;
mover.physicsBody.contactTestBitMask = worldCategory;
and here is the contact method:
- (void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.bodyA.categoryBitMask == worldCategory) {
mover.texture = [SKTexture textureWithImageNamed:#"birddead1"];
NSLog(#"Contact");
self.scene.paused = YES;
[pauseButton removeFromSuperview];
}
}
In my log there is being shown two lines with "Contact"
Set your object's restitution property to zero like this:
self.physicsBody.restitution = 0; //it's either self or the name of your object
If that does not solve your issue, look at the movement code related to your object(s). Look for any situation that cause a 'back and forth' movement which can create the double contact issue.
As a last resort you can set up a filter for your contacts:
Create a variable which stores the time a contact was made.
Compare the contact variable time against the current time in the update: method.
If the difference is less than your specified time (example 0.2 sec) then allow the contact and set your contact time variable to the current time. If the difference is below the filter time (0.2 sec), ignore the contact.
I think you can try to be more specific about which body is A and which is B and which hits what.
Maybe something like this:
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKSpriteNode *firstNode, *secondNode;
firstNode = (SKSpriteNode *)contact.bodyA.node;
secondNode = (SKSpriteNode *) contact.bodyB.node;
int bodyAA = contact.bodyA.categoryBitMask;
int bodyBB = contact.bodyB.categoryBitMask;
if ((bodyAA == birdCategory && (bodyBB == worldCategory)){
mover.texture = [SKTexture textureWithImageNamed:#"birddead1"];
NSLog(#"Contact");
self.scene.paused = YES;
[pauseButton removeFromSuperview];
}
}
If you are using iOS 7.1 you may want to also set the skView.showsPhysics to YES at ViewController.m this way you can clearly see what is happening.

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?

Implementing CCSprite class into my game

In my game, which is using cocos2d, there is going to be many different types of enemies, which all look different, and move all in different ways. Also, there is going to be a couple of different gamemodes, which both use the same enemies. As there will be different gamemodes, I decided to make each of my enemies have their own CCSprite class. In those there will be the way that the sprites move, the animation, etc. When one of these sprite is needed in my game, they will be spawned in to the scene. The only thing is, how do I do this? How do I call for one of the sprites to be create on the screen when they are using a class of their own?
If you want to tell me another way than having these sprites having their own classes, that is fine, but keep in mind that I will be having a couple of different gamemodes. If I do the code for the sprites in the CCLayer class of that gamemode, well I will have to write the code twice, which will take time.
Thanks.
You can just subclass CCSprite and override the default initializer initWithTexture:rect:
example taken from here
#implementation MySprite
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
if( (self=[super initWithTexture:texture rect:rect]))
{
// initialize your ivars here
//ivar1 = xxx;
//ivar2 = yyy;
//ivar3 = zzz;
}
return self;
}
#end
// And to create an instance of MySprite you simply do:
MySprite *sprite = [MySprite spriteWithFile...];
// or any of the supported CCSprite methods.
you can have a super class say EnemySprite that looks like this
#interface EnemySprite : CCSprite
- (void)addToLayer:(CCLayer *)layer;
- (void)removeFromLayer:(CCLayer *)layer;
#end
than create a subclass for each type of enemy for example:
#inteface BigEnemySprite : EnemySprite
#end
#implementation BigEnemySprite
- (void)addToLayer:(CCLayer *)layer {
[layer addChild:self];
// animation code for your big enemy
}
- (void)removeFromLayer:(CCLayer *)layer {
[layer removeChild:self];
// animation code
}
#end
than you can use them like
EnemySprite *enemy = [BigEnemySprite spriteFromFile:file];
[enemy addToLayer:self];

Resources