I'm trying to have a contact to be detected by SpriteKit in Swift 4. I set a flipper to a certain category and a ball to another one. Then I set their collision and contact mask to each other, I add a contactDelegate in didMove to, and print "contact" to inform that there is a contact in a didEnterFonction. However I cannot detect any contact at all. Even after following all the tutorials, and scouring through similar questions. I do not understand what I am doing wrong. My end goal is to have the ball stop from falling when it hits the flipper, but I am trying to detect any contact at all in the first place which do not work.
import SpriteKit
import GameplayKit
struct PhysicsCategory {
static let none : UInt32 = 0
static let all : UInt32 = UInt32.max
static let flip : UInt32 = 0b1
static let ball: UInt32 = 0b10 // 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
private var flip1: SKSpriteNode?
private var flip2: SKSpriteNode?
private var spinnyNode : SKShapeNode?
private var balle:SKSpriteNode?
override func sceneDidLoad() {
self.lastUpdateTime = 0
self.flip1 = self.childNode(withName:"//flip1") as? SKSpriteNode
self.flip2 = self.childNode(withName: "//flip2") as? SKSpriteNode
self.balle = self.childNode(withName: "//ball") as?SKSpriteNode
self.flip1?.physicsBody = SKPhysicsBody()
self.flip1?.physicsBody?.affectedByGravity = false
self.flip1?.physicsBody?.allowsRotation = true
self.balle?.physicsBody = SKPhysicsBody();
//self.balle?.physicsBody?.affectedByGravity = true
self.flip2?.physicsBody = SKPhysicsBody()
self.flip2?.physicsBody?.affectedByGravity = false
self.flip2?.physicsBody?.allowsRotation = true
self.balle?.physicsBody?.allContactedBodies()
self.flip1?.physicsBody?.usesPreciseCollisionDetection
= true
self.flip1?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip1?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip1?.physicsBody?.collisionBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip2?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.collisionBitMask = PhysicsCategory.ball
// Get label node from scene and store it for use later
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
// Create shape node to use during mouse interaction
let w = (self.size.width + self.size.height) * 0.05
self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: w, height: w), cornerRadius: w * 0.3)
if let spinnyNode = self.spinnyNode {
spinnyNode.lineWidth = 2.5
spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
SKAction.fadeOut(withDuration: 0.5),
SKAction.removeFromParent()]))
}
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case 0x31:
if let label = self.label {
label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
}
case 123:
print("left")
self.flip1?.run(SKAction.rotate(byAngle: -1.5, duration: 0.2))
// self.flip1?.physicsBody?.applyAngularImpulse(600)
self.flip1?.run(SKAction.rotate(byAngle: 1.5, duration: 0.1))
case 124:
self.flip2?.run(SKAction.rotate(byAngle: 1.5, duration: 0.2))
self.flip2?.run(SKAction.rotate(byAngle: -1.5, duration: 0.1))
case 15:
self.balle?.run(SKAction.move(to: CGPoint(x: 50,y: 50), duration: 1))
default:
print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
func didEnter(_ contact: SKPhysicsContact) {
print("contact")
}
}
You haven't given the physic bodies a shape or size. Take a look here and read the 'Creating a Body from a ___' sections.
Related
I've create a SKNode with physicsBody edgeFromLoop circular inside which i've added 8 small circular nodes. and 8 circular nodes are moving with CMMotionManager inside Parent circular node. with fast motion shake some balls disappears from screen. SKScene Class given below
Balls only disappears when anyone shake mobile hard randomly.
There are 8 balls initially but after some hard shake reduced.
class GameScene: SKScene {
let motionManager = CMMotionManager()
var Circle = SKShapeNode(circleOfRadius: 108)
var sound = SKAction.playSoundFileNamed(getRandomSound(), waitForCompletion: false)
var collisionBitmasks: [UInt32] = [UInt32]()
override func didMove(to view: SKView) {
Circle.fillTexture = SKTexture.init(image: UIImage.init(named: "img_Ball") ?? UIImage())
Circle.position = CGPoint.init(x: 0, y: 0)
Circle.name = "defaultCircle"
Circle.lineWidth = 0.0
Circle.fillColor = SKColor.lightGray.withAlphaComponent(0.8)
Circle.physicsBody = SKPhysicsBody.init(edgeLoopFrom: UIBezierPath.init(ovalIn: CGRect.init(x: Circle.frame.minX + 12,
y: Circle.frame.minY + 12,
width: Circle.frame.width - 24,
height: Circle.frame.height - 24)).cgPath)
Circle.physicsBody?.isDynamic = true
Circle.physicsBody?.affectedByGravity = false
Circle.physicsBody?.allowsRotation = false
addChild(Circle)
self.physicsWorld.contactDelegate = self
startAcceleroMeter()
for index in 1...8 {
addBalls(index)
}
}
func stop() {
motionManager.stopAccelerometerUpdates()
Circle.children.forEach { (ball) in
ball.physicsBody?.isDynamic = false
ball.physicsBody?.affectedByGravity = false
}
}
func startAcceleroMeter() {
Circle.children.forEach { (ball) in
ball.physicsBody?.isDynamic = true
ball.physicsBody?.affectedByGravity = true
}
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: .main) { (motionData, error) in
let gravity = CGVector.init(dx: (motionData?.acceleration.x ?? 0.0) * 20, dy: (motionData?.acceleration.y ?? 0.0) * 20)
print(gravity)
self.physicsWorld.gravity = gravity
}
}
func stopPlaying() -> [String] {
var dictionary = [String]()
for (_, ball) in (Circle.children).enumerated() {
dictionary.append("\(ball.position.x), \(ball.position.y), 0.0")
}
return dictionary
}
func addBalls(_ ballNo: Int) {
let ball = SKShapeNode.init(circleOfRadius: 12)
ball.name = "ball\(ballNo)"
ball.fillTexture = SKTexture.init(linearGradientWithAngle: CGFloat.pi, colors: [BallColors(rawValue: ballNo)?.toUIColor(false) ?? UIColor(), BallColors(rawValue: ballNo)?.toUIColor(true) ?? UIColor()], locations: [0, 1], size: ball.frame.size)
ball.fillColor = UIColor.white
ball.physicsBody = SKPhysicsBody.init(circleOfRadius: 12)
ball.physicsBody?.isDynamic = true
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.allowsRotation = true
ball.physicsBody?.friction = 0.2
ball.physicsBody?.restitution = 0.9
ball.physicsBody?.linearDamping = 0.1
ball.physicsBody?.angularDamping = 0.1
ball.physicsBody?.mass = 0.349065870046616
ball.physicsBody?.usesPreciseCollisionDetection = true
ball.position = CGPoint(x: Circle.frame.midX - CGFloat(ballNo), y: Circle.frame.midY - CGFloat(ballNo))
ball.physicsBody?.fieldBitMask = 1
ball.physicsBody?.categoryBitMask = UInt32.init(ballNo + 100)
let collisionBitMask = UInt32.init(ballNo + 20) | UInt32.init(ballNo + 100)
collisionBitmasks.append(collisionBitMask)
ball.physicsBody?.collisionBitMask = collisionBitMask //To be different for each
ball.physicsBody?.contactTestBitMask = UInt32.init(ballNo + 20)
Circle.addChild(ball)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
if collisionBitmasks.contains(contact.bodyA.collisionBitMask) && collisionBitmasks.contains(contact.bodyB.collisionBitMask) {
run(sound)
}
}
}
You need a hidden magic here. Each small ball adds a constraint that can prevent any accident from high speeds:
ball.constraints = [SKConstraint.distance(SKRange(upperLimit: 108 - 12), to: Circle)]
This is my GameScene code.
class GameScene: SKScene, SKPhysicsContactDelegate {
let orcWidth = UIScreen.main.bounds.width / 5
var orcCategory:UInt32 = 0x1 << 0
var knightCategory:UInt32 = 0x1 << 1
private var orc = SKSpriteNode()
private var knight = SKSpriteNode()
private var orcWalkingFrames: [SKTexture] = []
private var knightIdleFrames: [SKTexture] = []
private var knightAttackFrame: [SKTexture] = []
var background = SKSpriteNode(imageNamed: "game_background1")
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
setupbackground()
startGame()
}
func setupbackground() {
background.zPosition = 0
background.size = self.frame.size
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background)
}
func startGame() {
buildRandomOrcs()
buildKnight()
}
func stopGame() {
}
func buildOrc(yposition: CGFloat) {
var orcWalkFrames: [SKTexture] = []
let orcAnimatedAtlas = SKTextureAtlas(named: "OrcWalking")
let numImages = orcAnimatedAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "0_Orc_Walking_\(i)"
orcWalkFrames.append(orcAnimatedAtlas.textureNamed(orcTextureName))
}
self.orcWalkingFrames = orcWalkFrames
let firstFrameTexture = orcWalkingFrames[0]
orc = SKSpriteNode(texture: firstFrameTexture)
orc.name = "orc"
orc.position = CGPoint(x: frame.minX-orcWidth/2, y: yposition)
self.orc.zPosition = CGFloat(self.children.count)
orc.scale(to: CGSize(width: orcWidth, height: orcWidth))
orc.physicsBody = SKPhysicsBody(rectangleOf: orc.size, center: orc.position)
orc.physicsBody?.affectedByGravity = false
orc.physicsBody?.isDynamic = true
orc.physicsBody?.categoryBitMask = orcCategory
orc.physicsBody?.contactTestBitMask = knightCategory
orc.physicsBody?.collisionBitMask = knightCategory
addChild(orc)
walkOrc()
moveOrcForward()
}
func buildKnight() {
var knightIdleFrames: [SKTexture] = []
let knightIdleAtlas = SKTextureAtlas(named: "KnightIdle")
let numImages = knightIdleAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "_IDLE_00\(i)"
knightIdleFrames.append(knightIdleAtlas.textureNamed(orcTextureName))
}
self.knightIdleFrames = knightIdleFrames
let firstFrameTexture = knightIdleFrames[0]
knight = SKSpriteNode(texture: firstFrameTexture)
knight.name = "knight"
knight.position = CGPoint(x: frame.maxX-orcWidth/2, y: frame.midY)
self.knight.zPosition = 1
knight.scale(to: CGSize(width: -orcWidth, height: orcWidth))
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
knight.physicsBody?.affectedByGravity = false
knight.physicsBody?.isDynamic = false
knight.physicsBody?.categoryBitMask = knightCategory
knight.physicsBody?.contactTestBitMask = orcCategory
knight.physicsBody?.collisionBitMask = orcCategory
addChild(knight)
idleKnight()
}
func idleKnight() {
knight.run(SKAction.repeatForever(SKAction.animate(with: knightIdleFrames, timePerFrame: 0.1)))
}
func walkOrc() {
orc.run(SKAction.repeatForever(SKAction.animate(with: orcWalkingFrames,timePerFrame: 0.025)))
}
func moveOrcForward() {
orc.run(SKAction.repeatForever(SKAction.moveBy(x: 55, y: 0, duration: 0.25)))
}
func buildRandomOrcs () {
let wait = SKAction.wait(forDuration: TimeInterval(makeRandomNumberBetween(min: 0, max: 0)))
let spawn = SKAction.run {
self.buildOrc(yposition: self.makeRandomCGFloatNumber())
}
let spawning = SKAction.sequence([spawn,wait])
self.run(SKAction.repeat(spawning, count: 10))
}
func makeRandomCGFloatNumber() -> CGFloat {
let randomNumber = arc4random_uniform(UInt32((frame.maxY-orcWidth/2) - (frame.minY+orcWidth/2))) + UInt32(frame.minY+orcWidth/2)
return CGFloat(randomNumber)
}
func makeRandomNumberBetween (min: Int, max: Int) -> Int{
let randomNumber = arc4random_uniform(UInt32(max - min)) + UInt32(min)
return Int(randomNumber)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision:UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == orcCategory | knightCategory {
self.scene?.view?.isPaused = true
print("COLLIDED")
}
}
}
The problem is that the scene pauses almost 2-3 seconds after the collision.
I changed the position of knight and delay time changed.
For example, if I set position to frame.minX+orcWidth/2 there is no delay.
What is wrong with my code?
Your problem isn't things are being delayed, your problem is your bounding box is not where you think it is
use view.showPhysics = true to determine where your boxes are
once you realize they are in the wrong spots, go to this line
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
and fix it
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size)
Do so for the rest of your bodies
My guess is that manipulations of SKView properties must happen on main thread, i.e.
DispatchQueue.main.async { [unowned self] in
self.scene?.view?.isPaused = true
print("COLLIDED")
}
So I've got a class named FlashyPaddleEffect. I also mention it in another class, GameScene, but it doesn't apply any effect that it should. The paddle should be flashing blue and white colors, but it simply stays white. The paddle also should lose its physics body when it is blue.
If you need any other information, don't hesitate to ask.
NOTE: There might be problems with the code I gave (because of indents, it's quite tricky to make code by indenting 4 spaces every line, sorry).
import SpriteKit
import GameplayKit
class FlashyPaddleEffect {
var node = SKSpriteNode()
var ballNode = SKSpriteNode()
var updateTimer: Timer? = nil
var timer: Timer? = nil
#objc func changeNodeColor() {
switch node.color {
case SKColor.blue: node.color = SKColor.white
case SKColor.white: node.color = SKColor.blue
default: _ = 1 + 2
}
}
#objc func update() //I used the objc prefix to silence the warning the selectors of the timers produced. {
let previousPhysicsBody = node.physicsBody
if node.color == SKColor.blue {
node.physicsBody = nil
}
node.physicsBody = previousPhysicsBody
}
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
node = applyEffectTo
ballNode = ball
timer = Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(changeNodeColor), userInfo: nil, repeats: true)
updateTimer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(update), userInfo: nil, repeats: true)
_ = node
_ = timer
}
}
class GameScene: SKScene {
var ball = SKSpriteNode()
var player = SKSpriteNode()
var enemy = SKSpriteNode()
var scores = [0, 0]
var initScores = [0, 0]
var scoreLabels: [SKLabelNode]? = nil
let playLabel = SKLabelNode()
let timeLabel = SKLabelNode()
let timeUpLabel = SKLabelNode()
var secondsLeft: Int = 180
var initialTime: Int? = nil
var timer = Timer()
var amountOfPauseMenuCloses = 0
var resultsLabel = SKLabelNode()
let flashEffect = FlashyPaddleEffect() //I define a variable to mention the class easier later on.
override func didMove(to view: SKView) {
//Initialize nodes of the scene editor.
ball = self.childNode(withName: "Ball") as! SKSpriteNode
player = self.childNode(withName: "PPaddle") as! SKSpriteNode
enemy = self.childNode(withName: "EPaddle") as! SKSpriteNode
//Set styles for the Play Notification Label (referred to as PNL later)
playLabel.position = CGPoint(x: 0, y: 0)
playLabel.text = "Tap anywhere to play."
playLabel.name = "playLabel"
//Set styles for the Timer Label (referred to as Timer later)
timeLabel.position = CGPoint(x: 90, y: 0)
timeLabel.text = String(secondsLeft)
//Doing manipulations connected with scores here.
scores = [0, 0]
initScores = scores
scoreLabels = [self.childNode(withName: "PScoreLabel") as! SKLabelNode, self.childNode(withName: "EScoreLabel") as! SKLabelNode]
//Create a border for our ball to bounce off.
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
border.linearDamping = 0
border.angularDamping = 0
self.physicsBody = border
//To avoid the ball's damping.
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
showPause() //Show the (pause) menu at the beginning
//Set a variable to refer to as a time standard later.
initialTime = secondsLeft
//The game timer.
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(reduceSecondFromTimer), userInfo: nil, repeats: true)
flashEffect.make(applyEffectTo: player, ball: ball) //This is where I reference the class in another class.
}
//A function to show the (pause) menu.
func showPause(hideNodes: Bool = true) {
self.addChild(playLabel)
if hideNodes == true {
ball.removeFromParent()
player.removeFromParent()
enemy.removeFromParent()
scoreLabels?[0].removeFromParent()
scoreLabels?[1].removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
var alreadyChangedTextLabel = false
for i in scoreLabels! {
if i.name == "PScoreLabel" {i.text = String(scores[0])}
if i.name == "EScoreLabel" {i.text = String(scores[1])}
}
if ball.position.y <= player.position.y {scores[1] += 1}
if ball.position.y >= enemy.position.y {scores[0] += 1}
if secondsLeft == 0 {
showPause(hideNodes: false)
if alreadyChangedTextLabel == false {
timeUpLabel.text = "TIME UP! \(whoIsWinning(scores: scores)) won!"
alreadyChangedTextLabel = true
}
timeUpLabel.name = "timeUpLabel"
timeUpLabel.position = CGPoint(x: 0, y: 180)
if !(self.children.contains(timeUpLabel)) {
self.addChild(timeUpLabel)
}
timeLabel.removeFromParent()
}
if self.children.contains(playLabel) {
secondsLeft = initialTime!
}
timeLabel.text = String(secondsLeft)
let alignWithBall = SKAction.move(to: CGPoint(x: ball.position.x, y: enemy.position.y), duration: 0.8)
enemy.run(alignWithBall)
}
func initGame() {
//Initializing process for every game.
scores = initScores
ball.position = CGPoint(x: 0, y: 0)
if amountOfPauseMenuCloses == 1 {ball.physicsBody?.applyImpulse(CGVector(dx: 15, dy: 15))}
secondsLeft = initialTime!
timeLabel.text = String(secondsLeft)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for childNode in self.children {
if childNode.name == "playLabel" {
if self.children.contains(playLabel) {
playLabel.removeFromParent()
}
if !(self.children.contains(player)) {
self.addChild(player)
}
if !(self.children.contains(enemy)) {
self.addChild(enemy)
}
if !(self.children.contains(ball)) {
self.addChild(ball)
}
if !(self.children.contains((scoreLabels?[0])!)) {
self.addChild((scoreLabels?[0])!)
}
if !(self.children.contains((scoreLabels?[1])!)) {
self.addChild((scoreLabels?[1])!)
}
if !(self.children.contains(timeLabel)) {
self.addChild(timeLabel)
}
if self.children.contains(timeUpLabel) {
timeUpLabel.removeFromParent()
}
amountOfPauseMenuCloses += 1
initGame()
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
if self.nodes(at: t.location(in: self)).contains(player) {player.position.x = t.location(in: self).x}
}
}
func reduceSecondFromTimer() {
secondsLeft -= 1
}
func whoIsWinning(scores: Array<Int>) -> String {
var r: String? = nil
if scores[0] >= scores[1] {
r = "You"
}
if scores[1] >= scores[0] {
r = "Enemy"
}
return r!
}
}
Thanks a lot for answers.
P.S It's my first question ever so don't judge me strictly.
1) Do not use NSTimer use SKAction. I can see the way you are doing it you stack timer after timer, this is bad.
2) Do not have your temp variable global (node in this case), it makes code hard to read
3) Do not remove your physics body, simply remove the category.
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
ballNode = ball
let blue = SKAction.colorize(with SKColor.blue, colorBlendFactor: 1.0, duration sec: 0)
let white = SKAction.colorize(with SKColor.white, colorBlendFactor: 1.0, duration sec: 0)
let wait = SKAction.wait(for:0.6)
let turnOnPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = #######})
let turnOffPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = 0})
let seq = [blue, turnOffPhysics,wait,white,turnOnPhysics,wait]
let repeat = SKAction.repeatForever(seq)
applyEffectTo.run(repeat, withKey:"flashing")
}
Note: I have no idea what your categoryBitmask is, you need to fill it in
So I want a game made, this was striped from the Xcode SpriteKit sampler, pretty simple. It will evolve greatly as I get this key issue out of the way. It has a player, Wall's, and a door. Nodes are assigned, player works fine. Wall's attempted for children in self, but crashes with my comments removed. I have a guess as multiple nodes of same name? But the door, when assigned node, for some reason no matter what slowly falls, with no gravity ticked and no gravity coded.
Those are lesser concerns. I come to you today to pick at why my collisions might not be activating my collision argument functions, to enter the house.
Yes I am aware it says contact mapped to the event. It suits my theory I am pretty sure.
//
// GameScene.swift
// Sandbox
//
// Created by M on 7/1/16.
// Copyright © 2016 M. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
var playerNode : SKSpriteNode?
var wallNode : SKSpriteNode?
var doorNode : SKSpriteNode?
private var spinnyNode : SKShapeNode?
var furnishing : SKSpriteNode?
var playerCategory = 0x1 << 0
var wallCategory = 0x1 << 1
var doorCategory = 0x1 << 2
var pathCategory = 0x1 << 3
func nextRoom() {
let sceneNode = SKScene(fileNamed: "MyScene")
sceneNode?.scaleMode = .aspectFill
// Present the scene
if let view = self.view {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func loadRoom() {
let furnishing = SKSpriteNode(color: #colorLiteral(red: 0.2464724183, green: 0.05352632701, blue: 0.03394328058, alpha: 1), size:CGSize(width:25, height:25))
doorNode?.addChild(furnishing)
}
func enterHouse() {
let newWindow = CGSize(width: 500, height: 500)
doorNode?.scale(to: newWindow)
loadRoom()
}
func exitHouse(){
let oldWindow = CGSize(width: 100, height: 100)
doorNode?.scale(to: oldWindow)
}
override func sceneDidLoad() {
self.lastUpdateTime = 0
physicsWorld.contactDelegate = self
// Get nodes from scene and store for use later
self.playerNode = self.childNode(withName: "//player") as? SKSpriteNode
playerNode?.physicsBody = SKPhysicsBody(rectangleOf: (playerNode?.frame.size)!)
playerNode?.physicsBody?.isDynamic = true
playerNode?.physicsBody?.affectedByGravity = false
playerNode?.physicsBody?.categoryBitMask = UInt32(playerCategory)
playerNode?.physicsBody?.collisionBitMask = UInt32(wallCategory)
playerNode?.physicsBody?.contactTestBitMask = UInt32(doorCategory)
for child in self.children {
/*if child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
wallNode?.physicsBody?.isDynamic = false
wallNode?.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode?.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(child)
}
}*/
}
self.doorNode = self.childNode(withName: "door") as? SKSpriteNode
doorNode?.physicsBody?.affectedByGravity = false
doorNode?.physicsBody?.isDynamic = false
doorNode?.physicsBody = SKPhysicsBody(rectangleOf: (doorNode?.frame.size)!)
doorNode?.physicsBody?.categoryBitMask = UInt32(doorCategory)
doorNode?.physicsBody?.contactTestBitMask = UInt32(playerCategory)
}
func touchDown(atPoint pos : CGPoint) {
let fromX = playerNode?.position.x
let fromY = playerNode?.position.y
let toX = pos.x
let toY = pos.y
let resultX = toX - (fromX)!
let resultY = toY - (fromY)!
let newX = (playerNode?.position.x)! + resultX / 10
let newY = (playerNode?.position.y)! + resultY / 10
playerNode?.position.x = newX
playerNode?.position.y = newY
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
func didBeginContact(contact: SKPhysicsContact) {
//this gets called automatically when two objects begin contact with each other
// 1. Create local variables for two physics bodies
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
// 2. Assign the two physics bodies so that the one with the lower category is always stored in firstBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if secondBody.categoryBitMask == UInt32(doorCategory){
enterHouse()
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(withDeltaTime: dt)
}
self.lastUpdateTime = currentTime
}
}
I presume this line is causing the crash
wallNode?.physicsBody = SKPhysicsBody(rectangleOf: (wallNode?.frame.size)!)
as you are force unwrapping (!) the wallNode size however looking at your code you never assign it to anything like so
wallNode = self.childNode(withName: "wallNode") as? SKSpriteNode
or in the for loop.
Try this code in your for in loop that should avoid crashes and assigns your wall node.
for child in self.children where child.name == "wall" {
if let child = child as? SKSpriteNode {
wallNode = child // Try this
if let wallNode = wallNode { // safely unwrap wall node to avoid crashes
wallNode.physicsBody = SKPhysicsBody(rectangleOf: (wallNode.frame.size))
wallNode.physicsBody?.isDynamic = false
wallNode.physicsBody?.categoryBitMask = UInt32(wallCategory)
wallNode.physicsBody?.collisionBitMask = UInt32(playerCategory)
self.addChild(wallNode) // add wall node here instead if you are using your wallNode property
}
}
}
Hope this helps
I wanted to create a small animation of a car driving down the road, so I made an atlas of 9 different pictures. The car simply looks like its wheels are rotating and the car is bouncing a bit as it drives along. I already made an SKSpriteNode with an image and added a physics body on it so that it can jump and be affected by gravity.
So I was wondering how to add either a physics body to an SKAtlasTexture or create an animation through my image.xcassets folder. I tried to just change the SKSpriteNode to SKAtlasTexture, but that obviously didn't work as there are no physics bodies in SKAtlasTexture. So that's where I'm at. Any suggestions or solutions would be greatly appreciated.
Here some parts of my code:
class PlayScene: SKScene, SKPhysicsContactDelegate {
let road = SKSpriteNode(imageNamed: "road")
var origRoadPositionX = CGFloat(0)
var maxRoad = CGFloat(0)
var groundSpeed = 3
var carBaseLine = CGFloat(0)
let car = SKSpriteNode(imageNamed: "car")
enum ColliderType:UInt32{
case car = 1
case tower = 2
}
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor(hex: 0x80E8FF)
self.physicsWorld.contactDelegate = self
//Car
self.car.position = CGPointMake(CGRectGetMinX(self.frame)-20 + self.car.size.width, self.carBaseLine)
self.car.physicsBody = SKPhysicsBody (rectangleOfSize: self.car.size)
self.car.physicsBody?.allowsRotation = false
self.car.physicsBody?.affectedByGravity = false
self.car.physicsBody?.categoryBitMask = ColliderType.car.rawValue
self.car.physicsBody?.contactTestBitMask = ColliderType.tower.rawValue
self.car.physicsBody?.collisionBitMask = ColliderType.tower.rawValue
self.addChild(car)
If more code is needed in order to find a solution, let me know and i can supply more of it.
You can use atlas folder for performing animation with images.
Consider below example:
import SpriteKit
class GameScene: SKScene {
var bombFrames : [SKTexture]!
var bomb : SKSpriteNode!
let NoneCategory : UInt32 = 0x1 << 0
let ProjectileCategory : UInt32 = 0x1 << 2
let bombCategory : UInt32 = 0x1 << 7
override func didMoveToView(view: SKView) {
/* Setup your scene here */
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addBomb), SKAction.waitForDuration(5.0)])))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addBomb() {
let Name = "Bomb"
let AnimatedAtlas = SKTextureAtlas(named: Name)
var Framese = [SKTexture]()
let numImages = AnimatedAtlas.textureNames.count
for var i=1; i<=numImages; i++ {
let TextureName = "\(i)"
Framese.append(AnimatedAtlas.textureNamed(TextureName))
}
bombFrames = Framese
let firstFrame = bombFrames[0]
bomb = SKSpriteNode(texture: firstFrame)
let actualY = random(min: bomb.size.height/2, max: size.height - bomb.size.height/2)
bomb.position = CGPoint(x: size.width + bomb.size.width/2, y: actualY)
bomb.physicsBody = SKPhysicsBody(texture: bomb.texture, size: bomb.texture!.size())
bomb.physicsBody?.dynamic = true
bomb.physicsBody?.categoryBitMask = bombCategory
bomb.physicsBody?.contactTestBitMask = ProjectileCategory
bomb.physicsBody?.collisionBitMask = NoneCategory
bomb.physicsBody?.usesPreciseCollisionDetection = true
addChild(bomb)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.moveTo(CGPoint(x: -bomb.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bomb.runAction(SKAction.sequence([actionMove, actionMoveDone]))
playBombAnimation()
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func playBombAnimation() {
bomb.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(bombFrames, timePerFrame: 0.1, resize: false, restore: true)), withKey:"bombAnimation")
}
}
And don't forget to add atlas folder into your project navigator like this:
As you can see in code you can add physics body to your sprite. and if you want you can try this way.
Hope this will help.