Using anchorPoint in spritekit to each node - ios

Im wondering at my setting ball.position function, the point is to set ball's point to other spritenode(in left/middle/right side), i tried to write my own function, but it was full of bugs, (it's commented), next after review developer lib's. I found anchorPoint to parent position, but it's not working, i dont know exactly how to set current parent for ball. I'll be really grateful for some advices.
if (ball.position.y < block.position.y +10 && ball.position.y > block.position.y -10) {
midX = CGRectGetMidX(self.frame);
side = block1.size.width/2;
if (emitter.position.x < midX - side+5) {
// fixedEmitterPos = emitter.position.x+side;
ball.anchorPoint = CGPointMake(0,0.5);
}
if (emitter.position.x > midX + side-5){
// fixedEmitterPos = emitter.position.x-side;
ball.anchorPoint = CGPointMake(1,0.5);
}
if (emitter.position.x == 160) {
// fixedEmitterPos = emitter.position.x;
ball.anchorPoint = CGPointMake(0.5,0.5);
}
}

I don't know what exactly is your question but to change your anchor point you don't change anything in parent node. For example if anchor point is
ball.anchorPoint = CGPointMake(0.5,0.5);
the anchor point in exactly in the middle and if you change position of the ball like:
ball.position == CGPointMake(50,50);
the ball center will be exactly in that point (50, 50).
but if the anchor point will be:
ball.anchorPoint = CGPointMake(1, 1);
and you change the position to :
ball.position == CGPointMake(50,50);
the ball centre will be in
X = 50 - (ball width / 2)
Y = 50 - (ball height / 2)
If you want to set up ball position base on other sprite node you can do something like that:
//Attach left centre side of ball to other sprite:
ball.anchorPoint = CGPointMake(0, 0.5);
ball.position.x == otherSprit.position.x + otherSprit.size.with;
ball.position.y == otherSprit.position.y;
//Attach right centre side of ball to other sprite:
ball.anchorPoint = CGPointMake(1, 0.5);
ball.position.x == otherSprit.position.x;
ball.position.y == otherSprit.position.y;
Hope this is what you are about.

Related

How to apply impulse to all sprites of a group with a delay between each in iOS SpriteKit?

