How to rotate SKLabelNode in company with its parent SKShapeNode? - ios

When press the screen, balls created with the following codes:
var n = 1
func addBall(_ x:CGFloat){
let numShape = SKShapeNode(circleOfRadius: 30)
numShape.name = "ball"
numShape.position = CGPoint(x: x, y: frame.maxY-40)
numShape.physicsBody = SKPhysicsBody(circleOfRadius: 30)
numShape.fillColor = SKColor.white
numShape.physicsBody?.density = 0.1
numShape.physicsBody?.affectedByGravity = true
numShape.physicsBody?.friction = 0;
numShape.physicsBody?.restitution = 0.6
numShape.physicsBody?.allowsRotation = true
numShape.physicsBody?.isDynamic = true
let numLabel = SKLabelNode(fontNamed: "Helvetica")
numLabel.text = "\(n)"
numLabel.name = "\(n)"
numLabel.fontColor = .red
numLabel.position = CGPoint(x: 0, y: 0)
numLabel.verticalAlignmentMode = .center
numShape.addChild(numLabel)
self.addChild(numShape)
n += 1
}
The balls can rotate, but their childNode numlabel don't rotate in company with them. I try to update their zRotation like below:
override func update(_ currentTime: TimeInterval) {
self.enumerateChildNodes(withName: "ball") {
node, stop in
if (node is SKShapeNode) {
let ball = node as! SKShapeNode
let num = ball.children[0]
num.zRotation = ball.zRotation
}
}
}
They still refuse to rotate. If I change zRotation directly like num.zRotation = 2, they work.
How can I make them rotate in company with SKShapeNode?
Thx.

You need to set your friction to a number other than 0.
Also, concerning the shape node performance:
look at the draw count at the bottom of 50 shapes:
for _ in 1...50 {
let x = arc4random_uniform(UInt32(frame.maxX));
let xx = CGFloat(x) - size.width/4
let y = arc4random_uniform(UInt32(frame.maxY))
let yy = CGFloat(y) - size.width/4
let shape = SKShapeNode(circleOfRadius: 50)
shape.strokeColor = .blue
shape.lineWidth = 1
shape.position = CGPoint(x: xx, y: yy)
addChild(shape)
}
But now compare that to this image of only 2 draws with only a few lines of refactoring:
func addFiftySprites() {
let shape = SKShapeNode(circleOfRadius: 50)
shape.strokeColor = .blue
shape.lineWidth = 1
let texture = SKView().texture(from: shape)
for _ in 1...50 {
let x = arc4random_uniform(UInt32(frame.maxX));
let xx = CGFloat(x) - size.width/4
let y = arc4random_uniform(UInt32(frame.maxY))
let yy = CGFloat(y) - size.width/4
let sprite = SKSpriteNode(texture: texture)
sprite.position = CGPoint(x: xx, y: yy)
addChild(sprite)
}
}
The magic here is using let texture = SKView().texture(from: <SKNode>) to convert the shape to a sprite :) let sprite = SKSpriteNode(texture: texture)

Related

How do I make a hexagon with 6 triangular SCNNodes?

