touch and move sprite doesn't track well - ios

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.

Related

How to create button in sprite kit iOS (similar to toggle button)

I am trying to make a button in sprite kit using SKSpriteNode. I want the button image to change when it is pressed and revert back to old image as soon as the press ends. What i have done till now is following:-
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.startTouch = [[touches allObjects][0] locationInNode:self ];
for (UITouch *touch in touches){
CGPoint position = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:position];
if ([node.name isEqualToString:#"missileButton"]) {
TEMissileButtonNode *button = (TEMissileButtonNode*) node;
button.isPressed = YES;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches){
CGPoint position = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:position];
if ([node.name isEqualToString:#"missileButton"]) {
TEMissileButtonNode *button = (TEMissileButtonNode*) node;
button.isPressed = NO;
}
}
}
inside the update method i am calling this method to check if the touch has ended
-(void)changeMissileButton{
if (self.missileButton.isPressed) {
[self.missileButton addMoreMissileButtons];
[self.missileButton setTexture:[SKTexture textureWithImageNamed:#"missileButtonPressed"]];
}else{
[self.missileButton setTexture:[SKTexture textureWithImageNamed:#"missileButtonDeselected"]];
[self.missileButton hideMissileButtons];
}
}
The issue is that the touch doesn't get registered at times. Sometimes it works the way i want. When i touch it, its texture changes and when i remove my finger, the texture reverts back to old texture. But most of the times, the button doesn't react to my touch. Am i missing something?
Use touchesBegan, touchesMoved, touchesEnded.
touchesBegan - check if the button (SKSpriteNode) contains the touch. If so, change you button texture.
touchesMoved- if button does not contain touch, change texture back.
touchesEnded- if touch stayed within button during touchesMoved, change texture back.
Above is the logic for how to efficiently accomplish what you are trying to do. Yes, it is annoying Spritekit decided to abandon buttons.

How to know if an sprite stopped being touched because it moved?

I need to know if the user is touching a sprite. I can easily tell if he lifts his finger or moves his finger out:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"button"]) {
// Sprite stopped being touched
return;
}
}
However I'm clueless about how to know if the touch ends because the sprite moves out while the user keeps his finger in place. I image I should somehow check inside my update method but I don't know how:
// Called before each frame is rendered
-(void)update:(CFTimeInterval)currentTime {
// Check if the user is holding the sprite
}
How can this be done?
I think you are asking for something like this below. There are myriad ways to come at the problem, but here's a basic way to think about it. Depending on the actual complexity of your game, you might need to do some state checking to protect performance, but you get the gist:
//in touchesBegan and touchesMoved update a property:
_lastTouchPoint = //the touch point in scene, etc, blah blah
//if you care about a specific sprite you could also store it:
_someMovingSprite = theSpriteIJustTouchedOrCareAbout;
//in update
if (CGRectContainsPoint(_someMovingSprite.frame, _lastTouchPoint)) {
//the touch is inside the rect of the sprite
}
//OR
NSArray *spritesContainingTheTouchPoint = [self nodesAtPoint:_lastTouchPoint];
if (spritesContainingTheTouchPoint.count == 0) {
//no sprites are being touched
}else{
for (SKSpriteNode *spriteContainingTouch in spritesContainingTheTouchPoint) {
//do something to sprite
}
}
-(void)update:(CFTimeInterval)currentTime {
CGPoint location = [_lastTouchPoint locationInNode:self];
if (!CGRectContainsPoint(_sprite.frame, location)) {
#NSLog(#"Sprite Released");
}
}
Apple has the documentation on every touch event they provide. The method you're looking for is touchesEnded
Handle it the same way you handle touchesMoved and call any methods you need in order to notify the node that it is no longer being touched.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"button"]) {
// Notify the node that the touch has stopped
}
}

iOS - How do I perform an action when the user swipes the screen?

I want to shift the position of some UILabels when the user swipes the screen--specifically, move them the same distance the user swiped their finger.
How would I go about detecting and implementing this?
Thanks
Override touchesBegan:withEvent and touchesMoved:withEvent:. Keep track of the starting location in touchesBegan:withEvent. If repeated calls to touchesDragged (it is called while the drag is happening) show that you are moving fast enough for the action to be a swipe versus just a drag, use the difference between the starting location and the current touch location to animate changing the UILabels.
You can do that by using following methods for UIView.
In touchesBegan you should store the position where the user first touches the screen. So you can identify the left or right swipe when touches ends.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.startPoint = [touch locationInView:self];
}
Record the final position on touchesEnded method. By comparing this two positions you can determine the direction of movement.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint endPosition = [touch locationInView:self];
if (startPoint.x < endPoint.x) {
// Right swipe
} else {
// Left swipe
}
}
I hope this helps.

SKAction moveTo goes up when should go down

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.

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

Resources