How to choose from an array of sprites in swift - ios

So basically I am looking to choose one of the 4 different coloured balls at random to come into the scene which each have an animation, physics properties and movement & spacing that I have already coded. I am not sure exactly how to make the array then choose at random from the array of the 4 coloured balls so that I have one ball chosen at random to come into the scene.
To make it more clear what I'm asking here's some code (I only use two balls in this code so you don't have to read as much):
var moveandremove = SKAction() < this is in my ballScene.swift
The spawn runBlock is inside didMovetoView
let spawn = SKAction.runBlock({
() in
self.allballs()
})
let delay = SKAction.waitForDuration(2.0)
let SpawnDelay = SKAction.sequence([spawn, delay])
let spawndelayforever = SKAction.repeatActionForever(SpawnDelay)
self.runAction(spawndelayforever)
let distance = CGFloat(brnball.frame.width * 20 + brnball.frame.width)
let moveball = SKAction.moveByX(-distance, y: 0, duration: NSTimeInterval(0.003 * distance))
let removeball = SKAction.removeFromParent()
moveandremove = SKAction.sequence([moveball])
}
func allballs() {
TextureAtlasblk = SKTextureAtlas(named: "blkball")
for i in 1...TextureAtlasblk.textureNames.count{
var Name = "blkball_\(i)"
blkarray.append(SKTexture(imageNamed: Name))
}
blkball = SKSpriteNode(imageNamed: "blkball_1")
blkball.position = CGPoint(x: CGRectGetMidX(self.frame) + 100, y: CGRectGetMidY(self.frame))
blkball.zPosition = 7
blkball.setScale(0.1)
self.addChild(blkball)
blkball.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(blkarray, timePerFrame: 0.2)))
//brownball
TextureAtlasbrn = SKTextureAtlas(named: "brnball")
for i in 1...TextureAtlasbrn.textureNames.count{
var Name = "brnball_\(i)"
brnarray.append(SKTexture(imageNamed: Name))
}
brnball = SKSpriteNode(imageNamed: "brnball_1")
brnball.position = CGPoint(x: CGRectGetMidX(self.frame) + 50, y: CGRectGetMidY(self.frame))
brnball.zPosition = 7
brnball.setScale(0.1)
self.addChild(brnball)
brnball.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(brnarray, timePerFrame: 0.2)))
Here is my terrible starting point at trying to make an array to choose from each ball (this is inside my allballs() function):
var ballarray: NSMutableArray = [blkball, brnball, yelball, bluball]
runAction(moveandremove)
I am new to swift and pretty hopeless, would be awesome if someone could help me out :)
Thanks

It's hard for me to find the array that you're talking about in your code. But nevertheless, I can still show you how.
Let's say we have an [Int]:
let ints = [10, 50, 95, 48, 77]
And we want to get a randomly chosen element of that array.
As you may already know, you use the subscript operator with the index of the element to access an element in the array, e.g. ints[2] returns 95. So if you give a random index to the subscript, a random item in the array will be returned!
Let's see how can we generate a random number.
The arc4random_uniform function returns a uniformly distributed random number between 0 and one less the parameter. Note that this function takes a UInt32 as a parameter and the return value is of the same type. So you need to do some casting:
let randomNumber = Int(arc4random_uniform(UInt32(ints.count)))
With randomNumber, we can access a random element in the array:
let randomItem = ints[randomNumber]
Try to apply this technique to your situation.
Here's a generic method to do this as well:
func randomItemInArray<T> (array: [T]) -> T? {
if array.isEmpty {
return nil
}
let randomNumber = Int(arc4random_uniform(UInt32(array.count)))
return array[randomNumber]
}
Note that if the array passed in is empty, it returns nil.

You could make and extension for Array that returns a random element.
extension Array {
func randomElement() -> Element {
let i = Int(arc4random_uniform(UInt32(count - 1)))
return self[i]
}
}
You could take that a step further and allow a function to be applied directly to a random element.
mutating func randomElement(perform: (Element) -> Element) {
let i = Int(arc4random_uniform(UInt32(count - 1)))
self[i] = perform(self[i])
}
You can use this function when using an array of reference types.
func randomElement(perform: (Element) -> ()) {
let i = Int(arc4random_uniform(UInt32(count - 1)))
perform(self[i])
}

