How do I create a more efficient portrait gallery using Cocos2d? - cocos2d-iphone

I'm building an app that has gallery of portraits (like a museum) and my character(s) walk past them from left-to-right-to-left. My portrait (images) are all 2048x2048. I haven't reduced their size yet because I want to make sure they can be used for the iPad version. I know their large size is an issue because it crashes when I try to load all of them at once (and it takes a long time to launch even with only 10 images).
That being said, my real issue is trying to create an efficient method for adding/removing them (sprites) as needed. I came up with something that works but it is clearly not the best way to do this.
I am hoping someone can suggest a more efficient approach.
Here is my code. You can assume another method takes care of loading the images into a mutable array called framedSprites (except I can only load 10 at a time because of the size/crashing). The following method (checkPosition) is called every time the screen position changes (via a TouchMoved swipe). As I see it, I will have to create similar statements for each image/portrait in the array (very inefficient and time consuming)...
-(void)checkPosition {
CGSize winSize = [CCDirector sharedDirector].winSize;
for (CCSprite *sprite in framedSprites) {
if (sprite.tag == 2) {
if ((sprite.position.x > 2000.0f)&&(sprite.position.x < 2010.0f)) {
CCSprite *portrait = (CCSprite *)[_backgroundNode getChildByTag:0];
if (portrait.tag == 0) {
NSLog(#"Removing a Portrait Left 2 Places From This One");
[_backgroundNode removeChildByTag:0 cleanup:YES];
}
}
if ((sprite.position.x > 1980.0f)&&(sprite.position.x < 1990.0f)) {
CCSprite *portrait = (CCSprite *)[_backgroundNode getChildByTag:0];
if (portrait == nil) {
CCSprite * framedSprite = (CCSprite *)[framedSprites objectAtIndex:0];
NSLog(#"Adding a Portrait Left, 2 Places From This One");
framedSprite.position = ccp(600,winSize.height/2); //figuring these positions is also not efficient and time consuming
[_backgroundNode addChild:framedSprite z:0 parallaxRatio:ccp(0.4f,0.5f) positionOffset:framedSprite.position];
}
}
}
}
}

You should only need to have 3 images loaded at any one time to allow scrolling in either direction (assuming you're not also allowing movement in the y direction). The one on display, one either side as a buffer and then lazy load and replace as needed.
If you don't need to see the entire image at once you could also consider cutting it into tiles using a free tool like Tilen (http://itunes.apple.com/gb/app/tilen/id409199185?mt=12) and then loading the tiles as required.

Related

SDL tilemap rendering quite slow

Im using SDL to write a simulation that displays quite a big tilemap(around 240*240 tiles). Since im quite new to the SDL library I cant really tell if the pretty slow performance while rendering more than 50,000 tiles is actually normal. Every tile is visible at all times, being around 4*4px big. Currently its iterating every frame through a 2d array and rendering every single tile, which gives me about 40fps, too slow to actually put any game logic behind the system.
I tried to find some alternative systems, like only updating updated tiles but people always commented on how this is a bad practice and that the renderer is supposed to be cleaned every frame and so on.
Here a picture of the map
So I basically wanted to ask if there is any more performant system than rendering every single tile every frame.
Edit: So heres the simple rendering method im using
void World::DirtyBiomeDraw(Graphics *graphics) {
if(_biomeTexture == NULL) {
_biomeTexture = graphics->loadImage("assets/biome_sprites.png");
printf("Biome texture loaded.\n");
}
for(int i = 0; i < globals::WORLD_WIDTH; i++) {
for(int l = 0; l < globals::WORLD_HEIGHT; l++) {
SDL_Rect srect;
srect.h = globals::SPRITE_SIZE;
srect.w = globals::SPRITE_SIZE;
if(sites[l][i].biome > 0) {
srect.y = 0;
srect.x = (globals::SPRITE_SIZE * sites[l][i].biome) - globals::SPRITE_SIZE;
}
else {
srect.y = globals::SPRITE_SIZE;
srect.x = globals::SPRITE_SIZE * fabs(sites[l][i].biome);
}
SDL_Rect drect = {i * globals::SPRITE_SIZE * globals::SPRITE_SCALE, l * globals::SPRITE_SIZE * globals::SPRITE_SCALE,
globals::SPRITE_SIZE * globals::SPRITE_SCALE, globals::SPRITE_SIZE * globals::SPRITE_SCALE};
graphics->blitOnRenderer(_biomeTexture, &srect, &drect);
}
}
}
So in this context every tile is called "site", this is because they're also storing information like moisture, temperature and so on.
Every site got a biome assigned during the generation process, every biome is basically an ID, every land biome has an ID higher than 0 and every water id is 0 or lower.
This allows me to put every biome sprite ordered by ID into the "biome_sprites.png" image. All the land sprites are basically in the first row, while all the water tiles are in the second row. This way I dont have to manually assign a sprite to a biome and the method can do it itself by multiplying the tile size(basically the width) with the biome.
Heres the biome ID table from my SDD/GDD and the actual spritesheet.
The blitOnRenderer method from the graphics class basically just runs a SDL_RenderCopy blitting the texture onto the renderer.
void Graphics::blitOnRenderer(SDL_Texture *texture, SDL_Rect
*sourceRectangle, SDL_Rect *destinationRectangle) {
SDL_RenderCopy(this->_renderer, texture, sourceRectangle, destinationRectangle);
}
In the game loop every frame a RenderClear and RenderPresent gets called.
I really hope I explained it understandably, ask anything you want, im the one asking you guys for help so the least I can do is be cooperative :D
Poke the SDL2 devs for a multi-item version of SDL_RenderCopy() (similar to the existing SDL_RenderDrawLines()/SDL_RenderDrawPoints()/SDL_RenderDrawRects() functions) and/or batched SDL_Renderer backends.
Right now you're trying slam at least 240*240 = 57000 draw-calls down the GPU's throat; you can usually only count on 1000-4000 draw-calls in any given 16 milliseconds.
Alternatively switch to OpenGL & do the batching yourself.

Cocos2dx : Deleting kinematic bodies once they are off screen

I am trying to build a platform game. In my game, random platforms appear and move from right part of the screen to the left part .
Using :
b2Vec2 impulse = b2Vec2(-5, 0);
platformBody->SetLinearVelocity(impulse);
But when I see my stats, the number of call / countRef (First line of the Stats) is always increasing as the new platforms move from right to the left. I want to know how can I remove the existing platforms once they have moved passed the left screen (off screen). All my platforms are b2_kinematic bodies (Box2D).
EDIT ANSWER :
I managed to solve it through the following :
for(std::vector::size_type i = 0; i != m_platforms.size(); i++)
{
if (m_platforms[i]->GetPosition().x < 0.0f && m_platforms[i]->GetType() == b2_kinematicBody)
{
CCSprite *sprite = (CCSprite *) m_platforms[i]->GetUserData();
sprite->removeFromParentAndCleanup(true);
world->DestroyBody(m_platforms[i]);
}
}
You could check whether or not your bodies are out of screen on the main loop, and destroy them if so. Something like.-
if (platformBody->GetPosition().x < SCREEN_X_LIMIT) {
world->DestroyBody(platformBody);
// Also, remove attached sprite if exists
}

Cocos2d Kobold2d reorderChildren doesn't order CCSprites properly

I am having an issue in Kobold2d-Cocos2d regarding the layering of CCSprite objects.
All of my code works fine, except that a certain tile (a CCSprite), held in an array, is supposed to always be on top. The code has an array of references to CCSprites (Tile) and goes along all the tiles swapping them with each other. So element 0 and 1 switch (in the array and on screen using a moveTo), then the new element 1 and 2 switch, then the new element 2 and 3 switch etc...
The idea is the Tile at the start of the chain leapfrogs to the front of the line!
I always want that first tile to stay on top, but it isn't. It only does sometimes (when it's above the other Tile it's switching places with on the screen, implying it was added later)
Here's the CCScene code in question:
//reference first CCSprite in array -works
Tile *firstTile = [tileArray objectAtIndex:(int)[[[selTracker oldSelectionArray] objectAtIndex:0]element]];
//How much time should be spend leapfrogging each two tiles broken up over 1 second.
float intervalTime = .75/(float)[[selTracker oldSelectionArray] count];
//Will use this to count each tile set we leapfrog
float currentPass = 0;
//TODO: destroy this wrapper class that holds number, use NSNumber. Fine for now.
SimpCoord *lastCord;
//Make moving sprite higher than all others. DOESN'T WORK?
[self reorderChild:firstTile z:100];
//Loop through all the tiles that were selected
for (SimpCoord *coord in [selTracker oldSelectionArray])
{
if (lastCord)
{
//Queue each tile to leapfrog with our moving tile
if ([self respondsToSelector:#selector(switchTilesFuncWrapper:)])
{
NSArray *argArray = [NSArray arrayWithObjects:
[NSNumber numberWithInt:[lastCord element]],
[NSNumber numberWithInt:[coord element]],
[NSNumber numberWithFloat:(intervalTime)], nil];
[self performSelector:#selector(switchTilesFuncWrapper:) withObject:argArray afterDelay:((currentPass) * intervalTime)];
}
currentPass++;
}
lastCord = coord;
}
`
That calls the following code by the way that actually swaps the two Tiles (I excluded the function wrapper middleman I needed for multiple arguments):
(In case it makes it easier to understand, rather than use a 2d array to hold all my tiles I just only draw 10 per line, hence the tileCoordsByElement method - but that shouldn't be important)
//
// Switch two tiles on the screen and in the holder array
// onlySwitchArray pass in true to only swap tiles in organization array
// but provide no animation.
- (void) switchTiles:(NSNumber*)elePosVal secPos:(NSNumber*)elePos2Val timeToSwitch:(NSNumber*)time
{
float switchTime = [time floatValue];
int elePos = [elePosVal intValue];
int elePos2 = [elePos2Val intValue];
Tile *tmpTile = [tileArray objectAtIndex:elePos];
Tile *tmpTile2 = [tileArray objectAtIndex:elePos2];
//Move actual tiles on screen
//Move Tile is elePos (1) to elePos2
int curCol = 0;
int curRow = 0;
[self tileCoordsByElement:elePos2 x:&curRow y:&curCol];
CCSequence *tmpTile1_Seq = [CCSequence actions:
[CCMoveTo actionWithDuration:switchTime position:ccp((curRow-1)*tmpTile.width + tmpTile.width/2,
(curCol-1)*tmpTile.height + tmpTile.height/2)], nil];
[tmpTile runAction:[CCRepeat actionWithAction:tmpTile1_Seq times:1]];
//Move Tile that is elePos2 to elePos(1)
[self tileCoordsByElement:elePos x:&curRow y:&curCol];
CCSequence *tmpTile1_Seq2 = [CCSequence actions:
[CCMoveTo actionWithDuration:switchTime position:ccp((curRow-1)*tmpTile.width + tmpTile.width/2,
(curCol-1)*tmpTile.height + tmpTile.height/2)], nil];
[tmpTile2 runAction:[CCRepeat actionWithAction:tmpTile1_Seq2 times:1]];
//Swap tiles in array second
[tileArray replaceObjectAtIndex:elePos withObject:tmpTile2];
[tileArray replaceObjectAtIndex:elePos2 withObject:tmpTile];
}
Alright.
I'm aware some things I'm doing aren't exactly efficient and if it's not relevant I don't really want to focus to heavily on them. This is just a learning exercise for me - I just honestly don't understand why the origional Tile won't stay on top.
I've tried every resource or example I possibly can find for answers (sample included code, tutorials online, terrible book I bought, random somewhat related stackoverflow articles) and I'm getting nothing :\
In case it matters this is how I origionally added my sprites to the scene:
//Set up A CCSpriteBatchNode to load in pList/pngs of images
[[CCSpriteFrameCache sharedSpriteFrameCache]
addSpriteFramesWithFile:#"zAtl_BasicImages_64.plist"];
nodeBasicImages = [CCSpriteBatchNode batchNodeWithFile:#"zAtl_BasicImages_64.png" capacity:100];
//Initialize array used to track tiles in scene
tileArray = [[NSMutableArray alloc] init];
for (int i = 0; i<100; i++)
{
Tile *tmpTile = [[Tile alloc] init];
[tileArray addObject:tmpTile];
[self reorderChild:tmpTile z:-1];
}
Note: I checked and the zOrder on both Tiles is correct on the line before the MoveTo animation begins.
I added my sprites not to my layer, but to an image node because I was using a Sprite Atlas. So I was trying to access sprites that weren't even there. - I'm a new user so going to have to wait 8 hours until I can close. Peace. Why it didn't throw an error I'll never know.

Adding bullets yield OpenGL error 0x0503 in -[EAGLView swapBuffers]

I am trying to build up my first game with cocos2d. I am trying to insert bullets. when I get this error. The problem is it only occurs when a player fires not for enemy sprites. When this error occurs no only the position of player swaps with another and back also the bullet is destroyed after hitting two targets.
OpenGL error 0x0503 in -[EAGLView swapBuffers]
my weapon class has the following bullet implementation
if([self.bulletsArray count] <= ([self.numberOfBulletsPerFire intValue]*[self.numberOfBulletsOnScreen intValue]))
{
for (int i =0; i< [self.numberOfBulletsPerFire intValue]; i++) {
BulletClass *bullet = [[Bullet alloc]initWithPosition:position Direction:direction strength:self.weaponLevel spriteArray:spriteArray enemyArray:enemyArray base:base];
[self.bulletsArray addObject:bullet];
[self addChild:bullet];
[bullet release];bullet = nil;
}
}
in BulletClass i have the init method as :
(id)initWithPosition:(CGPoint)position
Direction:(KDirectionInput)direction
strength:(NSNumber *)strength
spriteArray:(NSMutableArray *)sprites
enemyArray:(NSMutableArray *)enemyArray
base:(CCSprite *)base{
if ((self = [super init])) {
self.base = base;
self.strength = strength;
self.movementDirection = direction;
self.spriteArray = sprites;
self.enemyArray = enemyArray;
self.velocity = 200/1;
self.bullet.position = position;
[self addChild:self.bullet z:2];
}
return self;
}
can anyone help me out here..
There are a few issues here that may be contributing to your problems.
First, while not a bug but more of a performance thing, you should not place counts and other object methods like intValue in your conditions, as this slows down the program. Define a local variable just before your loops that equals this number, and then use that variable in the loop so the software doesn't have count the same array or extract the same value over and over again every time it loops.
More importantly, I do not see anywhere that you are using a sprite frame cache (sprite batch), and if you are using sprite arrays that are very large where your array counts are high and manipulating and drawing these sprites many times as is typically requested by bullet operations, you could be adding a lot of burden to OpenGL unnecessarily, perhaps creating the buffer error you saw.
You should use a cocos sprite batch for your sprite arrays and give this another try; at the very least it will vastly improve the program's performance for bullet operations.

Is object remain fixed when scrolling background in cocos2d

I have one question when infinite background scrolling is done, is the object remain fixed(like doodle in doodle jump, papy in papi jump) or these object really moves.Is only background move or both (background and object )move.plz someone help me.I am searching for this solution for 4/5 days,but can't get the solution.So plz someone help me. And if object does not move how to create such a illusion of object moving.
If you add the object to the same layer as the scrolling background, then it will scroll as the background scrolls.
If your looking for an effect like the hero in doodle jump, you may want to look at having two or more layers in a scene.
Layer 1: Scrolling Background Layer
Layer 2: Sprite layer
SomeScene.m
CCLayer *backgroundLayer = [[CCLayer alloc] init];
CCLayer *spriteLayer= [[CCLayer alloc] init];
[self addChild:backgroundLayer z:0];
[self addChild:spriteLayer z:1];
//Hero stays in one spot regardless of background scrolling.
CCSprite *squidHero = [[CCSprite alloc] initWithFile:#"squid.png"];
[spriteLayer addChild:squidHero];
If you want objects to scroll with the background add it to the background layer:
//Platform moves with background.
CCSprite *bouncePlatform= [[CCSprite alloc] initWithFile:#"bouncePlatform.png"];
[backgroundLayer addChild:bouncePlatform];
Another alternative is to use a CCFollow action. You would code as if the background is static (which it will be) and the player is moving (which it will be), but add a CCFollow action to the player. This essentially moves the camera so that it tracks your player.
You can also modify the classes so that you can get the CCFollow action to follow with an offset (i.e., so the player is not in the middle of the screen) as well as to have a smoothing effect to it, so that when the player moves, the follow action is not jerky. See the below code:
*NOTE I am using cocos2d-x, the c++ port. The methods are similar in cocos2d, and you should be able to modify these to fit the cocos2d syntax. Or search around -- I found these for cocos2d and then ported to c++.
//defines the action to constantly follow the player (in my case, "runner.p_sprite is the sprite pointing to the player)
FollowWithOffset* followAction = FollowWithOffset::create(runner.p_sprite, CCRectZero);
runAction(followAction);
And separately, I have copied the class definition for CCFollow to create my own class, CCFollowWithAction. This also has a smoothing effect (you can look this up more online) so that when the player moves, the actions are not jerky. I modified "initWithTarget," to take into account an offset, and "step," to add a smoothing action. You can see the modifications in the comments below.
bool FollowWithOffset::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{
CCAssert(pFollowedNode != NULL, "");
pFollowedNode->retain();
m_pobFollowedNode = pFollowedNode;
if (rect.equals(CCRectZero))
{
m_bBoundarySet = false;
}
else
{
m_bBoundarySet = true;
}
m_bBoundaryFullyCovered = false;
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
m_obFullScreenSize = CCPointMake(winSize.width, winSize.height);
//m_obHalfScreenSize = ccpMult(m_obFullScreenSize, 0.5f);
m_obHalfScreenSize = CCPointMake(m_obFullScreenSize.x/2 + RUNNER_FOLLOW_OFFSET_X,
m_obFullScreenSize.y/2 + RUNNER_FOLLOW_OFFSET_Y);
if (m_bBoundarySet)
{
m_fLeftBoundary = -((rect.origin.x+rect.size.width) - m_obFullScreenSize.x);
m_fRightBoundary = -rect.origin.x ;
m_fTopBoundary = -rect.origin.y;
m_fBottomBoundary = -((rect.origin.y+rect.size.height) - m_obFullScreenSize.y);
if(m_fRightBoundary < m_fLeftBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2;
}
if(m_fTopBoundary < m_fBottomBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fTopBoundary = m_fBottomBoundary = (m_fTopBoundary + m_fBottomBoundary) / 2;
}
if( (m_fTopBoundary == m_fBottomBoundary) && (m_fLeftBoundary == m_fRightBoundary) )
{
m_bBoundaryFullyCovered = true;
}
}
return true;
}
void FollowWithOffset::step(float dt)
{
CC_UNUSED_PARAM(dt);
if(m_bBoundarySet){
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(m_bBoundaryFullyCovered)
return;
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
m_pTarget->setPosition(ccp(clampf(tempPos.x, m_fLeftBoundary, m_fRightBoundary),
clampf(tempPos.y, m_fBottomBoundary, m_fTopBoundary)));
}
else{
//custom written code to add in support for a smooth ccfollow action
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
CCPoint moveVect = ccpMult(ccpSub(tempPos,m_pTarget->getPosition()),0.25); //0.25 is the smooth constant.
CCPoint newPos = ccpAdd(m_pTarget->getPosition(), moveVect);
m_pTarget->setPosition(newPos);
}
}