swift scene.sks file objects not applying z rotation value - ios

I'm making a simple game application for IOS devices, loosely based from the book 'swift game development'. I have created a protocol which I use as a template for creating a class for each type of in game object. A platform for the player to jump on has the following class based on the protocol
import SpriteKit
class GrassyPlatform: SKSpriteNode, GameSprite {
var textureAtlas:SKTextureAtlas = SKTextureAtlas(named: "Enviroment")
var initialSize: CGSize = CGSize(width:630, height:44)
init() {
super.init(texture: textureAtlas.textureNamed("GrassPlatform1"), color: .clear, size: initialSize)
self.anchorPoint = CGPoint(x: 0.5, y:0.5)
physicsBody = SKPhysicsBody(rectangleOf: initialSize)
physicsBody?.restitution = 0
self.physicsBody?.isDynamic = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I'm using the scene editor to place these objects onto the scene, assigning each object the relevant custom class. Like the one above.
When I run the game the objects position (assigned only from the scene editor) is respected, but the zRotation value is ignored. For example setting the platform in the scene editor as so
But this results in the platform appearing at the correct position but with the default zRotation value and not the one assigned in the game scene.
I can adjust the rotation manually through self.zRotation but this defeats the whole point of using the scene.sks for level design.
Is there away to adjust the zRotation through game scene and if so how?
Thanks

Solved the problem was in a piece of code that I never added to the original post because I 'thought' it was irrelevant! boy was I wrong. Another class handles the different scenes. This code I took from the book and was not 100% on its operation when I added it, before getting caught up in something else
class EncounterManager {
let encounterNames:[String] = ["Level1A", "Level1B"] //A array of all the scenes in the level
var encounters:[SKNode] = [] //each scene is a node
var currentEncounterIndex:Int?
var previousEncounterIndex:Int?
init() {
for encounterFileName in encounterNames { //Loop all scenes in the scene array
let encounterNode = SKNode() //create a new node for the encounter/scene
if let encounterScene = SKScene(fileNamed: encounterFileName) { //load the encounter to the skscene
for child in encounterScene.children { //Loop through each child node of the skscene
let copyOfNode = type(of: child).init() //copy the node type and initilize to the encounterNode
copyOfNode.position = child.position //copy the position
copyOfNode.zPosition = child.zPosition //copy of zPosition
copyOfNode.name = child.name //copy the name
encounterNode.addChild(copyOfNode) //add child to encounter node
}
}
encounters.append(encounterNode)
//Save initial sprite positions for this encounter
saveSpritePositions(node: encounterNode)
}
}
This takes a copy of each object node and adds it to the game scene. The origonal author never needed to worry about the rotation of objects. So I added this and it worked
copyOfNode.zRotation = child.zRotation //copy of zRotation
Thanks anyway if you looked.

Related

setting up physics body from code when using required init(nscoder)

I'm attempting to take advantage of the init?(coder aDecoder: NSCoder) function so that I can subclass sprites in my sks file.
I can set the physics body in the sks file, but it doesn't work too well with my rectangular sprites I'm using that also have rounded corners. Because of the rounded corners, prior to using the init function, I was setting up the physicsbody in code, and setting the width slightly smaller than the squares width (that way when my hero slides off the square, there isn't a gap where the rounded corner is..
I know I can use the alpha mask physics body to get the same affect, except that with this, There seems to be a 1 px'ish gap when my hero is on the blocks, and it doesn't look good.
Wondering the reason why I can't set the physics body this, and also how I can do it or get around it?
Thanks for any help!!!
Update:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let p = userData?.value(forKey: "col") as? String {
setup(p)
print("working")
}
}
func setup(_ col: String) {
setupPhysics()
//allSprites.append(self)
In the setupPhysics I have:
override func setupPhysics() {
physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: frame.width - 4, height: frame.height - 1))
...more code...
}
This is my code for the sprite, and I have it correctly setup in the sks file...
I commented out the append to allSprites, so that I can demonstrate that it doesn't work. With the append uncommented, the sprites get added to allSprites, and then in my didMove function in the Scene file, it loops through allSprites and runs the setupPhysics():
func setup(_ col: String) {
//setupPhysics()
allSprites.append(self)
// scene file
func setupAllSpritesPhysics() {
for sprite in allSprites { sprite.setupPhysics() }
}
#Knight0fDragon :
You mention that this is an ugly fix. I disagree given the fact that it won't work the 'correct' way. And currently I don't have enough sprites to cause any kind of lag in the scene loading. I can look into that in the future if that is the case.
I'll admit I haven't tried the method you mentioned because I don't want to create a child node for each sprite I create in order to get around this even if it works. I appreciate the advice so far though.
To make this work with the SKS file, I would add a child SKNode with the dimensions of the body you want, and then in the Custom class code, do
required init(coder aDecoder:NSCoder){
super.init(coder:aDecoder)
if let child = childNode(withName:"child"), let physicsBody = child.physicsBody{
self.physicsBody = physicsBody
child.removeFromParent()
}
}
What we are doing here is transfering the physics body from the child to the parent, and then killing the child off. This allows our code to be a little more dynamic, and keeps design seperate from functionality.
Well, nice little bug from apple.
What is happening is physicsWorld doesn't exist during the time NSCoder happens, and instead of placing the physics body attached to the sprites body into the world when it does exist, it decides to cache the one located in the SKS file, and this is why it doesn't work.
So a very convenient way to get around this issue, is to implement a worldNode (It is good practice to do this anyway, especially if you want to create a pause screen) and just remove it then add it back in during sceneLoad:
class GameScene: SKScene,SKPhysicsContactDelegate {
lazy var worldNode : SKNode = { self.childNode(withName:"//worldNode")!}()
override func sceneDidLoad() {
worldNode.removeFromParent()
addChild(worldNode)
}
}

Instantiating class doesnt work - swift

I have a simple function call in my main class to create some instances from my Cube class but I cant seem to get my instances to be added to my scene. I tried returning self inside my Cube class but Swift wont let me do this inside init.
func addCubeLoop() {
for var i = 0; i <= 0; ++i {
cube = Cube(num: i, importedCube: importedCube1)
cubeArray.append(cube)
theScene.rootNode.addChildNode(cubeArray[i])
}
}
class Cube: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(num: Int, importedCube: SCNNode) {
let _scale: Float = 60
let cube: SCNNode = importedCube.copy() as! SCNNode
super.init()
cube.scale = SCNVector3Make(_scale, _scale, _scale)
let node = SCNNode()
node.addChildNode(cube)
node.position = SCNVector3Make(5, 20, 3)
let collisionBox = SCNBox(width: 5.0, height: 5.0, length: 5.0, chamferRadius: 0)
node.physicsBody?.physicsShape = SCNPhysicsShape(geometry: collisionBox, options: nil)
node.physicsBody = SCNPhysicsBody.dynamicBody()
node.physicsBody?.mass = 0.1
node.physicsBody?.restitution = 0.8
node.physicsBody?.damping = 0.5
node.name = "dice" + String(num)
node.physicsBody?.allowsResting = true
}
}
The nodes created in the init of Cube are not added as child nodes of it.
I've simplified the your code below to illustrate the problem.
func addCubeLoop() {
for /* loop */ {
// 1. create cube
cube = Cube(num: i, importedCube: importedCube1)
// 6. add cube to the scene's root node
theScene.rootNode.addChildNode(cubeArray[i])
}
}
class Cube: SCNNode {
init(importedCube: SCNNode) {
// 2. copy importedCube
let cube: SCNNode = importedCube.copy() as! SCNNode
// configure cube
// ...
// 3. create node
let node = SCNNode()
// 4. add cube (the copy) to node
node.addChildNode(cube)
// configure node
// ...
// 5. End of init
}
}
For each run through the loop, this is what happens.
A new Cube instance is created, passing importedCube1
In the Cube initializer, the imported cube argument is copied. The node "cube" is now a copy of the argument.
Still in the initializer, a new node (called "node" is created).
Still in the initializer, "cube" (the copy) is added to "node". At this point, cube is a child node of "node", but the Cube instance itself (which is a node) had no child nodes.
Init completes.
The newly created Cube instance is added to the scene's root node.
At this point there are four relevant nodes:
the root node,
the cube instance node
the node called "node"
the imported copy
The cube instance node is a child of the root node. The imported copy is a child of the "node" node. However, The "node" node doesn't have a parent.
The fix is to make sure that the all nodes are part of the hierarchy by adding the "node" node to self inside the Cube instance initializer.

How to know which SKSpriteNode is affected by collision detection in Swift?

Situation: I have two or more ships on my iOS screen. Both have different attributes like name, size, hitpoints and score points. They are displayed as SKSpriteNodes and each one has added a physicsBody.
At the moment those extra attributes are variables of an extended SKSpriteNode class.
import SpriteKit
class ship: SKSpriteNode {
var hitpoints: Int = nil?
var score: Int = nil?
func createPhysicsBody(){
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.dynamic = true
...
}
}
In this 'game' you can shoot at those ships and as soon as a bullet hits a ship, you get points. 'Hits a ship' is detected by collision.
func didBeginContact(contact: SKPhysicsContact){
switch(contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask){
case shipCategory + bulletCategory:
contactShipBullet(contact.bodyA, bodyB: contact.bodyB)
break;
default:
break;
}
}
Problem: Collision detection just returns a physicsBody and I do not know how to get my extended SKSpriteNode class just by this physicsBody.
Thoughts: Is it a correct way to extend SKSpriteNode to get my objects like a ship to life? When I add a ship to my screen it looks like:
var ship = Ship(ship(hitpoints: 1, score: 100), position: <CGPosition>)
self.addChild(ship)
Or is this just a wrong approach and there is a much better way to find out which object with stats so and so is hit by a bullet thru collision detection?
This question is similar to my other question - I just want ask this in broader sense.
The SKPhysicsBody has a property node which is the SKNode associated to the body. You just need to perform a conditional cast to your Ship class.
if let ship = contact.bodyA.node as? Ship {
// here you have your ship object of type Ship
print("Score of this ship is: \(ship.score)!!!")
}
Please note that the Ship node could be the one associated with bodyB so.
if let ship = contact.bodyA.node as? Ship {
// here you have your ship...
} else if let ship = contact.bodyB.node as? Ship {
// here you have your ship...
}
Hope this helps.

SpriteKit Node gives me nil when subclassed (maybe?) to another node | Swift

Okay so i am trying to learn how to create a game... I want a node to be the camera, so that i can move it and center the view to my player node. When i subclass (i don't know if it's right to say that i subclass it, maybe not...) the player node to the world node, the application crashes. When player is simply a node, a "subclass" of GameScene (and not of the "world" node), it goes "fine", i mean i can move my player (yeah but the camera doesn't work).
so here is my code (few // lines in italian but not relevant)
import SpriteKit
class GameScene: SKScene {
var world: SKNode? //root node! ogni altro nodo del world dev'essere sottoclasse di questo
var overlay: SKNode? //node per l'HUD e altre cose che devono stare sopra al world
var camera: SKNode? //camera node. muovo questo per cambiare le zone visibili
//world sprites
var player: SKSpriteNode!
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPoint(x: 0, y: 0) //center the scene's anchor point at the center of the screen
//world setup
self.world = SKNode()
self.world!.name = "world"
self.addChild(world!)
//camera setup
self.camera = SKNode()
self.camera!.name = "camera"
self.world!.addChild(self.camera!)
//UI setup
self.overlay = SKNode()
self.overlay?.zPosition = 10
self.overlay?.name = "overlay"
addChild(self.overlay!)
player = world!.childNodeWithName("player") as SKSpriteNode!
}
var directionToMove = CGVectorMake(0, 0)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
directionToMove = CGVectorMake((directionToMove.dx + (location.x - player!.position.x)), (directionToMove.dy + (location.y - player!.position.y)))
}
}
override func update(currentTime: CFTimeInterval) {
//*these both print the string*
if player == nil {
println("player is nil")
}
if player?.physicsBody == nil {
println("player.physicsBody is nil")
}
//*here it crashes*
player!.physicsBody!.applyForce(directionToMove)
}
override func didFinishUpdate() {
if self.camera != nil {
self.centerOnNode(self.camera!)
}
}
func centerOnNode(node: SKNode) {
let cameraPositionInTheScene: CGPoint = node.scene!.convertPoint(node.position, fromNode: node.parent!)
node.parent!.position = CGPoint(x: node.parent!.position.x - cameraPositionInTheScene.x, y: node.parent!.position.y - cameraPositionInTheScene.y)
}
}
thanks in advance : )
Your player is nil because you are accessing it from the world node, which is empty. By default, the player is a child of scene. You can change this in the scene editor by setting the node's parent property (see Figure 1).
Figure 1. SpriteKit Scene Editor's Property Inspector
I suggest you make the following changes:
Create the world, overlay, and camera nodes in the scene editor and access them with childNodeWithName
Add the player to the scene, not the world, and have it fixed in middle of the scene
Move the camera node not the player. The code in didFinishUpdate will automatically adjust the world to center the camera in the scene. You can add a physics body to the camera and move it by applying a force/impulse or by setting its velocity.
Add the other sprites to the world (instead of to the scene)
If you add the other sprites to the world, they will move appropriately when you adjust the world's position to center the camera. You will then need to move the other sprites relative to the world. The scene is just a window to view a portion of the world at a time.
I'm not sure why you are using optionals everywhere. You only need to use an optional when a variable might be nil. Are your world, overlay, and camera ever going to be nil? I would guess probably not.
To answer your question:
Youre trying to get player from world. I dont see that youve added any player sprite to the world node. It doesnt find it, and youre unwrapping an optional that isnt there. So you get an error.
You're going to have a lot more luck if you only use optionals when you need them. If you use them for everything youre going to add unnecessary complexity to your code.
Optionals should be the exception, not the rule. I could try to fix your code for you, but I'm not sure what you intended the player sprite to be?
Follow simple tutorials and start small. Things will make more and more sense over time.

