How to arc object with CGPathRef - ios

In my iOS game, I am trying to move an object along a path (see below):
I want to move the triangle above around to the bottom of the box.
But I don't know how to use CGPathRefs very well. Here is what I am trying:
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, 20, 0, 20, 0, M_PI/2, NO);
SKAction *moveTriangle = [SKAction followPath:path asOffset:YES orientToPath:YES duration:1.0];
[self.triangle runAction:moveTriangle];
And I keep getting odd results. Can anybody help me adjust my path to match the image above? Thanks!

Using prototypical's idea
// will arc around this sprite for demo
SKSpriteNode *boxOrange = [SKSpriteNode spriteNodeWithColor:[UIColor orangeColor] size:CGSizeMake(30, 30)];
boxOrange.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:boxOrange];
SKSpriteNode *heroSprite = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(70, 70)];
CGFloat offeset = 150; // change as required, ie bring hero closer to rotn point
heroSprite.position = CGPointMake(-offeset, heroSprite.position.y);
SKSpriteNode *guideContainer = [SKSpriteNode spriteNodeWithColor:[SKColor purpleColor] size:CGSizeMake(3, 3)];
// for full effect - change guide container to clearColor
// otherwise good for seeing where rotation point will occur
[guideContainer addChild:heroSprite];
[self addChild:guideContainer];
guideContainer.position = boxOrange.position;
[guideContainer runAction:[SKAction rotateToAngle:1.57 duration:3.0 shortestUnitArc:YES]];
// just found shortestUnitArc action .. this will be handy for at times

Related

Fixed floor in Sprite Kit Xcode

I can't seem to get a unmovable fixed floor / wall in Sprite Kit.
I have a scene which has an SKNode as a "floor" and when the user presses the screen, a block drops from the touched position onto the "floor". That works well, but when the user adds another lets say 20 blocks or so, suddenly the floor rotates.
This is the code for my Floor:
SKSpriteNode *floorNode = [[SKSpriteNode alloc] initWithColor:[SKColor clearColor] size:CGSizeMake(self.size.width*2, 10)];
floorNode.position = CGPointMake(CGRectGetMidX(self.frame), -5);
floorNode.name = #"floor";
floorNode.zPosition = 1;
floorNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:floorNode.size];
floorNode.physicsBody.affectedByGravity = NO;
floorNode.physicsBody.dynamic = NO;
floorNode.physicsBody.usesPreciseCollisionDetection = YES;
return floorNode;
Sorry i'm quite new to Sprite Kit. Does anyone know what's wrong with my code?
EDIT: I've added some images to make the problem a bit more clear. Since it happens in this screen also. Those black spheres you see are flying from left to right like a canonball. I increased the amount of spheres to show the problem occurring. This is the code:
- (SKNode *)newEditButton {
SKLabelNode *editButtonNode = [SKLabelNode labelNodeWithFontNamed:#"chalkduster"];
editButtonNode.text = #"EDIT CASTLE";
editButtonNode.fontSize = 19;
editButtonNode.fontColor = [SKColor darkGrayColor];
editButtonNode.position = CGPointMake(CGRectGetMidX(self.frame) + 110, CGRectGetMidY(self.frame) + 30);
editButtonNode.name = #"edit_button";
editButtonNode.zPosition = 2;
editButtonNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(140, 40)];
editButtonNode.physicsBody.usesPreciseCollisionDetection = YES;
editButtonNode.physicsBody.dynamic = NO;
editButtonNode.physicsBody.affectedByGravity = NO;
SKAction *hover = [SKAction sequence:#[[SKAction moveByX:0 y:6.0 duration:0.9],
[SKAction moveByX:0 y:-6.0 duration:0.9]]];
SKAction *hoverForever = [SKAction repeatActionForever:hover];
[editButtonNode runAction:hoverForever];
return editButtonNode;
}
- (SKNode *)newPlayButton {
SKLabelNode *playButtonNode = [SKLabelNode labelNodeWithFontNamed:#"chalkduster"];
playButtonNode.text = #"SLAUGHTER";
playButtonNode.fontSize = 19;
playButtonNode.fontColor = [SKColor darkGrayColor];
playButtonNode.position = CGPointMake(CGRectGetMidX(self.frame) - 110, CGRectGetMidY(self.frame) + 30);
playButtonNode.name = #"play_button";
playButtonNode.zPosition = 2;
playButtonNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(140, 40)];
playButtonNode.physicsBody.usesPreciseCollisionDetection = YES;
playButtonNode.physicsBody.dynamic = NO;
playButtonNode.physicsBody.affectedByGravity = NO;
SKAction *hover = [SKAction sequence:#[[SKAction moveByX:0 y:6.0 duration:0.9],
[SKAction moveByX:0 y:-6.0 duration:0.9]]];
SKAction *hoverForever = [SKAction repeatActionForever:hover];
[playButtonNode runAction:hoverForever];
return playButtonNode;
}
After a while 5-10 seconds, the "buttons" called "slaughter" and "edit castle" start to rotate everytime a sphere collides with it. But it doesn't seem like it has to do something with physics, because, if it rotates to the left, it's rotation direction will always be left. And if it starts rotating right, it's rotation direction will always be right.
It looks funny though! But not what i want ;-)
You don't know it, but you're telling SpriteKit to apply collisions from the cannonballs to the labels, and from the blocks to the floor!
That's because you're leaving SKPhysicsBody.collisionBitMask to its default value, which is to observe collisions for everything.
From the documentation:
Discussion
When two physics bodies contact each other, a collision may occur. This body’s collision mask is compared to the other body’s category mask by performing a logical AND operation. If the result is a nonzero value, this body is affected by the collision. Each body independently chooses whether it wants to be affected by the other body. For example, you might use this to avoid collision calculations that would make negligible changes to a body’s velocity.
The default value is 0xFFFFFFFF (all bits set).
So, do:
floorNode.physicsBody.collisionBitMask = 0

