Check when an SKSpriteNode passes a certain point or line - swift

In my game, there is an SKSpriteNode, which we will call a "gate," that is moving upwards in the scene. When the gate reaches the top of the screen, it moves back down to the bottom and begins traveling upwards again.
The way the player loses is if the character, which is located at a constant y position towards the top of the scene, collides with the gate. I have the collision and 'death' all worked out, but now I need to check if the character successfully passed 'through' the gate (meaning the gate moved past the character without hitting him). Basically, I need to know how to check when one moving node crosses a certain y position (in this case, that position is the character's y position). If this occurs, I want to increase the score by one.
Here is a rough sketch of the situation:
(click here for the rough sketch)
One thing I have tried is in the update method, if the gate's y position ever equals the character's y position, I know the two nodes have crossed, so I should increase the score. It looks like this:
override func update(currentTime: CFTimeInterval) {
if gate.position.y == character.position.y {
print("score")
score++
}
}
but doesn't seem to work. Is there any other way to check when the gate crosses the character's y position?
Thanks!

What may be happening is that the y coordinates aren't the same. For example if gate.position.y = 100, and character. position.y = 101, it won't work. Try this:
override func update(currentTime: CFTimeInterval) {
let minY = gate.position.y - 10
let maxY = gate.position.y + 10
if character.position.y > minY && character.position.y < maxY {
print("score")
score++
}
}
You can adjust the number 10 to see what works best. Hope this helps

Related

Swift 3 (SpriteKit): Locking the x axis of a SKSpriteNode and its physicsBody

I really need to know how to lock the x axis of an SKSpriteNode and its physicsBody. I need to keep the SKSpriteNode dynamic and affectedByGravity. The node is on a slope, so this is why it's x axis is moved due to gravity. However, I don't want the x axis of this SKSpriteNode to move due to gravity. Is there a way to lock the x axis in order to achieve this?
Thanks for any help :D
Edit: I have tried to apply a constraint to the x value like this:
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints?.append(xConstraint)
However this doesn't work and I'm not sure why and how to fix it. Anyone know of a solution?
Edit 2: SKPhysicsJointPin is actually looking more promising. In the comments of the first response/answer to this question, I have been trying to figure how to properly use it in my situation.
An example of my code:
let node = SKSpriteNode(imageNamed: "node")
enum collisionType: UInt32 {
case node = 1
case ground = 2
case other = 4 //the other node is unrelated to the ground and node
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
//Setup node physicsBody
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
node.physicsBody?.categoryBitMask = collisionType.node.rawValue
node.physicsBody?.collisionBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.contactTestBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.isDynamic = true
node.physicsBody?.affectedByGravity = true
addChild(node)
//Physics Setup
physicsWorld.contactDelegate = self
}
The node is on top of the ground, and the ground is composed of various SKSpriteNode lines that have a volumeBased physicsBody. The ground keeps adding new lines at the front, and removing the ones at the back, and changing the x value of each line by a negative (so the ground appears to moving - this process is performed in an SKAction). These lines (the parts of the ground) are on an angle which is why the node's x axis moves. I want the node to always be at the front of the ground (e.g. always on top of the newly created line). Currently, setting the position of the node like this locks the x axis (solving my issue):
override func didSimulatePhysics() {
//Manage node position
node.position.x = 195
node.position.y = CGFloat([yPosition of the first line of the ground - the yPosition keeps changing])
}
Note: ^This^ function is inside the GameScene class
The x axis actually stays the same like this. However, the issue is that now the physicsBody of the node is lower than the centre of the node (which didn't happen before).
A node's constraints property is nil by default. You'll need to create an array of one or more constraints and assign it to the property. For example
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints = [xConstraint]
Update
You may want to use a camera node instead of moving the ground in the scene. With a camera node, you move the main character and the camera instead of the ground.
I think you could set the linearDamping property to 0.0
The linearDamping is a property that reduces the body’s linear velocity.
This property is used to simulate fluid or air friction forces on the
body. The property must be a value between 0.0 and 1.0. The default
value is 0.1. If the value is 0.0, no linear damping is applied to the
object.
You should pay attention also to the other forces applied to your SKSpriteNode. The gravitational force applied by the physics world for example where dx value, as you request , should be setted to 0.0:
CGVector(dx:0.0, dy:-4.9)
Remember also that when you apply other forces vectors like velocity you should maintain the dx property to 0.0 as constant if you want to block the x axis.
You could find more details to the official docs
Update (after your details to the comments below):
You could also anchored your sprite to the ground with an SKPhysicsJoint (I don't know your project so this is only for example):
var myJoint = SKPhysicsJointPin.joint(withBodyA: yourSprite.physicsBody!, bodyB: yourGround.physicsBody!, anchor: CGPoint(x: yourSprite.frame.minX, y: yourGround.frame.minY))
self.physicsWorld.add(myJoint)
You can work with the anchor property to create a good joint as you wish or adding more joints.

How to adjust rotation of a SKSpriteNode

I've been working on a top view game, where there're two virtual joysticks present to control a character. For the joysticks, I've used Spritekit-Joystick. The character shoots automatically. The right Joystick is used for changing direction, and I have implemented this function to move the character:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (self.imageJoystick.velocity.x != 0 || self.imageJoystick.velocity.y != 0)
{
myCharacter.zRotation = self.imageJoystick.angularVelocity;
}
}
For the self.imageJoystick.angularVelocity, the Joystick library uses this code (the code is available from the link above, but just for the sake of making things easier and more readily available):
angularVelocity = -atan2(thumbNode.position.x - self.anchorPointInPoints.x, thumbNode.position.y - self.anchorPointInPoints.y);
There' a problem with the rotation. When I push the joystick (thumbnode) up, the rotation points to the right. I've used NSLog to get the value, and it shows around 0. When I push the joystic (thumbnode above) to the left, the character points upwards, and I get around 1.5 reading for angularVelocity. Pushing it down points the character to left and gives a -3 reading. Finally, when I push right, I point the character to South, and geta reading of around 1.5.
The same thing happens with the 'bullets' I shoot out. For now I'm using beams, which I found the answer in this [POST] on here2. I have added it below for your convenience (Not my answer, but I've changed it to be used in my game):
[[self childNodeWithName:#"RayBeam"]removeFromParent];
int x = myCharacter.position.x + 1000 * cos(myCharacter.zRotation);
int y = myCharacter.position.y + 1000 * sin(myCharacter.zRotation);
SKShapeNode* beam1 = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, myCharacter.position.x, myCharacter.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, x, y);
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor redColor]];
[beam1 setName:#"RayBeam"];
[self addChild:beam1];
The exact same thing happens. When I 1) push the joystick up, I shoot to right. 2) push the joystick to the left, I shoot Upward. 3) push downward, I shoot left, and 4) when I push the joystick to the right, I shoot downward.
I think I need to tweek the math and trigonometry equations, but maths isn't my forte, and I'm relatively new to this.
The image below is the image of the game (it's not completed at all), and you can see I'm pushing up but I'm pointing, and shooting to right.
Note: my sprite is also point to the right by default. I tried rotating all my sprites counter clockwise, and that works with the sprites, but not with the beam.
I really appreciate your help.
Thank you in advance.
If up is 0 radians then you should be calculating your beam's x and y with:
int x = myCharacter.position.x - 1000 * sin(myCharacter.zRotation);
int y = myCharacter.position.y + 1000 * cos(myCharacter.zRotation);

How to track node movement after contact in SpriteKit

I have a ball node and a hole node. I want to detect when will the ball fall into the hold.
Currently I'm using didBeginContact, but it only gives me the distance of two nodes at the begin of contact, in which case the ball won't necessarily fall into the hole (it falls when distance between two centers < hole's radius).
Is there a way I can track the position of the ball after the contact had happened?
You can add invisible SKSpriteNodes and track when the ball touches them. You would have node1 just in top of the hole (a zone that the ball MUST go through - or touch - before entering the hole) before going into the hole, and node2 just after the hole (again, a zone that the ball MUST go through - or touch - after entering the hole).
If the ball touches node1 but not node2, you know it was almost going into the hole but then it didn't. If the ball touches node2, you know the ball is trying to go through the hole from the bottom part.
I guess you can make a physics body of a hole a lot smaller. That way, when contact occurs, you will be sure that ball can fall into a hole. Take a look at this picture (second example):
On top of that as an additional idea, when contact is detected, you can let ball to move little more, and then scale it down to make impression of falling.
There is no need to test for a contact between the ball and the hole, since it doesn't tell you if the objective was completed. Alternatively, you can simply add a check in didSimulatePhysics to see if the ball is sufficiently close to the hole before starting the falling animation sequence. For example,
override func didSimulatePhysics() {
let dx = ball.position.x - hole.position.x
let dy = ball.position.y - hole.position.y
let distance = sqrt(dx*dx+dy*dy)
if (distance < hole.size.width / 2.0) {
// Start falling animation sequence
}
}
Optionally, you can test if the ball is moving fast enough to skip over the hole by
override func didSimulatePhysics() {
let dx = ball.position.x - hole.position.x
let dy = ball.position.y - hole.position.y
let distance = sqrt(dx*dx+dy*dy)
if (distance < hole.size.width / 2.0) {
// Check if the ball is moving fast
let vx = ball.physicsBody!.velocity.dx
let vy = ball.physicsBody!.velocity.dy
let speed = sqrt(vx*vx+vy*vy)
if (speed < minSpeedToSkipOverHole) {
// Start falling animation sequence
}
}
}
This problem is solved by this: I maintain a possible_falling_list of all contacted holes for each ball and use another timer to frequently check if a ball is falling into any hole. Since the list is small for each ball, the performance is not an issue.

Swift: comparing 2 object's positions in if statement not working?

Alright, so I'm trying to do something simple here and I think I've overcomplicated it- I need an if statement saying if this object goes off the screen (into negative y coordinates), have something happen. I can't get it.
I've tried a number of things, including if statements that compare to numbers like this, having it be equal and then trying greater/less than:
if block1.position.y == -50 {
savior.hidden = true
}
I've tried having the object be less than the size of the self.size.height :
if block1.position.y < self.size.height {
savior.hidden = true
}
And I've tried placing an object at the point off the screen and having an if statement comparing the 2 object's y positions:
if block1.position.y == ptBlock1.position.y {
savior.hidden = true
}
And nothing's working. Block1, the object I'm working with, is being sent to the specific point in an SKAction, so I know that it's getting there:
var moveDownLeft = SKAction.moveTo(CGPointMake(self.size.width * 0.35,-50), duration:5.5)
block1.runAction(moveDownLeft)
Why won't the if statement work?
EDIT:
I have tried this, and even when block1 visibly has a y position lower than ptBlock1, nothing happens:
if block1.position.y < ptBlock1.position.y {
savior.hidden = true
}
else if block1.position.y > ptBlock1.position.y {
savior.hidden = false
}
this object goes off the screen (into negative y coordinates)
You're making a false assumption there. Off the screen is not necessarily negative y coordinates.
The position of an SKNode is not measured with respect to screen; it is measured with respect to its superview, which is the SKScene. And the SKScene is much bigger than the screen! You need to convert from those coordinates to screen coordinates if you want to know what's happening relative to the screen.
(Just to give an example, if you make a new SpriteKit project from the template and log on touchesBegan to show the tap position, you will discover that a tap in the top left corner is at about {303,758}. So in that coordinate system an object is off the screen to the top if its y is greater than about 760. Contrast that with screen coordinates, where you are off the screen to the top if you are less than 0. These are very different coordinate systems!)

Giving a physicsBody a constant force

I have a ball bouncing up and down. I've accomplished this by setting it's physics body up like this:
linearDamping = 0
friction = 0
restitution = 1
And then just applyForce(CGVectorMake(0, 10)) in the setup call. The ball is now bouncing up and down, up and down with not interruption.
Now I want to add player control. The player can move the ball sideways. I could do this by just setting the balls velocity when the controls are pressed. BUT by doing that I lose all the stuff the physics engine gives me.
Here's a short snippet of code I have right now:
override func update(currentTime: CFTimeInterval) {
/* Apply damping only in x */
if !movingLeft && !movingRight {
let dx = ball!.physicsBody!.velocity.dx * xAlpha
let dy = ball!.physicsBody!.velocity.dy
ball!.physicsBody?.velocity = CGVectorMake(dx, dy)
}
if movingRight {
ball!.physicsBody?.applyImpulse(CGVectorMake(ballVelocityX, 0))
}
if movingLeft {
ball!.physicsBody?.applyImpulse(CGVectorMake(-ballVelocityX, 0))
}
}
The problem, as you might imagine, is that applying impulse upon impulse on each frame like this makes the ball go superfast, which is not what I want, when the controls are held down. But if I apply the force only on keyDown the ball will stop moving in that direction once the ball hits something. And also the keyDown event works wonky on OS X (because of key repeat settings).
So what I want to do is apply an impulse in the update function in a way that makes the force of the ball not exceed my limit but also not go away completely if the ball hits something.