Really need SKSpriteNode children to shoot correctly - ios

So I have a shipNode (which is initially facing right) with two children called _laserCannon1(and 2) that are configured by the method below:
-(void)initializeLaserCannonLocations
{
CGSize size = CGSizeMake(4.0, 4.0);
_laserCannon1 = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:size];
_laserCannon2 = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:size];
_laserCannon1.position = CGPointMake(self.size.width/2, self.size.height/2 + 15);
_laserCannon2.position = CGPointMake(self.size.width/2, self.size.height/2 - 15);
[self addChild:_laserCannon1];
[self addChild:_laserCannon2];
}
Then I am later "firing" lasers from them based off of the ship's zRotation as follows
-(void)fireLocalLaser
{
CGVector standardLaserVelocity = CGVectorMake(700*cosf(_myShip.zRotation - _myShip.laserCannon1.zRotation),
700*sinf(_myShip.zRotation - _myShip.laserCannon2.zRotation));
[self spawnLaserFrom:CGPointAdd(_myShip.laserCannon1.position, ship.position); withVelocity:standardLaserVelocity fromShip:_myShip];
[self spawnLaserFrom:CGPointAdd(_myShip.laserCannon2.position, ship.position); withVelocity:standardLaserVelocity fromShip:_myShip];
}
Now everything works fine when the ships is facing directly left or right, but for some odd reason as the ship begins to face more towards the top or bottom the shots are fired increasingly towards the middle of the ship, to the point that once the ship is facing vertically both shots are fired from the middle.position.x of the ship. Does it have to do with something from where I placed my _laserCannons?

I hope my math teacher doesn't see this!
Your problem is with the relative position of your 2 laser cannons as they rotate. I have included a picture so I don't have to go into a long winded description of what is happening.
I have included code for a kinda, sorta, duct tape solution on how you can do this if you really want to. It ain't pretty but it works. The code is good from 0 to 90 degrees so you should be able to work out the rest from there if you have your heart set on having 2 cannons. BUT I would strongly suggest your Mighty Space Empire invest in single cannon ships, as it will make your coding job a whole lot easier!
#implementation MyScene {
SKSpriteNode *ship1;
SKShapeNode *turret1;
SKShapeNode *turret2;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
[self createSpaceships];
}
return self;
}
-(void)createSpaceships {
ship1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(50, 50)];
ship1.position = CGPointMake(300, 150);
ship1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship1.size];
ship1.physicsBody.dynamic = NO;
[self addChild:ship1];
SKSpriteNode *frontOfShip = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(2, 50)];
frontOfShip.position = CGPointMake(25, 0);
[ship1 addChild:frontOfShip];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
// touch the left side of the screen to rotate the ship by +0.0785398 radians
// touch the right hand side of the screen to fire lasers
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
if(touchLocation.x < self.size.width/2) // left side of screen
{
SKAction *block0 = [SKAction runBlock:^{
ship1.zRotation = ship1.zRotation +0.0785398;
}];
[self runAction:block0];
} else // right side of screen
{
float turret1offsetX = 0;
float turret1offsetY = 0;
float turret2offsetX = 0;
float turret2offsetY = 0;
if((ship1.zRotation >0) && (ship1.zRotation <=0.785399))
{
turret1offsetX = (14/0.785398) * ship1.zRotation;
turret1offsetY = (23/0.785398) * ship1.zRotation;
turret2offsetX = (-20/0.785398) * ship1.zRotation;
turret2offsetY = (10/0.785398) * ship1.zRotation;
}
if((ship1.zRotation >0.785399) && (ship1.zRotation <=1.570797))
{
turret1offsetX = (14 - (9/0.785398)) * ship1.zRotation;
turret1offsetY = -4 + (27/0.785398) * ship1.zRotation;
turret2offsetX = (3 - (25/0.785398)) * ship1.zRotation;
turret2offsetY = 20 - (10/0.785398) * ship1.zRotation;
}
int x = ship1.position.x + 1000 * cos(ship1.zRotation);
int y = ship1.position.y + 1000 * sin(ship1.zRotation);
turret1 = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, (ship1.position.x+20)+turret1offsetX, (ship1.position.y-25)+turret1offsetY);
CGPathAddLineToPoint(pathToDraw, NULL, x, y);
turret1.path = pathToDraw;
[turret1 setStrokeColor:[UIColor redColor]];
[self addChild:turret1];
turret2 = [SKShapeNode node];
CGMutablePathRef pathToDraw2 = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw2, NULL, (ship1.position.x+20)+turret2offsetX, (ship1.position.y+25)+turret2offsetY);
CGPathAddLineToPoint(pathToDraw2, NULL, x, y);
turret2.path = pathToDraw2;
[turret2 setStrokeColor:[UIColor redColor]];
[self addChild:turret2];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[turret1 removeFromParent];
[turret2 removeFromParent];
}
#end

