Spritekit: move SKSpritekitNode in arc with SKPhysicsBody - ios

I'm trying to move an SKSpriteNode in an arc with physics in spritekit like so:
But am unsure as to which physic I should apply to it (applyImpulse, applyForce, applyTorque).
Currently using applyTorque, the code does not actually work, and produces no movement on the object:
_boy.physicsBody.velocity = CGVectorMake(1, 1);
CGVector thrustVector = CGVectorMake(0,100);
[_boy.physicsBody applyTorque:(CGFloat)atan2(_boy.physicsBody.velocity.dy, _boy.physicsBody.velocity.dx)];

applyTorque is not the right method for this. Torque is a twisting force that causes your node to rotate around its center point.
There is no one easy command for what you are looking to do. You also fail to mention your method of movement for your node. Apply force, impulse, etc... You are going to have to come up with a hack for this one.
The sample project below does what you are looking for and it will point you in the right direction. You are going to have to modify the code to suit your specific project's needs though.
Tap/click the screen once to start moving the node and tap/click the screen a second time to start to 90 degree movement change.
#import "GameScene.h"
#implementation GameScene {
int touchCounter;
BOOL changeDirection;
SKSpriteNode *node0;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor whiteColor];
node0 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(50, 50)];
node0.position = CGPointMake(150, 200);
node0.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node0.size];
node0.physicsBody.affectedByGravity = NO;
[self addChild:node0];
touchCounter = 0;
changeDirection = NO;
}
-(void)update:(CFTimeInterval)currentTime {
if(changeDirection)
[self changeMovement];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
touchCounter++;
for (UITouch *touch in touches) {
if(touchCounter == 1)
[node0.physicsBody applyImpulse:CGVectorMake(25, 0)];
if(touchCounter == 2)
changeDirection = YES;
}
}
-(void)changeMovement {
if(node0.physicsBody.velocity.dy<200) {
[node0.physicsBody applyImpulse:CGVectorMake(-0.1, 0.1)];
} else {
changeDirection = NO;
node0.physicsBody.velocity = CGVectorMake(0, node0.physicsBody.velocity.dy);
}
}

Related

How can I measure sprite's jump strength?

Hello,
I am new to spriteKit and I am trying to make a game. In the game I have a player that jumps from stair to stair, which comes from the top of the screen infinitely (Like in Doodle Jump, only the jump is controlled by the player's touch). I am trying to make a jump, by applying an impulse on the player, but I want to control the jump strength by the player's touch duration. How can I do so? The jump executes when the player STARTS touching the screen so I can't measure the jump intensity (By calculating the touch duration) ... Any ideas?
Thanks in advance!!! (:
Here's a simple demo to apply the impulse on a node with the duration of touch. The method is straightforward: set a BOOL variable YES when the touch began, and NO when the touch ended. When touching, it will apply a constant impulse in update method.
To make the game more natural, you might want to refine the impulse action, or scroll the background down as the node is ascending.
GameScene.m:
#import "GameScene.h"
#interface GameScene ()
#property (nonatomic) SKSpriteNode *node;
#property BOOL touchingScreen;
#property CGFloat jumpHeightMax;
#end
#implementation GameScene
- (void)didMoveToView:(SKView *)view
{
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
// Generate a square node
self.node = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(50.0, 50.0)];
self.node.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.node.size];
self.node.physicsBody.allowsRotation = NO;
[self addChild:self.node];
}
const CGFloat kJumpHeight = 150.0;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.touchingScreen = YES;
self.jumpHeightMax = self.node.position.y + kJumpHeight;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
self.touchingScreen = NO;
self.jumpHeightMax = 0;
}
- (void)update:(CFTimeInterval)currentTime
{
if (self.touchingScreen && self.node.position.y <= self.jumpHeightMax) {
self.node.physicsBody.velocity = CGVectorMake(0, 0);
[self.node.physicsBody applyImpulse:CGVectorMake(0, 50)];
} else {
self.jumpHeightMax = 0;
}
}
#end

Sprite kit moving sprite

