Move all objects in an array - IOS and Sprite Kit - ios

So here is what I am trying to achieve;
I am trying to create a tile map system. So when the player presses the up button for example, the player stays central and the map moves down to give the look that the player is walking up the world (its a top-down game).
I have created an array of SpriteNode's and a function that creates 64x64 tiles and adds each new object to the array. I am trying to figure out a way that I can apply a function to all of the tiles.
So for example, when the up button is pressed all the tiles start to move down.
I could do this manually:
SpriteNode *tile00;
SpriteNode *tile01;
SpriteNode *tile02;
...
and then change the position of each one manually when the player moves but this is going to be very tedious (especially considering that I plan to create rather large maps)
My Question: Can I apply functions to several SpriteNodes?

You should definitely create functions:
- (void) moveUp {
// Loop through array of nodes and move them down
}
- (void) moveDown {
// Loop through array of nodes and move them up
}
- (void) moveRight {
// Loop through array of nodes and move them left
}
- (void) moveLeft {
// Loop through array of nodes and move them right
}
You can loop through the nodes if you create an NSArray of nodes and do this:
for (SKSpriteNode *n in NodeArray){ // NodeArray is you NSArray
// Do something to n
}

If you have an NSArray of tile sprites, applying an action to all of them could be done like this
SKAction *moveAction = //move all tiles down, up, etc.;
for (SKSpriteNode *tile in self.tiles) /* an NSArray containing the tile sprites */
{
[tile runAction:moveAction];
}

Related

How to move spriteKit node up and down with a button

I want to move my sprite up and down using a button. Since you cant use UIButtons in sprite Kit i am using different SpriteKitNodes. The nodes will be arrow images. But i want to use the arrow images to move my original sprite but simply touching it. I thought that I would use SKAction but I'm stuck. Is it possible to move one sprite using another?
There may not be any provision for buttons in SpriteKit, but one can easily subclass SKSpriteNode to act as a button. I have created such a class which is available on GitHub here.
Using SKAction for movement based on direction buttons is not advisable. Instead, you need to use flags for direction buttons upon which you will move the node in the -update: method.
Maintain the flags as instance variables.
#implementation MyScene
{
BOOL upDirection;
BOOL downDirection;
}
Initialise them to FALSE in the -initWithSize: method.
This is how you should handle the flags in the -update method:
-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
if (upDirection)
{
myNode.position = CGPointMake(myNode.position.x, myNode.position.y + 5); //Increment value can be adjusted
}
if (downDirection)
{
myNode.position = CGPointMake(myNode.position.x, myNode.position.y - 5); //Decrement value can be adjusted
}
}

iOS - Adding ‘explosion’ effect (which breaks Sprite in to little pieces and then fall) to sprite in SpriteKit

