I've just been playing around with some of the features of XCode/Swift and building a simple game. I'm having difficulty adding a physicsBody to an SKSpriteNode.
I've created a 'Bullet' class and a 'Tank' class (which has an array of 10 'Bullets' as one of its properties).
The sprite property is being assigned properly with a texture. However, the physicsBody is coming back as 'nil'.
class Tank {
var position = CGPoint()
var speed = CGPoint(x: 0, y: 0)
var sprite = SKSpriteNode()
var viewSize = CGPoint() // width, height
var bullets: [Bullet] = []
var bulletNumber = 0
let TOTAL_BULLETS = 10
// ***Tank class constructor
init(playerNum: Int, filename: NSString, tankName: NSString) {
var spaceshipTexture = SKTexture(imageNamed: "Spaceship")
self.sprite = SKSpriteNode(texture: spaceshipTexture, size: spaceshipTexture.size())
self.sprite.physicsBody? = SKPhysicsBody(texture: spaceshipTexture, size: spaceshipTexture.size())
println(self.sprite.physicsBody)
}
// ****Bullet class
class Bullet {
var position = CGPoint(x: -50, y: -50)
var speed = CGPoint(x: 0, y: 0)
var sprite = SKSpriteNode()
var viewSize = CGPoint() // width, height
var isBeingFired = false
init () {
//self.sprite = SKSpriteNode(imageNamed: "Bullet")
var spaceshipTexture = SKTexture(imageNamed: "Spaceship")
self.sprite = SKSpriteNode(texture: spaceshipTexture, size: spaceshipTexture.size())
self.sprite.xScale = 0.04
self.sprite.yScale = 0.04
self.sprite.physicsBody? = SKPhysicsBody(texture: spaceshipTexture, size: spaceshipTexture.size())
self.sprite.physicsBody?.affectedByGravity = false
println(self.sprite.physicsBody)
}
// *** GameScene
class GameScene: SKScene {
var tank1 = Tank(playerNum: 1, filename: "Spaceship", tankName: "Tank1")
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
override func didMoveToView(view: SKView) {
self.addChild(tank1.sprite)
for i in 0 ..< tank1.TOTAL_BULLETS {
self.addChild(tank1.bullets[i].sprite)
}
For simplicity sake, I'm using the same image for both the Tank and the Bullet (just different sizes) to create the SKSpriteNode.
I'm not sure if this would help, but I add an instance of the Tank and all 10 of its bullets as a child to the GameScene.
Your problem is, that you don't add a SKTexture to your SKSpriteNode but an image. If you want to access an SKTexture you need to add it to your node like that:
var spaceshipTexture = SKTexture(imageNamed: "Spaceship")
var sprite = SKSpriteNode(texture: spaceshipTexture, size: spaceshipTexture.size())
Then you will be able to access the texture of your sprite to create your SKPhysicsBody
Related
Goal: get the video feed from ARSCNView. and assign as a video material to a SceneKIt SCNGeometry.
What I did:
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
// apply AR feed to the ship node material
let material = ship.geometry?.firstMaterial
material!.diffuse.contents = arView.scene.background.contents
Problem: the ship is white, without AR video feed
Full code bellow:
import UIKit
import QuartzCore
import SceneKit
import ARKit
class GameViewController: UIViewController, ARSCNViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let arView = ARSCNView()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
let material = ship.geometry?.firstMaterial
material!.diffuse.contents = arView.scene.background.contents
material!.lightingModel = .constant
// animate the 3d object
//ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
#objc
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// get its material
let material = result.node.geometry!.firstMaterial!
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.emission.contents = UIColor.black
SCNTransaction.commit()
}
material.emission.contents = UIColor.red
SCNTransaction.commit()
}
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
Create a fresh (out of the box) ARKit SceneKit based App
(the one with the SpaceShip)
Then you modify your viewWillAppear section like this:
Add the DispatchQueue stuff below.
Important: Add a short delay (here 5.0 seconds) to give the AR Subsystem enough time to initialise the Camera.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let shipMesh = self.sceneView.scene.rootNode.childNode(withName: "shipMesh", recursively: true)
shipMesh?.geometry?.firstMaterial?.diffuse.contents = self.sceneView.scene.background.contents
}
}
I hope, this is what you were looking for.
I would like to have one piece of code in my app where I can track the textures of two nodes at a time. If the textures of the two nodes don't match I would like them reset back to their original state before the user touched them. On top of that i would like another piece of code for tracking if the particular node's textures were matched under a particular time frame like 90 seconds. Any tips on how I can do something like this for my app? Thanks.
Here is my current code:
import Foundation
import SpriteKit
class EasyScreen: SKScene {
override func didMove(to view: SKView) {
var background = SKSpriteNode(imageNamed: "Easy Screen Background")
let timerText = SKLabelNode(fontNamed: "Arial")
timerText.fontSize = 40
timerText.fontColor = SKColor.white
timerText.position = CGPoint(x: 20, y: 400)
timerText.zPosition = 1
var counter:Int = 90
timerText.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
counter-=1
timerText.text = " Time: \(counter)"
print("\(counter)")
if counter <= 0{
let newScene = TryAgainScreen(fileNamed: "Try Again Screen")
newScene?.scaleMode = .aspectFill
self.view?.presentScene(newScene)
}
},SKAction.wait(forDuration: 1)])))
background.position = CGPoint(x: 0, y: 0)
background.size.width = self.size.width
background.size.height = self.size.height
background.anchorPoint = CGPoint(x: 0.5,y: 0.5)
let matchCardOne = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardTwo = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardThree = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardFour = SKSpriteNode(imageNamed: "Fruit Match Card")
matchCardOne.name = "FruitMatchCard1"
matchCardTwo.name = "FruitMatchCard2"
matchCardThree.name = "FruitMatchCard3"
matchCardFour.name = "FruitMatchCard4"
matchCardOne.size = CGSize(width: 150, height: 300)
matchCardTwo.size = CGSize(width: 150, height: 300)
matchCardThree.size = CGSize(width: 150, height: 300)
matchCardFour.size = CGSize(width: 150, height: 300)
matchCardOne.zPosition = 1
matchCardTwo.zPosition = 1
matchCardThree.zPosition = 1
matchCardFour.zPosition = 1
matchCardOne.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardTwo.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardThree.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardFour.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardOne.position = CGPoint(x: -125, y: 60)
matchCardTwo.position = CGPoint(x: -125, y: -260)
matchCardThree.position = CGPoint(x: 70, y: 60)
matchCardFour.position = CGPoint(x: 70 , y: -260)
addChild(background)
addChild(matchCardOne)
addChild(matchCardTwo)
addChild(matchCardThree)
addChild(matchCardFour)
addChild(timerText)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view?.isMultipleTouchEnabled = false
let touch = touches.first
let positionInSceneOne = touch!.location(in: self)
let tappedNodes = nodes(at: positionInSceneOne)
for node in tappedNodes{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard1" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchTwo = touches.first
let positionInSceneTwo = touch!.location(in: self)
let tappedNodesTwo = nodes(at: positionInSceneTwo)
for node in tappedNodesTwo{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard2" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchThree = touches.first
let positionInSceneThree = touch!.location(in: self)
let tappedNodesThree = nodes(at: positionInSceneThree)
for node in tappedNodesThree{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard3" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
let touchFour = touches.first
let positionInSceneFour = touch!.location(in: self)
let tappedNodesFour = nodes(at: positionInSceneFour)
for node in tappedNodesFour{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard4" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
}
}
}
}
}
}
An app I'm building drops the FPS when sprite nodes are created, I have searched posts far and wide and cannot figure out why, if anyone had any ideas I would appreciate it!
The issues occur on the simulator and device.
the code for the creating of the nodes is below.
Thank you.
#objc func createEnemy(){
let randomDistribution = GKRandomDistribution(lowestValue: -350, highestValue: 350)
let sprite = SKSpriteNode(imageNamed: "Virus")
sprite.position = CGPoint(x: 700, y: randomDistribution.nextInt())
sprite.name = "Virus"
sprite.zPosition = 1
sprite.size = CGSize(width: 70, height: 70)
addChild(sprite)
sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!, size: sprite.size)
sprite.physicsBody?.velocity = CGVector(dx: -500, dy: 0)
sprite.physicsBody?.linearDamping = 0
sprite.physicsBody?.contactTestBitMask = 1
sprite.physicsBody?.categoryBitMask = 0
sprite.physicsBody?.affectedByGravity = false
createBonus()
}
func createBonus(){
let randomDistribution = GKRandomDistribution(lowestValue: -350, highestValue: 350)
let sprite = SKSpriteNode(imageNamed: "Vaccine")
sprite.position = CGPoint(x: 700, y: randomDistribution.nextInt())
sprite.name = "Vaccine"
sprite.size = CGSize(width: 70, height: 70)
sprite.zPosition = 1
addChild(sprite)
sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!, size: sprite.size)
sprite.physicsBody?.velocity = CGVector(dx: -500, dy: 0)
sprite.physicsBody?.linearDamping = 0
sprite.physicsBody?.contactTestBitMask = 1
sprite.physicsBody?.categoryBitMask = 0
sprite.physicsBody?.collisionBitMask = 0
sprite.physicsBody?.affectedByGravity = false
}
Did you try to preload the textures?
let image = SKTexture(imageNamed: "nodeImage")
override func didMove(to view: SKView) {
image.preload{
print("image has been preloaded")
}
}
I am working with the joint method in spritekit, but once I join two sprites it seems to change the physics bodies. I have two rectangles joined at a corner and when they fold, I would expect the blue rectangle to sit on top of the white triangle, but this is the result. How do I maintain their physics bodies, so they don't overlap?
import SpriteKit
class GameScene: SKScene {
let character = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(150, 30))
let character2 = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(150, 30))
let character3 = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(50, 300))
let screenSize: CGRect = UIScreen.mainScreen().bounds
override func didMoveToView(view: SKView) {
// let scene = GameScene(size: SKView.frame)
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
borderBody.friction = 5
self.physicsBody = borderBody
self.physicsWorld.gravity = CGVectorMake(0.0, -1.0)
character.position = CGPointMake(550, 400)
character2.position = CGPointMake(400, 400)
character3.position = CGPointMake(400, 100)
character.physicsBody = SKPhysicsBody(rectangleOfSize: character.size)
character2.physicsBody = SKPhysicsBody(rectangleOfSize: character2.size)
character3.physicsBody = SKPhysicsBody(rectangleOfSize: character3.size)
self.character.physicsBody!.dynamic = true
self.character2.physicsBody!.dynamic = true
self.character3.physicsBody!.dynamic = true
self.addChild(character)
self.addChild(character2)
self.addChild(character3)
var myJoint = SKPhysicsJointPin.jointWithBodyA(character2.physicsBody, bodyB: character.physicsBody, anchor: CGPoint(x: CGRectGetMaxX(self.character2.frame), y: CGRectGetMinY(self.character.frame)))
self.physicsWorld.addJoint(myJoint)
var myJoint2 = SKPhysicsJointPin.jointWithBodyA(character2.physicsBody, bodyB: character.physicsBody, anchor: CGPoint(x: CGRectGetMaxX(self.character2.frame), y: CGRectGetMaxY(self.character.frame)))
//self.physicsWorld.addJoint(myJoint2)
}
I'm creating a series of moving pipes in my scene. But it always crashes after ~30 pipes are generated. Is it because of too many nodes in the scene and no memories for new ones? The code is like this:
import SpriteKit
class GameScene: SKScene {
var mainPipe: SKSpriteNode = SKSpriteNode()
var space:Float = 1000
var pipeCount:Int = 0
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.blackColor()
self.size.width = 640
self.size.height = 1136
}
func randomOffset() -> Float{
var rNum:Float = Float(arc4random()%181) // 0-180
return rNum
}
var durations: CFloat = 5.0
var colorPipes:UIColor = UIColor.grayColor()
func spawnPipeRow(offs:Float){
self.pipeCount = self.pipeCount + 1
println("\(self.pipeCount)")
//offs is the random number
//let offset = offs + (space/2) - 105
let offset = offs + Float(self.size.height/100) - 180
// mainPipe = SKSpriteNode(color:colorPipes, size:CGSize(width: view.bounds.size.width/3, height:700))
mainPipe = SKSpriteNode(color:colorPipes, size:CGSize(width: self.size.width/5, height:self.size.height/1.5))
let pipeBottom = (mainPipe as SKSpriteNode).copy() as SKSpriteNode
let pipeTop = (mainPipe as SKSpriteNode).copy() as SKSpriteNode
let xx = self.size.width * 2.0
self.setPositionRelativeBot(pipeBottom, x:Float(xx), y: offset )
self.setPositionRelativeTop(pipeTop, x:Float(xx), y: offset + space)
pipeBottom.physicsBody = SKPhysicsBody(rectangleOfSize: pipeBottom.size)
pipeTop.physicsBody = SKPhysicsBody(rectangleOfSize: pipeTop.size)
pipeBottom.physicsBody?.dynamic = false
pipeTop.physicsBody?.dynamic = false
//pipeTop.physicsBody?.contactTestBitMask = birdCategory
//pipeBottom.physicsBody?.contactTestBitMask = birdCategory
self.addChild(pipeBottom)
self.addChild(pipeTop)
var actionArray1:NSMutableArray = NSMutableArray()
actionArray1.addObject(SKAction.moveTo(CGPointMake(-1000, pipeBottom.size.height - 200), duration: NSTimeInterval(durations)))
var actionArray2:NSMutableArray = NSMutableArray()
actionArray2.addObject(SKAction.moveTo(CGPointMake(-1000, pipeTop.size.height - 200), duration: NSTimeInterval(durations)))
actionArray1.addObject(SKAction.removeFromParent())
actionArray2.addObject(SKAction.removeFromParent())
pipeBottom.runAction(SKAction.sequence(actionArray1))
pipeTop.runAction(SKAction.sequence(actionArray2))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
}
}
override func update(currentTime: CFTimeInterval) {
var timeSinceLastUpdate = currentTime - lastUpdateTimerInterval
lastUpdateTimerInterval = currentTime
if(timeSinceLastUpdate > 1){
timeSinceLastUpdate = 1/60
lastUpdateTimerInterval=currentTime
}
updateWithTimeSinceLastUpdate(timeSinceLastUpdate)
/* Called before each frame is rendered */
}
func setPositionRelativeBot(node:SKSpriteNode, x: Float, y: Float){
let xx = (Float(node.size.width)/2) + x
let yy = (Float(self.size.height)/2) - (Float(node.size.height)/2) + y
node.position.x = CGFloat(xx)
node.position.y = CGFloat(yy)
}
func setPositionRelativeTop(node:SKSpriteNode, x:Float, y:Float){
let xx = (Float(node.size.width)/2) + x
let yy = (Float(self.size.height)/2) + (Float(node.size.height)/2) + y
node.position.x = CGFloat(xx)
node.position.y = CGFloat(yy)
}
var lastUpdateTimerInterval:NSTimeInterval = NSTimeInterval()
var lastYieldTimeInterval:NSTimeInterval = NSTimeInterval()
var speedOfBird: CDouble = 1.8
func updateWithTimeSinceLastUpdate(timeSinceLastUpdate:CFTimeInterval){
lastYieldTimeInterval += timeSinceLastUpdate
if(lastYieldTimeInterval > speedOfBird ){
lastYieldTimeInterval=0
self.spawnPipeRow(self.randomOffset())
if speedOfBird > 0.8{
speedOfBird -= 0.1}
}
}
}
You should remove your sprites from the scene when you no longer need them. However your problem is probably not related to the memory occupied by your textures:
SpriteKit Programming Guide
An SKTexture object is created and attached to the sprite. This
texture object automatically loads the texture data whenever the
sprite node is in the scene, is visible, and is necessary for
rendering the scene. Later, if the sprite is removed from the scene or
is no longer visible, Sprite Kit can delete the texture data if it
needs that memory for other purposes. This automatic memory management
simplifies but does not eliminate the work you need to do to manage
art assets in your game.
The texture object itself is just a placeholder for the actual texture
data. The texture data is more resource intensive, so Sprite Kit loads
it into memory only when needed.
If you already have an SKTexture object, you can create new textures
that reference a portion of it. This approach is efficient because the
new texture objects reference the same texture data in memory.
Try to delete them with this code :
override func update(currentTime: CFTimeInterval) {
self.enumerateChildNodesWithName("nodeName") {
node, stop in
if (node is SKSpriteNode) {
let sprite = node as SKSpriteNode
// Check if the node is not in the scene
if (sprite.position.x < -sprite.size.width/2.0 || sprite.position.x > self.size.width+sprite.size.width/2.0
|| sprite.position.y < -sprite.size.height/2.0 || sprite.position.y > self.size.height+sprite.size.height/2.0) {
sprite.removeFromParent()
println("outside")
}
}
}
}
Don't forget to named your node :
node.name = "nodeName"
Hope your crash will stop