How to synchronize circle (with SKAction by SKShapeNode) to ball (by SKShapeNode)? - sprite-kit

I try to synchronize circle (create with SKAction by SKShapeNode) to ball (create by SKShapeNode).
Here is this code. But it couldn't work well.
What should I do?
-(void)makeContents{
//make ground
UIBezierPath *groundPath = [UIBezierPath bezierPath];
[groundPath moveToPoint:CGPointMake(0,250)];
[groundPath addLineToPoint:CGPointMake(568,100)];
SKShapeNode *ground = [[SKShapeNode alloc] init];
ground.path = groundPath.CGPath;
[self addChild:ground];
//make ball
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball.jpeg"];
ball.position = CGPointMake(10, 320);
ball.size = CGSizeMake(20, 20);
ball.physicsBody.dynamic = YES;
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:3];
[self addChild:ball];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//make circle
UIBezierPath *circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter:ball.position radius:10 startAngle:0 endAngle:2*M_PI clockwise:NO];
circle = [[SKShapeNode alloc] init];
circle.path = circlePath.CGPath;
circle.strokeColor = [SKColor blackColor];
//this line added after first post.
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10 center:ball.postion];
[self addChild:circle];
//make action
SKAction *scale = [SKAction sequence:#[[SKAction scaleBy:0.1 duration:2],
[SKAction scaleBy:0 duration:0.01]]];
[circle runAction:[SKAction repeatActionForever:scale]];
}
Thank you in advance for your help.

Related

didBeginContact entering too early

