How to rotate sprites around a joint - swift

I need to make the arms and hands rotate around the center of the hook, as shown in the image below without them separating or changing their shape (no changes in the angles between arms and hands just rotation at A), as in the image below:
I tried rotating the arms but this made them separate and change form. You can check out my code below:
let hookCategoryName = "hook"
let leftArmCategoryName = "leftArm"
let rightArmCategoryName = "rightArm"
let leftHandCategoryName = "leftHand"
let rightHandCategoryName = "rightHand"
let hookCategory : UInt32 = 0x1 << 0
let leftArmCategory : UInt32 = 0x1 << 1
let rightArmCategory : UInt32 = 0x1 << 2
let leftHandCategory : UInt32 = 0x1 << 3
let rightHandCategory : UInt32 = 0x1 << 4
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var hook = SKSpriteNode(imageNamed: "hook")
var leftArm = SKSpriteNode(imageNamed: "arm")
var rightArm = SKSpriteNode(imageNamed: "arm")
var leftHand = SKSpriteNode(imageNamed: "leftHand")
var rightHand = SKSpriteNode(imageNamed: "rightHand")
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
var yellowBg = SKSpriteNode(imageNamed: "yellowBg")
yellowBg.position = CGPoint(x: frame.midX, y: frame.midY)
yellowBg.zPosition = 2
addChild(yellowBg)
hook.position = CGPoint(x: frame.midX, y: frame.midY + frame.midY/2)
hook.zPosition = 5
hook.name = hookCategoryName
hook.physicsBody = SKPhysicsBody(rectangleOf: hook.frame.size)
hook.physicsBody?.categoryBitMask = hookCategory
hook.physicsBody?.isDynamic = false
addChild(hook)
rightArm.anchorPoint = CGPoint(x: 0.5, y: 1)
rightArm.position = hook.position
rightArm.zPosition = 5
rightArm.name = rightArmCategoryName
rightArm.physicsBody = SKPhysicsBody(rectangleOf: rightArm.frame.size)
rightArm.physicsBody?.categoryBitMask = rightArmCategory
rightArm.physicsBody!.isDynamic = true
addChild(rightArm)
leftArm.anchorPoint = CGPoint(x: 0.5, y: 1)
leftArm.position = hook.position
leftArm.zPosition = 5
leftArm.name = leftArmCategoryName
leftArm.physicsBody = SKPhysicsBody(rectangleOf: leftArm.frame.size)
leftArm.physicsBody?.categoryBitMask = leftArmCategory
leftArm.physicsBody!.isDynamic = true
addChild(leftArm)
// leftHand
leftHand.anchorPoint = CGPoint(x: 0.5, y: 0.5)
leftHand.position = CGPoint(x: leftArm.frame.minX - 22, y: leftArm.frame.minY + 7) //CGPoint(x: armLeft.position.x, y: armLeft.position.y)
leftHand.zPosition = 5
leftHand.name = leftHandCategoryName
leftHand.physicsBody = SKPhysicsBody(rectangleOf: leftHand.frame.size)
leftHand.physicsBody?.categoryBitMask = leftHandCategory
leftHand.zRotation = CGFloat(Double(-30).degreesToRadians)//CGFloat(-Double.pi/6)
//armLeft.physicsBody?.categoryBitMask = armCategory
leftHand.physicsBody!.isDynamic = true
addChild(leftHand)
// rightHand
rightHand.anchorPoint = CGPoint(x: 0.5, y: 0.5)
rightHand.position = CGPoint(x: rightArm.frame.minX + 30, y: rightArm.frame.minY + 7) //CGPoint(x: armLeft.position.x, y: armLeft.position.y)
rightHand.zPosition = 5
rightHand.name = rightHandCategoryName
rightHand.physicsBody = SKPhysicsBody(rectangleOf: rightHand.frame.size)
rightHand.physicsBody?.categoryBitMask = rightHandCategory
rightHand.zRotation = CGFloat(Double(30).degreesToRadians)//CGFloat(-Double.pi/6)
//armLeft.physicsBody?.categoryBitMask = armCategory
rightHand.physicsBody!.isDynamic = true
addChild(rightHand)
leftArm.zRotation = CGFloat(Double(-45).degreesToRadians)
rightArm.zRotation = CGFloat(Double(45).degreesToRadians)
rightHand.physicsBody?.contactTestBitMask = rightHandCategory
leftHand.physicsBody?.contactTestBitMask = leftHandCategory
rightHand.physicsBody?.collisionBitMask = rightHandCategory
leftHand.physicsBody?.collisionBitMask = leftHandCategory
let hookAndRightArmJoint = SKPhysicsJointPin.joint(withBodyA: hook.physicsBody!, bodyB: rightArm.physicsBody!, anchor: CGPoint(x: hook.position.x, y: self.rightArm.frame.maxY))
self.physicsWorld.add(hookAndRightArmJoint)
let hookAndLeftArmJoint = SKPhysicsJointPin.joint(withBodyA: hook.physicsBody!, bodyB: leftArm.physicsBody!, anchor: CGPoint(x: hook.position.x, y: self.leftArm.frame.maxY))
self.physicsWorld.add(hookAndLeftArmJoint)
let armsFixedJoint = SKPhysicsJointFixed.joint(withBodyA: leftArm.physicsBody!, bodyB: rightArm.physicsBody!, anchor: CGPoint.zero)
self.physicsWorld.add(armsFixedJoint)
//left arm and hand joint
let leftArmAndHandJoint = SKPhysicsJointPin.joint(withBodyA: leftArm.physicsBody!, bodyB: leftHand.physicsBody!, anchor: CGPoint(x: self.leftArm.frame.minX, y: self.leftArm.frame.minY)/*CGPoint(x: armLeft.position.x, y: self.armLeft.frame.minY)*/)
self.physicsWorld.add(leftArmAndHandJoint)
//right arm and hand joint
let rightArmAndHandJoint = SKPhysicsJointPin.joint(withBodyA: rightArm.physicsBody!, bodyB: rightHand.physicsBody!, anchor: CGPoint(x: self.rightArm.frame.maxX, y: self.rightArm.frame.minY)/*CGPoint(x: armLeft.position.x, y: self.armLeft.frame.minY)*/)
self.physicsWorld.add(rightArmAndHandJoint)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
hook.run(SKAction.rotate(byAngle: CGFloat(Double(60).degreesToRadians), duration: 0.5))
}
}
Also rotating the hook has no effect on the arms and hands as seen in the image below when the above code is run:
How can I get the rotation in the image first image?

