SKCameraNode working weird - swift

I'm trying to create a simple SpriteKit game. I have a player that starts from the bottom-center of the screen.
I added to my GameScene a camera to follow the player but it does not work. when I run it with this code:
class GameScene: SKScene {
let mPlayer = Player(circleOfRadius: 45, fillColor: myColors.blue, strokeColor: myColors.red)
let cameraNode = SKCameraNode()
override func didMove(to view: SKView) {
setup()
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
moveCamera()
Player.movePlayerUp(mPlayer: mPlayer)
}
func setup(){
mPlayer.position = CGPoint(x: self.frame.midX - PLAYER_WIDTH, y: self.frame.minY + PLAYER_HEIGHT)
self.camera?.position = mPlayer.position
self.camera = cameraNode
self.addChild(cameraNode)
self.addChild(mPlayer)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
mPlayer.run(SKAction.moveTo(x: location.x, duration: 0.1))
}
}
func moveCamera(){
let moveAction = SKAction.moveTo(y: mPlayer.position.y, duration: 0.1)
cameraNode.run(moveAction)
}
}
the player is starting from the bottom and immediately moves to the center of the screen like in this picture:
How can I make the camera to follow the player from the start and move with it to the top?
Hope my question is clear and sorry for the size of the picture.

This code works for me in a empty Xcode project. You are probably thinking its not because your background image is 1 color and you are only moving the player briefly.
Also in your update method you set to move the player constantly to the middle, which is not necessary.
Try adding some stripes to the background to make it easier to see if you move and than add this code to the update method do slowly move the player up constantly.
let action = SKAction.move(by: CGVector(dx: 0, dy: 5), duration: 1)
mPlayer.run(action)
You will see the camera is following the player correctly.
If you need to center your camera differently to the player e.g on the y axis than you just have to increase/decrease the y position of the camera.
let moveAction = SKAction.moveTo(y: mPlayer.position.y + 200, duration: 0.1)
cameraNode.run(moveAction)
Hope this helps

Related

Changing the angle of a SKSpriteNode during a rotation

I am trying to make a drifting game. In order for the car to drift, the car (when turning) needs to be at an angle. I have tried rotation, however this conflicts with the code I already have for turning the car. Here is my code, any help?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.previousLocation(in: self)
let position = touch.location(in: self)
let node = self.nodes(at: location).first
if position.x < 0 {
let rotate = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI), duration: 0.8))
car.run(rotate, withKey: "rotating")
} else {
let rotate = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(-M_PI), duration: 0.8))
car.run(rotate, withKey: "rotating")
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
car.position = CGPoint(x:car.position.x + cos(car.zRotation) * 3.0,y:car.position.y + sin(car.zRotation) * 3.0)
}
}
This code currently turns the car without adding any angle, or "drifting" effect.
one approach is you could nest your car inside another SKNode as a container. apply your steering rotation to the car node, as you're doing it now. then apply the drift rotation to the container node. the result effect will be the sum of the two.
//embed car inside a SKNode container so you can apply different rotations to each
let car = SKShapeNode(ellipseOf: CGSize(width: 20, height: 40)) //change to how you draw your car
let drift_container = SKNode()
drift_container.addChild(car) //embed car inside the container
self.addChild(drift_container) //`self` here is a SKScene
//apply angle rotation to the container
func drift(byAngle angle:CGFloat) {
let rotate = SKAction.rotate(toAngle: angle, duration: 0.2)
drift_container.run(rotate)
}
then in update be sure to update drift_container.position instead of the car's position

Is there a way I can add more than one sprite in SpriteKit globally?

