How do I attach a node with another node? - ios

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.

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.

iOS - SpriteKit : ApplyImpulse not working on SKSpriteNode

I'm kind of new with SpriteKit and I'm having some troubles with the physics, I've tried many things to make a SKSpriteNode move with a force or an impulse but it never moves, only gravity affects it when i set it.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.anchorPoint = CGPointMake(0.5, 0.5);
myWorld = [SKNode node];
[self addChild:myWorld];
SKSpriteNode* map = [SKSpriteNode spriteNodeWithImageNamed:#"grass"];
[myWorld addChild:map];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:map.frame];
self.physicsBody.friction = 0.0f;
self.physicsBody.categoryBitMask = 2;
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0);
self.physicsWorld.contactDelegate = self;
pointOfView = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithWhite:1 alpha:0.2] size:CGSizeMake(300, 548)];
[myWorld addChild:pointOfView];
pointOfView.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:pointOfView.frame.size];
pointOfView.physicsBody.friction = 0.0f;
pointOfView.physicsBody.restitution = 1.0f;
pointOfView.physicsBody.linearDamping = 0.0f;
pointOfView.physicsBody.allowsRotation = NO;
pointOfView.physicsBody.collisionBitMask = 2;
pointOfView.physicsBody.categoryBitMask = 3;
[pointOfView.physicsBody applyImpulse:CGVectorMake(10.0f, -10.0f)];
}
return self;
}
Here is most of my code in my main scene, there's nothing else except some empty methods like update or touchesBegan.. Am I missing something ? I've tried so many things, i'm starting to lose it haha
Thanks very much for any help !
It's a bit late, but the answer is here: Not able to apply impulse to SKSpriteNode physics body
The problem is that you've created a physics body with bodyWithEdgeLoopFromRect; this creates a static physics body which won't respond to applyImpulse. You need to add the physics body using bodyWithRectangleOfSize: instead.
pointOfView.physicsBody.dynamic = YES;
The dynamic property decides if a sprite should be affected by physics forces.
you have written wrong code your pointOfView not affected by gravity you are just applying a small impusle on it.
change this line self.physicsWorld.gravity = CGVectorMake(0.0, 0.0) to self.physicsWorld.gravity = CGVectorMake(0.0, -9.8); if u looking for negative gravity towards up direction and CGVectorMake(0.0, 9.8) if you looking for positive gravity towards downwards. and try to apply impulse after a interval [pointOfView.physicsBody applyImpulse:CGVectorMake(10.0f, -10.0f)];

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.

Make an elastic band type slingshot in spritekit