I have made this sprite that i can grab and move around.
My issue is that i want to be able to "throw" the sprite. Meaning, when i release the sprite i want it to continue in the direction that i moved it. Just like throwing a ball.
What should i do?
#implementation NPMyScene
{
SKSpriteNode *sprite;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
self.scaleMode = SKSceneScaleModeAspectFit;
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"GreenBall"];
sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite.physicsBody.velocity = self.physicsBody.velocity;
sprite.physicsBody.affectedByGravity = false;
sprite.physicsBody.dynamic = true;
sprite.physicsBody.friction = 0;
[sprite.physicsBody isDynamic];
[sprite.physicsBody allowsRotation];
[self addChild:sprite];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[sprite runAction:[SKAction moveTo:[[touches anyObject] locationInNode:self]duration:0.21]];
}
If you are talking about applying physics to the ball, then you need to think deeper about how Sprite Kit deals with physics.
When you specify the 'moveTo' action, you're not actually using Sprite kit's physics engine at all. You're simply specifying a point to animate that sprite to.
What you should be doing to achieve the effect you're looking for is attaching the sprite's position to your finger as it's moved around on the screen like so:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
sprite.position = [[touches anyObject] locationInNode:self];
}
Then when the finger is lifted, you need to calculate the direction and speed in which your finger just moved and apply the appropriate amount of force to the sprite's physics body using the applyForce method:
[sprite.physicsBody applyForce:calculatedForce];
You will need to figure out how to calculate the force to be applied, but play around with the applyForce method and look at what information you are able to get back from the touchedMoved: event to help you apply that force. Hope that helps.

I'm having issues with touchesBegan updating a CGVector value

I have made a CGVector which makes an upward impulse (simulating a jump) of the player ball. If I run the game with what I currently have (below), it will start fine, and the jump will happen perfectly as soon as the game starts. I am trying to make the ball jump every time a touch is recorded but I'm having issues calling the right method / making it work at all.
This is my code:
#import "MyScene.h"
#interface MyScene ()
#property (nonatomic) SKSpriteNode *paddle;
#end
int jump = 0;
#implementation MyScene
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"Touch Detected!");
// create the vector
// myVector = CGVectorMake(0, 15);
// [ball.physicsBody applyImpulse:myVector];
}
- (void)addBall:(CGSize)size {
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];
// create a new sprite node from an image
// create a CGPoint for position
CGPoint myPoint = CGPointMake(size.width/2,0);
ball.position = myPoint;
// add a physics body
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];
ball.physicsBody.restitution = 0;
// add the sprite node to the scene
[self addChild:ball];
// create the vector
CGVector myVector = CGVectorMake(0, 15);
// apply the vector
[ball.physicsBody applyImpulse:myVector];
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor whiteColor];
// add a physics body to the scene
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
// change gravity settings of the physics world
self.physicsWorld.gravity = CGVectorMake(0, -8);
[self addBall:size];
// [self addPlayer:size];
}
return self;
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
You can see I have commented out the myVector = CGVectorMake(0, 15); and the [ball.physicsBody applyImpulse:myVector]; in touchesBegan because It can't find ball and i'm not even sure it's right at all. Could anyone help me implement this jump using the impulse? Thanks!
You are on the right track, but multiple impulses of the vector (0,15), will cause the ball to go very high. You need to reset the ball's velocity to zero before applying any subsequent impulse.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touch Detected!");
// create the vector
ball.physicsBody.velocity = CGVectorMake(0,0);
myVector = CGVectorMake(0, 15);
[ball.physicsBody applyImpulse:myVector];
}
Also, you need to declare the ball as a global variable like so:
#implementation MyScene
{
SKSpriteNode *ball;
}
And then in the addBall: method, instead of
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];
just write
ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];

Throwing ball in SpriteKit

