SKNodes with SKPhysicsBody under SKAction move inaccuracy - objective-c

I have two SKNode with physics (one under another):
- (SKNode*) newNode {
SKShapeNode *node = [SKShapeNode shapeNodeWithCircleOfRadius:60];
node.strokeColor = [SKColor clearColor];
node.fillColor = [SKColor greenColor];
node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:60];
node.physicsBody.categoryBitMask = 1;
node.physicsBody.collisionBitMask = 1;
node.physicsBody.contactTestBitMask = 1;
return node;
}
- (void)createSceneContents {
SKNode *n1 = [self newNode];
n1.position = CGPointMake(100,100);
[self addChild:n1];
SKNode *n2 = [self newNode];
n2.position = CGPointMake(100,300);
[self addChild:n2];
...
}
Their goal is to reach a predetermined position directly opposite them at some distance.
To achieve this, I use SKAction (this is important and can not be changed):
[n1 runAction:[SKAction moveTo:CGPointMake(800,100) duration:3]];
[n2 runAction:[SKAction moveTo:CGPointMake(800,300) duration:3]];
In addition, on the first node's movement path I place a third node - exactly the same.
- (void)createSceneContents {
...
SKNode *obstacle = [self newNode];
obstacle = CGPointMake(500,300);
[self addChild:obstacle];
}
The third node must be shifted in the direction of first node's movement by the mechanism of physics.
Click to illustration of the Scene
(the red circles marks destination position of nodes)
Then I run the app, everything goes as is conceived, except for one thing - a first node (with obstacle on the path) was not reach the target location, just a few pixels:
Click to see result
The question is simple: why? And how can I fix it?
Thank you!

Related

contactTestBitMask fails to detect contact?

I am detecting contacts from a ball and two edges of the screen (left and right one)
For that purpose I created:
– SKSpriteNode *ball
– SKNode *leftEdge
– SKNode *rightEdge
Here I am setting up the bit masks
static const uint32_t kCCBallCategory = 0x1 << 0;
static const uint32_t kCCEdgeCategory = 0x1 << 1;
Here I am adding the edges
leftEdge.physicsBody = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointZero toPoint:CGPointMake(0.0, self.size.height)];
leftEdge.position = CGPointZero;
leftEdge.physicsBody.categoryBitMask = kCCEdgeCategory;
[self addChild:leftEdge];
Right edge is added exactly the same, except it has different name and position is set to
rightEdge.position = CGPointMake(self.size.width, 0.0);
Here I am configuring and adding the ball
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:6.0];
ball.physicsBody.categoryBitMask = kCCBallCategory;
ball.physicsBody.collisionBitMask = kCCEdgeCategory;
ball.physicsBody.contactTestBitMask = kCCEdgeCategory;
[_mainLayer addChild:ball];
Later in didBeginContact I am checking if first body is the ball and the second body is the edge and adding some explosion to it
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (firstBody.categoryBitMask == kCCBallCategory && secondBody.categoryBitMask == kCCEdgeCategory) {
// Collision between ball and the edge
[self addExplosion:contact.contactPoint withName:#"BallEdgeBounce"];
}
And the strange thing is – when the ball hits the left edge - code works fine and explosion effect added to the scene. But the right edge wont do the same. It came by surprise for me, because collisions works just fine. So why does the right edge behave like that? How do I fix this?
You could look at the project on the github, settings is done in GameScene.m file https://github.com/Fenkins/Space-Cannon
I've looked into your Git project and this is happening because you have wrongly set category for rightEdge ( to be more precise, you haven't set it at all). You should do something like this:
SKNode *rightEdge = [[SKNode alloc]init];
rightEdge.physicsBody = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointZero toPoint:CGPointMake(0.0, self.size.height)];
rightEdge.position = CGPointMake(self.size.width, 0.0);
// leftEdge.physicsBody.categoryBitMask = kCCEdgeCategory;//Error when copy/pasting ;-)
rightEdge.physicsBody.categoryBitMask = kCCEdgeCategory;
[self addChild:rightEdge];
You might have missed a simple practice carried out in the didBeginContact: method, of assigning the first and second bodies based on their categoryBitMask values.
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
...
}
This needs to be done because there is no guarantee which body will be the first or the second.
This is a different direction than where you seem to be going with your app, but it works.
SKSpriteNode *edgeLeft = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(1, self.frame.size.height)];
edgeLeft.physicsBody.collisionBitMask = kCCEdgeCategory;
edgeLeft.position = CGPointMake(3, self.frame.size.height / 2);
[self addChild:edgeLeft];
Then do the same for the top edge.
Hope this helped.

Why does this code not properly center scene when doing UIPanGestureRecognizer?

