SKAction moveTo goes up when should go down - ios

I was following the SpriteKit tutorial here to create a simple sprite kit shooter, where you make a space ship that shoots lasers at asteroids.
I want to make the lasers (each laser is an SKSpriteNode) move to the point where I click. I am getting the touch correctly within the method touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event. However when I setup an SKAction on the SKSpriteNode, it moves in the y direction OPPOSITE of where I click. Ie image the window has width (x) 500 and height (y) 400. When I touch the screen at the coordinate (300, 100), the lazer appears to move to the coordinate (300, 300).
I've verified that the coordinates in touchLocation are correct.
FYI I have only used the iPhone simulator for this - but that shouldn't matter, should it?
Relevant code snippet:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
SKSpriteNode *shipLaser = [_shipLasers objectAtIndex:_nextShipLaser];
shipLaser.position = CGPointMake(_ship.position.x+shipLaser.size.width,_ship.position.y+0);
shipLaser.hidden = NO;
[shipLaser removeAllActions];
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
SKAction *laserMoveAction = [SKAction moveTo:touchLocation duration:0.5];
SKAction *laserDoneAction = [SKAction runBlock:(dispatch_block_t)^() {
shipLaser.hidden = YES;
}];
SKAction *moveLaserActionWithDone = [SKAction sequence:#[laserMoveAction,laserDoneAction]];
[shipLaser runAction:moveLaserActionWithDone withKey:#"laserFired"];
}
EDIT: I wonder if it might have to do with the fact that SprikeKit's coordinate system originates from the bottom left, while UIKit originates from the top left??

You want the touchLocation in the SKScene, not the UIView.
change :
CGPoint touchLocation = [touch locationInView:self.view];
to :
CGPoint touchLocation = [touch locationInNode:self];

The problem was that SprikeKit's coordinate system originates from the bottom left, unlike UIKit's coordinate system which originates in the top right. So the coordinates for the touch event were in the UIkit's coord system, and the SKNode was moving in SpriteKit's system. Once I understood this difference it was pretty easy to fix.

Related

Spritekit how to run an SKAction depending on where a touch and hold is at

So I'm trying to implement a method into a game where if a player has their finger touching the screen at lets say 50 on the y axis or lower, an SKAction will run to move the character sprite down, and if their finger is touching above the 50, another action will run moving the sprite up. I don't know how you have it recognize a touch and hold though. Help please.
You will need to capture the touch event of the user in order to pull this off. I suggest checking out the excellent tutorial provided by RayWenderlich at: Animating Textures and Moving Them With Touch Events
If you don't have time for that this is what your code might look like:
First the knowledge that you will need is that the two most generic touch events are the:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
and the:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
Basically the touches began method is called when the user places their finger on the screen whereas the touches ended method will be triggered when the user takes their finger off the screen.
For this example I will use touchesEnded, but feel free to change it if you want, its as easy as switching the "Ended" with "Began".
All of the code I am about to show you will take place within the scenes implementation file:
If you just want the sprite to move up when above a certain point and down when below a certain point use the following:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//used to hold the location to travel
CGPoint moveLocation;
//check if the Y location is less than 50
if(location.y < 50)
{
//the the location to the bottom of the screen
moveLocation = CGPointMake(sprite.position.x, 0);
}
//and then if its more than 50
else
{
//set the locaiton to the top of the screen
moveLocation = CGPointMake(sprite.position.x, self.frame.size.height);
}
//move the sprite
//Check if their already moving
if(sprite actionForKey:#"moving")
{
//If they are stop them so they can move in the new direction
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:5.0f];
//remember to give the action a key so that we can check for it during the above if statement
[sprite runAction:moveAction withKey:#"moving"];
}
And there you have it. The only flaw is that it will always take 5 seconds to reach the location regardless of your distance from it, read on if you would like hints on how to correct that, or if you would like to see how to make the sprite travel to any touched location on the screen.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//move the sprite
//Check if their already moving
if(sprite actionForKey:#"moving")
{
//If they are stop them so they can move in the new direction
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:5.0f];
//remember to give the action a key so that we can check for it during the above if statement
[sprite runAction:moveAction withKey:#"moving"];
}
That code will make it so that for 5 seconds the sprite will move to the location touched on the screen. Just remember to replace the instances of "sprite" with the name of your SKSpriteNode variable. For more complex movement try the following:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//set the velocity of the subject
float velocity = self.frame.size.width/3.0;
//The above will ensure that it will take the sprite 3 seconds to travel the distance of the screen
//Determine the difference between the point touched and the sprites position
CGPoint moveDifference = CGPointMake(location.x - sprite.position.x, location.y - sprite.position.y);
//Use pythagorean theorem to figure out the actual length to move
float distanceToMove = sqrtf(moveDifference.x * moveDifference.x + moveDifference.y*moveDifference.y);
//Use the distance to travel and the velocity to determine how long the sprite should move for
float moveDuration = distanceToMove / velocity;
//and finally move the sprite
if(sprite actionForKey:#"moving")
{
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:moveDuration];
[sprite runAction:moveAction withKey:#"moving"];
}
That will set a velocity for the sprite, determine the length of the travel, and how long it will take to get to the new location.
If you want to involve textures I highly suggest reading the link I provided.
Upon request I would be glad to offer examples using forces to control movement speed as well.
I hope that helps, let me know if there are any problems I wasn't in a position where I could run the code but I am sure that it is fine.

touch and move sprite doesn't track well

I'm building a game using Sprite Kit that needs quick precise movements following the user's touch. I need to detect if the user has touched on the "Player" in the view, and if they have, when they move, the player sprite needs to move with the touch accordingly.
I have this working now, however, it's not very precise... the more movement input (without lifting your finger), the more offset the sprite gets from the touch location.
Here's the code I'm using right now.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if (self.isFingerOnPlayer) {
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
// Calculate new position for player
int playerX = player.position.x + (touchLocation.x - previousLocation.x);
int playerY = player.position.y + (touchLocation.y - previousLocation.y);
// Limit x and y so that the player will not leave the screen any
playerX = MAX(playerX, player.size.width/2);
playerX = MIN(playerX, self.size.width - player.size.width/2);
playerY = MAX(playerY, (player.size.width/2)-3+inBoundsOffset);
playerY = MIN(playerY, (self.size.height - ((player.size.width/2)-3) - inBoundsOffset));
// Update position of player
player.position = CGPointMake(playerX, playerY);
}
}
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
SKPhysicsBody* body = [self.physicsWorld bodyAtPoint:touchLocation];
if (body && [body.node.name isEqualToString: #"player"]) {
NSLog(#"Began touch on player");
self.isFingerOnPlayer = YES;
}
}
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
self.isFingerOnPlayer = NO;
}
It detects the touch location, checks to make sure that you're touching the player sprite, if you are, then when you move, so does the sprite... but it can get off quite quickly if you're slinging your finger around (as playing this game will cause the player to do).
Can anybody suggest a more accurate way of accomplishing this, that will keep the sprite under the user's finger even when moving around a lot without lifting your finger?
You have to keep in mind that -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event ONLY gets called when the moving finger writes (sorry couldn't help the Inspector Clouseau reference).
In effect what happens is that the user can very quickly move his/her finger from one location to the next and as soon the the finger is lifted, your position updates stops.
What I suggest you do is create a CGPoint property and have -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event store the location in the CGPoint property.
Then in your -(void)update:(CFTimeInterval)currentTime you add the code that actually moves the player to the finger coordinates. This should make things a lot smoother.

Determining whether a point lies within the boundary of a physics body in Sprite Kit

I'm using Sprite Kit to create a game involving balloons. The balloon image is a long rectangle (dotted line) but I've defined a physics body around the balloon itself using a polygon (solid line). I'd like players to be able to "pop" the balloon. I'm using the following block of code to achieve this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
[node removeFromParent];
SKAction *pop = [SKAction playSoundFileNamed:#"pop.wav" waitForCompletion:NO];
[self runAction:pop];
}
}
Unfortunately, when there are several balloons in the bunch, this method can result in a single tap popping multiple balloons. This is because the sprite images overlap (even though the physics bodies do not). There is also the undesired effect that a tap on the string will have the same effect as a tap on the balloon.
I have access to the coordinates of the touch point, but is it even possible to detect whether a point lies within the area defined by the physics body (as opposed to the node)?
Use the physics world's bodyAtPoint: method:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:location];
bodyAtPoint is buggy but you have a working alternative.
You can create rays using baloon polygon path.
[self.physicsWorld enumerateBodiesAlongRayStart:rayStart end:rayEnd
usingBlock:^(SKPhysicsBody *body, CGPoint point,CGVector normal, BOOL *stop) {
if(body.categoryBitMask & baloonCategory){
SKPhysicsBody *contactBody=body;
CGPoint contactPoint=point;
*stop=YES;
}
}];
I looked but wasn't able to find a way of checking whether a touch is made within the physicsBody of a sprite. I would suggest separating the string and the ballon sprites.
You can try any the following:
Using separate sprite nodes for the balloon and the string and connecting them using a SKPhysicsJoint.
Subclass SKSpriteNode and add two sprites, i.e. the balloon and the string as children of the Sprite. Then u can check whether a touch made on the sprite is on the ballon or not.
Additionally for the multiple ballon pop problem, you can modify your code like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
//Instead of popping all nodes within the touch, pop only the first object.
if ([nodes count] > 0)
{
SKNode *node = [nodes firstObject];
[node removeFromParent];
SKAction *pop = [SKAction playSoundFileNamed:#"pop.wav" waitForCompletion:NO];
[self runAction:pop];
}
}

How to rotate something using spritekit relative to user touch coordinates

I want to rotate a gun based on the user dragging their finger on the screen. I figure i will need my cartesian points in polar coordinates and that i will need a long press gesture which is both things that i have. I am just wondering how i would go about programming this? sorry i'm really new to sprite kit i have read all of apple's documentation and i'm having a hard time finding this. My anchor point is 0,0.
-(void)shootBullets:(UILongPressGestureRecognizer *) gestureRecognizer
{
double radius;
double angle;
SKSpriteNode *gun = [self newGun];
}
i figured out how to get the angle but now my zrotation wont work. Like i nslog and the angles are right but when i click gun.zrotation and i tap nothing happens? please help i'm getting uber mad.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
SKSpriteNode *gun = [self newGun];
self.gunRotation = atanf(location.y/location.x)/0.0174532925;
gun.zRotation = self.gunRotation;
NSLog(#"%f",gun.zRotation);
}
This is a very old question, but here is what I did - hopefully it will help someone out there:
#import "MyScene.h"
#define SK_DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) * 0.01745329252f) // PI / 180
Then add this:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint positionInScene = [touch locationInNode:self];
float deltaX = positionInScene.x - gun.position.x;
float deltaY = positionInScene.y - gun.position.y;
float angle = atan2f(deltaY, deltaX);
gun.zRotation = angle - SK_DEGREES_TO_RADIANS(90.0f);
}
}
Check out the answers I got to this question about how to implement a rotary knob, people have already figured out all the geometry, all you need to do is implement a transparent rotary knob, get its angle and pass it to your sprite kit object, using action rotate

SKAction move forward

How do I get a SKNode to move forward?
I've been able to get the node to move in a fixed direction using [SKAction moveByX:y:duration:] but I want it to move in a direction relative to the direction its facing.
I am looking to make each node turn 45 degrees then "walk" forward 50 pixels.
It seems simple enough I just am not able to find it.
This will rotate, then towards where you tap
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
CGPoint diff = CGPointMake(location.x - _myPlayer.position.x, location.y - _myPlayer.position.y);
CGFloat angleRadians = atan2f(diff.y, diff.x);
[_myPlayer runAction:[SKAction sequence:#[
[SKAction rotateToAngle:angleRadians duration:1.0],
[SKAction moveByX:diff.x y:diff.y duration:3.0]
]]];
}
}
}
I have thought about this as well. I want to make a node that can seem to be turning before proceeding. One way I have considered doing this is to have a child node attached the the "forward-most" portion of the node that is moving in the scene. Then I would calculate the distance of the tap form the child node. Does that make sense? Essentially, I would be able to calculate the front of the node from a tap anywhere in the scene.

Resources