SceneKit. Place nodes in one "surface" - ios

The users draw a line on drawing views and then I need to translate these points into 3d world, but place these points in one "surface". For this, I map the array of points into vectors (I use hitTest with .featurePoint) and then filter this array for the further one
func didEndDrawing(with points: [CGPoint]) {
guard let transform = sceneView.pointOfView?.transform else { return }
let cameraVectror = SCNVector3(transform.m31, transform.m32, transform.m33)
let farthestVector = points
.reduce((vector: SCNVector3(0, 0, 0), distance: CGFloat.zero)) { result, point in
guard let vector = getVector(for: point) else { return result }
let distance: CGFloat = cameraVectror.distance(to: vector)
return distance > result.distance ? (vector, distance) : result
}
.vector
}
let parentNode = SCNNode()
parentNode.position = farthestVector
How can I adjust coordinates (I guess z position) to have all the child nodes at the same distance from the point of view?
The idea of the app is freehand drawing in AR.
Update
With Voltan's help I was able to solve it
points.forEach { point in
let scenePoint = sceneView.unprojectPoint(SCNVector3(point.x, point.y, CGFloat(projectedPoint.z)))
let sphere = SCNSphere(radius: 0.01)
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
sphere.materials = [material]
let node = SCNNode(geometry: sphere)
node.position = scenePoint
sceneView.scene.rootNode.addChildNode(node)
}

If I'm understanding correctly, you want some kind of tap/drag combination - get the points from the 2D world and translate to a 3D world. This is some game code for a missile command type game, maybe it will help you with unprojectPoint stuff. There are some timers that aren't included, but hopefully you will get the idea.
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
if(data.gameState == .endGame)
{
endGameAnimates.stop()
let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in self.dismiss(animated: false, completion: nil) })
return
}
if(gameControl.isWaveComplete == true || gNodes.gameNodes.isPaused == true) { return }
currentLocation = recognizer.location(in: gameScene)
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(currentLocation.x, currentLocation.y, CGFloat(projectedPoint.z)))
if(data.gameState == .endGame) // Allow animations to finish, otherwise they will show up next round
{
DispatchQueue.main.async { self.endGameAnimates.stop() }
let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in self.dismiss(animated: false, completion: nil) })
return
}
if(data.missilesAvailable <= 0)
{
sound.playSoundType(vSoundType: .defenseFails)
hudControl.notify()
}
else
{
gameControl.defenseMissileShoot(vPosition: scenePoint, soundType: 0)
sound.playSoundType(vSoundType: .defenseFires)
}
}
//**************************************************************************
#objc func handlePan(recognizer: UIPanGestureRecognizer)
{
currentLocation = recognizer.location(in: gameScene)
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(currentLocation.x, currentLocation.y, CGFloat(projectedPoint.z)))
if(gameControl.isWaveComplete == true || gNodes.gameNodes.isPaused == true) { return }
switch recognizer.state
{
case UIGestureRecognizer.State.began:
gameControl.defenseMissileShoot(vPosition: scenePoint, soundType: 1)
SNDdefenseSoundCount = 0
if(data.missilesAvailable <= 0) { sound.playSoundType(vSoundType: .defenseFails); hudControl.notify() }
beginLocation.x = currentLocation.x
break
case UIGestureRecognizer.State.changed:
if(currentLocation.x > beginLocation.x + dragDistance)
{
beginLocation.x = currentLocation.x
if(data.missilesAvailable > 0) { gameControl.defenseMissileShoot(vPosition: scenePoint, soundType: 2) }
SNDdefenseSoundCount += 1
}
if(currentLocation.x < beginLocation.x - dragDistance)
{
beginLocation.x = currentLocation.x
if(data.missilesAvailable > 0) { gameControl.defenseMissileShoot(vPosition: scenePoint, soundType: 2) }
SNDdefenseSoundCount += 1
}
break
case UIGestureRecognizer.State.ended:
if(data.missilesAvailable > 0)
{
if(SNDdefenseSoundCount < 2) { sound.playSoundType(vSoundType: .defenseFires) }
else { sound.playSoundType(vSoundType: .defensePans) }
}
break
default:
break
}

Related

How can I create an onscreen controller that works in multiple scenes in SpriteKit?

