Swift 2.x: searching the node tree for multiple hits - ios

I want to search my SKScene class for childNodes that begin with "spaceship". Basically I have several spaceship nodes named "spaceship1", "spaceship2", "spaceship3", etc...
However I'm not getting the syntax right. This:
self.subscript("spaceship[0-9]")
results to :
Expected ',' separator
And this:
self.objectForKeyedSubscript("spaceship[0-9]")
Results to :
'objectForKeyedSubscript' is unavailable: use subscripting

There's a better approach to this problem than assigning tags like "spaceship" + counter.
The Spaceship class
Yes, for a number of reasons you should create a Spaceship class, like this
class Spaceship: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "spaceship")
super.init(texture: texture, color: .clearColor(), size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Retrieving all the spaceships
class GameScene:SKScene {
var spaceships: [Spaceship] {
return self.children.flatMap { $0 as? Spaceship }
}
}
Why a custom class is better than "numbered tags"
For several reasons
You don't get crazy assigning a new tag-with-counter value to each new sprite that should act like a Spaceship
You can add behaviour to your spaceship entity simply adding methods to the Spaceship class.
The compiler will block you if you erroneously use another node as a spaceship
You code is cleaner

This is a great handy reference for working with Strings in Swift.
http://useyourloaf.com/blog/swift-string-cheat-sheet/
As per that site
let spaceshipString = "spaceship1"
spaceshipString.hasPrefix("spaceship") // true
spaceshipString.hasSuffix("1") // true
With that in mind, you can just enumerate through all your nodes to find the ones with spaceship by doing the following.
func findSpaceShipNodes() {
self.enumerateChildNodesWithName("//*") {
node, stop in
if (( node.name?.hasSuffix("spaceship") ) != nil) {
// Code for these nodes in here //
}
}
}

Related

Extend SKAction to override timingMode

I have many SKActions in a SpriteKit project. The default timingMode for SKActions is "linear". Is it possible to use an extension to override this timingMode default to e.g. "easeInEaseOut" so ALL SKActions have timingMode = easeInEaseOut?
I have tried various "extension" styles but none will compile - normally returning "'timingMode' used within its own type" or "Initializer 'init()' with Objective-C selector 'init' conflicts with implicit initializer 'init()' with the same Objective-C selector"
The docs don't seem to give any examples of this, but surely this would be a useful thing to be able to do? Especially when you have hundreds of SKActions in your game?
Pick your poison, one extends the action to allow you to quickly call .easeInEaseOut timing mode, the other extends SKNode to allow you to run using a specific timing mode.
There is no way to change default behavior, the only other way is to create your own static methods for every action that exists, which can become cumbersome.
extension SKAction
{
//e.g. SKAction.move(to: CGPoint.zero, duration: 10).easeInEaseOut()
func easeInEaseOut() -> SKAction
{
self.timingMode = .easeInEaseOut
return self
}
}
extension SKNode
{
func runWithEaseInEaseOut(action:SKAction,withKey key: String = "")
{
action.timingMode = .easeInEaseOut
if key != ""
{
self.run(action,withKey:key)
}
else
{
self.run(action)
}
}
}

Updating multiple Spritekit nodes with the same name

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.

Remove a specific list of SKAction from a SKNode

During the development of some interactions between different nodes, I realized I need to remove a specific list of actions from a node.
The current version of Sprite-Kit framework provides some instance methods as:
removeAllActions()
action(forKey key: String)
hasActions()
Obviously every action that running in my node has a String key for its identifications.
So I thought of something that was very similar to removeAllAction, then I made an SKNode extension:
public extension SKNode {
func removeAllAction(in list:[String]) {
list.forEach { if self.action(forKey: $0) != nil { self.action(forKey: $0)?.speed = 0.0; self.removeAction(forKey: $0)}}
}
}
And in my project I can use it as:
let actionList = ["idle_walk_sx","idle_walk_dx","walk_dx","walk_sx","walk_idle_sx","walk_idle_dx","rotate_sx_dx","rotate_dx_sx"]
self.removeAllAction(in: actionList)
The code works well.
But I'm not really sure about two factors:
the effectiveness of the speed corrections (to zero), I thought it
appropriate to introduce it in the face of repeated actions. Should I remove it due to avoid strangness or leave it ?
How is it possible to extend this extension (expanding it) to any
childrens that have the same list to remove?
I agree with the comments. Probably speed is not necessary so the code + the scanning to the children nodes could be something like:
public extension SKNode {
func removeAllAction(in list:[String]) {
list.forEach { if self.action(forKey: $0) != nil { self.removeAction(forKey: $0)}}
self.children
.filter { $0.hasActions() }
.forEach { $0.removeAllAction(in: list) }
}
}

SpriteKit addChild(node: SKNode) plural?

Is there a plural version of this command, so I can type in a list of children to add?
Kind of like:
addChildren(myNodeABC, myNodeXYZ, myNode123)
Write an extension to do it: (I wrote it in Swift 3 style, I do not have XCode available right now to verify this works)
extension SKNode
{
func add(children: SKNode...) {
for child in children{
addChild(child)
}
}
}
usage:
node.add(children:node1,node2,node3)
note:
... is called variadic parameters in case you want to know more about them: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html
If you want to use less lines, do:
extension SKNode
{
func add(children: SKNode...) {
children.forEach({addChild($0)});
}
}

Accessing sprite nodes added in loop to call class inherited method?

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.

Resources