I am trying to stick a ball to a spinner when they collide at the contact point. However, it seems as though the didBeginContact is being called before the contact starts. Image is below showing them both spinning together but there is a big space.
Code is below:
#import "GameScene.h"
#implementation GameScene
#synthesize _flowIsON;
NSString *const kFlowTypeRed = #"RED_FLOW_PARTICLE";
const float kRED_DELAY_BETWEEN_PARTICLE_DROP = 0.01; //delay for particle drop in seconds
static const uint32_t kRedParticleCategory = 0x1 << 0;
static const uint32_t kSpinnnerCategory = 0x1 << 1;
NSString *const kStartBtn = #"START_BTN";
NSString *const kLever = #"Lever";
NSString *const START_BTN_TEXT = #"Start Game";
CFTimeInterval lastTime;
-(void)didMoveToView:(SKView *)view {
_bkgNode = (SKSpriteNode *)[self.scene childNodeWithName:#"Background"];
[self initializeScene];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:kStartBtn]) {
[node removeFromParent];
//initalize to ON
_flowIsON = YES;
//[self initializeScene];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}
-(void)update:(CFTimeInterval)currentTime {
float deltaTimeInSeconds = currentTime - lastTime;
//NSLog(#"Time is %f and flow is %d",deltaTimeInSeconds, _flowIsON);
if ((deltaTimeInSeconds > kRED_DELAY_BETWEEN_PARTICLE_DROP)) {
//TBD
SKAction *rotation = [SKAction rotateByAngle: M_PI/8.0 duration:0];
[_spinner runAction:rotation];
//only if its been past 1 second do we set the lasttime to the current time
lastTime = currentTime;
}
}
- (void) initializeScene {
self.physicsWorld.contactDelegate = self;
//create ball
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"Ball"];
ball.size = CGSizeMake(50, 50);
ball.position = CGPointMake(320, 1050);
ball.zPosition = 1;
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:25];
ball.physicsBody.restitution = 0.0;
ball.physicsBody.categoryBitMask = kRedParticleCategory;
ball.physicsBody.contactTestBitMask = kSpinnnerCategory;
ball.physicsBody.collisionBitMask = kSpinnnerCategory;
ball.name = #"Ball";
NSLog(#"Ball size is %f",ball.size.width);
[self addChild:ball];
//Create spinner
_spinner = [SKSpriteNode spriteNodeWithImageNamed:#"Spinner"];
_spinner.size = CGSizeMake(300, 300);
_spinner.position = CGPointMake(320, 500);
_spinner.zPosition = 1;
_spinner.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:150];
_spinner.physicsBody.affectedByGravity = NO;
_spinner.physicsBody.allowsRotation = YES;
_spinner.physicsBody.dynamic = NO;
_spinner.physicsBody.restitution = 0.0;
_spinner.physicsBody.categoryBitMask = kSpinnnerCategory;
_spinner.physicsBody.contactTestBitMask = kRedParticleCategory;
_spinner.physicsBody.collisionBitMask = kRedParticleCategory;
_spinner.name = #"Spinner";
[self addChild:_spinner];
//create pipe
// CGPoint center = CGPointMake(400, 600) ;
//
// UIBezierPath *bezierPath = [UIBezierPath bezierPath];
// [bezierPath addArcWithCenter:center radius:400 startAngle:1.825777 endAngle:2.011118 clockwise:YES];
// [bezierPath addLineToPoint:center];
// [bezierPath closePath];
//
// SKShapeNode *shapeNode = [SKShapeNode shapeNodeWithPath:bezierPath.CGPath];
// shapeNode.strokeColor = [UIColor whiteColor];
// shapeNode.fillColor = [UIColor whiteColor];
// [self addChild:shapeNode];
}
# pragma mark -- SKPhysicsContactDelegate Methods
- (void)didBeginContact:(SKPhysicsContact *) contact {
if ([contact.bodyA.node.name isEqualToString:#"Ball"] && [contact.bodyB.node.name isEqualToString:#"Spinner"]) {
[self connectNode1:(SKSpriteNode *)contact.bodyA.node toNode2:(SKSpriteNode *)contact.bodyB.node withContact:contact];
}
}
- (void)didEndContact:(SKPhysicsContact *) contact {
//NSLog(#"didEndContact called");
}
- (void) connectNode1:(SKSpriteNode *)node1 toNode2:(SKSpriteNode *)node2 withContact: (SKPhysicsContact *)contact
{
SKPhysicsJointFixed *joint = [SKPhysicsJointFixed jointWithBodyA:node1.physicsBody
bodyB:node2.physicsBody
anchor:node2.position];
[self.physicsWorld addJoint:joint];
}
#end
If I comment out the did begin contact method you can see the images are correctly sized becuase when they collide they rest on each other perfectly.
How come the contact.contactPoint is not the same as the point at which both bodys are colliding when I comment out the didEnterContact method? Any idea how to fix?
'didBeginContact' is not called too early.. the problem is that actions are evaluated earlier in the scene cycle: first 'didEvaluateActions()' then 'didSimulatePhysics()'. So, even though your SKactions may look correct, there comes a physics evaluation afterwards.. I suggest not to use actions to make rotation corrections when working with the physics engine.. Perhaps use constraints, those come after the physics update..

Rotate sprite with path

Hi i have a sprite following a path that is a bezier curve and id like the sprite to rotate to the direction it is currently moving, i can tell it to follow the path by setting orientToPath to YES in
SKaction *enemyCurve = [SKAction followpath:cgpath asOffset:NO orientToPath:YES duration:5];
but this just results in the sprite facing the final point of movement. Any suggestions on how i can make it rotate to the current direction its traveling in?
I have included the code i use for the path below. Thank you for any help and suggestions.
-(void)AddEnemy:(MotionType *)motion :(CGPoint*)Start :(CGPoint*)Finish :(EnemyType)enemyType{
double curTime = CACurrentMediaTime();
if (enemyType == One) {
if(curTime > _nextAsteroidspawn)
{
float randSecs = [self randomValueBetween:0.20 andValue:1.0];
_nextAsteroidspawn = randSecs + curTime;
float randY = [self randomValueBetween:0.0 andValue:self.frame.size.height];
float randDuration = [self randomValueBetween:2.0 andValue:10.0];
SKSpriteNode *asteroid = [_Asteroids objectAtIndex:_nextAsteroid];
_nextAsteroid++;
if(_nextAsteroid >= _Asteroids.count){
_nextAsteroid = 0;
}
[asteroid removeAllActions];
asteroid.position = CGPointMake(self.frame.size.width+asteroid.size.width/2, 50);
asteroid.hidden = NO;
CGMutablePathRef cgpath = CGPathCreateMutable();
CGPoint startingPoint = CGPointMake(self.frame.size.width+asteroid.size.width/2 , 50);
CGPoint controlPoint1 = CGPointMake(160, 300);
CGPoint controlPoint2 = CGPointMake(200, 600);
CGPoint endingPoint = CGPointMake(0, self.frame.size.width - self.frame.size.width - asteroid.size.width / 2);
CGPathMoveToPoint(cgpath, NULL, startingPoint.x, startingPoint.y);
CGPathAddCurveToPoint(cgpath, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x
, controlPoint2.y, endingPoint.x
, endingPoint.y);
SKAction *enemyCurve = [SKAction followPath:cgpath asOffset:NO orientToPath:YES duration:5];
CGPoint location = CGPointMake(-self.frame.size.width-asteroid.size.width, randY);
SKAction *moveAction = [SKAction moveTo:location duration:randDuration];
SKAction *doneAction = [SKAction runBlock:(dispatch_block_t)^(){
asteroid.hidden = YES;
}];
SKAction *moveAsteroidActionWithDone = [SKAction sequence:#[enemyCurve,moveAction, doneAction]];
[asteroid runAction:moveAsteroidActionWithDone withKey:#"asteroidMoving"];
CGPathRelease(cgpath);
//[asteroid runAction: [SKAction repeatActionForever:[SKAction animateWithTextures:_Asteroids timePerFrame:0.1f]]];
}
The code that creates the enemy node can be seen bellow, i do not apply any rotations to the node.
In initWithSize:
_Asteroids = [[NSMutableArray alloc] initWithCapacity:knumAsteroids];
for(int i = 0; i < knumAsteroids; i++)
{
SKSpriteNode *asteroid = [SKSpriteNode spriteNodeWithImageNamed:#"enemyBasic1"];
asteroid.hidden = YES;
[_Asteroids addObject:asteroid];
[self addChild:asteroid];
}
In Update:
[self AddEnemy:BezierCurve :NULL :NULL:One];
The method in update just links to the code posted at the top which determines the path the enemy will take i have also updated it to include the entire method.

Impulse doesn't apply in Sprite Kit

I'm attempting to apply an impulse to a sprite node in SK every time I touch, but nothing is happening. The touch is registering, but there is no impulse.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
SKSpriteNode *torso = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(40, 60)];
torso.position = CGPointMake (250,250);
torso.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(40, 60)];
torso.physicsBody.dynamic = YES;
[self addChild: torso];
SKSpriteNode *arm = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(15, 50)];
arm.position = CGPointMake (235,235);
arm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(40, 60)];
arm.physicsBody.dynamic = YES;
[self addChild: arm];
SKPhysicsJointPin *leftArm = [SKPhysicsJointPin jointWithBodyA:torso.physicsBody bodyB:arm.physicsBody anchor:torso.position];
[self.physicsWorld addJoint:leftArm];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[_torso.physicsBody applyImpulse:CGVectorMake(12, 10)];
NSLog(#"touch");
}
My sprite node does have another node linked to it, but I tried the impulse on it without the additional node and it still did not work.
For some reason, in order to move my sprite node I had to create it like this
_torso = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(40, 60)];
rather than
SKSpriteNode *torso = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(40, 60)];
Once I set a breakpoint and saw that _torso was nil, I referred to some previous work and saw my mistake.

