SceneKit: How to arrange buttons in ascending order using for in loop? - swift

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.

Related

enableAutomapping and SKTileMapNode: Create shapes with edges programmatically?

I am trying to create a flexible dialog box programmatically for dialog text in an RPG style game. I've designed a simple box graphic, divided it into all of the correct parts, and added it to my tile set as an 8-way Adjacency Group.
I can successfully add it to my camera node, but automapping does not work. All I get is a solid black box composed of the center tile. According to Apple's documentation, enableAutomapping "specifies whether the tile map uses automapping behavior like the scene editor," so I believe this setting should help me.
It only chooses the center tile, ignores all the edges/corners, and I get a solid black box. The behavior I am looking for is for SpriteKit to choose the appropriate edge and interior tile depending on the shape I am creating.
Here is what my code (Swift 4.2) currently looks like:
func createDialogBox() {
let dialogTileSet = SKTileSet(named: "Dialog")!
for tileGroup in dialogTileSet.tileGroups {
for tileRule in tileGroup.rules {
for tileDefinition in tileRule.tileDefinitions {
for texture in tileDefinition.textures {
texture.filteringMode = .nearest
}
}
}
}
let tileSize = CGSize(width: 32, height: 32)
let rows = 3, cols = 12
let dialogBox = SKTileMapNode(tileSet: dialogTileSet,
columns: cols,
rows: rows,
tileSize: tileSize)
dialogBox.enableAutomapping = true
for col in 0..<cols {
for row in 0..<rows {
dialogBox.setTileGroup(dialogTileSet.tileGroups.first, forColumn: col, row: row)
}
}
dialogBox.zPosition = 2
dialogBox.position = CGPoint(x: 48, y: -128)
self.camera?.addChild(dialogBox)
}
Advice for working with SpriteKit and SKTileMapNodes programmatically: learn how the editor works in Xcode before trying to do it in code. After asking my question, I decided to build it in the editor and I realized my conceptual model of tile map nodes was incorrect.
I was able to solve my issue by realizing that with automapping enabled, you only need to draw the center of the shape. The edges are filled in and the size of the TileMapNode must be able to accommodate the edge pieces. I had to make my TileMapNode slightly larger.
It works now. Here is the code (Swift 4.2):
func createDialogBox(string: String) {
let dialogTileSet = SKTileSet(named: "Dialog")!
for tileGroup in dialogTileSet.tileGroups {
for tileRule in tileGroup.rules {
for tileDefinition in tileRule.tileDefinitions {
for texture in tileDefinition.textures {
texture.filteringMode = .nearest
}
}
}
}
let tileSize = CGSize(width: 32, height: 32)
let rows = 5, cols = 14 // Increased the size of the tile map node.
let dialogBox = SKTileMapNode(tileSet: dialogTileSet,
columns: cols,
rows: rows,
tileSize: tileSize)
dialogBox.enableAutomapping = true
// Just draw the center line. Automapping will take care of the surrounding edges.
for col in 2...11 {
dialogBox.setTileGroup(dialogTileSet.tileGroups.first, forColumn: col, row: 2)
}
dialogBox.zPosition = 2
dialogBox.position = CGPoint(x: 48, y: -128)
self.camera?.addChild(dialogBox)
}

Swift CoreGraphics UIBezierPath will not fill interior correctly

