Rotate Sprite by Touch with a Limited Rotation Speed

Rotate sprite by touch with a limited rotation speed

I have a turret which ... should target the finger position, but not
immediately, by taking time to turn.

You won't be able to get away with SKActions for something like this. You can try but it will be really messy and inefficient. You need real-time motion control for something like this because the angular velocity of your turret needs to change constantly depending on the touch position.

So I wrote you a quick example project showing how to calculate the angular velocity. The project handles all special cases as well, such as preventing the angle from jumping over your target rotation.

import SpriteKit

class GameScene: SKScene {
let turret = SKSpriteNode(imageNamed: "Spaceship")
let rotationSpeed: CGFloat = CGFloat(M_PI) //Speed turret rotates.
let rotationOffset: CGFloat = -CGFloat(M_PI/2.0) //Controls which side of the sprite faces the touch point. I offset the angle by -90 degrees so that the top of my image faces the touch point.

private var touchPosition: CGFloat = 0
private var targetZRotation: CGFloat = 0

override func didMoveToView(view: SKView) {
turret.physicsBody = SKPhysicsBody(rectangleOfSize: turret.size)
turret.physicsBody!.affectedByGravity = false
turret.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(turret)
}

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
calculateAngleToTouch(touches.anyObject() as UITouch)
}

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
calculateAngleToTouch(touches.anyObject() as UITouch)
}

func calculateAngleToTouch(touch: UITouch) {
let position = touch.locationInNode(self)
let angle = atan2(position.y-turret.position.y, position.x-turret.position.x)

targetZRotation = angle + rotationOffset
}

override func update(currentTime: NSTimeInterval) {
var angularDisplacement = targetZRotation - turret.zRotation
if angularDisplacement > CGFloat(M_PI) {
angularDisplacement = (angularDisplacement - CGFloat(M_PI)*2)
} else if angularDisplacement < -CGFloat(M_PI) {
angularDisplacement = (angularDisplacement + CGFloat(M_PI)*2)
}

if abs(angularDisplacement) > rotationSpeed*(1.0/60.0) {
let angularVelocity = angularDisplacement < 0 ? -rotationSpeed : rotationSpeed
turret.physicsBody!.angularVelocity = angularVelocity
} else {
turret.physicsBody!.angularVelocity = 0
turret.zRotation = targetZRotation
}

}

}

Sample Image

Rotate an object in its direction of motion

It's fairly straightforward to rotate a sprite in the direction of its motion. You can do this by converting the x and y components of the sprite's velocity into an angle with the atan2 function. You should rotate the sprite only when its speed is greater than some nominal value to prevent the sprite from resetting to zero degrees when the sprite's speed is (nearly) zero.

If we extend CGVector to compute speed and angle from the velocity's components by

extension CGVector {
func speed() -> CGFloat {
return sqrt(dx*dx+dy*dy)
}
func angle() -> CGFloat {
return atan2(dy, dx)
}
}

we can rotate a sprite to face in the direction of its motion with

override func didSimulatePhysics() {
if let body = sprite.physicsBody {
if (body.velocity.speed() > 0.01) {
sprite.zRotation = body.velocity.angle() - offset
}
}
}

where offset = CGFloat(M_PI_2) if your sprite faces up when zRotation is zero, and offset = 0 if your sprite faces to the right when zRotation is zero.

Rotating sprite with finger in Cocos2d

@interface MainScene : CCLayer {
CCSprite *dial;

CGFloat dialRotation;

}

+ (id)scene;

@end
---------------------------------
@implementation MainScene

+ (id)scene
{
CCScene *scene = [CCScene node];
CCLayer *layer = [MainScene node];
[scene addChild:layer];

return scene;
}

- (id)init
{
if((self = [super init])) {
CCLOG(@"MainScene init");

CGSize size = [[CCDirector sharedDirector]winSize];

dial = [CCSprite spriteWithFile:@"dial.png"];
dial.position = ccp(size.width/2,dial.contentSize.height/2);
[self addChild:dial];

self.isTouchEnabled = YES;
[self scheduleUpdate];

}
return self;
}

- (void)dealloc
{
[super dealloc];
}

- (void)update:(ccTime)delta
{
dial.rotation = dialRotation;
}

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

}

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];

//acquire the previous touch location
CGPoint firstLocation = [touch previousLocationInView:[touch view]];
CGPoint location = [touch locationInView:[touch view]];

//preform all the same basic rig on both the current touch and previous touch
CGPoint touchingPoint = [[CCDirector sharedDirector] convertToGL:location];
CGPoint firstTouchingPoint = [[CCDirector sharedDirector] convertToGL:firstLocation];

CGPoint firstVector = ccpSub(firstTouchingPoint, dial.position);
CGFloat firstRotateAngle = -ccpToAngle(firstVector);
CGFloat previousTouch = CC_RADIANS_TO_DEGREES(firstRotateAngle);

CGPoint vector = ccpSub(touchingPoint, dial.position);
CGFloat rotateAngle = -ccpToAngle(vector);
CGFloat currentTouch = CC_RADIANS_TO_DEGREES(rotateAngle);

//keep adding the difference of the two angles to the dial rotation
dialRotation += currentTouch - previousTouch;
}

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

}

EDIT: I am sorry I can't get this to stay in code mode. But this is the solution and it should be easy to implement in your code. cheers!

Rotation of sprite by using sprites velocity (swift) flappy bird game

bird.physicsBody?.allowsRotation = true 
var velocityvector = bird.physicsBody?.velocity
let angle = atan2(velocityvector?.dy ?? 0, velocityvector?.dx ?? 0)
let rotateAction = SKAction.rotate(byAngle: angle, duration: 0.5)
// Or let rotateAction = SKAction.rotate(toAngle: angle, duration: 0.5)
bird.run(rotateAction)


Related Topics



Leave a reply



Submit