Swift / SpriteKit: evenly distribute SKShapeNodes horizontally and vertically into a grid - ios

I want to equally distribute an n amount of square SKShapeNodes over the screen to create a square grid. I tried searching for other examples of this, but I cannot find the solution I'm looking for as most solutions only focus on only distributing views horizontally.
I'm creating the squares in a for loop, however I can only get them to move on either the x or the y axes, not both. The below example gives me the best result, but as expected from the code only the first two squares are visible, the third one is created off screen:
amountOfTiles is a CGFloat set to 4
tile is an SKShapeNode
func addTiles() {
let screenWidth = self.view?.frame.size.width
tileWidth = screenWidth! / (amountOfTiles / 2)
tileHeight = tileWidth
tileX = 0
tileY = 0
tileRect = CGRect(x: tileX, y: tileY, width: tileWidth, height: tileHeight)
for _ in 0...Int(amountOfTiles) {
tile = SKShapeNode(rect: tileRect)
tile.position = CGPoint(x: tileX, y: tileY)
tile.fillColor = .red
addChild(tile)
tileX += tileWidth
tileY += tileHeight
}
}
My guess is that I should not update the Y and X positions of the squares at the same time, but I'm not sure how to solve this.

Fixed it myself. I removed amountOfTiles and added two separate CGFloats for x and y, which I update in a separate loop:
func addTiles() {
let xAmount:CGFloat = 4
let yAmount:CGFloat = 4
let screenWidth = self.view?.frame.size.width
tileWidth = screenWidth! / (xAmount)
tileHeight = tileWidth
tileY = 0
tileRect = CGRect(x: tileX, y: tileY, width: tileWidth, height: tileHeight)
for _ in 0..<Int(xAmount) {
tileX = 0
for _ in 0..<Int(yAmount) {
tile = SKShapeNode(rect: tileRect)
tile.fillColor = .red
addChild(tile)
tile.position = CGPoint(x: tileX, y: tileY)
tileX += tileWidth
}
tileY += tileHeight
}
}

Related

SpriteKit SKTileMapNode same physics body for multiple tiles

The following code is correctly adding physicsBodies for every "wall" tile in my SKTileMapNode. However, I would like to group them all under the same node with one physicsBody for the entire group. How do I achieve that?
private func setupTileMap() {
let tileSize = self.tileMap.tileSize
let halfWidth = CGFloat(self.tileMap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(self.tileMap.numberOfRows) / 2.0 * tileSize.height
for column in 0..<self.tileMap.numberOfColumns {
for row in 0..<self.tileMap.numberOfRows {
let tileDefinition = self.tileMap.tileDefinition(atColumn: column, row: row)
let tileX = CGFloat(column) * tileSize.width - halfWidth
let tileY = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.strokeColor = .clear
tileNode.position = CGPoint(x: tileX, y: tileY)
let physicsBodyCenter = CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0)
let physicsBody = SKPhysicsBody(rectangleOf: tileSize, center: physicsBodyCenter)
physicsBody.isDynamic = false
tileNode.physicsBody = physicsBody
if tileDefinition?.name == Nodes.wall.rawValue {
physicsBody.categoryBitMask = CategoryMask.wall.rawValue
}
self.tileMap.addChild(tileNode)
}
}
}

Trying to move SKShapeNodes one at a time in a loop

