Create Puzzle pieces from a single image

@grimfate rocks. Thank you for taking the time to help everyone.

1 Like

@slackmoehrle Just doing my duty to help the community.

Sorry mate, but I wasn’t able to make the code work :frowning: it compiles and executes without errors, but I don’t get any result. It won’t do anything.

Here’s the class, and the output.

PuzzleGame.zip (1.7 KB)

What output as your expecting? I can see some obvious changes that need to made to your code, but I’m not sure what you are expecting your code to look like.

This is what I’m trying to achieve. To have an image, and programmatically cut it into diferent pieces of a puzzle, and then being able to manipulate them so the player completes the puzzle and gets the original image. For the way the game is supposed to work, I can’t just cut the images in photoshop and load them as sprites (the easy way) to be the pieces.

I meant what you expect the code in PuzzleGame.zip to look like when you run it. Are you trying to turn pp6_01_edited.png into a puzzle piece and display it on screen?

So, because I am unaware of whether or not you have managed to create a puzzle piece yet, here is how you should change your code just to see if you can create a puzzle piece:

  1. Comment out or delete line 60, which sets the position of PuzzleImage. Moving the puzzle piece up and right moves it away from the area the RenderTexture uses to draw nodes.
  2. Comment out or delete line 62, which adds PuzzleImage to your scene. Having the picture on the screen will only make it harder to see the puzzle piece.
  3. Change line 68, which sets the position of the mask, to mask->setPosition(Vec2(origin.x, origin.y)); If you were trying to create the puzzle piece for the top-left of the puzzle image, the Y value should be the height of the puzzle minus the the height of the mask because the mask is set with an anchor point with a Y value of 0, which places the anchor point at the bottom of the mask.
  4. Change line 69, which sets the size of the mask, to mask->setScale(puzzleImage->getBoundingBox().size.width / mask->getContentSize().width / 3, puzzleImage->getBoundingBox().size.height / mask->getContentSize().height / 2); Setting the content size of a sprite doesn’t change the size of its image. Also getting the content size of a sprite will get you the size of the sprite when its scale is 1. To get the current size of a scaled sprite, use node->getBoundingBox().size.
  5. Comment out or delete line 70, which adds the mask to the screen. Unless you plan to have your white mask visible on screen, this line is unneeded.
  6. Change line 72, which gets the content size of the mask, to Size cs = mask->getBoundingBox().size; The mask is now scaled, so you need to use the bounding box to get its size on screen. You might like to change the variable name from cs as it’s not really the content size anymore.
  7. Change line 121, which sets the position of rtSprite to rtSprite->setPosition(origin + rtSprite->getContentSize() / 2); This move the bottom-left of the sprite to the bottom-right of the screen.

Here’s what the code for onEnter should look like:

    // load the image the makes up the puzzle
    Sprite *puzzleImage = Sprite::create("BPG/StoryBundles/NOH.bundle/NOHPuzzle_pack0_0.png");

    puzzleImage->setAnchorPoint(Vec2(0.0, 0.0));
    puzzleImage->setScale(visibleSize.width / puzzleImage->getContentSize().width, visibleSize.height / puzzleImage->getContentSize().height);
    puzzleImage->setBlendFunc({ GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA });

    int i = 1;

    Sprite *mask = Sprite::create("BPG/PLAPuzzleScene/piece6/pp6_01_edited.png");
    mask->setAnchorPoint(Vec2(0.0,0.0));
    mask->setPosition(Vec2(origin.x, origin.y));
    mask->setScale(puzzleImage->getBoundingBox().size.width / mask->getContentSize().width / 3, puzzleImage->getBoundingBox().size.height / mask->getContentSize().height / 2);

    Size cs = mask->getBoundingBox().size;
    // center the mask


    // create the render texture with the same size as the mask
    RenderTexture *rt = RenderTexture::create(cs.width, cs.height);

    CCLOG("Created RenderTexture");
    CCLOG("----------------------");
    // position the image so that the correct part of it is over the puzzle
    // piece; this position here, combined with the anchor Vec2 set earlier,
    // means the first puzzle piece will match the top-left of the image, and
    // the next piece will cover the next part of the image along the top;
    // remember to move the image in a negative direction to move it left or up


    // clear the RenderTexture; the colour doesn't matter as long as the alpha
    // value (the fourth number) is 0, or else it will interfere with the mask
    rt->beginWithClear(0, 0, 0, 0);
    CCLOG("Rendertexture clear");
    CCLOG("--------------------");
    // draw the mask on the RenderTexture; at this Vec2 the RenderTexture now
    // looks identical to the mask
    mask->visit();
    // draw the puzzle image; the blending function means only the parts that the
    // mask covers will be filled in with the puzzle image
    CCLOG("Mask drawn");
    CCLOG("--------------------");

    puzzleImage->visit();
    // the renderer needs to be called to finalise generating the image, or else
    // the RenderTexture won't get its final image until the next draw call, which
    // means the puzzle image will be in the same place for each puzzle piece, i.e.
    // all the pieces will cover the same part of the puzzle image
    Director::getInstance()->getRenderer()->render();

    // finish rendering onto the render texture
    rt->end();

    CCLOG("Image rendered");
    CCLOG("-----------------");
    // not sure how you are suppose to use the final image of the RenderTexture, but
    // I imagine getting rid of the RenderTexture and just using its texture is best.
    Sprite *rtSprite = Sprite::createWithTexture(rt->getSprite()->getTexture());

    // RenderTexture produces a texture which is inverted, so you need to uninvert it
    // to have it right way up
    rtSprite->setFlippedY(true);
    // put the puzzle piece wherever you want on screen
    rtSprite->setPosition(origin + rtSprite->getContentSize() / 2);
    this->addChild(rtSprite, 10);