How do I attach a node with another node?

I am having trouble trying to create platforms alongside a invisible line that is in the middle, I want it to have an SKPhysicsBody so that when passed through by the Hero Character, it increases the score, yet I can't seem to imagine the code the would work, specifically how to create the line with it being in the middle of the platform at all times, especially when the platform decides to move. Any tip would be greatly helpful.
|
|
________|_________
| | |
| | |
|________|_________|
This is one way to implement what you described.
- (void) didMoveToView:(SKView *)view
{
// Show outline of physics bodies for debugging purposes.
self.view.showsPhysics = YES;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKSpriteNode *platform = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(128, 64)];
platform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:platform.size];
platform.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:platform];
SKSpriteNode *line = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(2, 128)];
// Line is invisible
line.hidden = YES;
// physicsBody of line
line.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:line.size];
// Position line...adjust this accordingly
line.position = CGPointMake(platform.position.x, platform.position.y+line.size.height/4);
[self addChild:line];
// Connect nodes with physics joint
[self connectNode1:platform toNode2:line];
[platform.physicsBody applyImpulse:CGVectorMake(100.0, 0)];
}
This method connects two sprite nodes, at their midpoint, with a physics joint.
- (void) connectNode1:(SKSpriteNode *)node1 toNode2:(SKSpriteNode *)node2
{
CGPoint midPoint = CGPointMake((node1.position.x + node2.position.x)/2,
(node1.position.y + node2.position.y)/2);
SKPhysicsJointFixed *joint = [SKPhysicsJointFixed jointWithBodyA:node1.physicsBody
bodyB:node2.physicsBody
anchor:midPoint];
[self.physicsWorld addJoint:joint];
}
Make the line a child of the object using the - (void)addChild:(SKNode *)node as referenced in the SKNode Class Reference.
If you do not want a child/parent relationship you can tie the line to the object like this:
line.position = CGPointMake(object.position.x+10, object.position.y+10)
where +10 is any value you need it to be. If the 'object' moves you will have to update the line's position during the update: method to keep track with the object's position.

Disable change of a spritenode

I'm creating a game where i'm creating an SKSpriteNode at the bottom. Cause i want to make a buttom higher than the actually bottom cause of grass as the background. The problem is after I've created the physics body and my moving SKSpriteNode makes contact with the bottom. the bottom SpriteNode that i've created will move.
How can i make the bottom sprite node which has the function as being the bottom edge, not move at all. By that i mean that it cant change position.
My code:
SKSpriteNode *bottom = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(self.frame.size.width*2, 98)];
bottom.physicsBody.dynamic = NO;
bottom.position = CGPointMake(0, 0);
[self addChild:bottom];
bottom.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bottom.size];
You have to set the dynamic property of the physics body to NO. You are doing it before you actually create a physics body. That's why it does not have any effect. Change your code to:
SKSpriteNode *bottom = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor]
size:CGSizeMake(self.frame.size.width*2, 98)];
bottom.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bottom.size];
bottom.physicsBody.dynamic = NO;
bottom.position = CGPointMake(0, 0);
[self addChild:bottom];
And it should work.

dynamic cone shape changes depending on obstacles - reference to Third Eye Crime app

