I am currently making a game with Spritekit & Swift3 for the first time; it is a 2D, side-scrolling endless runner. I'm trying to have certain nodes move with my camera, but the problem is that they will be procedurally generated, belonging to the same class, so I need them all to have the same name. Here's the relevant sections of code, before I go any further:
(All variables have been initialized)
//move these nodes with the camera
private var ChoosePieces: ChoosePiecesClass?;
override func didMove(to view: SKView) {
initializeGame();
}
override func update(_ currentTime: TimeInterval) {
enumerateChildNodes(withName: "ChoosePieces") {
node, stop in
_ = node as! SKSpriteNode
self.ChoosePieces?.moveWithCamera();
}
}
private func initializeGame() {
ChoosePieces = childNode(withName: "ChoosePieces") as? ChoosePiecesClass;
}
I have tested the moveWithCamera() function and it works well--it simply increments the node's x-value by 10, moving at the same pace as the camera so that the node remains on screen at the same location.
My problem is that I'm not exactly sure how to use enumerateChildNodes(withName: "String") with my nodes, so that it will recognize all of them and move them. Right now the pieces just stay still. I found out about this function from another person's post--he/she was trying to spawn "enemies" in his/her game. Thanks in advance!
Usually with enumerateChildNodes you do something with the node that is returned e.g. modifying your own example:
override func update(_ currentTime: TimeInterval) {
enumerateChildNodes(withName: "ChoosePieces") {
node, stop in
if let piece = node as? SKSpriteNode { // Cast to Sprite if we can
piece.moveWithCamera()
}
}
}
So for every node that is returned, we cast it to a new SKSpriteNode called 'piece (if we can - using the as? operator - because enumerateChildNodes returns SKNodes) ' and then call moveWithCamera on the 'piece' node.
(the line piece.moveWithCamera() I made up - your original code appeared to be calling a class method(?) to do something. You might want self.moveWithCamera(piece) etc)
In your code, you did nothing with the nodes returned by your enumeration.
Related
Why is init(fileNamed:) of SKSpriteNode generating a nil?
I've tried the following code. I show only the code that is related to the problem:
let road = SKSpriteNode(fileNamed: "road.png")
override func didMove(to view: SKView) {
print("road", road as Any) // road nil
if let road = self.road {
road.position = view.center
road.physicsBody = SKPhysicsBody(rectangleOf: road.size)
print(road.physicsBody?.isDynamic as Any, "!")
road.physicsBody?.pinned = true
addChild(road)
}
}
I get a nil regardless of whether the image file is a regular png or an animated png file.
You should use SKSpriteNode(imageNamed:) instead of SKSpriteNode(fileNamed:).
SKSpriteNode(imageNamed:) - loads image as texture for you and creates a node.
SKSpriteNode(fileNamed:) is actually init method from SKNode, as it says in official doc:
init?(fileNamed: String)
Creates a new node by loading an archive file from the game’s main bundle.
So there are two different methods (constructors), from two different classes, and even though one inherit from another they should not be confused.
Why are swift functions so expensive?
I have been writing an app using SpriteKit. Every frame, I recalculate the position of the CameraNode in the update() function. Lets call this the //(cameraNodeCode). This current setup had little influence on the frames per second, it stayed at 60.
override func update() {
//(cameraNodeCode)
}
As the cameraNodeCode is quite large, I thought it would be better to simplify my code and put it into a function: updateCameraNode(). Now this was what I had:
func updateCameraNode() {
//(cameraNodeCode)
}
override func update() {
updateCameraNode()
}
When I set up the code like this, the frames per second suddenly dropped to 20. I was very surprised as I didn't think functions were this expensive to call. I decided to test my theory with this code:
func emptyFunction() {
}
override func update() {
emptyFunction()
emptyFunction()
emptyFunction()
emptyFunction()
emptyFunction()
}
As I predicted, when I did this the frames per second dropped drastically, to 4.1 frames per second!
My questions are:
Why is this happening? Is it as I think and because simply calling a function is so expensive, or is there something I am missing?
Is there a way that I could still keep my code looking simple without having 20 frames per second?
Update
The key information that I left out was that I was using Xcode playgrounds. I think that this is a bug with SpriteKit and playgrounds. I have filed a bug report with Apple so I’ll see where that gets me.
Speaking about Sprite-kit, I've tested your code to my iPhone 7 in a fresh "Hello world" template :
import SpriteKit
class GameScene: SKScene {
private var label : SKLabelNode?
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
}
func emptyFunction() {}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
//emptyFunction()
//emptyFunction()
//emptyFunction()
//emptyFunction()
//emptyFunction()
}
}
If I don't commented the lines (remove // ) inside the update method, nothing change. I've always 60fps. Check your project to find what are the lines that caused this drastic drop of fps, or if you test your code to a simulator try to a real device. Hope it helps.
Swift has three different methods of dispatch with different performance characteristics:
Direct Dispatch should be very fast. Also known as Static Dispatch.
Table Dispatch is a bit slower due to a method lookup in a witness table. Also known as Dynamic Dispatch.
Method Dispatch is the most dynamic dispatch method. However, it is also the slowest one of the three.
You can force the compiler to use static dispatch by adding final to your method:
final func emptyFunction() {
}
This will also give the compiler additional opportunities for optimisation, such as inlining code. Remember to build with optimisations turned on, which is not the case for debug builds. Therefore you should make sure to choose the release configuration for performance testing. Debug builds of Swift projects are often notoriously slow.
See this post on the Swift blog for more information on method dispatch and the static keyword.
This great post explains the three kinds of method dispatch in Swift and when they are used.
I have created a number of SKSpriteNodes, adding them to the scene in a for loop.
func addBlocks(number : Int) {
for _ in 1...number {
let block = Obstacle()
addChild(block)
}
}
Since they're created in this loop, I can't call block.myMethod(). I can do of course callchildNodeWithName("block") to perform things it inherits by being a node, like .removeFromParent().
What should I do if I want to call the method implemented in the Obstacle class (subclass of SKSpriteNode).
Obstacle subclass of SKSpriteNode
Let's say you have Obstacle defined as follow.
class Obstacle: SKSpriteNode {
func explode() { }
}
Obstacles with names
If you add an Obstacle to the scene you can add a name to the node like
func addBlocks(number : Int) {
for i in 1...number {
let block = Obstacle()
block.name = "block_\(i)" // <-- like here
addChild(block)
}
}
Retrieving an SKNode by name and casting it to Obstacle
Next when you invoke self.childNodeWithName(...) you get something like this SKNode?.
It means you can receive nil or something that is an SKNode. If you believed (like in this case) that the returned object is something more specific of an SKNode (like Obstacle) the you can try to cast it to your custom class type.
func explodeObstacle(i: Int) {
guard let obstacle = self.childNodeWithName("block_\(i)") as? Obstacle else {
debugPrint("Could not find an Obstacle with name block_\(i)")
return
}
obstacle.explode()
}
In the code below, if the cast does fail (which mean the returned value is not an Obstacle, then an error message is printed on the console).
Otherwise if the value is successfully casted do Obstacle, you can invoke any method you added to Obstacle.
I am working in between three files: Menu.swift, Main.swift and Game.swift.
In my Main.swift, I define the variable swipeNumber:
class Main {
var swipeNumber: Int = 0 {
didSet{
println("The new swipe number is \(swipeNumber)")
}
}
}
N.B. It is in a class so that I can reference the variable from other files, and the didSet property observer will function.
As you can see, its initial value (I think) is 0.
Then, in my Menu.swift, I retrieve the information from the Main class in Main.swift.
let main = Main()
I then have three buttons, which will, on touch, change the swipeNumber variable, based on which button was pressed.
class Menu: UIViewController {
#IBAction func pressedThreeSwipes(sender: AnyObject) {
main.swipeNumber = 3
}
#IBAction func pressedFiveSwipes(sender: AnyObject) {
main.swipeNumber = 5
}
#IBAction func pressedTenSwipes(sender: AnyObject) {
main.swipeNumber = 10
}
//...
}
When I run the program, my property observer appears to work, printing messages such as:
The new swipe number is 3
The new swipe number is 5
The new swipe number is 10
And in the Game class, (for troubleshooting purposes), I have another property observer, checking the integer of the variable swipeNumber when the button test is pressed:
class Game: UIView {
let main = Main()
func didMoveToView(view: UIView) {
/* Setup your scene here */
println("now")
println("\(main.swipeNumber)"
//Nothing happens here, suggesting that didMoveToView is failing
}
#IBAction func test(sender: AnyObject) {
println("\(main.swipeNumber)")
}
}
My func test prints a number, but sadly that number is not 3, 5, or 10. It's 0.
I think that the problem lies with my variable in Main.swift, however I am not sure.
Any advice or 'fixes', whether quick or lengthy, would be very greatly appreciated.
Thank you,
Will
You have different instances of your class Main, and they each carry a different value for the same properties.
You should try the Singleton pattern (see e.g. here or here).
When you call Main(), you are creating a new object...emphasis on NEW. It has no knowledge of what you've done to other objects of the same type. If you want to use the same object in different places, you need to make it a parameter and pass it into methods rather than creating a different object.
I have created a subclass of SKSpriteNode. I connect instances of that class together with joints of type SKPhysicsJointLimit. I do this within my didEndContact(contact: SKPhysicsContact) in my GameScene:
var joint = SKPhysicsJointLimit.jointWithBodyA(contact.bodyA, bodyB: contact.bodyB, anchorA: pos1!, anchorB: pos2!)
self.physicsWorld.addJoint(joint)
This works well so far.
Then i come to the point where i want to release the node from the joint. According to the SKPhysicsBody docs there is a property called "joints" which is an array holding SKPhysicsJoint objects. I thought thats exactly what I need, but I am not able to iterate over an instance's joints and remove them from the physicsWorld. To do the job i added a method to my custom SKSpriteNode subclass.
func freeJoints(world: SKPhysicsWorld){
if let joints = self.physicsBody?.joints {
for joint in joints{
println("found a joint: \(joint)")
// example print:
//found a joint: <PKPhysicsJointRope: 0x7fbe39e95c50>
world.removeJoint(joint as SKPhysicsJoint)
}
}
}
Calling the method fails after the println() statement with the message "Swift dynamic cast failed". I would really appreciate your opinion in how to work with an SKPhysicsBody's joint property. More specifically: How to use (cast?) the items in the array to be able to remove them from a scene's SKPhysicsWorld.
I spent a little more time in investigating this. This is what I have come up with:
I decided to add an property to my SKSpriteNode subclass and manage the joints myself
var joints: [SKPhysicsJointLimit]
override init(){
...
self.joints = []
...
}
Everytime I add an joint to the scene's SKPHysicsWorld I also add it to the joints array of the SKNNode itself. Whilst iterating the SKPHysicsBody's joints-Array failed (see question) at the point I wanted to cast it to SKPhysicsJoint, removing items from the physics world works as intended when iterating the array of SKPhysicsJointLimit items:
func freeJoints(world: SKPhysicsWorld){
for item in self.joints{
println("removing item from physics world \(item)")
world.removeJoint(item)
}
self.joints.removeAll(keepCapacity: false)
}
}
This seems not to be the most elegant way to do the job, since there already is a framework managed array that promises to be same thing. But I was unable to utilize it and this works for now.