I'm trying to make a hexagon grid with triangles without altering any pivot points, but I can't seem to position the triangles correctly to make single hexagon. I'm creating SCNNodes with UIBezierPaths to form triangles and then rotating the bezier paths. This seems to work fine UNTIL I try to use a parametric equation to position the triangles around a circle to form the hexagon, then they don't end up in the correct position. Can you help me spot where I'm doing wrong here?
class TrianglePlane: SCNNode {
var size: CGFloat = 0.1
var coords: SCNVector3 = SCNVector3Zero
var innerCoords: Int = 0
init(coords: SCNVector3, innerCoords: Int, identifier: Int) {
super.init()
self.coords = coords
self.innerCoords = innerCoords
setup()
}
init(identifier: Int) {
super.init()
// super.init(identifier: identifier)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
let myPath = path()
let geo = SCNShape(path: myPath, extrusionDepth: 0)
geo.firstMaterial?.diffuse.contents = UIColor.red
geo.firstMaterial?.blendMode = .multiply
self.geometry = geo
}
func path() -> UIBezierPath {
let max: CGFloat = self.size
let min: CGFloat = 0
let bPath = UIBezierPath()
bPath.move(to: .zero)
bPath.addLine(to: CGPoint(x: max / 2,
y: UIBezierPath.middlePeak(height: max)))
bPath.addLine(to: CGPoint(x: max, y: min))
bPath.close()
return bPath
}
}
extension TrianglePlane {
static func generateHexagon() -> [TrianglePlane] {
var myArr: [TrianglePlane] = []
let colors = [UIColor.red, UIColor.green,
UIColor.yellow, UIColor.systemTeal,
UIColor.cyan, UIColor.magenta]
for i in 0 ..< 6 {
let tri = TrianglePlane(identifier: 0)
tri.geometry?.firstMaterial?.diffuse.contents = colors[i]
tri.position = SCNVector3( -0.05, 0, -0.5)
// Rotate bezier path
let angleInDegrees = (Float(i) + 1) * 180.0
print(angleInDegrees)
let angle = CGFloat(deg2rad(angleInDegrees))
let geo = tri.geometry as! SCNShape
let path = geo.path!
path.rotateAroundCenter(angle: angle)
geo.path = path
// Position triangle in hexagon
let radius = Float(tri.size)/2
let deg: Float = Float(i) * 60
let radians = deg2rad(-deg)
let x1 = tri.position.x + radius * cos(radians)
let y1 = tri.position.y + radius * sin(radians)
tri.position.x = x1
tri.position.y = y1
myArr.append(tri)
}
return myArr
}
static func deg2rad(_ number: Float) -> Float {
return number * Float.pi / 180
}
}
extension UIBezierPath {
func rotateAroundCenter(angle: CGFloat) {
let center = self.bounds.center
var transform = CGAffineTransform.identity
transform = transform.translatedBy(x: center.x, y: center.y)
transform = transform.rotated(by: angle)
transform = transform.translatedBy(x: -center.x, y: -center.y)
self.apply(transform)
}
static func middlePeak(height: CGFloat) -> CGFloat {
return sqrt(3.0) / 2 * height
}
}
extension CGRect {
var center : CGPoint {
return CGPoint(x:self.midX, y:self.midY)
}
}
What it currently looks like:
What it SHOULD look like:
I created two versions – SceneKit and RealityKit.
SceneKit (macOS version)
The simplest way to compose a hexagon is to use six non-uniformly scaled SCNPyramids (flat) with their shifted pivot points. Each "triangle" must be rotated in 60 degree increments (.pi/3).
import SceneKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = self.view as! SCNView
let scene = SCNScene()
sceneView.scene = scene
sceneView.allowsCameraControl = true
sceneView.backgroundColor = NSColor.white
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
for i in 1...6 {
let triangleNode = SCNNode(geometry: SCNPyramid(width: 1.15,
height: 1,
length: 1))
// the depth of the pyramid is almost zero
triangleNode.scale = SCNVector3(5, 5, 0.001)
// move a pivot point from pyramid its base to upper vertex
triangleNode.simdPivot.columns.3.y = 1
triangleNode.geometry?.firstMaterial?.diffuse.contents = NSColor(
calibratedHue: CGFloat(i)/6,
saturation: 1.0,
brightness: 1.0,
alpha: 1.0)
triangleNode.rotation = SCNVector4(0, 0, 1,
-CGFloat.pi/3 * CGFloat(i))
scene.rootNode.addChildNode(triangleNode)
}
}
}
RealityKit (iOS version)
In this project I generated a triangle with the help of MeshDescriptor and copied it 5 more times.
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
let anchor = AnchorEntity()
let camera = PointOfView()
let indices: [UInt32] = [0, 1, 2]
override func viewDidLoad() {
super.viewDidLoad()
self.arView.environment.background = .color(.black)
self.arView.cameraMode = .nonAR
self.camera.position.z = 9
let positions: [simd_float3] = [[ 0.00, 0.00, 0.00],
[ 0.52, 0.90, 0.00],
[-0.52, 0.90, 0.00]]
var descriptor = MeshDescriptor(name: "Hexagon's side")
descriptor.materials = .perFace(self.indices)
descriptor.primitives = .triangles(self.indices)
descriptor.positions = MeshBuffers.Positions(positions[0...2])
var material = UnlitMaterial()
let mesh: MeshResource = try! .generate(from: [descriptor])
let colors: [UIColor] = [.systemRed, .systemGreen, .yellow,
.systemTeal, .cyan, .magenta]
for i in 0...5 {
material.color = .init(tint: colors[i], texture: nil)
let triangleModel = ModelEntity(mesh: mesh,
materials: [material])
let trianglePivot = Entity() // made to control pivot point
trianglePivot.addChild(triangleModel)
trianglePivot.orientation = simd_quatf(angle: -.pi/3 * Float(i),
axis: [0,0,1])
self.anchor.addChild(trianglePivot)
}
self.anchor.addChild(self.camera)
self.arView.scene.anchors.append(self.anchor)
}
}
There are a few problems with the code as it stands. Firstly, as pointed out in the comments, the parametric equation for the translations needs to be rotated by 90 degrees:
let deg: Float = (Float(i) * 60) - 90.0
The next issue is that the centre of the bounding box of the triangle and the centroid of the triangle are not the same point. This is important because the parametric equation calculates where the centroids of the triangles must be located, not the centres of their bounding boxes. So we're going to need a way to calculate the centroid. This can be done by adding the following extension method to TrianglePlane:
extension TrianglePlane {
/// Calculates the centroid of the triangle
func centroid() -> CGPoint
{
let max: CGFloat = self.size
let min: CGFloat = 0
let peak = UIBezierPath.middlePeak(height: max)
let xAvg = (min + max / CGFloat(2.0) + max) / CGFloat(3.0)
let yAvg = (min + peak + min) / CGFloat(3.0)
return CGPoint(x: xAvg, y: yAvg)
}
}
This allows the correct radius for the parametric equation to be calculated:
let height = Float(UIBezierPath.middlePeak(height: tri.size))
let centroid = tri.centroid()
let radius = height - Float(centroid.y)
The final correction is to calculate the offset between the origin of the triangle and the centroid. This correction depends on whether the triangle has been flipped by the rotation or not:
let x1 = radius * cos(radians)
let y1 = radius * sin(radians)
let dx = Float(-centroid.x)
let dy = (i % 2 == 0) ? Float(centroid.y) - height : Float(-centroid.y)
tri.position.x = x1 + dx
tri.position.y = y1 + dy
Putting all this together gives the desired result.
Full working ViewController can be found int this gist
Note the code can be greatly simplified by making the origin of the triangle be the centroid.

