I want to create a game in Cocos2dx in which i have an image which is not visible but when i move my finger on device it start drawing. Only that part of image draws where i move my finger. I have attached a screenshot as a reference to make my question more clear.
You can also look at using ClippingNode with the image as a child and a DrawNode as the stencil. Then do as @grimfate suggests to draw the touches into the Drawnode using one of the draw commands whether circles for each touch (drawPoint) or something that connects touches using drawSegment (or bezier curves).
I figured out the solution you probably want. It’s using a ClippingNode. Here’s some code:
// create the node that will remember where you draw
auto drawNode = DrawNode::create();
// create the node which will use the draw node to determine what is shown, i.e. DrawNode will
// be used as a "mask"
auto clippingNode = ClippingNode::create(drawNode);
addChild(clippingNode);
// without this, touches will be square...
clippingNode->setAlphaThreshold(0.0f);
// add the hidden sprite you want to be revealed
auto sprite = CCSprite::create("SpriteFilename.png");
clippingNode->addChild(sprite);
// match the Sprite and ClippingNode
auto size = sprite->getBoundingBox().size;
clippingNode->setContentSize(size);
sprite->setPosition(Point(size.width/2, size.height/2));
// respond to touches by drawing on the DrawNode
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = [drawNode](Touch* touch, Event* event){
Vec2 p = touch->getLocation();
drawNode->drawDot(p, 20, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
return true;
};
touchListener->onTouchMoved = [drawNode](Touch* touch, Event* event){
Vec2 p = touch->getLocation();
drawNode->drawDot(p, 20, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
I typed this out, so hopefully there are no mistakes, but it should be easy enough to figure out and fix if there are any. Here’s an explanation of what is happening:
The DrawNode is provided to the ClippingNode as a mask. What this means is that only the parts of the ClippingNode’s children that are under the filled in parts of the DrawNode will be shown. So, if the ClippingNode contained a sprite and I drew a circle on the DrawNode, you would only see the part of the sprite that was within the area of that circle.
Also, I was lazy in the onTouchBegan method. At the start of the function, you should test if the touch lies within the bounds of the ClippingNode and return false if it doesn’t. That means you need to adjust the function to have access to the ClippingNode (to get the area you can touch) and the DrawNode (so that you can draw on it.)
Create a RenderTexture and add it to your scene. Create a DrawNode and the hidden Sprite and retain them but don’t add them to the scene. Then you can either:
a. Apply a blending function of dst=GL_ZERO and src=GL_DST_ALPHA to sprite, or
b. Apply a blending function of dst=GL_ONE_MINUS_SRC_ALPHA and src=GL_ZERO to the DrawNode, and dst=GL_ONE and src=GL_ONE_MINUS_DST_ALPHA to the sprite.
Then whenever you draw on the DrawNode, update the RenderTexture by using
For A: renderTexture->beginWithClear(0, 0, 0, 0);
For B: renderTexture->beginWithClear(0, 0, 0, 1.0f);
drawNode->visit();
sprite->visit();
renderTexture->end();
The difference between A and B is:
A will draw the sprite wherever there is colour. The colour is provided by the DrawNode by drawing on it. This method means that the RenderTexture cannot have any colour on it except where you want the sprite drawn. It must be cleared to black with an alpha of 0 before drawing on it.
B will draw the sprite wherever there is no colour, i.e. zero alpha value. The DrawNode with its blending function works by “cutting out” the colour from the RenderTexture before the sprite fills in these parts. This requires the RenderTexture to be cleared to a colour with an alpha value of 1.0f or to have another image rendered on the RenderTexture before the DrawNode is visited.
If you do use B, you can actually use an alpha value less than 1.0f and you will see the sprite underneath. So if you use a clear of black with an alpha of something like 0.8f, you will faintly see the sprite underneath. This looks a little like you are cleaning a dirty surface.
Note: I believe the RenderTextures treat themselves as being down the bottom left of the screen when drawing onto them. What this means is that if you touch the bottom left of the screen, it will draw on the bottom left of the RenderTexture, even if you have moved the RenderTexture to the other side of the screen. This means that when you get the position of a touch, you need to adjust it to pretend your RenderTexture is down the bottom-left of the screen. So, I think that’s probably taking away half the width and height of the RenderTexture from the actual position of the touch.
Here is some rough code, built using Cocos2d-x v3.3beta0:
Header File (HelloWorldScene.h):
class HelloWorld : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);
private:
// the "hidden" sprite to slowly reveal
cocos2d::Sprite* spr;
// draw node defines where the sprite is revealed
cocos2d::DrawNode* dn;
// the render texture used to combine the draw node and the sprite
cocos2d::RenderTexture* rt;
// updates the render texture, applying the latest version of the draw node
void updateRT();
};
Source file (HelloWorldScene.cpp):
#include "HelloWorldScene.h"
USING_NS_CC;
Scene* HelloWorld::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = HelloWorld::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
// on "init" you need to initialize your instance
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size winSize = Director::getInstance()->getWinSize();
/* Add necessary nodes */
// the node which defines where the to draw the hidden image
dn = DrawNode::create();
dn->retain();
// the image to reveal
spr = Sprite::create("HelloWorld.png");
spr->retain();
// fill screen
spr->setScale(winSize.width / spr->getContentSize().width,
winSize.height / spr->getContentSize().height);
// center it on screen
spr->setPosition(Point(spr->getBoundingBox().size.width / 2,
spr->getBoundingBox().size.height / 2));
// create render texture
auto sprBB = spr->getBoundingBox().size;
rt = RenderTexture::create(sprBB.width, sprBB.height);
this->addChild(rt);
rt->setPosition(Point(sprBB.width / 2, sprBB.height / 2));
/* Set up blending functions (Method B) */
BlendFunc bf;
// apply blending function to draw node
bf.dst = GL_ONE_MINUS_SRC_ALPHA;
bf.src = GL_ZERO;
dn->setBlendFunc(bf);
// apply blending function to sprite
bf.src = GL_ONE_MINUS_DST_ALPHA;
bf.dst = GL_ONE;
spr->setBlendFunc(bf);
/* Handle touches */
auto el = EventListenerTouchOneByOne::create();
// apply initial touch to drawing node
el->onTouchBegan = [=](Touch* touch, Event* event){
Vec2 p = touch->getLocation();
dn->drawDot(p, 20, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
this->updateRT();
return true;
};
// apply touch's new location to drawing node
el->onTouchMoved = [=](Touch* touch, Event* event){
Vec2 p = touch->getLocation();
dn->drawDot(p, 20, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
this->updateRT();
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(el, this);
// set the render texture to its initial state
updateRT();
return true;
}
// clears the render texture, cuts out the parts where the drawing node has been
// coloured and then draws the sprite where in the cut out areas
void HelloWorld::updateRT() {
rt->beginWithClear(0.0f, 0.0f, 0.0f, 1.0f);
dn->visit();
spr->visit();
rt->end();
}
This uses method B and it works for me. It should create a black screen that slowly reveals the image as it is touched. I tested this as an OSX app.
/* Set up blending functions (Method B) */
BlendFunc bf;
// apply blending function to draw node
bf.dst = GL_ONE_MINUS_SRC_ALPHA;
bf.src = GL_ZERO;
dn->setBlendFunc(bf);
// apply blending function to sprite
bf.src = GL_ONE_MINUS_DST_ALPHA;
bf.dst = GL_ONE;
spr->setBlendFunc(bf);
to
/* Set up blending functions (Method A) */
BlendFunc bf;
// apply blending function to sprite
bf.src = GL_DST_ALPHA;
bf.dst = GL_ZERO;
spr->setBlendFunc(bf);
This happens if the RenderTexture is larger than the sprite and if the sprite isn’t centered. Based on your image, you need to either stretch the image to the same size as the screen, or shrink the RenderTexture to match the size of the sprite.
FYI If you shrink the RenderTexture and position it away from the bottom-left of the screen, you will need to adjust the location of your touches to account for this, because the RenderTexture seems to think it’s down the bottom-left when you draw on it, no matter where on screen it actually is.