I want to move the nodes from an offscreen position and line them up across the X-axis spaced out. The nodes have been created and stored in an array. I have the following code to do it. However, all of the nodes move at the same time. I want them to move successively when the prior node is in its new position. I have tried altering the duration of the wait action, but it doesn't work.
Thanks
var shapeNodes: [SKShapeNode] = [SKShapeNode]()
let w = (size.width + size.height) * 0.05
for _ in 1...5 {
let s = SKShapeNode(rectOf: CGSize(width: w, height: w), cornerRadius: w * 0.3)
s.position = CGPoint(x: frame.midX, y: frame.maxY + 75)
s.zPosition = 3
s.fillColor = UIColor.cyan
shapeNodes.append(s)
addChild(s)
}
var posX: CGFloat = 0.0
for n in shapeNodes {
let m = SKAction.moveBy(x: posX, y: -300, duration: 2.0)
m.timingMode = .easeIn
let w = SKAction.wait(forDuration: 2.0)
let seq = SKAction.sequence([ m, w ])
n.run(seq)
posX += 125
}
Give each node a wait action with a duration equal to the animation time of all previous nodes. The logic is very similar to your logic with the posX variable.
var posX: CGFloat = 0.0
var waitDuration: CGFloat = 0
for n in shapeNodes {
let duration = 2
let m = SKAction.moveBy(x: posX, y: -300, duration: duration)
m.timingMode = .easeIn
let w = SKAction.wait(forDuration: waitDuration)
let seq = SKAction.sequence([w, m])
n.run(seq)
posX += 125
waitDuration += duration
}
In the example above, I created another variable called waitDuration, which keeps track of the total time the node should wait before animating.
The duration of the wait action is set to the waitDuration, and at the end of the loop, I increase the waitDuration by the duration of the move action.

Xcode - Swift SpriteKit: SKTileMapNode with a "physics gap"

I am playing around with SpriteKit and the TileMapNode and i've got an annoying problem.
That's how it should look like.
That's how its actually looking in the simulator/device.
The white spaces are terrible and i have no idea, how to get rid of them.
My Tiles are about 70x70 in the "Sprite Atlas - Part" of the assets, i've configured my tilemapnode with a scale of 0.5 and tile size of 70x70.
While testing some cases, i figured out that this part of code triggers the error, but i have no idea, what could be wrong. Changing the SKPhysicsBody size to a smaller one, did not helped.
guard let tilemap = childNode(withName: "LevelGround") as? SKTileMapNode else { return }
let tileSize = tilemap.tileSize
let halfWidth = CGFloat(tilemap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(tilemap.numberOfRows) / 2.0 * tileSize.height
for row in 0..<tilemap.numberOfRows {
for col in 0..<tilemap.numberOfColumns {
if tilemap.tileDefinition(atColumn: col, row: row) != nil {
let x = CGFloat(col) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 70, height: 70), center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.collisionBitMask = 2
tileNode.physicsBody?.categoryBitMask = 1
tileNode.physicsBody?.contactTestBitMask = 2 | 1
tileNode.name = "Ground"
tilemap.addChild(tileNode)
}
}
}
tileNode.strokeColor = .clear
solved the problem
Update:
problem not solved... just moved :/
When checking, if ground & player are in contact, every new tile the status is switching between "contact" and "no contact".
When using a cube instead a circle, the cube begins to rotate. It seems, the corner of the cube get's stuck at the minimal space between the tiles.

how to make a node stack on top of another node and follow the object?