You can make the arms and hands children of a common parent node. You could create a blank SKNode for just this purpose like so:
let armsParent = SKNode()
Then instead of adding the arms and hands as children to the scene directly, add them as children of armsParent like so:
armsParent.addChild(leftArm)
armsParent.addChild(rightArm) // same for hands...
Then you can simply rotate armsParent with an SKAction to achieve what you want.
OR, to make it even simpler, you could just add the arms and hands as children to hook directly like this:
hook.addChild(leftArm) // same for other arm and hands...
Since the arms and hands are children of hook or armParent their positions will now be determined relative to their parent. So you might have to change all your .position = initialization code to accommodate this.

Related

Collision detection inconsistent

My collision detection is working most of the time, but sometimes one physics body will go inside the other, when they should collide. I have a car with physics and road sides with physics. The car will blow through the road sides occasionally. I'm moving the car forward by position, however I have also tried moving forward by force and the same thing happens. Does anyone have any idea what would be causing this?
I've defined my physics categorys like so;
struct PhysicsCategory: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) { self.rawValue = rawValue }
static let CarCategory = PhysicsCategory(rawValue: 0b00001)
static let RoadSidesCategory = PhysicsCategory(rawValue: 0b00010)
static let NoCollisionsCategory = PhysicsCategory(rawValue: 0b00000)
}
Giving the car physics like so;
func giveCarPhysics(physics: Bool) {
//give physicsBody to the car and set to collide with the background
if physics {
self.size = CGSize(width: self.size.width, height: self.size.height)
self.physicsBody = SKPhysicsBody(rectangleOf: CGSize (width: self.size.width * 0.6, height: self.size.height * 0.9), center: CGPoint(x: self.anchorPoint.x, y: self.anchorPoint.y + self.size.height / 2))
self.physicsBody?.mass = vehicleMass
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody?.friction = 0.5
self.physicsBody?.restitution = 0.2
self.physicsBody?.affectedByGravity = false
self.physicsBody?.isDynamic = true
self.physicsBody?.linearDamping = airResistance
self.physicsBody?.categoryBitMask = PhysicsCategory.CarCategory.rawValue
self.physicsBody?.collisionBitMask = PhysicsCategory.RoadSidesCategory.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.RoadSidesCategory.rawValue
}
}
And then giving the road sides physics;
func createPhysicsRight() {
for (index, backgroundImageRight) in self.physicsRight.enumerated() {
let roadSpriteR = SKSpriteNode()
roadSpriteR.anchorPoint = CGPoint(x: 0.5, y: 0.5)
roadSpriteR.position = CGPoint(x: 0, y: CGFloat(self.physicsCounterR) * self.frame.size.height)
roadSpriteR.zPosition = 0
roadSpriteR.name = "RoadR \(index)"
print(roadSpriteR.name as Any)
roadSpriteR.physicsBody = SKPhysicsBody(polygonFrom: backgroundImageRight)
roadSpriteR.physicsBody?.mass = backgroundMass
roadSpriteR.physicsBody?.affectedByGravity = false
roadSpriteR.physicsBody?.isDynamic = false
roadSpriteR.physicsBody?.categoryBitMask = PhysicsCategory.RoadSidesCategory.rawValue
roadSpriteR.physicsBody?.collisionBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteR.physicsBody?.contactTestBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteRArray.append(roadSpriteR)
}
}
func createPhysicsLeft() {
for (index, backgroundImageLeft) in self.physicsLeft.enumerated() {
let roadSpriteL = SKSpriteNode()
roadSpriteL.anchorPoint = CGPoint(x: 0.5, y: 0.5)
roadSpriteL.position = CGPoint(x: 0, y: CGFloat(self.physicsCounterL) * self.frame.size.height)
roadSpriteL.zPosition = 0
roadSpriteL.name = "RoadL \(index)"
roadSpriteL.physicsBody = SKPhysicsBody(polygonFrom: backgroundImageLeft)
roadSpriteL.physicsBody?.mass = backgroundMass
roadSpriteL.physicsBody?.affectedByGravity = false
roadSpriteL.physicsBody?.isDynamic = false
roadSpriteL.physicsBody?.categoryBitMask = PhysicsCategory.RoadSidesCategory.rawValue
roadSpriteL.physicsBody?.collisionBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteL.physicsBody?.contactTestBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteLArray.append(roadSpriteL)
}
}

