Swift GameKit Moving Sprite In Arc - ios

I need some help in making the player in my platform game jump. The problem I'm having is that he jumps through a pillar which he's not supposed to do (I do have the physics set up correctly). I suspect it is because of my horribly inefficient code below. I know there's a better way to move him along a path but I'm not sure how to do it.
var xStep = CGFloat(18)
var yStep = CGFloat(30)
var x = CGFloat(playerJump.position.x)
var y = CGFloat(playerJump.position.y)
let follow = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y += yStep
let follow2 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y += yStep
let follow3 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y += yStep
let follow4 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y += yStep
let follow5 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y -= yStep
let follow6 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y -= yStep
let follow7 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y -= yStep
let follow8 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
x += xStep
y -= yStep
let follow9 = SKAction.move(to: CGPoint(x: x, y:y), duration: 0.1)
let group = SKAction.group([
SKAction.repeat(jumpAnimation!, count: 1),
SKAction.sequence([follow,follow2,follow3,follow4,follow5,follow6,follow7,follow8,follow9])
])
if playerJump.parent == nil {
addChild(playerJump)
}
playerJump.run(SKAction.sequence([group]), completion: {
self.player.position=self.playerJump.position
self.playerJump.removeAllActions()
self.playerJump.removeFromParent()
self.addChild(self.player)
})
Thanks in advance for any assistance.
UPDATE
The problem started when I increased the value in xStep. The total distance the character jumps would put it past the pillar. That's why I thought my code was an issue. Here's a video of what's happening.
Jumping Video
UPDATE 2
Here's my newest code which still puts the player on the other side of the pillar.
#objc func jumpTapped() {
var xStep = CGFloat(18)
let yStep = CGFloat(12)
jumping=true
xStep = player.xScale * xStep
var x = player.position.x
var y = player.position.y
var moveAnimation = [SKAction]()
for i in 0...8 {
x += xStep
if i < 5 {
y += yStep
} else {
y -= yStep
}
let move = SKAction.move(to: CGPoint(x: x, y: y), duration: 0.1)
moveAnimation.append(move)
}
let group = SKAction.group([jumpAnimation!, SKAction.sequence(moveAnimation)])
player.physicsBody?.affectedByGravity = false
player.removeAllActions()
player.run(group) {
self.endJump()
}
}
func endJump() {
player.removeAllActions()
player.physicsBody?.affectedByGravity = true
player.run(SKAction.repeatForever(self.playerAnimation!))
jumping=false
}

It looks like you are setting player.position to self.playerJump.position
That is probably your problem, it is jumping beyond where the pillar is. You might as well have said player.position.x = player.position.x + 500 which will put you on the other side of the pillar as well ignoring any physics all together.
Why are you adding a sprite and running the actions on it? why not just run the animation and the actions on the player sprite?
As far as cleaning up your code goes you can try this. there are other things you can do as well like have the character jump along a Bezier path but this might be fine for what you are doing. But I highly doubt this will have any impact on your issue of the player jumping through the pillar.
Some small tidbits for you.
Sprite positions are already a CGFloat no need to cast them
short hand completion code syntax is object.run(someAction) { //do something when complete }
running an action with repeat of 1 is pointless
running a sequence of a single action (SKAction.sequence([group])) is also pointless
var xStep = CGFloat(18)
var yStep = CGFloat(30)
var x = playerJump.position.x
var y = playerJump.position.y
var moveAnimation: [SKAction]!
for _ in 0...9 {
x += xStep
y += yStep
moveAnimation.append(SKAction.move(to: CGPoint(x: x, y: y), duration: 0.1))
}
let group = SKAction.group([jumpAnimation, SKAction.sequence(moveAnimation)])
guard playerJump.parent else { addChild(playerJump)}
playerJump.run(group) {
self.player.position = self.playerJump.position
self.playerJump.removeAllActions()
self.playerJump.removeFromParent()
self.addChild(self.player)
}
Edit try adding the below function to your code
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "pillar") || (contactBName == "pillar") {
//assuming only player can hit pillar so don't need to check if one of the contacts is player
endJump()
}
}

I figured out what I was doing wrong. My mistake was moving the sprite within the SKAction. I should have been moving the sprite in the update method. So, I added this to update():
if jumping {
if jumpCount > 3 {
player.position.x += xStep // Jump straight up for a couple of frames to clear steps
}
if jumpCount < maxJump/2 {
player.position.y += yStep //Move sprite up for first half of jump
} else {
player.position.y -= yStep //Move sprite down for last half of jump
}
jumpCount += 1
if jumpCount >= maxJump {
endJump()
}
}
All the collision detection works properly and the player doesn't jump through pillars.