I'm working on a game where I have a line across the bottom of the screen that I use to launch things up into the air. It should behave like a rubber band or a slingshot. I have hacked together something that works, but it's kind of a bad solution and I'm hoping someone can suggest another way. My way basically involved redrawing a mutablepath by repeated calls to the draw method during the touchesMoved method. Again, I know this is a bad way of doing it, so sorry for the horrible code.
-(void)drawLine:(CGPoint)location
{
[_powerLine removeFromParent];
CGPoint pointTL = CGPointMake(19, 131);
CGPoint pointTR = CGPointMake(308, 131);
CGPoint pointBL = CGPointMake(location.x-10, location.y);
CGPoint pointBR = CGPointMake(location.x+10, location.y);
UIBezierPath *lineShape = [UIBezierPath bezierPath];
[lineShape moveToPoint:pointTL];
[lineShape addLineToPoint:pointBL];
[lineShape addLineToPoint:pointBR];
[lineShape addLineToPoint:pointTR];
_powerLine = [SKShapeNode node];
_powerLine.path = lineShape.CGPath;
_powerLine.lineWidth = 2.0;
_powerLine.strokeColor = [SKColor colorWithRed:0.0 green:0 blue:0 alpha:1];
[self addChild:_powerLine];
CGMutablePathRef powerLinePath = CGPathCreateMutable();
CGPathMoveToPoint(powerLinePath, nil, pointTL.x, pointTL.y);
CGPathAddLineToPoint(powerLinePath, nil, pointBL.x, pointBL.y);
CGPathAddLineToPoint(powerLinePath, nil, pointBR.x, pointBR.y);
CGPathAddLineToPoint(powerLinePath, nil, pointTR.x, pointTR.y);
_powerLine.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:powerLinePath];
_powerLine.physicsBody.categoryBitMask = WFPhysicsCategoryPowerline;
_lastBR = pointBR;
_lastBL = pointBL;
}
I'm hoping there is a better way to do this other than constantly redrawing it when the person pulls the line down to shot the object up in the air. I looked into spring joints but couldn't convince them to work. The other problem I had with spring joints was how to get the image to stretch to match where the line should be. This approach solves trying to stretch an image by simply eliminating the image. It would be nice to use springs so that I could avoid having to hand code the physics of this.
Anyone have thoughts on how to do this?
I'm not sure if you're concerned about how to draw a convincing elastic band onscreen, or how to emulate the physics of one. If it's the former I can't help you much, but if it's the latter you could try something like this! You should be able to copy and paste it into an existing sprite kit app to play around with it, just initialize it and call SKView's presentScene: with it.
(header file)
#import <SpriteKit/SpriteKit.h>
#interface SlingScene : SKScene
#end
(implementation file)
#import "SlingScene.h"
#interface SlingScene ()
#property (nonatomic, strong) SKAction *slingAction;
#end
#implementation SlingScene
- (instancetype)initWithSize:(CGSize)size {
self = [super initWithSize:size];
if (self) {
CGPoint center = CGPointMake(self.size.width/2.0, self.size.height/2.0);
self.slingAction = [SKAction sequence:
#[[SKAction waitForDuration:0.1],
[SKAction runBlock:
^{
[self.physicsWorld removeAllJoints];
}
]]];
// Create a square, which will be slung by the spring
SKSpriteNode *square =
[SKSpriteNode spriteNodeWithColor:[SKColor whiteColor]
size:CGSizeMake(60.0, 60.0)];
square.position = CGPointMake(center.x, center.y - 2*square.size.height);
square.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:square.size];
square.physicsBody.collisionBitMask = 0;
square.name = #"square";
// Create a post to anchor the square to
SKShapeNode *post = [SKShapeNode node];
post.path = CGPathCreateWithEllipseInRect(
CGRectMake(0.0, 0.0, 60.0, 60.0), NULL);
post.fillColor = [SKColor brownColor];
post.strokeColor = [SKColor brownColor];
post.position = CGPointMake(center.x-30.0, center.y-30.0);
post.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:60.0 center:center];
// Give the post a near-infinite mass so the square won't tug at it
// and move it around
post.physicsBody.mass = 1000000;
post.physicsBody.affectedByGravity = NO;
// Set their collision bit masks to the same value to allow them to pass
// through each other
post.physicsBody.collisionBitMask = 0;
square.physicsBody.collisionBitMask = 0;
// Add them to the scene
[self addChild:post];
[self addChild:square];
// Connect them via a spring
SKPhysicsJointSpring *spring =
[SKPhysicsJointSpring jointWithBodyA:post.physicsBody
bodyB:square.physicsBody
anchorA:center
anchorB:square.position];
spring.damping = 0.4;
spring.frequency = 1.0;
[self.physicsWorld addJoint:spring];
// Lower gravity from the default {0.0, -9.8} to allow the
// square to be slung farther
self.physicsWorld.gravity = CGVectorMake(0.0, -4.0);
}
return self;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// Move the square to the touch position
SKSpriteNode *square = (SKSpriteNode *)[self childNodeWithName:#"square"];
CGPoint location = [[touches anyObject] locationInNode:self];
[square runAction:[SKAction moveTo:location duration:0.1]];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// Sling the square by
// 1. allowing the spring to accelerate it, and
// 2. removing the spring altogether
[self runAction:self.slingAction];
}
#end
Another method might be to compute the x and y positions relative to a specific point, and apply an impulse in the opposite direction. For example, if you find dx = -100.0 and dy = -100.0, you could use applyImpulse:CGVectorMake(-dx, -dy) to launch it up and right. Using the spring joint gives you some acceleration, however.

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/

Resources