I'm having a little trouble implementing a background change when the player hits a certain score.
I'm using the if(self.score>=10) line to tell my game to change the background but it doesn't seem to be working. I have no errors with this line but no results either.
What I have:
#implementation Scene{
SKScrollingNode * floor;
SKScrollingNode * back;
SKLabelNode * scoreLabel;
}
- (void) createBackground
{
back = [SKScrollingNode scrollingNodeWithImageNamed:#"back" inContainerWidth:WIDTH(self)];
if(self.score>=10){
back = [SKScrollingNode scrollingNodeWithImageNamed:#"image2" inContainerWidth:WIDTH(self)];
[back setAnchorPoint:CGPointZero];
[back setPhysicsBody:[SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame]];
back.physicsBody.categoryBitMask = backBitMask;
back.physicsBody.contactTestBitMask = birdBitMask;
[self addChild:back];
}
there are certain points you have to keep in your mind i think your both background contains physics body
1)may be and the time of transition they first background colliding with second background and they preventing each other to swap
change your collisionBitMask for both background ->prevent them for colliding
for eg
bg.physicsBody.categoryBitMask=bg1;
bg.physicsBody.collisionBitMask=0;
bg.physicsBody.contactTestBitMask=bg2;
bg.physicsBody.categoryBitMask=bg2;
bg.physicsBody.collisionBitMask=0;
bg.physicsBody.contactTestBitMask=bg1;
or use skaction to swap backgrounds
The way i would do this would be to create a brand new SKScene entirely with a new background.
Or if you want them to scroll to the next background. I would create two backgrounds.
Ex.
//this syntax is wrong (dont remember)
//initializing
SKSpriteNode *bg1 = [SKSpritenode spritenodefromImage:#"bg1"];
SKSpriteNode *bg1 = [SKSpritenode spritenodefromImage:#"bg1"];
bg1.position = CGPointMake(self.size.width/2, self.size.height/2);
bg2.position = CGPointMake(self.size.width/2 + bg1.size.width, self.size.height/2);
//////////////////////////////
//then when you want to scroll them
-(void)scrollBackgrounds
{
bg1.position = CGPointMake(bg1.position.x - 5, self.size.height/2);
bg2.position = CGPointMake(bg2.position.x -5 , self.size.height/2);
}
Related
I develop an iOS game using SpriteKit (such a helpful framework to quickly make a game). I add texture and configure a physical body for a main character as image
The green rectangle is the frame of the physical body. I'm using the following code to create it
#interface MainCharacter : SKSpriteNode
#end
#implementation MainCharacter
+ (instancetype)mainCharacterAtPosition:(CGPoint)pos {
MainCharacter* mainChar = [[MainCharacter alloc] initWithTexture:[SKTexture textureWithImageNamed:#"stand_up"]];
mainChar.position = pos;
mainChar.xScale = 0.5f;
mainChar.yScale = 0.5f;
return mainChar;
}
- (instancetype)initWithTexture:(SKTexture *)texture {
if (self = [super initWithTexture:texture]) {
self.name = kCharacterName;
self.anchorPoint = CGPointMake(0.5f, 0.0f);
[self standup];
CGSize spriteSize = self.size;
CGPoint center = CGPointMake(spriteSize.width*(self.anchorPoint.x-0.5f), spriteSize.height*(0.5f-self.anchorPoint.y));
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize center:center];
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = kCharacterCategory;
self.physicsBody.contactTestBitMask = 0x0;
self.physicsBody.collisionBitMask = 0x0;
}
return self;
}
- (void)standup {
SKAction* standupAction = [SKAction setTexture:self.standupTexture resize:YES];
[self runAction:standupAction];
}
- (void)standdown {
SKAction* standownAction = [SKAction setTexture:self.standdownTexture resize:YES];
[self runAction:standownAction completion:^{
}];
[self performSelector:#selector(standup) withObject:nil afterDelay:1.0f];
}
MainCharacter is a class that inherits from SKSPriteNode, just an convienient class to manage a main character. Stand Up is a first state of the character. I have another state, temporarily called stand down (demonstrate as following image)
I add a swipe down gesture to make character stand down.
The green rectangle also the physical body but it's too large for the character. I want to make a physical body frame as the red rectangle.
Can anyone help me how to make the physical body smaller when my character stand down and enlarge the physical body after it stands up
You can destroy the current physics body self.physicsBody = nil; and then simply create a new one with the new size requirements.
I solve this problem by using 2 nodes for 2 states (as a suggestion): stand up state and stand down state. I named it
standupNode and standdownNode
First, add the standupNode to the game scene. If swipe donw gesture recognize, I remove the standupNode from game scene and add the standdownNode instead. On contrary, removing the standdownNode from the game scene then add the standupNode if character stands up
I have been working on a SpriteKit game for a while now and I have an annoying problem that I can't get rid of. I don't want to tell too much about the game, but it is very simple. I generate some objects (SKSpriteNodes) each second that are falling down from the top of the screen using SpriteKits physics and the player is interacting with them.
Most of the time the game runs perfectly (constant 60 FPS). The problem is that sometimes (maybe once per minute or something like that), the game starts to lag a little bit for about 3-5 seconds (still at 60 FPS) and then it runs perfectly again (note: i'm running the game on an iPhone 5s). It seems to be because of the physics, because if I add a normal move-action on an object, it runs very smoothly while the nodes affected by the physics are lagging.
I tried to remove some particles and effects that I have and I reuse my objects, but I can't remove the lag. I decided to create a very simple test project to see if the lag would be gone but it is still there. Here is the code:
#import "GameScene.h"
static const uint32_t groundCategory = 1 << 0;
static const uint32_t objectCategory = 1 << 1;
#implementation GameScene
-(void)didMoveToView:(SKView *)view
{
// Init
self.backgroundColor = [UIColor blackColor];
self.physicsWorld.gravity = CGVectorMake(0.0f, -3.2f);
self.physicsWorld.contactDelegate = self;
// Ground
SKPhysicsBody* ground = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointMake(0, -88) toPoint:CGPointMake(self.size.width, -88)];
ground.categoryBitMask = groundCategory;
self.physicsBody = ground;
// Start game loop
SKAction* waitAction = [SKAction waitForDuration:1];
SKAction* sequence = [SKAction sequence:#[[SKAction performSelector:#selector(addObject) onTarget:self], waitAction]];
SKAction* repeatAction = [SKAction repeatActionForever:sequence];
[self runAction:repeatAction withKey:#"fallingObjectsAction"];
}
- (void)addObject
{
SKSpriteNode* newObject = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(88, 88)];
newObject.name = #"Object";
newObject.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:newObject.size];
newObject.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height + newObject.size.height);
newObject.physicsBody.categoryBitMask = objectCategory;
newObject.physicsBody.contactTestBitMask = groundCategory;
newObject.physicsBody.collisionBitMask = 0;
[self addChild:newObject];
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString:#"Object"])
[contact.bodyA.node removeFromParent];
else if ([contact.bodyB.node.name isEqualToString:#"Object"])
[contact.bodyB.node removeFromParent];
}
#end
I generate one object each second and let it fall down from the top of the screen. When it hits the ground, it is removed.
Am I using the physics wrong or is it SpriteKit's fault that it is lagging? It seems strange, because I'm running a very simple project using an iPhone 5s with iOS 8.
Okay I've been having the same issue with physics. The issue was objects are jittery despite low CPU usage and a consistent FPS. I've finally figured out why. The solution I found is do not set physicsWorld.speed to 1.0; set it to a .9999. Now everything runs smoothly.
I hope this helps.
I am trying to reset the ball after it hit the goal to the middle of the screen, but it seems I can no longer set it's position anymore after i declare it's position during declaration.
This is how i define my ball:
-(void)setUpBall {
self.ball = [SKSpriteNode spriteNodeWithImageNamed:#"Ball"];
self.ball.name = bName;
self.ball.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
self.ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.ball.frame.size.width/2];
self.ball.physicsBody.restitution = 1.0;
self.ball.physicsBody.angularDamping = 0.0;
self.ball.physicsBody.linearDamping = 0.0;
self.ball.physicsBody.friction = 0.0;
self.ball.physicsBody.dynamic = true;
self.ball.physicsBody.allowsRotation = false;
self.ball.physicsBody.usesPreciseCollisionDetection = true;
self.ball.physicsBody.categoryBitMask = bCategory;
self.ball.physicsBody.collisionBitMask = pCategory | borderCategory;
self.ball.physicsBody.contactTestBitMask = borderCategory;
[self addChild:self.ball];
[self.ball.physicsBody applyImpulse:CGVectorMake(15, 10)];
}
This is my code to reset the ball to middle of the screen
-(void)reset {
NSLog(#"reset method ran");
[self.ball setPosition: CGPointMake(self.size.width/2, self.size.height/2)];
}
It seems that the reset method is definitely ran since there was output from the NSLog(#"reset method ran"); but the ball always continue moving like the setPosition never work at all
Doing things like setting position might not work in Sprite Kit if it's a result of a call from testing collisions or other things. You may need to add a BOOL property to your ball to flag it if you need to reset it, and then call the reset when you are updating all your sprites.
I have an asteroids type game where I was trying to replace one large asteroid with 3 smaller rocks when the collision detection against a player's missile fired – it did not work because the position was being set during the collision check part of the Sprite Kit update loop. When I added the new rocks during the standard part of the update loop, it worked fine.
Yopu could simply set up the ball again.
-(void)reset
{
NSLog(#"reset method ran");
[self.ball removeFromParent];
[self setUpBall];
}
Could you not replace your reset method with:
-(void)reset {
NSLog(#"reset method ran");
self.ball.position = CGPointMake(self.size.width/2, self.size.height/2)];
}
I tend to use this and not the setPosition method to relocate a sprite.
I am having a little trouble doing something that is supposed to be very simple.
I cannot get the floor of my tiles to display above a background picture. However I can get all my other game objects to show from my control pad, to my HUD to even coins and monsters set up in the same tile map. Basically everything appears in front the background like I expect the floor of my tilemap so it looks like im walking on air. I have tried many things like changing which layer i add the background picture or the tilemap floor too, or even tried setting it the same way i set my characters but same results. Tilemap floor is always at the back.
Adding my Set up code, Hope it is helpful too solving the problem.
I created this BG sprite since, I wanted my tilemap to scroll vertically or horzi. automatically. So the easiest way I found to do that was to make the tilemap the child of the "bg" and scroll the "bg" hence scrolling the tile map. However, I have tried setting the background as the child of the bg and setting the Z for both of them but that didnt seem to help.
Thanks in advance for any help in solving this
#implementation GameLevelScene
{
SKNode *_worldNode;
SKSpriteNode *bg;
SKSpriteNode *bkg;
}
Init
-(id)initWithSize:(CGSize)size level:(int)level {
if (self = [super initWithSize:size]) {
// [self showBackground];
NSDictionary *levelData = config[#"levels"][level];
//[show background];
if (levelData[#"tmxFile"]) {
[self showBackground];
_tileMap = [ JSTileMap mapNamed:levelData[#"tmxFile"]];
}
//self.backgroundColor = [SKColor colorWithRed:.4 green:.4 blue:.95 alpha:1.0];
// UIImage *bkgb =[UIImage imageNamed:#"land.jpg"];
// self.position=CGPointZero;
// self.anchorPoint=CGPointZero;
// self.backgroundColor=[UIColor colorWithPatternImage:bkgb];
//Above code shows no picture but it changes the color
[self setUpWorld];
[self createChar];
[self controlPadNode];
//[show background];
}
return self;
}
setUpWorld
- (void)setUpWorld
{
bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg3"];
bg.userInteractionEnabled=NO;
bg.anchorPoint = CGPointZero;
bg.zPosition=0;
bg.position = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
_worldNode = [SKNode node];
if (_tileMap) {
[bg addChild:_tileMap];
}
[bg addChild:_worldNode];
self.physicsWorld.contactDelegate = self;
}
create char
- (void)createChar
{
_Layer = [[TmxTileMapLayer alloc]
initWithTmxObjectGroup:[_tileMap
groupNamed:#"LevelOneObjects"]
tileSize:_tileMap.tileSize
gridSize:_bgLayer.gridSize];
[self addChild:_Layer];
}
Create Control
- (SKSpriteNode *)controlPadNode
//-(void)controlPad
{
SKSpriteNode *controlPadNode = [SKSpriteNode spriteNodeWithImageNamed:#"controller.png"];
controlPadNode.position = CGPointMake(100,50);
controlPadNode.name = #"controlPadNode";
controlPadNode.zPosition = 1.0;
[self addChild:controlPadNode];
}
background
-(void)showBackground
{
bkg = [SKSpriteNode spriteNodeWithImageNamed:#"desert_land.jpg"];
bkg.userInteractionEnabled=NO;
bkg.anchorPoint = CGPointZero;
bkg.position = CGPointZero;
bkg.zPosition=-1;
bkg.name = #"bkg";
// [self addChild:bkg];
//[_tileMap addChild:bkg];
// [_worldNode addChild:bkg];
[bg addChild:bkg];
}
Set your bkg.zposition to 1 and set your bg.zPosition to 2.
Also, the whole point of having a tile map is NOT to use a gigantic background picture but to instead use tiles.
** Update **
I just tested your code and ran a sample project myself. I assume you are using the Tiled app for your tiles. I ran variations on parents (self, worldNode, etc...) and zPositions. The bottom line is you cannot do it. Tiled does not have an alpha channel for its background color options. So either the background image is covered by the tile map or, in your case, the background image covers the tile map.
Your possible solutions are to either not use Tiled and place your tiles manually or to look for another tile app which has an alpha channel.
I noticed that where you define the wall properties of the tile if you say
SKSpriteNode* wall =
[SKSpriteNode spriteNodeWithColor:[SKColor greenColor]
size:CGSizeMake(w, h)];
the green color will be uptop of the background. Bit of a dirty fix than an answer
I am trying to create a game where a character runs forever to the right (the game is landscape). On the ground there are spikes that the character can jump over. Currently, I am creating a new (and somewhat random) set of spikes in almost a checkpoint-like style where once the character reaches a certain distance, the next set of randomly organized spikes are created and the checkpoint distance gets pushed back and so on. Along with the spikes, I have a separate but very similar checkpoint-like system that is used to create the tiles that make up the ground.
This is my code for that portion, 'endlessX' and 'endlessGroundX' are the checkpoint value:
- (void) didSimulatePhysics {
if (player.position.x > endlessX) {
int random = player.position.x + self.frame.size.width;
[self createSpike:random];
endlessX += self.frame.size.width/2.2 + arc4random_uniform(30);
}
if (player.position.x + self.frame.size.width > endlessGroundX) {
[self createGround:endlessGroundX];
endlessGroundX += tile1.frame.size.width;
}
[self centerOnNode: player];
}
The parameter of the createSpike and createGround method is just the 'x' value for the SKSpriteNodes.
I am currently having it as the character itself is the one moving and the spikes and tiles are stationary. This is how I am creating the character:
-(void) createPlayer {
player = [SKSpriteNode spriteNodeWithImageNamed:#"base"];
player.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
player.name = #"player";
player.zPosition = 60;
player.xScale = 0.8;
player.yScale = 0.8;
player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player.frame.size.height/2];
player.physicsBody.mass = 1;
player.physicsBody.linearDamping = 0.0;
player.physicsBody.angularDamping = 0.0;
player.physicsBody.friction = 0.0;
player.physicsBody.restitution = 0.0;
player.physicsBody.allowsRotation = NO;
player.physicsBody.dynamic = YES;
player.physicsBody.velocity = CGVectorMake(400, 0);
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = wallCategory;
player.physicsBody.contactTestBitMask = wallCategory | spikeCategory;
[myWorld addChild:player];
}
With that, the character will never lose any of its kinetic energy to friction or any other force like that. Then, I am using the 'center on node' method that apple used in their adventure game so that the character will always remain in the same x-position on the screen:
- (void) centerOnNode: (SKSpriteNode *) node {
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(self.frame.size.width/5 + node.parent.position.x - cameraPositionInScene.x, node.parent.position.y);
}
I am calling this method in 'didSimulatePhysics.'
When I run this for some time, the programs gets slower and slower. I am guessing that that is due to the fact that I am never removing these nodes and they are always being added. However, to fix this problem, I tried doing something like this:
-(void)update:(CFTimeInterval)currentTime {
[self enumerateChildNodesWithName:#"*" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x + 50 < player.position.x) {
[node removeFromParent];
}
}];
}
(the +50 would be just to make sure that the node is off the screen before removing it)
However, when I did this, instead of removing the specific node that satisfies the 'if' statement, the program removes all of the sprite nodes. Is there a different method or something that I am missing to fix this? Or are there any other simple ways to remove the specific nodes?
Lacking quite a few details, like how you are animating the spikes for instance, makes it a bit hard to be too specific. Nevertheless, from what you are sharing I guess you might be looking for something a little like this:
SKAction *moveSpikeAction = [SKAction moveToX:-50 duration:5];
SKAction *removeSpikeAction = [SKAction removeFromParent];
SKAction *spikeSequence = [SKAction sequence:#[moveSpikeAction, removeSpikeAction]];
[yourSpikeSpriteNode runAction:spikeSequence];
The idea simply being that when the spike has animated to the off screen position you use the removeFromParent action to clear it.