I've seen the Trailer of "Third Eye Crime".
How can you realise the blue field of view cone so that it's shape changes depending on obstacles?
My attempt was to cast rays till an obstacle occurs and then I take the end points of the rays to draw the cone shape.
The problem with my method is that the precision of the cone depends on the number of rays. Besides the more rays are casted the worse the performance.
Here you can see the rays:
Here you can see the cone shape drawn with the end points of the rays:
Are there better solutions?
Your question has been bugging me all day but I can't find a fully working answer. My work notes are too much for me to write it as a comment so I am adding it as an answer.
My first issue was to test for a contact between a field of view line and any obstruction object. Unfortunately SpriteKit only has the contactBitMask which can do this job AND provide contact coordinates.
I looked at SKNode's intersectsNode: but its return value is only a BOOL and we need coordinates.
I also looked at Apple's Sprite Kit Programming Guide's section Searching for Physics Bodies. This deals with line of sight, obstacles, etc... The command used here is bodyAlongRayStart:end:. The return is the first physics body it intersects with but without providing the coordinates for the actual contact point.
The code I ended up with first draws the complete line of sight cone. Next any lines found contacting an object got the contact point using contact.contactPoint and then deleted the offending line. So far so good. But I ran into trouble trying to draw the deleted line to the new end point (contact point). For some inexplicable reason only some of the deleted lines are being redrawn and not all.
Keep in mind this is rough code so slap it, pull it and throw it against the wall a couple of times. You don't really need the array for example. I hope this will steer you in the right direction or you can spot something I am too blind to see.
Side note: I ran this in simulator iPhone (4 inch) landscape.
#import "MyScene.h"
typedef NS_OPTIONS(uint32_t, CNPhysicsCategory)
{
Category1 = 1 << 0,
Category2 = 1 << 1,
Category3 = 1 << 2,
};
#interface MyScene()<SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKSpriteNode *player;
SKSpriteNode *obstacle1;
SKSpriteNode *obstacle2;
SKSpriteNode *obstacle3;
SKSpriteNode *obstacle4;
NSMutableArray *beamArray;
int beamCounter;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.physicsWorld.contactDelegate = self;
beamArray = [[NSMutableArray alloc] init];
beamCounter = 0;
player = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(20, 20)];
player.position = CGPointMake(100, 150);
[self addChild:player];
obstacle1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(200, 20)];
obstacle1.name = #"obstacle1";
obstacle1.position = CGPointMake(250, 100);
obstacle1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle1.size];
obstacle1.physicsBody.affectedByGravity = NO;
obstacle1.physicsBody.categoryBitMask = Category2;
obstacle1.physicsBody.collisionBitMask = 0x00000000;
[self addChild:obstacle1];
obstacle2 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(40, 40)];
obstacle2.name = #"obstacle2";
obstacle2.position = CGPointMake(400, 200);
obstacle2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle2.size];
obstacle2.physicsBody.affectedByGravity = NO;
obstacle2.physicsBody.categoryBitMask = Category2;
obstacle2.physicsBody.collisionBitMask = 0x00000000;
[self addChild:obstacle2];
obstacle3 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(50, 20)];
obstacle3.name = #"obstacle3";
obstacle3.position = CGPointMake(530, 130);
obstacle3.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle3.size];
obstacle3.physicsBody.affectedByGravity = NO;
obstacle3.physicsBody.categoryBitMask = Category2;
obstacle3.physicsBody.collisionBitMask = 0x00000000;
[self addChild:obstacle3];
for (int y = 0; y <= 320; y++)
{
SKShapeNode *beam1 = [SKShapeNode node];
beam1.name = [NSString stringWithFormat:#"%i",beamCounter++];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, player.position.x, player.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, 600, y);
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor yellowColor]];
[beam1 setLineWidth:2.0];
beam1.physicsBody = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointMake(player.position.x, player.position.y) toPoint:CGPointMake(600, y)];
beam1.physicsBody.affectedByGravity = NO;
beam1.physicsBody.categoryBitMask = Category1;
beam1.physicsBody.contactTestBitMask = Category2;
[self addChild:beam1];
[beamArray addObject:beam1];
}
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"bodyA:%# bodyB:%#",contact.bodyA.node.name, contact.bodyB.node.name);
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (Category1 | Category2))
{
CGPoint newEndPoint = contact.contactPoint;
[beamArray removeObject:contact.bodyA.node.name];
[contact.bodyA.node removeFromParent];
SKShapeNode *beam1 = [SKShapeNode node];
beam1.name = [NSString stringWithFormat:#"%i",beamCounter++];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, player.position.x, player.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, newEndPoint.x, newEndPoint.y);
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor greenColor]];
[beam1 setLineWidth:2.0];
[self addChild:beam1];
}
}
#end
Removing the touching lines:
Removing the touching lines and replacing the removed lines:
How I would do it is send out a ray at the edges of view and make note of the points that they hit. Then find points that are important(corners of objects for example), form rays with those points and use those rays to find collisions past those points and finally fill triangles with the points and the "source" of the light.
I might get a chance to make pictures to help demonstrate my algorithm later but this is what I have now.
I think the efficient way to do this is to use Bresenham's Algorithm. There are lots of references and example code in the Wikipedia references section.
http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
Here I've found several answers:
https://code.google.com/p/straightedge/
http://www.redblobgames.com/articles/visibility/

