Spritkit Added the node in the scene but not moving? - ios

I wanted to create the background with few clouds moving from right to left.But when i ran this i can only see the clouds created on the background but they are not moving.
I have following class to create the clouds then add them in the scene. the last function moveBGcloud() will move clouds from right to left.
import Foundation
import SpriteKit
import SpriteKit
class BGCloud :SKSpriteNode{
private func shuffle(cloudsArray: [SKSpriteNode]) -> [SKSpriteNode] {
var shuffledArray = cloudsArray;
for i in 0..<shuffledArray.count {
let j = Int(arc4random_uniform(UInt32(shuffledArray.count - i))) + i;
if i == j {continue}
swap(&shuffledArray[i], &shuffledArray[j]);
}
return shuffledArray;
}
private func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum);
}
func createClouds() -> [SKSpriteNode] {
var clouds = [SKSpriteNode]();
let cloud1 = SKSpriteNode(imageNamed: "Cloud 1");
cloud1.name = "BGCloud1";
let cloud2 = SKSpriteNode(imageNamed: "Cloud 2");
cloud2.name = "BGCloud2";
let cloud3 = SKSpriteNode(imageNamed: "Cloud 3");
cloud3.name = "BGCloud3"
let darkCloud = SKSpriteNode(imageNamed: "Dark Cloud");
darkCloud.name = "BGDarkCloud";
cloud1.xScale = 0.3;
cloud1.yScale = 0.3;
cloud2.xScale = 0.4;
cloud2.yScale = 0.4;
cloud3.xScale = 0.5;
cloud3.yScale = 0.5;
darkCloud.xScale = 0.2;
darkCloud.yScale = 0.2;
clouds.append(cloud1);
clouds.append(cloud2);
clouds.append(cloud3);
clouds.append(darkCloud);
clouds = shuffle(cloudsArray: clouds);
return clouds;
}
func arrangeBGCloudsInScene(scene: SKScene, minX: CGFloat, maxX: CGFloat,minY: CGFloat, maxY: CGFloat,speed:CGFloat) {
var clouds = createClouds();
for i in 0..<clouds.count {
var randomX = CGFloat();
var randomY = CGFloat();
randomX = randomBetweenNumbers(firstNum: minX, secondNum: maxX);
randomY = randomBetweenNumbers(firstNum: minY, secondNum: maxY);
clouds[i].position = CGPoint(x: randomX, y: randomY);
clouds[i].zPosition = 2;
scene.addChild(clouds[i]);
}
}
func moveBGcloud( speed:CGFloat){
let cloudSpeed = speed
self.position.x -= cloudSpeed
print("postion",self.position.x)
if self.position.x < -324 {
self.position.x = 332
}
}
}
Then in the GameplayScene Class: (Back ground scene class)
I added instance of BGCloud class
private var cloud1: BGCloud?
private var cloud2: BGCloud?
private var cloud3: BGCloud?
private var darkCloud:BGCloud?
private let BGCloud1Speed = 0.5
private let BGCloud2Speed = 0.7
private let BGCloud3Speed = 0.8
Then added function BGCloudController.arrangeBGCloudsInScene to initialize the background
private func initializeGame() {
BGCloudController.arrangeBGCloudsInScene(scene: self.scene!, minX: minX, maxX: maxX, minY: minY, maxY: maxY, speed: CGFloat(BGCloud1Speed))
getBackgrounds();
}
Get the clouds from scene:
private func getBackgrounds() {
cloud1 = self.childNode(withName: "BGCloud1") as? BGCloud!
cloud2 = self.childNode(withName: "BGCloud2") as? BGCloud!
cloud3 = self.childNode(withName: "BGCloud3") as? BGCloud!
}
The added CreateBGClouds function to move the background clouds from right to left in update function
override func update(_ currentTime: TimeInterval) {
createBGClouds()
}
private func createBGClouds(){
cloud1?.moveBGcloud(speed: CGFloat(BGCloud1Speed)) //only add cloud1 for testing
print("function called")
}
When i ran the game i can see the four background clouds were randomly on the scene but they are not moving. seems like
moveBGcloud() function in BGCloud class was not get called. The Cloud1,2,3 are nil not get the value.Why? Also i did not use scene editor.
Could you help me to check what is the problem or anything i missed. Thank you in advance.

As we figured out in the comments above there is kind of a typo in cloud instantiation.
Changing the assignments in func createClouds() to BGCloud fixes the issue:
let cloud1 = BGCloud(imageNamed: "Cloud 1");
cloud1.name = "BGCloud1";
let cloud2 = BGCloud(imageNamed: "Cloud 2");
cloud2.name = "BGCloud2";
let cloud3 = BGCloud(imageNamed: "Cloud 3");
cloud3.name = "BGCloud3"

Related

Collision in Spritekit not collide with another node