I am making a brick breaker style game in Xcode for iOS. There is no paddle and balls are all launched from the same location (determined by where the first ball falls from the last level). There are a varying number of balls launched each time based on the number of balls the player has created. Each game starts with one ball but the user collects more in playing. When applying an impulse to the ball sprites I can only either move one sprite or all ball sprites at once. How do I move the ball sprites with a delay between each sprites launch?
Below I will go over what I have tried already. Thank you in advance for any help!
So far I have tried a couple of things. First I was trying to create all ball sprites prior to allowing the user to launch and having all ball sprites at the same location. Then I enumerated over them and tried to launch them with a delay there. However this simply launched all balls simultaneously after the delay, not each individually.
gameWorldNode.enumerateChildNodes(withName: BallCategoryName) {
node, stop in
let ball = node as! SKSpriteNode
ball.move(toParent: gameWorldNode)
//Calculate Vector
var dx = CGFloat(touchLocation.x - ball.position.x)
var dy = CGFloat(touchLocation.y - ball.position.y)
//Calculate Magnitude
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
//Create Vector
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
let secondsToDelay = 0.25
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay){
//Apply impulse
ball.physicsBody!.applyImpulse(vector)
}
}
Secondly, It occurred to me that it would probably be better for game performance if I didn't create the ball sprite until right before the impulse is applied, after the previous ball is launched. So I tried the following code. However, while I think I may be on the right path now, the newly created ball sprites don't launch. They get created but for some reason they don't apply the impulse.
let ball = gameWorldNode.childNode(withName: BallCategoryName) as! SKSpriteNode
ball.move(toParent: gameWorldNode)
//Calculate Vector
var dx = CGFloat(touchLocation.x - ball.position.x)
var dy = CGFloat(touchLocation.y - ball.position.y)
//Calculate Magnitude
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
//Create Vector
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
ball.physicsBody!.applyImpulse(vector)
var i = 1
while (i != ballsAvailabe) {
//Create new ball sprite
let nextBall: SKSpriteNode = Ball()
nextBall.position = CGPoint(x: locationX, y: locationY)
gameWorldNode.addChild(nextBall)
let secondsToDelay = 0.1
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay) {
//Apply impulse to ball
nextBall.physicsBody!.applyImpulse(vector)
}
//Update i
i += 1
}
I have also tried to add the sprites to an array and iterate over the array. However, this simply launches each ball at once still.
for _ in 0..<ballsAvailabe {
//Create new ball sprite
let ball: SKSpriteNode = Ball()
ball.position = CGPoint(x: locationX, y: locationY)
ball.physicsBody!.categoryBitMask = nextBallCategory
ball.color = nextBallInPlay
ball.colorBlendFactor = 1
ball.physicsBody!.contactTestBitMask = BottomCategory | AddABallCategory | RedBlockCategory | BlueBlockCategory | OrangeBlockCategory | PurpleBlockCategory | GreenBlockCategory | CyanBlockCategory
ballSprites.append(ball)
gameWorldNode.addChild(ball)
}
for sprite in ballSprites {
let secondsToDelay = 0.3
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay) {
sprite.physicsBody!.applyImpulse(vector)
}
}
EDIT:
Following Johns comment below, I have added code to my second attempt to ensure the physics bodies are created properly. Sorry for all the variables in the code they are just global variables I have created and keep in its own file to ensure I am keeping values consistent throughout the code. This works better. When the user only has one ball it works perfectly. However, when the user has collected more balls to launch it will sometimes launch perfectly with the delay to the expected location. Other times it will launch only the first ball. Other times it will launch to an unexpected location.
Heres the updated code:
//Grab existing ball object
let ball = gameWorldNode.childNode(withName: BallCategoryName) as! SKSpriteNode
//Assign to game world node
ball.move(toParent: gameWorldNode)
//Apply ball properties
ball.color = nextBallInPlay
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2)
ball.physicsBody!.categoryBitMask = RedBallCategory
ball.name = BallCategoryName
ball.colorBlendFactor = 1
ball.physicsBody?.pinned = false
ball.physicsBody?.mass = ballMass
ball.physicsBody?.affectedByGravity = ballGravity
ball.physicsBody?.allowsRotation = ballRotation
ball.physicsBody?.isDynamic = ballDynamic
ball.zPosition = ballZPos
ball.physicsBody?.friction = ballFriction
ball.physicsBody?.restitution = ballRestitution
ball.physicsBody?.linearDamping = ballLinearDaming
ball.physicsBody?.angularDamping = ballAngularDamping
//Stop more launches and accidently using power ups
isFingerOnScreen = true
canBallBeLaunched = false
canUsePowerUps = false
//Ensure physics world speed is accurate
physicsWorld.speed = 1
//Launching location
var dx = CGFloat(touchLocation.x - locationX)
var dy = CGFloat(touchLocation.y - locationY)
//Calculate Magnitude
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
//Create Vector
let vector = CGVector(dx: 30.0 * dx, dy: 30.0 * dy)
//Apply impule to first ball
ball.physicsBody!.applyImpulse(vector)
//loop to create x number of balls based on what the user has collected
var i = 1
while (i != ballsAvailabe) {
//Create new ball sprite
let nextBall: SKSpriteNode = Ball()
nextBall.position = CGPoint(x: locationX, y: locationY)
gameWorldNode.addChild(nextBall)
//Ball properties
nextBall.color = nextBallInPlay
nextBall.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2)
nextBall.physicsBody!.categoryBitMask = RedBallCategory
nextBall.name = BallCategoryName
nextBall.colorBlendFactor = 1
nextBall.physicsBody?.pinned = false
nextBall.physicsBody?.mass = ballMass
nextBall.physicsBody?.affectedByGravity = ballGravity
nextBall.physicsBody?.allowsRotation = ballRotation
nextBall.physicsBody?.isDynamic = ballDynamic
nextBall.zPosition = ballZPos
nextBall.physicsBody?.friction = ballFriction
nextBall.physicsBody?.restitution = ballRestitution
nextBall.physicsBody?.linearDamping = ballLinearDaming
nextBall.physicsBody?.angularDamping = ballAngularDamping
//Delay between each ball
let secondsToDelay = 0.1
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDelay) {
//Apply impulse to ball
nextBall.physicsBody!.applyImpulse(vector)
}
//Update i
i += 1
}

Show direction to CGPoint SpriteKit

I need an arrow (white circle) that shows the direction to CGPoint while user move his icon and camera.
I mean that arrow (white circle) needs to take position on the edge of visible screen and shows the way that helps user to return to followed CGPoint.
Demo gif
You are expecting for two things:
place the arrow on the correct screen side given the target CGPoint position
Orient the arrow towards the target CGPoint
In your touchesMoved(_:) method, you can update the arrow rotation and position, not tested but the principle should work :
private func placeArrow(at sourceNode: SKNode, for targetNode: SKNode) {
//do not display arrow if already on screen
guard targetNode.position.x > cameraNode.position.x - screenSizeX/2
&& targetNode.position.x < cameraNode.position.x + screenSizeX/2
&& targetNode.position.y > cameraNode.position.y - screenSizeY/2
&& targetNode.position.y < cameraNode.position.y + screenSizeY/2
{
arrowNode.isHidden = true
return
}
//find arrow position, if on the left place to the left side, else on the right
//place at the medium y between the 2 points
let screenSizeX = UIScreen.main.bounds.width
let screenSizeY = UIScreen.main.bounds.height
let ymin = cameraNode.position.y - screenSizeY/2 + 10
let ymax = cameraNode.position.y + screenSizeY/2 - 10
let midY = (sourceNode.position.y + targetNode.position.y)/2
var clampedMidY = midY
if midY > ymax {
clampedMidY = ymax
} else if midY < ymin {
clampedMidY = ymin
}
arrowNode.position = CGPoint(x: (targetNode.position.x < sourceNode.position.x) ? cameraNode.position.x - screenSizeX/2 : cameraNode.position.x + screenSizeX/2, y: clampedMidY)
//find arrow orientation
//see https://stackoverflow.com/questions/38411494/rotating-a-sprite-towards-a-point-and-move-it-towards-it-with-a-duration
let v1 = CGVector(dx:0, dy:1)
let v2 = CGVector(dx: targetNode.position.x - sourceNode.position.x, dy: targetNode.position.y - sourceNode.position.y)
arrowNode.zRotation = atan2(v2.dy, v2.dx) - atan2(v1.dy, v1.dx)
}

