19
votes

I have a game where I would like to move an SKNode left or right depending on if the user touches the left or right side of the screen. How to I apply a constant movement to a node while the user is touching the screen?

10
It's just impulse = distance / deltaTime .. that's it.Fattie

10 Answers

37
votes

You really have three options for preserving the velocity.

One is to simply keep reseting the velocity back to your desired amount every update. In this case I chose 200,200. This will make your motion completely static.

-(void)update:(NSTimeInterval)currentTime {
    node.physicsBody.velocity=CGVectorMake(200, 200);
}

The second option is to preserve the velocity but leave room for motion distortions using a rate factor. The example below would preserve the velocity to 200,200 but not instantaneously. Instead it would depend on the rate. This will make your motion much more realistic and a little less static. For example, lets say you have a character moving at constant speed and you change direction, you will see a tiny acceleration as it changes to the new constant speed value, making the change in motion less static and more dynamic. I highly recommend this option.

-(void)update:(NSTimeInterval)currentTime {

    CGFloat rate = .05;
    CGVector relativeVelocity = CGVectorMake(200-node.physicsBody.velocity.dx, 200-node.physicsBody.velocity.dy);
    node.physicsBody.velocity=CGVectorMake(node.physicsBody.velocity.dx+relativeVelocity.dx*rate, node.physicsBody.velocity.dy+relativeVelocity.dy*rate);

}

The third option you have is to just set the velocity or apply an impulse ONCE (as opposed to every frame like we did above), but turn off its gravity and air resistance. The problem with this approach is that it will not always preserve your velocity. It will only keep constant speed until acted on by an external force (i.e. friction, colliding with another body, etc). This approach works for cases where you want some object to move at constant speed but still remain completely dynamic. I don't recommend this option at all if you are looking to control and/or preserve your objects velocity. The only scenario I could think of for this is maybe a game with moving asteroids that float around the screen at constant speed but are still affected by external forces (i.e. 2 asteroids collide which will result in a new constant speed).

node.physicsBody.velocity=CGVectorMake(200, 200);
node.physicsBody.linearDamping = 0; //You may want to remove the air-resistance external force.
node.physicsBody.affectedByGravity = false; //You may want to remove the gravity external force

Here is an example project I wrote in Swift using option 2. I tried to match your description. It simply moves a node left or right at constant speed. Be sure to experiment with the rate factor.

import SpriteKit
class GameScene: SKScene {
    var sprite: SKSpriteNode!
    var xVelocity: CGFloat = 0
    override func didMoveToView(view: SKView) {
        sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.physicsBody.affectedByGravity = false
        sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
        self.addChild(sprite)
    }
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        let touch = touches.anyObject() as UITouch
        let location = touch.locationInNode(self)
        if (location.x<self.size.width/2.0) {xVelocity = -200}
        else {xVelocity = 200}
    }
    override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!)  {
        xVelocity = 0
    }
    override func update(currentTime: CFTimeInterval) {
        let rate: CGFloat = 0.5; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
        let relativeVelocity: CGVector = CGVector(dx:xVelocity-sprite.physicsBody.velocity.dx, dy:0);
        sprite.physicsBody.velocity=CGVector(dx:sprite.physicsBody.velocity.dx+relativeVelocity.dx*rate, dy:0);
    }
}

Note: Try to stay away from SKActions for real-time motion. Only use them for animations.

By real-time motion, I mean motion in which the position and velocity of your objects are important to you at each step in their simulation. Whereas in animation, you are really only concerned with the start and end points over some duration. For example, a game with moving characters requires real-time motion. You need to be able to monitor at each step in the simulation the positions and velocities of your characters, and potentially make adjustments at certain intervals. But for simpler things like moving UI elements, you don't need to track their motion at each step, so use animation

5
votes

Add an ivar:

CGPoint velocity;

Set the motion, for example moving to right:

velocity.x = 5;

Integrate velocity every step:

-(void) update:(NSTimeInterval)currentTime
{
    CGPoint pos = self.position;
    pos.x += velocity.x;
    pos.y += velocity.y;
    self.position = pos;
}

PS: actions are notoriously bad for continuous operations, specifically movement, since they are time-based ie supposed to end after a certain duration. But what if the action's time runs out before it reaches its destination?

1
votes