Working on a game in SpriteKit to learn. Its a platformer with an onscreen controller. I have this all working using touchesBegan and touchesEnded to know when the player is pushing the buttons or not. This works fine, however when i want to load the next scene for 'level 2' i need to implement the controller all over again. I could do a lot of copy and pasting but I feel this will lead to a lot of duplication of code. Every tutorial I've ever read said to try to adhere to the DRY principle.
Im sorry if this is simple, but I have <6 months programming experience and am trying to learn and improve. Im assuming I would need to create a separate class for the onscreen controller so it can be reused, but Im a little lost on where to start.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = (touch.location(in: playerCamera))
print("LocationX: \(location.x), LocationY: \(location.y)")
let objects = nodes(at: location)
print("\(objects)")
if rightButton.frame.contains(location) {
rightButtonPressed = true
playerFacingRight = true
playerFacingLeft = false
thePlayer.xScale = 1
let animation = SKAction(named: "running")!
let loopingAnimation = SKAction.repeatForever(animation)
thePlayer.run(loopingAnimation, withKey: "moveRight")
moveRight()
} else if leftButton.frame.contains(location) {
leftButtonPressed = true
playerFacingLeft = true
playerFacingRight = false
thePlayer.xScale = -1
let leftAnimation = SKAction(named: "running")!
let leftLoopingAnimation = SKAction.repeatForever(leftAnimation)
thePlayer.run(leftLoopingAnimation, withKey: "moveLeft")
moveLeft()
} else if upButton.frame.contains(location) {
upButtonPressed = true
print("upButton is pressed")
if playerAndButtonContact == true {
print("contact - player + button + upButtonPressed=true")
print("\(movingPlatform.position)")
button.texture = SKTexture(imageNamed: "switchGreen")
let moveRight = SKAction.moveTo(x: -150, duration: 3)
if movingPlatform.position == CGPoint(x: -355, y: movingPlatform.position.y) {
movingPlatform.run(moveRight)
thePlayer.run(moveRight)
button.run(moveRight)
}
}
if playerAndDoorSwitchContact == true {
let switchPressed = SKAction.run{
self.switchPressedSound()
self.doorSwitch.texture = SKTexture(imageNamed: "switchGreen")
self.door.texture = SKTexture(imageNamed: "DoorUnlocked")
}
let wait = SKAction.wait(forDuration: 2)
let doorOpen = SKAction.run {
let doorOpen = SKSpriteNode(imageNamed: "DoorOpen")
doorOpen.alpha = 0
doorOpen.position = self.door.position
doorOpen.size = self.door.size
doorOpen.size = self.door.size
self.door.zPosition = -2
doorOpen.zPosition = -1
let fadeIn = SKAction.fadeIn(withDuration: 0.5)
let start = SKAction.run {
self.addChild(doorOpen)
doorOpen.run(fadeIn)
}
let sound = SKAction.run {
self.doorOpeningSound()
}
let opening = SKAction.group([sound, start])
self.door.run(opening)
}
let sequence = SKAction.sequence([switchPressed, wait, doorOpen])
self.doorSwitch.run(sequence)
}
if playerAndDoorContact == true {
self.view?.presentScene(level1, transition: transition)
}
} else if downButton.frame.contains(location) {
}
else if shoot.frame.contains(location) {
shoot()
} else if jumpButton.frame.contains(location) {
self.pressed = true
let timerAction = SKAction.wait(forDuration: 0.05)
let update = SKAction.run {
if(self.force < Constants.maximumJumpForce) {
self.force += 2.0
} else {
self.jump(force: Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
}
let sequence = SKAction.sequence([timerAction, update])
let repeat_seq = SKAction.repeatForever(sequence)
self.run(repeat_seq, withKey: "repeatAction")
}
}
}

How to use UIPinchGestureRecognizer velocity

I am trying to use UIPinchGestureRecognizer's property velocity to play an animation in a SceneKit node when the user stops pinching, but I don't understand how the velocity property is calculated/should be used.
Here is my code:
var initialScale = simd_float3(1)
#objc private func handlePinch(sender: UIPinchGestureRecognizer) {
let scale = simd_float3(Float(sender.scale))
let state = sender.state
let vel = Float(sender.velocity)
if state == .began {
updateQ.async(group: updateGrp) {
log.debug("Begin scale: \(scale)")
log.debug("Begin vel: \(vel)")
if let node = self.activeNode {
self.initialScale = node.simdScale
}
else {
self.initialScale = self.layeredImgNode.simdScale
}
}
}
else if state == .changed {
updateQ.async(group: updateGrp) {
log.debug("Changed scale: \(scale)")
log.debug("Changed vel: \(vel)")
let scale = self.initialScale * scale
if let node = self.activeNode {
node.simdScale = scale
}
else {
self.layeredImgNode.simdScale = scale
}
}
}
else if state == .ended {
updateQ.async(group: updateGrp) {
log.debug("End scale: \(scale)")
log.debug("End vel: \(vel)")
// Calculate for how long to run the animation
let time = Double(vel / Float(10))
let target = self.initialScale * (scale + simd_float3(vel))
log.debug("End target: \(target) and time: \(time)")
self.initialScale = simd_float3(1)
SCNTransaction.begin()
SCNTransaction.animationDuration = time
if let node = self.activeNode {
node.simdScale = target
}
else {
self.layeredImgNode.simdScale = target
}
SCNTransaction.commit()
}
}
The documentation states that velocity is "The velocity of the pinch in scale factor per second". However, I still don't understand how I should use it. Could someone help me?
Thanks.

