OK,
Having a mental block here... I got a range of vectors
node 0 SCNVector3(x: 8.208711e-08, y: 0.0, z: -1.9389672)
node 1 SCNVector3(x: -0.93896717, y: 0.0, z: -1.0000001)
node 2 SCNVector3(x: -8.208711e-08, y: 0.0, z: -0.06103283)
node 3 SCNVector3(x: 0.93896717, y: 0.0, z: -0.99999994)
node 4 SCNVector3(x: 0.0, y: 0.93896717, z: -1.0)
node 5 SCNVector3(x: 0.0, y: -0.93896717, z: -1.0)
And I want to select node2 since it has the smallest negative number. It's a stupid question, I know; I am sorry.
struct Sats {
var theNode = SCNNode()
var theIndex:Int = 7
}
if cubeNode != nil {
cubeNode.enumerateChildNodes { (node, stop) in
let search = /face(\d+)/
if let result = try? search.wholeMatch(in: node.name!) {
print("node \(result.1) \(node.worldPosition) ")
if node.worldPosition.z < -0.05 {
print("eureka \(theOne.theIndex)")
theOne.theNode = node
theOne.theIndex = Int(result.1)!
}
}
}
}
If all your objects are in an array, you can use min(by:). Like a lot of Swift's sorting methods it takes a block with two values, and you return TRUE if the second one is larger than the first:
myVectors.min { $0.x < $1.x }
The returned value is an optional – it's either the smallest value in the array, or nil if the array was empty.
Related
The task is to add 10 buttons (0...9) with labels using for in loop.
I created buttons based on class ButtonPrototype. I assigned label to each button via counter inside for in loop.
It works, but there is incorrect labels order:
I need another order:
How can I implement correct order?
Code:
func createButtons() {
for y in 0...1 {
for x in 0...4 {
counterForLoop += 1
self.button = ButtonPrototype(pos: .init( CGFloat(x)/7, CGFloat(y)/7, 0 ), imageName: "\(counterForLoop)")
parentNode.addChildNode(button)
parentNode.position = SCNVector3(x: 100,
y: 100,
z: 100)
}
}
}
The following approach perfectly makes the trick:
for y in 0...1 {
for x in 0...4 {
let textNode = SCNNode()
let ascendingOrder: String = "\(((x+1)+(y*5)) % 10)"
let geo = SCNText(string: ascendingOrder, extrusionDepth: 0.5)
geo.flatness = 0.04
geo.firstMaterial?.diffuse.contents = UIImage(named: ascendingOrder)
textNode.geometry = geo
textNode.position = SCNVector3(x*10, -y*10, 0)
sceneView.scene?.rootNode.addChildNode(textNode)
print(ascendingOrder)
}
}
You have at least two problems with your code. Your smallest button label is in the lower left and you want it to be in the lower right, and your labels go 0-9, and you want them to go from 1 to 10 (but display 10 as “0”).
To reverse the x ordering, change X to 10-x in your creation of a position, and change your imageName to “((counterForLoop+1)%10)”:
self.button = ButtonPrototype(
pos: .init(
CGFloat(10-x)/7,
CGFloat(y)/7,
0),
imageName: "\((counterForLoop+1)%10)")
By the way, you should add a SceneKit tag to your question. That seems more important than either the label tag or the loops tag.
I have this code:
var triangles: [[[CAShapeLayer]]] = Array(repeating: Array(repeating: Array(repeating: 0, count: 2), count: 15), count: 15);
But it generates an "Cannot convert value of type..." compilation error.
How can I solve that? I want to access my CAShapeLayers like this:
triangles[1][2][1].fillColor = UIColor(red: 40/255, green: 73/255, blue: 80/255, alpha: 1).cgColor;
Use optionals.
var triangles: [[[CAShapeLayer?]]] = Array(repeating: Array(repeating: Array(repeating: nil, count: 2), count: 15), count: 15)
Now there's a nil instead of a 0, which is what I think you were hinting at. But every triangles[x][y][z] is now an optional type you'll have to safely unwrap.
So now you have to do something like triangles[x][y][z] = CAShapeLayer() before you do anything to that object.
Edit for correction. Thanks #OOPer
I thought about it some more, and realized I didn't really answer your question.
So you may use for loops to initialize everything (which would be a pain), or you could do something like this every time you access an index:
if triangles[x][y][z] == nil
{
triangles[x][y][z] = CAShapeLayer()
}
let bloop = triangles[x][y][z]!
bloop.fillColor = UIColor(...
Then you could pull it out into an outside method so it becomes a 1 liner. Like:
func tri(at x: Int, _ y: Int, _ z: Int) -> CAShapeLayer
{
if triangles[x][y][z] == nil
{
triangles[x][y][z] = CAShapeLayer()
}
return triangles[x][y][z]!
}
Then when using it:
tri(at: 1, 2, 1).fillColor = ...
Of course, you should pull triangles out and make it a property of the class you're in, or you can include it in the parameter list of that 1 liner method.
All that nesting makes your code hard to understand, and Array(repeating:count:) can't do what you want anyway.
func newGrid() -> [[[CAShapeLayer]]] {
func newStack() -> [CAShapeLayer] {
return (0 ..< 2).map({ _ in CAShapeLayer() })
}
func newRow() -> [[CAShapeLayer]] {
return (0 ..< 15).map({ _ in newStack() })
}
return (0 ..< 15).map({ _ in newRow() })
}
var triangles = newGrid()
You cannot use "0" as the repeating value, it will be inferred to be type [[[Int]]]. Just replace "0" with "CAShapeLayer()"
I am trying to call a function via an SKAction that takes 1 input parameter.
When I declare the SKAction, I get an error: "Cannot convert value of type '()' to expected argument type '() -> Void'
self.run(SKAction.repeat(SKAction.sequence([SKAction.wait(forDuration: 1), SKAction.run(self.decreaseHealth(by: 5.0))]), count: 10))
func decreaseHealth(by amount: Double){
print(health)
health -= amount
print(health)
let percentageToDecrease = amount / totalHealth
let resizeAction = SKAction.resize(toHeight: CGFloat(600*(1-percentageToDecrease)), duration: 1)
let redAmount: CGFloat = CGFloat(1.0 - health / totalHealth)
let greenAmount: CGFloat = CGFloat(health / totalHealth)
let recolorAction = SKAction.colorize(with: UIColor(red: redAmount, green: greenAmount, blue: CGFloat(0.0), alpha: CGFloat(1.0)), colorBlendFactor: 0, duration: 1)
healthBar.run(SKAction.group([resizeAction, recolorAction]))
}
usually when you get that error, it just means you need to enclose the function inside of curly brackets. so:
self.run(SKAction.repeat(SKAction.sequence([SKAction.wait(forDuration: 1), SKAction.run( { self.decreaseHealth(by: 5.0) } )]), count: 10))
If you had split this into multiple lines, it would have been easier to spot.
this is because decreaseHealth is of type () but { decreasHealth } if of type () -> () or () -> Void
(Closures are function type in Swift, thus have parameter / arguments and returns)
I haven't used SpriteKit for 3 months. But something like the following should work.
func repeatMe() {
let waitAction = SKAction.wait(forDuration: 1.0)
let completionAction = SKAction.run {
self.decreaseHealth(by: 5.0)
}
let seqAction = SKAction.sequence([waitAction, completionAction])
let repeatAction = SKAction.repeat(seqAction, count: 10)
}
let bag = SKSpriteNode(imageNamed: "Content/bag")
holder.addChild(bag)
bag.name = "bag"
bag.zPosition = 5
bag.position = CGPoint(x: -3, y: (holder.size.height / 2) + (bag.size.height / 2) - 6)
bag.setScale(0.5)
print(bag.parent)
bag.runAction(SKAction.playSoundFileNamed("Content/wrap.wav", waitForCompletion: true))
bag.runAction(SKAction.scale(to: 1.0, duration: 1.0, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0)) {
bag.physicsBody = SKPhysicsBody(rectangleOfSize: bag.size)
bag.physicsBody!.dynamic = false
bag.physicsBody!.categoryBitMask = CollisionTypes.Bag.rawValue
bag.physicsBody!.collisionBitMask = CollisionTypes.Ingredients.rawValue
}
print(bag.parent)
The above code gets executed whenever the user taps the "holder" which is just a sprite already on the scene. It should create a new "bag" sprite, add it to the holder, and scale it up from 0.5 to 1.0 after running a sound effect.
The problem is that neither the sound effect or the scale action are being run. I searched this problem and it seems like the case is that the node hasn't been added but that's not the case here. I print the bag's parent before and after the actions and neither is nil. Also, the bag IS being added because I see it on the screen at 0.5 scale. How are neither of these actions being ran?
I have seen a number of posts on looping through a dictionary but they are a different and simpler than what I want to achieve here, in my opinion. I have the following arrays:
pt1.array = [0:[pt2.point],
1:[pt8.point, pt12.point, pt4.point],
2:[pt20.point, pt14.point, pt3.point],
3:[pt7.point, pt8.point, pt9.point]]
pt2.array = [0:[pt5.point],
1:[pt8.point, pt11.point, pt1.point],
2:[pt10.point, pt9.point, pt3.point],
3:[pt6.point, pt1.point, pt4.point]]
pt3.array = [0:[pt13.point],
1:[pt1.point, pt15.point, pt7.point],
2:[pt19.point, pt14.point, pt2.point],
3:[pt10.point, pt11.point, pt12.point]]
pt4.array = [0:[pt8.point],
1:[pt9.point, pt11.point, pt13.point],
2:[pt14.point, pt15.point, pt6.point],
3:[pt3.point, pt2.point, pt1.point]]
pt5.array = [0:[pt18.point],
1:[pt8.point, pt6.point, pt1.point],
2:[pt3.point, pt17.point, pt4.point],
3:[pt16.point, pt15.point, pt14.point]]
allPoints = [pt1, pt2, pt3, pt4, pt5]
How can I iterate to remove pt3.point which is a CGPoint from all the Int-[CGPoint] dictionaries in the allPoints array?
I tried the following:
for pt in allPoints {
for ptArrIndex in pt.arrays {
for (key, value) in ptArrIndex {
//remove point from dict here
}
}
}
but I got the error:
Type '(key: Int, value:[CGPoint])' (aka'(key: Int, value: Array<CGPoint>)') does not conform to protocol 'Sequence'
at the line:
for (key, value) in ptArrIndex {
EDIT
The struct that creates each of the points is below:
struct Point {
var point: CGPoint
var arrays: [Int: [CGPoint]]
}
UPDATED QUESTION
Based on Rob Napier’s suggestion I’ve updated the question:
I have a struct below:
struct Location {
var point: CGPoint
var changedLoc: [Int: [CGPoint]]
}
where point represents a CGPoint for Location and changedLoc represents all the possible groups of CGPoints Location can change to. I calculate this randomly.
I have the following Locations
var location1 = Location(point: initialBallPosition, changedLoc: [0: [CGPoint(x: 421.0, y: 43.0), CGPoint(x: 202.0, y: 69.0)], 1: [CGPoint(x: 121.0, y: 198.0)]])
var location2 = Location(point: initialBallPosition, changedLoc: [0: [CGPoint(x: 421.0, y: 43.0), CGPoint(x: 123.0, y: 254.0)], 1: [CGPoint(x: 90.0, y: 104.0)]])
var allLocations = [location1, location2]
From allLocations how can I remove the point CGPoint(x: 421.0, y: 43.0) which is in both location1 and location2 changedLoc?
func locationsRemoving(changedLoc removePoint: CGPoint, from locations: [Location]) -> [Location] {
return locations.map { location in
var changedLoc: [Int: [CGPoint]] = [:]
for (key, values) in location.changedLoc {
changedLoc[key] = values.filter{ $0 != removePoint }
}
return Location(point: location.point, changedLoc: changedLoc)
}
}
let removePoint = CGPoint(x: 421.0, y: 43.0)
print(allLocations)
print(locationsRemoving(changedLoc: removePoint, from: allLocations))
The confusing thing you have is that the property named array in the ptN instances is actually a Dictionary<Int, [CGPoint]>
So, for the structure you currently have, you'll need to amend your code to match:
for pt in allPoints {
for (key, ptArray) in pt.array {
for elem in ptArray {
//remove point from dict here
}
}
}