Sprite Kit pin joints appear to have an incorrect anchor

I'm testing out pin joints with Sprite Kit, and I'm finding something unusual happening.
My desired setup is this: one wide, flat box, and two circles; the circles are connected via SKPhysicsPinJoints to the box, so they can act as wheels.
Here's my code. I've tried to make it as concise as possible:
- (SKNode*) createWheelWithRadius:(float)wheelRadius {
CGRect wheelRect = CGRectMake(-wheelRadius, -wheelRadius, wheelRadius*2, wheelRadius*2);
SKShapeNode* wheelNode = [[SKShapeNode alloc] init];
wheelNode.path = [UIBezierPath bezierPathWithOvalInRect:wheelRect].CGPath;
wheelNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:wheelRadius];
return wheelNode;
}
- (void) createCar {
// Create the car
SKSpriteNode* carNode = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:CGSizeMake(150, 50)];
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:carNode];
// Create the left wheel
SKNode* leftWheelNode = [self createWheelWithRadius:30];
leftWheelNode.position = CGPointMake(carNode.position.x-80, carNode.position.y);
[self addChild:leftWheelNode];
// Create the right wheel
SKNode* rightWheelNode = [self createWheelWithRadius:30];
rightWheelNode.position = CGPointMake(carNode.position.x+80, carNode.position.y);
[self addChild:rightWheelNode];
// Attach the wheels to the body
CGPoint leftWheelPosition = leftWheelNode.position;
CGPoint rightWheelPosition = rightWheelNode.position;
SKPhysicsJointPin* leftPinJoint = [SKPhysicsJointPin jointWithBodyA:carNode.physicsBody bodyB:leftWheelNode.physicsBody anchor:leftWheelPosition];
SKPhysicsJointPin* rightPinJoint = [SKPhysicsJointPin jointWithBodyA:carNode.physicsBody bodyB:rightWheelNode.physicsBody anchor:rightWheelPosition];
[self.physicsWorld addJoint:leftPinJoint];
[self.physicsWorld addJoint:rightPinJoint];
}
What I'm expecting is that the pin joints are anchored at their centre points; however, when I test this, the anchors for the joints appear to be far off.
Am I missing something really obvious?
I also had this issue and the cause is setting the physics body before setting the sprites position.
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
Change the above to
carNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
carNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carNode.size];
It should work. Thanks Smick.
SpriteKit: How to create Basic Physics Joints
Try this code, I used yours and got weird issues, so started from scratch.
- (SKShapeNode*) makeWheel
{
SKShapeNode *wheel = [[SKShapeNode alloc] init];
CGMutablePathRef myPath = CGPathCreateMutable();
CGPathAddArc(myPath, NULL, 0,0, 16, 0, M_PI*2, YES);
wheel.path = myPath;
return wheel;
}
- (void) createCar
{
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0, 0, self.size.width, self.size.height)];
// 1. car body
SKSpriteNode *carBody = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(120, 8)];
carBody.position = CGPointMake(200, 200);
carBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:carBody.size];
[self addChild:carBody];
// 2. wheels
SKShapeNode *leftWheel = [self makeWheel];
leftWheel.position = CGPointMake(carBody.position.x - carBody.size.width / 2, carBody.position.y);
leftWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
[self addChild:leftWheel];
SKShapeNode *rightWheel = [self makeWheel];
rightWheel.position = CGPointMake(carBody.position.x + carBody.size.width / 2, carBody.position.y);
rightWheel.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
[self addChild:rightWheel];
// 3. Join wheels to car
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:carBody.physicsBody bodyB:leftWheel.physicsBody anchor:leftWheel.position]];
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:carBody.physicsBody bodyB:rightWheel.physicsBody anchor:rightWheel.position]];
// 4. drive car
[carBody.physicsBody applyForce:CGVectorMake(10, 0)];
}

Resources