I'm trying to draw countries and fill the interior a certain color. My data source is a TopoJSON file, which, in a nutshell, is made up of shapes that reference an array of arcs to create a shape. I convert this into an array of paths, which I then iterate through to draw the country. As you can see in the below screenshot, I'm drawing the correct lines of the outline of the country (Afghanistan).
However, when I try to use path.fill(), I end up getting the following. Note how the black lines are correct, but the colors go outside and inside haphazardly.
Code
var mapRegion = MapRegion()
var path = mapRegion.createPath()
var origin: CGPoint = .zero
geometry.paths
.enumerated()
.forEach { (geoIndex, shape) in
shape
.enumerated()
.forEach { (shapeIndex, coord) in
guard let coordPoint = coord.double else { return }
let values = coordinatesToGraphics(x: coordPoint.x, y: coordPoint.y)
let point = CGPoint(x: values.x, y: values.y)
if origin == .zero {
origin = point
}
// Shape is about to be closed
if shapeIndex != 0 && path.contains(point) {
// Close, save path (2)
path.addLine(to: origin)
// (3) path.close()
mapRegion.savePath()
// Add to map, reset process
canvas.layer.addSublayer(mapRegion)
mapRegions.append(mapRegion)
mapRegion = MapRegion()
path = mapRegion.createPath()
}
else {
if shapeIndex == 0 {
path.move(to: point)
} else {
path.addLine(to: point)
}
}
}
}
I've tried exhaustively messing with usesEvenOddFillRule (further reading), but nothing ever changes. I found that Comment (1) above helped resolve an issue where borders were being drawn that shouldn't be. The function savePath() at (2) runs the setStroke(), stroke(), setFill(), fill() functions.
Update: path.close() draws a line that closes the path at the bottom-left corner of the shape, instead of the top-left corner where it first starts drawing. That function closes the "most recently added subpath", but how are subpaths defined?
I can't say for sure whether the problem is my logic or some CoreGraphics trick. I have a collection of paths that I need to stitch together and treat as one, and I believe I'm doing that. I've looked at the data points, and the end of one arc to the beginning of the next are identical. I printed the path I stitch together and I basically move(to:) the same point, so there are no duplicates when I addLine(to:) Looking at the way the simulator is coloring the region, I first guessed maybe the individual arcs were being treated as shapes, but there are only 6 arcs in this example, and several more inside-outside color switches.
I'd really appreciate any help here!
Turns out that using path.move(to:) creates a subpath within the UIBezierPath(), which the fill algorithm seemingly treats as separate, multiple paths (source that led to discovery). The solution was to remove the extra, unnecessary move(to:) calls. Below is the working code and happy result! Thanks!
var mapRegion = MapRegion()
var path = mapRegion.createPath()
path.move(to: .zero)
var pointsDictionary: [String: Bool] = [:]
geometry.paths
.enumerated()
.forEach { (geoIndex, shape) in
shape
.enumerated()
.forEach { (shapeIndex, coord) in
guard let coordPoint = coord.double else { return }
let values = coordinatesToGraphics(x: coordPoint.x, y: coordPoint.y)
let point = CGPoint(x: values.x, y: values.y)
// Move to start
if path.currentPoint == .zero {
path.move(to: point)
}
if shapeIndex != 0 {
// Close shape
if pointsDictionary[point.debugDescription] ?? false {
// Close path, set colors, save
mapRegion.save(path)
regionDrawer.drawPath(of: mapRegion)
// Reset process
canvas.layer.addSublayer(mapRegion)
mapRegions.append(mapRegion)
mapRegion = MapRegion()
path = mapRegion.createPath()
pointsDictionary = [:]
}
// Add to shape
else {
path.addLine(to: point)
}
}
}
}

Waiting for SKAction completion inside a for loop

