I am currently making an iPhone game using SpriteKit in which two archers fire arrows at each other in an attempt to take away all of their opponent's health. I've got the mechanism for firing the arrow when the user drags back on the screen, but am wondering if there is a way to make the arrow sprite rotate in mid air as it follows the arc (facing upwards immediately after release and facing downward immediately prior to landing) so that it actually looks as if it were fired from a bow in real life.
This is the piece of code that is ran when the user's touch ends. You can see where I apply the impulse but I am not sure how to go about implementing the rotation. I understand how I could get the initial rotation to match the angle of the trajectory line using simple trigonometry, but don't know how to make it rotate with its arc.
func touchUp(atPoint pos : CGPoint) {
if arrowFired == false && firstTouchMade == true && pointOne != nil {
//Set pointTwo
pointTwo = pos
//Define the vert/hor of the pull-back
let verticalLeg = ((pointOne?.y)! - (pointTwo?.y)!)
let horizontalLeg = ((pointOne?.x)! - (pointTwo?.x)!)
//Apply the impulse
arrow?.run(SKAction.applyImpulse(CGVector(dx: horizontalLeg / 9, dy: verticalLeg / 9), duration: 0.001))
arrow?.physicsBody?.affectedByGravity = true
let array = [trajectoryLine, trajectoryLabel] as [Any]
removeChildren(in: array as! [SKNode])
arrowFired = true
} else if firstTouchMade == false && zoomingIn == false {
zoomingIn = true
cameraNode.run(SKAction.move(to: (arrow?.position)!, duration: 1), completion: {
self.firstTouchMade = true
self.zoomingIn = false
})
cameraNode.run(SKAction.scale(by: 0.5, duration: 1))
}
}
Sorry if this is an obvious question, I am relatively new to programming, Swift, and SpriteKit especially.
Related
In an AR app, I want to detect collisions between the user walking around and the walls of an AR node that I construct. In order to do that, I create an invisible cylinder right in front of the user and set it all up to detect collisions.
The walls are all part of a node which is a child of sceneView.scene.rootNode.
The cylinder, I want it to be a child of sceneView.pointOfView so that it would always follow the camera.
However, when I do so, no collisions are detected.
I know that I set it all up correctly, because if instead I set the cylinder node as a child of sceneView.scene.rootNode as well, I do get collisions correctly. In that case, I continuously move that cylinder node to always be in front of the camera in a renderer(updateAtTime ...) function. So I do have a workaround, but I'd prefer it to be a child of pointOfView.
Is it impossible to detect collisions if nodes are children of different root nodes?
Or maybe I'm missing something in my code?
The contactDelegate is set like that:
sceneView.scene.physicsWorld.contactDelegate = self so maybe this only includes sceneView.scene, but will exclude sceneView.pointOfView???
Is that the issue?
Here's what I do:
I have a separate file to create and configure my cylinder node which I call pov:
import Foundation
import SceneKit
func createPOV() -> SCNNode {
let pov = SCNNode()
pov.geometry = SCNCylinder(radius: 0.1, height: 4)
pov.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
pov.opacity = 0.3 // will be set to 0 when it'll work correctly
pov.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
pov.physicsBody?.isAffectedByGravity = false
pov.physicsBody?.mass = 1
pov.physicsBody?.categoryBitMask = BodyType.cameraCategory.rawValue
pov.physicsBody?.collisionBitMask = BodyType.wallsCategory.rawValue
pov.physicsBody?.contactTestBitMask = BodyType.wallsCategory.rawValue
pov.simdPosition = simd_float3(0, -1.5, -0.3) // this position only makes sense when setting as a child of pointOfView, otherwise the position will always be changed by renderer
return pov
}
Now in my viewController.swift file I call this function and set is as a child of either root nodes:
pov = createPOV()
sceneView.pointOfView?.addChildNode(pov!)
(Don't worry right now about not checking and unwrapping).
The above does not detect collisions.
But if instead I add it like so:
sceneView.scene.rootNode.addChildNode(pov!)
then collisions are detected just fine.
But then I need to always move this cylinder to be in front of the camera and I do it like that:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let currentPosition = pointOfView.simdPosition
let currentTransform = pointOfView.simdTransform
let orientation = SCNVector3(-currentTransform.columns.2.x, -currentTransform.columns.2.y, -currentTransform.columns.2.z)
let currentPositionOfCamera = orientation + SCNVector3(currentPosition)
DispatchQueue.main.async {
self.pov?.position = currentPositionOfCamera
}
}
For completeness, here's the code I use to configure the node of walls in ViewController (they're built elsewhere in another function):
node?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: node!, options: nil))
node?.physicsBody?.isAffectedByGravity = false
node?.physicsBody?.mass = 1
node?.physicsBody?.damping = 1.0 // remove linear velocity, needed to stop moving after collision
node?.physicsBody?.angularDamping = 1.0 // remove angular velocity, needed to stop rotating after collision
node?.physicsBody?.velocityFactor = SCNVector3(1.0, 0.0, 1.0) // will allow movement only in X and Z coordinates
node?.physicsBody?.angularVelocityFactor = SCNVector3(0.0, 1.0, 0.0) // will allow rotation only around Y axis
node?.physicsBody?.categoryBitMask = BodyType.wallsCategory.rawValue
node?.physicsBody?.collisionBitMask = BodyType.cameraCategory.rawValue
node?.physicsBody?.contactTestBitMask = BodyType.cameraCategory.rawValue
And here's my physycsWorld(didBegin contact) code:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue || contact.nodeB.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue {
print("Begin COLLISION")
contactBeginLabel.isHidden = false
}
}
So I print something to the console and I also turn on a label on the view so I'll see that collision was detected (and the walls indeed move as a whole when it works).
So Again, it all works fine when the pov node is a child of sceneView.scene.rootNode, but not if it's a child of sceneView.pointOfView.
Am I doing something wrong or is this a limitation of collision detection?
Is there something else I can do to make this work, besides the workaround I already implemented?
Thanks!
Regarding the positioning of your cyliner:
instead to use the render update at time, you better use a position constraint for your cylinder node to move with the point of view. the result will be the same, as if it were a child of the point of view, but collisions will be detected, because you add it to the main rootnode scenegraph.
let constraint = SCNReplicatorConstraint(target: pointOfView) // must be a node
constraint.positionOffset = positionOffset // some SCNVector3
constraint.replicatesOrientation = false
constraint.replicatesScale = false
constraint.replicatesPosition = true
cylinder.constraints = [constraint]
There is also an influence factor you can configure. By default the influence is 100%, the position will immediatly follow.
I am using Mapbox in my iOS Project with Swift language. I am currently looking a way to programatically rotate the mapview at a fix altitude with certain coordinates inside. I have tried alot and even have browsed their API Reference as well but could not find any help.
Has anyone tried to do anything like this in which MapView is being continously rotating keeping certain coornidates in bounds using MAPBOX Library.
Help would be really appreciated.
Thanks to #riastrad for the guidance and I have come up with a bunch of code that can help to acheive this feature.
Sharing with everyone so that they can get help if they require:
Code is for Swift 4.2
//Create a bound using two coordinates.
let coordinateBounds = MGLCoordinateBounds(sw: coordinateOne, ne: coordinateTwo)
//Add Insets for the bounds if needed
let mapEdgeInsets = UIEdgeInsets(top: 10.0, left: 0.0, bottom: 0.0, right: 10.0)
//get the camera that fit those bounds and edge insets
let camera = self.mapView.cameraThatFitsCoordinateBounds(coordinateBounds, edgePadding: mapEdgeInsets)
//Update camera pitch (if required)
camera.pitch = 60
//setup CameraHeading
let zoomLevel = self.mapView.zoomLevel
var cameraHeading = camera.heading
if zoomLevel > 14 {
cameraHeading += 2.2
} else {
cameraHeading += 0.7
}
if cameraHeading > 359 {
cameraHeading = 1
}
camera.heading = cameraHeading
//set new camera with animation
let newCamera = camera
self.mapView.setCamera(newCamera, withDuration: 0.1, animationTimingFunction: CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
put the above set of code inside a method and call that method repeatedly every 0.1 seconds.
private func enableRotationTimer(_ enable:Bool) {
guard self.store != nil else { return }
if enable == true {
if mapRotationTimer == nil {
mapRotationTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(rotateCamera), userInfo: nil, repeats: true)
}
} else {
mapRotationTimer?.invalidate()
mapRotationTimer = nil
}
}
Hope this helps for others.
Thanks
The setCamera function allows you to pass an animation function. You can see an example of this here: https://docs.mapbox.com/ios/maps/examples/camera-animation/
For limiting the bounds of your map camera, this requires some sleight of hand and a bit of calculating to determine where the camera is in relation to the bounds you’d like to set. You can find an example of this here: https://docs.mapbox.com/ios/maps/examples/constraining-gestures/
Based on how you’ve phrased your question, it sounds like you’ll need to combine these two approaches.
⚠️ disclaimer: I currently work at Mapbox⚠️
I'm having problem with 2 actions both of the actions are move to actions, that moves the pieces to empty node's position(Empty sknodes are added from sks file for). The problem only happens some times not always, in first one when player reaches loc:25 means wins it moves to empty node's position but it some times doesn't moves to there but the game goes on and if I minimise the game and open it they are already there where they should have moved previously.
Second problem this one is more important because with this skaction player moves to next Position. But very few times it doesn't works everything works fine but when it comes to execute that skaction it Stops totally, only touches works. but if I just minimise the game and open it, it moves to position and then game works fine. No issue then, How can this Happen?
I run the second skaction with sequence with 2 other actions and first one with skspritenode.run() action. and I don't have iOS device so I'm testing it on simulator.
Here are the both of SKActions that I use just the same thing nothing new :
for node in children {
if (node.name == String(nextSpace)) {
let moveAction:SKAction = SKAction.move(to: node.position, duration: 0.5)
moveAction.timingMode = .easeOut
let wait:SKAction = SKAction.wait(forDuration: 0.1)
let runAction:SKAction = SKAction.run({
if (self.movesRemaining == 1) {
self.KillHim(self.whosTurn, nextspace: nextSpace)
}
self.movesRemaining = self.movesRemaining - 1
self.setThePlayerSpace(space: nextSpace, player:self.whosTurn)
self.movePiece()
})
if whosTurn == .Player1 {
touchedNode.run(SKAction.sequence([moveAction, wait, runAction]))
} else {
playerPiece.run(SKAction.sequence([moveAction, wait, runAction]))
}
}
}
code for moving when player won :
if (currentSpacePlayer1Piece1 == 25) {
let loc:SKNode = childNode(withName: "c1")!
Player1Piece1.run(SKAction.move(to: loc.position, duration: 0.2))
currentSpacePlayer1Piece1 = 26
OneMoreMove += 1
} else if (currentSpacePlayer1Piece2 == 25) {
let loc:SKNode = childNode(withName: "c2")!
Player1Piece2.run(SKAction.move(to: loc.position, duration: 0.2))
currentSpacePlayer1Piece2 = 26
OneMoreMove += 1
}
is it could be happening because of using it in simulator?
As knightOfDragon said the node was being paused with some logic some times after removing, it works perfectly. If you have same issue search isPaused if you are using it somewhere not intended.
In my main app I'm trying to use 3D touch to activate a bullet fireing, but the results seem to be very inconsistant and it didn't always give my the write results. Also my iPhone 6S was crashing when I used this code, This is the code I used for a test.
for touch in touches {
let location = touch.locationInNode(self)
var force = touch.force * 10
var maxForce = touch.maximumPossibleForce
print ("The force is")
print (maxForce*4)
print (force*10000000)
if force != 0{
myLabel.text = "Force"
myLabel.fontSize = 45
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(myLabel)
myLabel.zPosition = 100000
}
else {
myLabel.removeFromParent()
}
I am a going to guess it is crashing because you add label to the parent multiple times (Your if condition is broken, no matter what function you call, (touchesBegan,touchesMoved,touchesEnded) only one of those conditions will always be true, in touchesBegan and touchesMoved, your force will always be > 0, in your touchEnded, your force should be 0. (I am going to assume this code is your touchMoved) Instead, add your label in the your viewDidLoad code, and use the .hidden field to show/hide your label on your moves. (Make sure on touchesEnded to hide it)
I'm using Sprite Kit's built-in physics engine in a game I'm writing. In order to check whether or not a character is on the ground, I'm using the bodyAlongRayStart() function of the SKScene's physicsWorld to check for bodies underneath the bottom-left and bottom-right corners of the character (this function belong's to the character's class):
func isInAir(sceneRef:GameScene) -> Bool {
var isInAir:Bool = false
let distance:CGFloat = 32 // how far from the origin we should check
// define where the rays start and end
let bottomLeftRayStart:CGPoint = CGPoint(x: self.position.x - (self.hitbox.size.width/2), y: self.position.y - (self.hitbox.size.height/2))
let bottomLeftRayEnd:CGPoint = CGPoint(x: self.position.x - (self.hitbox.size.width/2), y: (self.position.y - (self.hitbox.size.height/2) - distance))
let bottomRightRayStart:CGPoint = CGPoint(x: self.position.x + (self.hitbox.size.width/2), y: self.position.y - (self.hitbox.size.height/2))
let bottomRightRayEnd:CGPoint = CGPoint(x: self.position.x + (self.hitbox.size.width/2), y: (self.position.y - (self.hitbox.size.height/2) - distance))
// Are there any bodies along the lines?
var bottomLeftBody:SKPhysicsBody! = sceneRef.physicsWorld.bodyAlongRayStart(bottomLeftRayStart, end:bottomLeftRayEnd)
var bottomRightBody:SKPhysicsBody! = sceneRef.physicsWorld.bodyAlongRayStart(bottomRightRayStart, end:bottomRightRayEnd)
// no bodies found
if bottomLeftBody == nil && bottomRightBody == nil {
isInAir = true
} else {
// if one of the rays finds a wall (floor) or slope, we're not in the air
if (bottomLeftBody != nil && (bottomLeftBody!.categoryBitMask == _entityType.wall.rawValue || bottomLeftBody!.categoryBitMask == _entityType.slope.rawValue)) || (bottomRightBody != nil && (bottomRightBody.categoryBitMask == _entityType.wall.rawValue || bottomRightBody!.categoryBitMask == _entityType.slope.rawValue)) {
isInAir = false
}
}
return isInAir
}
This code is called in the scene's update() function (though I should probably move it to didSimulatePhysics()...) and it works fine! What's annoying, however, is that Swift in its infinite wisdom decides to print Chance to the console every time it detects a body along the ray.
With only one character in the scene it's not too much of a bother, but I'll be adding enemies soon (who work in much the same way, but will have different functions for handling their movement) and all those println() calls will slow the simulator right down. I can't see a way to suppress this; the call is coming from the class library which I can't alter.
Is there a reliable way to override the standard println() function, gloabally? Something like this:
func println(object: Any) {
#if DEBUG
Swift.println(object)
#endif
}
And if so, where in my project should I put it? It's currently in AppDelegate.swift but other libraries (and the bodyAlongRayStart() function) are still printing to the console even when I change the flag...
I submitted this bug to Apple. They told me it's fixed in the next version of Xcode. If it's really bugging you, you can download Xcode 6.3 beta.