How do you update something in an SKAction while using repeat repeatForever?

As you'll see below, I'm trying to update the durationNumber, so that the colorSwitch object turns faster. The durationNumber does update, but colorSwitch doesn't rotate faster. How do I accomplish this?
func turnWheel() {
var durationNumber = 2.0
let rotateAction = SKAction.rotate(byAngle: .pi/2, duration: durationNumber)
let switchAction = SKAction.run {
if let newState = SwitchState(rawValue: self.switchState.rawValue + 1) {
self.switchState = newState
} else {
self.switchState = .green
}
if self.score > self.scoreCheck {
self.scoreCheck += 1
durationNumber *= 0.5
}
}
colorSwitch.run(SKAction.repeatForever(SKAction.sequence([rotateAction, switchAction])))
}
You're changing the value of the local variable, but not changing the SKAction object itself. You need to change rotateAction.duration to have any effect on the action itself.
if self.score > self.scoreCheck {
self.scoreCheck += 1
durationNumber *= 0.5
rotateAction.duration = durationNumber
}
Do not update your duration, instead update your speed
func turnWheel() {
var durationNumber = 2.0
let rotateAction = SKAction.rotate(byAngle: .pi/2, duration: durationNumber)
let switchAction = SKAction.run {
if let newState = SwitchState(rawValue: self.switchState.rawValue + 1) {
self.switchState = newState
} else {
self.switchState = .green
}
if self.score > self.scoreCheck {
self.scoreCheck += 1
colorSwitch.speed *= 2
}
}
colorSwitch.run(SKAction.repeatForever(SKAction.sequence([rotateAction, switchAction])))
}

How to detect apple watch position using accelerometer and Gravity in swift?

I have creating an application for apple watch. The Logic is, when the user rise their hand and tap a button from app. At that time I will fetch the accelerometer values. And whenever user rise their hand and meet the captured position, I have to send message to iPhone.
For me am getting the values correctly But, It will always give the values based on accelerometer. Which means user doesn't rise the hand but accelerometer values matched. So values will send to mobile.
func startUpadateAccelerometer() {
self.motionManager.accelerometerUpdateInterval = 1.0 / 10.0
self.motionManager.startAccelerometerUpdates(to: OperationQueue()) { (accelerometerData, error) -> Void in
guard accelerometerData != nil else
{
print("There was an error: \(String(describing: error))")
return
}
DispatchQueue.main.async {
if(self.can_reset){
let differenceX : Bool = self.validateButtom(currentValue: accelerometerData!.acceleration.x, inititalValue: self.gravityReference.x)
let differenceY : Bool = self.validateButtom(currentValue: accelerometerData!.acceleration.y, inititalValue: self.gravityReference.y)
if(differenceX && differenceY && self.gravityOffsetDifference(currentValue: accelerometerData!.acceleration.x, referenceValue: self.gravityReference.x ) && self.gravityOffsetDifference(currentValue: accelerometerData!.acceleration.y, referenceValue: self.gravityReference.y)){
WKInterfaceDevice().play(.success)
// self.addLog(_logStr: EventsTypes.Achievements1.rawValue)
self.logString += String(format: "X: %0.3f Y: %0.3f Z: %0.3f \n", accelerometerData!.acceleration.x,accelerometerData!.acceleration.y,accelerometerData!.acceleration.z)
self.m_XYZValueLbl.setText(self.logString)
self.is_RechedZeroPos = true
self.session?.sendMessage(["msg" : "\(self.logString)"], replyHandler: nil) { (error) in
NSLog("%#", "Error sending message: \(error)")
}
} else {
if(self.checkAchievements2_3(deviceMotionData: accelerometerData!.acceleration) == true) {
if self.is_RechedZeroPos == true {
self.addLog(_logStr: EventsTypes.Achievements2.rawValue)
self.is_RechedZeroPos = false
} else {
self.addLog(_logStr: EventsTypes.Achievements3.rawValue)
}
}
}
} else {
self.gravityReference = accelerometerData!.acceleration
//self.logString = String(format: "Reference Acceleration %0.3f %0.3f %0.3f \n", self.gravityReference.x,self.gravityReference.y,self.gravityReference.z)
self.can_reset = true
}
}
}
}
func validateButtom(currentValue : Double , inititalValue : Double) -> Bool {
if( currentValue == 0 && inititalValue == 0) {
return true
} else if( currentValue < 0 && inititalValue < 0) {
return true
} else if( currentValue > 0 && inititalValue > 0) {
return true
} else {
return false
}
}
func gravityOffsetDifference(currentValue : Double , referenceValue: Double) -> Bool {
var difference : Double!
if (fabs(currentValue) <= fabs(referenceValue)) {
difference = fabs(referenceValue) - fabs(currentValue)
} else {
difference = fabs(currentValue) - fabs(referenceValue)
}
if (difference <= gravityOffset ) {
return true
} else {
return false
}
}
Please guide me to get the values only when the user captured the position.
Accelerometers measure changes in velocity along the x, y, and z axes
Fetching accelerometer data
let motion = CMMotionManager()
func startAccelerometers() {
// Make sure the accelerometer hardware is available.
if self.motion.isAccelerometerAvailable {
self.motion.accelerometerUpdateInterval = 1.0 / 60.0 // 60 Hz
self.motion.startAccelerometerUpdates()
// Configure a timer to fetch the data.
self.timer = Timer(fire: Date(), interval: (1.0/60.0),
repeats: true, block: { (timer) in
// Get the accelerometer data.
if let data = self.motion.accelerometerData {
let x = data.acceleration.x
let y = data.acceleration.y
let z = data.acceleration.z
// Use the accelerometer data in your app.
}
})
// Add the timer to the current run loop.
RunLoop.current.add(self.timer!, forMode: .defaultRunLoopMode)
}
}
Important
If your app relies on the presence of accelerometer hardware, configure the UIRequiredDeviceCapabilities key of its Info.plist file with the accelerometer value. For more information about the meaning of this key, see Information Property List Key Reference.

