SpriteKit, copying a physics body does NOT copy the settings? - swift

It seems almost incredible but,
let pb = model.physicsBody?.copy() as! SKPhysicsBody
print("orig .. \(model.physicsBody!.linearDamping)")
print("pb .. \(pb.linearDamping)")
they are NOT the same. No really.
This is so bizarre that I must be doing something wrong. What am I doing wrong?
Other than just manually copying all the properties, and maintaining the code forever as that evolves, how to do this??
Here's a dupe-qualities function if anyone needs it, to save typing. (This of 2017 - of course it has to be maintained forever as Apple add qualities.)
extension SKSpriteNode
{
func dupe() -> Any {
// annoyingly, Apple does not provide a dupe function as you would use
// when spawning from a model.
let c = self.copy() as! SKSpriteNode
c.physicsBody = self.physicsBody!.copy() as? SKPhysicsBody
c.physicsBody?.affectedByGravity = self.physicsBody!.affectedByGravity
c.physicsBody?.allowsRotation = self.physicsBody!.allowsRotation
c.physicsBody?.isDynamic = self.physicsBody!.isDynamic
c.physicsBody?.pinned = self.physicsBody!.pinned
c.physicsBody?.mass = self.physicsBody!.mass
c.physicsBody?.density = self.physicsBody!.density
c.physicsBody?.friction = self.physicsBody!.friction
c.physicsBody?.restitution = self.physicsBody!.restitution
c.physicsBody?.linearDamping = self.physicsBody!.linearDamping
c.physicsBody?.angularDamping = self.physicsBody!.angularDamping
c.physicsBody?.categoryBitMask = self.physicsBody!.categoryBitMask
c.physicsBody?.collisionBitMask = self.physicsBody!.collisionBitMask
c.physicsBody?.fieldBitMask = self.physicsBody!.fieldBitMask
c.physicsBody?.contactTestBitMask = self.physicsBody!.contactTestBitMask
return c
}
}
(Just one man's opinion, I feel it's better to not just override the NSCopy, since, the whole thing is a touchy issue and it's probably better to simply be explicit for the sake of the next engineer. It's very common to "dupe" the qualities of a game object to another, so, this is fine.)

It has to do with SKPhysicsBody being a wrapper class to PKPhysicsBody
Essentially what is going on is when you create a copy of SKPhysicsBody, it creates a new instance of PKPhysicsBody, not a copy of it.
To get around this, you need to write an extension that fills in the values for you:
extension SKPhysicsBody
{
override open func copy() -> Any {
guard let body = super.copy() as? SKPhysicsBody else {fatalError("SKPhysicsBody.copy() failed")}
body.affectedbyGravity = affectedByGravity
body.allowsRotation= allowsRotation
body.isDynamic= isDynamic
body.mass= mass
body.density = density
body.friction = friction
body.restitution = restitution
body.linearDamping = linearDamping
body.angularDamping = angularDamping
return body
}
}
Note, I typed this by hand, I do not have XCode available at this time to test if it does work.

Related

Copying SCNParticleSystem doesn't seem to work well

