I'm trying to pool my particle emitter nodes. I re-use them when they are needed by removing them from their old parent node and adding them as a child of a SKSpriteNode at a new location. I leave the emitter node position set to 0,0 so the emitter should appear in the center of its new parent sprite node.
The emitters display correctly the first time they are added as a child to a sprite node, but simply do not show up on subsequent attempts. This all worked great in iOS8 and is only broken in iOS9 (seems like lots of particle emitter bugs in iOS9?)
Here's a basic example of my code when I need to place the particle effect:
if emitter.parent != nil {
emitter.removeFromParent()
}
newLocationSpriteNode.addChild(emitter)
emitter.resetSimulation()
This worked perfectly in iOS8 - I could re-use my emitter nodes at new locations. In iOS9 the nodes only appear the first time this code runs and never show up again after. Do you have any insight into how I might work around this issue? Thanks!
I experienced the exact same problem as you described. Emitters were not visible when re-attached for the second time. Everything worked fine on ios8 though. After several hours of experimenting with different settings I almost gave up.. However, I found a solution that works now. First of all I have a pool of SKEmitterNodes which I re-use during gameplay. This method grabs an emitter from the pool (array) and adds it to the gameplay-layer (SKNode):
func createExplosion(position: CGPoint) {
let emitter = _pool.borrowDirtEmitter()
emitter.resetSimulation() //Important
emitter.position = position
emitter.targetNode = self //Important
if emitter.parent == nil {
self.addChild(emitter)
}
}
So "self" here is the actual node that I attach the emitter to. When the node is offscreen I clean up emitters (and other objects):
if let dirtEmitter = childNode as? SKEmitterNode {
if dirtEmitter.parent != nil {
dirtEmitter.removeFromParent()
}
dirtEmitter.targetNode = nil //Important!
_pool.returnDirtEmitter(dirtEmitter)
}
I haven´t had the time to go into more detail yet, but the "Important" comments should give you some pointers. I will try testing out an approach using an action to remove from parent as well (after x seconds), but since I´m making a side scroller I can get away with cleaning up when emitters are offscreen for now.
Hope this helps..
Related
Hey guys I'm having a small issue with ARKit and Unity.
In the game that I'm making I'm reloading my scene when the player dies, however when the scene is reloaded all the GameObjects are still in the same position from the last session. I want all my objects to return to their starting positions when the scene is reloaded.
I saw that some variables regarding position and rotation are marked as "static" in the code. I tried changing them but I got a lot of compile errors.
Does anyone know a way around this?
Create this method and call it whenever you want to reset the scene:
using UnityEngine.XR.iOS;
.
.
.
public void ResetScene() {
ARKitWorldTrackingSessionConfiguration sessionConfig = new ARKitWorldTrackingSessionConfiguration ( UnityARAlignment.UnityARAlignmentGravity, UnityARPlaneDetection.Horizontal);
UnityARSessionNativeInterface.GetARSessionNativeInterface().RunWithConfigAndOptions(sessionConfig, UnityARSessionRunOption.ARSessionRunOptionRemoveExistingAnchors | UnityARSessionRunOption.ARSessionRunOptionResetTracking);
}
I found the solution!
Add these lines of code in your ARCameraManager script in the "!UnityEditor" part.
UnityARSessionRunOption options = new UnityARSessionRunOption();
options = UnityARSessionRunOption.ARSessionRunOptionRemoveExistingAnchors | UnityARSessionRunOption.ARSessionRunOptionResetTracking;
m_session.RunWithConfigAndOptions(config, options);
Now every time you reload your scene all AR elements (planes, anchors, camera tracking data) are reset.
AppImage I have a wall of 4 rectangles that are different colors, to pass through the wall the color of the ball has to match that of the rectangle on the wall. The ball will pass through the wall, and a new wall will appear. However, when I detect this collision I get multiple collision readings. I have tested this by printing dead or alive, and it prints both or more many times.
func didBegin(_ contact: SKPhysicsContact) {
if let nodeA = contact.bodyA.node as? SKShapeNode, let nodeB = contact.bodyB.node as? SKShapeNode {
if nodeA.fillColor != nodeB.fillColor {
print("DEAD")
}
else {
print("Alive")
}
}
}
please help!!!
Yep - this happens. The way to handle it (you can't get sprite-kit to NOT call didBegin multiple times in some circumstances) is to make sure that your contact code accommodates this and that handling the contract multiple times does not cause a problem (such as adding to the score multiple times, removing multiple lives, trying to access a node or physicsBody that has been removed etc).
There is a discussion here: Sprite-Kit registering multiple collisions for single contact
Some things you can do include:
If you remove a node that is contacted, check for it being nil before
you remove it (for the duplicate contacts)
Add the node to a set and then remove all the nodes in the set in
didFinishUpdate
Add an 'inactive' flag' to the node's userData
Make the node a subclass of SKSpriteNode and add an inactive property
Etc etc.
Is there a way to pool/cache SKReferenceNodes in SpriteKit using Swift?
I am creating a game using xCodes visual level editor. I am creating different .sks files with the visual level editor that I am than calling in code when I need to. I am calling them in code because I am using them to create random levels or obstacles so I don't need all of them added to the scene at once.
At the moment I am doing it like this
I create a convince init method for SKReferenceNodes to init them with URLs. I am doing this because there is some sort of bug calling SKReferenceNodes by file name directly (https://forums.developer.apple.com/thread/44090).
Using such an extension makes makes the code a bit cleaner.
extension SKReferenceNode {
convenience init(roomName: String) {
let path: String
if let validPath = NSBundle.mainBundle().pathForResource(roomName, ofType: "sks") {
path = validPath
} else {
path = NSBundle.mainBundle().pathForResource("RoomTemplate", ofType: "sks")! // use empty roomTemplate as backup so force unwrap
}
self.init(URL: NSURL(fileURLWithPath: path))
}
}
and than in my scenes I can create them and add them like so (about every 10 seconds)
let room = SKReferenceNode(roomName: "Room1") // will cause lag without GCD
room.position = ...
addChild(room)
This works ok but I am getting some lag/stutter when creating these. So I am using GCD to reduce this to basically no stutter. It works well but I am wondering if I can preload all .sks files first.
I tried using arrays to do this but I am getting crashes and it just doesn't seem to work (I also get a message already adding node that has a parent).
I was trying to preload them like so at app launch
let allRooms = [SKReferenceNode]()
for i in 0...3 {
let room = SKReferenceNode(roomName: "Room\(i)")
allRooms.append(room)
}
and than use the array when I need too. This doesn't work however and I am getting a crash when trying to use code like this
let room = allRooms[0]
room.position =
addChild(room) // causes error/crash -> node already has parent
Has anyone done something similar? Is there another way I can pool/cache those reference nodes?. Am i missing something here?
Speaking about the SKReferenceNode preload, I think the policy to be followed is to load your object, find what kind they are and use the official preloading methods available in Sprite-Kit:
SKTextureAtlas.preloadTextureAtlases(_:withCompletionHandler:)
SKTexture.preloadTextures(_:withCompletionHandler:)
To avoid this kind of error you should create separate instances of the nodes.
Try to doing this:
let room = allRooms[0]
room.position = ...
room.removeFromParent()
addChild(room)
I just figured it out, I was just being an idiot.
Using arrays like I wanted to is fine, the problem I had which caused the crash was the following.
When the game scene first loads I am adding 3 rooms but when testing with the arrays I kept adding the same room
let room = allRooms[0]
instead of using a randomiser.
This obviously meant I was adding the same instance multiple times, hence the crash.
With a randomiser, that doesn't repeat the same room, this does not happen.
Furthermore I make sure to remove the room from the scene when I no longer need it. I have a node in the rooms (roomRemover) which fires a method to remove/create a new room once it comes into contact with the player.
This would be the code in DidBeginContact.
guard let roomToRemove = secondBody?.node.parent?.parent?.parent?.parent else { return }
// secondBody is the roomRemover node which is a child of the SKReferenceNode.
// If I want to remove the whole SKReferenceNode I need to add .parent 4 times.
// Not exactly sure why 4 times but it works
for room in allRooms {
if room == roomToRemove {
room.removeFromParent()
}
}
loadRandomRoom()
Hope this helps someone trying to do the same.
I have a game where a contact with the enemy ends the game but when I use removeFromParent on the enemy node it only removes the most recent one. The enemies are spawned using a switch statement to determine what side they enter from and once a contact happens and the game ends the nodes still present mess up the game by causing more contacts. If the user doesn't press anything and the enemies finish their trajectory during the game over screen then the problem doesn't occur.
Could I kill all the nodes from the screen any way once the game ends? Could I suspend input for like 3 seconds during the end screen to let the actions finish? Do I need to enumerate the enemies as they spawn to kill them off one by one once the game ends?
I've been stuck on this for like 3 days and would love some help. I think I explained it fairly well but if I need to show any code please ask and I will. I'm very new to swift by the way.
In your scene class you can write
// For all children
self.removeAllChildren()
// Removing Specific Children
for child in self.children {
//Determine Details
If child.name == "bob" {
child.removeFromParent
}
}
You can use the SKNode's method removeAllChildren(). Call it in the enemies' parent node. If it's the scene, the call should be scene.removeAllChildren().
Enumerating is probably the best way. I would give all your enemies a name(s)
let enemyName = "Enemy" // make it a property to avoid typos
myEnemySprite.name = enemyName
and than if you want to delete a particular set of enemies you can enumerate the scene and remove them
enumerateChildNodesWithName(enemyName) { (node, _) in
node.removeFromParent()
}
Note: If your enemies are children of another node (e.g enemyNode) instead of the scene you will have to enumerate like this
enemyNode.enumerateChildNodesWithName(enemyName) { (node, _) in
node.removeFromParent()
}
Alternatively you could also put all your enemies into an array after you added them to the scene.
Create the array
var enemiesArray = [SKSpriteNode]()
and than for each enemy you add to the scene you add them to the array as well
addChild(yourEnemy1Sprite)
enemiesArray.append(yourEnemy1Sprite)
Now if you want to delete them you can do this
for enemy in enemiesArray {
enemy.removeFromParent()
// clear array as well if needed
}
Hope this helps
I have a 3D scene created in scene kit comprising an area surrounded by invisible walls and I want to lob physics objects into this area in such a way that they can't escape once they're in. I had a mind to achieve this in the following fashion:
Create a wall object
Create a 'solidifier' that fits neatly inside the walls
Set each object's isInScene variable to false
Lob them in the vague direction of the solidifier
Upon each update, if an object is touching the solidifier but is not touching the walls, I change its collision mask to include the walls and set isInScene to true so I don't have to check it again.
This often seems to work very well, but every so often (and sadly quite often) I get an EXC_BAD_ACCESS error out of nowhere. The offending method seems to be contactTestBetweenBody, which I am using to determine when an object is touching either the walls or solidifier at times when normal collision detection is turned off. This is necessary to prevent the objects simply bouncing off the outside of the wall object.
Here's a small snippet of code to illustrate. Incidentally, 'objects' is a structure that retains a reference to the node along with other useful details:
if let solid = solidifier?.physicsBody, let wall = walls?.physicsBody {
let world = scene.physicsWorld
for i in 0 ..< objects.count {
if objects[i].isInScene == false, let body = objects[i].node.physicsBody {
let contactSolidifier = world.contactTestBetweenBody(body, andBody: solid, options: nil)
if contactSolidifier != nil {
let contactWall = world.contactTestBetweenBody(body, andBody: wall, options: nil)
if contactWall == nil {
objects[i].isInScene = true
body.collisionBitMask = CollisionMask.allSolids.rawValue
}
}
}
}
}
I found a much, much better solution to the problem that I just plain didn't think of for some reason. Forget about this ridiculously convoluted means of keeping the objects in view. Instead, just make the area you want to retain the objects within and reverse its facet direction and normals.
What I didn't realise was that SceneKit uses backface culling on collision detection too, so if a physics object hits the OUTSIDE of an object that is inside out it will pass clean through.
I would still be interested to know the reason for the bad access error still though, as the contact test methods are useful and I may want to use them for other reasons in the future.