I am a beginner developing a program for the AppStore using Xcode's sprite kit. I made a 'test' program so I can try out new things before adding it to my game. Right now I am fiddling around with swiping a scene - I have an SKNode (called "background") where I am adding several children as SKSpriteNodes. One sprite node is visible on the initial scene (the center, position 160,240), and two more that are not visible: to the left of the scene (position -160,240), and to the right of the scene (position 480,240).
I would like my game to be able to swipe left or right, and when it swipes left or right, the view will auto-center itself (with animation) to one of the three SKSpriteNodes. My code using the UIPanGestureRecognizer to move the background node works properly, and my code for auto-centering the view works MOSTLY (background position set to 0,0 or -320,0 or +320,0), but sometimes it has a strange offset and doesn't completely center itself (for example, the background position will be 7,0 or -34,0 when I pan right or left). What am I doing wrong?
P.S: I am using code from RayWenderlich's "iOS Games" for the SKTMoveEffect. I also want to note that if I make the function f(t)=t, there is no problem (at least in my several tests), but f(t)=t^2 or anything else seems to have an issue; if it helps to see the code for this I can post it too
#implementation LTMyScene
{
SKNode *background;
SKSpriteNode *spaceship1, *spaceship2;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
background=[SKNode node];
[self addChild:background];
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 30;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[background addChild:myLabel];
spaceship1=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship1.position=CGPointMake(-self.size.width/2, self.size.height/2);
spaceship1.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship1];
spaceship2=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship2.position=CGPointMake(self.size.width*3/2, self.size.height/2);
spaceship2.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship2];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
UIPanGestureRecognizer *swipe = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragPlayer:)];
[[self view] addGestureRecognizer:swipe];
}
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
[[[self view] layer] removeAllAnimations];
CGPoint trans = [gesture translationInView:self.view];
SKAction *moveAction = [SKAction moveByX:trans.x y:0 duration:0];
[background runAction:moveAction];
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
if([gesture state] == UIGestureRecognizerStateEnded) {
CGFloat finalX=0;
if (abs(background.position.x)<self.size.width/2) {
finalX=0;
} else if (abs(background.position.x)<self.size.width*3/2) {
finalX=self.size.width*background.position.x/abs(background.position.x);
}
NSLog(#"%f",finalX);
SKTMoveEffect *upEffect =
[SKTMoveEffect effectWithNode:background duration:0.5
startPosition:background.position
endPosition:CGPointMake(finalX, 0)];
upEffect.timingFunction = ^(float t) {
// return powf(2.0f, -3.0f * t) * fabsf(cosf(t * M_PI * 1.0f)) //has bounce
// return (-1.0f*t*t+1) //no bounce ... for parabola this is only solution with (1,0) and (0,1) as intercepts and vertex at (1,0)
return (t*t)
;};
SKAction *upAction = [SKAction actionWithEffect:upEffect];
[background runAction:upAction];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
NSLog(#"%f,%f",background.position.x,background.position.y);
}
#end
You have to remember that you are moving the larger background node with other nodes as its children. Keeping that in mind, the code you need to center on a specific node is:
_worldNode.position = CGPointMake(-(myNode.position.x-(self.size.width/2)), -(myNode.position.y-(self.size.height/2)));
The above assumes that your main background "canvas" is called _worldNode and your target node is a child of _worldNode

SpriteKit - Rotate sprite towards point

I am working on a game where a bunch of enemies spawn outside the screen and move towards the center (where there's a space station). However, I need the spaceships to face in the direction of the center. Currently, I'm using this code:
- (void)rotateNode:(SKNode *)nodeA toFaceNode:(SKNode *)nodeB {
double angle = atan2(nodeB.position.y - nodeA.position.y, nodeB.position.x - nodeA.position.x);
if (nodeA.zRotation < 0) {
nodeA.zRotation = nodeA.zRotation + M_PI * 2;
}
[nodeA runAction:[SKAction rotateToAngle:angle duration:0]];
}
but it only gives this result:
How should I get the sprites to rotate correctly?
All suggestions are appreciated!
EDIT:
I am calling the code above in the init method for the Enemy sprite
-(id)initWithLevel:(int)level andSize:(CGSize)size{
if(self = [super initWithImageNamed:#"alien01"]){
//position and init stuff
[self setPosition:self withSeed:seed andSize:size];
SKNode *center = [[SKNode alloc] init];
center.position = CGPointMake(self.size.width/2, self.size.height/2);
[self rotateNode:self toFaceNode:center];
}
return self;
}
Your problem lies here:
SKNode *center = [[SKNode alloc] init];
center.position = CGPointMake(self.size.width/2, self.size.height/2);
[self rotateNode:self toFaceNode:center];
The center node's position is based on the size of the enemy sprite.
You need to move this part to the scene class. Call the method at the point you initialise the enemy node in the scene. I am assuming the name of the enemy class as EnemySprite and that of the space station node as spaceStation.
EnemySprite *enemy = [[EnemySprite alloc] initWithLevel:1 andSize:CGSizeMake(100, 100)]; //Assuming values as well
[self addChild:enemy];
[self rotateNode:enemy toFaceNode:spaceStation];
This should make it work.

Detecting collision between objects in array in sprite kit

i'm trying to make a game where obstacles fall from the top of the screen and the player has to try to avoid them, but the didBeginContact method is not getting called and its driving me crazy. here are the parts of my code relevant to the node collisions...
//myScene.h
#interface MyScene : SKScene <SKPhysicsContactDelegate>
//myScene.m
#implementation MyScene
static const uint32_t playerCategory = 0x1 << 0;
static const uint32_t obstacleCategory = 0x1 << 1;
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
//setup scene
self.backgroundColor = [SKColor whiteColor];
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsWorld.contactDelegate = self;
obstacles = [NSMutableArray array];
//add player node
player = [SKNode node];
SKSpriteNode *spritePlayer = [SKSpriteNode spriteNodeWithImageNamed:#"black.png"];
spritePlayer.size = CGSizeMake(50, 50);
spritePlayer.position = CGPointMake(self.frame.size.width/2, 100);
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.frame.size];
player.physicsBody.dynamic = NO;
player.physicsBody.mass = 0.02;
[player addChild:spritePlayer];
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.contactTestBitMask = obstacleCategory;
player.physicsBody.collisionBitMask = 0;
player.physicsBody.usesPreciseCollisionDetection = YES;
[self addChild:player];
[self spawnNewObstacles];
}
return self;
}
- (void)spawnNewObstacles
{
//spawn obstacles
obstacle = [SKSpriteNode spriteNodeWithImageNamed:#"black.png"];
obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.frame.size];
obstacle.physicsBody.dynamic = YES;
obstacle.physicsBody.categoryBitMask = obstacleCategory;
obstacle.physicsBody.contactTestBitMask = playerCategory;
obstacle.physicsBody.collisionBitMask = 0;
obstacle.physicsBody.usesPreciseCollisionDetection = YES;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"didBeginContact Called!");
}
the didBeginContact method is not getting called and i can't find what wrong with my code
all help is appreciated...
sknode size isn't set correctly because there is no image reference, and if you log its size if should be inf, and when I tested your code I returned an exception.
to fix this error change the following line
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.frame.size]; change that to
to the following and it should work
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spritePlayer.size];
as I said below just ditch the player sknode, it is simply redundant unless there is a strong reason to use it.
///////previous answer
I couldn't help but to notice that you didn't add the obstacle node to any parent node, namely the scene, like what you did with the player node using this line
[self addChild:player];
you need to do the same thing with the obstacle node
[self addChild:obstacle];
, I am surprised that you didn't mention not seeing those nodes on the screen, unless you added them in a different section of your code that you did not include, then at this point I suggest you log the positions of both nodes to give an idea of how things are located on the screen.
as for the array you won't need it all you have to do is give a name for every obstacle node like #"obstacle", then you can use the following method to access all the nodes that has that name
obstacle.name = #"obstacle";
/////later when you want to check for node removal
[self enumerateChildNodesWithName:#"obstacle" usingBlock:^(SKNode *node, BOOL *stop) {
SKSpriteNode *child = (SKSpriteNode*)node;
if (child.position.y <0){ //or any arbitrary condition
[child removeFromParent];
}
}];
Lastly, I can't see the point from using the player as a sknode and then adding an skspritenode to it, unless I am missing something !!! :) . You can simply ditch the player sknode all together and simply use the spritePlayer node instead.
Hope this helps.