I am struggling with one issue. Global declaration of my sprite so that I can interact with it. In this game, I have created a local sprite called enemy featured below:
func spawnEnemy() {
let enemy = SKSpriteNode(imageNamed: "as")
let yPosition = CGFloat(frame.maxY - enemy.size.height)
let getXvalue = GKRandomDistribution(lowestValue: Int(frame.minX + enemy.size.width), highestValue: Int(frame.maxX - enemy.size.width))
let xPosition = CGFloat(getXvalue.nextInt())
enemy.position = CGPoint(x: xPosition, y: yPosition)
enemy.name = "asteroid"
enemy.zPosition = 100
addChild(enemy)
let animationDuration:TimeInterval = 6
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: xPosition, y: 0), duration: animationDuration))
actionArray.append(SKAction.self.removeFromParent())
enemy.run(SKAction.sequence(actionArray))
}
I want to tap the enemy to make it disappear from the screen. The variable is declared locally and not globally so the touchesBegan function does not "see" enemy. However, when I move the statement:
let enemy = SKSpriteNode(imageNamed: "as")
outside of local declaration and into global. It works until the code tries to spawn in another enemy and i get an error of "Tried to add an SKNode who already has a parent" This is the code I have running in my view did load:
run(SKAction.repeatForever(SKAction.sequence([SKAction.run{self.spawnEnemy()
}, SKAction.wait(forDuration: 1.0)])))
Every time it spawns a new enemy it crashes and says that the SKNode already has a parent which i understand. However, for my game to function I need the player to be able to touch the individual instance of that enemy and remove it. Hence my code for
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in:self) {
let nodesArray = self.nodes(at:location)
if nodesArray.first?.name == "asteroid" {
print("Test")
enemy.removeFromParent()
print("Test Completed")
}
}
}
Now the error says unresolved use of "enemy" because the enemy is not global. I have been going back and forth on this issue for quite some time. If anyone has any potential solution or work around I would be very grateful, and thank you for your help.
Move your enemies to their own class and handle the touch for each of those enemies in their own class. This cleans up your GameScene and keeps your code more organized. You can now add as many instances of enemy as you want.
FYI not related to this question but somethings to consider after you get this working
when game over or level change or win make sure you have a clean up function to remove all enemies
you should strongly consider recycling your objects vs creating them on the fly...better performance
try to separate as much code to your objects class as possible
class enemy: SKSpriteNode {
init() {
super.init(texture: nil, color: .clear, size: CGSize.zero)
setup()
}
func setup() {
isUserInteractionEnabled = true
name = "asteroid"
zPosition = 100
let image = SKSpriteNode(imageNamed: "as")
imagine.zPosition = 1
addChild(image)
self.size = image.size
animate()
}
func animate() {
let animationDuration: TimeInterval = 6
let move = SKAction.move(to: CGPoint(x: xPosition, y: 0), duration: animationDuration)
let remover = SKAction.self.removeFromParent()
run(SKAction.sequence(move, remover))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
removeFromParent()
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
let sequence = SKAction.sequence([SKAction.run{ self.spawnEnemy()
}, SKAction.wait(forDuration: 1.0)])
run(SKAction.repeatForever(sequence))
}
func spawnEnemy() {
let enemy = Enemy()
let yPosition = CGFloat(frame.maxY - enemy.size.height)
let getXvalue = GKRandomDistribution(lowestValue: Int(frame.minX + enemy.size.width), highestValue: Int(frame.maxX - enemy.size.width))
let xPosition = CGFloat(getXvalue.nextInt())
enemy.position = CGPoint(x: xPosition, y: yPosition)
addChild(enemy)
}
}

How Make a Sprite Node Rotate Around Another Node? (One More Line)

So, I am trying to challenge my self by recreating the popular App Store game, One More Line using SpriteKit and Swift 3; however, I am immediately having a big issue.
1)How do I get my node to rotate around another node when the user is tapping
2) I want my node to continue moving in the same direction it is facing when the user releases their finger (hope that makes sense...).
kinda like this
In my GameScene.sks I created a center node that is constantly rotating and a player. Then I did this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
player.move(toParent: center)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
player.move(toParent: self)
}
override func update(_ currentTime: TimeInterval) {
let dx = 5 * cos(player.zRotation)
let dy = 5 * sin(player.zRotation)
player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy))
}
player moves to the right and when I tap it circles around the node, when I release it continues to move to the right.
I know that I am going about this all wrong. Any help would be very much appreciated. Thanks!
I'm not sure I completely understand what you are looking for, but if you simply want a sprite to rotate evenly around a centre sprite, add the centre sprite, another centre sprite (invisible) and add your outer sprite to the invisible centre sprite. Then rotate your two centre sprites. Adjust the outerObject's x position to bring the outerObject closer or further from the centre object.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
anchorPoint = CGPoint(x: 0.5, y: 0.5)
let centerObject = SKSpriteNode(texture: nil, color: UIColor.blue, size: CGSize(width: 20, height: 20))
addChild(centerObject)
let invisibleParent = SKSpriteNode()
addChild(invisibleParent)
let outerObject = SKSpriteNode(texture: nil, color: UIColor.red, size: CGSize(width: 50, height: 50))
outerObject.position.x = 100
invisibleParent.addChild(outerObject)
centerObject.run(SKAction.repeatForever(SKAction.rotate(byAngle: 1, duration: 1)))
invisibleParent.run(SKAction.repeatForever(SKAction.rotate(byAngle: -1, duration: 1)))
}
}