I have a for loop that runs 10 times, each time creating a node and adding it to the scene. However, I want there to be a delay in between each node being added (add a node, wait a second, add a node, wait a second, etc.)
However, after the first 1 second, all 10 nodes are added at the same time. How can I achieve this desired effect of waiting a second in between each node being added?
Here is my code:
EDIT:
func createText(correct: Bool) {
let text = SKNode()
var line: String!
addChild(text)
if correct {
line = (GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(happyLines) as! [String])[0]
} else {
line = (GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(sadLines) as! [String])[0]
}
var xPos = self.frame.midX + 300
var yPos = self.frame.midY
var spaces = 1
// For each character in sentence, create a node of it
for character in line.characters {
runAction(SKAction.waitForDuration(1.0)) {
if spaces == 4 {
spaces = 0
print("space")
xPos = self.frame.midX + 300
yPos -= 30
}
xPos += 10
if character != " " {
let letter = SKLabelNode(fontNamed: GameScene.fontName)
letter.fontSize = 14 * GameScene.fontScale
letter.position = CGPoint(x: xPos, y: yPos)
letter.text = String(character)
text.addChild(letter)
} else {
spaces += 1
xPos += 10
}
}
}
runAction(SKAction.waitForDuration(2.0)) {
text.removeAllChildren()
text.removeFromParent()
}
}
You can achieve this using actions.
First, create actions for the delay and adding the node to the scene.
let waitAction = SKAction.wait(forDuration: 1)
let addNodeAction = SKAction.run {
let node = SKSpriteNode(imageNamed: "example")
self.addChild(node)
}
Next, create a sequence of actions so that the delay always occurs before the node is added to the scene.
let sequenceAction = SKAction.sequence([waitAction, addNodeAction])
Next, create an action that repeats the sequence 10 times.
let repeatAction = SKAction.repeat(sequenceAction, count: 10)
Finally, run this action and watch the nodes appear!
run(repeatAction)
EDIT:
To solve your second question in the comments about needing access to the current character (making each action where the node is added slightly different), loop through your characters to build a sequence of actions, and then run that action.
var actions = [SKAction]()
let waitAction = SKAction.wait(forDuration: 1)
for character in line.characters {
let addNodeAction = SKAction.run {
let node = SKSpriteNode(imageNamed: "example")
// use the character variable here
self.addChild(node)
}
actions.append(waitAction)
actions.append(addNodeAction)
}
let sequenceAction = SKAction.sequence(actions)
run(sequenceAction)
How about this? (not tested :)
for (i, character) in line.characters.enumerate() {
runAction(SKAction.waitForDuration(Double(i) * 1.0)) {
// rest of your for loop
So you enumerate through the characters, where i is the i-th character, starting at 0. With each character, you just multiply its index position in the string with the 1 second interval to give you the wait duration.

Choosing to spawn different SKSpriteNodes

I am making a game and I have objects which fall from the top of the screen to the bottom. I want to spawn choose between the objects and drop one of them. I currently the drop all at the same time.
func ShapePicker() -> SKSpriteNode{
let shapeArray = [purpleOctagon, coin, greenTriangle, orangeHexagon]
let MaxValue = self.size.width / 2 - 200
let MinValue = self.size.width / 3 * 0.95
let rangeMax = UInt32(MaxValue)
let rangeMin = UInt32(MinValue)
purpleOctagon.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(purpleOctagon)
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(greenTriangle)
coin.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(coin)
return shapeArray[Int(arc4random_uniform(UInt32(shapeArray.count)))]
}
I would like the program to randomly .addChild because right now it just puts them on the screen.
Your code implies that you want all of them to be on the screen, and then one randomly drops... So you do want to continue to .addChild. What you want, is for them to NOT drop all at once.
So, you need to change the .physicsBody.pinned to true to keep them at the top of the screen.
Then, in your update() you can check for how much time has passed, etc, and after a certain # of seconds you can do an arc4random_uniform and use that result to change the .pinned property of one of the nodes (thus causing that one and that one only to fall).
So, if the coin is 0, triangle is 1, and octagon is 2, then, in your .update keep track of the time elapsed and after say 3 seconds, do a random check 0-2, and that check will perform:
switch result {
case 0: // coin
coin.physicsBody?.pinned = false // makes it drop
case 1: // triangle
...
Just make sure that your nodes are in the proper scope so that you can do the logic in update()
If I read your Q wrong, and you only want to spawn and then drop just one, then you would still need the above switch statement, but instead of changing physicsBody you would do .addChild instead.
so inside of your func would be more like:
// This can be global or inside of your GameScene:
var myGlobalCurrentTime: CFTimeInterval
override func update(currentTime: CFTimeInterval) {
myGlobalCurrentTime = myTimerUpdateTime(currentTime)
func myDropFunc() {
... // Initialize your nodes
let result = myRandomNumber(3)
switch result {
case 0:
coin.position
= CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax),
y: self.size.height)
self.addChild(coin)
case 1:
...
}
}
// Execute the func:
myDropFunc()
}
I'm still a bit confused by your code and question, so please clarify in the comments so I can update this answer if needed.
You can move the addChild call out of this function, it returns a SKSpriteNode, which you can then add.
let sprite = ShapePicker()
addChild(sprite)
Or, just add the one randomly chosen and return it. You don't need to addChild nodes that you're not planning on using

Very slow minesweeper recursive algorithm in Swift

I'm working with Swift 3 and Xcode.
I'm creating an iOS game that is basically a Minesweeper, but there are no squares but hexagons, so each hexagon can have up to 6 mines in their surrounding.
I created a recursive algorithm, so that when the player touches an hexagon, if it's not a bomb, then it call a recursive function called "reveal" which :
- if one ore more mine in the surrounding and the touched hexagon is still hidden (by hidden I mean we don't know if it's a mine or not), reveal the hexagon & set the number of surrounding mine's label, and stop the function
- if no mine in the surrounding, for each nearby hexagon that is hidden, call the reveal function.
So here's what my code looks like :
class Hexagon: SKShapeNode
{
var mine: Bool
var hide: Bool
var proximityMines: Int
init(mine: Bool = false, proximityMines: Int = 0, hide: Bool = true)
{
self.mine = mine // if it's a mine
self.proximityMines = proximityMines // number of surrounding mines (that I calculated using a function after I generated the level)
self.hide = hide // if the hexagon is still hidden
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
func reveal(hexagon: Hexagon)
{
if hexagon.proximityMines == 0 && hexagon.hide == true // if there are no mines in the surrounding
{
hexagon.hide = false // we update the value of this hexagon
setNumberLabel(hexagon: hexagon) // we set the .proximityMines number as a label (here 0)
for proxyHexagon in proximityHexagons(hexagon: hexagon) // for each surrounding hexagon ...
{
if proxyHexagon.hide == true // ... that is still hidden
{
reveal(hexagon: proxyHexagon) // we call this function again
}
}
}
else if hexagon.proximityMines != 0 && hexagon.hide == true // else if there are mines in the surrounding
{
hexagon.hide = false // update
setNumberLabel(hexagon: hexagon) // set label
}
}
the proximityHexagons(hexagon: Hexagon) function returns an array containing all surrounding hexagons of a given hexagon.
So I really checked my algorithm again and again, and I really think it's the good one.
But the fact is that when I create a level with 0 or a really low amount of mine, and I click on an hexagon, it takes something like 2 seconds for the recursive function to update all the empty hexagons.
My map contains more or less 260 hexagons, and I debugged the number of calls of reveal() and it's about the same amount.
So why is it taking so much time ? I don't think the iPhone 6 can't handle this amount of operations ! I tried it on my iPhone, not an emulator.
Do you have any idea ?
Ok I've been thinking about this because it sounds like a fun problem. I didn't look up any minesweeper solvers, so I might be way out in left field, but here is how I would approach your problem.
First you have to give every mine an index, and you need to know the pattern of that index such that you can do a little math to get the surrounding indices of every mine. If the rows have identical numbers, and the numbering is sequential across rows, then the surrounding indices are:
[index - 1, index + 1,
index - rowCount, index - rowCount - 1,
index + rowCount, index + rowCount + 1]
Then I would make a class that holds a set of all the safe spots on the map that you had when you built the puzzle. I'll call it SafetyManager.
class SafetyManager {
var safeSpots: Set<Int> = all your safe spots
func indices(surrounding index: Int) -> Set<Int> {
return [index - 1, index + 1,
index - rowCount, index - rowCount - 1,
index + rowCount, index + rowCount + 1]
}
func safePlaces(around hexagon: Int) -> Set<Int> {
let allIndices = indices(surrounding: hexagon)
let safe = allIndices.intersection(safeSpots)
safeSpots.subtract(safe)
return safe
}
}
It's got two important functions, one calculates the surrounding indices, the second filters the safe spots. I'm using sets so we can quickly determine the intersection between the safe spots and the surrounding spots.
Next we need a class that would be instantiated when a move is made so we can do the recursion. Lets call it CheckManager.
class CheckManager {
var checked : [Int]
var unchecked : Set<Int>
init(firstHex: Hexagon, surroundingSafeSpots: Set<Int>) {
checked = [firstHex.index]
unchecked = surroundingSafeSpots
}
func nextUnchecked() -> Int? {
guard !unchecked.isEmpty else { return nil }
let next = unchecked.removeFirst()
checked += [next]
return next
}
func pleaseTake(these indices: Set<Int>) {
unchecked.formUnion(indices)
}
}
You initialize it with your first hexagon, or hex index, and the surrounding safespots that the safety manager would give you, if you get no safe spots from the SafetyManager, no need to instantiate.
It keeps a set of checked spots and unchecked spots. Two important functions, the second you use to give it newly acquired safe spots from the safety manager to be added to the unchecked list. The other returns an optional Int? of the next safe spot to check the surroundings of.
Then to do the recursion, something like this..
func check(spot: Hexagon) {
let safe = safetyMan.safePlaces(around: spot.index)
guard safe.count > 0 else { .. }
let checkMan = CheckManager(firstHex: spot, surroundingSafeSpots: safe)
while let i = checkMan.nextUnchecked() {
let safeSpots = safetyMan.safePlaces(around: i)
checkMan.pleaseTake(these: safeSpots)
} // goes until unchecked is empty
for spot in checkMan.checked {
// get the hex and reveal
}
}
You could keep a dictionary of [Int: Hexagon] to quickly grab the hex for a given index. I haven't tested this so I'm not sure if it works well, or at all or has some improper syntax. It would also probably be a lot faster to use multithreading. Fun problem. Good luck.
Okay, I managed to solve my problem.
The problem was the proximityHexagons function that was taking a lot of time. In fact, each time I called this function, he made 6 complex calculations and added the surrounding hexagons in an array, so it was taking a lot of time.
Here's what it looked like :
func proximityHexagons(hexagon: Hexagon) -> Array<Hexagon>
{
var array = [Hexagon]()
var nodeArray = [[Hexagon]]()
nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x, y: hexagon.position.y + hexagon.height)).filter({$0 is Hexagon}) as! [Hexagon])
nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x + hexagon.width * 3/4, y: hexagon.position.y + hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x + hexagon.width * 3/4, y: hexagon.position.y - hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x, y: hexagon.position.y - hexagon.height)).filter({$0 is Hexagon}) as! [Hexagon])
nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x - hexagon.width * 3/4, y: hexagon.position.y - hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
nodeArray.append(nodes(at: CGPoint(x: hexagon.position.x - hexagon.width * 3/4, y: hexagon.position.y + hexagon.height / 2)).filter({$0 is Hexagon}) as! [Hexagon])
// first, for each 6 directions, I'm adding in an array every nodes that are Hexagon, and then adding all of theses arrays in another bigger one
for node in nodeArray // for each hexagon array in the big array
{
if node.count != 0 // if there is an hexagon
{
array.append(node.first!) // we set the hexagon in the final array
}
}
return array // we return the array containing all surrounding hexagons
}
I prefer checking the surrounding hexagons with the nodes(at: Point) function because my levels aren't always regular maps, they can have a weird positioning and twiz_'s func indices(surrounding index: Int) function could not work.
So I kept my function, but I call it once at the beginning of the level and store in a new variable in my hexagon class all the surrounding hexagons of each hexagon:
class Hexagon: SKShapeNode
{
var mine: Bool
var hide: Bool
var proximityMines: Int
var proxyHexagons: [Hexagon] // here
init(mine: Bool = false, proximityMines: Int = 0, hide: Bool = true, proxyHexagons: [Hexagon] =
[Hexagon]())
{
self.mine = mine
self.proximityMines = proximityMines
self.hide = hide
self.proxyHexagons = proxyHexagons
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And then, in the reveal function, instead of calling the proximityHexagons function, I use the .proxyHexagons array of the hexagon, like this :
func reveal(hexagon: Hexagon)
{
if hexagon.proximityMines == 0 && hexagon.hide == true
{
hexagon.hide = false
setNumberLabel(hexagon: hexagon)
for proxyHexagon in hexagon.proxyHexagons // here
{
if proxyHexagon.hide == true
{
reveal(hexagon: proxyHexagon)
}
}
}
else if hexagon.proximityMines != 0 && hexagon.hide == true
{
hexagon.hide = false
setNumberLabel(hexagon: hexagon)
}
}
And now my function is way faster, I manage to reveal all 260 hexagons in 0.001 secs instead of the old 2.81 secs.