Constant movement in SpriteKit
Asked Answered
R

10

19

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?

Rett answered 10/10, 2013 at 3:34 Comment(1)
It's just impulse = distance / deltaTime .. that's it.Barrie
A
38

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

Arse answered 26/11, 2013 at 0:49 Comment(13)
Can you distinguish between real-time motion and animations?Quintin
Sure. 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 animationArse
What about adjusting the speed of an SKAction? How does that differ from sprite.physicsBody.velocity?Quintin
The speed property of an SKAction is the speed of the animation, not the speed of the physics body.Arse
Unless you mean adjusting the speed of a physics body using an SKAction. Then the difference is that setting the speed of a physics body using an SKAction requires a duration (because it's an animation). Whereas by adjusting the speed of a physicsBody, you are instantly modifying the value so you get complete control at each step in the simulation. It would also be inefficient and complex to run actions each frame to make adjustments to your physics body.Arse
hi @gfrs, this has been really useful. I'm probably going to use your second solution. Why didn't you recommend applyForce? In my case I tried it while clamping the velocity afterwards, but I felt it gave the physics engine too much control over the player movementOlly
Thanks. I didn't recommend applyForce/applyImpulse because its difficult to understand what velocity values you are setting when using those methods. Not to mention they change with the mass of the object. I find them to be an indirect means to just setting the velocity with exact values. I also try to make the engine avoid as many calculations as possible for performance reasons. But don't get me wrong, there are certainly scenarios where you can use them, but for tightly monitoring and adjusting the position and velocity of an object, I don't recommend them just because they add complexity.Arse
Also for option 2, you can try adjusting the rate factor at certain events in your game. For example, maybe when a certain event occurs you want your character to be more dynamic, then quickly switch back to being more static when the event ends, etc.Arse
@EpicByte what if you need real-time motion, but not constant velocity, i.e., need to move a sprite a set distance (think frogger and "hopping" from point A to point B)? do you still advise against skaction?Pursley
@Pursley Depends, do you need to know the position of the node at each step in the simulation as the node travels from point A to point B? If you only care about start and end points then animation using SKActions could work here. By the way, real-time motion does not mean constant velocity, you can choose to have accelerations as well. I tend to always use real-time motion for character and npc movement because I can control exactly how I want my nodes to move over time, and I can make adjustments at any step in the simulation, which makes real-time motion much more dynamic.Arse
@Pursley Remember, all motion that you can do with SKActions can be accomplished using real-time motion. But if you can get away with using SKActions for your character movement and your game gets the job done, then I see no problem using SKActions. It's just that you'll find that this is unfortunately not always the case.Arse
@EpicByte How can you ensure sudden and controlled stops with real-time motion? In other words, how to ensure that a sprite stops all movement (i.e., set velocity to 0) after a certain amount of time? Example: make a sprite move with velocity vector of (200, 100) for 0.10 seconds. This seemed only possible with SKAction. Can also post a new question if you prefer. Thanks!Pursley
@EpicByte I like the second solution, but how do I take into account different devices? On iPad my object "feels" much heavier than on iPhone.Thorley
V
5

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?

Vachell answered 10/10, 2013 at 5:18 Comment(7)
should you also keep track of the last time update: was called, and the delta time since that last update ?Hendry
not really, I know it's often said to apply delta time to motion but for gameplay it's almost always something you'd want to avoid in games - as framerate drop you don't want the game to behave as if it continued to render 60 frames because that just makes the game harder if not unfair to the playerVachell
If the character slows down and speeds up because of framerate fluctuations and velocity is tied to that , does that make it easier/funner for the player ? Of course the ideal is that your framerate doesn't slow down to where this becomes an issue in either method. But if a game is fluctuating in a range between 30 and 60 frames per second, the difference will be far more noticeable if not using the time delta to calculate movement.Eupatrid
The whole idea behind the delta is that you are adjusting movement to the current framerate, it is not about behaving like it's rendering at 60fps. You use the delta to calculate how far the character should have travelled since the last frame. With the time delta you can always set a threshold to limit the maximum time since the last frame which in worst case scenarios then acts pretty much the same as this implementation. In that case if the framerate slows down the character will just slow down, not move in larger chunks.Eupatrid
An example is that a given game will run fine at say 40 or 60fps. So someone with a lesser device who is getting 40fps might actually be getting a far different game experience than someone at 60fps on a better device. However with the time delta, that difference will hardly be noticeable as a player will still travel the same distance each second on either device.Eupatrid
On a device where you can optimize for a constant 60 fps (ie anything but desktop computers) the 1st goal is to achieve 60 fps all the time, or lock the framerate to a constant rate like 30 fps (due to vsync fps lock of 31-59 fps isn't possible, only 60,30,20,15). The problem then is that you can have short spikes making the framerate drop to 30, or even 15 fps for a few frames. W/o time delta the player will hardly notice te slowdown, with time delta objects may have skipped over great distances, the player may have missed a jump, etc.Vachell
good point about the vsync on iOS devices, I read your post on the topic - learn-cocos2d.com/2013/10/… I didn't know that it was vsync environment. If your game is going under 30 though, the problem is your performance! :) Not acceptable regardless of the approach. Definitely agree that if you can't maintain 60fps, you should lock to 30fps on iOS.Eupatrid
O
1

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
}
Olly answered 31/7, 2014 at 19:33 Comment(0)
B
1

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.)

Barrie answered 12/11, 2017 at 21:32 Comment(0)
H
0

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.

Hendry answered 10/10, 2013 at 4:44 Comment(0)
M
0

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);
}
Melbamelborn answered 6/11, 2013 at 7:52 Comment(0)
C
0

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.

Conversant answered 2/8, 2014 at 12:2 Comment(0)
B
0

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

Babbette answered 16/6, 2020 at 19:18 Comment(0)
M
-1

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

Mig answered 2/7, 2014 at 7:32 Comment(0)
R
-3
-(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);
}
Reeve answered 20/1, 2015 at 19:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.