I'm trying to create a game in which i have a object 1 rotatiing in a circle and another object appears and places itself ontop of object 1. currently the object just rotates around object 1 without stacking ontop of it. how do i get the object to stack itself on top and then follow it's orbit? here's my code now.
let player = SKSpriteNode(imageNamed: "Light")
override func didMoveToView(view: SKView) {
// 2
backgroundColor = SKColor.whiteColor()
// 3
player.position = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
// 4
player.size = CGSize(width:70, height:60 )
addChild(player)
let dx = player.position.x - self.frame.width / 2
let dy = player.position.y - self.frame.height / 2
let rad = atan2(dy, dx)
circle = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2), radius: 120, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(circle.CGPath, asOffset: false, orientToPath: true, speed: 100)
player.runAction(SKAction.repeatActionForever(follow))
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "plate")
// Determine where to spawn the monster along the Y axis
let actualY = random(min: monster.size.height/1, max: size.height - monster.size.height/1)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = CGPoint(x: size.width * 0.5 + monster.size.width/2, y: actualY)
// Add the monster to the scene
addChild(monster)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(3.0))
// Create the actions
let actionMove = SKAction.moveTo(CGPoint(x: -monster.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let follow = SKAction.followPath(circle.CGPath, asOffset: false, orientToPath:true, speed: 100)
monster.runAction(SKAction.repeatActionForever(follow))
}
Your verbal description appears to have something like the following in mind (you can copy and paste this into an iOS playground):
import UIKit
import SpriteKit
import XCPlayground
let scene = SKScene(size: CGSize(width: 400, height: 400))
let view = SKView(frame: CGRect(origin: .zero, size: scene.size))
view.presentScene(scene)
XCPlaygroundPage.currentPage.liveView = view
scene.backgroundColor = .darkGrayColor()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let player = SKShapeNode(rectOfSize: CGSize(width: 70, height: 60), cornerRadius: 5)
let radius: CGFloat = 120
let circle = CGPathCreateWithEllipseInRect(CGRect(x: -radius, y: -radius, width: radius * 2, height: radius * 2), nil)
let followCircle = SKAction.followPath(circle, asOffset: false, orientToPath: true, speed: 100)
player.runAction(.repeatActionForever(followCircle))
let monsterSize = CGSize(width: 35, height: 30)
let monster = SKShapeNode(rectOfSize: monsterSize, cornerRadius: 4)
monster.fillColor = .redColor()
monster.runAction(.repeatActionForever(followCircle))
scene.addChild(player)
scene.addChild(monster)
Where you make use of a random(min:max:) function that is probably implemented along the lines of:
public func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return min + (CGFloat(arc4random()) / CGFloat(UInt32.max)) * (max - min)
}
But you also have the code that seems to be saying:
let y = random(
min: scene.size.height / -2 + monsterSize.height / 2,
max: scene.size.height / 2 - monsterSize.height / 2
)
let xFrom = scene.size.width / 2
let xTo = scene.size.width / -2
monster.position = CGPoint(x: xFrom, y: y)
monster.runAction(
.moveToX(xTo, duration: NSTimeInterval(random(min: 2, max: 3)))
)
But this is unused. Instead the monster is set to follow the same circle path as the player. And so I am not sure really what exactly you are trying to achieve. (By the way, the simplest way of getting the monster "stack on top" of the player would be to make it the child node of the player...)
Hope some of this guesswork helps you in some way (to refine your question and code sample if nothing else).

How to draw 2D matrix of squares on iOS?

What is the best way how to draw a matrix of squares on iOS using SpriteKit? I am complete iOS beginner and I'm not sure what is the correct approach.
I was thinking creating a sprite representing a picture of a square and than add that sprite several times to specific locations. The data in the matrix will change over time and I need to reflect that in what is being drawn on the screen, though the shape of the matrix will remain the same.
This will get a basic 2d matrix of squares on the scree. If you need to keep track of these tile sprites, you can create an array of tiles and modify them elsewhere in your code. Hope this is helpful.
func setupMap() {
let tilesWide = 10
let tilesTall = 10
for i in 0..<tilesWide {
for j in 0..<tilesTall {
let tile = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 5, height: 5))
tile.anchorPoint = CGPointZero
let x = CGFloat(i) * tile.size.width
let y = CGFloat(j) * tile.size.height
tile.position = CGPoint(x: x, y: y)
self.addChild(tile)
}
}
}
update for Swift3 :
func setupMap() {
let tilesWide = 10
let tilesTall = 10
for i in 0..<tilesWide {
for j in 0..<tilesTall {
let tile = SKSpriteNode(color: SKColor.red, size: CGSize(width: 5, height: 5))
tile.anchorPoint = .zero
let x = CGFloat(i) * tile.size.width
let y = CGFloat(j) * tile.size.height
tile.position = CGPoint(x: x, y: y)
self.addChild(tile)
}
}
}

Resources