Make the Object to stick at the top of the screen SpriteKit - ios

How to stop the object that moves from the bottom of the screen to the top. And object should stick to top. Here is the code for that object:
SKSpriteNode* ball = [SKSpriteNode spriteNodeWithImageNamed: #"ball.png"];
ball.name = ballCategoryName;
ball.position = CGPointMake(self.frame.size.width/3, self.frame.size.height/3);
[self addChild:ball];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];
ball.physicsBody.friction = 1.0f;
ball.physicsBody.restitution = 1.0f;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = YES;
[ball.physicsBody applyImpulse:CGVectorMake(0.0f, -10.0f)];

- (void) checkPosition
{
if (ball.position.y > self.height.size) {
ball.position.y = ball.position.y - 1;
}
}
- (void)update:(NSTimeInterval)currentTime
{
[self checkPosition]; //update tests rough 60 times per sec.
}

Related

Sprite Kit player can still leave screen

I've been trying to prevent my sprite that is controlled by a UIPanGestureRecognizer from leaving the screen. I've tried creating this with this code but the player is still able to leave the screen.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = wallCategory;
player = [SKSpriteNode spriteNodeWithImageNamed:#"thePlayer.png"];
player.size = CGSizeMake(35, 35);
player.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
player.name = kPlayerName;
player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player.size.width/2];
player.physicsBody.dynamic = NO;
player.physicsBody.usesPreciseCollisionDetection = YES;
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.restitution = 0;
player.physicsBody.collisionBitMask = enemyCategory | wallCategory;
player.physicsBody.contactTestBitMask = enemyCategory | bonusCategory;
[self addChild:player];
}
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
if (!pan) {
pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(dragPlayer:)];
pan.minimumNumberOfTouches = 1;
pan.delegate = self;
[self.view addGestureRecognizer:pan];
}
}
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
CGPoint trans = [gesture translationInView:self.view];
SKAction *moveAction = [SKAction moveByX:trans.x y:-trans.y duration:0];
[player runAction:moveAction];
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
}
Any ideas?

Play/Pause buttons in a game

