Get SKCameraNode to follow a hero Spritenode - sprite-kit

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

Related

How to have a ball go through a hoop in spritekit?

I have a basketball rim png that I want a basketball to go through if the user taps on the correct location above the rim but whenever I tap the screen for the ball to drop down, it always bounces off the rim even if it is directly above it and is supposed to go through it. How would I allow the basketball to fall directly through the hoop? Here is my code. Thanks.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
//This is the basketball rim png that I put in my scene
let rim = SKSpriteNode(imageNamed: "resized Basketball hoop.png")
rim.position = CGPoint(x: 512, y: 384)
rim.physicsBody = SKPhysicsBody(texture: rim.texture!, size: rim.texture!.size())
rim.physicsBody?.isDynamic = false
addChild(rim)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//This is when I touch the screen and a basketball falls from where I touched
if let touch = touches.first {
let location = touch.location(in: self)
let basketball = SKSpriteNode(imageNamed: "basketball png.png")
basketball.physicsBody = SKPhysicsBody(circleOfRadius: basketball.size.width / 16)
basketball.size.width = basketball.size.width / 8
basketball.size.height = basketball.size.height / 8
basketball.physicsBody?.restitution = 0.4
basketball.position = location
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
addChild(basketball)
}
}
}

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)
}
}

SKCameraNode working weird

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

SpriteKit partial texture mapping

Is it possible to create a SKSpriteNode that displays only a part of a texture?
For example, can I create a square with size 100x100 displaying the specific region of a texture of size 720x720 like from x1=300 to x2=400 and y1=600 to y2=700?
Thanks for your help.
Try something like this:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let visibleArea = SKSpriteNode(color: .black, size: CGSize(width:100,height:100))
let parentNode = SKSpriteNode(color: .white, size: CGSize(width:200, height:200))
override func didMove(to view: SKView) {
let cropNode = SKCropNode()
let texture = SKSpriteNode(imageNamed: "Spaceship")
visibleArea.position = CGPoint(x: 0, y: 100)
cropNode.maskNode = visibleArea
cropNode.addChild(texture)
addChild(cropNode)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let previousPosition = touch.previousLocation(in: self)
let translation = CGPoint(x: location.x - previousPosition.x , y: location.y - previousPosition.y )
visibleArea.position = CGPoint(x: visibleArea.position.x + translation.x , y: visibleArea.position.y + translation.y)
}
}
}
Overriden touchesMoved method is there just because of better example. What I did here, is:
created SKCropNode
added a texture to it which will be masked
defined visible area which is SKSpriteNode and assigned it to crop node's mask property, which actually does the magic
Here is the result:
If you want to break up a texture into smaller chunks of textures to be used as puzzle pieces, then you want to use SKTexture(rect: in texture:)
Here is an example of how to use it:
let texture = SKTexture(...) //How ever you plan on getting main texture
let subTextureRect = CGRect(x:0,y:0.width:10,height:10) // The actual location and size of where you want to grab the sub texture from main texture
let subTexture = SKTexture(rect:subTextureRect, in:texture);
You now have a chunk of the sub texture to use in other nodes.

Swift | I'm trying to rotate a sprite and change the direction of the rotation ever time I tap

