On an OpenStreetMap map with osmDroid and osmBonusPack I display markers and by clicking on it a bubble opens, everything works as I want up to a certain number of markers.
The more markers I put on the map, the less responsive the application is.
For example with 1000 markers, it takes 6 seconds for the Toolbar menu to appear and as much for moving to another activity such as a simple text display.
My code.
private void creationMarker(GeoPoint arg,
String titre,
String proximite,
String description,
String identifiant) {
double doubleProximite;
Marker startMarker = new Marker(map);
startMarker.setPosition(arg);
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
InfoWindow infoWindow = new MyInfoWindow(R.layout.bonuspack_bubble_black, map);
((MyInfoWindow) infoWindow).setTitre(titre);
((MyInfoWindow) infoWindow).setDescription(description);
((MyInfoWindow) infoWindow).setSubDescription(identifiant);
startMarker.setTitle(((MyInfoWindow) infoWindow).getTitre());
startMarker.setTitle(((MyInfoWindow) infoWindow).getDescription());
startMarker.setTitle(((MyInfoWindow) infoWindow).getSubDescription());
startMarker.setIcon(getResources().getDrawable(R.drawable.croix_verte, null).mutate());
startMarker.setInfoWindow(infoWindow);
doubleProximite = Double.parseDouble(proximite);
Polygon circle = new Polygon();
circle.setPoints(Polygon.pointsAsCircle(arg, doubleProximite));
int myColorZone, myColorCloture;
myColorZone = this.getResources().getColor(R.color.SurfaceZoneActive, getTheme());
circle.setFillColor(myColorZone); // couleur avec arrière plan transparent
myColorCloture = this.getResources().getColor(R.color.ClotureActive, getTheme());
circle.setStrokeColor(myColorCloture);// couleur de la circonférence
circle.setStrokeWidth(3); // épaisseur du trait
map.getOverlays().add(circle);
map.getOverlays().add(startMarker);
}
I use a loop with an SQL database for the marker data.
I guess the more markers there are, the more events the app has to handle.
What solutions could solve my problem.
Thank you in advance for your answers
Markers are quite heavyweight or at least they have been some time ago when I had similar performance issues as you have. There might be a potential to optimize them, but that would mean modifying the Osmdroid library and creating a pull request with such changes.
Try to do some profiling to make sure the problem is not in your code but in the library (https://developer.android.com/studio/profile/cpu-profiler.html).
Pay attention to how you read the data from the databse - don't do it on main thread and measure how long it takes to make sure it's not the main issue.
Try SimpleFastPointOverlay from Osmdrid - it is supposed to be optimized for higher numbers of Markers (they have to have the same icon, though). Even if its not flexible enough for your needs, try it out to verify Markers are the real problem. After that you can look at the source code and use it as a learning material to implement your own custom Overlay.
A month has passed, I have a solution, I studied the project https://github.com/MKergall/osmbonuspack in the folder
https://github.com/MKergall/osmbonuspack/tree/master/OSMBonusPack/src/main/java/org/osmdroid/bonuspack/clustering
there are three files that interested me to manage clustering.
MarkerClusterer.java
RadiusMarkerClusterer.java
StaticCluster.java
Thanks to M.Kergall and Roman Sidorov who are the creators of these java sources.
I simply copied these files into a test project. I was inspired by the methods that manage Markers in order to put circular Polygons, clustering side it works well, but depending on the zoom level, when the circular polygons are drawn there is no longer any interest in clustering. I countered this problem by managing the drawings of the polygons from the screen, I draw that what there is the screen, ditto for the Markers. Now with 20,000 Markers and Polygons it's just starting to hinder the responsiveness of the application, when before it was around 200. Here are some code snippets below.
Here are some code snippets below.
Main activity code
private void getlireDataBaseMarkers() {
bar.setVisibility(View.VISIBLE);
bar.getLayoutParams().height = 6;
bar.requestLayout();
Thread t = new Thread(){
public void run() {
Bundle messageBundle=new Bundle();
nbrMarker = 0;
dbMarkers = new DBHelper(MainActivity.this);
if (dbMarkers.numberOfRows() == 0) {
return;
}
SQLiteDatabase db = dbMarkers.getReadableDatabase();
Cursor res = db.rawQuery(SqlOrderVisibilite, null);
res.moveToFirst();
int qtyMarkers = dbMarkers.numberOfRows();
bar.setMax(qtyMarkers);
bar.setProgress(0);
MyRadiusMarkerClusterer radiusMarkers = new MyRadiusMarkerClusterer(MainActivity.this);
Double zoom = 15.0;
radiusMarkers.setMaxPolygonZoomLevel(zoom);
GeoPoint lePoint = new GeoPoint(0.0, 0.0, 0.0);
while (!res.isAfterLast() &&
!isPausing.get() &&
isRunning.get()) {
lePoint.setLatitude(res.getDouble(res.getColumnIndex(MARKERS_COLUMN_LATITUDE)));
lePoint.setLongitude(res.getDouble(res.getColumnIndex(MARKERS_COLUMN_LONGITUDE)));
Marker startMarker = new Marker(map);
startMarker.setPosition(lePoint);
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
startMarker.setIcon(getResources().getDrawable(R.drawable.person, null).mutate());
Polygon circle = new Polygon(map);
int myColorZone, myColorCloture;
circle.setPoints(Polygon.pointsAsCircle(lePoint,getRandomDoubleBetweenRange(20,200)));
myColorZone = MainActivity.this.getResources().getColor(R.color.colorAccent, getTheme());
circle.setFillColor(myColorZone);
myColorCloture = MainActivity.this.getResources().getColor(colorPrimary, getTheme());
circle.getOutlinePaint().setColor(myColorCloture);
circle.getOutlinePaint().setStrokeWidth(4);
radiusMarkers.add(startMarker);
radiusMarkers.add(circle);
nbrMarker++;
myMessage = handler.obtainMessage();
messageBundle.putInt(WRITE_QTY_MARKERS,2); // incrémenter le progress bar
myMessage.setData(messageBundle);
handler.sendMessage(myMessage);
res.moveToNext();
}
map.getOverlays().add(radiusMarkers);
res.close();
dbMarkers.close();
myMessage = handler.obtainMessage();
messageBundle.putInt(WRITE_QTY_MARKERS,1); // afficher le nombre de markers
myMessage.setData(messageBundle);
handler.sendMessage(myMessage);
}
};
isRunning.set(true);
isPausing.set(false);
t.start();
}
part of MyMarkerClusterers code
#Override
public void draw (Canvas canvas, MapView mapView,boolean shadow){
//if zoom has changed and mapView is now stable, rebuild clusters:
// si le zoom a changé et que mapView est maintenant stable, reconstruisez les groupes
Double zoomLevel = mapView.getZoomLevelDouble();
//if (zoomLevel != mLastZoomLevel && !mapView.isAnimating()) {
if (!mapView.isAnimating()) {
mClusters = clusterer(mapView);
pClusters = pclusterer(mapView);
renderer(mClusters, pClusters, canvas, mapView);
setmLastZoomLevel(zoomLevel);
}
for (MyStaticCluster cluster : mClusters) {
cluster.getMarker().draw(canvas, mapView, shadow);
}
for (MyStaticCluster cluster : pClusters) {
if (getmLastZoomLevel() > mMaxPolygonZoomLevel) {
cluster.getPolygon().draw(canvas, mapView, shadow);
}
}
}
part of MyRadiusMarkerClusterer code
#Override
public ArrayList<MyStaticCluster> clusterer(MapView mapView) {
ArrayList<MyStaticCluster> clusters = new ArrayList<>();
convertRadiusToMeters(mapView);
mClonedMarkers = new ArrayList<>(); //shallow copy
ArrayList<Marker> mFiltreMarkers = new ArrayList<>(mItems);
Iterator<Marker> it = mFiltreMarkers.iterator();
while (it.hasNext()){
Marker valeur = it.next();
GeoPoint point = valeur.getPosition();
// limiter le nombre de Marker suivant l'écran
if( mapView.getProjection().getNorthEast().getLatitude() >= point.getLatitude() &&
mapView.getProjection().getNorthEast().getLongitude() >= point.getLongitude() &&
mapView.getProjection().getSouthWest().getLatitude() <= point.getLatitude() &&
mapView.getProjection().getSouthWest().getLongitude() <= point.getLongitude()) {
mClonedMarkers.add(valeur);
}
it.remove();
}
while (!mClonedMarkers.isEmpty()) {
Marker m = mClonedMarkers.get(0);
MyStaticCluster cluster = createCluster(m,null, mapView);
clusters.add(cluster);
}
return clusters;
}
#Override public ArrayList<MyStaticCluster> pclusterer(MapView mapView) {
ArrayList<MyStaticCluster> clusters = new ArrayList<>();
convertRadiusToMeters(mapView);
pClonedPolygon = new ArrayList<> (); //shallow copy
ArrayList<Polygon> pFiltrePolygon = new ArrayList<>(pItems);
Iterator<Polygon> it = pFiltrePolygon.iterator();
while (it.hasNext()){
Polygon valeur = it.next();
GeoPoint point = valeur.getInfoWindowLocation();
// limiter le nombre de Polygon suivant l'écran
if( mapView.getProjection().getNorthEast().getLatitude() >= point.getLatitude() &&
mapView.getProjection().getNorthEast().getLongitude() >= point.getLongitude() &&
mapView.getProjection().getSouthWest().getLatitude() <= point.getLatitude() &&
mapView.getProjection().getSouthWest().getLongitude() <= point.getLongitude()) {
pClonedPolygon.add(valeur);
}
it.remove();
}
while (!pClonedPolygon.isEmpty()) {
Polygon p = pClonedPolygon.get(0);
MyStaticCluster cluster = createCluster(null,p, mapView);
clusters.add(cluster);
}
return clusters;
}
Related
I am new to Cocos2D-X and have been trying to learn it for the past couple months now. I am trying to create a sample game from the book Cocos2d-x Game Development Blueprints and in this particular chapter it's explaining how to create a game using tiled map.
I Need help parsing a list of objects from a TMX file, I am trying to update the code from version 2.X to version 3.X, because I get compile errors. I need to change the deprecated CCArray and CCDictionary into the new cocos::Vector and Map in the following code
void GameWorld::CreateTiledMap()
{
// generate level filename
char buf[128] = {0};
sprintf(buf, "level_%02d.tmx", GameGlobals::level_number_);
// create & add the tiled map
tiled_map_ = CCTMXTiledMap::create(buf);
addChild(tiled_map_);
// get the size of the tiled map
columns_ = (int)tiled_map_->getMapSize().width;
rows_ = (int)tiled_map_->getMapSize().height;
// save a reference to the layer containing all the bricks
bricks_layer_ = tiled_map_->layerNamed("Bricks");
// parse the list of objects
CCTMXObjectGroup* object_group = tiled_map_->objectGroupNamed("Objects");
CCArray* objects = object_group->getObjects();
int num_objects = objects->count();
for(int i = 0; i < num_objects; ++i)
{
CCDictionary* object = (CCDictionary*)(objects->objectAtIndex(i));
// create the Hero at this spawning point
if(strcmp(object->valueForKey("name")->getCString(), "HeroSpawnPoint") == 0)
{
CreateHero(ccp(object->valueForKey("x")->floatValue(), object->valueForKey("y")->floatValue()));
}
// create an Enemy at this spawning point
else if(strcmp(object->valueForKey("name")->getCString(), "EnemySpawnPoint") == 0)
{
CCPoint position = ccp(object->valueForKey("x")->floatValue(), object->valueForKey("y")->floatValue());
CCPoint speed = ccp(object->valueForKey("speed_x")->floatValue(), object->valueForKey("speed_y")->floatValue());
CreateEnemy(position, speed);
}
// create a Platform at this spawning point
else if(strcmp(object->valueForKey("name")->getCString(), "PlatformSpawnPoint") == 0)
{
CCPoint position = ccp(object->valueForKey("x")->floatValue(), object->valueForKey("y")->floatValue());
CCPoint speed = ccp(object->valueForKey("speed_x")->floatValue(), object->valueForKey("speed_y")->floatValue());
CreatePlatform(position, speed);
}
// save the point where the level should complete
else if(strcmp(object->valueForKey("name")->getCString(), "LevelCompletePoint") == 0)
{
level_complete_height_ = object->valueForKey("y")->floatValue();
}
}
}
I have tried converting it myself, but have been unsuccessful.
I am having trouble converting the CCArray to cocos::Vector,
but mainly changing the CCDictionary to cocos::Map. Can someone Please help me?
Cocos2d-x has many tests, which is very helpful. From \tests\cpp-tests\Classes\TileMapTest\TileMapTest.cpp:
auto map = TMXTiledMap::create("TileMaps/test-object-layer.tmx");
addChild(map, -1, kTagTileMap);
CCLOG("----> Iterating over all the group objets");
auto group = map->getObjectGroup("Objects");
auto& objects = group->getObjects();
for (auto& obj : objects)
{
ValueMap& dict = obj.asValueMap();
float x = dict["x"].asFloat();
string name = dict["name"].asString();
}
I'm attempting to check for specific collisions between sprites with Physics bodies. The problem is that I don't know how to assign either a tag to each node physics body or set the collision bitmask properly for each sprite. I am successfully checking for collisions but it's only a true or false for all objects.
I'm following a guide in the cocos2d-x documentation that I've linked below. In it, it explains how to set masking bits and masking categories.
Collision filtering allows you to prevent collision between shapes. Cocos2d-x >supports collision filtering using category and groups bitmasks.
Cocos2d-x supports 32 collision categories. For each shape you can specify which category it belongs to. You also specify what other categories this shape can collide with. This is done with masking bits
The problem is that when I set:
sprite1->getPhysicsBody()->setCategoryBitmask(0x02); // 0010
sprite1->getPhysicsBody()->setCollisionBitmask(0x01); // 0001
There isn't collision detection for those sprites. But when I set:
invaderPhysicsBody->setContactTestBitmask(true);
There is a collision detection.
Does anybody know how to successfully set the collision bitmask or category bitmask?
Objects with possible collisions:
Vector of sprites for invaders.
Vector of player missiles
Vector of invader missiles
Sprite for player
Four sprites for shields
This is my onContactBegin function that will remove the Node once they collide. This is where I need to test which objects collided.
bool Gameplay::onContactBegin(PhysicsContact &contact) {
std::cout << "onContactBegin -------> " << std::endl;
player_score += 10;
auto nodeA = contact.getShapeA()->getBody()->getNode();
auto nodeB = contact.getShapeB()->getBody()->getNode();
std::cout << contact.getShapeA()->getTag() << std::endl;
if ((contact.getShapeA()->getCategoryBitmask() & contact.getShapeB()->getCollisionBitmask()) == 0
|| (contact.getShapeB()->getCategoryBitmask() & contact.getShapeA()->getCollisionBitmask()) == 0)
{
std::cout << "Overlap!" << std::endl;
}
nodeA->removeFromParent();
nodeB->removeFromParent();
return true;
}
Here is my collision listener:
// Enable collision listener.
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(Gameplay::onContactBegin, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contactListener, this);
This is how I currently create and set the Physics bodies for my sprites:
int spacing = 0;
// Four Player Shields
for (int i = 0; i < 4; i++) {
auto shield = Sprite::create("player_shield.png");
auto shield_x = shield->getContentSize();
auto shield_y = shield->getContentSize();
auto shieldPhysicsBody = PhysicsBody::createBox(Size(shield_x.width,shield_y.height), PHYSICSBODY_MATERIAL_DEFAULT);
shieldPhysicsBody->setContactTestBitmask(true);
shieldPhysicsBody->setDynamic(true);
shieldPhysicsBody->setTag(0);
shield->setPosition(Vec2(200 + spacing, 150));
shield->addComponent(shieldPhysicsBody);
this->addChild(shield);
spacing += 200;
}
Here is the guide I've been following.
There is a big mistake in your code. setContactTestBitmask accept a int rather than bool. When you call shieldPhysicsBody->setContactTestBitmask(true), it's actually shieldPhysicsBody->setContactTestBitmask(0x0000001). And 0x000001 & 0x000010 = 0, so no contact event will be raised. The & result of spriteA's categoryBitmask and spriteB's ContactTestBitmask determines whether a contact event will be raised. BTW, generally speaking, you don't need check contaction manually, ie:
if ((contact.getShapeA()->getCategoryBitmask() & contact.getShapeB()->getCollisionBitmask()) == 0
|| (contact.getShapeB()->getCategoryBitmask() & contact.getShapeA()->getCollisionBitmask()) == 0)
{
std::cout << "Overlap!" << std::endl;
}
I've been trying to remove elements (balls) that have been added to the Physics engine, but I can't find a way to do it.
This is the code I'm using to add the molecules to the Physics Engine:
var numBodies = 15;
function _addMolecules() {
for (var i = 0; i < numBodies; i++) {
var radius = 20;
var molecule = new Surface({
size: [radius * 2, radius * 2],
properties: {
borderRadius: radius + 'px',
backgroundColor: '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6)
}
});
molecule.body = new Circle({
radius: radius,
mass: 2
});
this.pe.addBody(molecule.body);
this.molecules.push(molecule);
this.moleculeBodies.push(molecule.body);
molecule.state = new Modifier({origin: [0.5, 0.5]});
//** This is where I'm applying the gravity to the balls and also where I'm checking the position of each ball
molecule.state.transformFrom(addBodyTransform.bind(molecule.body));
this._add(molecule.state).add(molecule);
}
}
and on the addBodyTransform function I'm adding the gravity to the balls and checking their position, and for any that are outside the top part of the viewport I want to remove it completely (I'm only using walls on the left, right and bottom edges of the viewport).
function addBodyTransform() {
var pos;
for (var i = 0; i < thisObj.moleculeBodies.length; i++) {
pos = thisObj.moleculeBodies[i].getPosition();
if(pos[1]<(-windowY/2)){
//I tried this but it doesn't work
thisObj.pe.removeBody(thisObj.moleculeBodies[i]);
thisObj.moleculeBodies[i].render = function(){ return null; };
}
}
thisObj.gravity.applyForce(this);
return this.getTransform();
}
It doesn't work. I tried a couple of other things, but no luck. Whereas changing the position of the balls on the function above worked fine:
thisObj.moleculeBodies[i].setPosition([0, 0]);
Does anybody have any idea how to remove a body (a circle in this case)?
P.S.: thisObj is the variable I'm assign the "this" object to in the constructor function and thisObj.pe is the instance of the PhysicsEngine(). Hope that makes sense.
After some investigation, using the unminified source code and trying out different things, I realised that there was something weird going on in the library.
Having a look at the repository, I found out that the function _getBoundAgent is being used before it is defined, which matched with the error I was getting (you can check it here: https://travis-ci.org/Famous/physics). So it looks like it is a bug in the Famo.us source-code. Hopefully it will be fixed in the next release.
For the time being, I had to create a hack, which is basically detaching all agents (as well as gravity) from the balls that go outside the viewport and setting their (fixed) position far outside the viewport (about -2000px in both directions).
I know it is not the best approach (a dirty one indeed), but if you have the same problem and want to use it until they release a fix for that, here is what I did:
function addBodyTransform() {
var pos = this.body.getPosition();
//Check if balls are inside viewport
if(pos[1]<(-(windowY/2)-100)){
if(!this.removed){
//flagging ball so the code below is executed only once
this.removed = true;
//Set position (x and y) of the ball 2000px outside the viewport
this.body.setPosition([(-(windowX/2)-2000), (-(windowY/2)-2000)]);
}
return this.body.getTransform();
}else{
//Add gravity only if inside viewport
thisObj.gravity.applyForce(this.body);
return this.body.getTransform();
}
}
and on the _addMolecules function, I'm adding a "molecule.removed = false":
function _addMolecules() {
for (var i = 0; i < numBodies; i++) {
...
molecule.state = new Modifier({origin: [0.5, 0.5]});
//Flagging molecule so I know which ones are removed
molecule.removed = false;
molecule.state.transformFrom(addBodyTransform.bind(molecule));
this._add(molecule.state).add(molecule);
}
}
Again, I know it is not the best approach and I will be keen in hearing from someone with a better solution. :)
I have an Init method and spawn() method which is called by a CCAction every 2 seconds!
What i want is to move pipePair node across the screen every 2 seconds, something like in the flappy bird game! But in my case am not able to add multiple pairs of the node in the screen plus the speed of the MoveBy CCAction fastens periodically.
So i want the nodes should be added periodically and they should move in a constant speed across the screen.
Any help would be appreciated.
bool HelloWorld::init()
{
//creating the world
b2Vec2 gravity;
gravity.Set(0.0f, -20.0f);
world = new b2World(gravity);
// Do we want to let bodies sleep?
world->SetAllowSleeping(true);
world->SetContinuousPhysics(true);
setAccelerometerEnabled( true );
scheduleUpdate();
setTouchEnabled(true);
//////////////////////////////
// 1. super init first
if( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255)) ) //RGBA
{
return false;
}
if ( !CCLayer::init() )
{
return false;
}
screenSize = CCDirector::sharedDirector()->getWinSize();
//Initializing CCNodes
_moving = CCNode::create();
addChild(_moving);
_pipes = CCNode::create();
_moving->addChild(_pipes);
CCSprite *test = CCSprite::create("goalssss.png");
CCRect rectOfPipe = test->boundingBox();
float64 distanceToMove = screenSize.width + 2.5*rectOfPipe.size.width;
CCMoveBy* movePipes = CCMoveBy::create(0.01*distanceToMove, CCPointMake(-screenSize.width, 0));
CCRemoveSelf *removePipes = CCRemoveSelf::create();
sequenceActionOfPipes = CCSequence::create(movePipes,removePipes);
sequenceActionOfPipes->retain();
CCCallFunc *spawn = CCCallFunc::create(this, callfunc_selector(HelloWorld::spawnPipes));
CCDelayTime *delayForTwoSecs = CCDelayTime::create(2.0);
CCSequence *sequenceForSpawnAndDelay = CCSequence::create(spawn,delayForTwoSecs);
CCRepeatForever *repeatActionForeverOfSpawnAndDelay = CCRepeatForever::create(sequenceForSpawnAndDelay);
this->runAction(repeatActionForeverOfSpawnAndDelay);`
}
This is my spawn method which is called every 2 seconds!
void HelloWorld::spawnPipes(){
CCNode *pipePair = CCNode::create();
pipePair->setPosition(CCPoint(screenSize.width, 0));
//pipePair->setZOrder(-10);
float64 randomVal = arc4random()%(int32)(screenSize.height/3);
CCSprite *_pipeUpReplica = CCSprite::create("goalssss.png");
_pipeUpReplica->setPosition(CCPoint(20, randomVal));
CCSprite *_pipeBelowReplica = CCSprite::create("goalssss.png");
_pipeBelowReplica->setPosition(CCPoint(20, randomVal+100+_pipeUpReplica->boundingBox().size.height));
pipePair->addChild(_pipeUpReplica);
pipePair->addChild(_pipeBelowReplica);
//run actions
pipePair->runAction(sequenceActionOfPipes);
_pipes->addChild(pipePair);
}
Firs of all, you don't need to do this :
if( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255)) ) //RGBA
{
return false;
}
if ( !CCLayer::init() )
{
return false;
}
CCLayerColor inherits from CCLayer (reference : docs) and I certainly hope you don;t inherit from both of them! :)
As to the problem at hand, I can't pinpoit a problem, but I can spot some possible pitfalls, which may be contrbuting to your issues.
A lot of nodes. You have your Layer, on which you add the _moving node on which you add the _pipes node on which you add the pipePair node. Unless they are essential for other things in your game, I can;t see a reason why you are not adding the pipePair directly on your layer.
You didn't set the position of _moving and _pipes - though it may seem unimportant, it is good to explicilty position all your nodes to not be suprised later on. A usefull thing here is the `CCPointZero.
I have never had any success whatsoever with reusing CCSequences like you do. It always turned out to be best to just create them when they are to be used and not worry about them later on.
Hope some of these will help!
Try increasing the delay from 2.0f to 3.0f
CCDelayTime *delayForTwoSecs = CCDelayTime::create(3.0);
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);
}
}