在SpriteKit投掷球

时间:2014-03-20 09:28:02

标签: ios objective-c concurrency sprite-kit

最后几天,我用spriteKit进行了一段时间的实验,并且(除其他事项外)试图将问题解决到" throw"通过触摸和拖动精灵。

同样的问题在Stackexchange上,但是他们告诉我先删除bug然后让代码进行审查。

我已经解决了主要障碍,代码工作正常,但是存在一个小问题。

(另外,如果有人为此提供更优质或更好的解决方案,我会感兴趣。我也很乐意听到关于如何在这种互动中完善现实感的建议。)< / p>

有时,球会卡住。 如果你想重现它,只需快速和短暂地滑动球。我怀疑使用gestureRecognizer来触摸动作&#34;触摸动作&#34;和&#34; touchesEnded&#34;回调异步并通过物理模拟中出现一些不可能的状态。

任何人都可以提供更可靠的方法来重现问题,那可能是什么解决方案?

该项目名为ballThrow,BT是类前缀。

#import "BTMyScene.h"
#import "BTBall.h"

@interface BTMyScene()

@property (strong, nonatomic) NSMutableArray *balls;
@property (nonatomic) CGFloat yPosition;
@property (nonatomic) CGFloat xCenter;
@property (nonatomic) BOOL updated;

@end

@implementation BTMyScene

const CGFloat BALLDISTANCE = 80;

-(id)initWithSize:(CGSize)size {

    if (self = [super initWithSize:size]) {

        _balls = [NSMutableArray arrayWithCapacity:5];

        //define the region where the balls will spawn
        _yPosition = size.height/2.0;
        _xCenter = size.width/2.0;

        /* Setup your scene here */
        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
    }
    return self;
}

-(void)didMoveToView:(SKView *)view {

    //Make an invisible border
    //this seems to be offset... Why the heck is this?
    self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:view.frame];

    [self createBalls:2];

    //move balls with pan gesture
    //could be improved by combining with touchesBegan for first locating the touch
    [self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveBall:)]];
}


-(void)moveBall:(UIPanGestureRecognizer *)pgr {

    //depending on the touch phase do different things to the ball
    if (pgr.state == UIGestureRecognizerStateBegan) {
        [self attachBallToTouch:pgr];
    }
    else if (pgr.state == UIGestureRecognizerStateChanged) {
        [self moveBallToTouch:pgr];
    }
    else if (pgr.state == UIGestureRecognizerStateEnded) {
        [self stopMovingTouch:pgr];
    }
    else if (pgr.state == UIGestureRecognizerStateCancelled) {
        [self stopMovingTouch:pgr];
    }
}


-(void)attachBallToTouch:(UIPanGestureRecognizer *)touch {

    //determine the ball to move
    for (BTBall *ball in self.balls) {

        if ([self isMovingBall:ball forGestureRecognizer:touch])
        {
            //stop ball movement
            [ball.physicsBody setAffectedByGravity:NO];
            [ball.physicsBody setVelocity:CGVectorMake(0, 0)];

            //the ball might not be touched right in its center, so save the relative location
            ball.touchLocation = [self convertPoint:[self convertPointFromView:[touch locationInView:self.view]] toNode:ball];

            //update location once, just in case...
            [self setBallPosition:ball toTouch:touch];

            if (_updated) {
                _updated = NO;
                [touch setTranslation:CGPointZero inView:self.view];
            }
        }
    }

}

-(void)moveBallToTouch:(UIPanGestureRecognizer *)touch {

    for (BTBall *ball in self.balls) {

        if ([self isMovingBall:ball forGestureRecognizer:touch])
        {
            //update the position of the ball and reset translation
            [self setBallPosition:ball toTouch:touch];
            if (_updated) {
                _updated = NO;
                [touch setTranslation:CGPointZero inView:self.view];
            }
            break;
        }
    }

}

-(void)setBallPosition:(BTBall *)ball toTouch:(UIPanGestureRecognizer *)touch {

    //gesture recognizers only deliver locations in views, thus convert to node
    CGPoint touchPosition = [self convertPointFromView:[touch locationInView:self.view]];

    //update the location to the touche´s location, offset by touch position in ball
    [ball setNewPosition:CGPointApplyAffineTransform(touchPosition,
                                                     CGAffineTransformMakeTranslation(-ball.touchLocation.x,
                                                                                      -ball.touchLocation.y))];

    //save the velocity between the last two touch records for later release
    CGPoint velocity = [touch velocityInView:self.view];
    //why the hell is the y coordinate inverted??
    [ball setLastVelocity:CGVectorMake(velocity.x, -velocity.y)];

}