I'm setting the velocity.dx directly, however I don't recommend this approach since it ignores the previous velocity completely.

public func setSpeed(speed: CGFloat) {
        physicsBody.velocity.dx = speed
}

I'd like to provide you with a solution that works better with the existing spritekit physic engine but I don't have one, I'll offer a bounty for it since I need a solution for this too.

For touches in Swift you can do:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        player.setSpeed(100) // You can use touches.anyObject().position to see the touch position and implement left/right movement
}
1
votes

Here's exactly how you do it mathematically ...

so, in this example a finger is wanting to move the item.

That defines exactly how much you want it to move in the frametime.

enter image description here

(In many (certainly not all) situations, you will have the mass as a normalizing "one kilogram". (If your physics is working on "abstract" bodies - you're pushing around "icons", "blobs" or the like - do that, use 1.0 kg. Naturally, if your physics is tanks, soccer balls, or whatever, you must use real world values.) With a "one kilo" mass, that's how you do it.)

Enjoy!

0
votes

Depends if you want to use physics forces or actions.

Something like this

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

for (UITouch *touch in touches) {

    UITouch* touch = [touches anyObject];
    CGPoint loc = [touch locationInNode:self];

    if (loc.x > self.frame.size.width * 0.5) {
        NSLog(@"move right");
        // move the sprite using an action
        // or set physics body and apply a force
    }
    else {
        NSLog(@"move left");

    }
}
}

If you decide to use actions, in touchesEnded remove all the actions from the sprite you are moving.

If you are applying a small forces in touchesBegan the sprite should come to a stop.

0
votes

Use the velocity property of the physicsBody. The variable right is the point of the touch minus the object (in this case self) and provide the associated velocity

-(void)segmentedTouchBegan:(NSValue *)p{
    CGPoint point = [p CGPointValue];
    int right = (point.x - self.screenWidth/2.0f)/ABS((point.x - self.screenWidth/2.0f)); //1 if right -1 if left
    self.physicsBody.velocity = CGVectorMake(SHIFT*right, 0);
}
-(void)segmentedTouchMoved:(NSValue *)p{
    CGPoint point = [p CGPointValue];
    int right = (point.x - self.screenWidth/2.0f)/ABS((point.x - self.screenWidth/2.0f)); //1 if right -1 if left
    self.physicsBody.velocity = CGVectorMake(SHIFT*right, 0);
}
-(void)segmentedTouchEnded:(NSValue *)p{
    self.physicsBody.velocity = CGVectorMake(0, 0);
}
0
votes

If your SKNode has physicsBody, apply force to it:

@interface GameScene()

@property (nonatomic) CGVector force;

@end

@implementation GameScene

- (void)update:(CFTimeInterval)currentTime {
    [self.player.physicsBody applyForce:self.force];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.sideForce = CGVectorMake(3000, 0);
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    self.sideForce = CGVectorMake(0, 0);
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    self.sideForce = CGVectorMake(0, 0);
}

@end

If it hasn't, use LearnCocos2D's answer.

0
votes

It's very simple, for example movement 100px per second:

func touchUp(atPoint pos : CGPoint)
{
    node.removeAllActions()
    let pxPerSecond : CGFloat = 100.0;
    var length = (pos - node.position).length()
    var totalSeconds = length / pxPerSecond;
    let moveAction = SKAction.move(to: pos, duration: Double(totalSeconds))
    node.run(moveAction)
}

override of substraction and normalize operations you can get inside this article https://www.raywenderlich.com/71-spritekit-tutorial-for-beginners

-1
votes

if you have to stop your motion at fixed position from left to right.

This for Swift and objective-c.

you have to use in physicsBody:

node.position = CGPoint(X: ..... , Y: .....) node.physicsBody = nil

-3
votes
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */

    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self];

    if (touchLocation.x > 50) {
        // Do whatever..
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(movePlayer:) userInfo:nil repeats:YES];
        NSLog(@"Right");
    }
    else if (touchLocation.x < 50){
        ////// use another NSTimer to call other function////
        NSLog(@"left");
    }
}


-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{

    if (_timer != nil) {
        [_timer invalidate];
        _timer = nil;
    }
}
-(void)movePlayer:(id)sender
{
    hero.physicsBody.velocity = CGVectorMake(400, 0);
}