I want to apply the same effect to a number of different sprites in my iOS game so am looking in to a general approach rather than an animation created in another program and using its images to create a UIImageView animation.
This effect is an ‘explosion’ type animation where my sprite’s image gets chopped in to different pieces, then, using the physics engine, those pieces get exploded out in different directions before falling down.
I’m new to SpriteKit, but am assuming I have to do this in Quartz? Then re-add the new images as sprites and apply the animation?
I don’t really know where to start, let alone how to continue with the rest of the steps.
Does anyone have any ideas?
Thanks.
1) Upon initialization of said exploding object, first we create a SKTextureAtlas which holds all of the images being animated. You must have the texture atlas resources available.
2) Load all of the relevant SKTextures into an array. This array should be private to the object to which the explosion is happening.
A safe way to do this:
- (void)loadContactImages {
NSMutableArray *frames = [NSMutableArray array];
SKTextureAtlas *explosionFrames = [SKTextureAtlas atlasNamed:#"explosionFrames"];
for(int i = 0; i < explosionFrames.textureNames.count; i++) {
NSString *textureName = [NSString stringWithFormat:#"explosionFrame_%d" , i];
SKTexture *texture = [explosionFrames textureNamed:textureName];
if(texture) {
[frames addObject:texture];
}
}
self.contactFrames = frames;
}
3) You will then define a method which will animate the explosion. Look into the SKAction header for more info. Plug in time per frame that makes sense to your explosion
- (SKAction *)runExplosion {
return [SKAction animateWithTextures:self.contactFrames timePerFrame:0.1];
}
4) As for the exploding pieces, you should preload them, so to limit the overhead of sending the pieces into random directions (with this we won't have the extra overhead of creating all those new sprites at the time of the explosion). Don't forget to give them physics bodies (SKPhysicsBody) and set the isAffectedByGravity property to YES!
- (SKAction *)explosionOfPieces {
return [SKAction runBlock:^ {
for(SKSpriteNode *piece in self.explodingPieces) {
piece.hidden = NO;
piece.position = self.position;
[self addChild:piece];
[piece.physicsBody applyImpulse:CGVectorMake(dx , dy)]; //specify the direction here
}
}];
}
5) Bringing it all together, expose a method to return a sequence of these actions. If you would like to have these two actions occur together, there is also a class method on SKAction called [SKAction group: (NSArray *)]
- (void)explosionSequence {
[self runAction: [SKAction sequence:#[ [self runExplosion] , [self explosionOfPieces]]]];
}
Note: Physics reactions occur in SKScene's. We expose this action so the scene can run it on your object within the scene. You will also want to conform to a protocol called SKPhysicsContactDelegate within your scene and call:
[myExplodingObject explosionSequence];
within the delegate method:
- (void)didBeginContact:(SKPhysicsContact *)contact
Within this SKPhysicsContact object lie two colliding bodies. Both of these bodies ("exploding object" and "explosion trigger") must have their SKPhysicsBody property initialized and their contactTestBitMask property set (so this object knows what it can collide with). I would also set the categoryBitMask property so we can directly introspect the bodies' type. We reference these colliding bodies like this:
- (void)didBeginContact:(SKPhysicsContact *)contact {
if(contact.bodyA.categoryBitMask == explodingObjectCategory && contact.bodyB.categoryBitMask == explodingTriggerCategory) {
[self.myExplodingObject explosionSequence];
}
}
Please look into Apple's docs for more info.

SpriteKit conveyor belt

I am trying to create a conveyor belt effect using SpriteKit like so
MY first reflex would be to create a conveyor belt image bigger than the screen and then move it repeatedly forever with actions. But this does not seem ok because it is dependent on the screen size.
Is there any better way to do this ?
Also obviously I want to put things (which would move independently) on the conveyor belt so the node is an SKNode with a the child sprite node that is moving.
Update : I would like the conveyor belt to move "visually"; so the lines move in a direction giving the impression of movement.
Apply physicsBody to all those sprites which you need to move on the conveyor belt and set the affectedByGravity property as NO.
In this example, I am assuming that the spriteNode representing your conveyor belt is called conveyor. Also, all the sprite nodes which need to be moved are have the string "moveable" as their name property.
Then, in your -update: method,
-(void)update:(CFTimeInterval)currentTime
{
[self enumerateChildNodesWithName:#"moveable" usingBlock:^(SKNode *node, BOOL *stop{
if ([node intersectsNode:conveyor])
{
[node.physicsBody applyForce:CGVectorMake(-1, 0)];
//edit the vector to get the right force. This will be too fast.
}
}];
}
After this, just add the desired sprites on the correct positions and you will see them moving by themselves.
For the animation, it would be better to use an array of textures which you can loop on the sprite.
Alternatively, you can add and remove a series of small sprites with a sectional image and move them like you do the sprites which are travelling on the conveyor.
#akashg has pointed out a solution for moving objects across the conveyor belt, I am giving my answer as how to make the conveyor belt look as if it is moving
One suggestion and my initial intuition was to place a larger rectangle than the screen on the scene and move this repeatedly. Upon reflecting I think this is not a nice solution because if we would want to place a conveyor belt on the middle, in a way we see both it ends this would not be possible without an extra clipping mask.
The ideal solution would be to tile the SKTexture on the SKSpriteNode and just offset this texture; but this does not seem to be possible with Sprite Kit (no tile mechanisms).
So basically what I'm doing is creating subtextures from a texture that is like so [tile][tile](2 times a repeatable tile) and I just show these subtextures one after the other to create an animation.
Here is the code :
- (SKSpriteNode *) newConveyor
{
SKTexture *conveyorTexture = [SKTexture textureWithImageNamed:#"testTextureDouble"];
SKTexture *halfConveyorTexture = [SKTexture textureWithRect:CGRectMake(0.5, 0.0, 0.5, 1.0) inTexture:conveyorTexture];
SKSpriteNode *conveyor = [SKSpriteNode spriteNodeWithTexture:halfConveyorTexture size:CGSizeMake(conveyorTexture.size.width/2, conveyorTexture.size.height)];
NSArray *textureArray = [self horizontalTextureArrayForTxture:conveyorTexture];
SKAction *moveAction = [SKAction animateWithTextures:textureArray timePerFrame:0.01 resize:NO restore:YES];
[conveyor runAction:[SKAction repeatActionForever:moveAction]];
return conveyor;
}
- (NSArray *) horizontalTextureArrayForTxture : (SKTexture *) texture
{
CGFloat deltaOnePixel = 1.0 / texture.size.width;
int countSubtextures = texture.size.width / 2;
NSMutableArray *textureArray = [[NSMutableArray alloc] initWithCapacity:countSubtextures];
CGFloat offset = 0;
for (int i = 0; i < countSubtextures; i++)
{
offset = i * deltaOnePixel;
SKTexture *subTexture = [SKTexture textureWithRect:CGRectMake(offset, 0.0, 0.5, 1.0) inTexture:texture];
[textureArray addObject:subTexture];
}
return [NSArray arrayWithArray:textureArray];
}
Now this is still not ideal because it is necessary to make an image with 2 tiles manually. We can also edit a SKTexture with a CIFilter transform that could potentially be used to create this texture with 2 tiles.
Apart from this I think this solution is better because it does not depend on the size of the screen and is memory efficient; but in order for it to be used on the whole screen I would have to create more SKSpriteNode objects that share the same moveAction that I have used, since tiling is not possible with Sprite Kit according to this source :
How do you set a texture to tile in Sprite Kit.
I will try to update the code to make it possible to tile by using multiple SKSpriteNode objects.

Cocos2d Sprite collision detection during action

I am unable to detect collision between two sprites, one moving in action.
I have a sprite of class "Enemy", moving across the screen via CCMoveTo, and another sprite of class "Hero", controlled by touch, both added onto a scene on class "MainGame"
The following indicates how the two sprites are added onto the scene and Enemy actioned:
MainGame.m
-(id) init{
if( (self=[super init]) ) {
_enemies = [[NSMutableArray alloc]init];
_enemyLink = [Enemy nodeWithTheGame:self];
_enemyLink.position = ccp(10, 10);
[self.enemies addObject:_enemyLink];
CCMoveTo *test = [CCMoveTo actionWithDuration:40 position:ccp(500, 250)];
[_enemyLink runAction:test];
_heroArray = [[NSMutableArray alloc]init];
_heroLink = [Hero nodeWithTheGame:self location:ccp(100,100)];
[_heroArray addObject:_heroLink];
[self scheduleUpdate];
}
}
-(void)update:(ccTime)delta{
if (CGRectIntersectsRect([self.enemyLink.enemySprite boundingBox], [self.heroLink.heroSprite boundingBox])) {
NSLog(#"rect intersects rect");
}
for (CCSprite *enemies in self.enemies) {
NSLog(#"enemy position: %f, %f",enemies.position.x,enemies.position.y);
}
}
I am unable to detect collision between these two sprites during and after the action. However if I move the Hero to the position on the scene (0,0) the log in my code will trigger, and the two will engage in attacks.
The for loop in the update indicates that the Enemy sprite position is constantly moving during the action. Hence why I am stumped as to why the collision is not being detected.
Have you checked that both enemy and hero positions are tested within the same coordinates space?
This is because boundingBox() is local, relative to its parent and if the nodes you compare do not have the same parent, the check will be invalid.
You can use convertToNodeSpace()/convertToWorldSpace() prior to checking bounding boxes.
Another answer that may be relevant to your case here: A sprite bounding box shows wrong position after moving the layer.

Cocos2D - Better to keep array of CGRects or array of CCSprites to track hit areas?

I am putting together a simple board game in cocos2d to get my feet wet.
To track user clicks I intend to listen for click on game squares, not the game pieces to simplify tracking game pieces. It would be 8x8 board.
Is it more efficient to:
A. Make an array of CGRects to test against and need to put the struct in an NSObject before adding to array. Seams simple but looks like a lot of work going into access the CGRects every time they are needed.
or
B. Make actual CCSprites and test against their bounding rectangle. Simple to code, but that there are an extra 64 unneeded visual objects on the screen, bloating memory use.
or even
C. Some other method, and I am fundamentally misunderstanding this tool.
I agree it seems unnecessary to create a sprite for each game square on your board if your board is totally static.
However CGRect is not an object type so it can not be added to an NSMutableArray, and I will also assume that at some point you will want to do other things with your game square, such as highlighting them and other stuff. What I suggest you do is that you create a class called GameSquare that inherits from CCNode and put them into an array:
// GameSquare.h
#interface GameSquare : CCNode {
//Add nice stuff here about gamesquares and implement in GameSquare.m
}
After that, you can create gamesquares as nodes:
// SomeLayer.h
#interface SomeLayer : CCLayer {
NSMutableArray *myGameSquares;
GameSquare *magicGameSquare;
}
#property (nonatomic, strong) GameSquare *magicGameSquare;
// SomeLayer.m
/* ... (somewhere in the layer setup after init of myGameSquares) ... */
GameSquare *g = [[GameSquare alloc] init];
g.position = CGPointMake(x,y); //replace x,y with your coordinates
g.size = CGSizeMake(w,h); //replace w,h with your sizes
[myGameSquares addObject:g];
self.magicGameSquare = [[GameSquare alloc] init];
magicGameSquare.position = CGPointMake(mX,mY); //replace with your coordinates
magicGameSquare.size = CGSizeMake(mW,mH); //replace with your sizes
After that, you can do hit test against the gamesquares like this (in your CCLayer subclass):
// SomeLayer.m
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [self convertTouchToNodeSpace: touch];
// Example of other objects that user might have pressed
if (CGRectContainsPoint([magicSquare getBounds], location)) {
// User pressed magic square!
[magicSquare doHitStuff];
} else {
for (int i=0; i<myGameSquares.count; i++) {
GameSquare *square = [myGameSquares objectAtIndex:i];
if (CGRectContainsPoint(square.boundingBox, location)) {
// This is the square user has pressed!
[square doHitStuff];
break;
}
}
}
return YES;
}
Yes, you will have to look through the list, but unless the player can press many squares in one touch, you can stop the search as soon as the right one is found, as demonstrated in the example.
(Assumed use of ARC)
PS. If you at some point need to add a any sprite for the GameSquare, simply add a CCSprite member in your GameSquare class and refer to that.

Resources