I want to rotate a single sprite indefinitely, and every time I tap the button, I want the sprite to rotate in the opposite direction (back and forth from clockwise to counter-clockwise etc.
Below is the code that I have:
http://pastebin.com/Avj8Njwt
class GameScene: SKScene {
var center = SKSpriteNode()
var bg = SKSpriteNode()
var bigCircle = SKSpriteNode()
let counterClockwise = SKAction.rotateByAngle(CGFloat(3.14), duration:1)
let clockwise = SKAction.rotateByAngle(CGFloat(-3.14), duration:1)
var spin = SKAction()
override func didMoveToView(view: SKView) {
//Background
var bgTexture = SKTexture(imageNamed: "images/bg.png")
bg = SKSpriteNode(texture:bgTexture)
bg.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
//Center Circle
var bigCircleTexture = SKTexture(imageNamed: "images/bigCircle.png")
bigCircle = SKSpriteNode(texture:bigCircleTexture)
bigCircle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(bigCircle)
//Center Triangle
var centerTexture = SKTexture(imageNamed: "images/center.png")
center = SKSpriteNode(texture:centerTexture)
center.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
spin = clockwise
center.runAction(SKAction.repeatActionForever(spin))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
if spin == clockwise {
spin = counterClockwise
}
else {
spin = clockwise
}
center.runAction(SKAction.repeatActionForever(spin))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Your problem is you're not removing the old SKAction trying to rotate your SKSpriteNode. To do that you need to keep track of which way your sprite is rotating. If I was going to implement this I would subclass SKSpriteNode, like so:
class RotatingSprite: SKSpriteNode {
// This is used to keep track of which way the sprite is rotating.
enum Direction {
case Left, Right
mutating func inverse() {
switch self {
case .Left : self = .Right
case .Right: self = .Left
}
}
}
// These names will be the keys used when running an action.
// This will allow you to stop the rotate-left or rotate-right actions.
static let rotateLeftName = "RotateLeftAction"
static let rotateRightName = "RotateRightAction"
var rotationDirection: Direction? {
didSet {
if let r = rotationDirection {
switch r {
// Checks the sprite isn't already rotating to the left.
// If it isn't, make the sprite rotate to the left.
case .Left where oldValue != .Left:
rotateLeft()
case .Right where oldValue != .Right:
rotateRight()
default:
break
}
}
}
}
private func rotateLeft() {
// Remove the action rotating the sprite to the right.
self.removeActionForKey(RotatingSprite.rotateRightName)
// And start the action rotating the sprite to the left.
let rotateAction = SKAction.rotateByAngle(-CGFloat(M_PI), duration: 1.0)
self.runAction(SKAction.repeatActionForever(rotateAction), withKey: RotatingSprite.rotateLeftName)
}
private func rotateRight() {
self.removeActionForKey(RotatingSprite.rotateLeftName)
let rotateAction = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1.0)
self.runAction(SKAction.repeatActionForever(rotateAction), withKey: RotatingSprite.rotateRightName)
}
}
Now you can use RotatingSprite like so:
class GameScene: SKScene {
let rotatingSprite = RotatingSprite(texture:bgTexture)
override func didMoveToView(view: SKView) {
rotatingSprite.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(rotatingSprite)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
// If the sprite isn't turning you've got to set it off.
if rotatingSprite.rotationDirection == nil {
rotatingSprite.rotationDirection = .Left
// If it is turning, change its direction.
} else {
rotatingSprite.rotationDirection!.inverse()
}
}
}
Hope that helps!
It is extremely easy to achieve this. Try this ,
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
[sprite removeAllActions];
SKAction *action;
if (isClockWise)
{
action = [SKAction rotateByAngle:M_PI duration:1];
}
else
{
action = [SKAction rotateByAngle:-M_PI duration:1];
}
isClockWise = !isClockWise;
[sprite runAction:[SKAction repeatActionForever:action]];
}
Where sprite is SKSpriteNode and initiate isClockWise to Yes or No depending on your initial movement direction.
A quick and dirty way to do this is the following:
import SpriteKit
class GameScene: SKScene {
var center = SKSpriteNode()
var bg = SKSpriteNode()
var bigCircle = SKSpriteNode()
let counterClockwise = SKAction.rotateByAngle(CGFloat(3.14), duration:1)
let clockwise = SKAction.rotateByAngle(CGFloat(-3.14), duration:1)
var spin = SKAction()
// this is used to identify which direction we are going in. When we change it spin is changed as well
var isClockwise: Bool = true {
didSet {
if isClockwise {
spin = clockwise
} else {
spin = counterClockwise
}
}
}
let actionKey = "spin" // this is used to identify the SKAction
override func didMoveToView(view: SKView) {
//Background
var bgTexture = SKTexture(imageNamed: "images/bg.png")
bg = SKSpriteNode(texture:bgTexture)
bg.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
//Center Circle
var bigCircleTexture = SKTexture(imageNamed: "images/bigCircle.png")
bigCircle = SKSpriteNode(texture:bigCircleTexture)
bigCircle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(bigCircle)
//Center Triangle
var centerTexture = SKTexture(imageNamed: "images/center.png")
center = SKSpriteNode(texture:centerTexture)
center.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
isClockwise = true // set the initial direction to clockwise
center.runAction(SKAction.repeatActionForever(spin), withKey: actionKey)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// remove the existing spin action
center.removeActionForKey(actionKey)
// reset the direction (this will automatically switch the SKAction)
isClockwise = !isClockwise
center.runAction(SKAction.repeatActionForever(spin), withKey: actionKey)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You need to remove the action before you apply a new one - you can selectively remove the action by calling runAction(action:,withKey:). This enables you to be able to remove that same action using the same key. The logic for changing spin is in didSet for the isClockwise var declaration.
It was as simple as placing center.removeAllActions in each if statement to make sure the sprite isn't currently moving when the direction is supposed to be changed.