how to get multiple spriteNodes to jump when screen is tapped - ios

I have this line of code, to make 3 spriteNodes, i would like them to jump up the screen when I tap anywhere on the screen, but not to follow where the screen is tapped, i can get a single spriteNode to do it, using
applyImpluse in touchBegan, i will be using the accelerometer to move x axis, any help will be greatly appreciated,
let numberOfBalls = 3
let ballWidth = SKSpriteNode(imageNamed: "ball6").size.width
let totalBallWidth = ballWidth * CGFloat(numberOfBalls)
ball6.size = CGSize(width: 50, height: 50)
let xOffset = (CGRectGetWidth(frame) - totalBallWidth)/2
for i in 0..<numberOfBalls{
let ball6 = SKSpriteNode(imageNamed: "ball6")
ball6.position = CGPoint(x: xOffset + CGFloat(CGFloat(i) / 2) * ballWidth, y: CGRectGetHeight(frame) / 2)
self.addChild(ball6)
ball6.physicsBody = SKPhysicsBody(circleOfRadius: ball6.frame.size.width/2)
ball6.physicsBody?.friction = 0.5
ball6.physicsBody?.restitution = 0.8
ball6.physicsBody?.mass = 0.2
ball6.physicsBody?.allowsRotation = true
ball6.physicsBody?.dynamic = true
ball6.physicsBody?.affectedByGravity = true

Related

How to add to a variable in SpriteKit

Relatively new to swift so apologies if I'm being stupid.
I'm using Xcode 13.3 beta 3
I have some code which spawns in a sprite named Monster and moves it from the right of the screen to the left at random speeds and at a random y location.
When it moves off screen it removes from parent and transitions to a loose screen.
what I want it to do when it leaves the screen is for it to add to a variable I've defined at the top of the class:
var pass = 0
here's the code for the monsters:
func addMonster() {
let monster = SKSpriteNode(imageNamed: "monster")
monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size)
monster.physicsBody?.isDynamic = true
monster.physicsBody?.categoryBitMask = PhysicsCatagory.monster
monster.physicsBody?.contactTestBitMask = PhysicsCatagory.projectile
monster.physicsBody?.collisionBitMask = PhysicsCatagory.none
let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
addChild(monster)
let actualDuration = random(min: CGFloat(1.65), max: CGFloat(5.5))
let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY), duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let loseAction = SKAction.run() { [weak self] in
guard let `self` = self else {return}
let end = SKTransition.crossFade(withDuration: 0.5)
let gameOverScene = GameOverScene(size: self.size, won: false)
self.view?.presentScene(gameOverScene, transition: end)
}
monster.run(SKAction.sequence([actionMove,loseAction, actionMoveDone]))
}
what I want is the loose action to just add +1 to the pass variable then have an if statement for when pass is greater than 3 to run the code that goes to the loose screen.
Hope that all makes sense
any help would be much appreciated!

Spritekit Camera Node scale but pin the bottom of the scene

