How can i remove a SKSpriteNode from parent, when the background is the same color as the sprite? - ios

Im making a game where the colour of a square will change every second and the background will also change colour every second, the user has to tap the square when it is the same colour as the background and the score will increase. But i cant work out how to do this.
This is the code i have so far:
#import "MyScene.h"
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
[self performSelector:#selector(backgrounds) withObject:nil ];
[self performSelector:#selector(createSquare) withObject:nil afterDelay:0];
[self performSelector:#selector(createPSquare) withObject:nil afterDelay:0];
}
return self;
}
-(void) backgrounds {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"blueOutline"];
background.name = #"blueOutline";
background.size = CGSizeMake(320, 480);
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:background];
//meathod sequence at interval
}
-(void) createSquare {
SKSpriteNode *blueSprite = [SKSpriteNode spriteNodeWithImageNamed:#"blue"];
blueSprite.name = #"blueSprite";
blueSprite.size = CGSizeMake(50, 50);
blueSprite.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:blueSprite];
//meathod sequence at interval
}
-(void) createPSquare {
SKSpriteNode *pinkSprite = [SKSpriteNode spriteNodeWithImageNamed:#"pink"];
pinkSprite.name = #"pinkSprite";
pinkSprite.size = CGSizeMake(50, 50);
pinkSprite.position = CGPointMake(CGRectGetMidX(self.frame)+10,CGRectGetMidY(self.frame));
[self addChild:pinkSprite];
//meathod sequence at interval
}
-(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];
if ([node.name isEqualToString:#"blueSprite"]) {
[node runAction:[SKAction removeFromParent]]; //Removes Sprite from parent
}
if ([node.name isEqualToString:#"pinkSprite"]) {
[node runAction:[SKAction removeFromParent]]; //Removes Sprite from parent
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end

I suggest you generalize the code that creates the squares and backgrounds to simplify adding more colors to your game. Here's an example of how to do that:
Define a type that identifies the type of sprite node to create
typedef NS_ENUM (NSInteger, SpriteType) {
SpriteTypeBackground,
SpriteTypeSquare
};
This method adds a square and a background to the scene each with a randomly selected color
- (void) addSquareAndBackground {
_background = [self createSpriteWithType:SpriteTypeBackground];
_square = [self createSpriteWithType:SpriteTypeSquare];
}
This removes the square and background from the scene
- (void) removeSquareAndBackground {
[_background removeFromParent];
[_square removeFromParent];
}
This creates either a square or a background sprite based on the specified type
-(SKSpriteNode *) createSpriteWithType:(SpriteType)type {
// Select a color randomly
NSString *colorName = [self randomColorName];
SKSpriteNode *sprite;
if (type == SpriteTypeBackground) {
NSString *name = [NSString stringWithFormat:#"%#Outline",colorName];
sprite = [SKSpriteNode spriteNodeWithImageNamed:name];
sprite.name = name;
sprite.size = CGSizeMake(320, 480);
}
else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:colorName];
sprite.name = [NSString stringWithFormat:#"%#Sprite",colorName];
sprite.size = CGSizeMake(50, 50);
}
sprite.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:sprite];
return sprite;
}
Randomly select a color name
// Set the total number of colors here
#define kNumberOfColors 2
- (NSString *) randomColorName {
NSString *colorName;
switch (arc4random_uniform(kNumberOfColors)) {
case 0:
colorName = #"blue";
break;
case 1:
colorName = #"pink";
break;
// Add more colors here
default:
break;
}
return colorName;
}
Add this to your touchesBegan method to test for a color match
if (node == _square) {
// Extract the color name from the node name
NSArray *squareNameParts = [node.name componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
// Extract the color name from the node name
NSArray *backgroundNameParts = [_background.name componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
// Compare if the colors match
if ([backgroundNameParts[0] isEqualToString: squareNameParts[0]]) {
NSLog(#"Score");
}
}
All that's left is to create an SKAction that calls addSquareAndBackground, waits for one second, and then calls removeSquareAndBackground. Lather, rinse, repeat!
EDIT: Add this above your #implementation MyScene statement:
#interface MyScene()
#property SKSpriteNode *background;
#property SKSpriteNode *square;
#end

Related

Why the texture in my SKSpriteNode gets removed when it's done animating?

I have a SKSpriteNode, I'm Trying to animate it in my touchesbegan and the animation works fine but when it's done animating, the default texture for my SKSpriteNode is gone. My R.atlas contains images named R1,R2..R7 ,What am I doing wrong? Thanks a lot.
#interface MyScene()
{
SKSpriteNode * rightTubeNode;
}
#property NSArray* rightTubeAnimationArray;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKTextureAtlas *rightTubeAtlas = [SKTextureAtlas atlasNamed:#"R"];
rightTubeTexture = [rightTubeAtlas textureNamed:#"R1"];
NSMutableArray *rightAnimFrames = [NSMutableArray array];
for (int i = 1; i <= rightTubeAtlas.textureNames.count; ++i){
NSString *texture =[NSString stringWithFormat:#"R%d",i];
[rightAnimFrames addObject:[rightTubeAtlas textureNamed:texture]];
}
self.rightTubeAnimationArray = rightAnimFrames;
rightTubeNode = [self createRightTubeNode];
rightTubeNode.position = CGPointMake(self.frame.size.width/2+rightTubeNode.frame.size.width/2,50);
rightTubeNode.zPosition = 0.1;
[self addChild:rightTubeNode];
}
return self;
}
- (SKSpriteNode *)createRightTubeNode
{
SKSpriteNode *rightTube = [SKSpriteNode spriteNodeWithTexture:rightTubeTexture];
rightTube = [SKSpriteNode spriteNodeWithTexture:rightTubeTexture];
rightTube.name = #"rightTubeNode";
return rightTube;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
CGPoint nodesLocation = CGPointMake(location.x,self.frame.size.height/2);
if (nodesLocation.x>self.frame.size.width/2) {
SKNode *archerNode = [self childNodeWithName:#"rightTubeNode"];
if (archerNode != nil){
SKAction *animate = [SKAction animateWithTextures:self.rightTubeAnimationArray
timePerFrame: 0.02];
[archerNode runAction:animate];
}
}
}
}
Re-adding the R.atlas (with checking the Create groups options box) without any further changes did the trick, Thanks to #Whirlwind

Removing specific nodes in SpriteKit

Need some help as a beginner: I've got different nodes: 4 squares (sprite1) and 1 counter (counterLabel, counts the nodes, which have been removed). I want to remove the 4 squares by touching them. With the code below the squares can be removed, but also the counter. Strangely enough, because I tried to address the square nodes (sprite1) exclusively. Is there any possibility to remove the square nodes (sprite 1) exclusively?
#implementation GameScene {
BOOL updateLabel;
SKLabelNode *counterLabel;
}
int x;
int y;
int counter;
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]){
self.backgroundColor = [SKColor /*colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0*/ whiteColor];
counter = 0;
updateLabel = false;
counterLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
counterLabel.name = #"myCounterLabel";
counterLabel.text = #"0";
counterLabel.fontSize = 48;
counterLabel.fontColor = [SKColor greenColor];
//counterLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
//counterLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeBottom;
counterLabel.position = CGPointMake(50,50); // change x,y to location you want
//counterLabel.zPosition = 900;
[self addChild: counterLabel];
}
return self;
}
-(void) didMoveToView:(SKView *)view {
SKTexture *texture1 = [SKTexture textureWithImageNamed:#"square"];
for (int i = 0; i < 4; i++) {
x = arc4random()%668;
y = arc4random()%924;
SKSpriteNode *sprite1 = [SKSpriteNode spriteNodeWithTexture:texture1];
sprite1.position = CGPointMake(x, y);
sprite1.name = #"square";
[self addChild:sprite1];
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint: [touch locationInNode: self]];
for (SKNode *sprite1 in nodes) {
[sprite1 removeFromParent];
counter ++;
updateLabel = true;
}
}
-(void)update:(CFTimeInterval)currentTime {
if(updateLabel == true){
counterLabel.text = [NSString stringWithFormat:#"%i",counter];
updateLabel = false;
}
}
#end
you should use the property name of SKSpriteNode
in this case you can do:
for (SKNode *sprite1 in nodes) {
if(![sprite1.name isEqualToString:#"myCounterLabel"]) {
[sprite1 removeFromParent];
}
counter ++;
updateLabel = true;
}
So if the SKNode name is different to the name of counterLabel, then removeFromParent.

Sprite Kit: create centred sprites in grid

I want to create a new sprite in a grid if the field is empty. I also want this new sprite to be centred in the empty field. Let's say the field is:
1 1 1
1 1 0
1 1 1
Whenever I click on one of the fields that is filled with a 1, the sprite disappears (this is what I want and this works fine).
Whenever I click on the 0, a new sprite is added with the following code:
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:positionInScene];
if(touchedNode.position.x == 0) { // x is 0 when I click an empty field, aka background
[self addThing:positionInScene];
}
And:
-(void)addThing:(CGPoint)newLocation{
SKSpriteNode *thing = [SKSpriteNode spriteNodeWithImageNamed:#"thing"];
thing.location = newLocation;
[self addChild:thing];
thing.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:thing.size.width/2];
thing.physicsBody.dynamic = NO;
thing.physicsBody.categoryBitMask = somethingCategory;
thing.physicsBody.contactTestBitMask = somethingElseCategory;
thing.physicsBody.collisionBitMask = 0;
}
However, this sprite is centred in the exact coordinates I click, rather than the centre point of the empty field. How can I make this happen?
The following creates an N x M grid with a sprite at each row and column. When the user touches one of the sprites, it rotates the sprite and then removes it from the scene. If a sprite is not found at the touch location, a sprite is added there.
#define kBoardMinX 25
#define kBoardMinY 25
#define kNumCols 3
#define kNumRows 3
#define kSpacing 40
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
[self addPiecesToScene];
}
return self;
}
// Convert row and col to the appropriate point (in scene coordinates) in the grid
- (CGPoint) pointFromRow:(NSInteger)row andCol:(NSInteger)col
{
return CGPointMake(kBoardMinX+col*kSpacing, kBoardMinY+(kNumRows-row-1)*kSpacing);
}
// If a sprite is found at (row, col) rotate it, else add a sprite there
- (void) rotatePieceAtRow:(NSInteger)row andCol:(NSInteger)col
{
SKSpriteNode *node = (SKSpriteNode *)[self nodeAtPoint:[self pointFromRow:row andCol:col]];
if (node && (SKNode *)node != self) {
SKAction *rotate = [SKAction rotateByAngle:M_PI*2 duration:2];
SKAction *remove = [SKAction removeFromParent];
SKAction *rotateThenRemove = [SKAction sequence:#[rotate,remove]];
[node runAction:rotateThenRemove];
}
else {
[self addPieceAtRow:row andCol:col];
}
}
- (void) togglePieceAtTouchLocation:(CGPoint)location
{
// Convert touch location to row and column
NSInteger col = (NSInteger)roundf((location.x - kBoardMinX) / kSpacing);
NSInteger row = kNumRows - (NSInteger)roundf((location.y - kBoardMinY) / kSpacing) - 1;
// Check if the touch was within the grid
if (col >= 0 && col < kNumCols && row >= 0 && row < kNumRows) {
[self rotatePieceAtRow:row andCol:col];
}
}
- (void) addPieceAtRow:(NSInteger)row andCol:(NSInteger)col
{
CGSize size = CGSizeMake(32, 32);
SKColor *color = [SKColor whiteColor];
SKSpriteNode *node = [SKSpriteNode spriteNodeWithColor:color size:size];
node.position = [self pointFromRow:row andCol:col];
[self addChild:node];
}
- (void) addPiecesToScene
{
for (int row=0;row<kNumRows;row++) {
for (int col=0;col<kNumCols;col++) {
[self addPieceAtRow:row andCol:col];
}
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
[self togglePieceAtTouchLocation:location];
}
}

How to make a background continuously scroll vertically with spritekit

I have my background set and it is scrolling horizontally, here is my code:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++)
{
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
bottomScrollerHeight = bg.size.height;
bg.position = CGPointMake(i * bg.size.width, 0);
bg.anchorPoint = CGPointZero;
bg.name = #"background";
[self addChild:bg];
}
}
and also this code:
- (void)moveBottomScroller
{
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled off the screen, if yes then put it at the end of the other node
if (bg.position.x <= -bg.size.width)
{
bg.position = CGPointMake(bg.position.x + bg.size.width*2,
bg.position.y);
}
[bg removeFromParent];
[self addChild:bg]; //Ordering is not possible. so this is a hack
}];
}
The second part makes the background scroll. Without it, the background is still.
Also, without the movebottomscroller, my sprite appears on top of the background. With the movebottomscroller, he appears behind the scrolling background. Is there any command to bring him to the front, above any other backgrounds?
Thank you!
Try the approach below, hope that works for you.
#interface GameScene()
#property (nonatomic) NSTimeInterval lastTimeSceneRefreshed;
#end
#implementation GameScene
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self buildBackground];
[self startScrolling];
}
return self;
}
// This method will add 3 background nodes
- (void)buildBackground {
float centerX = CGRectGetMidX(self.frame);
SKSpriteNode *firstBackgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
firstBackgroundNode.name = #"background";
firstBackgroundNode.position = CGPointMake(centerX,
firstBackgroundNode.size.height*firstBackgroundNode.anchorPoint.y);
[self addChild:firstBackgroundNode];
float previousYPosition = firstBackgroundNode.position.y;
for (int i = 0; i < 2; i++) {
SKSpriteNode *backgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
backgroundNode.position = CGPointMake(centerX,
previousYPosition + backgroundNode.frame.size.height);
previousYPosition = backgroundNode.position.y;
backgroundNode.name = #"background";
[self addChild:backgroundNode];
}
}
- (void)update:(CFTimeInterval)currentTime {
// Updating background nodes
// We don't want to update backgrounds each frame (60 times per second)
// Once per second is enough. This will reduce CPU usage
if (currentTime - self.lastTimeSceneRefreshed > 1) {
[self backgroundNodesRepositioning];
self.lastTimeSceneRefreshed = currentTime;
}
}
- (void)backgroundNodesRepositioning {
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode *backgroundNode = (SKSpriteNode *)node;
if (backgroundNode.position.y + backgroundNode.size.height < 0) {
// The node is out of screen, move it up
backgroundNode.position = CGPointMake(backgroundNode.position.x, backgroundNode.position.y + backgroundNode.size.height * 3);
}
}];
}
- (void)startScrolling {
SKAction *moveAction = [SKAction moveByX:0 y:-200 duration:1];
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
[node runAction:[SKAction repeatActionForever:moveAction] withKey:#"movement"];
}];
}
I don't fully understand your approach here, but I can answer your question so I'll just go ahead and do that - I hope I haven't misunderstood.
SKNode has a method insertChild:atIndex: that would allow you to put the background nodes at the back, or put the sprite at the front.
SKNode Class Reference

