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)];
Related
I have rectangle shape paddle node, my question is - is it possible to "cut" node in half so when, for example ball, hit right side of node ball will go back with bigger angle, that it came, and same thing on other side. Im bad with words so, here are image of what I want to do :
For paddle node i use this code:
paddle = [[SKSpriteNode alloc] initWithImageNamed: #"board.png"];
paddle.name = paddleCategoryName;
paddle.position = CGPointMake(self.frame.origin.x + 50, CGRectGetMidY(self.frame));
[self addChild:paddle];
paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
paddle.physicsBody.restitution = 0.1f;
paddle.physicsBody.friction = 0.4f;
paddle.physicsBody.dynamic = NO;
and for ball :
ball = [SKSpriteNode spriteNodeWithImageNamed: #"ball.png"];
[self addChild:ball];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width];
ball.physicsBody.friction = 0.0f;
ball.physicsBody.restitution = 1.0f;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;
I have two body
SKShapeNode *hero = [SKShapeNode shapeNodeWithCircleOfRadius:HERO_SIZE];
hero.lineWidth = 1;
hero.fillColor = [SKColor orangeColor];
hero.name = #"Hero";
hero.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:HERO_SIZE];
hero.physicsBody.categoryBitMask = hero;
hero.physicsBody.collisionBitMask = friendlyCategory | enemyCategory;
hero.physicsBody.contactTestBitMask = friendlyCategory | enemyCategory;
hero.position = [self getStartPosition];
SKShapeNode *circle = [SKShapeNode shapeNodeWithCircleOfRadius:BALL_SIZE];
circle.position = [self randomPossition];
circle.lineWidth = 2;
circle.strokeColor = [SKColor blueColor];
circle.fillColor = color;
circle.name = #"Circle";
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:BALL_SIZE];
circle.physicsBody.categoryBitMask = friendlyCategory;
circle.physicsBody.collisionBitMask = hero;
circle.physicsBody.contactTestBitMask = hero;
then,
- (void)didEndContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (friendlyCategory | hero ))
{
SKShapeNode* node = (SKShapeNode*)contact.bodyA.node;
if ([node.name isEqualToString:#"Hero"])
{
self.activeFriendlyBall = (SKShapeNode*)contact.bodyB.node;
}
else
{
self.activeFriendlyBall = (SKShapeNode*)contact.bodyA.node;
}
self.hero.position = self.activeFriendlyBall.position;
}
}
Hero must be added above activeFriendlyBall, for him center position. But it added near him. I think it because physic body added. But I need to use physic body for other logic.
http://cl.ly/image/0g1S0d3h1h0w
must be like, how in top screen.
I find solution.
self.heroBall.physicsBody.velocity = CGVectorMake(0, 0);
[self.heroBall runAction: [SKAction moveTo:self.activeFriendlyBall.position duration:0.0]];
I believe it is because you have collision detection enabled for both, which means they can't be put on top of one another. If you are just trying to determine if one sprite comes into contact with another all you need is the contact detection. Removing either hero or friendlyball from the collision detection bit mask will allow them to be placed on top of each other/go through each other etc.
If you don't care about collisions at all for one of them (the sprite doesn't 'bounce' off anything) I'd recommend just setting collision detection to 0 for that sprite.
Best
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.
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/
If I apply an impulse of 1 in the y direction, the ball bounces back and forth without losing any energy. However, if the initial impulse is 0.5 or below, the ball loses all energy instantly when it hits the wall. Why is this happening? I have a pretty good understanding of the properties of the SKPhysicsBody class. Try this code to see if you get the same behavior on your computer.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
SKPhysicsBody* borderBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody = borderBody;
self.physicsBody.friction = 0.0f;
self.backgroundColor = [SKColor colorWithRed:0.7 green:0.7 blue:0.7 alpha:1.0];
SKShapeNode *ball = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
[ball setStrokeColor:[UIColor blackColor]];
CGPathMoveToPoint(pathToDraw, NULL, 0, 0);
CGPathAddEllipseInRect(pathToDraw, NULL, CGRectMake(-16, -16, 32, 32));
ball.path = pathToDraw;
ball.position = CGPointMake(size.width / 2, size.height / 2);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];
ball.physicsBody.friction = 0.0f;
ball.physicsBody.restitution = 1.0f;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;
[self addChild:ball];
[ball.physicsBody applyImpulse:CGVectorMake(0, 0.5)];
}
return self;
}
When the collision velocity is small enough (such as your CGVector of (0, 0.5)), Box2d underlying the Sprite Kit physics engine will compute the collision as inelastic (i.e. as if the restitution was 0, removing any bounciness), and the node will not bounce.
This is, per the Box2d documentation, to prevent jitter.
In the Box2d source code, you even have this line:
/// A velocity threshold for elastic collisions. Any collision with a relative linear
/// velocity below this threshold will be treated as inelastic.
#define b2_velocityThreshold
Your impulse should be above this threshold for the restitution to be respected.