Yes as sangony put it, it really just has to do with your relative positioning of the _laserCannons from your shipNode. I would personally just start with a singular laserCannon for now, and get more comfortable with the math involved until you feel ready to move on to adding more.

Related

How to jump through a physic body (like a platform) and land on it (SpriteKit)

I am trying to create a game in Objective-C like Mario / Sonic.
For now it works well but i still can't figure it out how I can make my hero from jumping though a physic body when he stands below it but can stand on top.
I was trying to create the physics body when the heros position is higher then the platform and to disable it again.
The answer to this question is actually not too difficult but the implementation into a full fledged game will be much more difficult for you. This is not something for a novice programmer to start out with.
First the same code project (tap/click on screen to jump up):
#import "GameScene.h"
typedef NS_OPTIONS(uint32_t, Level1PhysicsCategory) {
CategoryPlayer = 1 << 0,
CategoryFloor0 = 1 << 1,
CategoryFloor1 = 1 << 2,
};
#implementation GameScene {
int playerFloorLevel;
SKSpriteNode *node0;
SKSpriteNode *node1;
SKSpriteNode *node2;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor whiteColor];
node0 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(400, 10)];
node0.position = CGPointMake(300, 200);
node0.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node0.size];
node0.physicsBody.dynamic = NO;
node0.physicsBody.categoryBitMask = CategoryFloor0;
node0.physicsBody.collisionBitMask = CategoryPlayer;
[self addChild:node0];
node1 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(400, 10)];
node1.position = CGPointMake(300, 300);
node1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node1.size];
node1.physicsBody.dynamic = NO;
node1.physicsBody.categoryBitMask = CategoryFloor1;
node1.physicsBody.collisionBitMask = CategoryPlayer;
[self addChild:node1];
node2 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(50, 50)];
node2.position = CGPointMake(300, 250);
node2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node2.size];
node2.physicsBody.categoryBitMask = CategoryPlayer;
node2.physicsBody.collisionBitMask = CategoryFloor0;
[self addChild:node2];
playerFloorLevel = 0;
}
-(void)update:(CFTimeInterval)currentTime {
if(((node2.position.y-25) > (node1.position.y+10)) && (playerFloorLevel == 0)) {
node2.physicsBody.collisionBitMask = CategoryFloor0 | CategoryFloor1;
playerFloorLevel = 1;
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
//SKNode *node = [self nodeAtPoint:touchLocation];
// change 75 value to 50 to see player jump half way up through floor 1
[node2.physicsBody applyImpulse:CGVectorMake(0, 75)];
}
}
The gist of the code is the player node (node2) has to keep checking its y position (update method) in relation to the other floors. In the example, the player jumps up through floor1. Once the player is higher than floor1, the player node's physics body modifies its collision bit mask to include floor1.
Sounds easy enough. However, in a real game you will have a large number of floors and all floors might not be evenly spaced y distances. You have to keep all that in mind when coding.

Can't seem to figure what's wrong here

I've used this same exact code before, and it worked perfect every time. However, I'm trying to use it to move the paddle sprite along a horizontal path at position 100 for y and it updates with the location of my touch but for some reason it's not moving at all. I can't spot what's wrong. Can someone take a look and let me know?
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
CGPoint newPosition = CGPointMake(location.x, 100);
//stop the paddle from going too far
if (newPosition.x < self.paddle.size.width/ 2) {
newPosition.x = self.paddle.size.width/ 2;
}
if (newPosition.x > self.size.width - (self.paddle.size.width/ 2)) {
newPosition.x = self.size.width - (self.paddle.size.width/ 2);
}
self.paddle.position = newPosition;
}
}
-(void) addPlayer:(CGSize)size {
SKSpriteNode *paddle = [SKSpriteNode spriteNodeWithImageNamed:#"paddle"];
//resize sprite
paddle.size = CGSizeMake(125, 31.25);
//position sprite
paddle.position = CGPointMake(size.width/2, 100);
//add physics body to paddle
paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
//change to static so wont be moved by physics
paddle.physicsBody.dynamic = NO;
//add sprite to scene
[self addChild:paddle];
}
-(instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]){
self.backgroundColor = [SKColor colorWithRed:(29.0f/255) green:(29.0f/255) blue:(29.0f/255) alpha:1.0];
//change gravity
self.physicsWorld.gravity = CGVectorMake(0, 0);
//add physics body to scene
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self addPlayer:size];
[self addBricks:size];
[self addBall:size];
}
return self;
}