Last days, I experimented some time with spriteKit and (amongst other things) tried to solve the problem to "throw" a sprite by touching it and dragging.
The same question is on Stackexchange, but they told me to first remove the bug and then let the code be reviewed.
I have tackled the major hurdles, and the code is working fine, but there consist one little problem.
(Additionally, I'd be interested if somebody has a more polished or better working solution for this. I'd also love to hear suggestions about how to perfect the feeling of realism in this interaction.)
Sometimes, the ball just gets stuck.
If you want to reproduce that, just swipe the ball really fast and short. I suspect the gestureRecognizer to make "touchesMoved" and "touchesEnded" callback asynchronous and through that some impossible state occurs in the physics simulation.
Can anybody provide a more reliable way to reproduce the issue, and what could be the solution for that?
The project is called ballThrow and BT is the class prefix.
#import "BTMyScene.h"
#import "BTBall.h"
#interface BTMyScene()
#property (strong, nonatomic) NSMutableArray *balls;
#property (nonatomic) CGFloat yPosition;
#property (nonatomic) CGFloat xCenter;
#property (nonatomic) BOOL updated;
#end
#implementation BTMyScene
const CGFloat BALLDISTANCE = 80;
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_balls = [NSMutableArray arrayWithCapacity:5];
//define the region where the balls will spawn
_yPosition = size.height/2.0;
_xCenter = size.width/2.0;
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
}
return self;
}
-(void)didMoveToView:(SKView *)view {
//Make an invisible border
//this seems to be offset... Why the heck is this?
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:view.frame];
[self createBalls:2];
//move balls with pan gesture
//could be improved by combining with touchesBegan for first locating the touch
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveBall:)]];
}
-(void)moveBall:(UIPanGestureRecognizer *)pgr {
//depending on the touch phase do different things to the ball
if (pgr.state == UIGestureRecognizerStateBegan) {
[self attachBallToTouch:pgr];
}
else if (pgr.state == UIGestureRecognizerStateChanged) {
[self moveBallToTouch:pgr];
}
else if (pgr.state == UIGestureRecognizerStateEnded) {
[self stopMovingTouch:pgr];
}
else if (pgr.state == UIGestureRecognizerStateCancelled) {
[self stopMovingTouch:pgr];
}
}
-(void)attachBallToTouch:(UIPanGestureRecognizer *)touch {
//determine the ball to move
for (BTBall *ball in self.balls) {
if ([self isMovingBall:ball forGestureRecognizer:touch])
{
//stop ball movement
[ball.physicsBody setAffectedByGravity:NO];
[ball.physicsBody setVelocity:CGVectorMake(0, 0)];
//the ball might not be touched right in its center, so save the relative location
ball.touchLocation = [self convertPoint:[self convertPointFromView:[touch locationInView:self.view]] toNode:ball];
//update location once, just in case...
[self setBallPosition:ball toTouch:touch];
if (_updated) {
_updated = NO;
[touch setTranslation:CGPointZero inView:self.view];
}
}
}
}
-(void)moveBallToTouch:(UIPanGestureRecognizer *)touch {
for (BTBall *ball in self.balls) {
if ([self isMovingBall:ball forGestureRecognizer:touch])
{
//update the position of the ball and reset translation
[self setBallPosition:ball toTouch:touch];
if (_updated) {
_updated = NO;
[touch setTranslation:CGPointZero inView:self.view];
}
break;
}
}
}
-(void)setBallPosition:(BTBall *)ball toTouch:(UIPanGestureRecognizer *)touch {
//gesture recognizers only deliver locations in views, thus convert to node
CGPoint touchPosition = [self convertPointFromView:[touch locationInView:self.view]];
//update the location to the toucheĀ“s location, offset by touch position in ball
[ball setNewPosition:CGPointApplyAffineTransform(touchPosition,
CGAffineTransformMakeTranslation(-ball.touchLocation.x,
-ball.touchLocation.y))];
//save the velocity between the last two touch records for later release
CGPoint velocity = [touch velocityInView:self.view];
//why the hell is the y coordinate inverted??
[ball setLastVelocity:CGVectorMake(velocity.x, -velocity.y)];
}
-(void)stopMovingTouch:(UIPanGestureRecognizer *)touch {
for (BTBall *ball in self.balls) {
if ([self isMovingBall:ball forGestureRecognizer:touch]) {
//release the ball: enable gravity impact and make it move
[ball.physicsBody setAffectedByGravity:YES];
[ball.physicsBody setVelocity:CGVectorMake(ball.lastVelocity.dx, ball.lastVelocity.dy)];
break;
}
}
}
-(BOOL)isMovingBall:(BTBall *)ball forGestureRecognizer:(UIPanGestureRecognizer *)touch {
//latest location of touch
CGPoint touchPosition = [touch locationInView:self.view];
//distance covered since the last call
CGPoint touchTranslation = [touch translationInView:self.view];
//position, where the ball must be, if it is the one
CGPoint translatedPosition = CGPointApplyAffineTransform(touchPosition,
CGAffineTransformMakeTranslation(-touchTranslation.x,
-touchTranslation.y));
CGPoint inScene = [self convertPointFromView:translatedPosition];
//determine weather the last touch location was on the ball
//if last touch location was on the ball, return true
return [[self nodesAtPoint:inScene] containsObject:ball];
}
-(void)update:(CFTimeInterval)currentTime {
//updating the ball position here improved performance dramatically
for (BTBall *ball in self.balls) {
//balls that move are not gravity affected
//easiest way to determine movement
if ([ball.physicsBody affectedByGravity] == NO) {
[ball setPosition:ball.newPosition];
}
}
//ball positions are refreshed
_updated = YES;
}
-(void)createBalls:(int)numberOfBalls {
for (int i = 0; i<numberOfBalls; i++) {
BTBall *ball;
//reuse balls (not necessary yet, but imagine balls spawning)
if(i<[self.balls count]) {
ball = self.balls[i];
}
else {
ball = [BTBall newBall];
}
[ball.physicsBody setAffectedByGravity:NO];
//calculate ballposition
CGPoint ballPosition = CGPointMake(self.xCenter-BALLSIZE/2+(i-(numberOfBalls-1)/2.0)*BALLDISTANCE, self.yPosition);
[ball setNewPosition:ballPosition];
[self.balls addObject:ball];
[self addChild:ball];
}
}
#end
The BTBall (subclass of SKShapeNode, because of the custom properties needed)
#import <SpriteKit/SpriteKit.h>
#interface BTBall : SKShapeNode
const extern CGFloat BALLSIZE;
//some properties for the throw animation
#property (nonatomic) CGPoint touchLocation;
#property (nonatomic) CGPoint newPosition;
#property (nonatomic) CGVector lastVelocity;
//create a standard ball
+(BTBall *)newBall;
#end
The BTBall.m with a class method to create new balls
#import "BTBall.h"
#implementation BTBall
const CGFloat BALLSIZE = 80;
+(BTBall *)newBall {
BTBall *ball = [BTBall node];
//look
[ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];
[ball setFillColor:[UIColor redColor]];
[ball setStrokeColor:[UIColor clearColor]];
//physics
SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];
[ball setPhysicsBody:ballBody];
[ball.physicsBody setAllowsRotation:NO];
//ball is not moving at the beginning
ball.lastVelocity = CGVectorMake(0, 0);
return ball;
}
#end
1. A couple of problems (see comments in code) are related to the spriteKit coordinate system. I just do not get the border of the scene align with its actual frame, though I make it with the exact same code that Apple gives us in the programming guide. I have moved it from initWithSize to didMoveToView due to a suggestion here on Stackoverflow, but that did not help. It is possible to manually offset the border with hardcoded values, but that does not satisfy me.
2. Does anybody know a debugging tool, which colors the physics body of a sprite, in order to see its size and whether it is at the same position as the sprite?
Update: Problems above solved by using YMC Physics Debugger:
This lines of code are correct:
[ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];
SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];
Because 0,0 is the center of the physics body, the origin of the path must be translated.