Moving SKSpriteNode with touch, speeds up at half screen point

I'm working on a 2-d Platformer style game. I'm very new to IOS and Swift development. I'm attempting to use a button (a different node) to move my character across the screen from left to right. It works fine until I reach the halfway point then it speeds up dramatically and letting go of the touch of the button doesn't always stop it. Sometimes it requires another touch. Also the background doesn't appear to keep up with the player. Once the player hits mid screen the background should shift as the player continues moving.
I've pieced what I've done together from multiple tutorials SO examples but I'm stuck on this point.
class StoryModeScene: SKScene, SKPhysicsContactDelegate {
var tileMap = JSTileMap(named: "legend1Level1.tmx")
var tileSize:CGSize!
var xPointsToMovePerSecond:CGFloat = 0
var rightMoveButton = SKSpriteNode(imageNamed: "right-move")
var leftMoveButton = SKSpriteNode(imageNamed: "left-move")
var jumpButton = SKSpriteNode(imageNamed: "a-button")
var fireButton = SKSpriteNode(imageNamed: "b-button")
var forwardMarch:Bool = false
var mightAsWellJump:Bool = false
var onGround:Bool = true
//CREATE THE PLAYER ATLAS FOR ANIMATION
let playerAtlas = SKTextureAtlas(named:"legend1")
var playerSprites = Array<Any>()
var player = SKSpriteNode(imageNamed: "legend1")
var repeatActionPlayer = SKAction()
override func didMove(to view: SKView) {
/* Setup your scene here */
setupScene()
addPlayer()
//PREPARE TO ANIMATE THE PLAYER AND REPEAT THE ANIMATION FOREVER
let animatedPlayer = SKAction.animate(with: self.playerSprites as! [SKTexture], timePerFrame: 0.1)
self.repeatActionPlayer = SKAction.repeatForever(animatedPlayer)
leftMoveButton.position.x = 64
leftMoveButton.position.y = 64
leftMoveButton.name = "moveLeft"
addChild(leftMoveButton)
rightMoveButton.position.x = 124
rightMoveButton.position.y = 64
rightMoveButton.name = "moveRight"
addChild(rightMoveButton)
jumpButton.position.x = 771
jumpButton.position.y = 64
jumpButton.name = "jumpButton"
addChild(jumpButton)
fireButton.position.x = 836
fireButton.position.y = 64
fireButton.name = "fireButton"
addChild(fireButton)
}
override func update(_ currentTime: TimeInterval) {
if (forwardMarch) {
//let moveAction = SKAction.moveBy(x: 3, y: 0, duration: 1)
//let repeatForEver = SKAction.repeatForever(moveAction)
//let seq = SKAction.sequence([moveAction, repeatForEver])
//run the action on your ship
//player.run(seq)
player.position.x = player.position.x + 3
setViewpointCenter(player.position)
}
if (mightAsWellJump) {
let jumpForce = CGPoint(x: 0.0, y: 310.0)
let jumpCutoff: Float = 150.0
if mightAsWellJump && onGround {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx + jumpForce.x, dy: player.physicsBody!.velocity.dy + jumpForce.y)
onGround = false
} else if !mightAsWellJump && player.physicsBody!.velocity.dy > CGFloat(jumpCutoff) {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: CGFloat(jumpCutoff))
}
player.position = CGPoint(x: player.position.x, y: player.position.y + 5);
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches) {
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpButton" {
mightAsWellJump = true
player.texture = SKTexture(imageNamed: "legend1_jump")
}
if name == "moveRight" {
forwardMarch = true
self.player.run(repeatActionPlayer)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if rightMoveButton.contains(touch.location(in: self)) {
forwardMarch = false
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
}
if jumpButton.contains(touch.location(in: self)) {
mightAsWellJump = false
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
}
}
func setViewpointCenter(_ position: CGPoint) {
var x = max(position.x, size.width / 2)
var y = max(position.y, size.height / 2)
x = min(x, (tileMap!.mapSize.width * tileMap!.tileSize.width) - size.width / 2)
y = min(y, (tileMap!.mapSize.height * tileMap!.tileSize.height) - size.height / 2)
let actualPosition = CGPoint(x: CGFloat(x), y: CGFloat(y))
let centerOfView = CGPoint(x: size.width / 2, y: size.height / 2)
let viewPoint = CGPoint(x: (centerOfView.x - actualPosition.x) * 3, y: centerOfView.y - actualPosition.y)
tileMap!.position = viewPoint
}
func setupScene() {
playerSprites.append(playerAtlas.textureNamed("legend1_0"))
playerSprites.append(playerAtlas.textureNamed("legend1_1"))
playerSprites.append(playerAtlas.textureNamed("legend1_2"))
playerSprites.append(playerAtlas.textureNamed("legend1_3"))
playerSprites.append(playerAtlas.textureNamed("legend1_4"))
playerSprites.append(playerAtlas.textureNamed("legend1_5"))
backgroundColor = UIColor(red: 165.0/255.0, green: 216.0/255.0, blue: 255.0/255.0, alpha: 1.0)
physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
anchorPoint = CGPoint(x: 0, y: 0)
position = CGPoint(x: 0, y: 0)
let point = tileMap!.calculateAccumulatedFrame()
print (point)
tileMap!.position = CGPoint(x: 0, y: 0)
addChild(tileMap!)
addFloor()
}
func addFloor() {
for a in 0..<Int(tileMap!.mapSize.width) {
for b in 0..<Int(tileMap!.mapSize.height) {
let layerInfo:TMXLayerInfo = tileMap!.layers.firstObject as! TMXLayerInfo
let point = CGPoint(x: a, y: b)
let walls = tileMap!.layerNamed("Walls")
let wallInfo:TMXLayerInfo = walls!.layerInfo
let wallGIDs = wallInfo.layer.tileGid(at: wallInfo.layer.point(forCoord: point))
if wallGIDs > 0 {
//print (wallGIDs)
//let node = walls
let node = wallInfo.layer.tile(atCoord: point)
node!.physicsBody = SKPhysicsBody(rectangleOf: node!.size)
node!.physicsBody?.isDynamic = false
}
}
}
}
func addPlayer() {
tileSize = tileMap?.tileSize
player.position = CGPoint(x: tileSize.width + player.size.width/2, y: tileSize.height + player.size.height*8)
let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 50, height: 95))
player.physicsBody = SKPhysicsBody(rectangleOf: rect.size)
player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
player.physicsBody!.isDynamic = true
player.physicsBody!.restitution = 0
player.physicsBody!.allowsRotation = false
player.physicsBody!.friction = 1.0
addChild(player)
}
}
Holding the rightMoveButton should move at a consistent pace to the right. As the player gets to mid screen the view point of the background should shift until it reaches the end of the background at which point the player can move off screen and complete the level. Releasing the button should allow the player to stop.
You can move the scene as the character moves by creating an SKCameraNode. You then can choose when to move the camera to create the right affect. Make sure to set the camera as your game’s camera. You can learn about cameras here. As for the speeding up, I assume it has something to do with your physicsBody. If you’re interested about player movement, you could look here or here. There are many other great videos and sites online, which you could find by searching “SpriteKit player movement.”
Eli Front's answer pointed me in the right direction. I still have some issues with this code but the answer to my question was using SKCamera node as Eli pointed out. I just wanted to post the code that works so if anyone has a similar question there's a code example as far as camera moving with the player. The speed issue was essentially an optical illusion with the background moving too fast.
Note that the controls are children of the camera. That is so they move with the camera as well otherwise as your node moves the controls would move off screen.
import UIKit
import SpriteKit
import GameKit
class StoryModeScene: SKScene, SKPhysicsContactDelegate {
var tileMap = JSTileMap(named: "legend1Level1.tmx")
var tileSize:CGSize!
var xPointsToMovePerSecond:CGFloat = 0
var rightMoveButton = SKSpriteNode(imageNamed: "right-move")
var leftMoveButton = SKSpriteNode(imageNamed: "left-move")
var jumpButton = SKSpriteNode(imageNamed: "a-button")
var fireButton = SKSpriteNode(imageNamed: "b-button")
var forwardMarch:Bool = false
var mightAsWellJump:Bool = false
var onGround:Bool = true
//CREATE THE Player ATLAS FOR ANIMATION
let playerAtlas = SKTextureAtlas(named:"legend1")
var playerSprites = Array<Any>()
var player = SKSpriteNode(imageNamed: "legend1")
var repeatActionPlayer = SKAction()
let cam = SKCameraNode()
var previousUpdateTime: TimeInterval = 0
override func didMove(to view: SKView) {
// SETUP CAMERA
self.camera = cam
scene?.addChild(cam)
cam.position.x = 448
cam.position.y = 212
setupScene()
addPlayer()
//PREPARE TO ANIMATE THE PLAYER AND REPEAT THE ANIMATION FOREVER
let animatedPlayer = SKAction.animate(with: self.playerSprites as! [SKTexture], timePerFrame: 0.1)
self.repeatActionPlayer = SKAction.repeatForever(animatedPlayer)
// SETUP CONTROLS
leftMoveButton.position.x = -338
leftMoveButton.position.y = -112
leftMoveButton.name = "moveLeft"
leftMoveButton.zPosition = 5
cam.addChild(leftMoveButton)
rightMoveButton.position.x = -278
rightMoveButton.position.y = -112
rightMoveButton.name = "moveRight"
cam.addChild(rightMoveButton)
jumpButton.position.x = 278
jumpButton.position.y = -112
jumpButton.name = "jumpButton"
jumpButton.zPosition = 5
cam.addChild(jumpButton)
fireButton.position.x = 338
fireButton.position.y = -112
fireButton.name = "fireButton"
cam.addChild(fireButton)
}
override func update(_ currentTime: TimeInterval) {
if (forwardMarch) {
if player.position.x > 448 && player.position.x < 1800 {
cam.position.x = player.position.x
} else if player.position.x >= 1800 {
cam.position.x = 1800
}
setViewpointCenter(player.position)
player.position.x = player.position.x + 3
}
if (mightAsWellJump) {
let jumpForce = CGPoint(x: 0.0, y: 310.0)
let jumpCutoff: Float = 150.0
if mightAsWellJump && onGround {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx + jumpForce.x, dy: player.physicsBody!.velocity.dy + jumpForce.y)
onGround = false
} else if !mightAsWellJump && player.physicsBody!.velocity.dy > CGFloat(jumpCutoff) {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: CGFloat(jumpCutoff))
}
player.position = CGPoint(x: player.position.x, y: player.position.y + 5);
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches) {
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpButton" {
mightAsWellJump = true
player.texture = SKTexture(imageNamed: "legend1_jump")
}
if name == "moveRight" {
player.physicsBody!.velocity.dy = 0.0
forwardMarch = true
self.player.run(repeatActionPlayer)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches) {
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpButton" {
mightAsWellJump = false
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
}
if name == "moveRight" {
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
forwardMarch = false
}
}
}
}
func setViewpointCenter(_ position: CGPoint) {
var x = max(position.x, size.width / 2)
var y = max(position.y, size.height / 2)
x = min(x, (tileMap!.mapSize.width * tileMap!.tileSize.width) - size.width / 2)
y = min(y, (tileMap!.mapSize.height * tileMap!.tileSize.height) - size.height / 2)
let actualPosition = CGPoint(x: CGFloat(x), y: CGFloat(y))
let centerOfView = CGPoint(x: size.width / 2, y: size.height / 2)
let viewPoint = CGPoint(x: centerOfView.x - actualPosition.x, y: centerOfView.y - actualPosition.y)
if (actualPosition.x > 1800) {
tileMap!.position = tileMap!.position
} else {
tileMap!.position = viewPoint
}
}
func setupScene() {
// ADD PLAYER SPRITES #TODO MAKE THIS DYNAMIC
playerSprites.append(playerAtlas.textureNamed("legend1_0"))
playerSprites.append(playerAtlas.textureNamed("legend1_1"))
playerSprites.append(playerAtlas.textureNamed("legend1_2"))
playerSprites.append(playerAtlas.textureNamed("legend1_3"))
playerSprites.append(playerAtlas.textureNamed("legend1_4"))
playerSprites.append(playerAtlas.textureNamed("legend1_5"))
backgroundColor = UIColor(red: 165.0/255.0, green: 216.0/255.0, blue: 255.0/255.0, alpha: 1.0)
anchorPoint = CGPoint(x: 0, y: 0)
position = CGPoint(x: 0, y: 0)
// let point = tileMap!.calculateAccumulatedFrame()
tileMap!.position = CGPoint(x: 0, y: 0)
addChild(tileMap!)
addFloor()
}
func addFloor() {
for a in 0..<Int(tileMap!.mapSize.width) {
for b in 0..<Int(tileMap!.mapSize.height) {
// let layerInfo:TMXLayerInfo = tileMap!.layers.firstObject as! TMXLayerInfo
let point = CGPoint(x: a, y: b)
let walls = tileMap!.layerNamed("Walls")
let wallInfo:TMXLayerInfo = walls!.layerInfo
let wallGIDs = wallInfo.layer.tileGid(at: wallInfo.layer.point(forCoord: point))
if wallGIDs > 0 {
let node = wallInfo.layer.tile(atCoord: point)
node!.physicsBody = SKPhysicsBody(rectangleOf: node!.size)
node!.physicsBody?.isDynamic = false
}
}
}
}
func addPlayer() {
tileSize = tileMap?.tileSize
player.position = CGPoint(x: tileSize.width + player.size.width/2, y: tileSize.height + player.size.height*8)
let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 50, height: 95))
player.physicsBody = SKPhysicsBody(rectangleOf: rect.size)
player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
player.physicsBody!.isDynamic = true
player.physicsBody!.restitution = 0
player.physicsBody!.allowsRotation = false
player.physicsBody!.friction = 1.0
player.physicsBody!.mass = 1.0
addChild(player)
}
}
As I mentioned I still have some issues like the player node floating/flying when I jump/run at the same time. The 1st jump is the correct height and all subsequent jumps are smaller etc but I'm working on figuring those out.

