How to remove a child SKSpritenode from SKNode? - ios

i will explain a part of my code. I have Spritenodes (images) who are moving down on the screen.
SKTexture* Squaretexture = [SKTexture textureWithImageNamed:#"squaregreen"];
SquareTexture.filteringMode = SKTextureFilteringNearest;
Square = [SKSpriteNode spriteNodeWithTexture:SquareTexture];
Square.name = #"square";
.
.
.
[_objects addChild:Square];
_objects is a SKNode and Square is a SKSpriteNode. Now there is my code: every one second there is one square, who came from "over the screen" and is moving to the bottom. (Also there are more then one squares on the screen).
Now I want this: When I touch a square it should be "deleted" or hidden, but only the one who i touch. With my code, when i touch all squares are deleted or nothing. I tried with removefromparent and removechild, but i couldn't solve it.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"Point in myView: (%f,%f)", location.x, location.y);
if ([node.name isEqualToString:#"Square"]) {
[Square removeFromParent];
[Square removeAllChildren];
}
}
Do you have a suggestion how can I do it?
Thanks for Answers.
Mehmet

You almost had it right. The trick is that you need to have a unique identifier for each object (sprite) that you create and then store those objects in an array for later use.
The code below creates 5 sprites and gives them unique names: Sprite-1, Sprite-2, etc...
Whenever a touch is registered, it extracts the touched node's name, searches the array for the matching object, removes the object from the view and lastly removes the object from the array.
Note that my sample code is based on landscape view.
#import "MyScene.h"
#implementation MyScene
{
NSMutableArray *spriteArray;
int nextObjectID;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
spriteArray = [[NSMutableArray alloc] init];
nextObjectID = 0;
// create 5 sprites
for (int i=0; i<5; i++)
{
SKSpriteNode *mySprite = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(30, 30)];
nextObjectID ++; // increase counter by 1
mySprite.name = [NSString stringWithFormat:#"Sprite-%i",nextObjectID]; // add unique name to new sprite
mySprite.position = CGPointMake(50+(i*70), 200);
[spriteArray addObject:mySprite];
[self addChild:mySprite];
}
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"touched node name: %#",node.name);
NSLog(#"objects in spriteArray: %lu",(unsigned long)[spriteArray count]);
NSMutableArray *discardedItems = [NSMutableArray array];
for(SKNode *object in spriteArray)
{
if([object.name isEqualToString:node.name])
{
[object removeFromParent];
[discardedItems addObject:object];
}
}
[spriteArray removeObjectsInArray:discardedItems];
NSLog(#"objects in spriteArray: %lu",(unsigned long)[spriteArray count]);
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
#end

Related

How to draw a line in SpriteKit efficiently

In my SpriteKit scene, user should be able to draw line with his/her finger. I have a working solution, if the line is long, FPS gets down to 4-6 and the line starts to get polygonal, as the image below:
To draw myline (SKShapeNode*), I collect points of touches movement in an NSMutableArray* noteLinePoints in this way
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
SKNode *node = [self nodeAtPoint:touchPoint];
if(noteWritingActive)
{
[noteLinePoints removeAllObjects];
touchPoint = [[touches anyObject] locationInNode:_background];
[noteLinePoints addObject:[NSValue valueWithCGPoint:touchPoint]];
myline = (SKShapeNode*)node;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(myline)
{
CGPoint touchPoint = [[touches anyObject] locationInNode:_background];
[noteLinePoints addObject:[NSValue valueWithCGPoint:touchPoint]];
[self drawCurrentNoteLine];
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if(myline)
{
myline.name = #"note";
myline = nil;
}
NSLog(#"touch ended");
}
and I draw the line in this way
- (CGPathRef)createPathOfCurrentNoteLine
{
CGMutablePathRef ref = CGPathCreateMutable();
for(int i = 0; i < [noteLinePoints count]; ++i)
{
CGPoint p = [noteLinePoints[i] CGPointValue];
if(i == 0)
{
CGPathMoveToPoint(ref, NULL, p.x, p.y);
}
else
{
CGPathAddLineToPoint(ref, NULL, p.x, p.y);
}
}
return ref;
}
- (void)drawCurrentNoteLine
{
if(myline)
{
SKNode* oldLine = [self childNodeWithName:#"line"];
if(oldLine)
[self removeChildrenInArray:[NSArray arrayWithObject:oldLine]];
myline = nil;
myline = [SKShapeNode node];
myline.name = #"line";
[myline setStrokeColor:[SKColor grayColor]];
CGPathRef path = [self createPathOfCurrentNoteLine];
myline.path = path;
CGPathRelease(path);
[_background addChild:myline];
}
}
How can I fix this problem? because all subsequent lines are all polygonal only, I think because the fps is very low and sampling rate of touches automatically get also very low...
Please note that for the test I used an iPad 3 (my app needs to be working from iPad 2 model with iOS7)
Do not constantly create new SKShapeNodes, there is a bug which is causing your slowdown. Instead, only use 1 SKShapeNode (Or create a bunch but reuse them), and append the path with new info (So there is no need to constantly add the myline to the background)
Alternative:
Use 1 community SKSkapeNode for rendering of the path, then convert the SKShapeNode to a texture with view.textureFromNode, then add an SKSpriteNode with this new texture instead of the shape node

SKNode Subclass Not Moving Children

I have a sprite that can't exist in my game without a pairing SKFieldNode so my solution was to create a subclass of SKSpriteNode and create a property for the SKFieldNode but it didn't work because the SKSpriteNode was acting weird (I don't remember exactly what happened). So my next approach was to change the subclass to SKNode and then I would make the SKSpriteNode and the SKFieldNode a property of this new SKNode. But then it turns out that touchesMoved will only move one of the properties (whichever is on top) which turns out to always be the SKSpriteNode.
What's the best approach to this problem, and how can I fix it so that I can have an SKFieldNode for every SKSpriteNode while still making sure that actions and methods still work properly.
Current code of SKNode subclass:
#interface Whirlpool : SKNode
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff;
#property (nonatomic, strong) SKFieldNode *gravityField;
#end
#import "Whirlpool.h"
#import "Categories.h"
#implementation Whirlpool
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff {
if (self = [super init]) {
// whirlpool sprite
SKSpriteNode *whirlpoolSprite = [[SKSpriteNode alloc] initWithImageNamed:#"whirlpool"];
whirlpoolSprite.size = CGSizeMake(100, 100);
whirlpoolSprite.position = pos;
//removing physicsBody and associated attributes for now so that the boat does not collide with the whirlpool
//whirlpoolSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:whirlpoolSprite.size.width / 2];
//whirlpoolSprite.physicsBody.dynamic = NO;
whirlpoolSprite.zPosition = 1;
whirlpoolSprite.name = #"whirlpool";
[whirlpoolSprite runAction:[SKAction repeatActionForever:[self sharedRotateAction]]];
// whirlpool gravity field
_gravityField = [SKFieldNode radialGravityField];
_gravityField.position = pos;
_gravityField.strength = strength;
_gravityField.falloff = falloff;
_gravityField.region = [[SKRegion alloc] initWithRadius:region];
_gravityField.physicsBody.categoryBitMask = gravityFieldCategory;
_gravityField.zPosition = 1;
[self addChild:whirlpoolSprite];
[self addChild:_gravityField];
}
return self;
}
- (SKAction *)sharedRotateAction {
static SKAction *rotateWhirlpool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
rotateWhirlpool = [SKAction rotateByAngle:-M_PI * 2 duration:4.0];
});
return rotateWhirlpool;
}
#end
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// if the finger touches the boat or the whirlpool, update its location
if ([node.name isEqualToString:#"boat"]) {
node.position = CGPointMake(location.x, node.position.y);
} else if ([node.name isEqualToString:#"whirlpool"]) {
node.position = location;
}
}
}
I believe your issues comes down to that fact that "whirlpool" is a child of your SKNode subclass. So when you are identifying that you are indeed touching a "whirlpool" you are moving it within its parent (the SKNode subclass) and the SKFieldNode and parent stay put. This little adjustment to your original code should work...if I understand the problem correctly.
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// if the finger touches the boat or the whirlpool, update its location
if ([node.name isEqualToString:#"boat"]) {
node.position = CGPointMake(location.x, node.position.y);
} else if ([node.name isEqualToString:#"whirlpool"]) {
//move the SKNode subclass the whirlpool is a child of
node.parent.position = location;
}
}
}
Hopefully that helps.
Yikes, the problem here is the way you are grabbing nodes, you may be grabbing the wrong nodes due to all the children. Instead take this approach:
We already know that you are subclassing your sprites, so in your subclasses, add the following code:
//Whirlpool
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
self.position = location;
}
}
Then:
//Boat
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
self.position.x = location.x;
}
}
Then for your Scene, do this:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
//At this point it should call the children on touch events
[super.touchesMoved: touches withEvent:event];
}

didBeginContact method not working as intended

I have two nodes and a boolean. Simple enough. When node A contacts Node B and the boolean is 0, nothing happens. However if the boolean is 1, Node A is removed through the didBeganContact method.
Extremely simple, however I have an annoying problem on when I want Node A removed.
Node B is a rectangle and node A is a square going in the middle of the rectangle, the boolean is called and turned into 1 when I tap and hold the Node B using the touchesBegan method. Now before Node A contacts Node B, I tap and hold Node B and when Node A contacts, its removed, but when Node A is already in the middle, and I tap Node B, nothing happens and I don't know why.
Rectangle Method
-(void)rectangle
{
SKSpriteNode *rectangle = [[SKSpriteNode alloc] init];
rectangle = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(75, 150)];
rectangle.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
rectangle.name = #"rect";
rectangle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rectangle.size];
rectangle.physicsBody.categoryBitMask = rectangleCategory;
rectangle.physicsBody.contactTestBitMask = fallingSquareCategory;
rectangle.physicsBody.collisionBitMask = 0;
[self addChild:rectangle];
}
touchesBeganMethod
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"rect"])
{
radBool = 1;
}
}
touchesEnded
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"rect"])
{
radBool = 0;
}
}
square Method
-(void)square
{
SKAction *move = [SKAction moveToY:CGRectGetMidY(self.frame) duration:1.75];
SKSpriteNode *fallingSquare = [[SKSpriteNode alloc] init];
fallingSquare = [SKSpriteNode spriteNodeWithColor:[UIColor yellowColor] size:CGSizeMake(75, 75)];
fallingSquare.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame));
fallingSquare.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fallingSquare.size];
fallingSquare.physicsBody.categoryBitMask = fallingSquareCategory;
fallingSquare.physicsBody.contactTestBitMask = rectangleCategory
fallingSquare.physicsBody.collisionBitMask = 0;
[self addChild:fallingSquare];
[fallingSquare runAction:move];
}
didBeginContact
static inline SKSpriteNode *nodeFromBody(SKPhysicsBody *body1, SKPhysicsBody *body2, uint32_t category) {
SKSpriteNode *node = nil;
if (body1.categoryBitMask & category) {
node = (SKSpriteNode *)body1.node;
}
else if (body2.categoryBitMask & category) {
node = (SKSpriteNode *)body2.node;
}
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
SKSpriteNode *R1 = nil;
SKSpriteNode *fallingS = nil;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
R1 = nodeFromBody(firstBody, secondBody, rectangleCategory);
fallingS = nodeFromBody(firstBody, secondBody, fallingSquareCategory);
if (R1 && fallingS && radBool == 1)
{
[fallingS removeFromParent];
}
}
I believe your issue is the "begin" part of didBeginContact. It only gets called the first time they contact and not every loop. Because the bool was not set to YES when they first contacted it will never be evaluated again.
I believe I ran into this issue once before and the solution was to create a new physical body when you touch it. This "should" trigger didBeginContact the next go around. You might also be able to change a property on the physical body, but if I recall correctly I didn't get that to work and had to init a new physical body.
For example try updating your touchesBegan with this
touchesBeganMethod
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"rect"])
{
radBool = 1;
node.physicsBody = nil;
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rectangle.size];
node.physicsBody.categoryBitMask = rectangleCategory;
node.physicsBody.contactTestBitMask = fallingSquareCategory;
node.physicsBody.collisionBitMask = 0;
}
}
Hope that works for you.