SpriteKit: Collisions with 300 nodes, very low FPS

I want to make a simple SpriteKit app where I can add "rocks" and they fall to bottom of the screen. Just like this: http://aamukasa.fi/II-13-347. The implementation is quite easy but I will get huge performance issues when there are more than 100 nodes. The FPS goes under 10 when all the blocks collides together. Is there anyway to achieve this kind of functionality with good FPS and about 300-400 blocks?
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithWhite:1 alpha:1];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.scaleMode = SKSceneScaleModeAspectFit;
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *rock = [[SKSpriteNode alloc] initWithColor:[SKColor blackColor] size:CGSizeMake(20,10)];
rock.position = location;
rock.name = #"rock";
rock.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rock.size];
[self addChild:rock];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
You might want to try to give your rocks a circular physicsbody. I don't think it will solve the issue but might increase framerate a bit! As far as I've understood, circular physicsbodies are better for performance than rectangular ones.
You should test on a device to see actual FPS. In the simulator I get 60 FPS until I reach 200 bricks.
Then it starts dropping but on the device you could get better results.
Not sure if you are still looking, but could try something like this:
-(void)didSimulatePhysics
{
[self enumerateChildNodesWithName:#"rock" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0)
[node removeFromParent];
}];
}
Otherwise the nodes will stay in memory and FPS will worsen overtime.

Resources