Related

How to calculate distance from point 2 to point 3 and 3 to 4 so on using ARKit?

I am making an app to calculate the distance and area now the problem is I made an array and I am appending my nodes in there.
func calculate () {
let start = dotNodes[0]
let end = dotNodes[1]
let a = end.position.x - start.position.x
let b = end.position.y - start.position.y
let c = end.position.z - start.position.z
let distance = sqrt(pow(a,2) + pow(b,2) + pow(c, 2))
updateText(text:"\(abs( distance))", atPosition: (end.position))
}
Now the start point is 0 index and end is index 1 but these are only two points. How can I make it to calculate distance from 2 to 3 and 3to 4 so on, and at the end when the last point is touching the point 1 it should give me area?
As #Maxim has said you can begin by simplifying your calculations ^______^.
I will attempt to answer your question however, using the GLK Math Helper Methods which if your'e interested you can read more about here: GLK Documentation.
In essence what you need to do is iterate through your array of positions, and calculate the distance between these in segments of 2. When your last iteration has only one element, then you would calculate the position between this and the first one.
Since I am not great a Maths, I did a quick search on StackOverflow to find a solution, and made use of the answer provided by #Gasim in the post Iterate Over Collections Two At A Time In Swift.
Since my attempt is quite lengthy, instead of going through each part step, by step, I have provided answer which is fully commented, and hope will point you in the right direction.
As always if someone else can help refactor and or improve the code, please feel free:
//
// ViewController.swift
// Measuring Example
//
// Created By Josh Robbins (∩`-´)⊃━☆゚.*・。゚* on 27/04/2019.
// Copyright © 2019 BlackMirrorz. All rights reserved.
//
import UIKit
import ARKit
class ViewController: UIViewController {
#IBOutlet weak var augmentedRealityView: ARSCNView!
var augmentedRealityConfiguration = ARWorldTrackingConfiguration()
var augmentedRealitySession = ARSession()
var markerNodes = [SCNNode]()
typealias NodeNameData = (name: String, node: SCNNode)
typealias DistanceData = (distance: Float, positionA: GLKVector3, positionB: GLKVector3)
//---------------------
//MARK:- Initialization
//---------------------
override func viewDidLoad() {
super.viewDidLoad()
setupARSession()
}
/// Sets Up Our ARSession
func setupARSession(){
augmentedRealityView.session = augmentedRealitySession
augmentedRealitySession.run(augmentedRealityConfiguration, options: [.removeExistingAnchors, .resetTracking])
}
/// Creates A Node To Mark The Touch Position In The Scene
///
/// - Returns: SCNNode
func markerNode() -> SCNNode{
let node = SCNNode(geometry: SCNSphere(radius: 0.01))
node.geometry?.firstMaterial?.diffuse.contents = UIColor.cyan
return node
}
//------------------------
//MARK:- Marker Placemenet
//------------------------
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Users Current Touch Point & Check We Have A Valid HitTest Result
guard let touchPoint = touches.first?.location(in: self.augmentedRealityView),
let hitTest = self.augmentedRealityView.hitTest(touchPoint, types: .featurePoint).first
else { return }
//2. Get The World Transorm & Create An SCNNode At The Converted Touch Position
let transform = hitTest.worldTransform
let node = markerNode()
node.position = SCNVector3(transform.columns.3.x, transform.columns.3.y, transform.columns.3.z)
self.augmentedRealityView.scene.rootNode.addChildNode(node)
//3. Add The Node To Our Markers Array So We Can Calculate The Distance Later
markerNodes.append(node)
//4. If We Have 5 Marker Nodes Then Calculate The Distances Between Them & Join Them Together
if markerNodes.count == 5{
calculateMarkerNodeDistances()
markerNodes.removeAll()
}
}
//-------------------
//MARK:- Calculations
//-------------------
/// Enemurates Our Marker Nodes & Creates A Joining Node Between Them
func calculateMarkerNodeDistances(){
var index = 0;
while index < markerNodes.count {
let nodeA = markerNodes[index];
var nodeB : SCNNode? = nil;
if index + 1 < markerNodes.count {
nodeB = markerNodes[index+1];
}
//1. Create A Joining Node Between The Two Nodes And Calculate The Distance
if let lastNode = nodeB{
let nodeA = NodeNameData("Node \(index)", nodeA)
let nodeB = NodeNameData("Node \(index+1)", lastNode)
self.augmentedRealityView.scene.rootNode.addChildNode(joiningNode(between: [nodeA, nodeB]))
}else{
//2. Here We Can Assume The We Have Reached The Last Node So We Calculate The Distance Between The 1st & Last Nodes
guard let initialNode = markerNodes.first, let lastNode = markerNodes.last else { return }
let nodeA = NodeNameData("Node 0 ", initialNode)
let nodeB = NodeNameData("Node \(markerNodes.count)", lastNode)
self.augmentedRealityView.scene.rootNode.addChildNode(joiningNode(between: [nodeA, nodeB]))
}
//Increment By 1 So We Join The Nodes Together In The Correct Sequence e.g. (1, 2), (3, 4) And Not (1, 2), (3, 4)
index += 1;
}
}
/// Creates A Joining Node Between Two Names
///
/// - Parameter nodes: [NodeNameData]
/// - Returns: MeasuringLineNode
func joiningNode(between nodes: [NodeNameData]) -> MeasuringLineNode{
let distance = calculateDistanceBetweenNodes([nodes[0], nodes[1]])
let joiner = MeasuringLineNode(startingVector: distance.positionA, endingVector: distance.positionB)
return joiner
}
/// Calculates The Distance Between Two SCNNodes
///
/// - Parameter nodes: [NodeNameData]
/// - Returns: DistanceData
func calculateDistanceBetweenNodes(_ nodes: [NodeNameData]) -> DistanceData{
//1. Calculate The Distance
let positionA = GLKVectorThreeFrom(nodes[0].node.position)
let positionB = GLKVectorThreeFrom(nodes[1].node.position)
let distance = GLKVector3Distance(positionA, positionB)
let meters = Measurement(value: Double(distance), unit: UnitLength.meters)
print("Distance Between Markers [ \(nodes[0].name) & \(nodes[1].name) ] = \(String(format: "%.2f", meters.value))m")
//2. Return The Distance A Positions Of The Nodes
return (distance, positionA, positionB)
}
/// Creates A GLKVector3 From An SCNVectore3
///
/// - Parameter vector3: SCNVector3
/// - Returns: GLKVector3
func GLKVectorThreeFrom(_ vector3: SCNVector3) -> GLKVector3 { return GLKVector3Make(vector3.x, vector3.y, vector3.z) }
}
//-------------------------
//MARK:- Mesuring Line Node
//-------------------------
class MeasuringLineNode: SCNNode{
/// Creates A Line Between Two SCNNodes
///
/// - Parameters:
/// - vectorA: GLKVector3
/// - vectorB: GLKVector3
init(startingVector vectorA: GLKVector3, endingVector vectorB: GLKVector3) {
super.init()
let height = CGFloat(GLKVector3Distance(vectorA, vectorB))
self.position = SCNVector3(vectorA.x, vectorA.y, vectorA.z)
let nodeVectorTwo = SCNNode()
nodeVectorTwo.position = SCNVector3(vectorB.x, vectorB.y, vectorB.z)
let nodeZAlign = SCNNode()
nodeZAlign.eulerAngles.x = Float.pi/2
let box = SCNBox(width: 0.001, height: height, length: 0.001, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor.white
box.materials = [material]
let nodeLine = SCNNode(geometry: box)
nodeLine.position.y = Float(-height/2)
nodeZAlign.addChildNode(nodeLine)
self.addChildNode(nodeZAlign)
self.constraints = [SCNLookAtConstraint(target: nodeVectorTwo)]
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
}
Based on this simple (and hopefully accurate answer) the result was something like so:
Distance Between Markers [ Node 0 & Node 1 ] = 0.14m
Distance Between Markers [ Node 1 & Node 2 ] = 0.09m
Distance Between Markers [ Node 2 & Node 3 ] = 0.09m
Distance Between Markers [ Node 3 & Node 4 ] = 0.05m
Distance Between Markers [ Node 0 & Node 5 ] = 0.36m
In my example I am calculating the distances of five nodes but you could call this a any point. And of course you will then need to use a formula for calculating the area itself. However this should be more than enough to point you in the right direction.
Hope it helps...
The best performing (and also the easiest) way is to use SIMD -
https://developer.apple.com/documentation/accelerate/simd/working_with_vectors
let dist = simd_distance(start, end)
where vectors should probably be redefined as simd_float3 (or SIMD3<Float>, if you are using Swift 5).
P.S. You need to import simd framework first.

Developing an ARKit app that leaves text for others to view

I am creating an iOS AR app that sets text in a specific location and leaves it there for others to view. Is there a better way to implement it than what I am doing?
Currently, I have it set so that the text is saved to Firebase and loads it by setting the nodes relative to the camera’s position. I’m wondering if there is a way to save ARAnchors in a fashion similar to what I am doing but is that possible?
My current function for saving the text to the location via a user tapping the screen:
/*
* Variables for saving the user touch
*/
var touchX : Float = 0.0
var touchY : Float = 0.0
var touchZ : Float = 0.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// will be used for getting the text
let textNode = SCNNode()
var writing = SCNText()
// gets the user’s touch upon tapping the screen
guard let touch = touches.first else {return}
let result = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitResult = result.last else {return}
let hitTransform = SCNMatrix4.init(hitResult.worldTransform)
let hitVector = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
// saves X, Y, and Z coordinates of touch relative to the camera
touchX = hitTransform.m41
touchY = hitTransform.m42
touchZ = hitTransform.m43
// Was thinking of adding the ability to change colors. Probably can skip next seven lines
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: input.text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
// modifies the node’s position and size
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
// last few lines save the info to Firebase
let values = ["X" : touchX, "Y" : touchY, "Z" : touchZ, "Text" : input.text!] as [String : Any]
let childKey = reference.child("Test").childByAutoId().key
if input.text != nil && input.text != "" {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} else {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} // if
} // override func
/*
* Similar to the previous function but used in next function
*/
func placeNode(x: Float, y: Float, z: Float, text: String) -> Void {
let textNode = SCNNode()
var writing = SCNText()
let hitVector = SCNVector3Make(x, y, z)
touchX = x
touchY = y
touchZ = z
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
} // func
/*
* This next function is used in my viewDidLoad to load the data
*/
func handleData() {
reference.child("Test").observeSingleEvent(of: .value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let xCoord = Float(truncating: child.childSnapshot(forPath: "X").value as! NSNumber)
let yCoord = Float(truncating: child.childSnapshot(forPath: "Y").value as! NSNumber)
let zCoord = Float(truncating: child.childSnapshot(forPath: "Z").value as! NSNumber)
let inscription = child.childSnapshot(forPath: "Text").value
self.placeNode(x: xCoord , y: yCoord , z: zCoord , text: inscription as! String)
} // for
} // if
}) // reference
} // func
I have looked into a few things such as ARCore but that looks like it uses Objective-C. I’ve made this app in Swift and I am not sure if I can incorporate ARCore with how I have implemented my current application.
Do I just need to get over it and learn Objective-C? Can I still work with what I have?
I think that ARCore anchors are only available for 24 hours, so that could be a problem.
You probably need to use ARKit2.0's ARWorldMap and save it as data on firebase for others to see the text in the same place, otherwise you are assuming in your code that future users will start their AR session in the exact same position and direction as the person who left the text. You probably need to use core location first to see where in the world the user is.