I describe as StackOverflow standards the following issue.
Summarize the problem
I have issue about colliding two nodes. One is composed by a crowd and each people is a single item of my crowd defined in the same way (included in a while just to be clear and using index "i" and "j" to create the row of the crowd). I wanted to make disappear once arrive a node (as civilian) to the bottom and going to the top and the crowd remove the civilian spawned along the path. Actually I have this thing and the func tells me that the colliding happens but it didn't 'cause nothing happens and the civilian node captured by crowd it didn't disappear or removed with the removefromparent(). I've got no error messages with my compiler, it works for him. My scope is : detecting node civilian during the path by the crowd and remove this one from the path.
What I've tried
I tried many things to fix this. The first thing is following a lot of tutorials about how Collision Masks etc.. work. I know what they do. But what I've tried it was to make a invisible line for the last line of crowd of people just to see if the problem is the crowd itself and making that if the civilian node arrives to collide the invisible line is like he was in contact with the crowd but it didin't this effect. I followed a lot of tutorial such as HackingWithswift, Youtube tutorials but the procedure for me it's clear but nothing happens (sorry for being repetitive).
Show code
My problem is about this GameScene.sks because it it just one file with all the functions.
import SpriteKit
import GameplayKit
enum CategoryMask: UInt32 {
case civilian_value = 1
case crowd_value = 2
case background_value = 0
}
enum GameState {
case ready
case playing
case dead
}
var gameState = GameState.ready {
didSet {
print(gameState)
}
}
class GameScene: SKScene, SKPhysicsContactDelegate {
let player = SKSpriteNode(imageNamed: "player1")
let textureA = SKTexture(imageNamed: "player1")
let textureB = SKTexture(imageNamed: "player2")
let pause = SKSpriteNode(imageNamed: "pause-button")
let resume = SKSpriteNode(imageNamed: "pause-button")
var civilian = SKSpriteNode()
let pauseLayer = SKNode()
let gameLayer = SKNode()
weak var sceneDelegate: GameSceneDelegate?
//main
override func didMove(to view: SKView) {
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
//func for dynamic background
moveBackground(image: ["background1", "background2", "background3", "background1"], x: 0, z: -3, duration: 5, size: CGSize(width: 0.5, height: 1.0))
character(player: player)
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(civilians),
SKAction.wait(forDuration: 3.0)])))
run(SKAction.run(crowdSpawn))
pause.name="pause"
pause.position = CGPoint(x: frame.minX/1.3, y: frame.minY/1.15)
pause.size=CGSize(width: 0.1, height: 0.1)
pause.zPosition = 4
addChild(pause)
if self.scene?.isPaused == true {
resume.name="resume"
resume.position = CGPoint(x: frame.minX/1.5, y: frame.minY/1.15)
resume.size=CGSize(width: 0.1, height: 0.1)
resume.zPosition = 12
addChild(resume)
}
}
func pauseGame() {
sceneDelegate?.gameWasPaused()
let barr = SKSpriteNode()
let barrbehind = SKSpriteNode()
let buttonresume = SKSpriteNode(imageNamed: "back")
barrbehind.name = "barrbehind"
barrbehind.zPosition = 9
barrbehind.color = SKColor.black
barrbehind.size = CGSize(width: frame.width, height: frame.height)
barrbehind.alpha = 0.5
self.addChild(barradietro)
barr.name = "bar"
barr.size = CGSize(width: 0.4, height: 0.5)
barr.color = SKColor.white
barr.zPosition = 10
self.addChild(barr)
buttonresume.name = "resume"
buttonresume.zPosition = 11
buttonresume.color = SKColor.black
buttonresume.size = CGSize(width: 0.1, height: 0.1)
buttonresume.alpha = 0.5
self.addChild(buttonresume)
self.scene?.isPaused = true
}
//random func (it helps for generate randomly civilians along the path
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
//func to define civilians
func civilians() {
let civilian = SKSpriteNode(imageNamed: "PV")
civilian.name = "civilian"
//posiziono il civile
civilian.position = CGPoint(x: frame.size.width/8.0 * random(min: -1.5, max: 1.5), y: -frame.size.height * 0.45)
civilian.physicsBody = SKPhysicsBody(rectangleOf: civilian.size)
civilian.zPosition = 3
civilian.physicsBody?.categoryBitMask = CategoryMask.civilian_value.rawValue
civilian.physicsBody?.collisionBitMask = CategoryMask.crowd_value.rawValue
civilian.physicsBody?.contactTestBitMask = CategoryMask.crowd_value.rawValue
civilian.physicsBody?.isDynamic = true
//civilian size
civilian.size=CGSize(width: 0.2, height: 0.2)
//civilian movement
civilian.run(
SKAction.moveBy(x: 0.0, y: frame.size.height + civilian.size.height,duration: TimeInterval(1.77)))
addChild(civilian)
}
//func for the main character
func character(player: SKSpriteNode){
player.position = CGPoint(x: 0, y: 0)
player.size = CGSize(width: 0.2, height: 0.2)
let animation = SKAction.animate(with: [textureB,textureA], timePerFrame:0.2)
player.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(player)
player.run(SKAction.repeatForever(animation))
}
//func for generate the crowd
func crowdSpawn(){
var i = 0.0
var j = 0.25
var crowdRaw : Bool = true
while crowdRaw {
if i <= 1 {
let crowd = SKSpriteNode(imageNamed: "player1")
crowd.name = "crowd"
//posiziono il civile
crowd.size=CGSize(width: 0.15, height: 0.15)
crowd.position = CGPoint(x: -frame.size.width / 3.6 + CGFloat(i)/2 * crowd.size.width , y: frame.size.height / 2 + (CGFloat(j)*2) * -crowd.size.height)
crowd.zPosition = 3
let animation = SKAction.animate(with: [textureB,textureA], timePerFrame:0.25)
crowd.run(SKAction.repeatForever(animation))
crowd.run(SKAction.moveBy(x: frame.size.width / 16.0 + CGFloat(i) * crowd.size.width, y: 0, duration: 0))
let infectedCollision = SKSpriteNode(color: UIColor.red,
size: CGSize(width: 1, height: 0.1))
infectedCollision.physicsBody = SKPhysicsBody(rectangleOf: infectedCollision.size)
infectedCollision.physicsBody?.categoryBitMask = CategoryMask.crowd_value.rawValue
//collisionBitMask : qui la linea della folla non può collidere con il civilian
infectedCollision.physicsBody?.collisionBitMask = CategoryMask.civilian_value.rawValue
infectedCollision.physicsBody?.contactTestBitMask = CategoryMask.civilian_value.rawValue
infectedCollision.physicsBody?.isDynamic = true
infectedCollision.name = "infectedCollision"
infectedCollision.position = crowd.position
addChild(crowd)
addChild(infectedCollision)
i += 0.25
} else {
j += 0.25
i = 0.0
}
if j == 1 {
crowdRaw = false
}
}
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.node?.position == contact.bodyB.node?.position {
let actionMoveDone = SKAction.removeFromParent()
civilian.run(SKAction.sequence([actionMoveDone]))
}
}
//func about the touches
func touchDown(atPoint pos : CGPoint) {
let action = SKAction.move(to: pos, duration: 1.0)
// playerSprite is a SpriteKit sprite node.
player.run(action)
}
//func about the touches
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
switch gameState {
case .ready:
gameState = .playing
case .playing:
for t in touches {
let location = t.location(in: self)
player.position.x = location.x/2
for node in self.nodes(at: location){
if node.name == "civilian" {
let explode = SKAction.colorize(with: UIColor.systemBlue,colorBlendFactor: 5.0, duration: 2)
let vanish = SKAction.fadeOut(withDuration: 2.0)
node.run(explode , completion: {
node.run(vanish) {
node.removeFromParent()
}
})
}else if node.name == "pause" {
pauseGame()
}else if node.name == "resume" {
self.scene?.isPaused = false
}
}
}
case .dead:
print("dead")
}
}
//function to have different backgrounds in scrolling (3 backgrounds in a loop)
func moveBackground(image: [String], x: CGFloat, z:CGFloat, duration: Double, size: CGSize) {
for i in 0...3 {
let background = SKSpriteNode(imageNamed: image[i])
background.position = CGPoint(x: x, y: size.height * CGFloat(i))
background.size = size
background.zPosition = z
let move = SKAction.moveBy(x: 0, y: -background.size.height*3, duration: 0)
let back = SKAction.moveBy(x: 0, y: background.size.height*3, duration: duration)
let sequence = SKAction.sequence([move,back])
let repeatAction = SKAction.repeatForever(sequence)
addChild(background)
background.run(repeatAction)
}
}
}
Ok, it was fun to recall how all this SpriteKit stuff works :D
First problem you have is node/sprite creation. The solution could be some kind of Factory pattern with more or less abstraction. GameScene doesn't have to know how nodes are initialized/configured. Scene could know only which type of nodes exist, and thats enough to get them ready for use.
//MARK: - Factory
protocol AbstractFactory {
func getNode()-> SKNode
func getNodeConfig()->SpriteConfig
}
class CivilianFactory : AbstractFactory {
// Local Constants
private struct K {
static let size = CGSize(width: 32, height: 32)
static let name = "civilian"
static let color = UIColor.yellow
}
// Here we get Civilian sprite config
func getNodeConfig() -> SpriteConfig {
let physics = SpritePhysicsConfig(categoryMask: Collider.civilian, contactMask: Collider.player | Collider.wall, collisionMask: Collider.none)
return SpriteConfig(name: K.name, size: K.size, color: K.color, physics: physics)
}
func getNode() -> SKNode {
let config = getNodeConfig()
let sprite = SKSpriteNode(color: config.color, size: config.size)
sprite.color = config.color
sprite.name = config.name
sprite.zPosition = 1
if let physics = config.physics {
sprite.physicsBody = SKPhysicsBody(rectangleOf: config.size)
sprite.physicsBody?.isDynamic = physics.isDynamic
sprite.physicsBody?.affectedByGravity = physics.isAffectedByGravity
sprite.physicsBody?.categoryBitMask = physics.categoryMask
sprite.physicsBody?.contactTestBitMask = physics.contactMask
sprite.physicsBody?.collisionBitMask = physics.collisionMask
}
}
return sprite
}
}
Same as this, You will make other "factories" as needed (just copy the factory and change visual/physics data setup). For this example I will make PlayerFactory.
and with next method I will create my nodes:
private func getNode(factory:AbstractFactory)->SKNode{
return factory.getNode()
}
and then just use it like this:
let node = getNode(factory: self.civiliansFactory) // or self.whateverFactory
Here you just provide a factory you want (can be anything that conforms to AbstractFactory), and in return, You get a desired node (You can return here anything that is SKNode). This way, we have hid initialization process, dependencies etc. from outside world (GameScene), and put everything in one place.
So, quite flexible, plus removes a bunch of repeating code from your scene.
And here are config structs for sprites creation:
//MARK: - Sprite Config
struct SpriteConfig {
let name:String
let size:CGSize
let color:UIColor
let physics:SpritePhysicsConfig? // lets make this optional
}
struct SpritePhysicsConfig {
let categoryMask: UInt32
let contactMask: UInt32
let collisionMask:UInt32
let isDynamic:Bool
let isAffectedByGravity:Bool
init(categoryMask:UInt32, contactMask:UInt32, collisionMask:UInt32, isDynamic:Bool = true, isAffectedByGravity:Bool = false){
self.categoryMask = categoryMask
self.contactMask = contactMask
self.collisionMask = collisionMask
self.isDynamic = isDynamic
self.isAffectedByGravity = isAffectedByGravity
}
}
Now some useful extensions that I needed:
//MARK: - Extensions
//Extension borrowed from here : https://stackoverflow.com/a/37760551
extension CGRect {
func randomPoint(x:CGFloat? = nil, y:CGFloat? = nil) -> CGPoint {
let origin = self.origin
return CGPoint(x: x == nil ? CGFloat(arc4random_uniform(UInt32(self.width))) + origin.x : x!,
y: y == nil ? CGFloat(arc4random_uniform(UInt32(self.height))) + origin.y : y!)
}
}
//Extension borrowed from here: https://stackoverflow.com/a/33292919
extension CGPoint {
func distance(point: CGPoint) -> CGFloat {
return abs(CGFloat(hypotf(Float(point.x - x), Float(point.y - y))))
}
}
And the GameScene:
//MARK: - Game Scene
class GameScene: SKScene {
//MARK: - Local Constants
// It's always good to have some kind of local constants per file, so that you have all variables in one place when it comes to changing/tuning
private struct K {
struct Actions {
static let civilianSpawningKey = "civilian.spawning"
static let playerMovingKey = "player.moving"
static let spawningDuration:TimeInterval = 0.7
static let spawningRange = 0.2
static let fadeOutDuration:TimeInterval = 0.35
}
struct General {
static let playerSpeed:CGFloat = 350
}
}
//MARK: - Private Properties
private var player:SKSpriteNode?
// Just in case, nodes are removed after physics simulation is done (in didSimulatePhysics which is called in each frame)
// Frame-Cycle Events : https://developer.apple.com/documentation/spritekit/skscene/responding_to_frame-cycle_events
private var trash:[SKNode] = []
private let civilianFactory = CivilianFactory()
private let playerFactory = PlayerFactory()
//MARK: - Scene lifecycle
override func sceneDidLoad() {
physicsWorld.contactDelegate = self
spawnCivilians()
}
//MARK: - Creating & Spawning sprites
private func getNode(factory:AbstractFactory)->SKNode{
return factory.getNode()
}
private func spawnCivilian(at position: CGPoint){
let node = getNode(factory: civilianFactory)
node.position = position
addChild(node)
}
private func spawnPlayer(at position: CGPoint){
// If its a first time, create player and leave it there
guard let `player` = player else {
let node = getNode(factory: playerFactory)
node.position = position
self.player = (node as? SKSpriteNode)
addChild(node)
return
}
// If player exists, move it around
let distance = player.position.distance(point: position)
let speed = K.General.playerSpeed
// To maintain same moving speed, cause if we use constant here, sprite would move faster or slower based on a given distance
let duration = distance / speed
let move = SKAction.move(to: position, duration:duration)
// This is a good way to check if some action is running
if player.action(forKey: K.Actions.playerMovingKey) != nil {
player.removeAction(forKey: K.Actions.playerMovingKey)
}
player.run(move, withKey: K.Actions.playerMovingKey)
}
private func spawnCivilians(){
let wait = SKAction .wait(forDuration: K.Actions.spawningDuration, withRange: K.Actions.spawningRange)
let spawn = SKAction.run({[weak self] in
guard let `self` = self else {return}
self.spawnCivilian(at: self.frame.randomPoint())
})
let spawning = SKAction.sequence([wait,spawn])
self.run(SKAction.repeatForever(spawning), withKey:K.Actions.civilianSpawningKey)
}
//MARK: - Touches Handling
func touchDown(atPoint pos : CGPoint) {
spawnPlayer(at: pos)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
}
So I pretty much commented everything. Here, you :
Start spawning civilians infinitely, immediately after the scene is loaded
On touch you add player to the scene
On every next touch player travels to the touch location (by the same speed)
And contacts:
//MARK: - Physics
struct Collider{
static let player : UInt32 = 0x1 << 0
static let civilian : UInt32 = 0x1 << 1
static let wall : UInt32 = 0x1 << 2
static let none : UInt32 = 0x0
}
extension GameScene: SKPhysicsContactDelegate{
//MARK: - Removing Sprites
override func didSimulatePhysics() {
for node in trash {
// first remove node from parent (with fadeOut)
node.run(SKAction.sequence([SKAction.fadeOut(withDuration: K.Actions.fadeOutDuration), SKAction.removeFromParent()]))
}
trash.removeAll() // then empty the trash
}
//MARK: Removing
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else {
//Silliness like removing a node from a node tree before physics simulation is done will trigger this error
fatalError("Physics body without its node detected!")
}
let mask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch mask {
// Contact between player and civilian detected
case Collider.player | Collider.civilian:
if let civilian = (contact.bodyA.categoryBitMask == Collider.civilian ? nodeA : nodeB) as? SKSpriteNode
{
trash.append(civilian)
}
default:
break
}
}
}
I guess those contacts and node removal were your problem. The point is that nodes that have physics body, are safer to remove from a node tree when didSimulatePhysics method is finished. There is a link in comments that explains what happens each frame, but the bottom point is, that physics engine retains physics body cause simulation is not finished, but the node is removed and that often end up in some unexpected results.
So to try how this work, you just copy / paste it in your GameScene. Here is how it looks:
You can see how nodes are really removed by observing nodes count label. (to enable these labels, you just go (in your view controller class) with (self.view as? SKView)?.showsNodeCount = true, showsFPS, showsPhysics etc).

