Rotate a Sprite on a bezier path with touch - Cocos2D/Box2D

懵懂的女人 提交于 2019-12-30 10:52:54

问题


I have an arrow that I rotate with touch. I was wondering if it was possible to rotate the arrow on a curved line? I've done some research and I think it is called a bezier path? Is it even possible to rotate a sprite on a bezier path using this code and if so how would I even incorporate it?

    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, _arrow.position);
    CGFloat firstRotateAngle = -ccpToAngle(firstVector);
    CGFloat previousTouch = CC_RADIANS_TO_DEGREES(firstRotateAngle);

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

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

For example,

I have a ball sitting on the ground and an arrow right above it. When you touch the screen and move the arrow, the arrow moves on a half circle axis.

The curve would look like this Half Circle and the arrow would be rotating on the axis.

Please let me know if I need to be more clear. I really need some help with this.


回答1:


Have faced with same problem couple of days. Most of links in this answer is broken, so I have found material here and here and made this code. Works like magic. Hope it will help someone.

Small description: I have object (self) witch rotates by finger around another object (self.target), and i have some animated sprites like guides of self movement, which rotates around self.target by bezier function. algoritm is quite fast, i have permanent initialization of 100+ guides and it works without CPU overload.

/**
 Each bezier curve is an array with 8 floats, x1, y1, x2, y2, x3, y3, x4, y4., where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points.
 @note adapted for xCode by Valentine Konov valentine\@konov.su 2013

 @return a array of objects that represent bezier curves which approximate the circular arc centered at the origin.
 @param  startAngle to endAngle (radians) with the specified radius.
 */

-(NSArray*)createArcWithRadius:(float)radius_ withStartAngle:(float)startAngle_ withEndAngle:(float)endAngle_;
{
//    OMLog(@"radius:%.2f startAngle:%.2f endAngle:%.2f",radius_,startAngle_,endAngle_);
    // normalize startAngle, endAngle to [-2PI, 2PI]

    float twoPI = M_PI * 2;
    float startAngle = startAngle_;
    float endAngle = endAngle_;
    //    float startAngle = fmodf(startAngle_,twoPI);
    //    float endAngle = fmodf(endAngle_,twoPI);

    // Compute the sequence of arc curves, up to PI/2 at a time.  Total arc angle
    // is less than 2PI.

    NSMutableArray* curves = [NSMutableArray array];
    float piOverTwo = M_PI / 2.0;
    float sgn = (startAngle < endAngle) ? 1 : -1;

    float a1 = startAngle;
    for (float totalAngle = fminf(twoPI, fabsf(endAngle - startAngle)); totalAngle > 0.00001f /*FLT_EPSILON*/; nil) {
        float a2 = a1 + sgn * min(totalAngle, piOverTwo);
        [curves addObject: [self createSmallArc:radius_ a1:a1 a2:a2]];
        totalAngle -= fabsf(a2 - a1);
        a1 = a2;
    }
    return curves;
}

/**
 Cubic bezier approximation of a circular arc centered at the origin,

 This algorithm is based on the approach described in:
 A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa,"
 Information Technology and Control, 35(4), 2006 pp. 371-378.
 @note adapted for xCode by Valentine Konov valentine\@konov.su 2013

 @param from (radians) a1 to a2, where a2-a1 < pi/2

 @return an array with 8 floats, x1, y1, x2, y2, x3, y3, x4, y4. where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points.

 */
-(NSArray*)createSmallArc:(float)r a1:(float)a1 a2:(float)a2
{
    // Compute all four points for an arc that subtends the same total angle
    // but is centered on the X-axis

    float a = (a2 - a1) / 2.0; //

    float x4 = r * cosf(a);
    float y4 = r * sinf(a);
    float x1 = x4;
    float y1 = -y4;

    float k = 0.5522847498;
    float f = k * tan(a);

    float x2 = x1 + f * y4;
    float y2 = y1 + f * x4;
    float x3 = x2;
    float y3 = -y2;

    // Find the arc points actual locations by computing x1,y1 and x4,y4
    // and rotating the control points by a + a1

    float ar = a + a1;
    float cos_ar = cosf(ar);
    float sin_ar = sinf(ar);


    return [NSArray arrayWithObjects:                           //
        [NSNumber numberWithFloat:(r * cosf(a1))],              //startPoint.x
        [NSNumber numberWithFloat:(r * sinf(a1))],              //startPoint.y
        [NSNumber numberWithFloat:(x2 * cos_ar - y2 * sin_ar)], //ctrlPoint1.x
        [NSNumber numberWithFloat:(x2 * sin_ar + y2 * cos_ar)], //ctrlPoint1.y
        [NSNumber numberWithFloat:(x3 * cos_ar - y3 * sin_ar)], //ctrlPoint2.x
        [NSNumber numberWithFloat:(x3 * sin_ar + y3 * cos_ar)], //ctrlPoint2.y
        [NSNumber numberWithFloat:(r * cosf(a2))],              //endPoint.x
        [NSNumber numberWithFloat:(r * sinf(a2))],              //endPoint.y
        nil];
}