using flatMap to enumerate SpriteKit nodes

I tried using the following to get all nodes of a certain class:
let enemies = children.flatMap { $0 as? SomeEnemyClass }
The result is an empty array. I would like to understand why I'm not getting any results. enumerateChildNodesWithName works, but it doesn't list the parent/child relationships which I thought might shed some light. Thanks!
Here is the code: There is a simple class for the enemy, a level manager that replaces nodes in the scene editor with enemy objects, and GameScene.swift. Everything gets assigned to a world node.
Enemy Class
class Enemy: SKSpriteNode {
func spawn(parentNode:SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50)) {
parentNode.addChild(self)
self.size = size
self.position = position
self.name = "enemy"
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = PhysicsCategory.enemy.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.hero.rawValue
self.physicsBody?.collisionBitMask = 0
}
}
Level Manager
class LevelManager
{
let levelNames:[String] = ["Level1"]
var levels:[SKNode] = []
init()
{
for levelFileName in levelNames {
let level = SKNode()
if let levelScene = SKScene(fileNamed: levelFileName) {
for node in levelScene.children {
switch node.name! {
case "enemy":
let enemy = Enemy()
enemy.spawn(level, position: node.position)
default: print("Name error: \(node.name)")
}
}
levels.append(level)
}
}
func addLevelsToWorld(world: SKNode)
{
for index in 0...levels.count - 1 {
levels[index].position = CGPoint(x: -2000, y: index * 1000)
world.addChild(levels[index])
}
}
}
GameScene.swift - didMoveToView
world = SKNode()
world.name = "world"
addChild(world)
levelManager.addLevelsToWorld(self.world)
levelManager.levels[0].position = CGPoint(x:0, y: 0)
map and flatMap transform one array into another, what you are really trying to do is just filtering your child nodes for enemies. Use filter instead.
let enemines = children.filter { $0 is SomeEnemyClass }
That said, I don't see any reason why your sample code would not work.
Here is an example of this code working.
This code will only work if you have real classes derived from SKNode. It is much more common to use node name strings to identify types of nodes. This would work if you had a constant string name for your enemies.
let enemies = children.filter { $0.name == "SomeEnemy" }
You said
let enemies = children.flatMap { $0 as? SomeEnemyClass }
The result is an empty array. I would like to understand why I'm not getting any results. enumerateChildNodesWithName works
First of all this code
let enemies = children.flatMap { $0 as? Enemy }
is correct and it does returns an array of Enemy. Specifically all objects inside children that are Enemy or subclasses of Enemy.
Look
Why is not working for you?
You said that enumerateChildNodesWithName si working for you. This makes me think that you don't have Enemy objects into children. You just have SKNode(s) you assigned the name enemy.
What I learned was that the node tree is really a tree and you have to start looking on the right branch to get results. The answer to my question is:
let enemies = childNodeWithName("//level")!.children.flatMap { ($0 as? SomeEnemyClass) }
or
let enemies = childNodeWithName("//level")!.children.filter { $0 is SomeEnemyClass }
This is not an answer to my question, but it works too...
self.enumerateChildNodesWithName("//*") {
node, stop in
if (node.name == "enemy") {
print(node)
}
}
Thank you to everyone who offered their input. It got me pointed in the right direction.

Shift Swift Array

Array of Colors
let colorArray = [
UIColor.redColor(),
UIColor.orangeColor(),
UIColor.yellowColor(),
UIColor.greenColor(),
UIColor.blueColor()
]
The goal is to shift the array:
To start with a different color.
To preserve the circular order of colors.
Example #1
If we wanted to start with the orange color (the color at index 1 in the original array), the array would look like this:
let colorArray = [
UIColor.orangeColor(),
UIColor.yellowColor(),
UIColor.greenColor(),
UIColor.blueColor(),
UIColor.redColor(),
]
Example #2
If we wanted to start with the green color (the color at index 3 in the original array), the array would look like this:
let colorArray = [
UIColor.greenColor(),
UIColor.blueColor(),
UIColor.redColor(),
UIColor.orangeColor(),
UIColor.yellowColor()
]
Short & clear Swift 3 & 4 solution I came up with:
extension Array {
func shifted(by shiftAmount: Int) -> Array<Element> {
// 1
guard self.count > 0, (shiftAmount % self.count) != 0 else { return self }
// 2
let moduloShiftAmount = shiftAmount % self.count
let negativeShift = shiftAmount < 0
let effectiveShiftAmount = negativeShift ? moduloShiftAmount + self.count : moduloShiftAmount
// 3
let shift: (Int) -> Int = { return $0 + effectiveShiftAmount >= self.count ? $0 + effectiveShiftAmount - self.count : $0 + effectiveShiftAmount }
// 4
return self.enumerated().sorted(by: { shift($0.offset) < shift($1.offset) }).map { $0.element }
}
}
Explanation:
Arrays with no elements and shifts producing the identity of the
original array are returned immediately
To get the effective shift amount regardless of the amount passed with the function, we do some modulo calculation to get rid of shifts that would rotate the elements in the array more than once (e.g. in an Array with 5 Objects, a shift of +7 is the same as a shift of +2). Since we always want to shift to the right, in order to be done with one simple function instead of two, negative inputs have to be dealt with (e.g. in an Array with 5 Objects, a shift of -2 is the same as a shift of +3). Therefore we adjust the negative results of the modulo calculation by the length of the array.
Of course those 3 lines could be done in one, but I wanted to make this as readable as possible.
Now we prepare the actual shift by taking the index ($0) of element and returning the shifted index by adding the amount calculated in step 2. If the new index lands outside of the array length, it needs to be wrapped around to the front.
And finally we apply all the preparation to our array with some trickery: enumerated() gives us an array of tuples [(offset: Int, element: Int)], which is simply the original index of every element and the element itself. We then sort this enumerated array by the manipulated offset (aka the element's index) via applying the function from step 3. Lastly we get rid of the enumeration by mapping the sorted elements back into an array.
This extension works with arrays of any type. Examples:
let colorArray = [
UIColor.red,
UIColor.orange,
UIColor.yellow,
UIColor.green,
UIColor.blue
]
let shiftedColorArray = [
UIColor.green,
UIColor.blue,
UIColor.red,
UIColor.orange,
UIColor.yellow
]
colorArray.shifted(by: 2) == shiftedColorArray // returns true
[1,2,3,4,5,6,7].shifted(by: -23) // returns [3,4,5,6,7,1,2]
I know this might be late. But the easiest way to rotate or shift an array is
func shifter(shiftIndex: Int) {
let strArr: [String] = ["a","b","c","d"]
var newArr = strArr[shiftIndex..<strArr.count]
newArr += strArr[0..<shiftIndex]
println(newArr) }
shifter(2) //[c, d, a, b] you can modify the function to take array as input
You can extend Array to include a method to return an array containing the elements of the original array rotated by one element:
extension Array {
func rotate(shift:Int) -> Array {
var array = Array()
if (self.count > 0) {
array = self
if (shift > 0) {
for i in 1...shift {
array.append(array.removeAtIndex(0))
}
}
else if (shift < 0) {
for i in 1...abs(shift) {
array.insert(array.removeAtIndex(array.count-1),atIndex:0)
}
}
}
return array
}
}
To shifts the elements of an array once
let colorArray:[UIColor] = [
.redColor(),
.orangeColor(),
.yellowColor(),
.greenColor(),
.blueColor()
]
let z = colorArray.rotate(1)
// z is [.orangeColor(), .yellowColor(), .greenColor(), .blueColor(), .redColor()]
and twice
let z = colorArray.rotate(2)
// z is [.yellowColor(), .greenColor(), .blueColor(), .redColor(), .orangeColor()]
A variation of #zizutg's answer, that can shift in both directions (positive and negative)
extension Array {
public func shifted(by index: Int) -> Array {
let adjustedIndex = index %% self.count
return Array(self[adjustedIndex..<self.count] + self[0..<adjustedIndex])
}
}
// True modulo function https://stackoverflow.com/a/41180619/683763
infix operator %%
public func %%(_ dividend: Int, _ divisor: Int) -> Int {
precondition(divisor > 0, "modulus must be positive")
let reminder = dividend % divisor
return reminder >= 0 ? reminder : reminder + divisor
}
You can iterate by handling starting index.
func iterate<T>(array:Array<T>, start:Int, callback:(T) -> ()) {
let count = array.count
for index in start..<(start + count) {
callback(array[index % count])
}
}
If you want to start from index 3
iterate(colors, 3, { (color) -> () in println("color - \(color)")})

PhysicsBody is nil using SpriteKit with Swift

I have a SpriteKit game I am building and I am loading a level from a multidimensional array. The loadlevel function works the first time. It does fail if I do a println of the physicsBody above the physicsBody assignment (after the initialization of the physicBody). When I remove all the tiles with removeChildrenInArray the secondtime I run load level it throws an error saying fatal error: unexpectedly found nil while unwrapping an Optional and it points to line right below the println below. And the println indicates that it is the physicsBody that is nil. In my mind there is no reason a freshly initialize PhysicsBody should ever be nil. The println prints physicsBody nil. I have no idea why the physicsBody would be nil. I am just trying to reset the level by removing all block nodes and adding new ones in the original place according to the level map.
func loadLevel() {
var levels = Levels().data
var frameSize = view.frame.size
var thisLevel = levels[currentLevel]
println("running load level")
for (rowIndex,row) in enumerate(thisLevel) {
for (colIndex,col) in enumerate(row) {
if col == 4 {
continue
}
println("COL: \(col)")
var tile = SKSpriteNode(texture: SKTexture(imageNamed: "brick_\(tileMap[col])"))
tile.name = "tile_\(rowIndex)_\(colIndex)"
tile.position.y = frameSize.height - (tile.size.height * CGFloat(rowIndex)) - (tile.size.height/2)
tile.position.x = tile.size.width * CGFloat(colIndex) + (tile.size.width/2)
var physicsBody = SKPhysicsBody(rectangleOfSize: tile.size)
tile.physicsBody = physicsBody
tile.physicsBody.affectedByGravity = false
tile.physicsBody.categoryBitMask = ColliderType.Block.toRaw()
tile.physicsBody.contactTestBitMask = ColliderType.Ball.toRaw()
tile.physicsBody.collisionBitMask = ColliderType.Ball.toRaw()
scene.addChild(tile)
tileCount++
}
}
}
Here is my ColliderType
enum ColliderType:UInt32 {
case Paddle = 1
case Block = 2
case Wall = 3
case Ball = 4
}
This is my reset function contents:
func reset() {
tileCount = 0
var removeTiles = [SKSpriteNode]()
// remove all the tiles
for child in scene.children {
var a_tile = child as SKSpriteNode
if a_tile.name.hasPrefix("tile_") {
a_tile.removeFromParent()
a_tile.name = ""
removeTiles.append(a_tile)
}
}
removeTiles.removeAll(keepCapacity: false)
ball!.position = CGPoint(x: 200, y: 200)
ballVel = CGPoint(x: 0, y: -5)
currentLevel++
loadLevel()
lost = false
won = false
}
Here is my Level structs
struct Tile {
let map = ["blue","green","purple","red"]
}
struct Levels {
let data = [
[
[4,4,0,4,0,4,0,4,0,4,0,4,0,0,4,4],
[4,4,1,4,1,4,1,4,1,4,1,4,1,1,4,4],
[4,4,2,4,2,4,2,4,2,4,2,4,2,2,4,4],
[4,4,3,4,3,4,3,4,3,4,3,4,3,3,4,4]
],
[
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2],
[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]
]
]
}
If this is a bug in Swift I am trying to figure out a way around so I can just make this work.
Looks like SKPhysicsBody is instantiated with an empty size. Try to create the physics body object with an explicit size, like so:
var physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(100, 100))
Alternatively, you can set the size directly on the SKSpriteNode or use one of its constructors that take a CGSize construct like initWithTexture:color:size:

Resources