Locking scene to a sprite node swift xcode - ios

Basically, I want to move the character (player) and have the “camera” or scene follow them in the world. The problem is Apple/xcode does not have a built in camera node and if I try to move the scene, I get an error saying something to the effect of “SKScene cannot be moved.” I don’t want to just move the background around and lock the player in place because I want to be able to apply forces to the player node. I have gone to Apple’s website to the Advanced Scene Processing page and followed the example but it doesn’t work. I just need a nudge in the right direction. Thanks!
Code:
(I have deleted pieces of code that are irrelevant)
class PlayScene: SKScene, SKPhysicsContactDelegate {
let world = SKNode()
let player = SKShapeNode(circleOfRadius: 25)
var cannonRotation = 0.0
var fired = false
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.addChild(world)
//Adds the background to the world
//Adds the elements to the scene
player.fillColor = UIColor.whiteColor()
player.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMinY(self.frame)+65+25)
player.zPosition = 2
player.physicsBody = SKPhysicsBody(circleOfRadius: 25)
player.physicsBody?.dynamic = true
player.physicsBody?.restitution = 0.9
player.physicsBody?.categoryBitMask = BodyType.player.rawValue
//Adds the physics properties to GameScene
let physicsGround = SKPhysicsBody(edgeFromPoint: CGPoint(x: CGRectGetMinX(self.frame), y: CGRectGetMinY(self.frame)+65), toPoint: CGPoint(x: CGRectGetMaxX(self.frame), y: CGRectGetMinY(self.frame)+65))
self.physicsBody = physicsGround
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
powerLevel()
totalPower = powerReached * playCannon.powerMultiplier
self.addChild(player)
player.physicsBody?.applyImpulse(CGVector(dx: CGFloat(Float(totalPower)*cosf(Float(cannonRotation))), dy: CGFloat(Float(totalPower)*sinf(Float(cannonRotation)))))
fired = true
}

Override didsimulatePhysics, And in that function call this
self.centerOnNode(player);
What that does, It calles a function we will later create and it sendes a parameter of an SKNode and in your case it is the player node. Next create a method called centerOnNede and add the following parameters
func centerOnNode(node: SKNode) {
}
In the centerOnNode function add this following line of code.
var cameraPosition: CGPoint = node.scene?.convertPoint(node.position, fromNode: node.parent!)
cameraPosition.x = 0
let posX = node.parent?.position.x
let posY = node.parent?.position.y
node.parent?.position = CGPointMake(posX! - cameraPosition.x, posY! - cameraPosition.y)
That will center the camera on to the node and update it's position. Code converted from here. How to make camera follow SKNode in Sprite Kit?

There is a detailed example of how to do this in the Apple Adventure reference game. It has both Swift and Objective-C examples.
In summary what they recommend is that you
Put all nodes on a background node (SKNode) container.
implement didSimulatePhysics and move the world in the opposite direction of the player. That will keep the player in the middle of the screen. Special logic will be needed if you want the camera to bump into the edge of your world.
A camera node would be very helpful, but has not been implemented into SpriteKit.

As of iOS 9 there is now an SKCameraNode that does all the heavy lifting for you.
Simply add these three lines of code in your didMoveToView function:
let cameraNode = SKCameraNode()
self.addChild(cameraNode)
self.camera = cameraNode
Then in your didFinishUpdate function just set the cameraNode's position to whatever SKSpriteNode you want it to follow.

Related

How to move sprite just left or right using CoreMotion?