Live Rotation of SCNNode

I want my node to rotate on the Y axis while the camera is rotating.
class ArtScene: SCNScene{
var image = UIImage()
var geometry = SCNBox()
var motion = MotionKit()
var node = SCNNode()
convenience init(create: Bool) {
self.init()
image = UIImage(named: "sf")!
let lenght: CGFloat = 50
let width: CGFloat = self.image.size.width
let height: CGFloat = self.image.size.height
self.geometry = SCNBox(width: width / 1000 , height: height / 1000, length: lenght / 1000, chamferRadius: 0.005)
print("Geometry WIDTH: \(self.geometry.width)")
print("Geometry LENGHT: \(self.geometry.length)")
self.geometry.firstMaterial?.diffuse.contents = UIColor.red
self.geometry.firstMaterial?.specular.contents = UIColor.white
self.geometry.firstMaterial?.emission.contents = UIColor.blue
node = SCNNode(geometry: self.geometry)
self.rootNode.addChildNode(node)
}
func rotate() {
motion.getDeviceMotionObject { (deviceMotion) in
let rotationY = deviceMotion.rotationRate.y
print(rotationY)
let rotationAction = SCNAction.rotate(by: CGFloat(1.0), around: SCNVector3(0,rotationY,0), duration: 2.0)
self.node.runAction(rotationAction)
}
}
}
I can print the Device motion, but can't get the node to rotate at the same time. How can I do it?