SKPhysicsContactDelegate didBegin won't run

I was creating a Swift Playground using SpriteKit but couldn't get collision detection to work. I've read all of the posts regarding this issue on StackOverFlow and none of them has solved my issues. Below is my code:
public class MyScene: SKScene, SKPhysicsContactDelegate {
var node: SKSpriteNode?
var touchedNodeCreator: Timer?
let nodeCategory : UInt32 = 0x1 << 1
let touchedNodeCategory : UInt32 = 0x1 << 2
var score = 0
public override func didMove(to view: SKView) {
//Setup
physicsWorld.contactDelegate = self
self.backgroundColor =
UIColor(red:0.60, green:0.88, blue:1.00, alpha:1.0)
//node
node = SKSpriteNode(imageNamed: "node")
node!.setScale(0.175)
node!.physicsBody? = SKPhysicsBody(rectangleOf: node!.size)
node!.physicsBody?.categoryBitMask = nodeCategory
node!.physicsBody?.contactTestBitMask = touchedNodeCategory
node!.physicsBody?.collisionBitMask = floorCategory
floor.physicsBody?.categoryBitMask = floorCategory
floor.physicsBody?.collisionBitMask = nodeCategory
let nodeStartingPos = CGPoint(x: node!.size.width / 2 + 25, y: self.size.height / 5 + node!.size.height / 2)
node!.position = nodeStartingPos
addChild(node!)
//Create touchedNode Timer
touchedNodeCreator = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { (timer) in
self.createtouchedNode()
}
}
func createtouchedNode() {
let touchedNode = SKSpriteNode(imageNamed: "otherNode")
touchedNode.setScale(0.2)
touchedNode.name = "TouchedNode"
touchedNode.physicsBody? = SKPhysicsBody(rectangleOf: touchedNode.size)
touchedNode.physicsBody?.affectedByGravity = false
let maxY = self.size.height / 1 - touchedNode.size.height / 2
let minY = self.size.height / 5 + 25
let rangeY = maxY - minY
let y = maxY - CGFloat(arc4random_uniform(UInt32(rangeY)))
let touchedNodeStartingPos = CGPoint(x: self.size.width + 20, y: y)
touchedNode.position = touchedNodeStartingPos
touchedNode.physicsBody?.categoryBitMask = touchedNodeCategory
touchedNode.physicsBody?.contactTestBitMask = nodeCategory
addChild(touchedNode)
let moveLeft = SKAction.moveBy(x: -size.width - touchedNode.size.width, y: 0, duration: 3)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([moveLeft, remove])
touchedNode.run(sequence)
}
public func didBegin(_ contact: SKPhysicsContact) {
print("Began")
if contact.bodyA.categoryBitMask == touchedNodeCategory {
contact.bodyA.node?.removeFromParent()
score += 1
print(score)
} else if contact.bodyB.categoryBitMask == touchedNodeCategory {
contact.bodyB.node?.removeFromParent()
score += 1
print(score)
}
}
Please keep in mind that I have omitted a lot of code, mainly just code that moves the node when any touch is detected.
I would appreciate it if you guys could help me get this solved. Thanks!

How to detect no collision in a sprite kit game

I'm doing a game where you have to capture candies using a spider hung by a thread, as I show in this link: Game screenshot (I'm new here so I can't post images yet). I already have the movement of the spider from left to right and also I'm able to catch the candies using SKAction moving through 'Y', my only issue is I didn't figure it out yet how to know if the spider don't capture any candy, during his movement, I was trying use the allContactedBodies function when the action finish but the count of the array returned is always zero. Any suggestions?
Here is the code :
class GameScene: SKScene, SKPhysicsContactDelegate {
private var rope = SKSpriteNode(imageNamed: "rope")
private var anchor = SKSpriteNode(imageNamed: "anchor")
private var currentCharacter: SKSpriteNode!
private var candy: SKSpriteNode!
var direction : String = "backward"
var lastCandyAdded: TimeInterval = 0
let candyVelocity: CGFloat = 4.0
let characterBitMask : UInt32 = 0x1 << 1
let candyBitMask: UInt32 = 0x1 << 2
let characterVelocity: CGFloat = 18.0
var direction : String = "backward"
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
self.addAnchor()
self.addRope()
self.addCharacter()
let jointOneFixed = SKPhysicsJointFixed.joint(withBodyA: anchor.physicsBody!, bodyB: rope.physicsBody!, anchor: anchor.position)
self.physicsWorld.add(jointOneFixed);
let jointTwoFixed = SKPhysicsJointFixed.joint(withBodyA: rope.physicsBody!, bodyB: currentCharacter.physicsBody!, anchor: currentCharacter.position)
self.physicsWorld.add(jointTwoFixed);
}
func addAnchor(){
anchor.position = CGPoint(x: self.size.width / 2, y: self.size.height + 1)
anchor.anchorPoint = CGPoint(x: 0.5, y: 0.5)
anchor.setScale(1)
anchor.zPosition = 2
anchor.name = "anchor"
anchor.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: anchor.size.width, height: anchor.size.height))
anchor.physicsBody?.affectedByGravity = false
anchor.physicsBody?.friction = 0;
anchor.physicsBody?.linearDamping = 0;
anchor.physicsBody?.mass = 10;
self.addChild(anchor)
}
func addCharacter() {
let characterName: String = UserDefaults.standard.string(forKey: "current_character")!
currentCharacter = SKSpriteNode(imageNamed: characterName);
currentCharacter.position = CGPoint(x: self.size.width / 2, y: self.size.height - 400)
currentCharacter.anchorPoint = CGPoint(x: 0.5, y: 0.5)
currentCharacter.setScale(0.43)
currentCharacter.zPosition = 2
currentCharacter.name = "character"
currentCharacter.physicsBody = SKPhysicsBody(rectangleOf: currentCharacter.size)
currentCharacter.physicsBody?.categoryBitMask = characterBitMask
currentCharacter.physicsBody?.contactTestBitMask = candyBitMask
currentCharacter.physicsBody?.collisionBitMask = candyBitMask;
currentCharacter.physicsBody?.affectedByGravity = false;
currentCharacter.physicsBody?.friction = 0;
currentCharacter.physicsBody?.linearDamping = 0;
currentCharacter.physicsBody?.mass = 20;
self.addChild(currentCharacter)
}
func addRope() {
rope.position = CGPoint(x: anchor.position.x, y: anchor.position.y - 70)
rope.setScale(0.65)
rope.zPosition = 1
rope.name = "rope"
rope.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: rope.size.width, height: rope.size.height))
rope.physicsBody?.affectedByGravity = false;
rope.physicsBody?.friction = 0;
rope.physicsBody?.linearDamping = 0;
rope.physicsBody?.mass = 5;
rope.physicsBody?.allowsRotation = false
self.addChild(rope)
}
func addCandy() {
let number = Int.random(min: 1, max: 24)
let candyWord = "candie"
let candyTexture = SKTexture(imageNamed: "\(candyWord)\(number)")
candy = SKSpriteNode(texture: candyTexture)
candy.zPosition = 3
candy.setScale(0.40)
candy.physicsBody = SKPhysicsBody(circleOfRadius: max(candy.size.width / 2, candy.size.height / 2))
candy.physicsBody?.isDynamic = true
candy.name = "candy"
candy.physicsBody?.categoryBitMask = candyBitMask
candy.physicsBody?.contactTestBitMask = characterBitMask
candy.physicsBody?.collisionBitMask = characterBitMask
candy.physicsBody?.affectedByGravity = false
candy.position = CGPoint(x: self.frame.size.width + 20, y: self.frame.size.height / 2 + 150)
self.addChild(candy)
}
func moveCandy() {
self.enumerateChildNodes(withName: "candy", using: {(node, stop) -> Void in
if let candy = node as? SKSpriteNode {
candy.position = CGPoint(x: candy.position.x - self.candyVelocity, y: candy.position.y)
if candy.position.x < 0 {
candy.removeFromParent()
}
}
})
}
override func update(_ currentTime: TimeInterval) {
self.moveCandy()
self.moveCharacter()
if currentTime - self.lastCandyAdded > 0.75 {
self.lastCandyAdded = currentTime + Double.random(min: 0.00, max: 0.60)
self.addCandy()
}
}
func collisionBetween(candy: SKNode, object: SKNode) {
let moveUp = SKAction.moveBy(x: 0, y: 100, duration:0.0)
let shrinkRope = SKAction.animate(with: [SKTexture(imageNamed: "rope")], timePerFrame: 0)
let moveUpBlock = SKAction.run({
self.anchor.run(moveUp)
})
let shrinkRopeBlock = SKAction.run({
self.rope.run(shrinkRope)
})
let sequence = SKAction.sequence([SKAction.wait(forDuration: 0.07), shrinkRopeBlock, moveUpBlock])
self.run(sequence)
candy.removeFromParent()
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA.name == "candy" && nodeB.name == "character" {
collisionBetween(candy: nodeA, object: nodeB)
} else if nodeA.name == "character" && nodeB.name == "candy" {
collisionBetween(candy: nodeB, object: nodeA)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
let moveDown = SKAction.moveBy(x: 0, y: -100, duration:0.0)
let stretchRope = SKAction.animate(with: [SKTexture(imageNamed: "rope_stretch")], timePerFrame: 0)
let moveDownBlock = SKAction.run({
self.anchor.run(moveDown, completion: {
var physicBodies = self.currentCharacter.physicsBody?.allContactedBodies();
// This count is always zero
print(physicBodies?.count)
})
})
let stretchRopeBlock = SKAction.run({
self.rope.run(stretchRope)
})
let sequence = SKAction.sequence([moveDownBlock, stretchRopeBlock])
self.run(sequence)
}
func moveCharacter(){
self.enumerateChildNodes(withName: "anchor", using: {(node, stop) -> Void in
if let anchorNode = node as? SKSpriteNode {
if anchorNode.position.x < 120 {
anchorNode.position = CGPoint(x: anchorNode.position.x + self.characterVelocity, y: anchorNode.position.y)
self.direction = "forward"
} else if anchorNode.position.x > self.size.width - 120 {
anchorNode.position = CGPoint(x: anchorNode.position.x - self.characterVelocity, y: anchorNode.position.y)
self.direction = "backward"
} else if self.direction == "forward" {
anchorNode.position = CGPoint(x: anchorNode.position.x + self.characterVelocity, y: anchorNode.position.y)
self.direction = "forward"
} else {
anchorNode.position = CGPoint(x: anchorNode.position.x - self.characterVelocity, y: anchorNode.position.y)
self.direction = "backward"
}
}
})
}
}
First of all you have a lot of code in this question...too much, it makes it hard to focus on what is really happening. You are also missing a block of code for moveCharacter().
You should strongly look at creating subclasses for your player, rope and (mostly)candies. I would also look into creating an array of candies initially so that you are not dynamically creating physics objects during run time.
As for your question wouldn't it be as simple as creating a couple of variables in your class
private var isMoving = false
private var didGetCandy = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//prevent the user from firing the move while in the middle of a move
guard !isMoving else { return }
isMoving = true
let moveDownBlock = SKAction.run({
self.anchor.run(moveDown) {
//this is an appreviated form of a 'completion' block
self.isMoving = false
print("did I get a candy \(didGetCandy)")
}
})
}
func collisionBetween(candy: SKNode, object: SKNode) {
didGetCandy = true
}