For this game I am creating, I want the Sprite "Character" to either move left or right by me tilting the device either left or right.
I have looked into it and changed the code, but it seems like that it does not work.
When I run it, the "Character" moves in only one direction and still goes either up or down.
I would just like the "Character" to move only left or right, not up or down.
Any help on this?
Here is a look at the GameScene.swift file:
import SpriteKit
import CoreMotion
class GameScene: SKScene {
let motionManager = CMMotionManager()
var Ground = SKSpriteNode()
var Character = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
Character = SKSpriteNode(imageNamed: "NinjaGhost")
Character.physicsBody = SKPhysicsBody(circleOfRadius: Character.size.height)
Character.size = CGSize(width: 200, height: 200)
Character.physicsBody?.allowsRotation = false
Character.zPosition = 2
Character.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.addChild(Character)
Ground = SKSpriteNode(imageNamed: "Dirt Background")
Ground.size = CGSize(width: 1920, height: 1080)
Ground.zPosition = 1
Ground.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.addChild(Ground)
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue() ) {
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * 1, 0)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You should follow the swift guidelines, your properties should not start with capital letters, only classes, structs, enums and protocols should. It makes code harder to read on stack overflow (code is marked blue but shouldn't) and in general its not good practice.
Change your code to this because there was a few errors.
character = SKSpriteNode(imageNamed: "NinjaGhost")
character.size = CGSize(width: 200, height: 200) // Better to call this before positioning
character.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2) // Give pos before physics body
character.physicsBody = SKPhysicsBody(circleOfRadius: character.size.width / 2) // You have to divide by 2 when using circleOfRadius to get proper size in relation to the sprite
character.physicsBody?.allowsRotation = false
character.physicsBody?.affectedByGravity = false // Stop falling
character.zPosition = 2
self.addChild(character)
In your motion queue code you are manipulating the scene itself, so that will not work.
Try this code instead which uses the physics body to move the sprite. This is my preferred way of doing it because it gives the sprite more natural movement, especially when changing direction, compared to directly manipulating the sprites position property (If you want to manipulate the sprite position property directly than read this article. http://www.ioscreator.com/tutorials/move-sprites-accelerometer-spritekit-swift)
if let error = error { // Might as well handle the optional error as well
print(error.localizedDescription)
return
}
guard let data = motionManager.accelerometerData else { return }
if UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeLeft {
character.physicsBody?.applyForce(CGVectorMake(-100 * CGFloat(data.acceleration.y), 0))
} else {
character.physicsBody?.applyForce(CGVectorMake(100 * CGFloat(data.acceleration.y), 0))
}
This will make the sprite move on the x axis. I am using data.acceleration.y because in this example the game is in landscape so you have to use the Y data to move it on the x axis.
I am also checking in which landscape orientation the device is so that the motion works in both orientations.
Adjust the 100 here to get the desired force depending on the size of the sprite (higher is more force). Big sprites properly need a few 1000s to move.

SKFieldNode with circular region only affecting bodies when center is within region

Ok so from the SKFieldNode documentation, under the region property:
A field node applies its effect to all physics bodies that are partially or completely inside its region. The default value is a region of infinite size.
From this I understand that once a part of the physics body attached to a node enters the SKRegion on the field node ("partially or complete inside the region"), the field will start acting on the body.
So I started using the fields but came across the fact that they only start acting on the physics body when the body's center is within the region, not any part of the body.
Here is the code I wrote:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
let field = SKFieldNode.springField()
field.position = view.center
let shape = SKShapeNode(circleOfRadius: size.height / 3)
shape.fillColor = SKColor.grayColor()
field.region = SKRegion(path: shape.path!)
field.addChild(shape)
addChild(field)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
let ball = SKShapeNode(circleOfRadius: size.height / 10)
ball.position = location
ball.strokeColor = SKColor.blackColor()
ball.physicsBody = SKPhysicsBody(circleOfRadius: size.height / 10)
let center = SKShapeNode(circleOfRadius: 1)
center.strokeColor = SKColor.blackColor()
ball.addChild(center)
self.addChild(ball)
ball.physicsBody!.applyImpulse(CGVectorMake(60, 60))
}
}
}
(I added the shape node to the field to see where it`s region ends, and added the center to the ball to make it clear when it runs that the field only acts once the center is in the region.)
When I run it the field only starts acting on the balls when their center is within the region. It seems to me this is not what should happen based on the documentation. I haven't changed any of the field node's properties before adding it, so I expected the standard behavior.
I don't know it I am misinterpreting the documentation or there is some other property I have to set on the field for it to act on any physics body that is partially within its region.
A walkaround to let ball be effected by field when they touched immediately is to enlarge the region (radius) of field. Something like:
field.region = SKRegion(radius: shapeRadius + ballRadius)

SKPhysicsBody scale with parent scaling

I have a node, A, in a parent node, B.
I'm using setScale on node B to give the illusion of zooming, but when I do that, node A and all of its siblings do not have the expected physics bodies.
Even though A appears smaller and smaller, its physics body stays the same size. This leads to wacky behavior.
How can I "zoom out" and still have sensible physics?
SKPhysicsBody cannot be scaled. As a hack you could destroy the current physics body and add a smaller one as you scale up or down but unfortunately this is not a very efficient solution.
The SKCameraNode (added in iOS9) sounds perfect for your scenario. As opposed changing the scale of every node in the scene, you would change the scale of only the camera node.
Firstly, you need to add an SKCameraNode to your scene's hierarchy (this could all be done in the Sprite Kit editor too).
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let camera = SKCameraNode()
camera.position = CGPoint(x: size.width / 2, y: size.height / 2)
self.addChild(camera)
self.camera = camera
}
Secondly, since SKCameraNode is a node you can apply SKActions to it. For a zoom out effect you could do:
guard let camera = camera else { return }
let scale = SKAction.scaleBy(2, duration: 5)
camera.runAction(scale)
For more information have a look at the WWDC 2015 talk: What's New In Sprite Kit.
A simple example that shows that a physics body scales/behaves appropriately when its parent node is scaled. Tap to add sprites to the scene.
class GameScene: SKScene {
var toggle = false
var node = SKNode()
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame)
scaleMode = .ResizeFill
view.showsPhysics = true
addChild(node)
let shrink = SKAction.scaleBy(0.25, duration: 5)
let grow = SKAction.scaleBy(4.0, duration: 5)
let action = SKAction.sequence([shrink,grow])
node.runAction(SKAction.repeatActionForever(action))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(node)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
if (toggle) {
sprite.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width/2.0)
}
else {
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
}
sprite.xScale = 0.125
sprite.yScale = 0.125
sprite.position = location
node.addChild(sprite)
toggle = !toggle
}
}
}

Centering the camera on the character

I am a beginner and I have started my first project. It should be a little game where you can run to the left and to the right. But I don't know how to set the camera settings.
I'd be very thankful if you could help me, tell me how I can improve my code and what is wrong.
Here is my Code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground:SKSpriteNode = SKSpriteNode()
var hero: MPCharacter!
var tree:MPTree!
override func didMoveToView(view: SKView) {
backgroundColor = UIColor(red: 155.0/255.0, green: 201.0/255.0, blue: 244.0/255.0, alpha: 1.0)
self.anchorPoint = CGPointMake(0.5, 0.5)
// --> hero <--
hero = MPCharacter()
hero.position = CGPointMake(frame.size.width/2, view.frame.size.height/2)
hero.zPosition = 100
hero.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(hero.size.width, hero.size.height))
hero.physicsBody!.dynamic = true
addChild(hero)
// --> ground <--
ground = SKSpriteNode(imageNamed: "ground")
ground.size = CGSizeMake(frame.size.width, frame.size.height/3)
ground.position = CGPointMake(frame.size.width/2, frame.size.height/2 - ground.size.height)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(frame.size.width, ground.size.height - 25))
ground.physicsBody!.dynamic = false
addChild(ground)
// --> Tree <--
tree = MPTree()
tree.position = CGPointMake(frame.size.width/2, ground.size.height + tree.size.height/3 - 10)
tree.zPosition = 10
tree.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(tree.size.width, tree.size.height - 10))
tree.physicsBody!.dynamic = false
addChild(tree)
// --> Physics <--
self.physicsWorld.gravity = CGVectorMake(0, -5.0)
self.physicsWorld.contactDelegate = self
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame) {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(-5, 10))
}
else {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(5, 10))
}
}
}
}
Im not quite sure what you are trying to achieve. One way to make your character 'run' to the left (or right) is by moving a background (and other game objects) to the left when the character is running right (and vice versa). This way your character will always be in the center of the screen, because he is not really moving, you create an 'illusion' that he is moving in your gameworld. Giving your character a run animation will make it look even better...
There is a tutorial on the website of Ray Wenderlich which also covers 'parallax scrolling':
http://www.raywenderlich.com/49625/sprite-kit-tutorial-space-shooter
Hope this helps..
If you're trying to center the camera on the player, Apple Docs provide you a nice example for how to do so over here (I would add code, but the best way to learn is to get your hands dirty). You might have to translate the methods to swift, but it is the general implementation for what you wish to do. The problem with the answer above is that if you would move the background, you would have to calculate the forces and the impulses directly and apply the inverse vectors to the background. This might require more processing than with simply moving the camera. Hope this helps!!

SKPhysics with Swift and SpriteKit

I am trying to create a iOS game using Swift Programming Language.
I want to use swipe gestures on the screen to make the character (player) move up, down, left and right
So far so good. But I want some physics. I want the player to collide with the edges of the screen. (and later I want it to collide with other stuff, but I need to know the code first ;)
QUESTION: How do I identify a collision between 2 objects (player and edge of screen)
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player:SKSpriteNode = SKSpriteNode()
let moveUp = SKAction.moveByX(0, y:2000, duration:10.0) //it is set to 2000 because I want to see if it collides with the edge of the screen
let moveDown = SKAction.moveByX(0, y: -200, duration: 2.0)
// Define the bitmasks identifying various physics objects
let worldCategory:UInt32 = 0x1 << 0
let playerCategory:UInt32 = 0x1 << 1
override func didMoveToView(view: SKView) {
}
override init(size:CGSize) {
super.init(size: size)
self.backgroundColor = SKColor.whiteColor()
player = SKSpriteNode(imageNamed: "Kundvagn1")
player.position = CGPointMake(self.frame.size.width/2, player.size.height/2 + 20)
self.addChild(player)
player.runAction(moveUp)
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.dynamic = true //I dont think the question marks should be there, but Xcode doesn´t accept the code if there is no question marks there
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = worldCategory
player.physicsBody?.collisionBitMask = 0
player.physicsBody?.usesPreciseCollisionDetection = true
}
func didBeginContact(contact: SKPhysicsContact!) {
println("Collision")
}
Assuming that you want the left and right edges to block, simply add two SKShapeNode rectangular objects to your scene, set their heights to the screen height, and place them just beyond the left and right edges of the screen. Set up their physics bodies and you should be good to go.
If you want all the edges of the screen to be boundaries, you need to setup a line-based physics body for your scene, as detailed here: Add boundaries to an SKScene
I added this in case you need a code example to jonogilmour's instructions . Turn on the ShowsPhysics = true in your viewController to see physics. Both sides should be blue. If you want a contact notification of course you would add to this code with your bit mask stuff. Hope this helps!
var leftEdge = SKNode()
leftEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointZero, toPoint: CGPointMake(0.0, self.size.height))
leftEdge.position = CGPointZero;
self.addChild(leftEdge)
var rightEdge = SKNode()
rightEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointZero, toPoint: CGPointMake(0.0, self.size.height))
rightEdge.position = CGPointMake(self.size.width, 0.0);
self.addChild(rightEdge)

Resources