How to set ball's random position in Spritekit?

I am making a game where there are two balls on the scene, but the user is going to tap on the correct ball. However, when user taps on the correct ball, I want to set a random position for my second ball that when the user taps on the correct ball, I want to also want the second ball to move randomly.
Here is my code for setting up the random positions of the ball:
let currentBall = SKShapeNode(circleOfRadius: CGFloat(size))
let shape = SKShapeNode()
currentBall.fillColor = pickColor()
//Rectangle
shape.path = UIBezierPath(roundedRect: CGRect(x: 64, y: 64, width: 160,height: 160), cornerRadius: 50).cgPath
shape.fillColor = pickColor()
self.addChild(shape)
func randomBallPosition() -> CGPoint {
let xPosition = view!.scene!.frame.midX - viewMidX + CGFloat(arc4random_uniform(UInt32(viewMidX*2)))
let yPosition = view!.scene!.frame.midY - viewMidY + CGFloat(arc4random_uniform(UInt32(viewMidY*2)))
return CGPoint(x: xPosition, y: yPosition)
}
func handleTap() {
currentBall.position = randomBallPosition()
shape.position = randomBallPosition()
}
The issue is that you're setting the exact same position, which you have used for the first ball. As far as I understand both ball should appear at random independent positions. You should create a function to return a new position and call it for each ball:
class MyScene: SKScene {
var currentBall: SKShapeNode!
var shape: SKShapeNode!
override func didMove(to view: SKView) {
super.didMove(to: view)
let currentBall = SKShapeNode(circleOfRadius: CGFloat(size))
currentBall.fillColor = pickColor()
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x: 64, y: 64, width: 160,height: 160), cornerRadius: 50).cgPath
shape.fillColor = pickColor()
self.addChild(shape)
}
func handleTap() {
//your code here, except that you don't create new nodes, just modify existing ones
//at some point you will change balls' positions like this:
currentBall.position = randomBallPosition()
secondBall.position = randomBallPosition()
}
func randomBallPosition() -> CGPoint {
let xPosition = CGFloat(arc4random_uniform(UInt32((view?.bounds.maxX)! + 1)))
let yPosition = CGFloat(arc4random_uniform(UInt32((view?.bounds.maxY)! + 1)))
return CGPoint(x: xPosition, y: yPosition)
}
}
Hope this helps