UberJump Tutorial SpriteKit error

I'm using Ray Wenderlich's tutorial called UberJump (https://www.raywenderlich.com/87232/make-game-like-mega-jump-sprite-kit-swift-part-2) and I've run into a logic error. The code used to remove objects is not working and I can't figure out why. I downloaded his source code and his code is not working either.
Here is what I'm having trouble with:
override func update(currentTime: NSTimeInterval) {
//new max height?
//1
if Int(player.position.y) > maxPlayerY {
//2
GameState.sharedInstance.score += Int(player.position.y) - maxPlayerY!
//3
maxPlayerY = Int(player.position.y)
//4
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
// Remove game objects that have passed by
foregroundNode.enumerateChildNodesWithName("NODE_PLATFORM", usingBlock: {
(node, stop) in
let platform = node as! PlatformNode
platform.checkNodeRemoval(self.player.position.y)
})
foregroundNode.enumerateChildNodesWithName("NODE_STAR", usingBlock: {
(node, stop) in
let star = node as! StarNode
star.checkNodeRemoval(self.player.position.y)
})
//calculate player y offset
if player.position.y > 200.0 {
backgroundNode.position = CGPoint(x: 0.0, y: -((player.position.y - 200.0)/10))
midgroundNode.position = CGPoint(x: 0.0, y: -((player.position.y - 200.0)/4))
foregroundNode.position = CGPoint(x: 0.0, y: -(player.position.y-200.0))
}
}
The "remove game objects that have passed by" code is not working. It is supposed to remove the platforms as the character jumps on them. Did I write the code out wrong? Thanks.
EDIT: Here is the checkNodeRemovalFunction as well.
class GameObjectNode: SKNode {
func collisionWithPlayer(player: SKNode) -> Bool {
// Award score
return false
}
func checkNodeRemoval(playerY: CGFloat) {
if playerY > self.position.y + 300.0 {
self.removeFromParent()
}
}
It seems like there could be an issue with your checkNodeRemoval function depending on how you're inheriting from the GameObjectNode. Something you could try is taking the checkNodeRemoval function out and checking directly in your update method.
// Remove game objects that have passed by
foregroundNode.enumerateChildNodesWithName("NODE_PLATFORM", usingBlock: {
(node, stop) in
print("found platform node")
if self.player.position.y > node.position.y + 300.0 {
print("attempt to remove platform")
platform.removeFromParent()
}
})
foregroundNode.enumerateChildNodesWithName("NODE_STAR", usingBlock: {
(node, stop) in
print("found star node")
if self.player.position.y > node.position.y + 300.0 {
print("attempt to remove star")
star.removeFromParent()
}
})

Resources