Moving a camera in SpriteKit

//UPDATE
The updated code has been added that works as I expected. See didSimulatePhysics method in the updated code below. In my case, I only care about moving a character left or right on the x axis where 0 on the x axis is the absolute left and right on the x axis is a configurable value. The Apple adventure game really helped a lot too.
//ORIGINAL POST BELOW
I'm working with Apple SpriteKit and I'm struggling to implement a camera as I would like it to behave. What I've done in the code is load a sprite character, two buttons, and a red box that is off to the right outside of the view at the start. What I'd like to be able to do is move the character with the buttons, and once the player reaches the middle or end of the screen, the camera will then re-adjust to uncover what couldn't be seen in the view. So moving to the right should eventually show the red box that is off outside of the view initially once the player gets there. However, with the code I'm using below, I'm unable to get the camera to follow and adjust the coordinates to the main character at all. I've looked at Apple's advanced scene processing doc as well as a few other stack overflow posts but can't seem to get it right. If anyone could offer some advice it would be appreciated.
#define cameraEdge 150
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
/* Setup your scene here */
//320 568
self.backgroundColor = [SKColor whiteColor];
myWorld = [[SKNode alloc] init];
[self addChild:myWorld];
mainCharacter = [SKSpriteNode spriteNodeWithImageNamed:#"0"];
mainCharacter.physicsBody.dynamic = YES;
mainCharacter.name = #"player";
mainCharacter.position = CGPointMake(20, 20);
CGRect totalScreenSize = CGRectMake(0, 0, 800, 320);
SKSpriteNode *box = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(60, 60)];
SKSpriteNode *boxTwo = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(60, 60)];
SKSpriteNode *boxThree = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(60, 60)];
boxThree.position = CGPointMake(40, 50);
[myWorld addChild:boxThree];
boxTwo.position = CGPointMake(1100, 50);
box.position = CGPointMake(650, 50);
[myWorld addChild:box];
[myWorld addChild:boxTwo];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:totalScreenSize];
self.physicsWorld.gravity = CGVectorMake(0, -5);
mainCharacter.name = #"mainCharacter";
mainCharacter.physicsBody.linearDamping = 0;
mainCharacter.physicsBody.friction = 0;
mainCharacter.physicsBody.restitution = 0;
mainCharacter.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:mainCharacter.size];
[myWorld addChild:mainCharacter];
[self addChild:[self buildLeftButton]];
[self addChild:[self buildRightButton]];
}
return self;
}
- (void)didSimulatePhysics
{
SKSpriteNode *hero = mainCharacter;
if(hero)
{
CGPoint heroPosition = hero.position;
CGPoint worldPosition = myWorld.position;
NSLog(#"%f", heroPosition.x);
CGFloat xCoordinate = worldPosition.x + heroPosition.x;
if(xCoordinate < cameraEdge && heroPosition.x > 0)
{
worldPosition.x = worldPosition.x - xCoordinate + cameraEdge;
self.worldMovedForUpdate = YES;
}
else if(xCoordinate > (self.frame.size.width - cameraEdge) && heroPosition.x < 2000)
{
worldPosition.x = worldPosition.x + (self.frame.size.width - xCoordinate) - cameraEdge;
self.worldMovedForUpdate = YES;
}
myWorld.position = worldPosition;
}
}
-(SKSpriteNode *)buildLeftButton
{
SKSpriteNode *leftButton = [SKSpriteNode spriteNodeWithImageNamed:#"left"];
leftButton.position = CGPointMake(20, 20);
leftButton.name = #"leftButton";
leftButton.zPosition = 1.0;
return leftButton;
}
-(SKSpriteNode *)buildRightButton
{
SKSpriteNode *leftButton = [SKSpriteNode spriteNodeWithImageNamed:#"right"];
leftButton.position = CGPointMake(60, 20);
leftButton.name = #"rightButton";
leftButton.zPosition = 1.0;
return leftButton;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if([node.name isEqualToString:#"leftButton"])
{
[mainCharacter.physicsBody applyImpulse:CGVectorMake(-120, 0)];
}
else if([node.name isEqualToString:#"rightButton"])
{
[mainCharacter.physicsBody applyImpulse:CGVectorMake(120, 10)];
}
}
If you want the view to always be centered on your player's position, modify your code with these points in mind:
1) Create a SKNode and call it myWorld, worldNode or any other name like that.
2) Add the worldNode [self addChild:worldNode];
3) Add all other nodes to the worldNode, including your player.
4) In the didSimulatePhysics method, add this code:
worldNode.position = CGPointMake(-(player.position.x-(self.size.width/2)), -(player.position.y-(self.size.height/2)));
Your view will now always be centered on your player's position.
Update May 2015:
If you are using a map created with Tiled Map Editor, you can use the free SKAToolKit framework. Features include player camera auto follow, test player, test HUD and sprite buttons.

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

Sprite Kit Joints

I am trying to make an animated character that can reach out for things on the screen, without moving his torso.
If he can not reach than his hand should go the maximal amount in this direction and then be counteracted by the physical limitation of his static torso.
So in my code the green rectangle (hand) moves where I click and the torso follows.
I would like to be able to make the red rect (body) stationary and make the green rectangle "point" at the touch point position.
I have tried to apply a fixed joint to between the scene and the red body rectangle, but this did not seem to work.
- (void) createSceneContent
{
SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.affectedByGravity = NO;
body.physicsBody.dynamic = YES;
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
self.hand.position = CGPointMake(body.position.x + body.size.width / 2 + self.hand.size.width / 2, body.position.y);
[self addChild:self.hand];
self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
self.hand.physicsBody.dynamic = NO;
self.scene.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x + body.size.width / 2, body.position.y)]];
[self.hand runAction:[SKAction moveByX:100 y:10 duration:0.1]];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
[self.hand runAction:[SKAction moveTo:location duration:0.1]];
}
First of all, you don't really need to use physics here, if you only want the arm to point at the touched location.
Just change the arm node's anchorPoint to the shoulder (where you would put the pin on the joint) and rotate it around that point (the point is held in a helper shoulderNode, so that it's easy to convert to its coordinates later). You can calculate the rotation angle using atan2f. Here's the code:
- (void) createSceneContent
{
SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
self.hand.position = CGPointMake(body.position.x + body.size.width*0.5, body.position.y);
self.hand.anchorPoint = CGPointMake(0, 0.5);
[self addChild:self.hand];
SKNode *shoulderNode = [SKNode node];
shoulderNode.position = self.hand.position;
shoulderNode.name = #"shoulderNode";
[self addChild:shoulderNode];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
CGPoint locationConv = [self convertPoint:location toNode:[self childNodeWithName:#"shoulderNode"]];
self.hand.zRotation = (atan2f(locationConv.y, locationConv.x));
}
On the other hand, if you ever need to use physics bodies here, it's probably a bad idea to use SKActions to move the arm, and you also might want to set body.physicsBody.dynamic = NO to make the torso static. Then, make sure you add the pin joint correctly, and set its frictionTorque property to a high value to get less dangling. One of the ways to make the hand point at the right location is its velocity vector set in the update method - just use the converted location coordinates (here they are relative to the body node's centre). Full example code:
- (void) createSceneContent {
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.dynamic = NO;
body.physicsBody.affectedByGravity = YES;
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(20, 150)];
// experiment with the anchorPoint for different results
self.hand.anchorPoint = CGPointMake(0.5, 0);
self.hand.position = CGPointMake(body.position.x, body.position.y);
[self addChild:self.hand];
self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
self.hand.physicsBody.dynamic = YES;
self.hand.physicsBody.affectedByGravity = NO;
SKPhysicsJointPin *pinHand = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x, body.position.y-5)];
[self.physicsWorld addJoint:pinHand];
// experiment with the friction torque value to achieve desired results
pinHand.frictionTorque = 1.0;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
location = [touch locationInNode:self];
}
-(void)update:(CFTimeInterval)currentTime {
CGPoint locationConv = [self convertPoint:location toNode:body];
// experiment with the multiplier (here: 10) for faster/slower movement
self.hand.physicsBody.velocity = CGVectorMake(locationConv.x*10, locationConv.y*10);
}
Hope that helps!

Resources