How do I make an object bounce off the edges in swift

I am trying to make my 'Enemy' bounce off all the boundaries. For example, when the Enemy collides with the left, right, top and bottom of the screen, it should change direction.
This is my current code:
import SpriteKit
import GameplayKit
struct Physics {
static let Enemy: UInt32 = 0x1 << 1
let BorderCategory : UInt32 = 0x1 << 2
let BottomCategory : UInt32 = 0x1 << 3
let BallCategory : UInt32 = 0x1 << 4
}
class GameScene: SKScene {
var Enemy = SKSpriteNode()
var gameStarted = Bool()
var gameState = "running"
var destX : CGFloat = 0.0
var destY : CGFloat = 0.0
var score = 0
override func didMove(to view: SKView) {
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
let screenSize: CGRect = UIScreen.main().bounds // get the screen size
let screenWidth = screenSize.width //get the width
let screenHeight = screenSize.height //get the height
Enemy = SKSpriteNode(imageNamed: "red2")
Enemy.size = CGSize(width: 60, height: 70)
Enemy.position = (CGPoint(x: self.frame.width / 6 - Enemy.frame.width, y: self.frame.height / 10))
Enemy.physicsBody = SKPhysicsBody(circleOfRadius: Enemy.frame.height / 2)
Enemy.physicsBody?.categoryBitMask = Physics.Enemy
//Enemy.physicsBody?.categoryBitMask = Physics.Ground | Physics.wall
//Enemy.physicsBody?.contactTestBitMask = Physics.Ground | Physics.wall
Enemy.physicsBody?.affectedByGravity = false
Enemy.physicsBody?.isDynamic = true
self.addChild(Enemy)
Enemy.physicsBody?.velocity = CGVector(dx: 50, dy: 50)
if (Enemy.position.x == screenWidth) {
Enemy.physicsBody?.velocity = CGVector(dx: -50, dy: 0) // change direction at edge DOESN'T WORK
}
if(Enemy.position.y == screenHeight){
Enemy.physicsBody?.velocity = CGVector(dx: 0, dy: -50) //change direction at edge DOESN'T WORK
}
}
OK, based on your code I've written a working bouncing sprite. It uses the physics-engine to achieve this, rather than manually changing the direction (which I guess is how you should go about doing this).
override func didMove(to view: SKView) {
let borderBody = SKPhysicsBody(edgeLoopFrom: frame)
borderBody.friction = 0
borderBody.categoryBitMask = Physics.wall
physicsBody = borderBody
let enemy = enemySprite(size: CGSize(width: 60, height: 70), position: CGPoint(x: frame.size.width/2, y: frame.size.height/2))
addChild(enemy)
let force = SKAction.applyForce(CGVector(dx: 300, dy: 300) , duration: 0.1)
enemy.run(force)
}
func enemySprite(size: CGSize, position: CGPoint) -> SKSpriteNode {
let enemy = SKSpriteNode(color: SKColor.red(), size: CGSize(width: 60, height: 80))
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.frame.height / 2) // A bit silly with circle, but you seem to have an image for this use
enemy.physicsBody?.categoryBitMask = Physics.enemy
enemy.physicsBody?.restitution = 1
enemy.physicsBody?.friction = 0
enemy.physicsBody?.collisionBitMask = Physics.wall
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.angularDamping = 0
enemy.physicsBody?.linearDamping = 0
return enemy
}
A question to consider is also how you set up the scene? Is the scene-size the same as the view-size? Otherwise the scene's physicsBody will not make the ball bounce where you expect it to...
It won't (in most cases) because of your conditions - using equal to operators instead of greater/lower than.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// Create enemy with physics body attached and add it to the scene
let enemy = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 20, height: 20))
enemy.name = "enemy"
enemy.position = CGPoint(x: size.width / 2, y: size.height / 2)
enemy.physicsBody = SKPhysicsBody(rectangleOfSize: enemy.size)
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.linearDamping = 0
enemy.physicsBody!.mass = 0.1
enemy.physicsBody!.velocity = CGVector(dx: 150, dy: 0)
addChild(enemy)
}
override func didSimulatePhysics() {
let enemy = childNodeWithName("enemy")!
if (enemy.position.x > size.width && enemy.physicsBody!.velocity.dx > 0)
|| (enemy.position.x < 0 && enemy.physicsBody!.velocity.dx < 0) {
enemy.physicsBody!.velocity.dx *= -1
}
}
}

Resources