How to z-rotate two SKSpriteNodes together as if one?

How can I group two SKSpriteNodes together so that I can z-rotate them as if they are one? Let's say one SKSpriteNode is a rope and the other is a ball attached to the end of the rope. How can I make it look like they swing together? What would be the SKAction to do this?
Two options:
Put them both in a SKNode and rotate the SKNode (around a certain anchor point)
SKNode *container = [[SKNode alloc] init];
[container addChild:ballSprite];
[container addChild:ropeSprite];
container.zRotation = someValue;
Or rotate them separately and them move them so it looks as if they were rotated together.
Probably not the most elegant solution but here is how i have added SKSpriteNodes to a SKNode (container). containerArray contains the nodes that should be added. The newSpriteName (NSString) is used when deciding if the sprite is displayed with it's front side or back side.
// Add a container
_containerNode = [SKNode node];
_containerNode.position = _theParentNodePosition;
_containerNode.name = #"containerNode";
[_background addChild:_containerNode];
for (SKNode *aNode in containerArray) {
if (![aNode.name isEqualToString:#"background"] && ![aNode.name isEqualToString:#"title1"] && ![aNode.name isEqualToString:#"title2"]) {
// Check if "back" sprite should be added or the front face
if ([[aNode.name substringFromIndex:[aNode.name length] - 1] isEqualToString:#"b"]) {
newSpriteName = #"back";
} else {
newSpriteName = aNode.name;
}
// Prepare the new node
SKSpriteNode *newNode = [SKSpriteNode spriteNodeWithImageNamed:newSpriteName];
newNode.name = aNode.name;
newNode.zPosition = aNode.zPosition;
newNode.position = CGPointMake(aNode.position.x - _theParentNodePosition.x, aNode.position.y - _theParentNodePosition.y);
// Delete the old node
SKNode *deleteNode = [_background childNodeWithName:aNode.name];
[deleteNode removeFromParent];
// Add the new node
[_containerNode addChild:newNode];
}
}
And then do a rotation as DrummerB suggested