I am trying to loop my background image in SK, the problem is the background is long so I split the image up into tiles that I paste together with code. I want to loop this huge background but I don't know how, the regular methods of looping backgrounds haven't worked because my background is made up of multiple nodes.
This is my method I use to render the nodes together.
- (SKNode *) createBackgroundNode
{
// 1
// Create the node
SKNode *backgroundNode = [SKNode node];
// 2
// Go through images until the entire background is built
for (int nodeCount = 0; nodeCount < 20; nodeCount++) {
// 3
NSString *backgroundImageName = [NSString stringWithFormat:#"Background%02d", nodeCount+1];
SKSpriteNode *node = [SKSpriteNode spriteNodeWithImageNamed:backgroundImageName];
// 4
node.anchorPoint = CGPointMake(0.5f, 0.0f);
node.position = CGPointMake(160.0f, nodeCount*64.0f);
// 5
backgroundNode.name =#"background";
[backgroundNode addChild:node];
}
// 6
// Return the completed background node
return backgroundNode;
}
Then Inside my update method, I use this code to move all the tiles at once by moving the background layers position in my desired direction.
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop) {
SKSpriteNode *bg = (SKSpriteNode *) node;
bg.position = CGPointMake(0.0f, -((_player.position.y - 200.0f)/10));
}];
The problem is, I can't get the size of the background layer only the position (at least I think I can't) So I have no idea how I can loop all these tiles seamlessly ?
I preferably want to do it all in one class but any helpful suggestions would be greatly appreciated!
Related
I have a custom LineBall class as shown below:
#implementation LineBall
-(instancetype) init {
self = [super initWithImageNamed:LINE_BALL_IMAGE];
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width/2];
self.physicsBody.categoryBitMask = BBPhysicsCategoryLineBall;
self.name = #"lineBall";
self.physicsBody.friction = 0.0f;
self.physicsBody.restitution = 1.0f;
self.physicsBody.linearDamping = 0.0f;
self.physicsBody.allowsRotation = NO;
self.physicsBody.dynamic = YES;
return self;
}
Later on I add this to the GameScene and it works as expected. The problem is that now I want to draw lines wherever the LineBall travels. How can I do that?
Here's one way to draw a trail behind a moving ball.
In the didSimulatePhysics method:
Store the ball's positions over time in a mutable array. Insert at index = 0.
Remove the oldest (last) array element if the number of elements exceeds the max trail size
Create a mutable CGPath by looping over the elements of the array using CGPathAddLineToPoint to connect each point
Remove the old trail from the scene if it exist
Create the ball's trail by creating an SKShapeNode from the path using shapeNodeWithPath
Add the SKShapeNode to the scene
To keep it simple, what is the easiest way to make the default [ Menu ] in default HelloWorld Scene (for example) as it's own layer. Issue I'm having now is that the scene is completely black, with nothing showing up!
GameLayer node:
- (id)init
{
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
self.theMap = [CCTiledMap tiledMapWithFile:#"AftermathRpg.tmx"];
self.contentSize = theMap.contentSize;
self.metaLayer = [theMap layerNamed:#"Meta"];
metaLayer.visible = NO;
CCTiledMapObjectGroup *objects = [theMap objectGroupNamed:#"mainChar"];
NSMutableDictionary *startPoint = [objects objectNamed:#"startPosition"];
int x = [[startPoint valueForKey:#"x"] intValue];
int y = [[startPoint valueForKey:#"y"] intValue];
self.mainChar = [CCSprite spriteWithImageNamed:#"mainChar.png"];
mainChar.position = ccp(x,y);
[self addChild:mainChar];
[self addChild:theMap z:-1];
[self setCenterOfScreen: mainChar.position];
return self;
}
HudLayer node
-(id)init
{
CGSize winSize = [[CCDirector sharedDirector] viewSize];
CCButton *backButton = [CCButton buttonWithTitle:#"[ Menu ]" fontName:#"Verdana-Bold" fontSize:18.0f];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
[backButton setTarget:self selector:#selector(onBackClicked:)];
[self addChild:backButton];
return self;
}
Scene
+ (GameScene *)scene
{
return [[self alloc] init];
}
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
CGSize winSize = [CCDirector sharedDirector].viewSize;
self.gameLayer = [GameLayer node];
[self addChild:gameLayer z:-1];
//self.contentSize = self.gameLayer.contentSize;
hudLayer = [HudLayer node];
hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);
[self addChild:hudLayer z:1];
return self;
}
From the OP I take that you have two issues with one being that the HUD is not static (i.e. it is moving as your map moves which you don't want) and that it is not positioning at the top of the screen.
Looking at the position issue first, your position is set to normalized. Since the scene's content size has been made to be the size of your map, which I take is larger than your screen, then this is why it is showing up at the top right of the map and not the screen. To fix this don't do normalized positioning. If you want to be able to still express the position in the 0 to 1 range, use (remove the line that sets the position type to normalized also):
CGSize winSize = [[CCDirector sharedDirector] viewSize];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
If your map is 10,000 x 10,000 then using the normalized positioning like you are will set the button to (8,500, 9,500) rather than the top of the screen.
Looking at the static issue next, from the looks of it you have the Hello World scene that you are adding everything to right? It also looks like you are moving the Hello World scene with a call to:
[self setCenterOfScreen: player.position];
What you want to do instead is this, you first have a scene:
HelloWorldScene* scene;
And to this scene you are adding two "main" layers with one being your gameplay layers as children of a main gameplay layer and the other being your HUD layer, which for example could look something like:
GameplayLayer* gameLayer;
HudLayer* hudLayer;
[scene addChild:gameLayer];
[scene addChild:hudLayer];
When the player moves (or camera or whatever), what should be moving is the game's layer, not the root Hello World scene. Moving the root scene will move all of its children, which includes the hud. That is not what you want.
When I worked on, for example, the Goldfish Mysteries app (https://itunes.apple.com/us/app/finn-friends-mysteries/id740040227?mt=8) I had essentially layers for:
Story (comprised of multiple sub-layers like bg, characters, etc)
Text (highlighted text that plays along with the narration audio)
HUD (comprised of multiple sub-layers)
Whenever there is movement on the story level it occurs on the story layer. When the HUD appears then the text and story layers are paused recursively (i.e. them and all of their children) along with the narration audio if any is playing but the HUD layer remains untouched. Resuming consists of resuming story, text, and any playing narration. I don't remember off the top if dropping the hud moved down the story and text layers in this specific app since I don't have my iPad in front of me, but i have done apps in the past where dropping the hud shifted all other layers. In that situation and for a simple app it would be fine to move the scene since the scene would only be shifted enough to show the TOC (in this type of app for example). What you look to be doing is moving the entire scene with the player's movement, which is not what you really wanted to do by the looks of it.
Either way you want a clear separation between layers and operations that are meant to only happen on specific layers should be directed only towards those layers.
Hope this helped.
UPDATE (Edited):
Based on your new edited OP, you have a new problem that you've introduced. For the hud you shouldn't have to set the layer's position since inside the hud layer everything is already being laid out relative to the screen anyways. So what you should have instead is:
hudLayer = [HudLayer node];
[self addChild:hudLayer z:1];
The second issue is that you are not properly writing your init methods for the game and hud layer classes. The init should look like:
- (instanceType)init
{
self = [super init];
if (self)
{
// Do your init stuff...
}
return self;
}
You are never calling:
self = [super init];
I am building an airport simulation game using sprite kit. My layout for the game states intact before adding SKPhysics body and once SKPhysicsbody is set for nodes, my sprite nodes goes wary.
This is what I am adding to scene without SKPhysicsBody. Imagine a airport with many gates and flights standing next to the gates. That is what I am trying to achieve with below code.
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
allPlanes = [[NSMutableArray alloc]init];
// setting gravity to 0
[[self physicsWorld] setGravity:CGVectorMake(0, 0)];
[[self physicsWorld] setContactDelegate:self];
[self setBackgroundColor:[UIColor whiteColor]];
// Below method will provide runway and other static objects that can be seen in an airport
[self setupAirport];
/* This is where I am setting up by plane sprites.This method will provide a node which is added to the scene. As you can notice, the sprites are added at specific coordinates until they fill the screen. Imagine three or four gates with flights standing by those gates. That is what I am trying to achieve with below while loop */
int xval = 20;
while (xval < kScreenWidth)
{
[self setupFlightAtPoint:xval];
xval = xval + 60;
}
}
return self;
}
Now code for methods [self setupFlightAtPoint:xPos]
-(void) setupFlightAtPoint:(CGFloat ) xPos
{
// Below code will provide a static gate like object.gateNode is of type Gate class which is a subclass of SKNode
gateNode = [[Gate node] newGate];
[gateNode setPosition:CGPointMake(xPos, kScreenHeight * 0.37)];
[self addChild:gateNode];
// Below code provide plane node and positions it near the gate object.Plane is subclass of SKNode
Plane *plane = [[Plane alloc]init];
imageNode = [plane newPlane];
imageNode.planeIdentifier = xPos;
[imageNode setPosition:CGPointMake(gateNode.frame.origin.x + 12, gateNode.frame.origin.y+15)];
[allPlanes addObject:imageNode];
[self addChild:imageNode];
}
Plane object method
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
[self addChild:spriteNode];
return self;
}
Till now everything looks fine.Please see attached image called scene1 to see what I see with above code.
Now my problem begins here when I am trying to set physics body to my plane sprites. In my "newPlane" method, I am adding below code
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
[self addChild:spriteNode];
SKPhysicsBody *planePhysics = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
[spriteNode setPhysicsBody:planePhysics];
[[spriteNode physicsBody] setAffectedByGravity:NO];
return self;
}
After setting Physicsbodies, my scene looks like this
only one plane sprite is seen in my scene now and I am not able to figure out why?
Try initializing and assigning the physics body before adding the sprite as child:
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
spriteNode.position = CGPointMake(100, 200);
SKPhysicsBody *planePhysics = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
spriteNode.physicsBody = planePhysics;
spriteNode.physicsBody.affectedByGravity = NO;
[self addChild:spriteNode];
return self;
}
I also converted the code to use dot notation, I find this easier to type and read.
Hi I am creating an iphone game
in my game layer, I have two identical background images but I want them to go one after the other.
Once the game animal (eg a penguin) crosses the first background, I want the first background to go after the second one-- becoming a continuous background until the game is over.
I have tried everything--- for loops, while loops and such but nothing seems to work
Does anyone have an idea for how I could go about doing this?
Thank you for any help you can provide me.
this is all I have so far after many different tries
- (id) init
{
if((self = [super init]))
{
for ( int x = 0; x < 10000000; ++x)
{
CCSprite *bg = [CCSprite spriteWithFile:#"level1.png"];
[bg setPosition:ccp(160,240)];
ccTexParams params = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
[bg.texture setTexParameters:¶ms];
[self addChild:bg z:0];
CCSprite *bg2 = [CCSprite spriteWithFile:#"level1.png"];
[bg2 setPosition:ccp(320,480)];
ccTexParams param = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
[bg2.texture setTexParameters:¶m];
[self addChild:bg z:0];
}
If the backgrounds are the identical you don't actually need 2 images, you can just set the texture rect position of the one and it will continuously move. If you call moveBackground on a timer or in the update method it will continually scroll.
-(void)init
{
if((self=[super init]))
{
background = [CCSprite spriteWithFile:#"background.png"];
ccTexParams params = {GL_LINEAR,GL_LINEAR,GL_REPEAT,GL_REPEAT};
[background.texture setTexParameters:¶ms];
[self addChild:background z:0];
}
}
-(void)moveBackground
{
// Scroll background 5 pixels to the left
int speed = 5;
background.textureRect = CGRectMake(background.textureRect.origin.x-speed,
background.textureRect.origin.y,
background.textureRect.size.width,
background.textureRect.size.height);
}
everyone, I made a shooting game just like 1942(classic shooting game),but here's the question, FPS drops to 5~6 when the fighter shoot out a straight line of bullet,about 7~8 bullets in the screen,Actually, only 8 bullets of player will appear in games, when bullets fly out of the screen they would reset to the fighter's center and become invisible,all bullets are individual sprites, just like:
Sprites * bullet1 = [Sprite spriteWithFile:#"bullet.png"];
........
Sprites * bullet8 = [Sprite spriteWithFile:#"bullet.png"];
Is there any method can store a lots of bullets instead of defining them one by one??
Because enemies may fire too, I can imagine the FPS may get closer to zero.....
Can anyone help me??
you could do something like have a "bullet pool" in there, as you are using cocos, what I would do is to allocate lot's of bullets when I start the game. Check how many bullets do you need approximately, pre allocate those.
When an Entity in your game needs to fire a bullet, it just asks for a bullet to this pool, you give the properties to the "pre allocated" bullet, the bullet appears on the screen, and when it impacts/disappear you then return that bullet to your bullet pool.
if you need some code:
/*You pre-allocate your bullets.*/
for(int i = 0; i < MAX_BULLETS; i++)
{
Bullet *aBullet = [[Bullet alloc] init];
[bulletsArray addObject:aBullet];
[aBullet release];
}
//Then in game when you fire:
Bullet *aBullet = [PoolManager bulletWithSprite:myBulletSprite]; // Where myBulletSprite is PRE allocated and you don't allocate sprites in Game.
You can store the sprites in an array:
NSMutableArray * bulletsBuilder = [[NSArray alloc] init];
for(int i = 0; i < MAX_NUMBER_OF_SPRITES; ++i) {
[bullets addObject:[Sprite spriteWithFile:#"bullet.png"]];
}
NSArray * bullets = [NSArray arrayWithArray:bulletsBuilder];
[bulletsBuilder release];
And access them later using their identifier:
Sprite * spr = [bullets objectAtIndex:spriteIndex];
What's a problem? Use sprite batching! It's kind of special layer that allow you to speed up the rendering of the multiple sprites.
CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithFile:#"bullet.png"];
Unless you're not Texture Atlas you cannot use different sprites to render them.
Then load one sprite and add to the batch itself as many sprites as you need.
for(;i>10;){[batch addSprite:sprite];}
Add to self.
Core Animation layers are lightweight and should help your performance. You can load the image once and then load the bullets image into the contents of an array of CALayers. Something like this:
// bulletImage is an instance variable
bulletImage = [UIImage imageNamed:#"bullet.png"];
bulletLayers = [[NSMutableArray alloc] init];
for (i = 0; i < 9; ++i)
{
CALayer *bulletLayer = [CALayer layer];
[bulletLayer setBounds:[bulletImage bounds]];
[bulletLayer setPosition:gunBarrelOrigin];
[bulletLayer setContents:(id)[bulletImage CGImage]];
[bulletLayers addObject:bulletLayer];
}
// Use the array of layers.
// ...
You don't specify whether you are using views or layers in your Sprite class, but if you are loading the same image multiple times, you're definitely creating unnecessary overhead.