This should create a puzzle piece that matches the bottom-left of your puzzle image and places it at the bottom-left of the screen.

1 Like

Not exactly like turning the pp6_01_edited.png (which is the mask) into a separate piece (although it’d also work for me) but rather like using the mask image (the white part) as a mold to create a new sprite which contains the part of the main image that will serve as a piece of the puzzle, I don’t know if I’m explaining myself properly, english is not my first language.

Thanks for your help, I’ll be trying your suggestions :smile:

Hey mate!

I tested your code and it works perfectly! You’re awesome :smiley:

However, I’m having a problem and I have no idea what it is, I have tried to fix it myself and failed, so I came to ask you again.

The thing is, the masking and cutting only works, when the position of the mask is (0,0):

but if I try to place it somewhere else, for example, the center of the screen, and re-run the game (I don’t change its position in execution time), it just shows the backgroun (black in this case):

and, if I comment all the code for the masking and the render texture, and just show the image in the screen, it turns out to be just where I placed.

any idea of what might be happening? I tried a couple of things but didn’t work at all.

Thanks in advance mate :smile:

The mask needs to be in the bottom-left of the screen when the code I gave you calls Director::getInstance()->getRenderer()->render();. You should be able to move it wherever you want after that.

So, everytime I call Director::getInstance()->getRenderer()->render(); the mask must be on bottom-left corner? That means I need to move the main image and set the mask image always on bottom-left, is that right?

Yes. Here is a simple explanation for how the RenderTexture works:

When you call renderTexture->begin(), an image the same size of the RenderTexture is placed down the bottom-left of the screen. Every time you call visit() on a node, you are telling the node to draw itself onto that image. Calling Director::getInstance()->getRenderer()->render(); gives that image to the RenderTexture so you can use it.

This is not a perfect explanation, but hopefully it will help you understand how to use it.

Hello mate! Thanks for your help :slight_smile:

I think I just managed to make it work the way I needed, but I couldn’t have done it without your help. Thank you :smiley:

I have just one last question: how do I get rid of a node if I haven’t added it to the scene?

If you have used a node’s create function, e.g. Sprite::create(), then it will get rid of itself. Using the create method will use Cocos2d’s autorelease system which automatically removes a node from memory (if you haven’t used the node’s retain function.)

In the code I gave you, puzzleImage, mask and rt should all get rid of themselves.

http://www.cocos2d-x.org/wiki/Reference_Count_and_AutoReleasePool_in_Cocos2d-x

I have a problem using the masked image. I have added the masked sprite to Scene1 and its successfully added.

Then i switched to other scene from the current one and switched back to previous scene then a error is coming.

Something Like - RendererWebGL.js:314 Uncaught TypeError: Cannot read property ‘indexOf’ of undefined

STEPS -

I have added masked sprite in helloworld scene and added a button to switch to other scene like gameplay scene and in gameplay scene i have added a button to switch to helloworld scene.

For the first time when helloworld scene appears everything works fine masked image appears.

Now i switched to gameplay scene and from here and i switched back to helloworld scene. Now the error comes. ie. RendererWebGL.js:314 Uncaught TypeError: Cannot read property ‘indexOf’ of undefined

I have used this code.

	// Create a mask
	var maskSprite = new cc.Sprite(spriteFrameCache.getSpriteFrame("mask.png"));
	// maskSprite.setBlendFunc(cc.ONE, cc.ZERO);
	maskSprite.setPosition(maskSprite.width / 2, maskSprite.height / 2);

	var puzzleImage = new cc.Sprite(spriteFrameCache.getSpriteFrame("puzzle.png"));

	puzzleImage.setBlendFunc(cc.DST_ALPHA, cc.ONE_MINUS_DST_ALPHA);


	puzzleImage.setPosition(maskSprite.width / 2, maskSprite.height / 2);

	var cs = maskSprite.getBoundingBox();
	var rendererTexture = new cc.RenderTexture(cs.width, cs.height);

	rendererTexture.beginWithClear(0, 0, 0, 0);
	// rendererTexture.begin();

	maskSprite.visit();
	puzzleImage.visit();

	rendererTexture.end();

	var rtSprite = new cc.Sprite();
	rtSprite.initWithTexture(rendererTexture.getSprite().getTexture());
	rtSprite.getTexture().setAntiAliasTexParameters();
	rtSprite.flippedY = true;

	rtSprite.setPosition(500,450);
	this.addChild( rtSprite,20);

Please let me know what i am doing wrong.