I am attempting to rotate an SKSpriteNode to face the direction of a CGPoint. I have managed to do this like so:
CGPoint direction = rwNormalize(offset);
self.player.zRotation = atan2f(direction.y, direction.x);
How would I call this so that the SKSpriteNode will animate its rotation rather than it being instantaneous. Would it also be possible to keep the same speed of rotation in the animation no matter where the sprite should turn? Thanks in advance!
Use SKAction:
CGPoint direction = rwNormalize(offset);
float angle = atan2f(direction.y, direction.x);
// Speed of rotation (radians per second)
float speed = 2.0;
float duration = angle > M_PI_2 ? angle/speed : (angle + M_PI_2)/speed;
[self.player runAction:
[SKAction rotateToAngle:angle duration:duration]];
Related
For the sake of simplicity, the scene has a circle sprite and a square sprite. The square sprite is the child of an SKNode that follows around the circle sprite so that rotation of the square always happens around the circle. However, when the node is rotated, the square randomly drifts upwards. This behavior stops once the rotation makes its way all the way around. Here is the code:
_square = [SKSpriteNode spriteNodeWithImageNamed:#"square"];
_circle = [SKSpriteNode spriteNodeWithImageNamed:#"circle"];
_circle.position = CGPointMake(-200, 300);
[self addChild:_circle];
_rotateNode = [SKNode node];
_rotateNode.position = CGPointMake(300, 300);
[self addChild:_rotateNode];
[_rotateNode addChild:_square];
SKAction *moveBall = [SKAction moveTo:CGPointMake(1200, 300) duration:12];
[_circle runAction:moveBall];
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SKAction *rotate = [SKAction rotateByAngle:-M_PI_2 duration:0.2];
[_rotateNode runAction:rotate];
}
-(void)update:(CFTimeInterval)currentTime {
_rotateNode.position = _circle.position;
double xOffset = (300 -_circle.position.x);
double yOffset = (300 - _circle.position.y);
_square.position = CGPointMake(xOffset, yOffset);
}
Anyone know why this is happening?
If I understand the question, you are asking why the square moves in unexpected ways? The circle is moving, but on every frame you are setting the rotateNode's position to be the same as the circle, and they both have the same parent: self, so we can ignore circle for any debugging because it just executes its action every frame to get a new position.
The oddness, most likely, comes in because you are manually repositioning rotateNode, which would then move all of its children, including square. But in every frame you are also repositioning square manually. So rotateNode rotates, changes the position of square because it rotated, and then you reposition square to have a fixed offset. Further complicated depending on whether your goal is to position the square in world space or in parent space. But it's not clear what your goal is there.
I'm making a game with SpriteKit that involves a hero bouncing on a surface in the positive x and y direction. The hero has this size:
CGSize heroSize = CGSizeMake(hero.size.width-140, hero.size.height-35);
hero.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:heroSize];
hero.physicsBody.usesPreciseCollisionDetection = YES;
and uses applyImpulse to bounce in the positive x/y direction:
[self.physicsBody applyImpulse:CGVectorMake(xvelocity, yvelocity)];
SKAction *rotation = [SKAction rotateByAngle: -2.0/M_PI duration:.5];
[self runAction: rotation];
The ground has a friction level of 0.4 and restitution of .5. Because the hero rotates when bouncing, he will sometimes bounce backwards and have a negative X velocity. I was wondering if there is any way to prevent an SKSpriteNode from moving in the -X direction. Would a solution be to create a better size for the physics body of my SKSpriteNode? It seems like this could happen with any shape, but would be less likely with more circular objects. Any input is greatly appreciated.
Thanks
You can monitor your hero's velocity and limit negative x movement by using this:
if(hero.physicsBody.velocity.dx < 0) {
hero.physicsBody.velocity = CGVectorMake(0, hero.physicsBody.velocity.dy);
}
I am having trouble getting collision detection to work with my two objects. Here is my current code:
_firstPosition = CGPointMake(self.frame.size.width * 0.817f, self.frame.size.height * .40f);
_squirrelSprite = [SKSpriteNode spriteNodeWithImageNamed:#"squirrel"];
_squirrelSprite.position = _firstPosition;
_atFirstPosition = YES;
[self addChild:_squirrelSprite];
SKAction *wait = [SKAction waitForDuration:3.0];
SKAction *createSpriteBlock = [SKAction runBlock:^{
SKSpriteNode *lightnut = [SKSpriteNode spriteNodeWithImageNamed:#"lightnut.png"];
BOOL heads = arc4random_uniform(100) < 50;
lightnut.position = (heads)? CGPointMake(257,600) : CGPointMake(50,600);
[self addChild: lightnut];
SKAction *moveNodeUp = [SKAction moveByX:0.0 y:-700.0 duration:1.3];
[lightnut runAction: moveNodeUp];
}];
SKAction *waitThenRunBlock = [SKAction sequence:#[wait,createSpriteBlock]];
[self runAction:[SKAction repeatActionForever:waitThenRunBlock]];
I was following this answer at first: Sprite Kit Collision Detection but when I set the physics body to my squirrel and nut they would just fall. Is there any way to not mess with the physics (I'm happy with how everything currently works in the app) and just make it so that when one object touches the other the game will end? Is there a way to just set a radius around a sprite? Thank you for any help or information that can be provided.
Physics bodies are required for collision detection. If you don't also want gravity, either set the physics world's gravity to zero or turn off the affectedByGravity property on each physics body.
You can use the Pythagorean theorem to determine the distance between two sprite nodes. Here's an example of how to do that:
CGFloat dx = sprite1.position.x - sprite2.position.x;
CGFloat dy = sprite1.position.y - sprite2.position.y;
CGFloat distance = sqrt(dx*dx+dy*dy);
// Check if the two nodes are close
if (distance <= kMaxDistance) {
// Do something
}
I don't really know how to explain what i'm after, so I drew some (very) artistic diagrams to help convey the idea. I'll also try and explain it the best I can.
I'm essentially trying to 'shoot' bullets/lasers/whatever from a circle in the center of the screen, and for it to repeat this at a rather rapid rate. Here are two pictures which kind of show what i'm trying to achieve: (Don't have enough reputation to post them here.
(1) http://i.imgur.com/WpZlTQ7.png
This is kind of where I want the bullets to shoot from, and how many I'd like.
(2) http://i.imgur.com/psdIjZG.png
This is pretty much the end result, I'd like them to repeatedly fire and make the screen kind of look like this.
Can anyone refer me to what I should be looking at in order to achieve this?
When dealing with circles, it's usually easier to use polar coordinates. In this case, each direction can be represented with a magnitude and an angle, where the magnitude is the amount of the force/impulse to apply to the bullet and angle is the direction to shoot the bullet.
The basic steps are
Determine the number of directions
Determine the angle increment by dividing 2*PI (360 degrees) by the number of directions
Start at angle = 0
Shoot bullet in the direction specified by angle
Convert the angle and magnitude to cartesian coordinates to form a vector
Apply an impulse or a force to the bullet using the vector
Increment angle by angle increment
Here's an example of how to do that in Obj-C:
#implementation GameScene {
CGFloat angle;
SKTexture *texture;
CGFloat magnitude;
CGFloat angleIncr;
}
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
long numAngles = 15;
magnitude = 1;
angleIncr = 2 * M_PI / numAngles;
angle = 0;
texture = [SKTexture textureWithImageNamed:#"Spaceship"];
SKAction *shootBullet = [SKAction runBlock:^{
SKSpriteNode *bullet = [SKSpriteNode spriteNodeWithTexture:texture];
bullet.size = CGSizeMake(8, 8);
bullet.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bullet.size.width/2];
bullet.physicsBody.affectedByGravity = NO;
bullet.position = self.view.center;
bullet.zRotation = angle-M_PI_2;
[self addChild:bullet];
CGFloat dx = magnitude * cos(angle);
CGFloat dy = magnitude * sin(angle);
CGVector vector = CGVectorMake(dx,dy);
[bullet.physicsBody applyImpulse:vector];
angle = fmod(angle+angleIncr,2*M_PI);
}];
SKAction *wait = [SKAction waitForDuration:0.25];
SKAction *shootBullets = [SKAction sequence:#[shootBullet, wait]];
[self runAction:[SKAction repeatActionForever:shootBullets]];
}
From what I understand, you want this sprite to shoot a bunch of projectiles every X seconds.
SKAction *ShootProjectiles = [SKAction runBlock:^{
//Create Projectile1
projectile1.physicsBody.applyImpulse(CGVectorMake(1, 0)); //Shoot directly right
//Create Projectile2
projectile2.physicsBody.applyImpulse(CGVectorMake(1, 1)); //Shoot diagnally Up and to the right
//Follow this pattern to create all projectiles desired to be shot in one burst
}];
SKAction *delayBetweenShots = [SKAction scaleBy:0 duration:5];
SKAction* ShootSequence= [SKAction repeatActionForever:[SKAction sequence:#[ShootProjectiles, delayBetweenShots]]];
[self runAction: ShootSequnce];
What this does is create as many projectiles as you desire and fire them in the direction of the vectors you define. It then waits 5 seconds (the scaleBy:0 action does nothing except delay) and then repeats over and over again until you remove the action.
I am working on the basis of Ray Wenderlich's tutorial on rotating turrets in Cocos 2d (see here: http://www.raywenderlich.com/25791/rotating-turrets-how-to-make-a-simple-iphone-game-with-cocos2d-2-x-part-2). I need my game to be in portrait mode so I have managed to get the position of the turret correctly:
The turret manages to shoot right, but not left. Here is my code:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (_nextProjectile != nil) return;
// Choose one of the touches to work with
UITouch *touch = [touches anyObject];
CGPoint location = [self convertTouchToNodeSpace:touch];
// Set up initial location of projectile
CGSize winSize = [[CCDirector sharedDirector] winSize];
_nextProjectile = [[CCSprite spriteWithFile:#"projectile2.png"] retain];
_nextProjectile.position = ccp(160, 20);
// Determine offset of location to projectile
CGPoint offset = ccpSub(location, _nextProjectile.position);
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
// Determine where you wish to shoot the projectile to
int realX = winSize.width + (_nextProjectile.contentSize.width/2);
float ratio = (float) offset.y / (float) offset.x;
int realY = (realX * ratio) + _nextProjectile.position.y;
CGPoint realDest = ccp(realX, realY);
// Determine the length of how far you're shooting
int offRealX = realX - _nextProjectile.position.x;
int offRealY = realY - _nextProjectile.position.y;
float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;
// Determine angle to face
float angleRadians = atanf((float)offRealY / (float)offRealX);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
float rotateDegreesPerSecond = 180 / 0.5; // Would take 0.5 seconds to rotate 180 degrees, or half a circle
float degreesDiff = _player.rotation - cocosAngle;
float rotateDuration = fabs(degreesDiff / rotateDegreesPerSecond);
[_player runAction:
[CCSequence actions:
[CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
[CCCallBlock actionWithBlock:^{
// OK to add now - rotation is finished!
[self addChild:_nextProjectile];
[_projectiles addObject:_nextProjectile];
// Release
[_nextProjectile release];
_nextProjectile = nil;
}],
nil]];
// Move projectile to actual endpoint
[_nextProjectile runAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:realMoveDuration position:realDest],
[CCCallBlockN actionWithBlock:^(CCNode *node) {
[_projectiles removeObject:node];
[node removeFromParentAndCleanup:YES];
}],
nil]];
_nextProjectile.tag = 2;
}
Thanks for the help!
You are checking x axis instead of Y
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return
;
Did you actually set the application to run in portrait mode or have you just rotated the simulator and repositioned the turret?
If you didn't explicitly set the app to run in portrait your x and y coordinates will be swapped (x will run from the ios button to the top of the phone, not accross as you would expect).
If it is converted properly I have answered this question before :)
This issue here is that you've copy-pasted the math instead of editing it properly for your purposes. There are some assumptions made in Ray's code that rely on you shooting always to the right of the turret instead of up, down, or left.
Here's the math code you should be looking at:
// Determine offset of location to projectile
CGPoint offset = ccpSub(location, _nextProjectile.position);
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
Note here that you will have an offset.x less than 0 if the tap location is to the left of the turret, so this is an assumption you took from Ray but did not revise. As gheesse said, for your purposes this should be set to offset.y as you don't want them shooting south of the projectile's original location. But this is only part of the problem here.
// Determine where you wish to shoot the projectile to
int realX = winSize.width + (_nextProjectile.contentSize.width/2);
Here's your other big issue. You did not revise Ray's math for determining where the projectile should go. In Ray's code, his projectile will always end up on a location that is off the screen to the right, so he uses the width of the screen and projectile's size to determine the real location he wants the projectile to go. This is causing your issue since you don't have the assumption that your projectile will always head right - yours will always go up (hint, code similar to this should be used for your realY)
float ratio = (float) offset.y / (float) offset.x;
int realY = (realX * ratio) + _nextProjectile.position.y;
Again, Ray makes assumptions in his math for his game and you haven't corrected it in this realY. Your code has the turret turning in ways that will effect the realX coordinate instead of the realY, which is the coordinate that Ray's always shoot right turret needed to effect.
You need to sit down and re-do the math.