How to remove node from parent if touched more than once?
The best way to remove your collided nodes is using the method didFinishUpdate
, if you remove or launch a method to remove your node from didBeginContact
your game could crash searching a collided node that meanwhile is in the process of being removed..
class BadGuy: SKSpriteNode {
var badGuyBulletCollisionsCounter: Int = 0
init() {
let texture = SKTexture(imageNamed: "CircleBadGuy")
super.init(texture: texture, color: nil, size: texture.size())
...
// fill this part with your BadGuy code
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Declare a global var :
var nodesToRemove = [SKNode]()
In the didBeginContact
method:
func didBeginContact(contact: SKPhysicsContact) {
let A : SKPhysicsBody = contact.bodyA
let B : SKPhysicsBody = contact.bodyB
if (A.categoryBitMask == Numbering.Badguy) && (B.categoryBitMask == Numbering.Laser) || (A.categoryBitMask == Numbering.Laser) && (B.categoryBitMask == Numbering.Badguy)
{
badGuy = A.node as! BadGuy
badGuy.badGuyBulletCollisionsCounter += 1
runAction(BadGuyLostSound)
bulletsTouchedBadGuy(badGuy, Laser: B.node as! SKSpriteNode)
}
}
In the bulletsTouchedBadGuy
method :
func bulletsTouchedBadGuy(badGuy: BadGuy, laser: SKSpriteNode){
nodesToRemove.append(laser)
if badGuy.badGuyBulletCollisionsCounter == 2 {
nodesToRemove.append(badGuy)
}
}
Finally:
override func didFinishUpdate()
{
nodesToRemove.forEach(){$0.removeFromParent()}
nodesToRemove = [SKNode]()
}
How to remove a node when hit more than once
Here is a sample solution to your issue (this is a macOS project just replace mouseDown
with touchesBegan
if you want to convert).
Click the screen to watch the villains health deplete, and when reaches 0 the villain will die and remove from scene:
let category1 = UInt32(1)
let category2 = UInt32(2)
class Villain: SKSpriteNode {
var lives = 2
var hitThisFrame = false
init(color: SKColor, size: CGSize) {
super.init(texture: nil, color: color, size: size)
let pb = SKPhysicsBody(rectangleOf: self.size)
pb.categoryBitMask = category1
pb.contactTestBitMask = category2
self.physicsBody = pb
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Hero: SKSpriteNode {
init(color: SKColor, size: CGSize) {
super.init(texture: nil, color: color, size: size)
let pb = SKPhysicsBody(rectangleOf: self.size)
pb.categoryBitMask = category2
pb.contactTestBitMask = category1
self.physicsBody = pb
}
required init?(coder aDecoder: NSCoder) { fatalError() }
}
class GameScene: SKScene, SKPhysicsContactDelegate {
let villain = Villain(color: .blue, size: CGSize(width: 50, height: 50))
let hero = Hero (color: .green, size: CGSize(width: 50, height: 50))
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector.zero
hero.position.y -= 100
addChild(villain)
addChild(hero)
}
func didBegin(_ contact: SKPhysicsContact) {
let contactedBodies = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask
if contactedBodies == category1 + category2 {
// Find which one of our contacted nodes is a villain:
var vil: Villain
if let foundVil = contact.bodyA.node as? Villain {
vil = foundVil
} else if let foundVil = contact.bodyB.node as? Villain {
vil = foundVil
} else {
fatalError("one of the two nodes must be a villain!!")
}
if vil.hitThisFrame {
// Ignore a second contact if already hit this frame:
return
} else {
// Damage villain:
vil.lives -= 1
print(" vil lives: \(vil.lives)")
vil.hitThisFrame = true
if vil.lives == 0 {
// Kill villain:
print("villain is dead!!!")
vil.physicsBody = nil
vil.removeFromParent()
}
}
}
}
override func didSimulatePhysics() {
// Reset hero position (so as to not trigger another didBegin()
hero.position = CGPoint(x: 0, y: -100)
// Allow villain to be hit again next frame:
villain.hitThisFrame = false
}
override func mouseDown(with event: NSEvent) {
// Trigger didBegin():
hero.position = villain.position
}
}
Removing Node From Parent (Sprite Kit) Objective C
Here is an example about how you can create a sprite, move it until it ends up off-screen and remove it right after that:
#import "GameScene.h"
@interface GameScene()
@property (nonatomic, strong)SKSpriteNode *sprite;
@end
@implementation GameScene
-(void)didMoveToView:(SKView *)view{
self.sprite = [SKSpriteNode spriteNodeWithColor:[SKColor purpleColor] size:CGSizeMake(50.0f,50.0f)];
//Place a sprite on right edge of the screen (I assume that your view and a scene have same size)
self.sprite.position = CGPointMake(self.frame.size.width, self.frame.size.height / 2.0f);
SKAction *moveSprite = [SKAction moveTo:CGPointMake(-self.sprite.size.width, self.frame.size.height / 2.0f) duration:5.0f];
SKAction *removeSprite = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:@[moveSprite, removeSprite]];
[self addChild:self.sprite];
[self.sprite runAction:sequence withKey:@"moving"];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (self.sprite.parent) {
NSLog(@"Sprite has a parent");
}else{
NSLog(@"Sprite doesn't have a parent");
}
}
@end
Try this code with, and without [SKAction removeFromParent]
(see what is printed after sprite is off screen in both cases).
Remove multiple elements with same name using removeChild?
Here's a solution that removes the first level children with the specified name for the parent with the specified id. If you want to go deeper, you can recursively call it on the child elements you get inside (you'll have to add a parent
parameter as well).
function removeChildren (params){
var parentId = params.parentId;
var childName = params.childName;
var childNodes = document.getElementById(parentId).childNodes;
for(var i=childNodes.length-1;i >= 0;i--){
var childNode = childNodes[i];
if(childNode.name == 'foo'){
childNode.parentNode.removeChild(childNode);
}
}
}
And to call it:
removeChildren({parentId:'div1',childName:'foo'});
And a fiddle for testing:
Notes: You can only access the name element dependably in JavaScript when it supported on your element (e.g. NOT on DIVs!). See here for why.
UPDATE:
Here's a solution using className based on our conversation:
function removeChildren (params){
var parentId = params.parentId;
var childName = params.childName;
var childNodesToRemove = document.getElementById(parentId).getElementsByClassName('foo');
for(var i=childNodesToRemove.length-1;i >= 0;i--){
var childNode = childNodesToRemove[i];
childNode.parentNode.removeChild(childNode);
}
}
Remove all childNodes in 0(1) on DOM element
I really doubt children can be removed in O(1), even with node.innerHTML = ''
as the underlying implementation may very well be a O(N) operation.
What you should consider to improve performance is to minimize the number of DOM reflows.
- You could try replacing the element with a clone.
const list = document.querySelector('ul');const listClone = list.cloneNode(false);list.parentNode.replaceChild(listClone, list);
<ul> <li>First</li> <li>Last</li></ul>
SpriteKit detect if node is removed from parent
There is no such method. But you can create one.
Create a subclass of (for example) SKSpriteNode and override all "remove" methods (or just those you are using). Within that method send a message to whichever object needs to receive the removal event, or send a notification and register other objects to receive that notification. Don't forget to call the super implementation of the overridden method.
Remove cclayer once all actions finished on its children
Make your top level container a CCNodeRGBA and set in the init :
self.cascadeColorEnabled=YES;
self.cascadeOpacityEnabled=YES;
self.opacity=255;
When you run a CCFadeAction on that, the node will do all the forklifting of cascading down to children and grand-children. At the end of the fade action,
id fade = [CCFadeTo actionWithDuration:2.5 opacity:0];
id done = [CCCallBlock actionWithBlock:^{
[self removeFromParentAndCleanup:YES];
// plus whatever else you see fit
}];
id seq = [CCSequence actions:fade, done, nil];
[self runAction:seq];
ob cit : from memory, not compiled nor tested, YMMV
SKEmitterNode isn't removing itself from parent?
particleLifetime determines the average lifetime of a particle, in seconds. That doesn't affect on removal of SKEmitterNode from parent.
numOfParticlesToEmit which refers to Maximum field in Particles area of Particle Editor determines the number of particles that emitter should emit before stopping. That doesn't affect on removal of SKEmitterNode from parent too. Also note that you've set 0 in this field which will enable infinitely emitting.
So, if you want to remove node from parent when emitter is done with emitting, you can set the number of particles to emit (field called Maximum in Particles area inside editor) and run an SKAction sequence which will:
- start an emitter
- wait for some duration of time
- and remove the emitter from parent (at this point emitter should finish with emitting)
Here is an simple example to show you how to do this with SKAction sequence:
class GameScene: SKScene {
let emitter : SKEmitterNode = NSKeyedUnarchiver.unarchiveObjectWithFile(NSBundle.mainBundle().pathForResource("MyParticle", ofType: "sks")!) as SKEmitterNode
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.blackColor()
}
func addEmitter(position:CGPoint){
var emitterToAdd = emitter.copy() as SKEmitterNode
emitterToAdd.position = position
let addEmitterAction = SKAction.runBlock({self.addChild(emitterToAdd)})
var emitterDuration = CGFloat(emitter.numParticlesToEmit) * emitter.particleLifetime
let wait = SKAction.waitForDuration(NSTimeInterval(emitterDuration))
let remove = SKAction.runBlock({emitterToAdd.removeFromParent(); println("Emitter removed")})
let sequence = SKAction.sequence([addEmitterAction, wait, remove])
self.runAction(sequence)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch: AnyObject? = touches.anyObject()
let location = touch?.locationInNode(self)
self.addEmitter(location!)
}
}
And here is the result (note how node's count is changing after emitting is done) :
Hope this helps
EDIT:
For those interested in how to make similar effect like from the video above, try with something like this:
The point is to use Color Ramp, and to choose Add for a blend mode.
Here is the dropbox link to the .sks file : Effect.sks
Touch detection of SKSpriteNode with child nodes
You can do this inside the node subclass:
class PlanListItem:SKSpriteNode {
var isSelected: Bool = false
override init(texture size etc) {
//your init
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("PlanListItem touched")
isSelected = !isSelected
}
}
Increase Touchable Area of an SKSpriteNode
In you code you have two nodes:
- original
- invisible
Who is the parent, who is the child?
According to your question "If I touch the invisible
SKNode
, then both the invisible
and original
SKNodes disappear..however, for whatever reason, the original
SKNode
always appears infront of the invisible
SKNode
" seems that you want invisible
inside original
, in other words original
should be the parent and invisible
should be the child.
But your code says the exact opposite:
invisible.addChild(original)
self.addChild(invisible)
So , we make an example:
Suppose that your original
node is green, this how appear your elements follow your code: invisible
node (red) do an addChild
to the original
(green) , and then you add to self the invisible
. Invisible
is parent, original
is child.
Finally, if you touch the red rectangle to remove it, both red and childs (green) will be removed, if you touch the green rectangle to remove it, only the green rectangle disappear.
Hope you can help you to understand what happen to your code.
Related Topics
How to Get Mouse Location with Swiftui
How to Define Static Constant in a Generic Class in Swift
How to Unwrap Arbitrarily Deeply Nested Optionals in Swift
Toolbar Is Deleting My Back Button in the Navigationview
Ambigious Reference to Member Request() Issues with Alamofire After Migration to Swift 3
Xcodebuild -Exportarchive Wont Allow Me to Specify Filename
Swift Array to Array of Tuples
Swift: Reflecting Properties of Subclass of Nsmanagedobject
Why 'Self.Self' Compiles and Run in Swift
Fullscreen for Swift Playgrounds on iPad
Tvos Remote Notification Replacement
How to Implement a Generic Struct That Manages Key-Value Pairs for Userdefaults in Swift
How to Make Deinit Take Effect in Swift
Numeric Types Don't Automatically Bridge to Nsnumber in Pure Swift on Ubuntu Linux
How Many Way Are There to Do Crud Operation in Sqlite Swift
Mutually Recursive Generic Enums
Why Specializing a Generic Function Explicitly Is Not Allowed