/**
 Bezier approximation example

 @note adapted for xCode by Valentine Konov valentine\@konov.su 2013

 @param inSprite_ is sprite, angle_ signed angle radiants

 @return CCSequence of [CCSpawns of (CCBezierTo and CCRotateBy)]

 */

-(id)calcBezierCircle:(CCSprite*)inSprite_ withAngle:(float)angle_
{
    double speed = 100; //points per second

    CGPoint positionOffset = ccpSub(((CCNode*)self.target).position, self.position);
    //((CCNode*)self.target).position is circle center
    double startAngle = [self calcAngle:inSprite_.position ownerRelated:false];
    while (startAngle<0) startAngle += 2*M_PI;
    while (startAngle>=2*M_PI) startAngle -= 2*M_PI;
    double endAngle = startAngle + angle_;
    float radius = [self calcRadius];

    NSArray* curves = [self createArcWithRadius:radius withStartAngle:startAngle withEndAngle:endAngle];
    NSMutableArray* bezierActions = [NSMutableArray array];
    for (NSArray* curve in curves) {
        CGPoint startPoint =    ccpAdd(ccp([[curve objectAtIndex:0] floatValue], [[curve objectAtIndex:1] floatValue]), positionOffset);
        CGPoint controlPoint1 = ccpAdd(ccp([[curve objectAtIndex:2] floatValue], [[curve objectAtIndex:3] floatValue]), positionOffset);
        CGPoint controlPoint2 = ccpAdd(ccp([[curve objectAtIndex:4] floatValue], [[curve objectAtIndex:5] floatValue]), positionOffset);
        CGPoint endPoint =      ccpAdd(ccp([[curve objectAtIndex:6] floatValue], [[curve objectAtIndex:7] floatValue]), positionOffset);

        ccBezierConfig bezier;
        bezier.controlPoint_1 = controlPoint1;
        bezier.controlPoint_2 = controlPoint2;
        bezier.endPosition =endPoint;

        float bezierAngle = ccpAngleSigned(ccpSub(startPoint, positionOffset), ccpSub(endPoint, positionOffset));
        float bezierDuration = radius*fabsf(bezierAngle)/speed;
        id bezierTo = [CCBezierTo actionWithDuration:bezierDuration bezier:bezier];
        id rotateBy = [CCRotateBy actionWithDuration:bezierDuration angle:CC_RADIANS_TO_DEGREES(-bezierAngle)];
        CCAction * bezierToAndRotateBy = [CCSpawn actions:bezierTo, rotateBy, nil];

        [bezierActions addObject:bezierToAndRotateBy];
    }
    if ([bezierActions count]<1) {
        return nil;
    }
    return [CCSequence actionWithArray:bezierActions];
}


/**
 Calculates angle
 @param position_ current position of sprite on sircle, ownerRelated boolean, wich is startPoint is {1,0} or owner.position
 @return angle (radiant)
 */
-(float)calcAngle:(CGPoint)position_ ownerRelated:(bool)ownerRelated {
    if (ownerRelated) {
        CGPoint v1 = ccpSub(((CCNode*)self.target).position, self.position);
        CGPoint v2 = ccpSub(ccpSub(((CCNode*)self.target).position, self.position),position_);
        return ccpAngleSigned(v1, v2);
    }
    else {
        CGPoint v1 = ccp([self calcRadius], 0.0f);
        CGPoint v2 = ccpSub(position_,ccpSub(((CCNode*)self.target).position, self.position));
        return ccpAngleSigned(v1, v2);
    }
}

/**
 Calculates radius
 @return radius
 */
-(float)calcRadius;
{
    return sqrt(pow(self.position.x-((CCSprite*)self.target).position.x, 2)+pow(self.position.y-((CCSprite*)self.target).position.y, 2));
}



回答2:


Never tried but i think it's possible, basically what you want to do is when the user touch the screen the arrow move to the touch location on a curve line and the arrow rotate on the curve ?

first you need to make the arrow rotate and then perform the bezier action

//on touch
CGSize s = [[CCDirector sharedDirector] winSize];

//rotate action we make the arrow rotate forever
id actionBy = [CCRotateBy actionWithDuration:2  angle: 360];
[arrow runAction: [CCRepeatForever actionWithAction:actionBy]];

//bezier action
ccBezierConfig bezier;
bezier.controlPoint_1 = ccp(0, s.height/2);
bezier.controlPoint_2 = ccp(300, -s.height/2);
bezier.endPosition = ccp(300,100);

id bezierForward = [CCBezierBy actionWithDuration:3 bezier:bezier];
id action = [CCCallFunc actionWithTarget:self selector:@selector(endAction)];
[arrow runAction:[CCSequence actions:bezierForward, action]];

//write a method endAction
 [arrow stopAllActions];


来源:https://stackoverflow.com/questions/7494795/rotate-a-sprite-on-a-bezier-path-with-touch-cocos2d-box2d

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!