How to rotate an SKSpriteNode around the node's Y-axis? - objective-c

I'm trying to rotate an SKSpriteNode node around it's Y-axis. I know there's the zRotation property and that will rotate the node clockwise or counter-clockwise; however, I'd like to rotate the node around it's own Y axis (like a dancing ballerina for instance), and I can't seem to find any functionality to do so. What is the best recommended way of doing this?

SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.position = location;
SKAction* action0 = [SKAction scaleXTo:1.0 duration:0.5];
SKAction* action1 = [SKAction scaleXTo:0.1 duration:0.5];
SKAction* action2 = [SKAction scaleXTo:-0.1 duration:0.5];
SKAction* action3 = [SKAction scaleXTo:-1.0 duration:0.5];
SKAction* action = [SKAction sequence:#[action0, action1, action2, action3]];
[sprite runAction:[SKAction repeatActionForever:action]];
[self addChild:sprite];
This will get you what looks like a 3D card flip, but obviously if you're expecting an object to have depth you wont get that programatically with scaling.
Or well less animations (As LearnCocos2D suggested):
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.position = location;
SKAction* action0 = [SKAction scaleXTo:1.0 duration:0.5];
SKAction* action1 = [SKAction scaleXTo:-1.0 duration:0.5];
SKAction* action = [SKAction sequence:#[action0, action1]];
[sprite runAction:[SKAction repeatActionForever:action]];
[self addChild:sprite];

Typically if I am wanting to rotate something like you seem to be describing, and want it to look good, I will do it via sprite frames depicting different degrees of rotation.
You can indeed fake it a little by tweening your xScale property from 1 to -1 , and visa versa.
The xScale trick is feasible for cards maybe (or paper mario), but for a ballerina, not so much. You are looking for 3D rotation of a 2D object, and that's not going to happen via that method.
So if the xScale trick is not feasible for your needs, you'll need to handle it via sprite frames.
Here's an example of a 3d SpriteSheet that would allow you to achieve the rotation you desire :
This animated gif is an example of sprite frames to get that y rotation :

SpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed#"Sprite"];
spriteNode.position = position;
SKAction *action = [SKAction repeatActionForever:[SKAction customActionWithDuration:duration actionBlock:^(SKNode *node, CGFloat elapsedTime) {
node.xScale = sin(2 * M_PI * elapsedTime / duration);
}]];
[self addChild:spriteNode];
However, the sprite looks like having no thickness.

Although SpriteKit doesn't have full 2.5D capabilities (rotating and transforming along X, Y and Z axes), I've developed a work around for rotating SKSpriteNodes around either the X or Y axes. Because SKView is a subclass of UIView, we can rotate it in 3D space the same way we rotate a UIView, using CATransform3D.
The main point is that the sprite that needs to be rotated on the Y axis lives in its own scene, which is presented in its own SKView.
Triggering the following code on in a loop will animate your SKView (containing the SKScene and the SKSpriteNode) on the y axis. You can still have your main scene running at the same time in the background and swap in the sprites that need to be animated on the y axis and then swap them out when animation complete, and remove Y rotation scene. It's not a pretty solution, but it's all there is until Apple adds better 2.5D capabilities.
I've written a tutorial on SpriteKit and 2.5D with sample code if you need more info. http://www.sdkboy.com/?p=283
self.rotAngle -= 10;
float angle = (M_PI / 180.0f) * self.rotAngle/10;
CATransform3D transform3DRotation = CATransform3DMakeRotation(1.0, angle, 0.0, 0.0);
self.mySKView.layer.transform = transform3DRotation;

Related

Sprite Kit. Follow Node Position (Objective C)

Is there a way for a sprite node to follow another sprite node with an SKAction? i am using this but it doesn't seem to fully catch up with my rocket.
move = [SKAction moveTo:_rocket.position duration:1.0];
[beams runAction:move];
I have a rocket firing a laser beam at another rocket, and i want the beam to follow the rocket when it fires, but also slowly catch up to it so it hits it. The code above does follow it but stays slightly behind it.
Here is the code for my two movements for the rockets
SKAction *moveRight = [SKAction moveByX: 100.0 y:0 duration:2];
SKAction *sequence = [SKAction sequence:#[moveRight]];
SKAction *endless = [SKAction repeatActionForever:sequence];
[spaceShip runAction:endless withKey:#"rocketKey"];
Second Rocket
SKAction *moveShip = [SKAction moveByX:150.0f y:0 duration:2.00f];
SKAction *sequence = [SKAction sequence:#[moveShip]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[spaceShipL runAction:repeat];
I've tested it without the SKAction and it would work fine but i need both of the rockets to be always moving.
There's a few ways to achieve this that I know of.
You could use some kind of pathfinding.
You could add your laser beam as a child to rocket2, and move it relative to that rocket. (You would need some extra calculations to ensure that if rocket2 should move towards the laser, the laser won't slow down/move backwards)
Best solution, regularly get the position of rocket2 and have the laser follow it. Rather than using moveTo, use applyForce. This won't be bound by time, and so should be able to catch up to your rocket:
SKSpriteNode *laser = [SKSpriteNode spriteNodeWithImageNamed:#"laser"];
laser.physicsBody = [SKPhysicsBody bodyWithTexture: laser.texture size: laser.texture.size];
static const CGFloat thrust = 0.10;
CGVector thrustVector = CGVectorMake(thrust*cosf(laserDirection),
thrust*sinf(laserDirection));
[laser.physicsBody applyForce:thrustVector];

SpriteKit SKSpriteNode slide

I'm trying determine why slide is happened. I have created 2 nodes, and one of them is moving using SKAction. But when one of them above on other, it does not moving with node under them.
It's a little bit hard to explain, so I have created the video:
https://www.youtube.com/watch?v=F4OuTBVd5sM&feature=youtube_gdata_player
Thanks for your replies!
Parts of code:
CGPoint positionPoint = [valuePoint CGPointValue];
SKSpriteNode *slab = [SKSpriteNode spriteNodeWithTexture:plitaTexture];
slab.name = #"plita";
slab.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:plitaTexture.size];
slab.position = positionPoint;
slab.physicsBody.dynamic = NO;
CGFloat duration = 3.0;
CGFloat firstDuration = duration/(self.size.width/slab.position.x);
SKAction *moveLeftFirstTime = [SKAction moveTo:CGPointMake(0.0 + plitaTexture.size.width/2, positionPoint.y) duration:firstDuration];
SKAction *moveLeft = [SKAction moveTo:CGPointMake(0.0 + plitaTexture.size.width/2, positionPoint.y) duration:5];
SKAction *moveRight = [SKAction moveTo:CGPointMake(self.size.width - plitaTexture.size.width/2, positionPoint.y) duration:5];
SKAction *movingRightLeft = [SKAction sequence:#[moveRight, moveLeft]];
SKAction *moving = [SKAction sequence:#[moveLeftFirstTime,[SKAction repeatActionForever:movingRightLeft]]];
[slab runAction:moving];
[self addChild:slab];
Rabbit init code:
-(instancetype)init {
if (self = [super initWithImageNamed:#"eating-rabbit1"]) {
self.physicsBody = [SKPhysicsBody bodyWithTexture:self.texture size:self.texture.size];
self.physicsBody.allowsRotation = NO;
self.physicsBody.dynamic = YES;
self.physicsBody.friction = 1;
}
return self;
}
The main character is sliding because you are moving the slab by changing its position over time. Imagine you are standing on top of a train that is moving, friction will cause you to move along in the direction of the train. However, if the train magically moved from point A to point B by disappearing and reappearing 1 cm at a time, you will remain at point A. That is what's happening to your main character.
To correct this issue, you will need to move the slabs using physics. Here is an example of how to do that:
slab.physicsBody.affectedByGravity = NO;
slab.physicsBody.dynamic = YES;
// These will help prevent the slab from twisting due to the weight of the main character
slab.physicsBody.angularDamping = 1.0;
slab.physicsBody.mass = 1e6;
// Air resistance will slow slab's movement, set the damping to 0
slab.physicsBody.linearDamping = 0;
Define a set of SKActions that will move the slabs left and right. Adjust the speed and duration variables to achieve the desired motion.
SKAction *moveLeft = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
node.physicsBody.velocity = CGVectorMake(-speed, 0);
}];
SKAction *moveRight = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
node.physicsBody.velocity = CGVectorMake(speed, 0);
}];
SKAction *wait = [SKAction waitForDuration:duration];
SKAction *action = [SKAction sequence:#[moveRight, wait, moveLeft, wait]];
[slab runAction:[SKAction repeatActionForever:action]];
You need to set the slab's physicsBody to be dynamic.
slab.physicsBody.dynamic = YES;
slab.physicsBody.affectedByGravity = NO;
A non-dynamic physicsBody is rendered static and ignores any forces and impulses applied on it. Look up the property in the documentation here.

Position on Node After Rotation

Okay so I have a node that's node.size is a (5,5) rectangle. Its position is (100,100), and its anchor point is the default (0.5, 0.5).
I tried to find the point at the farthest right, but still centre Y. Naturally this returned the point (102.5, 100). This is perfect. I found the point by simply using CGPointMake(node.position + node.size.width/2, node.position);
This works great, but the thing is I want to rotate the node while still knowing what that point is. I was wondering if their was some kind of math I could perform using the nodes zRotation in order to find the new point (the same point on the nodes frame) after the rotation.
To make it clear I want to know where the point that is originally 102.5, 100, is after the node rotates.
Thank you all very much in advance for all of your answers
Let r be the distance from the anchor point of the sprite to the point of interest. In your case,
r = sqrt(dx*dx+dy*dy) = 2.5;
where dx = 2.5 and dy = 0.
Also, let theta be the rotation angle, which is initially zero. We now have a point in polar coordinates, (r, theta), that represents the point (2.5, 0). We can now rotate the point about the sprite's anchor point by changing theta. After rotating the sprite, we convert (r, theta) to cartesian coordinates by
x = r * cos(theta);
y = r * sin(theta);
and add the sprite's position to convert the point to scene coordinates.
x = x + sprite.position.x;
y = y + sprite.position.y;
EDIT: I think this is what you are trying to implement.
#interface MyScene()
#property SKSpriteNode *object1;
#property SKSpriteNode *object2;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size]) {
_object1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(10,5)];
_object1.position = CGPointMake(100, 100);
[self addChild:_object1];
SKAction *wait = [SKAction waitForDuration: 2.5f];
SKAction *rotate = [SKAction rotateByAngle:3*M_PI/4 duration:2.0f];
SKAction *addObject = [SKAction runBlock:^{
_object2 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(10,5)];
_object2.zRotation = _object1.zRotation;
CGFloat dx = _object1.size.width / 2;
CGFloat dy = 0;
CGFloat r = sqrtf(dx*dx + dy*dy);
CGFloat x = r*cosf(_object1.zRotation);
CGFloat y = r*sinf(_object1.zRotation);
_object2.position = CGPointMake(_object1.position.x + 2*x, _object1.position.y + 2*y);
[self addChild:_object2];
}];
SKAction *seq = [SKAction sequence:#[wait, rotate, addObject]];
[_object1 runAction:seq];
}
return self;
}
Okay, I had to post code so I had to make this an answer.
So I tried your method 0x141E, and it worked perfectly. The only problem I am having is that whenever the angle is not a multiple of 0.5PI it seems to calculate improperly. Maybe its just the way that I coded it though. The goal of my code was to place one object right beside another by measuring the distance between a left most, middle point of one object with the other objects right most, middle point. This way they would be touching left side with right side... Side-by-Side basically. I placed one object first and added the second using your given algorithm, but the result as I stated above got a little funky in between angles divisible by 0.5 PI. Here's what I've done, it should work if you import the code straight into XCode if you want to see what I am talking about (I'm sorry but I don't have enough rep to attach images or I would give a screenshot):
//This is the MyScene implementation file
#interface MyScene()
{
SKSpriteNode *_object1;
SKSpriteNode *_object2;
}
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size]
{
object1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(10,5)];
[self addChild:object1];
//The wait is just to make it feel smoother and not so sudden when starting up the simulator
SKAction *wait = [SKAction waitForDuration: 2.5f];
SKAction *rotate = [SKAction rotateByAngle:0.25*M_PI duration:2.0f];
SKAction *addObject = [SKAction runBlock:^{
_object2 = [spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(10,5)];
//I set the rotation of the second object to the rotation of the first so they will always be touching side-by-side
_object2.zRotation = _object1.zRotation;
//I used the full width and heigth rather than half to save time. The two ojects are of equal width and height so instead of finding both distances and adding them together I just doubled the distance
CGFloat r = sqrtf(_object2.frame.size.width*_object2.frame.size.width+_object2.frame.size.height*_object2.frame.size.height);
CGFloat x = r*cosf(_object2.zRotation);
CGFloat y = r*sinf(_object2.zRotation);
object2.position = CGPointMake(_object2.position.x + x, _object2.position + y);
[self addChild:_object2];
}
SKAction *seq = [SKAction sequence:#[wait, rotate, addObject]];
[self runAction:seq];
}
}
#end
Thank you again for all of your help thus far, and thank you even more for all of your potential future help.

Moving Sprite in a Circle

I am trying to move a Sprite ("Spaceship") in a circle, where the click was performed.
Here is the code that is triggered when a click is performed:
- (void)newSpaceShipAt: (CGPoint)location
{
SKSpriteNode *hull = [[SKSpriteNode alloc]initWithImageNamed:#"Spaceship"];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, location.x, location.y, self.size.width / 5.0, 0, 360, YES);
SKAction *move = [SKAction sequence:#[
[SKAction followPath:path duration:1],
]];
[hull runAction:[SKAction repeatActionForever:move]];
hull.name = #"Spaceship";
hull.scale = 0.5;
hull.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:MAX(hull.size.width / 2.0, hull.size.height / 2.0)];
hull.physicsBody.dynamic = NO;
[self addChild:hull];
}
I was the Spaceship to move in the circle indefinitely about the point "location", which is where the click was performed. However, what happens is that after one successful revolution, the spaceship moves "location" relative from the click. For example, if the click was performed on (50, 100) then after one revolution it will move to (100, 200), relative to the scene, and it will carry on.
What can I do to fix this problem? Or if I my approach is totally wrong, what would be a better one?
Also, what is the best way to draw a trail (a simple line), to the movement of the sprite?
From the documentation of followPath:duration:...
Discussion
Calling this method is equivalent to calling the
followPath:asOffset:orientToPath:duration: method, passing in YES to
both the offset and orient parameters.
The behaviour of this method will tell the hull to orient to the path and also tell it to use the path as an offset. i.e. it will move 50,100 each time it runs.
Use this method instead...
[SKAction followPath:path asOffset:NO orientToPath:YES duration:1.0];
That should solve your problem.
Also, get rid of that sequence.
Do this...
SKAction *moveAction = [SKAction followPath:path asOffset:NO orientToPath:YES duration:1.0];
[hull runAction:[SKAction repeatActionForever:moveAction]];

How to set horizontal back and forth movements on a sprite?

I am writing a game where i'm spawning some sprites that move on the same horizontal plane, i.e., their y-coordinate is always the same. I'm not sure physics, but rather only SKActions.
Initially I wrote this to spawn the sprite at location Y, and move to a point off the screen in a randomly chosen direction. Now, I'd like this sprite to instead turn around and go backwards until it hits the opposite end of the screen, where I wan it to again turn around, etc.
Initially I have the action set up as such :
SKAction * actionMove = [SKAction moveTo:destination duration:duration];
where destination and duration are basically randomly generated. This obviously moves the sprites in one direction only.
what's an elegant way to I make this an un-ending loop of sprites turning around and repeating their path over and over again?
I figured out how to do this with action sequences. I initially tried setting this up in the
update
method of the scene, and that made everything terribly slow, as expected. So I set up the following sequence of actions (I have the sprites moving back and forth at the same y-coordinate, so they just need to turn around the continue their journey)
CGPoint destination1, destination2;
if (sprite.direction == LeftDirection)
{
destination1 =CGPointMake(-sprite.size.width/2, sprite.position.y);
destination2 = CGPointMake(self.frame.size.width + sprite.size.width/2, sprite.position.y);
}else {
destination1 =CGPointMake(self.frame.size.width + sprite.size.width/2
, sprite.position.y);
destination2 = CGPointMake(-sprite.size.width/2, sprite.position.y);
}
int duration = [sprite getDuration]; //this generates a random duration amount
// Create the actions
SKAction * actionMove1 = [SKAction moveTo:destination1 duration:duration];
//turn the sprite around
SKAction * rotateAction = [SKAction runBlock:^{
sprite.xScale = sprite.xScale * -1;
sprite.direction = (sprite.direction == LeftDirection) ? RightDirection : LeftDirection;
}];
//reverse the movement
SKAction * actionMove2 = [SKAction moveTo:destination2 duration:duration];
SKAction * actionMove = [SKAction sequence:#[actionMove1, rotateAction, actionMove2, rotateAction]];
SKAction * repeat = [SKAction repeatActionForever:actionMove];
once this is created, we run the repeat action on the sprite. And voila! continuous movement that changes direction.