Adding the NSObject to the a SKScene

I recently making a racing game with using the SpriteKit. I create an NSObject class called "GameObject" to store all the properties, like physics. I also create another NSObject class called "GameWorld" to store all the game objects, like creating player objects and other objects with their location and the functions of the direction buttons. However, when I start to write a game scene class, I cannot add the world object to the scene, which means the car doesn't show up in the scene. Would anyone can help me about this question? The codes of my SKScene class is provided below,
#interface GamePlay : SKScene
#property (nonatomic) GameWorld *world;
-(void)update:(NSTimeInterval)currentTime;
-(id) initWithSize:(CGSize)s andWorld:(GameWorld *)w;
#end
#implementation GamePlay
- (id) initWithSize:(CGSize)s andWorld:(GameWorld *)w
{
self = [super initWithSize:s];
if (self)
{
_world = w;
}
return self;
}
-(void) didMoveToView:(SKView *)view
{
if (!self.contentCreated ) {
[self createSceneContents];
self.contentCreated = YES;
}
}
-(void) createSceneContents
{
// turn off gravity for the world
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.scaleMode = SKSceneScaleModeAspectFit;
//Create the background
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"road.png"];
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:background];
//Create the buttons for directions
SKSpriteNode *up = [SKSpriteNode spriteNodeWithImageNamed:#"Up.png"];
up.position = CGPointMake(290, 115);
up.name = #"upDirection";
[self addChild:up];
SKSpriteNode *down = [SKSpriteNode spriteNodeWithImageNamed:#"Down.png"];
down.position = CGPointMake(290, 40);
down.name = #"downDirection";
[self addChild:down];
SKSpriteNode *left = [SKSpriteNode spriteNodeWithImageNamed:#"Left.png"];
left.position = CGPointMake(30, 40);
left.name = #"leftDirection";
[self addChild:left];
SKSpriteNode *right = [SKSpriteNode spriteNodeWithImageNamed:#"Right.png"];
right.position = CGPointMake(CGRectGetMidX(self.frame), 40);
right.name = #"rightDirection";
[self addChild:right];
// add objects from GameWorld to this scene using the world's worldNode property
// Here is the place I confuse. the world doesn't show up
[self addChild:_world.worldNode];
}
//making buttons for the cars
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
//setting a node to keep the position of each direction buttons
SKNode *n = [self nodeAtPoint:positionInScene];
NSLog(#"Node n:%#", n);
//compare the location of each SKSpriteNode and the touch location
SKNode *up = [self childNodeWithName:#"upDirection"];
if (n == up) {
[self.world goForward];
}
SKNode *down =[self childNodeWithName:#"downDirection"];
if (n == down) {
[self.world goBackward];
}
SKNode *left = [self childNodeWithName:#"leftDirection"];
if (n == left) {
[self.world goLeft];
}
SKNode *right = [self childNodeWithName:#"rightDirection"];
if (n == right) {
[self.world goRight];
}
}
-(void)update:(NSTimeInterval)currentTime
{
//I don't know how to start yet. = =
}
#end
The GameWorld Class is provided below:
#interface GameWorld : NSObject
#property (nonatomic) SKNode *worldNode;
#property (nonatomic) NSArray *gameObjs;
-(id)init;
-(void) initializeWorld;
-(void) goForward;
-(void) goBackward;
-(void) goLeft;
-(void) goRight;
#end
#implementation GameWorld
-(id)init
{
if (self) {
self = [super init];
GameObject *player = [[GameObject alloc] initWithImageNamed:#"pCar.png" andPosition:CGPointMake(200, 85)];
GameObject *otherCar = [[GameObject alloc]initWithImageNamed:#"AICar.png" andPosition:CGPointMake(200, 100)];
_worldNode = [SKNode node];
// now create the objects, put them in an SKNode
// create the _worldNode with a size equal to the virtual world size
// then add the game objects to that node
_worldNode.scene.size = CGSizeMake(320, 480);
_gameObjs = #[player, otherCar];
}
return self;
}
- (void) initializeWorld
{
// add skSpriteNodes to the worldNode
SKSpriteNode *player = [[SKSpriteNode alloc] initWithImageNamed:#"pCar.png"];
[_worldNode addChild:player];
SKSpriteNode *otherCar = [[SKSpriteNode alloc] initWithImageNamed:#"AICar.png"];
[_worldNode addChild:otherCar];
}
-(void) goForward
{
GameObject *thePlayer = _gameObjs[1];
thePlayer.node.position = CGPointMake(thePlayer.node.position.x, thePlayer.node.position.y + 10);
}
-(void) goBackward
{
GameObject *thePlayer = _gameObjs[1];
thePlayer.node.position = CGPointMake(thePlayer.node.position.x, thePlayer.node.position.y - 10);
}
-(void) goLeft
{
GameObject *thePlayer = _gameObjs[1];
thePlayer.node.position = CGPointMake(thePlayer.node.position.x - 10, thePlayer.node.position.y);
}
-(void) goRight
{
GameObject *thePlayer = _gameObjs[1];
thePlayer.node.position = CGPointMake(thePlayer.node.position.x + 10, thePlayer.node.position.y);
}
#end
There is no need to use NSObject is most cases. Use SKNode and SKSpriteNode instead. By subclassing these classes you can add your custom properties to them.
I suggest you the following structure of the project:
-- GameScene (SKScene)
---- BackgroundLayer (SKNode)
-------- BackgroundNode (SKSpriteNode)
---- UiLayer (SKNode)
-------- your buttons here... (SKSpriteNode)
---- CarLayer (SKNode)
-------- PlayerNode (SKSpriteNode)
-------- OtherCarNode (SKSpriteNode)
In this case you don't have to store objects in manually created arrays, such as gameObjs. For example, all buttons can be accessed from this property: uiLayer.children.
I have created a template, which I use for my Sprite Kit games, take a look on it:
https://github.com/andrew8712/spritekit-game-template

Resources