sprite kit: verify if touch is inside a node

I'm trying to figure out how to detect if I touch a node in sprite kit. I thought it was function like this in UIView:
[myView pointInside:point withEvent:nil];
But so far I can not have found an alternative to this. What I'm trying to accomplish is to know I have touch the sprite node in the screen.
I'll really appreciate if you can help me to I acomplish this
Here is what I have in my code. I'm adding animated node:
-(void)addMyNodeAnimated
{
NSMutableArray *myNodeArray = [NSMutableArray array];
NSArray *animatedFrames = [NSArray new];
SKTextureAtlas *AnimatedAtlas = [SKTextureAtlas atlasNamed:#"pc2"];
int numImages = (int)AnimatedAtlas.textureNames.count;
for (int i=1; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"%#-%d", #"pc2", i];
SKTexture *temp = [AnimatedAtlas textureNamed:textureName];
[myNodeArray addObject:temp];
}
animatedFrames = myNodeArray;
SKTexture *temp = animatedFrames[0];
SKSpriteNode *animationNode = [SKSpriteNode spriteNodeWithTexture:temp];
animationNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
animationNode.userInteractionEnabled = YES;
animationNode.name = #"AnimationNode";
[self addChild:animationNode];
[animationNode runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:animatedFrames
timePerFrame:0.1f
resize:NO
restore:YES]] withKey:#"AnimationRuning"];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint _touchLocation = [[touches anyObject] locationInNode:self];
SKNode *node = [self nodeAtPoint:_touchLocation];
if (node != nil)
{
NSLog(#"node name %#", node.name);
}
}
When I touch the node in the screen the node returns null.
Any of know why is this?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"yourSpriteName"] && [node.name isEqualToString:#"yourEffectName"]) {
//Whatever you want.
}
}