SceneKit get texture coordinate after touch with Swift

I want to manipulate 2D textures in a 3D SceneKit scene.
Therefore i used this code to get local coordinates:
#IBAction func tap(sender: UITapGestureRecognizer) {
var arr:NSArray = my3dView.hitTest(sender.locationInView(my3dView), options: NSDictionary(dictionary: [SCNHitTestFirstFoundOnlyKey:true]))
var res:SCNHitTestResult = arr.firstObject as SCNHitTestResult
var vect:SCNVector3 = res.localCoordinates}
I have the texture read out from my scene with:
var mat:SCNNode = myscene.rootNode.childNodes[0] as SCNNode
var child:SCNNode = mat.childNodeWithName("ID12", recursively: false)
var geo:SCNMaterial = child.geometry.firstMaterial
var channel = geo.diffuse.mappingChannel
var textureimg:UIImage = geo.diffuse.contents as UIImage
and now i want to draw at the touchpoint to the texture...
how can i do that? how can i transform my coordinate from touch to the texture image?
Sounds like you have two problems. (Without even having used regular expressions. :))
First, you need to get the texture coordinates of the tapped point -- that is, the point in 2D texture space on the surface of the object. You've almost got that right already. SCNHitTestResult provides those with the textureCoordinatesWithMappingChannel method. (You're using localCoordinates, which gets you a point in the 3D space owned by the node in the hit-test result.) And you already seem to have found the business about mapping channels, so you know what to pass to that method.
Problem #2 is how to draw.
You're doing the right thing to get the material's contents as a UIImage. Once you've got that, you could look into drawing with UIGraphics and CGContext functions -- create an image with UIGraphicsBeginImageContext, draw the existing image into it, then draw whatever new content you want to add at the tapped point. After that, you can get the image you were drawing with UIGraphicsGetImageFromCurrentImageContext and set it as the new diffuse.contents of your material. However, that's probably not the best way -- you're schlepping a bunch of image data around on the CPU, and the code is a bit unwieldy, too.
A better approach might be to take advantage of the integration between SceneKit and SpriteKit. This way, all your 2D drawing is happening in the same GPU context as the 3D drawing -- and the code's a bit simpler.
You can set your material's diffuse.contents to a SpriteKit scene. (To use the UIImage you currently have for that texture, just stick it on an SKSpriteNode that fills the scene.) Once you have the texture coordinates, you can add a sprite to the scene at that point.
var nodeToDrawOn: SCNNode!
var skScene: SKScene!
func mySetup() { // or viewDidLoad, or wherever you do setup
// whatever else you're doing for setup, plus:
// 1. remember which node we want to draw on
nodeToDrawOn = myScene.rootNode.childNodeWithName("ID12", recursively: true)
// 2. set up that node's texture as a SpriteKit scene
let currentImage = nodeToDrawOn.geometry!.firstMaterial!.diffuse.contents as UIImage
skScene = SKScene(size: currentImage.size)
nodeToDrawOn.geometry!.firstMaterial!.diffuse.contents = skScene
// 3. put the currentImage into a background sprite for the skScene
let background = SKSpriteNode(texture: SKTexture(image: currentImage))
background.position = CGPoint(x: skScene.frame.midX, y: skScene.frame.midY)
skScene.addChild(background)
}
#IBAction func tap(sender: UITapGestureRecognizer) {
let results = my3dView.hitTest(sender.locationInView(my3dView), options: [SCNHitTestFirstFoundOnlyKey: true]) as [SCNHitTestResult]
if let result = results.first {
if result.node === nodeToDrawOn {
// 1. get the texture coordinates
let channel = nodeToDrawOn.geometry!.firstMaterial!.diffuse.mappingChannel
let texcoord = result.textureCoordinatesWithMappingChannel(channel)
// 2. place a sprite there
let sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 10, height: 10))
// scale coords: texcoords go 0.0-1.0, skScene space is is pixels
sprite.position.x = texcoord.x * skScene.size.width
sprite.position.y = texcoord.y * skScene.size.height
skScene.addChild(sprite)
}
}
}
For more details on the SpriteKit approach (in Objective-C) see the SceneKit State of the Union Demo from WWDC14. That shows a SpriteKit scene used as the texture map for a torus, with spheres of paint getting thrown at it -- whenever a sphere collides with the torus, it gets a SCNHitTestResult and uses its texcoords to create a paint splatter in the SpriteKit scene.
Finally, some Swift style comments on your code (unrelated to the question and answer):
Use let instead of var wherever you don't need to reassign a value, and the optimizer will make your code go faster.
Explicit type annotations (res: SCNHitTestResult) are rarely necessary.
Swift dictionaries are bridged to NSDictionary, so you can pass them directly to an API that takes NSDictionary.
Casting to a Swift typed array (hitTest(...) as [SCNHitTestResult]) saves you from having to cast the contents.

Resources