(Xcode Swift SpriteKit) How do I move a sprite in the direction it is facing

I have a sprite in the centre of the screen which can be rotated left or right which is determined by touching the left or right part of the screen.
What I would like to do is have the sprite continuously moving forwards but always in the direction it is facing. I know how to do basic movement with SKActions etc... but have no idea how I calculate the movement to be continuously in the direction that the sprite has rotated to?
Maths has never been my strong point so would very much appreciate some sample code to help me along.
var player = SKSpriteNode()
override func didMove(to view: SKView) {
player = SKSpriteNode(imageNamed: "4B.png")
player.setScale(0.3)
player.zPosition = 100
self.addChild(player)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
if position.x < 0 {
let rotate = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI), duration: 2))
player.run(rotate, withKey: "rotating")
} else {
let rotate = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(-M_PI), duration: 2))
player.run(rotate, withKey: "rotating")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
player.removeAction(forKey: "rotating")
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
You will need to use sin and cos. An SKAction may not be the best thing for you, so I would just do this in the update method for now till you find a better spot:
sprite.position = CGPoint(x:sprite.position.x + cos(sprite.zRotation) * 10,y:sprite.position.y + sin(sprite.zRotation) * 10)
Where 10 is the magnitude that you want the sprite to move (aka move 10 pixels)
This assumes that angle 0 means the sprite is looking right, angle 90 (PI/2) is looking up, angle 180 (PI) is looking left, and angle 270 (3PI/2) is looking down.

Get SKCameraNode to follow a hero Spritenode

I can't seem to figure this out. I've tried many different things and none of them seem to work. With my current code, the camera and the hero never line up and the scene seems to jump pretty far when I touch the screen. All I want to do is when I touch the screen have the hero move to the touch point and have the camera follow him. Is there some way to lock the camera to the hero spritenode?
import SpriteKit
let tileMap = JSTileMap(named: "level2.tmx")
let hero = SKSpriteNode(imageNamed: "hero")
let theCamera: SKCameraNode = SKCameraNode()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.anchorPoint = CGPoint(x: 0, y: 0)
self.position = CGPoint(x: 0, y: 0)
hero.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
hero.xScale = 0.5
hero.yScale = 0.5
hero.zPosition = 2
tileMap.zPosition = 1
tileMap.position = CGPoint(x: 0, y: 0)
self.addChild(tileMap)
self.addChild(hero)
self.addChild(theCamera)
self.camera = theCamera
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
let action = SKAction.moveTo(location, duration: 1)
hero.runAction(action)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
self.camera?.position = hero.position
}
}
The reason why you saw the scene jumped pretty far is because the scene.size doesn't equal to the screen size. I guess you might initialize your first scene like this:
// GameViewController.swift
if let scene = GameScene(fileNamed:"GameScene") {...}
That code will load GameScene.sks whose size is 1024*768 by default. But since you add your SKSpriteNode programmatically, you can initialize the scene in this way to fit the screen size:
// GameViewController.swift
// Only remove if statement and modify
let scene = GameScene(size: view.bounds.size) ...
This will solve most of the problem you have. Moreover, I suggest moving the camera node using SKAction:
override func update(currentTime: CFTimeInterval) {
let action = SKAction.moveTo(hero.position, duration: 0.25)
theCamera.runAction(action)
}
The last thing, add this line to align the camera with your hero at the start:
self.camera?.position = hero.position