Related

Sceneview Translate /move Point of view camera with gesture

So I need to move Point of view of the sceneview with the pan gesture in Mac catalyst app.
for iPhone and iPad there is default two finger drag.
I have tried with
let point = gesture.location(in: self)
let v1 = self.projectPoint(SCNVector3Zero)
let vector = self.unprojectPoint(SCNVector3Make(Float(point.x), Float(point.y), v1.z))
self.pointOfView?.position = SCNVector3(vector.x, vector.y, self.pointOfView!.position.z)
if I apply this to SceneKit root node it is working , but I need to move point of view only not the position of the root node
Please suggest
Whoever still not get it
here is the solution
var previousLocation = SCNVector3(x: 0, y: 0, z: 0)
#objc func panned(gesture:UIPanGestureRecognizer) {
let view = self.sceneView1!
let translation = gesture.translation(in: view)
let location = gesture.location(in: view)
let secLocation = CGPoint(x: location.x + translation.x, y: location.y + translation.y)
let P1 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 0.0))
let P2 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 1.0))
let Q1 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 0.0))
let Q2 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 1.0))
let t1 = -P1.z / (P2.z - P1.z)
let t2 = -Q1.z / (Q2.z - Q1.z)
let x1 = P1.x + t1 * (P2.x - P1.x)
let y1 = P1.y + t1 * (P2.y - P1.y)
let P0 = SCNVector3Make(x1, y1,0)
let x2 = Q1.x + t2 * (Q2.x - Q1.x)
let y2 = Q1.y + t2 * (Q2.y - Q1.y)
let Q0 = SCNVector3Make(x2, y2, 0)
var diffR = Q0 - P0
diffR = SCNVector3Make(diffR.x * -1, diffR.y * -1, diffR.z * -1)
// diffR *= -1
let cameraNode = view.pointOfView
switch gesture.state {
case .began:
previousLocation = cameraNode!.position
break;
case .changed:
cameraNode?.position = SCNVector3Make(previousLocation.x + diffR.x, previousLocation.y + diffR.y, previousLocation.z + diffR.z)
break;
default:
break;
}
}

Why is my SKSpriteNode not traveling in a straight line as the code intends?

