So in my game i have a function that spawns coins,they are given the name "coin", Now I have no way to reference the coins,example to kill them or move them.So what I'm trying to do is make a reference to be able to use in my code to just change its zPosition.
Everytime I run my app and have a function run that uses the coinRef [ex. to change the zPosition], the app crashes with the error:
'Thread 1 EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)'
Heres my code:
let coinRef: SKSpriteNode = self.childNodeWithName("coin")! as! SKSpriteNode
func hideCoins() {
coinRef.zPosition = -1
}
func showCoins() {
coinRef.zPosition = 101
}
func killCoins() {
coinRef.removeFromParent()
}
Looking at what you write
So in my game i have a function that spawns coins,they are given the name "coin"
it looks like there are multiple coins in your scene. As you can imagine a single name coin is not enough to univocally identify more then 1 coin :)
We'll need a way do identity multiple coins.
1. The Coin class
class Coin: SKSpriteNode {
private static var lastID: UInt = 0
let id:UInt
init() {
self.id = Coin.lastID++
let texture = SKTexture(imageNamed: "coin")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.name = "coin"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you can see Coin has an internal mechanism to assign a new id to each new instance. You can use this id to reference the coins in your scene.
let coin0 = Coin()
coin0.id // 0
let coin1 = Coin()
coin1.id // 1
let coin2 = Coin()
coin2.id // 2
2. Managing your coins
class GameScene: SKScene {
func retrieveCoin(id:UInt) -> Coin? {
return children.filter { ($0 as? Coin)?.id == id }.first as? Coin
}
func hideCoin(id:UInt) {
retrieveCoin(id)?.hidden = true
}
func showCoin(id:UInt) {
retrieveCoin(id)?.hidden = true
}
func deleteCoin(id:UInt) {
retrieveCoin(id)?.removeFromParent()
}
}
The retrieveCoin method returns (if does exist) a coin with the specified id. Otherwise nil is returned.
The hideCoin and showCoin do change the hidden property to change its visibility.
Finally deleteCoin remove from the scene the Coin with the specified id.
Try this. Initialise coinRef before the didMoveToView function, and then give coinRef its value in the didMoveToView function.
class scene : SKScene {
let coinRef: SKSpriteNode = SKSpriteNode()
override func didMoveToView(view: SKView) {
coinRef: SKSpriteNode = self.childNodeWithName("coin")! as! SKSpriteNode
}
func hideCoins() {
coinRef.zPosition = -1
}
func showCoins() {
coinRef.zPosition = 101
}
func killCoins() {
coinRef.removeFromParent()
}
}
Related
I am attempting to detect the intersection of two scnnodes in SceneKit. They are registered as GKEntities. I want to associate a component to both entities that will detect the intersection of their physics bodies using SCNPhysicsContactDelegate, but I don't want to do this in the view controller as I know it is not best practice and it would make it difficult to register that as a component. Any help would be appreciated.
Thanks for the assistance,
Montreaux
import Foundation
import SpriteKit
import GameplayKit
import SceneKit
class MeleeComponent: GKComponent, SCNPhysicsContactDelegate {
let damage: CGFloat
let destroySelf: Bool
let damageRate: CGFloat
var lastDamageTime: TimeInterval
let aoe: Bool
// let sound: SKAction
let entityManager: EntityManager
init(damage: CGFloat, destroySelf: Bool, damageRate: CGFloat, aoe: Bool, entityManager: EntityManager) {
self.damage = damage
self.destroySelf = destroySelf
self.damageRate = damageRate
self.aoe = aoe
// self.sound = sound
self.lastDamageTime = 0
self.entityManager = entityManager
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Get required components
guard let teamComponent = entity?.component(ofType: TeamComponent.self),
let spriteComponent = entity?.component(ofType: SpriteComponent.self) else {
return
}
// Loop through enemy entities
var aoeDamageCaused = false
let enemyEntities = entityManager.entitiesForTeam(teamComponent.team.oppositeTeam())
for enemyEntity in enemyEntities {
// Get required components
guard let enemySpriteComponent = enemyEntity.component(ofType: SpriteComponent.self),
let enemyHealthComponent = enemyEntity.component(ofType: HealthComponent.self) else {
continue
}
// Check for intersection
if (spriteComponent.node.frame.intersects(enemySpriteComponent.node.frame)) {
// Check damage rate
if (CGFloat(CACurrentMediaTime() - lastDamageTime) > damageRate) {
// Cause damage
// spriteComponent.node.parent?.run(sound)
if (aoe) {
aoeDamageCaused = true
} else {
lastDamageTime = CACurrentMediaTime()
}
// Subtract health
enemyHealthComponent.takeDamage(damage)
// Destroy self
if destroySelf {
entityManager.remove(entity!)
}
}
}
}
if (aoeDamageCaused) {
lastDamageTime = CACurrentMediaTime()
}
}
}
In SCNPhysicsWorld, There are a few APIs can help you.
such as
func contactTestBetween(_ bodyA: SCNPhysicsBody, _ bodyB: SCNPhysicsBody, options: [SCNPhysicsWorld.TestOption : Any]? = nil) -> [SCNPhysicsContact]
It's similar to the code in your example:
if (spriteComponent.node.frame.intersects(enemySpriteComponent.node.frame))
But it's in SceneKit and will give you more information about the contact.
I have a node I added in the scene. Also in the scene I give it a component to bounce. The component looks like this:
class BounceComponent: GKComponent, ComponentSetupProt {
var bounce: SKAction?
var node: SKSpriteNode?
#GKInspectable var diff: CGFloat = 0.2
#GKInspectable var startScale: CGFloat = 1
#GKInspectable var duration: TimeInterval = 0.5
override init() {
super.init()
comps.append(self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup() {
print("setup")
node = entity?.component(ofType: GKSKNodeComponent.self)!.node as? SKSpriteNode
setupBounce()
}
func setupBounce() {
...
}
func performBounce() {
print("performing")
node?.run(bounce!)
}
}//
In the didMove function on my scene file, it calls the components setup(). This works fine. I'm trying to call the function performBounce() when I click on the button...
if (play?.contains(pos))! {
print("test")
if let _ = play?.entity {
print("we have an entity")
}
play?.entity?.component(ofType: BounceComponent.self)?.performBounce()
}
When I click, the only thing it prints is "test" and the entity is nil. I was under the assumption that when you add a node to the editor, it also sets up the entity for that node, so I'm not sure why it is nil. Curious if anyone could shed some light on this?
Thanks for any help!
The entity on the SKSpriteNode is a weak reference, you need to make sure you retain your entities from your gameplaykit object
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "Main") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as? Main {
sceneNode.entities = scene.entities // add this
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.showsDrawCount = true
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
class Main: Base {
var entities = [GKEntity]() // add this
I've build a Scene1 in Xcode's Scene editor. And I've referenced another scene which has animation to this Scene1.
Now, I'm trying to cast-out an SKSpriteNode which is inside an SKReferenceNode.
The name of the SKSpriteNode that I'm trying to cast, on a scene which was references is: "sc01eyelid".
Any suggestions what I might do wrong here?
thank you in advance.
import SpriteKit
import GameplayKit
class Scene1: SKScene {
var misha: SKReferenceNode = SKReferenceNode()
var eyelidForScene1:SKSpriteNode = SKSpriteNode()
override func didMove(to view: SKView) {
castMishaForScene1()
castOutEyelid()
}
//Casting out misha
func castMishaForScene1() {
if let someSpriteNode:SKReferenceNode = self.childNode(withName: "mishaRefNode") as? SKReferenceNode {
misha = someSpriteNode
print("CASTED\(misha)")
}
else {
print("could not cast\(misha)")
}
}
//Casting out eyelid
func castOutEyelid() {
if let someSpriteNode:SKSpriteNode = misha.childNode(withName: "sc01eyelid") as? SKSpriteNode {
eyelidForScene1 = someSpriteNode
print("CASTED\(eyelidForScene1)")
}
else {
print("could not cast\(eyelidForScene1)")
}
}
}
In order to access any Node of the SKRefference one needs to put additional "//" in the withName statement:
if let someSpriteNode:SKSpriteNode = misha.childNode(withName: "//sc01eyelid") as? SKSpriteNode {}
So withName: "//sc01eyelid" would work to access the sc01eyelid Node.
More info here:
https://developer.apple.com/documentation/spritekit/sknode
I'm building a game in SpriteKit that includes a player to select a character out of 7 options. Scene 1 will include 7 images. Each image will represent one character. The player will select the character by pressing on the image selected and new scene 2 will be presented. I want the selected character image to appear on scene 2. I saved data of selected character in scene 1, but when scene 2 is presented, it always shows character 1 and not another. Please advise what is the correct line of code to access var from scene 1 and bring it over to scene 2.
import SpriteKit
class Scene1: SKScene {
var Player1 = SKSpriteNode()
var Player2 = SKSpriteNode()
var Player3 = SKSpriteNode()
var Player4 = SKSpriteNode()
var Player5 = SKSpriteNode()
var Player6 = SKSpriteNode()
var Player7 = SKSpriteNode()
var playerSelect1 = false
var playerSelect2 = false
var playerSelect3 = false
var playerSelect4 = false
var playerSelect5 = false
var playerSelect6 = false
var playerSelect7 = false
// in touches began..
if self.nodeAtPoint(location) == self.player1 {
playerSelect1 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect1")
// then i present scene2
} else if self.nodeAtPoint(location) == self.player2 {
playerSelect2 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect2")
//then i present scene2
} else if self.nodeAtPoint(location) == self.player3 {
playerSelect3 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect3")
//then i present scene2
} else if self.nodeAtPoint(location) == self.player4 {
playerSelect4 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect4")
//then i present scene2
// i didn't list all 7 characters to make code shorter
class Scene2: SKScene {
var playerSelected: Scene1()
// did move to view
var character1 = SKSpriteNode()
var character2 = SKSpriteNode()
var character3 = SKSpriteNode()
var character4 = SKSpriteNode()
//grab the selection value
let playerSelect1 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect1")
let playerSelect2 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect2")
let playerSelect3 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect3")
let playerSelect4 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect4")
if playerSelect1 == true {
self.addChild(character1)
} else if playerSelect2 == true {
self.addChild(character2)
} else if playerSelect3 == true {
self.addChild(character3)
} else if playerSelect4 == true {
self.addChild(character4)
}
}
About your question there are many ways you can follow to achieve what you want. Now I personally only use NSUserDefaults when I need to communicate with for example to a today extension. You might also want to save your data files at a later time so I thought to this:
class Settings: NSObject { // NSCoding in case you want to save your data
var playerGender: [Bool]! = [Bool]()
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(playerGender, forKey: "playerGender")
}
init(coder aDecoder: NSCoder!) {
playerGender = aDecoder.decodeObjectForKey("playerGender") as! [Bool]
}
override init() {
super.init()
self.playerGender = Array(count:7, repeatedValue:false)
}
}
class GameManager: NSObject {
static let sharedInstance = GameManager()
var settings : Settings! = Settings()
}
class Scene1: SKScene {
let gameManager = GameManager.sharedInstance
var player : [SKSpriteNode]?
override func didMoveToView(view: SKView) {
// in touches began..
if self.nodeAtPoint(location) == self.player1 {
gameManager.settings.playerGender[0] = true
}
}
}
class Scene2: SKScene {
let gameManager = GameManager.sharedInstance
var character : [SKSpriteNode]?
override func didMoveToView(view: SKView) {
//grab the selection value
let playerSelect1 = gameManager.settings.playerGender[0] //it's true
}
}
Explaination:
Settings is a class where you can write your game settings, and in future , you can also save the property values to a file.
GameManager is a shared instance, you can call it where do you want.
So let's consider the first time the user selects player 1. You store the selected player as a Bool in NSUserDefaults. This value will persist in local storage until you change it, even after the app is closed. So the next time the user selects player 2, you set the Bool for playerSelect2. The problem is, that in Scene2 you start your if statement checking if playerSelect1 == true, which it is because you set it to true once and never changed it back. With your current method, you would have to set the desired playerSelect to true, but also reset all of the others to false. I would recommend changing your strategy to store a String corresponding to the selected player in NSUserDefaults. Something like this should get you started.
When you want to set the selected player:
if self.nodeAtPoint(location) == self.player1 {
NSUserDefaults.standardUserDefaults().setObject("playerSelect1", forKey: "selectedPlayer")
else if self.nodeAtPoint(location) == self.player2 {
NSUserDefaults.standardUserDefaults().setObject("playerSelect2", forKey: "selectedPlayer")
else if ...
When you want to determine which player was selected in Scene2:
//grab the value from local storage
let selectedPlayer = NSUserDefaults.standardUserDefaults().objectForKey("selectedPlayer") as! String
if selectedPlayer == "playerSelect1" {
//handle player 1 case
}
else if selectedPlayer == "playerSelect2" {
//handle player 2 case
}
I am able to initialize this class, and see my particle effect, but the subclass doesn't call init() or deinit. Why?
class Particles: SKEmitterNode {
var test: Int?
override init() {
test = 1
super.init()
println("created particle emitter")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
println("destroyed particle emitter")
}
}
This is called from GameScene()
let particle = Particles(fileNamed:"test")
and I tried this to:
var particle: Particles?
viewDidLoad:
particle = Particles(fileNamed:"test")
Subclasses can only call superclass designated initialiser so cannot call super.init(fileNamed:). A workaround is not to make an initialiser at all but make a class method and perform setup code in there.
I copied the code apple provides to unarchive an sks scene and changed it to work with your emitter. The method finds the particle file if it exists and unarchives it as a Particles object:
class Particles: SKEmitterNode {
var test: Int?
class func fromFile(file : String) -> Particles? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var data = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: data)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKEmitterNode")
let p = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as Particles
archiver.finishDecoding()
//Perform any setup here
p.test=11
return p
}
return nil
}
}
and use it like so:
let p=Particles.fromFile("MyParticle")!
p.test=0
p.particleSpeed=10
addChild(p)