I'm trying to use an SCNParticleSystem as a "template" for others. I basically want the exact same properties except for the color animation for the particles. Here's what I've got so far:
if let node = self.findNodeWithName(nodeName),
let copiedParticleSystem: SCNParticleSystem = particleSystemToCopy.copy() as? SCNParticleSystem,
let colorController = copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color],
let animation: CAKeyframeAnimation = colorController.animation as? CAKeyframeAnimation {
guard animation.values?.count == animationColors.count else {
return nil
}
// Need to copy both the animations and the controllers
let copiedAnimation: CAKeyframeAnimation = animation.copy() as! CAKeyframeAnimation
copiedAnimation.values = animationColors
let copiedController: SCNParticlePropertyController = colorController.copy() as! SCNParticlePropertyController
copiedController.animation = copiedAnimation
// Finally set the new copied controller
copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color] = copiedController
// Add the particle system to the desired node
node.addParticleSystem(copiedParticleSystem)
// Some other work ...
}
I copy not only the SCNParticleSystem, but also SCNParticlePropertyController and CAKeyframeAnimation just to be safe. I've found that I've had to manually do these "deep" copies "manually" since a .copy() on SCNParticleSystem doesn't copy the animation, etc.
When I turn on the copied particle system on the node it was added to (by setting the birthRate to a positive number), nothing happens.
I don't think the problem is with the node that I've added it to, since I've tried adding the particleSystemToCopy to that node and turning that on, and the original particle system becomes visible in that case. This seems to indicate to me that the the node I've added the copied particle system to is OK in terms of its geometry, rendering order, etc.
Something else perhaps worth mentioning: the scene is loaded from a .scn file and not created programmatically in code. In theory that shouldn't affect anything, but who knows ...
Any ideas on why this copied particle system doesn't do anything when I turn it on?
Do not use copy() method for particle systems !
copy() method does not allow copy particles' color (copied particles will be default white).
You can test it with the following code:
let particleSystem01 = SCNParticleSystem()
particleSystem01.birthRate = 2
particleSystem01.particleSize = 0.5
particleSystem01.particleColor = .systemIndigo // INDIGO
particleSystem01.emitterShape = .some(SCNSphere(radius: 2.0))
let particlesNode01 = SCNNode()
particlesNode01.addParticleSystem(particleSystem01)
particlesNode01.position.y = -3
sceneView.scene.rootNode.addChildNode(particlesNode01)
let particleSystem02 = particleSystem01.copy() // WHITE
let particlesNode02 = SCNNode()
particlesNode02.addParticleSystem(particleSystem02 as! SCNParticleSystem)
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)
Use clone() method for nodes instead !
clone() method works more consistently for 3d objects and particle systems and it can help you save particles' color but, of course, doesn't allow save a position for each individual particle.
let particlesNode02 = particlesNode01.clone() // INDIGO
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)

updating an SKLabel to show the right integer