Increase region where SKNode can be pressed

I'm wondering if there's an easy way that I could take an SKNode and increase the region in which it is pressed.
For example, I am currently checking if a node is clicked like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:positionInScene];
if ([node.name isEqualToString:TARGET_NAME]) {
// do whatever
}
}
}
If the node drawn on the screen is something like 40 pixels by 40 pixels, is there a way that if a user clicks within 10 pixels of the node, it would render as being clicked?
Thanks!
You could add an invisible sprite node as a child to the visible node. Have the child node's size be larger than the visible node's.
For example, on OSX this would work in a scene:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKSpriteNode *visibleNode = [[SKSpriteNode alloc] initWithColor:[NSColor yellowColor] size:CGSizeMake(100, 100)];
visibleNode.name = #"visible node";
visibleNode.position = CGPointMake(320, 240);
SKSpriteNode *clickableNode = [[SKSpriteNode alloc] init];
clickableNode.size = CGSizeMake(200, 200);
clickableNode.name = #"clickable node";
[visibleNode addChild:clickableNode];
[self addChild:visibleNode];
}
return self;
}
-(void)mouseDown:(NSEvent *)theEvent
{
CGPoint positionInScene = [theEvent locationInNode:self];
SKNode *node = [self nodeAtPoint:positionInScene];
NSLog(#"Clicked node: %#", node.name);
}
The clickable node extends 50px outwards from the edges of the visible node. Clicking within this will output "Clicked node: clickable node".
The node named "visible node" will never be returned by the call to [self nodeAtPoint:positionInScene], because the clickable node overlays it.
The same principle applies on iOS.

Resources