How to add to ammo if item collected sprite kit

i have a game where the guy runs around and collects stuff. So I want the score keeper to update and increase if he collects an item and decrease if he uses the item. For example, life item. so here is the code I got so far for labels but that works with text score not integers. any ideas? thanks
- (void)setupUI
{
int barHeight = 45;
CGSize backgroundSize = CGSizeMake(self.size.width, barHeight);
SKColor *backgroundColor = [SKColor colorWithWhite:10 alpha:50];
SKSpriteNode *hudBarBackground = [SKSpriteNode spriteNodeWithColor:backgroundColor size:backgroundSize];
hudBarBackground.position = CGPointMake(0, self.size.height - barHeight);
hudBarBackground.anchorPoint = CGPointZero;
[_hudLayerNode addChild:hudBarBackground];
// 1
SKLabelNode *scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"AvenirNextCondensed-HeavyItalic"];
// 2
scoreLabel.fontSize = 20.0;
scoreLabel.text = #"Score: 0";
scoreLabel.name = #"scoreLabel";
scoreLabel.fontColor = [SKColor orangeColor];
// 3
scoreLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
// 4
scoreLabel.position = CGPointMake(self.size.width - self.size.width / 4, self.size.height - scoreLabel.frame.size.height + 3);
// 5
[_hudLayerNode addChild:scoreLabel];
_scoreFlashAction = [SKAction sequence: #[[SKAction scaleTo:1.5 duration:0.1],
[SKAction scaleTo:1.0 duration:0.1]]];
[scoreLabel runAction: [SKAction repeatAction:_scoreFlashAction count:10]];
}
double score; //property
-(void)setupUI {
//...
SKAction *tempAction = [SKAction runBlock:^{
scoreLabel.text = [NSString stringWithFormat:#"Score: 3.0f", self.score];
}];
SKAction *waitAction = [SKAction waitForDuration:0.2];
[scoreLabel runAction:[SKAction repeatActionForever:[SKAction sequence:#[tempAction,waitAction]]]];
//...
}

animate point multiple times and more naturally

so my current version looks like this: Animation Movie
i'm fairly new to core-animations, so what i try to achieve is that there are multiple points like the current one, moving from the left box in various angles, heights and speeds out of it, just like a tennis ball machine.
the first problem is, that my "ball" doesn't look like it gets grabbed from gravity and also the speed at first is not fast enough.
also, how to do this animation multiple times with variating distances between the beginning.
if something is unclear, PLEASE leave a comment.
my current code:
- (void)loadView {
[super loadView];
self.view.backgroundColor = [UIColor lightGrayColor];
CGPoint startPoint = CGPointMake(20, 300);
CGPoint endPoint = CGPointMake(300, 500);
UIBezierPath *trackPath = [UIBezierPath bezierPath];
[trackPath moveToPoint:startPoint];
[trackPath addQuadCurveToPoint:endPoint controlPoint:CGPointMake(endPoint.x, startPoint.y)];
CALayer *point = [CALayer layer];
point.bounds = CGRectMake(0, 0, 20.0, 20.0);
point.position = startPoint;
point.contents = (id)([UIImage imageNamed:#"point.png"].CGImage);
[self.view.layer addSublayer:point];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = trackPath.CGPath;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.repeatCount = HUGE_VALF;
anim.duration = 2.0;
[point addAnimation:anim forKey:#"movepoint"];
CALayer *caseLayer = [CALayer layer];
caseLayer.bounds = CGRectMake(0, 0, 140.0, 150.0);
caseLayer.position = startPoint;
caseLayer.contents = (id)([UIImage imageNamed:#"case.png"].CGImage);
[self.view.layer addSublayer:caseLayer];
}
Easiest way to model gravity is with those parabolic equations that you learn in basic physics. In short, the function below takes an initial position, a speed, and an angle (IN RADIANS, where 0 is to the right). a CALayer appears at position and gets shot at that speed and angle.
I'm using a CADisplayLink (which is effectively a timer that synchronizes with your frame rate) to call a function very quickly. Every time the function is called, the point is moved. The horizontal speed is constant and the vertical speed increases towards the bottom every frame to emulate gravity (`vy -= 0.5f; ). If you want more/less gravity, just mess around with this 0.5f value.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
point = [[CALayer alloc] init];
point.bounds = CGRectMake(0.0f, 0.0f, 10.0f, 10.0f);
point.backgroundColor = [UIColor redColor].CGColor;
point.position = CGPointMake(-10.0f, -10.0f);
[self.layer addSublayer:point];
[point release];
}
return self;
}
-(void)animateBallFrom:(CGPoint)start withSpeed:(CGFloat)speed andAngle:(CGFloat)angle
vx = speed*cos(angle);
vy = speed*sin(angle);
[CATransaction begin];
[CATransaction setDisableActions:YES];
point.position = start;
[CATransaction commit];
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animate)];
[displayLink setFrameInterval:2];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
-(void)animate {
if (point.position.x + point.bounds.size.width/2.0f < 0.0f || point.position.x > self.bounds.size.width + point.bounds.size.width/2.0f ||
point.position.y + point.bounds.size.height/2.0f < 0.0f || point.position.y > self.bounds.size.height + point.bounds.size.height/2.0f) {
[displayLink invalidate];
} else {
[CATransaction begin];
[CATransaction setDisableActions:YES];
point.position = CGPointMake(point.position.x+vx, point.position.y-vy);
vy -= 0.5f;
[CATransaction commit];
}
}
and the interface:
#interface Bounce : UIView {
CALayer *point;
CADisplayLink *displayLink;
CGFloat vx, vy;
}
-(void)animateBallFrom:(CGPoint)start withSpeed:(CGFloat)speed andAngle:(CGFloat)angle;
#end
I think there may be a couple of things you can do here.
- One is to use kCAMediaTimingFunctionLinear instead of kCAMediaTimingFunctionEaseOut.
- The other is to set your control point in the center of the ball's fall, wherever that may be. Try this:
CGPoint startPoint = CGPointMake(20, 300);
CGPoint controlPoint = CGPointMake(160, 400);
CGPoint endPoint = CGPointMake(300, 500);
UIBezierPath *trackPath = [UIBezierPath bezierPath];
[trackPath moveToPoint:startPoint];
[trackPath addQuadCurveToPoint:endPoint controlPoint:controlPoint];
CALayer *point = [CALayer layer];
point.bounds = CGRectMake(0, 0, 20.0, 20.0);
point.position = startPoint;
point.contents = (id)([UIImage imageNamed:#"point.png"].CGImage);
[self.view.layer addSublayer:point];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = trackPath.CGPath;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim.repeatCount = HUGE_VALF;
anim.duration = 2.0;
[point addAnimation:anim forKey:#"movepoint"];
You should then be able to move the control point around for different paths.