ok so first off I have an achievement Variable that is defined from a struct like so:
struct Achieve {
var node: SKSpriteNode?
var aName:String = ""
var aDes:String = ""
var aImage: String = "" {
didSet {
node?.texture = SKTexture(imageNamed: aImage)
}
}
var aAmount:Int = 0
var aRequired:Int = 0
var aStage:Int = 0
}
var Ach1 = Achieve(node: nil, aName: "Player", aDes: "Games Played", aImage: "locked", aAmount: 0, aRequired: 10, aStage: 1)
My problem is when I try and change the number of the aAmount property it isn't displayed on the SKLabel that displays the amount it just stays at 0 my sklabel is defined in a function like below:
func generateLabels(location: CGPoint, page:SKSpriteNode, text: String) -> SKLabelNode {
let node = SKLabelNode()
node.fontColor = UIColor.whiteColor()
node.fontName = "Helvetica"
node.position = location
node.fontSize = 15
node.text = text
page.addChild(node)
return node
}
func menu() {
_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView, text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
}
It seems to work if I make changes to aAmount when I stop running it and then change it and then run the game again. it also seems to make changes to the aAmount property during gameplay but it doesn't seem to update the label for some reason during the gameplay. Can someone please tell me why it won't update?
Also i'm updating the aAmount property like so:
Ach1.aAmount += 1
print("\(Ach1.Amount)")
In your Achieve struct, add a property called label:
var label: SKLabelNode?
And change the aAmount property to look something like this:
var aAmount: Int = 0 {
didSet {
label?.text = "\(aAmount) / \(aRequired)"
}
}
Now when you generate the labels, replace this:
_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView,
text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
with this
var label = _ = generateLabels(CGPointMake(0, -285), page:page1ScrollView,
text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
Ach1.label = label
Now when you change the aAmount property of Ach1, the label's text should change as well.
Suggestions:
Judging from this and your last question about sprite node images not updating, I think your programming skills are not mature enough. In the long term, you should learn the basics first. Try playing around with UIKit and read more others' code. See how other people do things. You will definitely learn a lot. In the short term though, you shouldn't make each of all these properties binds to a node. That will make Achieve too dependent. What you should do is make Achieve a subclass of SKSpriteNode and add those other sprite nodes and label nodes as sub nodes of the Achieve node.
If I read your code, there isn't any part where you update your SKLabelNode.
If you call the function :
func menu() {
_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView, text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
}
for example to didMoveToView, it generate a label with your aAmount value when you call your scene (so 1 time only as didMoveToView works).
Instead, assuming that you call this menu function many times, it generate everytime a new label but there is no code written from you where you remove the previous label or labels added in past (see page.addChild(node)) and I don't understand why you want to return a label from your function if you don't use it, in fact you assign your result to _. Also you don't explain what is "page1ScrollView", are you using a scrollview?
Anyway, the best mode to update your label is simply to create 1 time only (for example on didMoveToView) and, everytime you want to update it, doing:
myNodeLabel.text = "\(Ach1.aAmount) / \(Ach1.aRequired)"
instead of re-create it everytime. I hope this helps to understand the dynamics that prevent your label to upgrade.
You have to reassign the text of the SKLabel. So when you call the function to change the number (or whatever it is), also run the code to make the label what you want it to be. My guess as to why it only works sometimes is that sometimes you call it in the update (or somewhere else that gets called repeatedly) and sometimes you call it somewhere like didMoveToView (only called once, so label doesn't get updated because of the way your code is).
Just whenever you update aAmount, add this code after it:
label.text = "\(aAmount)"

Game Scene cannot be constructed because it has not accessible initializers

Xcode is dropping an error and I dont know how to solve it. Maybe someone can give me a hand or a good hint?
Here is my code that is working within my Game Scene:
var currentLevel: Int = 1
var platforms: PlatformsNode!
var platformNode1: PlatformNode!
var platformNode2: PlatformNode!
var platformNode3: PlatformNode!
var platformArray = [SKNode]()
let passenger = SKSpriteNode(imageNamed: "passenger")
var passengerNumber:Int = 1
var startPlatform = [3, 3, 2, 1, 2]
var destinationPlatform = [1, 1, 3, 2, 1]
// Level selection
class func level(levelNum: Int) -> GameScene? {
let scene = GameScene(fileNamed: "Level\(levelNum)")! // <- compiler error
scene.currentLevel = levelNum
scene.scaleMode = .AspectFill
return scene
}
As soon as I want to replace
let passenger = SKSpriteNode(imageNamed: "passenger")
with let passenger: PassengerNode! the compiler drops an compiler error "Game Scene cannot be constructed because it has not accessible initializers".
And this error just shows up, when I change the way I want to declare let passenger.
The reason I want to change it, is that I want to replace it with a class, so that the passenger
a. can have a different type with a different texture (this can may be solved differently)
b. can be removed from the parent and later be added again - with hopefully no error.
You got any ideas? I am really stuck somehow :-?
declaring passenger with only its type and no initial value requires that you add an init() to your class because the compiler does not know what initial value to give it when the object is created.
You can either create an init() and set a value to passenger or delare it as
let passenger: PassengerNode! = nil
(this assumes that your code does not reference the variable before making sure to put something in it or checks for nil/optional before using it).

Declaring a random amount of objects in Swift

I'm basically trying to do what this guy is trying to do, but with Swift:
Declaring a random amount of objects in c++
I'm pretty new to programming and very new to Swift so I'm a little fuzzy on how to do some things. Basically, I just want to declare a random amount of enemies that my player has to weave through and to register when he's hit one of them. I've been looking every where for an answer and either this is a really stupid question, so stupid no one has ever needed to put it on the internet, or Sift is too new for it to have been a problem for someone else. I'm guessing it's a stupid question, but regardless I'm out of ideas on how to figure this out.
Thanks.
Swift arrays are dynamic, at least the mutable type that are declared with var array.
So you can just add objects to your array until you have enough objects.
e.g.:
var objects = [String]()
//let numberOfObjects = arc4random_uniform(10) // between 0 and 9
let numberOfObjects = arc4random_uniform(5) + 5 // between 5 and 9
for i in 0..<numberOfObjects {
let object = String()
objects.append(object)
}
println("we have \(countElements(objects)) objects")
Maybe I'm too dense to understand the question, but it seems to me that simply declaring an NSMutableArray would do what you want:
let myArray:NSMutableArray = NSMutableArray()
You would have all the flexibility to add or remove objects as you please.
In any given loop, you could remove all objects, then use a random number generator to add back a random number of objects if that's what you're trying to do.
My additional comment on the other answer to this is that I would explicitly type cast everything in the statement that generates the random number:
var randomNumber:Int = Int(arc4random_uniform(UInt32(5)))
Here's an example using a simple Enemy class:
class Enemy : NSObject {
override init() {
super.init()
}
}
let range = (5, 20)
let numEnemies = Int(arc4random_uniform(UInt32(range.1 - range.0) + 1) + UInt32(range.0))
var enemyArray = [Enemy]()
for i in 0..<numEnemies {
enemyArray.append(Enemy())
}
You want a random number of objects in an array. Assume:
class MyObject {}
and that you have an upper bound on the number of objects:
let theObjectLimit = 10
then you can create a random number of new, distinct instances of MyObject with simply:
let theObjects = (1...arc4random_uniform(theObjectLimit)).map {
_ -> MyObject in return MyObject ()
}
When learning a language like Swift, that has first class functions (aka closures), you'll want to use functions like map and reduce wherever possible - especially instead of iteration.

How to use properties from SCNFloor with Swift and Scenekit?

I can't figure out how to access properties from the Scenekit SCNFloor class using Swift, in objective-c they do it like this:
SCNNode*floor = [SCNNode node];
floor.geometry = [SCNFloor floor];
((SCNFloor*)floor.geometry).reflectionFalloffEnd = 10;
This creates the floor, how do i acces "reflectionFalloffEnd"?
let levelFloor = SCNNode()
levelFloor.geometry = SCNFloor()
Thanks
EDIT Thanks for the quick responses it works now :)
Whether you're in ObjC or Swift, for the compiler to be happy with you using SCNFloor properties or methods on a reference, that reference needs to have the type SCNFloor.
You can do that inline with a cast:
// ObjC
((SCNFloor*)floor.geometry).reflectionFalloffEnd = 10;
// Swift
(levelFloor.geometry as SCNFloor).reflectionFalloffEnd = 10
But that can get rather unwieldy, especially if you're going to set a bunch of properties on a geometry. I prefer to just use local variables (or constants, rather, since the references don't need to change):
let floor = SCNFloor()
floor.reflectionFalloffEnd = 10
floor.reflectivity = 0.5
let floorNode = SCNNode(geometry: floor)
floorNode.position = SCNVector3(x: 0, y: -10.0, z: 0)
scene.rootNode.addChildNode(floorNode)
(levelFloor.geometry as SCNFloor).reflectionFalloffEnd = 10 is how you would make that cast in Swift - you were almost there!
as an alternative - if you are going to need to access this property often you may end up with a bunch of as casts in your code :) you may instead prefer to simply declare constants to hold those objects:
let levelFloor = SCNNode()
let levelFloorGeometry = SCNFloor()
levelFloor.geometry = levelFloorGeometry
levelFloorGeometry.reflectionFalloffEnd = 10
// and so on as this property of your SCNNode's geometry needs to change throughout your app