I have code of a subclass of SKScene, which show cars driving from the right to the left. For some reason, the cars drive at an angle instead of straight. Below is the code for my subclass, excluding the parts that do not have anything to do with the problem. Why are the cars not driving in a straight line?
override func didMove(to view: SKView) {
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(addCompetitor),
SKAction.wait(forDuration: 3.0)
])
))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / /* 0xFFFFFFFF */ 4294967296)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addCompetitor() {
// Create sprite
let car = SKSpriteNode(imageNamed: "utili")
car.physicsBody = SKPhysicsBody(rectangleOf: car.size) // 1
// Determine where to spawn the car along the Y axis
let actualY = random(min: car.size.height/2, max: size.height - car.size.height/2)
// Position the car slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
car.position = CGPoint(x: size.width + car.size.width/2, y: actualY)
// Add the car to the scene
addChild(car)
// Determine speed of the car
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
// Create the actions
let actionMove = SKAction.move(to: CGPoint(x: -car.size.width/2, y: actualY), duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
car.run(SKAction.sequence([actionMove, actionMoveDone]))
}

Trying to move SKShapeNodes one at a time in a loop

I want to move the nodes from an offscreen position and line them up across the X-axis spaced out. The nodes have been created and stored in an array. I have the following code to do it. However, all of the nodes move at the same time. I want them to move successively when the prior node is in its new position. I have tried altering the duration of the wait action, but it doesn't work.
Thanks
var shapeNodes: [SKShapeNode] = [SKShapeNode]()
let w = (size.width + size.height) * 0.05
for _ in 1...5 {
let s = SKShapeNode(rectOf: CGSize(width: w, height: w), cornerRadius: w * 0.3)
s.position = CGPoint(x: frame.midX, y: frame.maxY + 75)
s.zPosition = 3
s.fillColor = UIColor.cyan
shapeNodes.append(s)
addChild(s)
}
var posX: CGFloat = 0.0
for n in shapeNodes {
let m = SKAction.moveBy(x: posX, y: -300, duration: 2.0)
m.timingMode = .easeIn
let w = SKAction.wait(forDuration: 2.0)
let seq = SKAction.sequence([ m, w ])
n.run(seq)
posX += 125
}
Give each node a wait action with a duration equal to the animation time of all previous nodes. The logic is very similar to your logic with the posX variable.
var posX: CGFloat = 0.0
var waitDuration: CGFloat = 0
for n in shapeNodes {
let duration = 2
let m = SKAction.moveBy(x: posX, y: -300, duration: duration)
m.timingMode = .easeIn
let w = SKAction.wait(forDuration: waitDuration)
let seq = SKAction.sequence([w, m])
n.run(seq)
posX += 125
waitDuration += duration
}
In the example above, I created another variable called waitDuration, which keeps track of the total time the node should wait before animating.
The duration of the wait action is set to the waitDuration, and at the end of the loop, I increase the waitDuration by the duration of the move action.

Node rotation in Scenekit

Im having trouble figuring out how to rotate my player node towards each click. Ive already got the intercept of a click on the x-z plane and set the node to move to it, and I have found out the radians of the click compared to the plane of x and z on which the character moves. After the character isn't at 0 y rotation my method doesn't work though. Im guessing theres an easier way to do this, just hoping someone can point me to some documentation or tell me what way I can figure out the difference between the clicks angle and the current orientation of the player and make him face towards the movement.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if game.state == .tapToPlay {
startGame()
}
if game.state == .playing {
let touch = touches.first!
let location = touch.location(in: scnView)
let hitResults = scnView.hitTest(location, options: nil)
if hitResults.first?.node.name == "Grass" {
//pigNode.runAction(SCNAction.move(to: (hitResults.first?.localCoordinates)!, duration: 1.0))
let moveAction = SCNAction.move(to: (hitResults.first?.localCoordinates)!, duration: 1.0)
let location = hitResults.first?.localCoordinates
/*
pigNode.runAction(SCNAction.rotateTo(x: 0.0,
y: CGFloat(offset.y),
z: 0.0, duration: 1.0, usesShortestUnitArc: true))
let rotateAction = SCNAction.rotateTo(x: CGFloat((locationOnPlane?.x)!),
y: CGFloat((locationOnPlane?.y)!),
z: CGFloat((locationOnPlane?.z)!),
duration: 1.0, usesShortestUnitArc: true)
*/
let locationOnPlane = CGPoint(x: Double((location?.x)!), y: Double((location?.z)!))
let offset = CGPoint(x: Double(locationOnPlane.x) - Double(pigNode.position.x), y: Double(locationOnPlane.y) - Double(pigNode.position.z))
let length = sqrt(offset.x * offset.x + offset.y * offset.y)
let direction = CGPoint(x: offset.x / length, y: offset.y / length)
let rotationAngle = CGFloat(atan2(direction.y, direction.x))
let rotateAction = SCNAction.rotate(by: rotationAngle, around: SCNVector3(x: 0.0, y: 1.0, z: 0.0), duration: 1.0)
//let rotateAction = SCNAction.rotateBy(x: 0.0, y: rotationAngle, z: 0.0, duration: 1.0)
//let rotateAction = SCNAction.rotateTo(x: 0.0, y: rotationAngle, z: 0.0, duration: 1.0, usesShortestUnitArc: true)
print(rotationAngle)
print(pigNode.eulerAngles.y)
let groupAction = SCNAction.group([moveAction, rotateAction])
pigNode.runAction(groupAction)
}
}
}
Check this answer Rotate a sprite to sprite position not exact in SpriteKit with Swift this is the code for rotation
let angle = atan2(location.y - cannon.position.y , location.x - cannon.position.x)
cannon.zRotation = angle - CGFloat(M_PI_2)
I hope this helps you

Scenekit Pan 2D Translation to Orthographic 3D only horizontal

