最后几天,我用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物理调试器解决上述问题:
这行代码是正确的:
[ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];
SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];
因为0,0是物理体的中心,所以必须翻译路径的原点。