How to keep a 30˚ distance between two lines anchored at a point

I am trying to create two lines that are anchored at a certain point (sprite) and rotate to form a 30 degree angle between them. Below is an image what I want to achieve.
This is what I've done so far:
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var anchorSprite = SKSpriteNode(imageNamed: "anchorSprite")
var armLeft = SKSpriteNode(imageNamed: "lineSprite")
var armRight = SKSpriteNode(imageNamed: "lineSprite")
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -1.8)
self.physicsWorld.contactDelegate = self
var tealBg = SKSpriteNode(imageNamed: "tealBg")
tealBg.position = CGPoint(x: frame.midX, y: frame.midY)
tealBg.zPosition = 10
addChild(tealBg)
anchorSprite.position = CGPoint(x: frame.midX, y: frame.midY + frame.midY/2)
anchorSprite.zPosition = 20
anchorSprite.physicsBody = SKPhysicsBody(rectangleOf: anchorSprite.frame.size)
anchorSprite.physicsBody?.categoryBitMask = pinCategory
anchorSprite.physicsBody?.isDynamic = false
addChild(anchorSprite)
armRight.anchorPoint = CGPoint(x: 0.5, y: 1)
armRight.position = anchorSprite.position
armRight.zPosition = 20
armRight.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
armRight.zRotation = CGFloat(Double(15).degreesToRadians)//CGFloat(Double.pi/6)
armRight.physicsBody!.isDynamic = true
addChild(armRight)
armLeft.anchorPoint = CGPoint(x: 0.5, y: 1)
armLeft.position = anchorSprite.position
armLeft.zPosition = 20
armLeft.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
armLeft.zRotation = CGFloat(Double(-15).degreesToRadians)//CGFloat(-Double.pi/6)
armLeft.physicsBody!.isDynamic = true
addChild(armLeft)
//Pin joints
var pinAndRightArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armRight.frame.maxY))
self.physicsWorld.add(pinAndRightArmJoint)
var pinAndLeftArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armLeft.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armLeft.frame.maxY))
self.physicsWorld.add(pinAndLeftArmJoint)
}
Below is an image from running the above code (they are close together).
How can I make sure the lines are always 30˚ apart and maintain 30˚ apart even when rotated?
To keep your two lines separated by exactly 30°, you can use an SKPhysicsJointFixed, which is just what it sounds like: it pins two physicsBodies together in a fixed position. Since you already have them positioned the way you want, just add this code where you have the other SKPhysicsJoints to hold them that way:
let fixArms = SKPhysicsJointFixed.joint(withBodyA: armLeft.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint.zero)
self.physicsWorld.add(fixArms)
Result:
If you make the line nodes children of the anchor sprite (instead of the scene), rotating the anchor sprite node will rotate all the lines along with it without doing anything special with physics. You just need to mind the anchor points so that they align properly (i.e. line's anchor at its extremity rather than center)

How can I make my level menu scrollable vertically?

I have the following level Menu (as seen below). I would like to make it vertically scrollable, resulting in a total height double that of the screen (full scroll height). How can I achieve this?
Below is the code for the image above:
class LevelMenu: SKScene {
let levelButtonSize = SKSpriteNode(imageNamed: "b1").size
let levelButton1: SKSpriteNode = SKSpriteNode(imageNamed: "b1")
let levelButton2: SKSpriteNode = SKSpriteNode(imageNamed: "b2")
let levelButton3: SKSpriteNode = SKSpriteNode(imageNamed: "b3")
let levelButton4: SKSpriteNode = SKSpriteNode(imageNamed: "b4")
let levelButton5: SKSpriteNode = SKSpriteNode(imageNamed: "b5")
let levelButton6: SKSpriteNode = SKSpriteNode(imageNamed: "b6")
let levelButton7: SKSpriteNode = SKSpriteNode(imageNamed: "b7")
let levelButton8: SKSpriteNode = SKSpriteNode(imageNamed: "b8")
let levelButton9: SKSpriteNode = SKSpriteNode(imageNamed: "b9")
let levelButton10: SKSpriteNode = SKSpriteNode(imageNamed: "b10")
let levelButton11: SKSpriteNode = SKSpriteNode(imageNamed: "b11")
let levelButton12: SKSpriteNode = SKSpriteNode(imageNamed: "b12")
override init(size: CGSize){
super.init(size: size)
let bg = SKSpriteNode(imageNamed: "bg")
backgroundImage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(bg)
let column1PosX = levelButtonSize.width*cDiff
let column2PosX = levelButtonSize.width*cDiff + levelButtonSize.width*2.0
let column3PosX = levelButtonSize.width*cDiff + levelButtonSize.width*4.0
let row1PosY = self.frame.height - levelButtonSize.width*1.5
let row2PosY = row1PosY - levelButtonSize.height - levelButtonSize.width*rDiff
let row3PosY = row2PosY - levelButtonSize.height - levelButtonSize.width*rDiff
let row4PosY = row3PosY - levelButtonSize.height - levelButtonSize.width*rDiff
levelButton1.position = CGPoint(x: column1PosX, y: row1PosY)
levelButton1.zPosition = 10
self.addChild(levelButton1)
levelButton2.position = CGPoint(x: column2PosX, y: row1PosY)
self.addChild(levelButton2)
levelButton3.position = CGPoint(x: column3PosX, y: row1PosY)
self.addChild(levelButton3)
levelButton4.position = CGPoint(x: column1PosX, y: row2PosY)
self.addChild(levelButton4)
levelButton5.position = CGPoint(x: column2PosX, y: row2PosY)
self.addChild(levelButton5)
levelButton6.position = CGPoint(x: column3PosX, y: row2PosY)
self.addChild(levelButton6)
levelButton7.position = CGPoint(x: column1PosX, y: row3PosY)
self.addChild(levelButton7)
levelButton8.position = CGPoint(x: column2PosX, y: row3PosY)
self.addChild(levelButton8)
levelButton9.position = CGPoint(x: column3PosX, y: row3PosY)
self.addChild(levelButton9)
levelButton10.position = CGPoint(x: column1PosX, y: row4PosY)
self.addChild(levelButton10)
levelButton11.position = CGPoint(x: column2PosX, y: row4PosY)
self.addChild(levelButton11)
levelButton12.position = CGPoint(x: column3PosX, y: row4PosY)
self.addChild(levelButton12)
}
UPDATE
Based on Ron Myschuk's solution, the code below show's what I've been able to achieve and this link shows a .gif of the issue I am having currently, where the screen scrolls too much at the top of the menu.
class LMScene: SKScene {
let levelButtonSize = SKSpriteNode(imageNamed: "b1").size
let levelButton1: SKSpriteNode = SKSpriteNode(imageNamed: "b1")
let levelButton2: SKSpriteNode = SKSpriteNode(imageNamed: "b2")
let levelButton3: SKSpriteNode = SKSpriteNode(imageNamed: "b3")
let levelButton4: SKSpriteNode = SKSpriteNode(imageNamed: "b4")
let levelButton5: SKSpriteNode = SKSpriteNode(imageNamed: "b5")
let levelButton6: SKSpriteNode = SKSpriteNode(imageNamed: "b6")
let levelButton7: SKSpriteNode = SKSpriteNode(imageNamed: "b7")
let levelButton8: SKSpriteNode = SKSpriteNode(imageNamed: "b8")
let levelButton9: SKSpriteNode = SKSpriteNode(imageNamed: "b9")
let levelButton10: SKSpriteNode = SKSpriteNode(imageNamed: "b10")
let levelButton11: SKSpriteNode = SKSpriteNode(imageNamed: "b11")
let levelButton12: SKSpriteNode = SKSpriteNode(imageNamed: "b12")
let levelButton13: SKSpriteNode = SKSpriteNode(imageNamed: "b13")
let levelButton14: SKSpriteNode = SKSpriteNode(imageNamed: "b14")
let levelButton15: SKSpriteNode = SKSpriteNode(imageNamed: "b15")
let levelButton16: SKSpriteNode = SKSpriteNode(imageNamed: "b16")
let levelButton17: SKSpriteNode = SKSpriteNode(imageNamed: "b17")
let levelButton18: SKSpriteNode = SKSpriteNode(imageNamed: "b18")
private var scrollCell = SKSpriteNode()
private var moveAmtX: CGFloat = 0
private var moveAmtY: CGFloat = 0
private let minimum_detect_distance: CGFloat = 30
private var initialPosition: CGPoint = CGPoint.zero
private var initialTouch: CGPoint = CGPoint.zero
private var resettingSlider = false
override init(size: CGSize){
super.init(size: size)
scrollCell = SKSpriteNode(color: .blue, size: CGSize(width: self.size.width, height: 2*self.size.height - self.frame.width*0.24734))
scrollCell.position = CGPoint(x: 0, y: 0)
scrollCell.anchorPoint = CGPoint.zero
scrollCell.zPosition = 0
self.addChild(scrollCell)
let backgroundImage = SKSpriteNode(imageNamed: "bg")
backgroundImage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(backgroundImage)
let column1PosX = levelButtonSize.width/2 + self.frame.width*0.14855
let column2PosX = 3*levelButtonSize.width/2 + 2*self.frame.width*0.14855
let column3PosX = 5*levelButtonSize.width/2 + 3*self.frame.width*0.14855
let row1PosY = self.frame.height - levelButtonSize.height/2 - self.frame.width*0.24734
let row2PosY = row1PosY - levelButtonSize.height - self.frame.width*0.24734
let row3PosY = row2PosY - levelButtonSize.height - self.frame.width*0.24734
let row4PosY = row3PosY - levelButtonSize.height - self.frame.width*0.24734
let row5PosY = row4PosY - levelButtonSize.height - self.frame.width*0.24734
let row6PosY = row5PosY - levelButtonSize.height - self.frame.width*0.24734
levelButton1.position = CGPoint(x: column1PosX, y: row1PosY)
levelButton1.zPosition = 10
scrollCell.addChild(levelButton1)
levelButton2.position = CGPoint(x: column2PosX, y: row1PosY)
levelButton2.zPosition = 10
scrollCell.addChild(levelButton2)
levelButton3.position = CGPoint(x: column3PosX, y: row1PosY)
levelButton3.zPosition = 10
scrollCell.addChild(levelButton3)
levelButton4.position = CGPoint(x: column1PosX, y: row2PosY)
levelButton4.zPosition = 10
scrollCell.addChild(levelButton4)
levelButton5.position = CGPoint(x: column2PosX, y: row2PosY)
levelButton5.zPosition = 10
scrollCell.addChild(levelButton5)
levelButton6.position = CGPoint(x: column3PosX, y: row2PosY)
levelButton6.zPosition = 10
scrollCell.addChild(levelButton6)
levelButton7.position = CGPoint(x: column1PosX, y: row3PosY)
levelButton7.zPosition = 10
scrollCell.addChild(levelButton7)
levelButton8.position = CGPoint(x: column2PosX, y: row3PosY)
levelButton8.zPosition = 10
scrollCell.addChild(levelButton8)
levelButton9.position = CGPoint(x: column3PosX, y: row3PosY)
levelButton9.zPosition = 10
scrollCell.addChild(levelButton9)
levelButton10.position = CGPoint(x: column1PosX, y: row4PosY)
levelButton10.zPosition = 10
scrollCell.addChild(levelButton10)
levelButton11.position = CGPoint(x: column2PosX, y: row4PosY)
levelButton11.zPosition = 10
scrollCell.addChild(levelButton11)
levelButton12.position = CGPoint(x: column3PosX, y: row4PosY)
levelButton12.zPosition = 10
scrollCell.addChild(levelButton12)
levelButton13.position = CGPoint(x: column1PosX, y: row5PosY)
levelButton13.zPosition = 10
scrollCell.addChild(levelButton13)
levelButton14.position = CGPoint(x: column2PosX, y: row5PosY)
levelButton14.zPosition = 10
scrollCell.addChild(levelButton14)
levelButton15.position = CGPoint(x: column3PosX, y: row5PosY)
levelButton15.zPosition = 10
scrollCell.addChild(levelButton15)
levelButton16.position = CGPoint(x: column1PosX, y: row6PosY)
levelButton16.zPosition = 10
scrollCell.addChild(levelButton16)
levelButton17.position = CGPoint(x: column2PosX, y: row6PosY)
levelButton17.zPosition = 10
scrollCell.addChild(levelButton17)
levelButton18.position = CGPoint(x: column3PosX, y: row6PosY)
levelButton18.zPosition = 10
scrollCell.addChild(levelButton18)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
if let touch = touches.first as UITouch! {
self.scrollCell.removeAllActions()
initialTouch = touch.location(in: self.scene!.view)
moveAmtY = 0
initialPosition = self.scrollCell.position
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let movingPoint: CGPoint = touch.location(in: self.scene!.view)
moveAmtY = movingPoint.y - initialTouch.y
scrollCell.position = CGPoint(x: initialPosition.x, y: initialPosition.y - moveAmtY)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
checkForResettingSlider()
yMoveActions(moveTo: -moveAmtY)
}
func checkForResettingSlider() {
if resettingSlider { return }
let scrollDif: CGFloat = (scrollCell.size.height - self.size.height) / 2.0
if scrollCell.position.y > scrollDif {
let move: SKAction = SKAction.moveTo(y: scrollDif, duration: 0.3)
move.timingMode = .easeOut
scrollCell.run(move, completion: { self.resettingSlider = false })
}
if scrollCell.position.y < -scrollDif {
let move: SKAction = SKAction.moveTo(y: 0 - scrollDif, duration: 0.3)
move.timingMode = .easeOut
scrollCell.run(move, completion: { self.resettingSlider = false })
}
}
func yMoveActions(moveTo: CGFloat) {
let move: SKAction = SKAction.moveBy(x: 0, y: (moveTo * 1.5), duration: 0.3)
move.timingMode = .easeOut
self.scrollCell.run(move, completion: { self.checkForResettingSlider() })
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is some code that I use to scroll vertically, I've adapted it to fit your menu. It doesn't perfectly line up to all your items, but it'll give you somewhere to start. And it'll show you how to figure this out on your own.
EDIT: I've updated the code use these funds instead. Still declare your buttons the same way but call my createMenu func to actually create the menu. It is looped so it auto adjusts if you change the number of menu items. the only thing you have to be aware of is; if you add or remove buttons change the array at the top of createMenu accordingly. Also adjust the padding variable to how much vertical space you want between the items
func createMenu() {
let buttons = [levelButton1, levelButton2, levelButton3, levelButton4, levelButton5, levelButton6, levelButton7, levelButton8, levelButton9, levelButton10, levelButton11, levelButton12, levelButton13, levelButton14, levelButton15, levelButton16, levelButton17, levelButton18]
let padding: CGFloat = 400
let numberOfRows = CGFloat(buttons.count / 3)
scrollCell = SKSpriteNode(color: .blue, size: CGSize(width: 1024, height: levelButtonSize.height * numberOfRows + padding * numberOfRows))
scrollCell.position = CGPoint(x: 0 - self.size.width / 4, y: 0 - (scrollCell.size.height - self.size.height / 2))
scrollCell.anchorPoint = CGPoint.zero
scrollCell.zPosition = 0
self.addChild(scrollCell)
// let backgroundImage = SKSpriteNode(imageNamed: "bg")
// backgroundImage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
// self.addChild(backgroundImage)
let column1PosX = scrollCell.size.width / 3 / 2
let column2PosX = scrollCell.size.width / 2
let column3PosX = scrollCell.size.width / 3 / 2 + scrollCell.size.width / 3 * 2
var colCount = 0
var rowCount = 0
for button in buttons {
var posX: CGFloat = column2PosX
if colCount == 0 {
posX = column1PosX
}
else if colCount == 2 {
posX = column3PosX
colCount = -1
}
let indexOffset = CGFloat(rowCount) * (levelButtonSize.height + padding)
let posY = scrollCell.size.height - levelButtonSize.height / 2 - (indexOffset + padding / 2)
button.position = CGPoint(x: posX, y: posY)
button.setScale(0.5)
button.zPosition = 10
scrollCell.addChild(button)
if colCount == -1 {
rowCount += 1
}
colCount += 1
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
self.scrollCell.removeAllActions()
initialTouch = touch.location(in: self.scene!.view)
moveAmtY = 0
initialPosition = self.scrollCell.position
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let movingPoint: CGPoint = touch.location(in: self.scene!.view)
moveAmtY = movingPoint.y - initialTouch.y
scrollCell.position = CGPoint(x: initialPosition.x, y: initialPosition.y - moveAmtY)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
checkForResettingSlider()
yMoveActions(moveTo: -moveAmtY)
}
func checkForResettingSlider() {
let topPos: CGFloat = scrollCell.size.height - self.size.height / 2
let bottomPos = 0 - (self.size.height / 2)
if scrollCell.position.y > bottomPos {
let move = SKAction.moveTo(y: bottomPos, duration: 0.3)
move.timingMode = .easeOut
scrollCell.run(move)
}
if scrollCell.position.y < -topPos {
let move = SKAction.moveTo(y: -topPos, duration: 0.3)
move.timingMode = .easeOut
scrollCell.run(move)
}
}
func yMoveActions(moveTo: CGFloat) {
let move = SKAction.moveBy(x: 0, y: (moveTo * 1.5), duration: 0.3)
move.timingMode = .easeOut
self.scrollCell.run(move, completion: { self.checkForResettingSlider() })
}

Swift 3 Expect Argument Error

I am creating some functions in order to make a game similar to Flappy Bird. I am a complete beginner and am trying to understand everything fully as I go before moving on. I have been able to get my obstacle to move but when I attempt to put it into a function to allow me more flexibility later on with multiple obstacles I receive an error.
'Cannot convert type '()' to expected argument type 'SKAction'
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Fire Barrel 1")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = bgColor
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
// Obstacle
func addObstacle1(){
Obstacle1.position = CGPoint(x: self.frame.width / 2, y: -self.frame.height / 2 + Obstacle1.frame.height)
Obstacle1.zPosition = 1
addChild(Obstacle1)
}
func moveObstacle1(){
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
run(moveDistance)
}
addObstacle1()
Obstacle1.run(moveObstacle1())
}
Change the declaration of moveObstacle1 to this:
func moveObstacle1() -> SKAction{
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
return moveDistance
}
EDIT:
Regarding your comment,
run is a method. When you call it, it runs the SKAction you passed in. That's it! What you are trying to do is run(moveObstacle1()). What does that mean exactly? How can you pass a method call as a parameter? At runtime, the return value of moveObstacle1() is passed to run. In other words, for run(moveObstacle1()) to compile, moveObstacle1() must return a value using the return statement. And that value must be of type SKAction, since that's the thing you're passing to run.
return is used to return a value from a moveObstacle1(), so that you can call run(moveObstacle1()).
run is just a regular old method.
Change:
Obstacle1.run()
To:
Obstacle1.run(SKAction(moveObstacle1()))
The error message is pretty clear, you need to pass a SKAction to your SKSpriteNode.
Edit
import SpriteKit
import GameplayKit
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Spaceship")
let sprite = SKSpriteNode(imageNamed:"Spaceship")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = UIColor.green
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
sprite.size = CGSize(width:50, height: 50)
sprite.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
self.addChild(sprite)
Obstacle1.run(SKAction(moveObstacle1()))
}
func moveObstacle1(){
let action = SKAction.moveTo(x: self.frame.size.width * 2, duration: 20)
sprite.run(action)
}
}

Swift 3 Repeat Spawning of SKNode

I have a SKNode that spawns and moves across the screen. It all works as intended. I am, however, unsure how to make this occur multiple times. My desire is for the 'barrels' to spawn every 2 seconds. You can see my attempt in the code below.
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Fire Barrel 1")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = bgColor
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
// Obstacle
func addObstacle1(){
Obstacle1.position = CGPoint(x: self.frame.width / 2, y: -self.frame.height / 2 + Obstacle1.frame.height)
Obstacle1.zPosition = 1
addChild(Obstacle1)
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
Obstacle1.run(moveDistance)
}
let spawnObstacles = SKAction(addObstacle1())
let delay = SKAction.wait(forDuration: 2)
let sequence = SKAction.sequence([delay, spawnObstacles])
run(SKAction.repeatForever(sequence))
}
Begin with putting the addObstacle outside of didMove method. Then put the following in your didMove method:
_ = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(addObstacle1), userInfo: nil, repeats: true)
It should repeat your addObstacle every two seconds :)