How do I wrap x & y axis in SpriteKit?

So in my GameScene I have these lines in the beginning of func didMoveToView
let border = SKPhysicsBody(edgeLoopFromRect: self.frame)
border.friction = 0
self.physicsBody = border
self.physicsWorld.contactDelegate = self
It works fine for preventing my player from going outside the screen; However, I want my player, or anything else in the scene, to go to the other side of the screen when touching the border. In other words: If i kept going right i would keep going right until i hit the border then my player would appear from the left border and continue going right in an endless loop.
Same goes for y axis.
How is this possible in code?
Try this code:
if player.position.x + player.size.width / 2 > size.width {
player.position.x = -player.size.width
}
else if player.position.x + player.size.width / 2 < 0 {
player.position.x = size.width + player.size.width / 2
Same logic for Y axis.
For whoever is interested to reproduce this, I had to delete the following code:
let border = SKPhysicsBody(edgeLoopFromRect: self.frame)
border.friction = 0
self.physicsBody = border
self.physicsWorld.contactDelegate = self
Then, in touchesMoved I added:
if Player.position.x + Player.size.width / 16 > size.width {
Player.position.x = Player.size.width / 16
}
else if Player.position.x + Player.size.width / 16 < 0 {
Player.position.x = size.width - Player.size.width / 16
}
for the x axis. & the same can be done for the Y axis except we will change the x to y & the width to height.

SpriteKit move rotated physicsBody with applyImpulse

I want to move a physicsBody with the applyImpulse method in a direction based on the physicsBody rotation.
Foe example, the physicsBody is a square in shape, I call a "move" which will apply an impulse to make it move up vertically. I then call a method to rotate the physicsBody 45 degrees right. If I call the "move" method again, the physicsBody will move diagonally right and up.
I suggest that you follow Sprite Kit’s coordinate and rotation conventions. Specifically, your sprite image should be facing right at zero degrees (the default value), and a positive value is a counter-clockwise rotation. That said, here's one way to apply an impulse in the direction a sprite is facing:
// Specify the force to apply to the SKPhysicsBody
CGFloat r = 5;
// Create a vector in the direction the sprite is facing
CGFloat dx = r * cos (sprite.zRotation);
CGFloat dy = r * sin (sprite.zRotation);
// Apply impulse to physics body
[sprite.physicsBody applyImpulse:CGVectorMake(dx,dy)];
UPDATED:
Fixed with the below thanks to #0x141E
-(void)characterJump {
CGFloat radianFactor = 0.0174532925;
CGFloat rotationInDegrees = _body.zRotation / radianFactor;
CGFloat newRotationDegrees = rotationInDegrees + 90;
CGFloat newRotationRadians = newRotationDegrees * radianFactor;
CGFloat r = 500;
CGFloat dx = r * cos(newRotationRadians);
CGFloat dy = r * sin(newRotationRadians);
[_body.physicsBody applyImpulse:CGVectorMake(dx, dy)];
}

Stop SKAction at collision with PhysicsBody

I have a SpriteNode which is affected by gravity only on the y-Axis and can be moved with the accelerometer on the x-Axis. I also have a border (SKPhysicsBody) on my Scene which keeps my node inside my scene. The problem is now that my node ignores the border when it performs its SKAction caused by the accelerometer.
My code for the x movement/ accelerometer and its action:
birdNode is my SpriteNode
float destX = 0.0;
float currentX = birdNode.position.x;
BOOL shouldMove = NO;
if (data.acceleration.x < -0.1) {
destX = currentX + (data.acceleration.x * playerXSpeed);
shouldMove = YES;
}
else if (data.acceleration.x > 0.1) {
destX = currentX + (data.acceleration.x * playerXSpeed);
shouldMove = YES;
}
if (shouldMove) {
if (birdNode.position.x + destX < self.frame.size.width || birdNode.position.x - destX > 0) {
SKAction *moveBird = [SKAction moveToX:destX duration:0.1];
[birdNode runAction:moveBird];
}
}
I assume that you have your collision bit masks properly set up to handle collisions. You should confirm this with a test to double check.
I believe your issue is with the speed of your object. If the object is moving too fast, and I do not see any velocity limits in your posted code, your object can move past the screen boundary in one update cycle.
For example, your current object's position is (400,100). Given enough velocity, your object's next update: position could be (600,100). This means that your object literally jumped past the boundary without causing a collision.
The solution is to either limit the velocity or to institute a position check before setting your object's new position. For example, if the new x position is > the screen width then set the x position to the max allowed screen width x.

Resources