I clearly don't understand the SKPhysicsJoint very well, but there is so little info on the web yet, other than the Apple docs of course. What is wrong with the following code, which I would think should keep the head and neck permanently joined - my intention is that they act like 2 pieces of paper with a pin, so that they can rotate a bit, but not just totally come apart. When I run this code, they fall to the bottom of the SKScene they're in, hit the ground, then the head falls off the body.
Maybe the joint is not moving WITH them or something, it's just staying in place while they move??
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
}
[self addChild:self.head];
[self addChild:self.chest];
[self addChild:self.leftLeg];
self.chestJointPinAnchor = CGPointMake(self.chest.position.x, self.chest.position.y+39);
self.chestJointPin = [SKPhysicsJointPin jointWithBodyA:self.head.physicsBody bodyB:self.chest.physicsBody anchor:self.chestJointPinAnchor];
[self.physicsWorld addJoint:self.chestJointPin];
This is because you set sprite's position after you set up its physicsBody property.
I haven't discovered any mention of that in documentation, but I broke my head last weekend trying to figure out why my manually created rope works, but recursively one doesn't.
SpriteNodes' position MUST be set before physicsBody.
So, just reorder your code somehow like that:
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
}
Oh, I've noticed, you've already found an answer by yourself... Could you please mark your question as answered then.
That seems about right.
A pin joint allows both bodies to rotate around the joint's anchor point. A real world example is an analog clock. Or a bicycle's pedals. Or a car wheel's axle.
One thing you have to know is that bodies connected through a joint will not collide with each other. You can use the pin joint limits though to prevent the head from doing a full 360 spin around the pin joint anchor.
Okay, so I found out that this is an actual bug in Sprite Kit. The fix is to set the sprite's position before setting its physicsBody. I did that and it worked perfectly, as expected.
Additionally, not invalidating what was said above, but apparently the physics engine assumes the scene's anchorPoint is left at (0, 0). If you change it to something else, the physics engine will still treat it like (0, 0).
Related
So for the App I'm working on, I have two cubes colliding. I check for this in the standard way. The app tells me when they collide in my "didBeginContact" method.
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.bodyA.categoryBitMask == WALL_CATEGORY && contact.bodyB.categoryBitMask == CHARACTER_CATEGORY) {
CGPoint point = contact.contactPoint;
}
}
So i know where the collision takes place, but because it is two squares it can be at any point along the side including the corners. So how would I check if the collision on the left/right/top/bottom exclusively?
Edit: Correct Answer: Might not be the cleanest way to do it but it works. Hopefully it'll help someone in the future.
m_lNode = [SKNode node];
m_lNode.position = CGPointMake(-(CHARACTER_SIZE / 2), 0);
m_lNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(1, m_character.size.height)];
m_lNode.physicsBody.allowsRotation = NO;
m_lNode.physicsBody.usesPreciseCollisionDetection = YES;
m_lNode.physicsBody.categoryBitMask = CHARACTER_L_CATEGORY;
m_rNode = [SKNode node];
m_rNode.position = CGPointMake((CHARACTER_SIZE / 2), 0);
m_rNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(1, m_character.size.height)];
m_rNode.physicsBody.allowsRotation = NO;
m_rNode.physicsBody.usesPreciseCollisionDetection = YES;
m_rNode.physicsBody.categoryBitMask = CHARACTER_R_CATEGORY;
m_tNode = [SKNode node];
m_tNode.position = CGPointMake(0, (CHARACTER_SIZE / 2));
m_tNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(m_character.size.width , 1)];
m_tNode.physicsBody.allowsRotation = NO;
m_tNode.physicsBody.usesPreciseCollisionDetection = YES;
m_tNode.physicsBody.categoryBitMask = CHARACTER_T_CATEGORY;
m_bNode = [SKNode node];
m_bNode.position = CGPointMake(0, -(CHARACTER_SIZE / 2));
m_bNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(m_character.size.width, 1)];
m_bNode.physicsBody.allowsRotation = NO;
m_bNode.physicsBody.usesPreciseCollisionDetection = YES;
m_bNode.physicsBody.categoryBitMask = CHARACTER_B_CATEGORY;
[m_character addChild:m_tNode];
[m_character addChild:m_bNode];
[m_character addChild:m_lNode];
[m_character addChild:m_rNode];
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.bodyA.categoryBitMask == WALL_CATEGORY) {
switch (contact.bodyB.categoryBitMask) {
case CHARACTER_T_CATEGORY:
NSLog(#"Top");
m_isHitTop = true;
break;
case CHARACTER_B_CATEGORY:
NSLog(#"Bottom");
m_isHitBottom = true;
break;
case CHARACTER_L_CATEGORY:
NSLog(#"Left");
m_isHitLeft = true;
break;
case CHARACTER_R_CATEGORY:
NSLog(#"Right");
m_isHitRight = true;
break;
}
}
}
Added some relevant code. It's my code so there is variables amongst other things but you should be able to figure it out.
I think the best way to determine what side is involved in your contact (top,left,bottom,right side) is to:
calculate the points leaved by a centering cross for up, down, left and right sides (for example if you have a squared sprite..)
let halfWidth = self.frame.width/2
let halfHeight = self.frame.height/2
let down = CGPointMake(self.frame.origin.x+halfWidth,self.frame.origin.y)
let up = CGPointMake(self.frame.origin.x+halfWidth,self.frame.origin.y+self.frame.size.height)
let left = CGPointMake(self.frame.origin.x,self.frame.origin.y+halfHeight)
let right = CGPointMake(self.frame.origin.x+self.frame.size.width,self.frame.origin.y+halfHeight)
Calculate the distance between the contactPoint and each point sides (up, left , right and down)
This step can be possible with this little function:
func getDistance(p1:CGPoint,p2:CGPoint)->CGFloat {
let xDist = (p2.x - p1.x)
let yDist = (p2.y - p1.y)
return CGFloat(sqrt((xDist * xDist) + (yDist * yDist)))
}
After, the point involved with the lowest distance calculated is the side point nearest the contactPoint.
What are the advantages of this method:
You don't paint zombies or ghost nodes and relative physic bodies
This method work also inside a dynamic mutable CGPath, not only with a
knowed rectangular boundaries
Is fast, few lines of code and if you add other few lines you can be
able to determine also diagonals and make more precise your
algorithm.
the easiest way is to add child sprites the top, left, right, bottom of your squares. Add physics bodies to those, and then you can tell where things are colliding. I'd recommend trying this method first unless you have many many squares on the screen.
If you have a ton of squares on the screen and youre worried about performance. then maybe use contact.contactPoint and convert that point to square coordinates. given the center point of the square, the angle of the squares rotation and that point, you should be able to tell where the square collided. That would require some math.. and I was afraid writing up that kind of solution when the first one may be all that you really need.
I am trying to create a realistic bobblehead app, and want to use physics for the bobble animation. I have seen other apps out there that do it, and from what I gather I need to use SpriteKit.
I have created a SKScene and used a SKPhysicsJointPin to pin the head to the body, but it doesnt move around at all. Any ideas or suggestions?
UPDATED CODE (5/28):
Now using 2 spring joints on the head and it moves left to right, but not up and down. Also, tapping quickly causes the head to eventually go far enough right to "fall of" and out of view. Weird.
Have any ideas on what the proper setting would be to allow it to bobble up, down, left, and right whil staying remotely centered on it's starting position and staying within a specified region so it doesnt come off the body and look all funny?
BobbleheadView is a subclassed SKView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor =[UIColor clearColor];
self.showsFPS = YES;
self.showsNodeCount = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(animateBobble)];
[self addGestureRecognizer:tap];
SKScene *bobbleheadScene = [SKScene sceneWithSize:self.bounds.size];
[self presentScene:bobbleheadScene];
// 1. Body
self.body = [SKSpriteNode spriteNodeWithImageNamed:#"Bobble-Body"];
self.body.position = CGPointMake(self.frame.size.width/2, self.body.frame.size.height/2);
self.body.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[bobbleheadScene addChild:self.body];
// 2. Head
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"Bobble-Head"];
self.head.position = CGPointMake(self.center.x, self.body.frame.size.height);
//self.head.physicsBody.affectedByGravity = YES;
// self.head.physicsBody.dynamic = NO;
//This bobbles head great, but head falls off body and out of view
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size center:self.head.position];
//End
//self.head.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.head.frame];
//self.head.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.head.size.height/2];
[bobbleheadScene addChild:self.head];
// 3. Ceiling
self.ceiling = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(32, 32)];
self.ceiling.position = CGPointMake(self.frame.origin.x+self.frame.size.width/2, self.frame.size.height);
self.ceiling.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[bobbleheadScene addChild:self.ceiling];
//Spring Joint for Ceiling to Head
SKPhysicsJointSpring *spring1 = [SKPhysicsJointSpring jointWithBodyA:self.ceiling.physicsBody bodyB:self.head.physicsBody anchorA:self.ceiling.position anchorB:CGPointMake(self.head.frame.origin.x+self.head.frame.size.width/2, 0)];
spring1.frequency = 20.0; //gives the spring some elasticity.
spring1.damping = 5.0; //Will remove damping to create the 'pendulum'
[bobbleheadScene.physicsWorld addJoint:spring1];
//Spring Joint for Head to Body
SKPhysicsJointSpring *spring = [SKPhysicsJointSpring jointWithBodyA:self.body.physicsBody bodyB:self.head.physicsBody anchorA:CGPointMake(self.body.position.x+self.body.size.width/2, self.body.position.y) anchorB:CGPointMake(self.head.position.x+self.head.size.width/2, self.body.position.y-self.body.size.height/2)];
spring.frequency = 10.0; //gives the spring some elasticity.
spring.damping = 1.0;
[bobbleheadScene.physicsWorld addJoint:spring];
}
return self;
}
-(void)animateBobble{
NSLog(#"Did Tap Bobblehead!");
[self.head.physicsBody applyImpulse:CGVectorMake(100, -200)];
//[self.body.physicsBody applyImpulse:CGVectorMake(20, 10)];
}
The only way you'll get this to work is to make a "sled".
So: make an app where, on screen there are (say) six sliders.
TBC I mean, when the app is actually running, you'll see six sliders.
TBC, this is only a sled, it's only for you as a developer, it's not for the consumer app.
(I believe the term "sled" comes from the automotive industry; when they make the first version of a chassis/engine to test it out.)
Make it so that some sliders control spring power/damp (or whatever factors you have available in cocos) and that other sliders nudge the position of the connections/spring lengths.
it's the only way to get a result! Be sure to show the values on screen, or at least print them out on the console as they change.
This is the everyday thing in game development. Typically the sled is more work than the actual consumer scene or feature. You won't be able to achieve it without a "sled". I hope it helps!
I made a little prototype game and so far everything works fine, collision also. Now I want to optimize some stuff. I have creeps with different properties and I want to put them in an array. But I don't know how to check the objects in an array for collision because the command is expecting a SKnode I guess. Here I am defining the creeps and the array:
SKSpriteNokde *creep1 = [SKSpriteNode spriteNodeWithTexture:_creepTexture1];
[creep1 setScale:1];
creep1.position = CGPointMake( 10, y )
creep1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:creep1.frame.size];
creep1.physicsBody.dynamic = NO;
[CreepPair addChild:creep1];
SKSpriteNokde *creep2 = [SKSpriteNode spriteNodeWithTexture:_creepTexture2];
[creep2 setScale:2];
creep2.position = CGPointMake( 50, y + creep1.size.height);
creep2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:creep2.frame.size];
creep2.physicsBody.dynamic = NO;
[CreepPair addChild:creep2];
..
SKSpriteNode *Level1 = [NSArray arrayWithObjects:creep1,creep2,creep3,creep4,nil];
Here I am checking for collision and this works:
if ([creep1 intersectsNode:Player] {
creep1.hidden = YES;
NSLog(#"Lost 1 Life!");
}
But I want to check all creeps like this:
if ([Level1.allobjects intersectsNode:Player] {
creep1.hidden = YES;
NSLog(#"Lost 1 Life!");
}
The last code obviously don't work, but how can I manage this?
This might work
for (SKSpriteNokde *creep in Level1)
{
if ([creep intersectsNode:Player] {
creep.hidden = YES;
NSLog(#"Lost 1 Life!");
}
}
here I assume that Level1 is array.
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.
I am trying to programmatically build a pendulum using iOS SpriteKit using the built in physics.
Currently, I have the pendulum pivot, a weight, and a limited joint that allows the weight to swing... But, I have no idea on how to code a line (rod) between the pivot and weight.
I assume that drawing a line with SKShapeNode would be a start...?
-(void)setupPendulum
{
pivot = [SKSpriteNode spriteNodeWithImageNamed:#"pivot.png"];
pivot.position = CGPointMake(self.size.width / 2, self.size.height / 2);
pivot.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:1.0];
pivot.physicsBody.dynamic = NO;
pivot.physicsBody.affectedByGravity = NO;
pivot.xScale = 0.25;
pivot.yScale = 0.25;
[self addChild:pivot];
weight = [SKSpriteNode spriteNodeWithImageNamed:#"weight.png"];
weight.position = CGPointMake(150, 512);
weight.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:100.0];
weight.physicsBody.dynamic = YES;
weight.physicsBody.affectedByGravity = YES;
weight.physicsBody.mass = 0.5;
weight.xScale = 0.75;
weight.yScale = 0.75;
[self addChild:weight];
SKPhysicsBody *ab = pivot.physicsBody;
SKPhysicsBody *bb = weight.physicsBody;
CGPoint ap = pivot.position;
CGPoint bp = weight.position;
SKPhysicsJointLimit *joints = [SKPhysicsJointLimit jointWithBodyA:ab
bodyB:bb
anchorA:ap
anchorB:bp];
[self.physicsWorld addJoint:joints];
}
My project required the SKPhysicsJointLimit instead of the pin so after a lot of trial and error I found the following solution. The line is removed and drawn in didSimulatePhysics. The line connects the "satelliteShip" as it orbits around the "motherShip". I'm new to all this so I appreciate any feedback on this approach.
Setup variable first:
SKShapeNode *_lineNode;
Now draw the line in didSimulatePhysics:
- (void)didSimulatePhysics {
if (_lineNode){
[_lineNode removeFromParent];
}
SKNode *satelliteShip = [self childNodeWithName:kSatelliteShipName];
CGMutablePathRef pathToDraw;
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, motherShip.position.x, motherShip.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, satelliteShip.position.x, satelliteShip.position.y);
CGPathCloseSubpath(pathToDraw);
_lineNode = [SKShapeNode node];
_lineNode.path = pathToDraw;
CGPathRelease(pathToDraw);
_lineNode.strokeColor = [SKColor grayColor];
[self addChild:_lineNode];
}
The correct direction was to look at SKPhysicsJointPin, which can use a SKShapeNode or SKSpriteNode (etc.).
https://developer.apple.com/library/iOS/documentation/SpriteKit/Reference/SKPhysicsJointPin_Ref/Reference/Reference.html
Note About my question and the comments on it:
Since there isn't very much documentation (now) on SpriteKit beyond the (small book sized) reference by Apple and a grip of single-class-app examples/tutorials online -- most peoples responses are to, "just look it up + Cocos2d in google" and just re-do that. That's nice en all, but I'm not a copy-paste kinda of person. I like to learn the best way which is sustainable and reusable. This is why I asked what the best way to do it vs. posting busted Cocos2d ported copy-paste code and letting someone fill in the blanks like I see happen too often.