I am having a little more mathematical problem with 3D programming and I am hoping you can help me!
I am trying to create a 3D game using Scenekit with a isometric angle.
This code creates my orthographic camera:
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.name = "Camera"
cameraNode.position = SCNVector3Make(-5.0, -5.0, 10.0)
cameraNode.eulerAngles = SCNVector3Make(PI / 3.0, 0.0, -PI / 4.0)
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = 7.0
scene.rootNode.addChildNode(cameraNode)
Now i want to move the camera using a pan gesture, producing a scroll feeling. To make this possible the camera shouldn't move vertically, only horizontally. The touch location on screen and the unprojected position in the 3D world should stay the same while moving.
I thought about calculating the 2D translation into 3D difference and ignoring the vertical component. This code actually works and almost produces the desired result, but the speed is not correct. If I pan, the camera seems to accelerate and not react correctly:
var previousTranslation = CGPointMake(0.0, 0.0)
func pan(gesture: UIPanGestureRecognizer)
{
let view = self.view as SCNView
let translation = gesture.translationInView(view)
let location = gesture.locationInView(view)
let diffTrans = translation - previousTranslation
previousTranslation = translation
let cameraNode = scene.rootNode.childNodeWithName("Camera", recursively: false)
let worldPointTrans = view.unprojectPoint(SCNVector3Make(-Float(diffTrans.x), -Float(diffTrans.y), 0.0))
let worldPoint0 = view.unprojectPoint(SCNVector3Make(0.0, 0.0, 0.0))
var diff = worldPointTrans - worldPoint0
diff.x = diff.x / Float(cameraNode!.camera!.orthographicScale)
diff.y = diff.y / Float(cameraNode!.camera!.orthographicScale)
diff.z = 0
cameraNode?.position += diff
}
Does anybody know a sophisticated way of calculating a screen translation into a horizontal 3D translation, ignoring the vertical axis?
Thank you in advance :)
EDIT:
The pan works for horizontal translation now. But not for vertical, because I set the difference on the z axis to zero.
I found my own solution.!
I am calculating a ray at the start location of the gesture (P1-P2) and a ray at the translated location (Q1-Q2). Now I have two rays and I let both intersect with the XY Plane to receive the points P0 and Q0
The difference of P0 and Q0 is the unprojected translation.
This technique should also work with a non-orthogonal camera, but i didn't test this yet.
It seems to me that it works, but if anybody could mathematically confirm this assumption, I would be glad to read that :)
Here is the code:
var previousLocation = SCNVector3(x: 0, y: 0, z: 0)
func pan(gesture: UIPanGestureRecognizer)
{
let view = self.view as SCNView
let translation = gesture.translationInView(view)
let location = gesture.locationInView(view)
let secLocation = location + translation
let P1 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 0.0))
let P2 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 1.0))
let Q1 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 0.0))
let Q2 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 1.0))
let t1 = -P1.z / (P2.z - P1.z)
let t2 = -Q1.z / (Q2.z - Q1.z)
let x1 = P1.x + t1 * (P2.x - P1.x)
let y1 = P1.y + t1 * (P2.y - P1.y)
let P0 = SCNVector3Make(x1, y1,0)
let x2 = Q1.x + t1 * (Q2.x - Q1.x)
let y2 = Q1.y + t1 * (Q2.y - Q1.y)
let Q0 = SCNVector3Make(x2, y2, 0)
var diffR = Q0 - P0
diffR *= -1
let cameraNode = view.scene!.rootNode.childNodeWithName("Camera", recursively: false)
switch gesture.state {
case .Began:
previousLocation = cameraNode!.position
break;
case .Changed:
cameraNode?.position = previousLocation + diffR
break;
default:
break;
}
}
I've calculated the equations for the isometric panning, the code is below.
//camera pan ISOMETRIC logic
func pan(gesture: UIPanGestureRecognizer) {
let view = self.sceneView as SCNView
let cameraNode = view.scene!.rootNode.childNode(withName: "Camera", recursively: false)
let translation = gesture.translation(in: view)
let constant: Float = 30.0
var translateX = Float(translation.y)*sin(.pi/4.0)/cos(.pi/3.0)-Float(translation.x)*cos(.pi/4.0)
var translateY = Float(translation.y)*cos(.pi/4.0)/cos(.pi/3.0)+Float(translation.x)*sin(.pi/4.0)
translateX = translateX / constant
translateY = translateY / constant
switch gesture.state {
case .began:
previousLocation = cameraNode!.position
break;
case .changed:
cameraNode?.position = SCNVector3Make((previousLocation.x + translateX), (previousLocation.y + translateY), (previousLocation.z))
break;
default:
break;
}
}
And to get scaling right, you need to use screenheight as a variable for orthographicScale. The scaling i used here is 30x magnification, note the 30 is also used for the constant in the code above.
let screenSize: CGRect = UIScreen.main.bounds
let screenHeight = screenSize.height
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.name = "Camera"
let cameraDist = Float(20.0)
let cameraPosX = cameraDist*(-1.0)*cos(.pi/4.0)*cos(.pi/6.0)
let cameraPosY = cameraDist*(-1.0)*sin(.pi/4.0)*cos(.pi/6.0)
let cameraPosZ = cameraDist*sin(.pi/6)
cameraNode.position = SCNVector3Make(cameraPosX, cameraPosY, cameraPosZ)
cameraNode.eulerAngles = SCNVector3Make(.pi / 3.0, 0.0, -.pi / 4.0)
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = Double(screenHeight)/(2.0*30.0) //30x magnification constant. larger number = larger object
scene.rootNode.addChildNode(cameraNode)

Resources