I have created an SKCameraNode which is is able to move around and look at a to-scale version of the solar system.
I am having an issue when the camera zooms in and moves around as shown here:
https://gfycat.com/HighScarceHorsemouse
This is the code that moves the position of the camera:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let previousPointOfTouch = touch.previousLocation(in: self)
let amountDraggedY = (pointOfTouch.y - previousPointOfTouch.y)
let amountDraggedX = (pointOfTouch.x - previousPointOfTouch.x)
hud.touchesMoved(x: amountDraggedX, y: amountDraggedY)
}
}
hud.touchesMoved calls this:
func touchesMoved(x: CGFloat, y:CGFloat){
print("Previous Position:" + cam.position.debugDescription)
print("Position Change: (\(x), \(y))")
cam.position.x = cam.position.x - x
cam.position.y = cam.position.y - y
print("New Position: " + cam.position.debugDescription)
}
The output of the print function gives me this when the camera is zoomed out:
Previous Position:(-2940752.25, 86727.8203125)
Position Change: (-2940752.25, -2940752.25)
New Position: (-2940752.25, 86727.8203125)
When the camera zooms in, and moves around I get these values
Previous Position:(-2940770.0, 86966.34375)
Position Change: (0.0, -0.0859375)
New Position: (-2940770.0, 86966.4296875)
For some reason when I am moving my finger across the screen, the touchesMoved function doesn't give a value for the change in position in the x-axis, and instead gives zero. Once I move my finger a certain amount left or right and it snaps to the next position.
I tried getting the location of the touch in the view (instead of the SKScene), and then multiplied it by the camera scale to get a more precise number to move the camera by. But for some reason the position of the camera refused to move until it was given a value big enough to snap left or right.
Also, the video only shows it jittering only along the x-axis. This is when the earth was almost directly left of the sun. When the earth completes 1/4th of its orbit and is above the sun, the jittering and movement issues will only happen along the y-axis. This also means that the jitteriness does not occur when the camera is near (0, 0).
I then tried scaling down the entire solar system because I thought the large numbers were causing the issue. But it still didn't fix it. Then I tried using an SKAction to move the camera but that did not work either.
Could this be an issue with using CGFloats. I thought maybe they are not giving precise enough value and instead rounding the position values of the camera.
Any clues or indications as to what this could be would be extremely helpful.
EDIT:
So it looks like this a problem when setting a CGPoint equal to an SKNode's position
func updatePos(delta:CGFloat, ct:CGFloat){
let t = CGFloat(Int(ct*10000) % Int(T*10000))/10000
let newPos = getPos(at: t)
self.prevPos = self.position
self.position = newPos
self.vel = calcVel()
if(self.name == "Earth"){
print("P1: " + newPos.debugDescription)
print("P2: " + self.position.debugDescription)
//self.position.
}
}
This prints:
P1: (-147027315091.13248, 4715744568.852638)
P2: (-147027312640.0, 4715744768.0)
P1: (-147027315075.2827, 4715745071.273114)
P2: (-147027312640.0, 4715745280.0)
P1: (-147027315059.33746, 4715745576.7201805)
P2: (-147027312640.0, 4715745792.0)
P1: (-147027315043.39218, 4715746082.167245)
P2: (-147027312640.0, 4715746304.0)
P1: (-147027315027.54242, 4715746584.587657)
P2: (-147027312640.0, 4715746816.0)
P1: (-147027315011.59717, 4715747090.034788)
P2: (-147027312640.0, 4715747328.0)
P1: (-147027314995.65192, 4715747595.481853)
P2: (-147027312640.0, 4715747840.0)
My calcPos() function will calculate a very accurate position, however when I set it equal to the the node's position it all of a sudden gets rounded. This definitely is the cause of the issue. Is there a way to fix or get around this?
Related
I'm trying to move a node to a touch location using physicsBody (as opposed to SKAction). I am trying to use applyForce, but I may be going about this wrong.
In my touchesBegan function I record the touch location in a constant and pass it into my movePlayer function. The initial touch works as expected. However, subsequent touches seem to move from a relative position, which makes me think that I should recalculate my vector. I'm just not sure how to do this. Any help is appreciated.
func movePlayer(to touchPoint: CGPoint) {
let vector = CGVector(dx: touchPoint.x, dy: touchPoint.y)
player.physicsBody?.applyForce(vector, at: player.position)
player.physicsBody?.velocity = vector
playerIsMoving = true
}
This question and others discuss how to track a node in SpriteKit using a SKCameraNode.
However, our needs vary.
Other solutions, such as updating the camera's position in update(_ currentTime: CFTimeInterval) of the SKScene, do not work because we only want to adjust the camera position after the node has moved Y pixels down the screen.
In other words, if the node moves 10 pixels up, the camera should remain still. If the node moves left or right, the camera should remain still.
We tried animating the camera's position over time instead of instantly, but running a SKAction against the camera inside of update(_ currentTime: CFTimeInterval) fails to do anything.
I just quickly made this. I believe this is what you are looking for?
(the actual animation is smooth, just i had to compress the GIF)
This is update Code:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
SKShapeNode *ball = (SKShapeNode*)[self childNodeWithName:#"ball"];
if (ball.position.y>100) camera.position = ball.position;
if (fabs(ball.position.x-newLoc.x)>10) {
// move x
ball.position = CGPointMake(ball.position.x+stepX, ball.position.y);
}
if (fabs(ball.position.y-newLoc.y)>10) {
// move y
ball.position = CGPointMake(ball.position.x, ball.position.y+stepY);
}
}
I would not put this in the update code, try to keep your update section clutter free, remember you only have 16ms to work with.
Instead create a sub class for your character node, and override the position property. What we are basically saying is if your camera is 10 pixels away from your character, move towards your character. We use a key on our action so that we do not get multiple actions stacking up and a timing mode to allow for the camera to smoothly move to your point, instead of being instant.
class MyCharacter : SKSpriteNode
{
override var position : CGPoint
{
didSet
{
if let scene = self.scene, let camera = scene.camera,(abs(position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
}
}
Edit: #Epsilon reminded me that SKActions and SKPhysics access the variable directly instead of going through the stored property, so this will not work. In this case, do it at the didFinishUpdate method:
override func didFinishUpdate()
{
//character should be a known property to the class, calling find everytime is too slow
if let character = self.character, let camera = self.camera,(abs(character.position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: character.position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
So basically I have the coin object, and I want to launch it across the screen. I have the following code which runs some calculations on the swipe, but not sure what is relevant for my current situation:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = true
let touch = touches.first
let loc = touch?.locationInNode(self)
if coin.containsPoint(loc!){
touchPoint = loc!
touchTime = (touch?.timestamp)!
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = false
let touch = touches.first
let loc = touch?.locationInNode(self)
var xDiff = (loc?.x)! - touchPoint.x
var yDiff = (loc?.y)! - touchPoint.y
let distance = sqrt(xDiff * xDiff + yDiff * yDiff)
}
I want the coin to be essentially thrown in the direction of the swipe, but I want it to reach a certain y-coordinate on the screen each time, before falling back down due to gravity. So I guess I need to somehow calculate the perfect speed each time to make it reach that y-point, and then push the coin object in the swipe direction at that speed?
Any help much appreciated! I'm online for the next few hours, so ask for more info and I'll be able to get back to you pretty quick.
It depends if you want the coin to be interacting with other physics bodies afterward. You have attached several "physics" tags to the question so I assume you want that :)
I'd go for a mathematics-based approach indeed. So basically you are just getting a direction from the swipe. The speed itself is dependent on the gravity that's affecting the coin.
You can separate the speed vector in the vertical and horizontal component.
The vertical distance to cover is let verticalDistance = targetHeight - coin.position.y. The coin has to have zero vertical speed at the highest point, and we know that during the animation it decelerates with a constant -9.81 pt/s*s (off the top of my head).
So the speed graph looks like this:
with formula f(x) = -9.81 * x + v where v is the vertical starting speed we are solving for. Algebra tells us that f(x) intersects with the x-axis with x-value v / 9.81.
The total distance covered up until the point where f(x) intersects with the x-axis is equal to the surface area of the triangle under f(x). This triangle's surface is 1/2 * v/9.81 * v (which is 1/2 * width * height).
Since you know what this distance should be (verticalDistance from previous paragraph), algebra dictates v = sqrt(2 * 9.81 * d). Now you know you starting vertical speed, based on the height the coin has to travel!
Since you know the coins angle (dictated by user's swipe) and the vertical speed, you can calculate the horizontal speed. I'm leaving that to you ;) Note that we are of course ignoring drag / friction which could impact the coin speed. Also my algebra might be rusty.
As a side note
I would replace let touch = touches.first with
guard let touch = touches.first else {
return
}
In this way, touch is a non-optional.
I'm a fairly new beginner into the iOS world, so forgive me if I leave out some details or if I'm not being clear enough. I have a ball placed on the screen at the bottom and would like to know how to make it go left if the user taps on the left half of iPhone and go right if the user taps on the right half of the iPhone.
The code that I'm trying to make work is this:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationOfTouch(<#touchIndex: Int#>, inView: <#UIView?#>)
}
ball!.physicsBody!.applyImpulse(CGVectorMake(25.0, 40.0))
}
I know there is code missing, but I can't seem to understand how to approach it. Am I doing this right? I will deeply appreciate your help.
so I did figure out the code. This is what I did:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touch = touches.first as! UITouch
var point = touch.locationInView(self.view)
if point.x < size.width / 2 {
ball!.physicsBody!.applyImpulse(CGVectorMake(-5.0, 10.0))
}
else {
ball!.physicsBody!.applyImpulse(CGVectorMake(5.0, 10.0))
}
}
Now, I'm coming across another problem, which might not be as complicated as I'm thinking to solve it. So, initially the ball is stationary when the app launches, and it goes in (-5,10) direction if tapped left, and (5,10) if tapped right. The problem is, when I tap right, when the app starts, it goes in (5,10) direction, but it doesn't go in left in the same direction. If I tap on right first when app launches and ball starts moving towards (5,10) direction, I want it to move left in the exact same direction from which it started while moving forward, almost in like a zig zag format. Something like this format /V, if you were to look at that in portrait view, except the line will be the ball going left and right. I hope it makes sense :)
I will keep trying to figure it out and hopefully have it figured out by the time you read it.
I'm also fairly new so I don't know of a more advanced solution.
What I would do is draw/add a line in the center of the screen. For example, I would use a SKSpriteNode and place it in the center of the screen.
var middleLine = SKSpriteNode(imageNamed: "CenterLine")
middleLine.alpha = 0 //you don't want the line to show
middleLine.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
This would place the line sprite at the center of the screen. And then, inside the touchesBegan, I would make an if statement concerning if the tap is less than the y coordinate of the center line or greater. If it's less, then the tap would be on the left, if it's greater than the y coordinate, the tap would be on the right.
You would need to make it return the location of the touch where the user tapped in order to compare.
EDIT: To make it easier, you don't even have to add the sprite, just compare the touch x and y to the CGRectGetMidX(self.frame) and CGRectGetMidY(self.frame)
I used this in my game when figuring out if an object was leaving the screen. I compared its x,y coordinates if it was greater than the coordinates inside the frame (or less than).
I'm trying to build a game, using SpriteKit, in which there's a ball that bounces up and down. Now I want to let the player control the balls movement in the X axis and let the physics engine control the Y velocity.
For example, when the ball hits a corner it starts moving sideways on it's own. I would like it to bounce of the corner and then quickly stabilize and stop moving side-ways. Is there anyway of doing this without trying to counteract any sideways movement by applying an impulse? Would it be easier to just manually control the ball's movement up and down?
I've tried applying a counteracting force without much success (the ball freaks out):
override func update(currentTime: NSTimeInterval) {
let ballDx = ball?.physicsBody?.velocity.dx
if let ballVelocityX = ballDx {
if ballVelocityX != 0 {
ball?.physicsBody?.applyForce(CGVectorMake(ballVelocityX * -1, 0))
}
}
}
Sounds like you need to apply linear damping in the x direction. Here's an example of how to do that:
// Adjust this value as needed. It should be in [0,1], where a value of 1 will
// have no effect on the ball and a value of 0 will stop the ball immediately.
let xAlpha:CGFloat = 0.95
override func update(currentTime: CFTimeInterval) {
/* Apply damping only in x */
let dx = sprite.physicsBody!.velocity.dx * xAlpha
let dy = sprite.physicsBody!.velocity.dy
sprite.physicsBody?.velocity = CGVectorMake(dx, dy)
}