SCNPlane between two SCNVector Scenekit - swift

I have 2 SCNVectors startPoint and endPoint, I'm try to create a SCNplane extend between this point.
I read some answer here in SO but no one can answer my doubt.
I found code to draw a line between to point but nothing for a plane.
class CustomPlane : SCNNode {
var startPoint : SCNVector3
var endPoint : SCNVector3
init(startP: SCNVector3, endP: SCNVector3) {
self.startPoint = startP
self.endPoint = endP
super.init()
makePlane()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makePlane(){
let distLenght = startPoint.distance(to: endPoint)
let scnPlane = SCNPlane(width: 1, height: CGFloat(distLenght))
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.red
scnPlane.materials = [mat]
let node = SCNNode(geometry: scnPlane)
node.eulerAngles = SCNVector3(deg2rad(270), deg2rad(-90), 0)
node.position = SCNVector3(startPoint.x, startPoint.y+0.1, startPoint.z)
self.addChildNode(node)
}
}
the code above draw actually the floor but is not following the direction between the 2 point.

Related

How to create a SCNNode with a custom geometry?

I am trying to create a SCNNode whose geometry is created using an array of x, y, and z positions. I am using the following code; however, this is not showing up. Does anyone know what's wrong?
class CustomShapeNode: SCNNode {
init(positions: [(Double, Double, Double)]) {
super.init()
// Create an array of SCNVector3 from the positions array
var scnPositions = [SCNVector3]()
for position in positions {
scnPositions.append(SCNVector3(Float(position.0), Float(position.1), Float(position.2)))
}
// Create a geometry from the positions array
let geometry = SCNGeometry(sources: [SCNGeometrySource(vertices: scnPositions)], elements: [SCNGeometryElement(indices: Array(0..<scnPositions.count), primitiveType: .triangles)])
// Set the geometry to the node
self.geometry = geometry
// Set the color of the node geometry
self.geometry?.firstMaterial?.diffuse.contents = UIColor.red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Use the following code:
import SceneKit
class ViewController: UIViewController {
var sceneView: SCNView? = nil
let node = CustomShapeNode([SCNVector3( 0,0,0), // CCW
SCNVector3( 1,1,0),
SCNVector3(-1,1,0)])
override func viewDidLoad() {
super.viewDidLoad()
sceneView = self.view as? SCNView
sceneView?.scene = SCNScene()
sceneView?.backgroundColor = .darkGray
sceneView?.allowsCameraControl = true
sceneView?.scene?.rootNode.addChildNode(node)
}
}
class CustomShapeNode: SCNNode {
init(_ positions: [SCNVector3]) {
super.init()
let normalsPerFace = 1
let indices: [Int32] = [0, 1, 2]
let source = SCNGeometrySource(vertices: [positions[0],
positions[1],
positions[2]])
let normals = [positions[0],positions[1],positions[2]].map {
[SCNVector3](repeating: $0, count: normalsPerFace)
}.flatMap { $0 }
let normalSource = SCNGeometrySource(normals: normals)
let data = Data(bytes: indices,
count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: data,
primitiveType: .triangles,
primitiveCount: 1,
bytesPerIndex: MemoryLayout<Int32>.size)
let p1 = CGPoint(x: CGFloat(positions[0].x),
y: CGFloat(positions[0].y))
let p2 = CGPoint(x: CGFloat(positions[1].x),
y: CGFloat(positions[1].y))
let p3 = CGPoint(x: CGFloat(positions[2].x),
y: CGFloat(positions[2].y))
let texCoord = SCNGeometrySource(textureCoordinates: [p1, p2, p3])
self.geometry = SCNGeometry(sources: [source, normalSource, texCoord],
elements: [element])
self.geometry?.firstMaterial?.diffuse.contents = UIColor.systemOrange
self.geometry?.firstMaterial?.lightingModel = .constant
self.geometry?.firstMaterial?.isDoubleSided = true
}
required init?(coder: NSCoder) {
fatalError("Hasn't been implemented yet")
}
}

How to refine the shape of an SKSpriteNode for tap gestures?

In my app I create mutiple SKSpriteNodes, gems. Code snippet 1
When I loop through nodes from the main scene in a tap gesture, Code snippet 2, the gems register in a perfect square shape even though they are not. Screenshot enclosed I have highlighted all areas that the orange gem registers in a tap as white.
Since the gem is itself not a square, I'd like to know if there is a way refine its shape so it would only show up in the list of nodes in UITapGestureRecognizer if the orange part is tapped. I have even tried by assigning it a physicsBody. But that made no difference.
Code Snippet 1
class MyGem : SKSpriteNode{
init(iColor : Gems) {
fileName = "\(iColor)_gem"
let skTexture = SKTexture(imageNamed: fileName)
super.init(texture: skTexture, color: .clear, size: .zero)
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.size = CGSize(width: myGV.gemSize.width, height: myGV.gemSize.height)
self.zPosition = theZ.gem
self.position = CGPoint(x: 0, y: 0 )
self.isUserInteractionEnabled = false
self.name = "gem"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Code Snippet 2
#objc func tappedView(_ sender:UITapGestureRecognizer) {
if sender.state == .ended{
var post = sender.location(in: sender.view)
post = self.convertPoint(fromView: post)
for node in self.nodes(at: post){
if let touchNode = node as? MyGem
print("touched gem")
highliteGem(theGem: touchNode, clearAll: false)
return
}

Sprite is missing on scene

I'm new in Sprite Kit and I have a strange problem with my GameScene. Can't figure out, what causes the problem. I present my scene from controller in viewWillAppearMethod in this way:
let atlas = SKTextureAtlas(named: "Sprites")
atlas.preload { [unowned self] in
DispatchQueue.main.async {
self.gameScene = GameScene(level: self.level, size: self.gameSKView!.bounds.size)
self.gameScene.scaleMode = .resizeFill
self.gameSKView?.presentScene(self.gameScene)
self.gameSKView?.ignoresSiblingOrder = true
self.gameSKView?.showsNodeCount = true
}
}
My sprite atlas content looks like: link
Than i create my spaceship:
final class SpaceshipSpriteNode: SKSpriteNode {
required init(size: CGSize) {
let texture = SKTexture(image: #imageLiteral(resourceName: "Spaceship"))
super.init(texture: texture, color: .white, size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
func configureSpaceship() {
let middleRow = Int(Double(unwrappedMatrix.rowsCount) / 2)
let middleColumn = Int(Double(unwrappedMatrix.columnsCount) / 2)
let xOffset = CGFloat(level.startPoint.column - middleColumn)
let yOffset = CGFloat(level.startPoint.row - middleRow)
spaceship = SpaceshipSpriteNode(size: spaceshipSize)
spaceship.position = CGPoint(x: frame.midX + (spaceshipSize.width * xOffset), y: frame.midY + (spaceshipSize.height * yOffset))
spaceshipObject.addChild(spaceship)
addChild(spaceshipObject)
}
configureSpaceship method is called in didMove(to view: SKView)
The problem is that sometimes(1 per 3/4/5/6 cases) my spaceship is missing from the scene. Visibility, position, size are always the same, a count of the nodes on scene is the same too. Some images here link
According to comments, I have changed zPosition for my objects:
tile.zPosition = 0
spaceship.zPosition = 1.0
backgroundSpriteNode.zPosition = -1
And everything start working correct, thanks guys.

Is there a simple way to store two objects of different type in a container?

I'm trying to position an SKCameraNode at the center of multiple objects in my scene. I'm thinking of placing all objects of interest into some sort of container, and then calculating the centroid every update(). I have two classes, Ball and Car, that inherit from PhysicalObject, which inherits from SKSpriteNode. What I'd like to do is something like
var cameraObjects: Array<Ball, Car>!
...
var ball1 = Ball()
var car1 = Car()
cameraObjects.append(ball1)
cameraObjects.append(car1)
...
// Loop through objects and calculate centroid
But this crashes at the first append().
Note that I'd rather not create a dummy protocol for each class, as mentioned here. Is there a simple way to do this?
PhysicalObject.swift
import SpriteKit
class PhysicalObject: SKSpriteNode {
var objectMass: CGFloat!
override init(texture: SKTexture!, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.name = "physicalObject"
//Physics setup
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.affectedByGravity = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ball.swift
import SpriteKit
class Ball: PhysicalObject {
let MAXBALLSPEED :CGFloat = 0.08
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes */
let ballTexture = SKTexture(imageNamed: "ball")
let ballRadius = CGFloat(50)
let ballSize = CGSize(width: ballRadius, height: ballRadius)
super.init(texture: ballTexture, color: UIColor.clear, size: ballSize)
self.position = spawnPosition
self.name = "ball"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height/2)
self.objectMass = CGFloat(0.005)
physicsBody?.isDynamic = true // Default is true
physicsBody?.restitution = 1.0
self.physicsBody?.categoryBitMask = PhysicsCategory.Ball
self.physicsBody?.contactTestBitMask = PhysicsCategory.Boards | PhysicsCategory.Car
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Car.swift
import SpriteKit
class Car: PhysicalObject {
let MAXCARSPEED: CGFloat = 500
var steering: Bool
var diagonalLength: CGFloat
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes of cars */
let carTexture = SKTexture(imageNamed: "BasicCar")
let carWidth = CGFloat(screenSize.width/20)
let carScale = CGFloat(183.0/140.0)
let carSize = CGSize(width: (carWidth*carScale), height: carWidth)
// Initialize movement variables
self.steering = false
self.diagonalLength = hypot(carSize.width, carSize.height)
//self.steerRight = false
//self.steerLeft = false
super.init(texture: carTexture, color: UIColor.clear, size: carSize)
self.speed = CGFloat(MAXCARSPEED)
self.position = spawnPosition
self.name = "car"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
let carPhysicsSize = CGSize(width: self.size.width, height: self.size.height)
self.physicsBody = SKPhysicsBody(rectangleOf: carPhysicsSize)
self.objectMass = CGFloat(2000)
self.physicsBody?.friction = 0.5
self.physicsBody?.angularDamping = 1.0
self.physicsBody?.linearDamping = 1.0
self.physicsBody?.restitution = 1.0
physicsBody?.isDynamic = true // Default is true
self.physicsBody?.categoryBitMask = PhysicsCategory.Car
self.physicsBody?.contactTestBitMask = PhysicsCategory.Car | PhysicsCategory.Ball
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can make an Array<PhysicalObject> (a.k.a. [PhysicalObject]), which can store both Ball objects, and Car objects.
You may find that the enumerateChildNodes method of SKNode is a better way to enumerate over all the objects with a certain name. That way, you don't need to manage the array yourself.
Take a look at Apple's documentation.
All you need to do is place all the objects you want be involved with the camera into a new parent SKNode, and use calculateAccumulatedFrame to get the frame around all the nodes, then just use the center of the parent's frame.

Attemped to add a SKNode which already has a parent - Swift

Ok. this code is driving me crazy. It just don't work. The only message I received is "Attemped to add a SKNode which already has a parent". Yes I know that there has been some discussions here, but none of them give the solution I need.
This is the code. I really appreciate any help.
import SpriteKit
class MyScene: SKScene {
let intervalShapeCreation:NSTimeInterval = 2.0 // Interval for creating the next Shape
let gravitationalAcceleration:CGFloat = -0.5 // The gravitational Y acceleration
let shapeSequenceAction = SKAction.sequence([
SKAction.scaleTo(1.0, duration: 0.5),
SKAction.waitForDuration(2.0),
SKAction.scaleTo(0, duration: 0.5),
SKAction.removeFromParent()
])
override init(size: CGSize) {
super.init(size: size)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
addBackground()
initializeScene()
}
// MARK: Level Building
func initializeScene() {
self.physicsWorld.gravity = CGVectorMake(0.0, gravitationalAcceleration)
runAction(SKAction.repeatActionForever(
SKAction.sequence([SKAction.runBlock(self.createShape),
SKAction.waitForDuration(intervalShapeCreation)])))
}
func addBackground() {
let backgroundAtlas = SKTextureAtlas(named: "background")
let background = SKSpriteNode(texture: backgroundAtlas.textureNamed("background"))
background.position = CGPoint(x: size.width/2, y: size.height/2)
background.anchorPoint = CGPointMake(0.5, 0.5)
background.zPosition = -1
background.name = "background"
self.addChild(background)
}
func createShape() {
let newShape = sSharedAllPossibleShapes[0]
print("\n shape creada: \(newShape.name)")
newShape.position = CGPointMake(size.width / 2, CGFloat( Int.random(fromZeroToMax: 500)))
self.addChild(newShape)
newShape.runAction(shapeSequenceAction)
}
}
createShape doesn't actually create a SKShapeNode. It gets the first shape from the sSharedAllPossibleShapes array, then adds it as child to self. The second time you call this method that shape already has a parent and can't be added again.
You have to create a new instance of SKShapeNode. The way I see it your array here really needs to contain the CGPath objects that define the shape, not the nodes themselves because you can't reuse nodes the way you intended to.