I've had some luck learning how to scroll the background infinitely in Sprite-kit but the current way I've been doing this only scrolls to the left. I have a left and right movement button and would also need the method below to infinitely scroll to the right as well as the left. Ive tried to implement it myself but the results are horrible, any help is appreciated.
How iv'e been scrolling
for (int i = 0; i < 2; i++) {
SKSpriteNode * bg = [SKSpriteNode spriteNodeWithImageNamed:#"bgimage"];
bg.anchorPoint = CGPointZero;
bg.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.origin.y);
bg.name = #"snow1";
[self addChild:bg];
}
and in update method for when the "right button" is pushed:
[self enumerateChildNodesWithName:#"snow1" usingBlock: ^(SKNode *node, BOOL *stop) {
SKSpriteNode *bg = (SKSpriteNode *) node;
bg.position = CGPointMake(bg.position.x - 5, bg.position.y);
if (bg.position.x <= -bg.size.width) {
bg.position = CGPointMake(bg.position.x + bg.size.width * 2, bg.position.y);
}
}];
This method only scrolls to the left, I also need the background to infinitely scroll to the right when the "left button" is pushed.
You are decrementing bg.position.x when scroll to the left. Obviously, you need to increment bg.position.x if you want to move the node to the right:
// if left button is pressed
[self enumerateChildNodesWithName:#"snow1" usingBlock: ^(SKNode *node, BOOL *stop) {
SKSpriteNode *bg = (SKSpriteNode *) node;
bg.position = CGPointMake(bg.position.x + 5, bg.position.y);
if (bg.position.x >= bg.size.width) {
bg.position = CGPointMake(bg.position.x - bg.size.width * 2, bg.position.y);
}
}];
Related
In my app, I have an image that acts as the ground, and scrolls along the bottom. I initialize and scroll it with:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++)
{
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"Bottom_Scroller"];
bg.zPosition = BOTTOM_BACKGROUND_Z_POSITION;
bottomScrollerHeight = bg.size.height;
bg.position = CGPointMake((i * bg.size.width) + (bg.size.width * 0.5f) - 1, bg.size.height * 0.5f);
bg.name = #"bg";
bg.physicsBody = [SKPhysicsBody bodyWithTexture:bg.texture size:bg.texture.size];
bg.physicsBody.categoryBitMask = bottomBackgroundCategory;
bg.physicsBody.contactTestBitMask = flappyBirdCategory;
bg.physicsBody.collisionBitMask = 0;
bg.physicsBody.affectedByGravity = NO;
[self addChild:bg];
}
}
- (void)moveBottomScroller
{
[self enumerateChildNodesWithName:#"bg" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled of the screen, if yes then put it at the end of the other node
if (bg.position.x + bg.size.width * 0.5f <= 0)
{
bg.position = CGPointMake(bg.size.width*2 - (bg.size.width * 0.5f) - 2,
bg.position.y);
}
}];
}
However, after it scrolls for so long, it shows a gap in it, as shown below. How can I fix this?
What I'd do is:
// This would go in the init or didMoveToView method of your scene
const NSUInteger numBgs = 3;
for (NSUInteger i = 0; i < numBgs; i++) {
CGFloat color = 0.2f * i;
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:color green:color blue:color alpha:1.0] size:CGSizeMake(512, 300)];
bg.anchorPoint = CGPointMake(0.5, 0);
bg.position = CGPointMake((i * bg.size.width) + (bg.size.width * 0.5f), CGRectGetMinY(self.frame));
bg.name = #"bg";
[self addChild:bg];
if (i == numBgs-1) { // Means it's last bg on the right
lastBg = bg;
}
}
[self enumerateChildNodesWithName:#"bg" usingBlock:^(SKNode *node, BOOL *stop) {
SKSpriteNode * bg = (SKSpriteNode *) node;
//Checks if bg node is completely scrolled of the screen, if yes then put it at the end of the other node
if (bg.position.x + bg.size.width * 0.5f <= 0)
{
bg.position = CGPointMake(lastBg.position.x + bg.frame.size.width,
bg.position.y);
lastBg = bg;
}
CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
}];
Where lastBg is an instance variable that points to the bg located at the top most right so that re-positioning is always going to be relative to this sprite.
Other things you can try are switching the check of the position before the re-positioning (as I did in the example) and also remove the check out of the enumerate block and do it independently of the re-positioning.
I used to do like in the example and it worked fine. Let me know how it does.
I have my background set and it is scrolling horizontally, here is my code:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++)
{
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
bottomScrollerHeight = bg.size.height;
bg.position = CGPointMake(i * bg.size.width, 0);
bg.anchorPoint = CGPointZero;
bg.name = #"background";
[self addChild:bg];
}
}
and also this code:
- (void)moveBottomScroller
{
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled off the screen, if yes then put it at the end of the other node
if (bg.position.x <= -bg.size.width)
{
bg.position = CGPointMake(bg.position.x + bg.size.width*2,
bg.position.y);
}
[bg removeFromParent];
[self addChild:bg]; //Ordering is not possible. so this is a hack
}];
}
The second part makes the background scroll. Without it, the background is still.
Also, without the movebottomscroller, my sprite appears on top of the background. With the movebottomscroller, he appears behind the scrolling background. Is there any command to bring him to the front, above any other backgrounds?
Thank you!
Try the approach below, hope that works for you.
#interface GameScene()
#property (nonatomic) NSTimeInterval lastTimeSceneRefreshed;
#end
#implementation GameScene
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self buildBackground];
[self startScrolling];
}
return self;
}
// This method will add 3 background nodes
- (void)buildBackground {
float centerX = CGRectGetMidX(self.frame);
SKSpriteNode *firstBackgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
firstBackgroundNode.name = #"background";
firstBackgroundNode.position = CGPointMake(centerX,
firstBackgroundNode.size.height*firstBackgroundNode.anchorPoint.y);
[self addChild:firstBackgroundNode];
float previousYPosition = firstBackgroundNode.position.y;
for (int i = 0; i < 2; i++) {
SKSpriteNode *backgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
backgroundNode.position = CGPointMake(centerX,
previousYPosition + backgroundNode.frame.size.height);
previousYPosition = backgroundNode.position.y;
backgroundNode.name = #"background";
[self addChild:backgroundNode];
}
}
- (void)update:(CFTimeInterval)currentTime {
// Updating background nodes
// We don't want to update backgrounds each frame (60 times per second)
// Once per second is enough. This will reduce CPU usage
if (currentTime - self.lastTimeSceneRefreshed > 1) {
[self backgroundNodesRepositioning];
self.lastTimeSceneRefreshed = currentTime;
}
}
- (void)backgroundNodesRepositioning {
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode *backgroundNode = (SKSpriteNode *)node;
if (backgroundNode.position.y + backgroundNode.size.height < 0) {
// The node is out of screen, move it up
backgroundNode.position = CGPointMake(backgroundNode.position.x, backgroundNode.position.y + backgroundNode.size.height * 3);
}
}];
}
- (void)startScrolling {
SKAction *moveAction = [SKAction moveByX:0 y:-200 duration:1];
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
[node runAction:[SKAction repeatActionForever:moveAction] withKey:#"movement"];
}];
}
I don't fully understand your approach here, but I can answer your question so I'll just go ahead and do that - I hope I haven't misunderstood.
SKNode has a method insertChild:atIndex: that would allow you to put the background nodes at the back, or put the sprite at the front.
SKNode Class Reference
I'm working on a game that is in portrait mode and can't seem to get this background to scroll smoothly on the y-axis. It doesn't properly add a second background so there is always a gap between the 2 backgrounds.
Heres the code:
static const float BG_VELOCITY = 100.0;
static inline CGPoint CGPointAdd(const CGPoint a, const CGPoint b)
{
return CGPointMake(a.x + b.x, a.y + b.y);
}
static inline CGPoint CGPointMultiplyScalar(const CGPoint a, const CGFloat b)
{
return CGPointMake(a.x * b, a.y * b);
}
Methods:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++) {
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg"];
bg.zRotation = M_PI_2;
bg.position = CGPointMake(320, self.frame.origin.y);
bg.anchorPoint = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
}
}
- (void)moveBg
{
[self enumerateChildNodesWithName:#"bg" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(0, -BG_VELOCITY);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled of the screen, if yes then put it at the end of the other node
if (bg.position.y <= -bg.size.height)
{
bg.position = CGPointMake(bg.position.x,
bg.position.y + bg.size.height*2);
}
}];
}
What am I doing wrong that it has a gap in between the two backgrounds. Note: Background size is 568 x 320 if that is necessary info.
Thanks.
There a few problems. The biggest is the fact that your image is made in landscape mode. Simply edit the image in photoshop or preview to rotate it 90 degrees, then take out the zRotation. This will work for your height dilemma. Then change bg.position = CGPointMake(320,... etc to bg.position = CGPointMake (0, i * bg.size.height);
Also change your if statement to:
if (bg.position.y <= -bg.size.height)
{
bg.position = CGPointMake(0, bg.position.y + bg.size.height*2);
}
And your code will work.
While moving the character it must stay inside a rectangle centred on the screen and all other game objects must scroll.
But as you get to the edge of the world it must repeat.
The characters can move in any direction.
You have to divide the world in 3 sections. Section 1 and 3 must be identical. If you reach the end of the world (Section 3) you can switch back to section 1.
http://www.youtube.com/watch?v=-FX-tFks5pg
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_background = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
_background.anchorPoint = CGPointMake(0, 0);
_background.name = #"background";
_background.position = CGPointMake(0, 0);
[self addChild:_background];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (_lastUpdateTime) {
_deltaTime = currentTime - _lastUpdateTime;
} else {
_deltaTime = 0;
}
_lastUpdateTime = currentTime;
if (_deltaTime > 1) {
_deltaTime = 1.0 / 60.0;
}
[self enumerateChildNodesWithName:#"background" usingBlock:^(SKNode *node, BOOL *stop) {
node.position = CGPointMake(node.position.x - backgroundMoveSpeed * _deltaTime, node.position.y);
if (node.position.x < - (node.frame.size.width + 100)) {
[node removeFromParent];
}
}];
if (_background.position.x < -bound) {
//bound = 500
SKSpriteNode *temp = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
temp.anchorPoint = CGPointMake(0, 0);
temp.name = #"background";
temp.position = CGPointMake(_background.position.x + _background.frame.size.width, 0);
[self addChild:temp];
_background = temp;
}
the background image's size is 2048x640, so you should change the bound according to your background image's size.
This is my code to make my background repeat, just one background and it should be repeating forever:
SKTexture* bgTexture = [SKTexture textureWithImageNamed:#"nightbackground"];
bgTexture.filteringMode = SKTextureFilteringNearest;
SKAction* movebgSprite = [SKAction moveByX:-bgTexture.size.width*2 y:0 duration:0.1 * bgTexture.size.width*2];
SKAction* resetbgSprite = [SKAction moveByX:bgTexture.size.width*2 y:0 duration:0];
SKAction* movebgSpritesForever = [SKAction repeatActionForever:[SKAction sequence:#[movebgSprite, resetbgSprite]]];
for( int i = 0; i < 2 + self.frame.size.width / ( bgTexture.size.width * 2 ); ++i ) {
SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithTexture:bgTexture];
[sprite setScale:1.0];
sprite.zPosition = -20;
sprite.position = CGPointMake(self.size.width/2, self.size.height/2);
[sprite runAction:movebgSpritesForever];
[self addChild:sprite];
But it does not repeat forever, it stops after a while. What might be wrong with it?
This case will work in landscape orientation and have the view repeat scrolling infinitely to the left (-x direction). You create two instances of the background SKSPriteNode and place them next to each other. When the first one scrolls completely off the left side of the screen, it is placed to the right of the second background sprite node.
in didMoveToView:
for (int i = 0; i < 2; i++) {
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
bg.anchorPoint = CGPointZero;
bg.position = CGPointMake(i * bg.size.width, 0);
bg.name = #"starBackground";
[self addChild:bg];
}
in the update: loop
[self enumerateChildNodesWithName:#"background" usingBlock:^(SKNode *node, BOOL *stop) {
SKSpriteNode *bg = (SKSpriteNode *)node;
bg.position = CGPointMake(bg.position.x - 1.5, bg.position.y);
if (bg.position.x <= -bg.size.width) {
bg.position = CGPointMake(bg.position.x + bg.size.width * 2, bg.position.y);
}
}];