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
Related
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
Can I mock SKPhysicsContact object to feed into -(void)didEndContact:(SKPhysicsContact *)contact method? Or is there any other technique, that can be leveraged here?
class PhysicsTestCase: XCTestCase {
var physics: GamePhysics!
...
func testCaseOfCollisionsHandling() {
let contact = SKPhysicsContact()
contact.bodyA = SKPhysicsBody(circleOfRadius: 10) // Error, 'bodyA' is get-only property
physics.didEnd(contact) // Physics conforms to `SKPhysicsContactDelegate`
}
...
}
...
// The class that is being tested
class GamePhysics: NSObject, SKPhysicsContactDelegate {
// MARK: - SKPhysicsContactDelegate
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else {
fatalError("No nodes in colliding bodies")
}
switch (nodeB, nodeA) {
case let (ball as LogicalBall, tile as LogicalTile):
// Performing some logic
...
}
}
func didEnd(_ contact: SKPhysicsContact) {
...
}
...
}
Although, subclassing proposed by Jon Reid in https://stackoverflow.com/a/44101485/482853 is very neat, I didn't managed to make it work in this particular case because of elusive nature of SKPhysicsContact class itself.
The way this problem can be solved is to use good old Objective C runtime:
func testBallsCollisionIsPassedToHandler() {
let ballAMock = LogicalBallMock()
let bodyA = SKPhysicsBody(circleOfRadius: 10)
bodyA.perform(Selector(("setRepresentedObject:")), with: ballAMock) // So the bodyA.node will return ballAMock
let ballBMock = LogicalBallMock()
let bodyB = SKPhysicsBody(circleOfRadius: 10)
bodyB.perform(Selector(("setRepresentedObject:")), with: ballBMock) // So the bodyB.node will return ballBMock
let contact = SKPhysicsContact()
contact.perform(Selector(("setBodyA:")), with: bodyA)
contact.perform(Selector(("setBodyB:")), with: bodyB)
physics.didEnd(contact)
// Assertions ...
}
When we can't change a type because we don't own the API, the solution is to use the legacy code technique Subclass and Override Method:
class TestablePhysicsContact: SKPhysicsContact {
var stubbedBodyA: SKPhysicsBody?
override var bodyA: SKPhysicsBody {
return stubbedBodyA!
}
}
To use this in your example test:
func testCaseOfCollisionsHandling() {
let contact = TestablePhysicsContact()
contact.stubbedBodyA = SKPhysicsBody(circleOfRadius: 10)
physics.didEnd(contact)
// assert something
}
For more on this technique, see https://qualitycoding.org/swift-partial-mock/
I have an asset loading and caching singleton defined as such:
class AssetLoader {
fileprivate var rootNodes = Dictionary<String, SCNNode>()
static let sharedInstance = AssetLoader()
fileprivate init() {
}
func rootNode(_ named: String) -> SCNNode {
if self.rootNodes[named] != nil {
return self.rootNodes[named]!.clone()
} else {
let scene = SCNScene(named: "art.scnassets/\(named).scn")
self.rootNodes[named] = scene!.rootNode
return self.rootNodes[named]!.clone()
}
}
}
I am using it to make my scene building faster. I'm creating assets from extensions as such:
extension CAAnimation {
class func animationWithScene(named: String) -> CAAnimation? {
unowned let rootNode = AssetLoader.sharedInstance.rootNode(named)
var animation: CAAnimation?
rootNode.enumerateChildNodes({ (child, stop) in
if child.animationKeys.count > 0 {
animation = child.animation(forKey: child.animationKeys.first!)
stop.initialize(to: true)
}
})
return animation
}
}
extension SCNNode {
class func nodeWithScene(named: String) -> SCNNode? {
unowned let rootNode = AssetLoader.sharedInstance.rootNode(named)
let node = SCNNode()
for child in rootNode.childNodes {
node.addChildNode(child)
}
node.eulerAngles = SCNVector3(x: Float(-M_PI_2), y: 0, z: 0)
node.scale = SCNVector3Make(kMeshScale, kMeshScale, kMeshScale)
return node
}
}
Instruments is saying I'm leaking memory like crazy on each calls to clone(). I tried using weak and unowned wherever I could without causing crashes and it doesn't change anything. Anyone has a clue? Is that a bug in SceneKit?
Thanks
If I understand correctly you keep your original nodes in the rootNodes Dictionary of your AssetLoader and return a clone of those in the rootNode func.
My architecture is similar and my issue was the following : when I would remove the cloned node from the scene tree the memory wouldn't get released. Is that your problem?
I fixed the issue by adding an "unload" func in my singleton to nullify the original nodes when removing the cloned nodes from the scene tree. That fixed my memory issues.
With your code that would look something like :
func unloadRootNode(_ named: String) {
rootNodes.removeValue(forKey: named)
}
I'm trying to give my main character a Custom Class so that I can define all his behaviors inside the defined class.
I already did something like this, by using protocols:
protocol CustomNodeEvents{
func didMoveToScene()
}
This protocol is in the GameScene file, before the class starts.
Then I call the function like this in the GameScene class:
override func didMove(to view: SKView) {
if let customnode = player as? CustomNodeEvents{
customnode.didMoveToScene()
}
}
Where the player is defined as:
player = self.childNode(withName: "//player") as! SKSpriteNode
also inside the didMove(to view: SKView) function.
Now I created another file (my custom class file) and I write:
import SpriteKit
class Custom: SKSpriteNode, CustomNodeEvents{
func didMoveToScene() {
print("It Worked")
}
}
I don't have any errors but it doesn't actually run the block of code inside my Custom class (in the GameScene.sks file I already attached the class to the player).
My question is how can I make it work?
And the second question is, is this the best way to define a Custom Class and "connect" it with other classes?
EDIT:
class GameScene: SKScene {
var player: SKSpriteNode!
override func didMove(to view: SKView) {
player = self.childNode(withName: "//player") as! SKSpriteNode
if let customnode = player as? CustomNodeEvents{
customnode.didMoveToScene()
}
Not sure where the problem is, but this code works for me:
import SpriteKit
protocol CustomNodeEvents
{
func didMoveToScene()
}
class Custom : SKSpriteNode,CustomNodeEvents
{
func didMoveToScene()
{
print("It Worked")
}
}
class GameScene:SKScene{
var player : SKSpriteNode!
override func didMove(to view: SKView) {
player = self.childNode(withName: "//player") as! SKSpriteNode
if let customnode = player as? CustomNodeEvents
{
customnode.didMoveToScene()
}
else
{
print("Error creating node")
}
}
}
I can only conclude that there is an issue in the sks file, and the player we are grabbing is not the player you are looking for, or the player is not of class Custom. (Note I placed the player sprite at the top most level of the scene)
Seems like you are casting player as a SpriteNode and then later trying to upcast it to a CustomNodeEvents protocol. Assuming your sprite custom class is set in the .sks file as you say, SpriteKit should be creating an instance of your custom class so you can just do:
if let customNode = self.childNode(withName: "//player") as! Custom {
customNode.didMoveToScene()
}
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()
}
}