ios - Swift 2.2 - SpriteKit - sublassing SKSpriteNode class

For a puzzle game project on iOS, I try to subclass the SKSpriteNode class from SpriteKit with the following code:
class SKPuzzle: SKSpriteNode {
var name2:String = "";
}
I need to add other variables in SKSpriteNode like another name (name2) in this case. Here is the use I made of the class in a class type SKScene:
class GameScene: SKScene {
let background = SKSpriteNode(imageNamed: "BW")
var selectedNode = SKPuzzle()
override init(size: CGSize) {
super.init(size: size)
let imageNames = [sheet.Puzzle13() , sheet.Puzzle19(),sheet.Puzzle30(),
sheet.Puzzle11(), sheet.Puzzle29(), sheet.Puzzle35() ]
for i in 0..<imageNames.count {
let imageName = imageNames[i]
let sprite = SKPuzzle(texture: imageName)
sprite.name = kAnimalNodeName
sprite.name2 = "\(i)"
let offsetFraction = (CGFloat(i) + 1.0)/(CGFloat(imageNames.count) + 1.0)
sprite.position = CGPoint(x: size.width * offsetFraction, y: size.height / 2)
sprite.zPosition = 1
background.addChild(sprite)
}
}
I have the sprite object from the subclass SKPuzzle wich contains the new variable name2.
sprite.name2 = "\(i)"
The problem I have is the variable selectedNode (created with)
var selectedNode = SKPuzzle()
used later in the program contain always a nil value for the data name and name2. When I click on the jigsaw parts of the game, I get the following error:
fatal error: unexpectly found nil while unwrapping an optional value in the following function:
func panForTranslation(translation : CGPoint) {
let position = selectedNode.position
if selectedNode.name! == kAnimalNodeName {
selectedNode.position = CGPoint(x: position.x + translation.x * 2, y: position.y + translation.y * 2)
}
}
selectedNode seems containing only nil values. The code is working fine when I just use the SKSpriteNode but failed with my SKPuzzle class.
Here is the whole code of the program:
import SpriteKit
import UIKit
private let kAnimalNodeName = "puzzle"
private let kdancing = "dancing"
class SKPuzzle: SKSpriteNode {
var name2:String = "";
}
class GameScene: SKScene {
let background = SKSpriteNode(imageNamed: "BW")
var selectedNode = SKPuzzle()
var selectedVideo = SKVideoNode()
override init(size: CGSize) {
super.init(size: size)
// 1
self.background.name = kdancing
self.background.anchorPoint = CGPointZero
background.zPosition = 0
self.addChild(background)
//background.play()
// 2
let sheet = Statiques()
let sprite_dancing1 = SKSpriteNode(texture: sheet.Dancing1())
let sprite_dancing2 = SKSpriteNode(texture: sheet.Dancing2())
sprite_dancing1.name = kdancing
sprite_dancing2.name = kdancing
sprite_dancing1.position = CGPoint(x: 837, y: 752)
sprite_dancing1.zPosition = 1
sprite_dancing2.position = CGPoint(x: 1241, y: 752)
sprite_dancing2.zPosition = 1
background.addChild(sprite_dancing1)
background.addChild(sprite_dancing2)
let imageNames = [sheet.Puzzle13() , sheet.Puzzle19(), sheet.Puzzle30(), sheet.Puzzle11(), sheet.Puzzle29(), sheet.Puzzle35() ]
for i in 0..<imageNames.count {
let imageName = imageNames[i]
let sprite = SKPuzzle(texture: imageName)
sprite.name = kAnimalNodeName
sprite.name2 = "\(i)"
let offsetFraction = (CGFloat(i) + 1.0)/(CGFloat(imageNames.count) + 1.0)
sprite.position = CGPoint(x: size.width * offsetFraction, y: size.height / 2)
sprite.zPosition = 1
background.addChild(sprite)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let positionInScene = touch.locationInNode(self)
selectNodeForTouch(positionInScene)
}
}
override func didMoveToView(view: SKView) {
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("handlePanFrom:"))
self.view!.addGestureRecognizer(gestureRecognizer)
}
func handlePanFrom(recognizer : UIPanGestureRecognizer) {
if recognizer.state == .Began {
var touchLocation = recognizer.locationInView(recognizer.view)
touchLocation = self.convertPointFromView(touchLocation)
self.selectNodeForTouch(touchLocation)
} else if recognizer.state == .Changed {
var translation = recognizer.translationInView(recognizer.view!)
translation = CGPoint(x: translation.x, y: -translation.y)
self.panForTranslation(translation)
recognizer.setTranslation(CGPointZero, inView: recognizer.view)
} else if recognizer.state == .Ended {
}
}
func degToRad(degree: Double) -> CGFloat {
return CGFloat(degree / 180.0 * M_PI)
}
func selectNodeForTouch(touchLocation : CGPoint) {
// 1
let touchedNode = self.nodeAtPoint(touchLocation)
if touchedNode is SKPuzzle {
// 2
if !selectedNode.isEqual(touchedNode) {
selectedNode.removeAllActions()
selectedNode.runAction(SKAction.rotateToAngle(0.0, duration: 0.1))
//selectedNode = touchedNode as! SKSpriteNode
// 3
if touchedNode.name! == kAnimalNodeName {
let sequence = SKAction.sequence([SKAction.rotateByAngle(degToRad(-4.0), duration: 0.1),
SKAction.rotateByAngle(0.0, duration: 0.1),
SKAction.rotateByAngle(degToRad(4.0), duration: 0.1)])
selectedNode.runAction(SKAction.repeatActionForever(sequence))
}
}
}
}
func panForTranslation(translation : CGPoint) {
let position = selectedNode.position
if selectedNode.name! == kAnimalNodeName {
selectedNode.position = CGPoint(x: position.x + translation.x * 2, y: position.y + translation.y * 2)
}
}
}
Thanks by advance for your help, I'm beginning to code with Swift on iOS.
When selectedNode is created by SKPuzzle(), name is nil.
You have to set selectedNode.name to some value in init method.