I have a camera node that is scaled at 1. When I run the game, I want it to scale it down (i.e. zoom out) but keep the "floor" at the bottom. How would I go about pinning the camera node to the bottom of the scene and effectively zooming "up" (difficult to explain). So the bottom of the scene stays at the bottom but the rest zooms out.
I have had a go with SKConstraints but not having any luck (I'm quite new at SpriteKit)
func setConstraints(with scene: SKScene, and frame: CGRect, to node: SKNode?) {
let scaledSize = CGSize(width: scene.size.width * xScale, height: scene.size.height * yScale)
let boardContentRect = frame
let xInset = min((scaledSize.width / 2), boardContentRect.width / 2)
let yInset = min((scaledSize.height / 2), boardContentRect.height / 2)
let insetContentRect = boardContentRect.insetBy(dx: xInset, dy: yInset)
let xRange = SKRange(lowerLimit: insetContentRect.minX, upperLimit: insetContentRect.maxX)
let yRange = SKRange(lowerLimit: insetContentRect.minY, upperLimit: insetContentRect.maxY)
let levelEdgeConstraint = SKConstraint.positionX(xRange, y: yRange)
if let node = node {
let zeroRange = SKRange(constantValue: 0.0)
let positionConstraint = SKConstraint.distance(zeroRange, to: node)
constraints = [positionConstraint, levelEdgeConstraint]
} else {
constraints = [levelEdgeConstraint]
}
}
then calling the function with:
gameCamera.setConstraints(with: self, and: scene!.frame, to: nil)
(This was code from a tutorial I was following) The "setConstraints" function is an extension of SKCameraNode
I'm not sure this will give me the correct output, but when I run the code to scale, it just zooms from the middle and shows the surrounding area of the scene .sks file.
gameCamera.run(SKAction.scale(to: 0.2, duration: 100))
This is the code to scale the gameCamera
EDIT: Answer below is nearly what I was looking for, this is my updated answer:
let scaleTo = 0.2
let duration = 100
let scaleTop = SKAction.customAction(withDuration:duration){
(node, elapsedTime) in
let newScale = 1 - ((elapsedTime/duration) * (1-scaleTo))
let currentScaleY = node.yScale
let currentHeight = node.scene!.size.height * currentScaleY
let newHeight = node.scene!.size.height * newScale
let heightDiff = newHeight - currentHeight
let yOffset = heightDiff / 2
node.setScale(newScale)
node.position.y += yOffset
}
You cannot use a constraint because your scale size is dynamic.
Instead you need to move your camera position to give the illusion it is only scaling in 3 directions.
To do this, I would recommend creating a custom action.
let scaleTo = 2.0
let duration = 1.0
let currentNodeScale = 0.0
let scaleTop = SKCustomAction(withDuration:duration){
(node, elapsedTime) in
if elapsedTime == 0 {currentNodeScale = node.scale}
let newScale = currentNodeScale - ((elapsedTime/duration) * (currentNodeScale-scaleTo))
let currentYScale = node.yScale
let currentHeight = node.scene.size.height * currentYScale
let newHeight = node.scene.size.height * newScale
let heightDiff = newHeight - currentHeight
let yOffset = heightDiff / 2
node.scale(to:newScale)
node.position.y += yOffset
}
What this is doing is comparing the new height of your camera with the old height, and moving it 1/2 the distance.
So if your current height is 1, this means your camera sees [-1/2 to 1/2] on the y axis. If you new scale height is 2, then your camera sees [-1 to 1] on the y axis. We need to move the camera up so that the camera sees [-1/2 to 3/2], meaning we need to add 1/2. So we do 2 - 1, which is 1, then go 1/2 that distance. This makes our yOffset 1/2, which you add to the camera.

Trying to animate a SKNode inside a UIView

I have programmatically created a SKView inside of my UIView. Now I want to animate a new SKNodes in all directions, starting in the middle (this works) depending on the view that is passed through. All works fine except for the fact that the ending position of the SKNode is weird. Instead of shooting in all directions, it is going inside of a corner, way out of the view's boundaries. It should never go out of the view's boundaries. I am converting the CGPoint to my scene from the View.
This is my code:
func animateExplosion(sender: UIButton){
let star = SKSpriteNode(imageNamed: "ExplodingStar")
let starHeight = sender.frame.height / 3
star.size = CGSize(width: starHeight, height: starHeight)
var position = sender.frame.origin
position = self.sceneScene.convertPoint(fromView: position)
star.position = position
let minimumDuration = 1
let maximumDuration = 2
let randomDuration = TimeInterval(RandomInt(min: minimumDuration * 100, max: maximumDuration * 100) / 100)
let fireAtWill = SKAction.move(to: getRandomPosition(view: sender), duration: randomDuration)
let rotation = SKAction.rotate(byAngle: CGFloat(randomAngle()), duration: Double(randomDuration))
let fadeOut = SKAction.fadeOut(withDuration: randomDuration)
let scaleTo = SKAction.scale(to: starHeight * 2, duration: randomDuration)
let group = SKAction.group([fireAtWill, rotation]) //for testing no fade out or remove parent
let sequence = SKAction.sequence([group])
if randomAmountOfExplodingStars > 0{
randomAmountOfExplodingStars -= 1
sceneScene.addChild(star)
star.run(sequence)
animateExplosion(sender: sender)
}
}
the getRandomPosition where the bug properly is:
func getRandomPosition(view: UIView) -> CGPoint{
let direction = RandomInt(min: 1, max: 4)
var randomX = Int()
var randomY = Int()
if direction == 1{
randomX = Int(view.frame.width / 2)
randomY = RandomInt(min: -Int(view.frame.height / 2), max: Int(view.frame.height / 2))
}
if direction == 2{
randomX = RandomInt(min: -Int(view.frame.width / 2), max: Int(view.frame.width / 2))
randomY = Int(view.frame.height / 2)
}
if direction == 3{
randomX = -Int(view.frame.width / 2)
randomY = RandomInt(min: -Int(view.frame.height / 2), max: Int(view.frame.height / 2))
}
if direction == 4{
randomX = RandomInt(min: -Int(view.frame.width / 2), max: Int(view.frame.width / 2))
randomY = -Int(view.frame.height / 2)
}
var randomPosition = CGPoint(x: randomX, y: randomY)
//randomPosition = self.sceneScene.convertPoint(fromView: randomPosition)
return randomPosition
}
I know that code looks awful, but it should do the trick right? The passed through view is a UIButton inside of a UIView. The SKView shares exactly the same constrains as that UIView. The animation should start in the middle and end somewhere to the boundaries of the passed view.
Yay got it to work finally. So dumb I did not notice before. The sender would be always a child inside of a UIView which is smaller. The return function was great, but the SKNode should not move to the returned function, but moved by.
Updated code:
let randomPositionOfSender = getRandomPosition(view: sender)
let fireAtWill = SKAction.moveBy(x: randomPositionOfSender.x, y: randomPositionOfSender.y, duration: randomDuration)

Adding AnchorPoint to SKNode breaks SKScene positioning

I am trying to have my SKCameraNode start in the bottom left corner, and have my background anchored there as well. When I set the anchor point to CGPointZero, here is what my camera shows:
EDIT:
Interestingly, If I set my AnchorPoint to CGPoint(x:0.5, y:0.2), I get it mostly lined up. Does it have to do with the camera scale?
EDIT 2:
If I change my scene size, I can change where the background nodes show up. Usually they appear with their anchor point placed in the center of the screen, which implies the anchorPoint of the scene is in the center of the screen.
I am new to using the SKCameraNode, and so I am probably setting it's constraints incorrectly.
Here are my camera constraints: I don't have my player added yet, but I want to set my world up first before I add my player. Again I am trying to have everything anchored off CGPointZero.
//Camera Settings
func setCameraConstraints() {
guard let camera = camera else { return }
if let player = worldLayer.childNodeWithName("playerNode") as? EntityNode {
let zeroRange = SKRange(constantValue: 0.0)
let playerNode = player
let playerLocationConstraint = SKConstraint.distance(zeroRange, toNode: playerNode)
let scaledSize = CGSize(width: SKMViewSize!.width * camera.xScale, height: SKMViewSize!.height * camera.yScale)
let boardContentRect = worldFrame
let xInset = min((scaledSize.width / 2), boardContentRect.width / 2)
let yInset = min((scaledSize.height / 2), boardContentRect.height / 2)
let insetContentRect = boardContentRect.insetBy(dx: xInset, dy: yInset)
let xRange = SKRange(lowerLimit: insetContentRect.minX, upperLimit: insetContentRect.maxX)
let yRange = SKRange(lowerLimit: insetContentRect.minY, upperLimit: insetContentRect.maxY)
let levelEdgeConstraint = SKConstraint.positionX(xRange, y: yRange)
levelEdgeConstraint.referenceNode = worldLayer
camera.constraints = [playerLocationConstraint, levelEdgeConstraint]
}
}
I have been using a Udemy course to learn the SKCameraNode, and I have been trying to modify it.
Here is where I set the SKMViewSize:
convenience init(screenSize: CGSize, canvasSize: CGSize) {
self.init()
if (screenSize.height < screenSize.width) {
SKMViewSize = screenSize
}
else {
SKMViewSize = CGSize(width: screenSize.height, height: screenSize.width)
}
SKMSceneSize = canvasSize
SKMScale = (SKMViewSize!.height / SKMSceneSize!.height)
let scale:CGFloat = min( SKMSceneSize!.width/SKMViewSize!.width, SKMSceneSize!.height/SKMViewSize!.height )
SKMUIRect = CGRect(x: ((((SKMViewSize!.width * scale) - SKMSceneSize!.width) * 0.5) * -1.0), y: ((((SKMViewSize!.height * scale) - SKMSceneSize!.height) * 0.5) * -1.0), width: SKMViewSize!.width * scale, height: SKMViewSize!.height * scale)
}
How can I get both the camera to be constrained by my world, and have everything anchored to the CGPointZero?

Detecting collision with a child of a node and another node in Swift 2

So I created a parent node named planet, that has to two children one of which is a 'landingPad'. I'm trying to detect collision between this child and another node named 'lander'. I've tried a lot of options but nothing seems to work. Thanks in advance.
func createPlanet() {
var planet = SKSpriteNode()
planet.zPosition = 1
planet.name = "mars"
redPlanet = SKSpriteNode(imageNamed: "redPlanet")
redPlanet.name = "red"
redPlanet.zPosition = 2
planet.addChild(redPlanet)
landingPad = SKSpriteNode(imageNamed: "landingPad")
landingPad.name = "pad"
landingPad.zPosition = 3
landingPad.position = CGPoint(x: 0, y: redPlanet.size.height / 2 - 60)
landingPad.physicsBody = SKPhysicsBody(texture: landingPad.texture!, size: size)
landingPad.physicsBody!.dynamic = false
landingPad.physicsBody!.categoryBitMask = landingPadMask
landingPad.physicsBody!.collisionBitMask = landerMask
landingPad.physicsBody!.contactTestBitMask = 0
planet.addChild(landingPad)
planet.position = CGPoint(x: frame.size.width / 2, y: -redPlanet.size.height / 6)
planet.physicsBody = SKPhysicsBody(circleOfRadius: redPlanet.size.height / 2)
planet.physicsBody!.dynamic = false
planet.physicsBody!.categoryBitMask = planetMask
planet.physicsBody!.contactTestBitMask = 0
let spinner = SKAction.rotateByAngle(CGFloat(M_PI), duration: 3)
planet.runAction(SKAction.repeatActionForever(spinner))
addChild(planet)
}
And the lander code...
func createLander() {
var randomX = RandomInt(min: 10, max: 400)
lander = SKSpriteNode(imageNamed: "lander")
lander.position = CGPoint(x: CGFloat(randomX), y: frame.size.height + 20)
lander.name = "lander"
lander.zPosition = 10
lander.physicsBody = SKPhysicsBody(texture: lander.texture!, size: lander.size)
lander.physicsBody!.dynamic = true
lander.physicsBody!.affectedByGravity = true
lander.physicsBody!.friction = 0.2
lander.physicsBody!.restitution = 0.75
lander.physicsBody!.linearDamping = 0.208
lander.physicsBody!.angularDamping = 0.1
lander.physicsBody!.categoryBitMask = landerMask
lander.physicsBody!.collisionBitMask = landingPadMask | cometMask
addChild(lander)
}
you are missing:
lander.physicsBody!.contactTestBitMask = landingPadMask | cometMask
and
landingPad.physicsBody!.contactTestBitMask = landerMask
And for the delegate:
self.physicsWorld.contactDelegate = self;
Then you get the collision with:
didBeginContact and didEndContact

Resources