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]];
Related
As much as Stack is great source I spend some time reading the Objective C docs for SpriteKit. I wanted to create a run block of actions that manipulate different "Characters" SKSprite Nodes in a particular scene, "For example if you pick a particular character, and you x number of rounds, with this character, the character will disappear over time. Would this particular behavior need to be defined, in both GameScene Class, and HUD Scene? I saw this example in the Docs and this was my attempt to convert from OjC to Swift...
SKAction *moveUp = [SKAction moveByX:0 y:100.0 duration:1.0];
SKAction *zoom = [SKAction scaleTo:2.0 duration:0.25];
SKAction *wait = [SKAction waitForDuration: 0.5];
SKAction *fadeAway = [SKAction fadeOutWithDuration:0.25];
SKAction *removeNode = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:#[moveUp, zoom, wait, fadeAway, removeNode]];
[node runAction: sequence];
Then Swift:
var moveUp: SKAction = SKAction.moveByX(0, y: 100.0, duration: 1.0)
var zoom: SKAction = SKAction.scaleTo(2.0, duration: 0.25)
var wait: SKAction = SKAction.waitForDuration(0.5)
var fadeAway: SKAction = SKAction.fadeOutWithDuration(0.25)
var removeNode: SKAction = SKAction.removeFromParent()
var sequence: SKAction = SKAction.sequence([moveUp, zoom, wait, fadeAway, removeNode])
node.runAction(sequence)
This is just a test scenario, yet I am little confused on how to apply to specific node. Would this be called in touches began section where one would break out touch events for that particular node. Any feed back would be greatly appreciated.
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];
I am simply trying to add a sprite "light" on top of "spaceship". So as you can see below that I added light as the child of the spaceship, however, the light is appearing below the spaceship, as seen in the picture below. Can anyone tell me why this is happening, and how can I fix it?
- (void)newSpaceshipAtLocation:(CGPoint)location{
SKSpriteNode *hull = [[SKSpriteNode alloc]initWithImageNamed:#"Spaceship"];
hull.position = location;
hull.name = #"Spaceship";
hull.scale = 0.5;
SKSpriteNode *light = [self lights];
light.position = CGPointMake(hull.size.width / 5.0, hull.size.height/5.0);
[hull addChild:light];
[self addChild:hull];
}
- (SKSpriteNode *)lights{
SKSpriteNode *light = [[SKSpriteNode alloc]initWithColor:[NSColor yellowColor] size:CGSizeMake(50.0, 50.0)];
SKAction *blink = [SKAction sequence:#[
[SKAction fadeOutWithDuration:0.5],
[SKAction fadeInWithDuration:0.5],
]];
[light runAction:[SKAction repeatActionForever:blink]];
light.name = #"light";
return light;
}
Try setting the zPosition property on light. If you set it to anything higher than the hull sprite, it should work.
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.
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;