How to combine two SKTextures into one

I am trying to create a background fora game and have two background images. I can use two different SKSpriteNode(), SKTexture, SKAction.sequence..etc and some math to stitch them together but it comes out kind of choppy and also I cant seem to get the math just right. They are supposed to be able to be combined to make a long changing background but it always offsets.
Can someone tell me how to combine both textures into one so I dont have to break my head with the math. Or if someone spots some errors in my math can you please point them out? both background have the same width of 1200 pixels
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var bgTexture = SKTexture(imageNamed: "bg_2_midground_1.png")
var bgTexture2 = SKTexture(imageNamed: "bg_2_midground_2.png")
var totalBG = bgTexture.size().width + bgTexture2.size().width
var moveBG1 = SKAction.moveByX(-bgTexture.size().width, y: 0, duration: 12)
//shift bg back to be able to repeat itself
var replaceBG1 = SKAction.moveByX(bgTexture.size().width, y: 0, duration: 0.0) //move immediately back to replace self
var moveBG1Forever = SKAction.repeatActionForever(SKAction.sequence([moveBG1,replaceBG1]))
var moveBG2 = SKAction.moveByX(-bgTexture2.size().width, y: 0, duration: 12)
//shift bg back to be able to repeat itself
var replaceBG2 = SKAction.moveByX(bgTexture2.size().width, y: 0, duration: 0.0) //move immediately back to replace self
var moveBG2Forever = SKAction.repeatActionForever(SKAction.sequence([moveBG2,replaceBG2]))
for var i:CGFloat = 0; i<2; i++ {
var bg = SKSpriteNode(texture: bgTexture)
var bg2 = SKSpriteNode(texture: bgTexture2)
bg.position = CGPointMake(bgTexture.size().width/2.0 + bgTexture.size().width * i + bgTexture2.size().width * i, CGRectGetMidY(self.frame))
bg2.position = CGPointMake(bgTexture.size().width/2.0 + bgTexture2.size().width + bgTexture2.size().width * 2 * i, CGRectGetMidY(self.frame))
bg.runAction(moveBG1Forever)
bg2.runAction(moveBG2Forever)
self.addChild(bg)
self.addChild(bg2)
println(bg.position)
println(bgTexture.size().width)
println(bg2.position)
println(bgTexture2.size().width)
}
}
Instead of using SKActions, you can use the update function to move all background nodes together, moving each node to the end as soon as each node goes out of the screen.
To make things more manageable, we can create a custom Background SKNode.
class BackgroundNode : SKNode
{
override init() {
super.init()
var bgTexture1 = SKTexture(imageNamed: "b1.jpg")
var bgTexture2 = SKTexture(imageNamed: "b2.png")
let totalBG = bgTexture1.size().width + bgTexture2.size().width
for index in 0..<2
{
var bg1 = SKSpriteNode(texture: bgTexture1)
var bg2 = SKSpriteNode(texture: bgTexture2)
bg1.anchorPoint = CGPointMake(0, 0)
bg2.anchorPoint = CGPointMake(0, 0)
let i = CGFloat(index)
bg1.position = CGPointMake(i * bgTexture1.size().width + i * bgTexture2.size().width, 0)
bg2.position = CGPointMake((i+1) * bgTexture1.size().width + i * bgTexture2.size().width, 0)
self.addChild(bg1)
self.addChild(bg2)
lastNode = bg2
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var speedOfBackground : CGFloat = 300.0
var previousTime : CFTimeInterval = -1
var lastNode : SKSpriteNode!
func update(currentTime: CFTimeInterval) {
if previousTime != -1
{
var outOfBoundsSprite : SKSpriteNode? = nil
let deltaX = speedOfBackground * CGFloat(currentTime - previousTime)
for sknode in self.children
{
if let sprite = sknode as? SKSpriteNode
{
sprite.position = CGPointMake(sprite.position.x - deltaX, sprite.position.y)
if (sprite.position.x < -sprite.size.width)
{
outOfBoundsSprite = sprite
}
}
}
if (outOfBoundsSprite != nil)
{
outOfBoundsSprite?.position.x = lastNode.position.x + lastNode.size.width
lastNode = outOfBoundsSprite!
}
}
previousTime = currentTime
}
}
You can vary the speedOfBackground property to vary the speed of movement.
It can be used in the SKScene like this
class GameScene: SKScene ,SKPhysicsContactDelegate {
var background : BackgroundNode!
override func didMoveToView(view: SKView) {
background = BackgroundNode()
background.speedOfBackground = 500.0
self.addChild(background)
}
override func update(currentTime: CFTimeInterval) {
background.update(currentTime)
}
}
#rakeshbs - Your answer was perfect, I did decide to expand it a bit. This will allow anyone to throw a Atlas in their project and be able to set up a scrolling background of any number of images. HOWEVER, watch out for loading to many assets at once, if you do that you need to remove them from the game or bad things might happen. =P
import SpriteKit
class BackgroundNode: SKNode {
override init() {
super.init()
// create containers for all the Textures and Nodes
var backgroundTextures = [SKTexture]()
var backgroundSpriteNodes = [SKSpriteNode]()
// load the TextureAtlas for the Background
let backgroundAtlas : SKTextureAtlas = SKTextureAtlas(named: "BackgroundImages")
let imagesCount:Int = backgroundAtlas.textureNames.count
// fetch all of the image resources and store them as both textures and spriteNodes
for var i = 1; i <= imagesCount; i++ {
let imageTextureName = "background_\(i)"
let imageTexture = backgroundAtlas.textureNamed(imageTextureName)
let spriteNode = SKSpriteNode(texture: imageTexture)
spriteNode.anchorPoint = CGPointMake(0, 0)
backgroundTextures.append(imageTexture)
backgroundSpriteNodes.append(spriteNode)
}
// for each image figure out what the x value of the location should be
for var i = 0; i < imagesCount; i++ {
var addtionalWidth:CGFloat = 0
// add the x value of all the images before this image to come up with the latest x
for var j = 0; j < i; j++ {
addtionalWidth += backgroundTextures[j].size().width
}
// assign each SKNode a position
backgroundSpriteNodes[i].position = CGPointMake(0 + addtionalWidth, 0)
// add the Node to the scene
self.addChild(backgroundSpriteNodes[i])
}
// keep track of where you are to reset scrolls images
lastNode = backgroundSpriteNodes[imagesCount - 1]
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var speedOfBackground : CGFloat = 300.0
var previousTime : CFTimeInterval = -1
var lastNode : SKSpriteNode!
func update(currentTime: CFTimeInterval) {
if previousTime != -1 {
var outOfBoundsSprite : SKSpriteNode? = nil
let deltaX = speedOfBackground * CGFloat(currentTime - previousTime)
for sknode in self.children {
if let sprite = sknode as? SKSpriteNode {
sprite.position = CGPointMake(sprite.position.x - deltaX, sprite.position.y)
if (sprite.position.x < -sprite.size.width) {
outOfBoundsSprite = sprite
}
}
}
if (outOfBoundsSprite != nil) {
outOfBoundsSprite?.position.x = lastNode.position.x + lastNode.size.width
lastNode = outOfBoundsSprite!
}
}
previousTime = currentTime
}
}

Resources