-(void)stopMovingTouch:(UIPanGestureRecognizer *)touch {

    for (BTBall *ball in self.balls) {

        if ([self isMovingBall:ball forGestureRecognizer:touch]) {

            //release the ball: enable gravity impact and make it move
            [ball.physicsBody setAffectedByGravity:YES];
            [ball.physicsBody setVelocity:CGVectorMake(ball.lastVelocity.dx, ball.lastVelocity.dy)];
            break;
        }
    }
}

-(BOOL)isMovingBall:(BTBall *)ball forGestureRecognizer:(UIPanGestureRecognizer *)touch {

    //latest location of touch
    CGPoint touchPosition = [touch locationInView:self.view];

    //distance covered since the last call
    CGPoint touchTranslation = [touch translationInView:self.view];

    //position, where the ball must be, if it is the one
    CGPoint translatedPosition = CGPointApplyAffineTransform(touchPosition,
                                                             CGAffineTransformMakeTranslation(-touchTranslation.x,
                                                                                              -touchTranslation.y));
    CGPoint inScene = [self convertPointFromView:translatedPosition];

    //determine weather the last touch location was on the ball
    //if last touch location was on the ball, return true
    return [[self nodesAtPoint:inScene] containsObject:ball];
}


-(void)update:(CFTimeInterval)currentTime {

    //updating the ball position here improved performance dramatically
    for (BTBall *ball in self.balls) {
        //balls that move are not gravity affected
        //easiest way to determine movement
        if ([ball.physicsBody affectedByGravity] == NO) {
            [ball setPosition:ball.newPosition];
        }
    }

    //ball positions are refreshed
    _updated = YES;

}

-(void)createBalls:(int)numberOfBalls {

    for (int i = 0; i<numberOfBalls; i++) {

        BTBall *ball;

        //reuse balls (not necessary yet, but imagine balls spawning)
        if(i<[self.balls count]) {
            ball = self.balls[i];
        }
        else {
            ball = [BTBall newBall];
        }

        [ball.physicsBody setAffectedByGravity:NO];

        //calculate ballposition
        CGPoint ballPosition = CGPointMake(self.xCenter-BALLSIZE/2+(i-(numberOfBalls-1)/2.0)*BALLDISTANCE, self.yPosition);
        [ball setNewPosition:ballPosition];

        [self.balls addObject:ball];
        [self addChild:ball];
    }

}

@end

BTBall(SKShapeNode的子类,因为需要自定义属性)

#import <SpriteKit/SpriteKit.h>

@interface BTBall : SKShapeNode

const extern CGFloat BALLSIZE;

//some properties for the throw animation
@property (nonatomic) CGPoint touchLocation;

@property (nonatomic) CGPoint newPosition;

@property (nonatomic) CGVector lastVelocity;

//create a standard ball
+(BTBall *)newBall;

@end

使用类方法创建新球的BTBall.m

#import "BTBall.h"

@implementation BTBall

const CGFloat BALLSIZE = 80;

+(BTBall *)newBall {

    BTBall *ball = [BTBall node];

    //look
    [ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];
    [ball setFillColor:[UIColor redColor]];
    [ball setStrokeColor:[UIColor clearColor]];

    //physics
    SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];
    [ball setPhysicsBody:ballBody];
    [ball.physicsBody setAllowsRotation:NO];

    //ball is not moving at the beginning
    ball.lastVelocity = CGVectorMake(0, 0);

    return ball;
}

@end

1.一些问题(参见代码中的注释)与spriteKit坐标系有关。我只是没有让场景的边框与其实际帧对齐,尽管我使用与Apple给我们in the programming guide完全相同的代码。由于Stackoverflow上的建议,我已将其从initWithSize移至didMoveToView,但这没有帮助。可以使用硬编码值手动偏移边框,但这不符合我的要求。

<德尔> 2。有没有人知道一个调试工具,它为精灵的物理主体着色,以便查看它的大小以及它是否与精灵位于同一位置?

更新:使用YMC物理调试器解决上述问题:
Physics Bodies drawn

这行代码是正确的:

[ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];

SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];

因为0,0是物理体的中心,所以必须翻译路径的原点。

0 个答案:

没有答案