MY code:
-(SKSpriteNode*) pauseButton
{
SKSpriteNode* pauseButton = [SKSpriteNode spriteNodeWithImageNamed:#"pausebutton"];
pauseButton.position = CGPointMake(CGRectGetMaxX(self.frame) -30,CGRectGetMaxY(self.frame) - 30);
pauseButton.anchorPoint = CGPointMake(1.0, 1.0);
pauseButton.name = #"pauseButton";
pauseButton.zPosition = 160;
return pauseButton;
}
-(SKSpriteNode*) playButton
{
SKSpriteNode* playButton = [SKSpriteNode spriteNodeWithImageNamed:#"playbutton"];
playButton.position = CGPointMake(CGRectGetMaxX(self.frame)- 30, CGRectGetMaxY(self.frame) - 30);
playButton.anchorPoint = CGPointMake(1.0, 1.0);
playButton.name = #"playButton";
playButton.zPosition = -10;
return playButton;
}
In touchesBegan:
SKSpriteNode *pauseButton = (SKSpriteNode*)[self childNodeWithName:#"pauseButton"];
CGPoint location = [touch locationInNode:self];
if (!(gameIsOver))
{
if([pauseButton containsPoint:location])
{
if (self.scene.view.paused==YES)
{
[myTimer start];
pauseButton.zPosition = 160;
[self childNodeWithName:#"playButton"].zPosition = -10;
self.scene.view.paused = NO;
}
else if (self.scene.view.paused ==NO)
{
pauseButton.zPosition = -10;
[self childNodeWithName:#"playButton"].zPosition = 160;
[myTimer pause];
[self performSelector:#selector(pauseGame) withObject:Nil afterDelay:0.01];
}
}
-(void) pauseGame
{
self.scene.view.paused = YES;
}
My problems:
I do not want to use: [self performSelector:#selector(pauseGame) withObject:Nil afterDelay:0.01]; as its not accurate. I want to set self.scene.view.paused = YES; as soon as someone clicks the pause button. However, not setting a delay doesn't let the pause button change to play. Is there a way I can fix this.
Sometimes, my pause button doesn't change to play. I dont know why. This is rare though.
Any help would be appreciated.
Thanks

Spritekit drastic frame rate drop

I have tried my best to boil this question down as simple as possible. I have a coin object in my game:
#implementation
-(CollectableCoin*)initWithLocation:(CGPoint) Location andValue: (int) val
{
self = [super initWithImageNamed:#"coin"];
[self setScale:.35];
_value = val;
_collected = false;
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
self.physicsBody.categoryBitMask = APAColliderTypeCoin;
self.physicsBody.collisionBitMask = APAColliderTypeBall;
self.physicsBody.mass = 0.00009;
self.physicsBody.restitution = .35;
self.position = Location;
self.name = #"collectableCoin";
return self;
}
#end
I also have a shelf object:
#implementation Shelf
-(Shelf*)initWithLocation:(CGPoint) location andWidth:(NSInteger) width
{
self = [super initWithImageNamed:#"shelf"];
if(self)
{
self.size = CGSizeMake(width, HEIGHT);
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
self.physicsBody.dynamic = false;
self.physicsBody.restitution = 0;
self.position = location;
self.name = #"shelf";
SKSpriteNode* topOfShelf;
if(width > 5)
topOfShelf = [[SKSpriteNode alloc] initWithColor:[UIColor yellowColor] size:CGSizeMake(width-2, 1)];
else
topOfShelf = [[SKSpriteNode alloc] initWithColor:[UIColor yellowColor] size:CGSizeMake(width, 1)];
topOfShelf.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:topOfShelf.size];
topOfShelf.physicsBody.restitution = 1;
topOfShelf.physicsBody.dynamic = false;
topOfShelf.position = CGPointMake(0, location.y + self.size.height/2);
NSLog([NSString stringWithFormat:#"%f", location.y + self.size.height/2]);
NSLog([NSString stringWithFormat:#"%f", location.y]);
topOfShelf.name = #"shelf";
[self addChild:topOfShelf];
}
return self;
}
#end
I create a scene like so:
-(id)initWithSizeTest:(CGSize)size
{
self.physicsWorld.gravity = CGVectorMake(0, 0);
_gameState = READYTOSTART;
self.physicsWorld.contactDelegate = self;
if (self = [super initWithSize:size])
{
self.physicsWorld.gravity = CGVectorMake(0, 0);
for(int i = 0; i < 25; i++)
{
CollectableCoin* orb = [[CollectableCoin alloc] initWithLocation:CGPointMake(i*10, self.size.height*.75) andValue:1];
[self addChild:orb];
}
Shelf* shelf = [[Shelf alloc] initWithLocation:CGPointMake(self.size.width/2, self.size.height/2) andWidth:self.size.width];
[self addChild:shelf];
}
return self;
}
Here is the touchesBegan method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
if(_gameState == READYTOSTART)
{
self.physicsWorld.gravity = CGVectorMake(0, -2.0);
_gameState = PLAYING;
[[self childNodeWithName:#"taptostart"] removeFromParent];
}
When the scene starts, I have a row of coins hovering above a shelf, gravity is disabled, and I have a solid 60fps. When I tap the screen, the touchesbegan function enables gravity and the coins fall on the shelf, and the frame rate drops to 5fps. The didBeginContact function is not being called because the shelf object is not dynamic nor does it have contact or collision bitmasks, so I am fairly sure that it is not being overloaded by extraneous calls to didBeginContact. This happens on an iPad mini and an iPhone 4s, but not in any of the simulators. This is a very simple example of the actual problem that I am having. Anyone have any insights?

What will be the best approach implementing infinite/repeating scrolling world in SpriteKit?

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.

Prevent player from falling through the ground - Sprite Kit

I've been trying to work on a simple Sprite Kit game that involves dodging red balls. I'm using the built-in gravity mechanism, but I'm having trouble preventing the player from falling through the ground. I've looked up a solution (set ground.physicsBody.dynamic = NO), but the player still falls through. What exactly do I need to do?
Edit: The green and brown texture is the ground. Right now the player is set to not being dynamic, so it is 'flying'
Here is my code in the MyScene.m file:
//
// MyScene.m
// DodgeMan
//
// Created by Cormac Chester on 3/8/14.
// Copyright (c) 2014 Testman Industries. All rights reserved.
//
#import "MyScene.h"
#import "EndGameScene.h"
static const uint32_t redBallCategory = 0x1 << 0;
static const uint32_t playerCategory = 0x1 << 1;
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
/* Setup your scene here */
//Sets player location
playerLocX = 50;
playerLocY = 100;
//Sets player score
score = 0;
//Set Background
self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];
//Set Ground
SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:#"ground"];
ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);
ground.xScale = 0.5;
ground.yScale = 0.5;
ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
ground.physicsBody.dynamic = NO;
//Player
self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:#"character"];
self.playerSprite.position = CGPointMake(playerLocX, playerLocY);
//Set Player Physics
self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];
self.playerSprite.physicsBody.dynamic = YES;
self.playerSprite.physicsBody.categoryBitMask = playerCategory;
self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;
self.playerSprite.physicsBody.collisionBitMask = 0;
self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;
//Score Label
self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial-BoldMT"];
self.scoreLabel.text = #"0";
self.scoreLabel.fontSize = 40;
self.scoreLabel.fontColor = [SKColor blackColor];
self.scoreLabel.position = CGPointMake(50, 260);
//Pause Button
self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:#"pauseButton"];
self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);
self.pauseButton.name = #"pauseButton";
//Add nodes
[self addChild:ground];
[self addChild:self.playerSprite];
[self addChild:self.scoreLabel];
//[self addChild:self.pauseButton];
//Sets gravity
self.physicsWorld.gravity = CGVectorMake(0,-2);
self.physicsWorld.contactDelegate = self;
}
return self;
}
-(void)addBall
{
SKSpriteNode *redBall = [SKSpriteNode spriteNodeWithImageNamed:#"locationIndicator"];
int minY = redBall.size.height / 2;
int maxY = self.frame.size.height - redBall.size.height / 2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
NSLog(#"Actual Y: %i", actualY);
//Initiates red ball offscreen
if (actualY >= 75)
{
//Prevents balls from spawning in the ground
redBall.position = CGPointMake(self.frame.size.width + redBall.size.width/2, actualY);
[self addChild:redBall];
}
redBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redBall.size.width/2];
redBall.physicsBody.dynamic = YES;
redBall.physicsBody.categoryBitMask = redBallCategory;
redBall.physicsBody.contactTestBitMask = playerCategory;
redBall.physicsBody.collisionBitMask = 0;
redBall.physicsBody.affectedByGravity = NO;
redBall.physicsBody.usesPreciseCollisionDetection = YES;
//Determine speed of red ball
int minDuration = 3.0;
int maxDuration = 5.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction *actionMove = [SKAction moveTo:CGPointMake(-redBall.size.width/2, actualY) duration:actualDuration];
SKAction *actionMoveDone = [SKAction removeFromParent];
SKAction *ballCross = [SKAction runBlock:^{
score++;
self.scoreString = [NSString stringWithFormat:#"%i", score];
self.scoreLabel.text = self.scoreString;
NSLog(#"Score was incremented. Score is now %d", score);
}];
[redBall runAction:[SKAction sequence:#[actionMove, ballCross, actionMoveDone]]];
}
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast
{
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > 0.5) {
self.lastSpawnTimeInterval = 0;
[self addBall];
}
}
-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
// Handle time delta.
//Prevents bad stuff happening
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
self.lastUpdateTimeInterval = currentTime;
if (timeSinceLast > 1) { // more than a second since last update
timeSinceLast = 1.0 / 120.0;
self.lastUpdateTimeInterval = currentTime;
}
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
NSDate *startTime;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
/* Called when a touch begins */
[super touchesBegan:touches withEvent:event];
//Starts Timer
startTime = [NSDate date];
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//Pauses Scene
if ([node.name isEqualToString:#"pauseButton"])
{
NSLog(#"Pause button pressed");
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
/* Called when a touch ends */
[super touchesEnded:touches withEvent:event];
NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow];
NSString *elapsedTimeString = [NSString stringWithFormat:#"Elapsed time: %f", elapsedTime];
NSLog(#"%#", elapsedTimeString);
for (UITouch *touch in touches)
{
//Gets location of touch
CGPoint location = [touch locationInNode:self];
NSLog(#"Touch Location X: %f \n Touch Location Y: %f", location.x, location.y);
//Prevents destination from being in the ground
if (location.y < 88)
{
location.y = 87.5;
}
//Moves and animates player
//int velocity = elapsedTime * -3000;
int velocity = 800.0/1.0;
NSLog(#"Velocity: %i", velocity);
float realMoveDuration = self.size.width / velocity;
SKAction *actionMove = [SKAction moveTo:location duration:realMoveDuration];
[self.playerSprite runAction:[SKAction sequence:#[actionMove]]];
}
NSLog(#"Touch ended");
}
//Collision between ball and player
- (void)redBall:(SKSpriteNode *)redBall didCollideWithPlayer:(SKSpriteNode *)playerSprite
{
NSLog(#"Player died");
[redBall removeFromParent];
[playerSprite removeFromParent];
SKTransition *reveal = [SKTransition crossFadeWithDuration:0.5];
SKScene *endGameScene = [[EndGameScene alloc] initWithSize:self.size gameEnded:YES];
[self.view presentScene:endGameScene transition: reveal];
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
//Red ball collides with the player
if ((firstBody.categoryBitMask & redBallCategory) != 0 && (secondBody.categoryBitMask & playerCategory) != 0)
{
[self redBall:(SKSpriteNode *) firstBody.node didCollideWithPlayer:(SKSpriteNode *) secondBody.node];
}
}
#end
you definitely can't set its dynamic to no brother. you need gravity effect that player. (he is not effected by physic world so he is flying right now. we need him to fall down to ground aren't we? :)
So here is the simple solution. idea is that you create a "invisible rectangle block" on the ground surface that has physic body. and you need to set its dynamic to no in order to prevent it falling down
so this block is a node obviously, and its size : as high as the ground , and as wide as the screen. and you need to adjust the position a little bit to put its upper bound right on the ground surface.
good luck
i actually drew a picture but i can't post it here because of my reputation :(
Your problem is with physicsBody's categoryBitMask and collisionTestBitMask.
Your bitwise declarations :
static const uint32_t redBallCategory = 0x1 << 0;
static const uint32_t playerCategory = 0x1 << 1;
This has actually set the following bit patterns (i've shortened to 8 bits for the example) :
redBallCategory - 00000001 and
playerCategory - 00000010
However in the following code, you tell the player to only collide with collision bit mask - 00000000;
self.playerSprite.physicsBody.collisionBitMask = 0;
So your first problem is here. The player will not collide with any category that you have defined.
Your second problem is you have not given the ground a categoryBitMask, or collisionBitMask. By default this means all bits are set, IE the ground's collisionBitMask is equal to 11111111;
There will be no collision between these two physics bodies.
Try this - i have simply added a third physics category, and edited your code slightly to set the ground categoryBitMask / collisionBitMask, and your player collisionBitMask.
static const uint32_t redBallCategory = 0x1 << 0;
static const uint32_t playerCategory = 0x1 << 1;
static const uint32_t groundCategory = 0x1 << 2;
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
/* Setup your scene here */
//Sets player location
playerLocX = 50;
playerLocY = 100;
//Sets player score
score = 0;
//Set Background
self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];
//Set Ground
SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:#"ground"];
ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);
ground.xScale = 0.5;
ground.yScale = 0.5;
ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
ground.physicsBody.categoryBitMask=groundCategory;
ground.physicsBody.collisionBitMask=playerCategory|redBallCategory;
ground.physicsBody.dynamic = NO;
//Player
self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:#"character"];
self.playerSprite.position = CGPointMake(playerLocX, playerLocY);
//Set Player Physics
self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];
self.playerSprite.physicsBody.dynamic = YES;
self.playerSprite.physicsBody.categoryBitMask = playerCategory;
self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;
self.playerSprite.physicsBody.collisionBitMask = groundCategory|redBallCategory;
self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;
//Score Label
self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial-BoldMT"];
self.scoreLabel.text = #"0";
self.scoreLabel.fontSize = 40;
self.scoreLabel.fontColor = [SKColor blackColor];
self.scoreLabel.position = CGPointMake(50, 260);
//Pause Button
self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:#"pauseButton"];
self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);
self.pauseButton.name = #"pauseButton";
//Add nodes
[self addChild:ground];
[self addChild:self.playerSprite];
[self addChild:self.scoreLabel];
//[self addChild:self.pauseButton];
//Sets gravity
self.physicsWorld.gravity = CGVectorMake(0,-2);
self.physicsWorld.contactDelegate = self;
}
return self;
}
Just:
self.playerSprite.physicsBody.dynamic = NO;
should work.
Your problem occurs due to scaling. For some reason in sprite kit scaling an image doesn't change it's size when used in following code. Judging by your pictures, your physics body rectangle for your ground is actually twice as big as you think and already engulfing the player, which is why there would by no collision detection. This is from recent experience with a very similar style game.
Do you have an edge loop physcis body around the scene? Collision flags and category flags set correctly so the player collides with the ground?
i had same problem and i soved it simply...
On the update method i've putted an if statement:
if(player.position.y<your_closest_value_near_ground){
player.position.y == your_Closest_value_near_ground
}
The comparition differs by the anchor point you have.. hope it helps someone

Resources