cocos之部分ui控件的使用

现在cocos有了各种编辑器,需要手写ui的时候不是很多了,但也因为如此,偶尔需要手写的时候,反倒会因为陌生写出各种问题出来,而且对这些ui的接口比较熟悉的话,也有利于日常使用,所以这里稍微总结下部分ui控件的接口和使用,方便以后再用的时候快速回想起来,细节最好还是看源代码。

scale9Sprite

  • jsbinding代码位于 bindings/auto/jsb_cocos2dx_ui_auto.cpp内
  • c++代码位于cocos2d-x/cocos/ui/UIScale9Sprite.cpp内
  • scale9的实现原理是将纹理根据设定的区域,生成9个sprite,然后batch渲染
  • 它可以跟sprite一样通过setSpriteFrame来切换纹理,但切换之后需要重新执行setPreferredSize。否则显示不正确

用的比较多的接口有:

1
2
3
4
5
6
7
8
createWithSpriteFrame(SpriteFrame* spriteFrame);
createWithSpriteFrame(SpriteFrame* spriteFrame, const Rect& capInsets);
create(const std::string& file);

//实际显示大小
setPreferredSize(const Size& preferedSize)
//设定缩放区域
setCapInsets(const Rect& capInsets)

EditBox

  • jsbinding代码位于 bindings/auto/jsb_cocos2dx_ui_auto.cpp内
  • c++代码位于cocos2d-x/cocos/ui/UIEditBox/UIEditBox.cpp内
  • 只需要注意它的各个状态的sprite都是scale9即可

使用较多的接口有:

1
2
3
4
5
6
7
create(const Size& size, Scale9Sprite* normalSprite, Scale9Sprite* pressedSprite = nullptr, Scale9Sprite* disabledSprite = nullptr);

setFontSize(int fontSize);
setFontColor(const Color3B& color);
setPlaceHolder(const char* pText); //提示字符串

getString(); //在C++接口是getText,注意名称不一样

Controlbutton

  • jsbinding代码位于 bindings/auto/jsb_cocos2dx_extension_auto.cpp内
  • c++代码位于cocos2d-x/extensions/GUI/CCControlExtension/CCControlButton.cpp内

使用较多的接口有

1
2
3
4
5
6
7
8
9
create(cocos2d::ui::Scale9Sprite* sprite)
create(const std::string& title, const std::string& fontName, float fontSize)

setPreferredSize(const Size& size)
setBackgroundSpriteForState(ui::Scale9Sprite* sprite, State state)
setEnabled(bool enabled)

//绑定事件
button.addTargetWithActionForControlEvents(this, this._onClickSend, cc.CONTROL_EVENT_TOUCH_UP_INSIDE);

scrollView

  • jsbinding代码位于 bindings/auto/jsb_cocos2dx_extension_auto.cpp内
  • c++代码位于cocos2d-x/extensions/GUI/CCScrollView/CCScrollView.cpp内
  • 回调jsbinding位于bindings/manual/extension/jsb_cocos2dx_extension_manual内
  • scrollView本身的大小是viewSize。它内部有一个container,当我们执行addChild时,实际上是被加到了这个container里,它的大小比viewSize要大,所以才可以滚动,设置它的大小是setContentSize。这是与其他node的setContentSize方法不一样的地方,需要注意。

使用较多的接口有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create()
create(Size size, Node* container/* = nullptr*/)

setContainer(Node * pContainer)
addChild(Node * child, int zOrder, int tag)

setDelegate()
setDirection(Direction eDirection)

setContentSize(const Size & size)
setViewSize(Size size)
setContentOffset(Vec2 offset, bool animated/* = false*/)

//回调
scrollViewDidScroll(ScrollView* view)

tableView

  • jsbinding代码位于 bindings/auto/jsb_cocos2dx_extension_auto.cpp内
  • c++代码位于cocos2d-x/extensions/GUI/CCScrollView/CCTableView.cpp内
  • 回调jsbinding位于bindings/manual/extension/jsb_cocos2dx_extension_manual内,在这里也额外定义了一个create方法,而没有放在上面的jsbinding里
  • js-bindings/bindings/script/extension/jsb_ext_create_apis.js定义了新的构造函数cc.TableView.prototype._ctor = function(dataSouurce, size, container),所以可以直接new
  • 在jsbinding的init函数里,以及create函数里,都会自动调用一次reloadData,所以在js层代码中,新建一个tableView是不需要手动调用reloadData的,而C++代码则需要。

使用较多的接口有

1
2
3
4
5
setDirection(Direction eDirection)
setVerticalFillOrder(VerticalFillOrder fillOrder)
reloadData()

//回调就不写了,基本上都会知道

其它

像checkbox,ControlSlider等用的不多的,可以临时去查一下。

cocos之使用ccb的一些坑

cocosBuilder是一款很老的编辑器了,所以如果还在使用的话,需要注意一些坑。

1.labelttf不是labelttf

通过查看源代码,以及查看成员方法,可以得出,ccb将LabelTTF解析成了Label,所以在ccb中创建的Labelttf,代码中只能调用Label的方法,像setFontFillColor等LabelTTF的方法都是不能使用的。

虽然Label上有一个方法setTextColor可以设置颜色,但如果使用,会发现最终显示的颜色与设定的颜色并不一致,它将你在ccb里给他设定的初始颜色做了混合,如果想要生效,那要么在ccb里设成白色,要么在代码中调setTextColor前,先调用setColor(cc.color.WHITE)

同样,setFontSize是LabelTTF上的方法,代码中不能使用,办法是使用Label上的setSystemFontSize方法来代替。当然,最彻底的解决办法就是修改ccb的解析代码,它位于editor-support/cocosbuilder/ccLabelTTFLoader.h中

2.一个ccb中最好不要有多个动画

我们一般调用

1
animationManager.runAnimationsForSequenceNamedTweenDuration();

来播放动画,这个方法一上来就会把现有的动画都停止掉,所以如果一个界面有多个动画,最好不要放在同一个ccb里,否则一旦需要同时播放的时候,正在播放中的动画就会被停止

3.owner最好不要重用,方法绑定在owner或者documentRoot上,在load之前必须先设置

cc.BuilderReader.load方法第二个参数为owner,它用于寻找ccb中设定的属性和方法。owner最好不要重用,否则会出现各种奇怪的现象。例如我们需要加载一个子ccb时,可以新建一个node作为owner来使用。

1
2
3
4
5
6
7
8
9
var delegate = new cc.Node();	//用来做ower
delegate.retain();
delegate._finishAction = function(){
node.removeFromParent();
delegate.release();
}

var node = cc.BuilderReader.load(name, delegate);
//owner必须继承自node

另外回调必须在调用load之前绑定好,这里_finishAction就是绑定的回调,虽然回调的时机在初始化以后,但如果不先设置,那么在解析的时候找不到接口,会导致绑定不成功

4.不能在tableCellTouched中移除界面

这个与ccb没有关系,顺便提醒一下。我们经常有点击后关闭界面的需求,在别的情况下可能不会出什么问题,但如果在tableView的tableCellTouched回调中移除,会发生崩溃。因为在这个回调之后,touchEnd其实还没有结束,还会继续处理scroll上的一些代码,如果移除掉了根节点,导致内存被释放,那后续的代码就崩溃了

cocos之jsbinding

jsbinding是cocos通过引擎内置的spiderMonkey实现javaScript和C++交互的方案。当我们需要自己实现c++和js的互相调用时,基本上可以拷贝cocos引擎内的代码,cocos基于spiderMonkey的API进行了一些封装,使用起来比较方便。这篇博客只涉及我对代码的一些理解,如果想了解spiderMonkey的更多细节可以查看官网

如果能尽量看明白jsbinding的相关代码,在碰到问题时可以更快的解决,比如很常见的invalid native object报错,通过看代码可以知道它是找不到与js对象绑定的c++对象,比如js代码报错说某个方法是undefined时,我们可以去jsbinding的实现代码里查看一下这个方法是否封装进去了,比如一些类或者方法和属性在c++层和js层的名称不一样等等。

js调用C++代码

我们在AppDelegate.cpp里可以看到很多cocos的注册函数,通过这些函数,将c++类注册到js的环境中,然后就可以在js代码里构造这个类的实例,调用它的方法。我们基本上照抄就可以实现一个,代码就不贴了

  • jsb_myclass_class->name 这个name就是在js代码中用来执行new时候的类名称
  • JS_SetProperty(cx, proto, “__is_ref”, JS::FalseHandleValue); 根据C++类是否继承自Ref,设为True或者False
  • 注册属性(静态属性和成员属性)和方法(静态方法和成员方法),按照类似的格式去实现即可
1
2
3
4
5
6
7
8
9
10
static JSPropertySpec properties[] = {
JS_PSGS("myProp", _js_get_myProp, _js_set_myProp, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PS_END
};

static JSFunctionSpec funcs[] = {
JS_FN("func", js_MyClass_func, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_FS_END
};

  • 我们在js代码中执行new MyClass时,就会调用C++层的 js_MyClass_constructor函数,在这里会新建一个c++对象,并新建一个js对象,然后建立映射关系,之后在互相调用的时候,才可以找得到彼此
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool js_MyClass_constructor(JSContext *cx, uint32_t argc, jsval *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
bool ok = true;
MyClass* cobj = new (std::nothrow) MyClass();

js_type_class_t *typeClass = js_get_type_from_native<MyClass>(cobj); //从_js_global_type_map里获取,是在下面jsb_register_class的时候存进去的

// link the native object with the javascript object

JS::RootedObject proto(cx, typeClass->proto.ref());
JS::RootedObject parent(cx, typeClass->parentProto.ref());
JS::RootedObject jsObj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent));
js_proxy_t* newproxy = jsb_new_proxy(cobj, jsObj);
JS::AddNamedObjectRoot(cx, &newproxy->obj, "MyClass");


args.rval().set(OBJECT_TO_JSVAL(jsObj));
if (JS_HasProperty(cx, jsObj, "_ctor", &ok) && ok)
ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(jsObj), "_ctor", args);
//如果js层实现了_ctor方法,则调用它
return true;
}
  • 然后看一个简单的绑定C++函数到js中的实现。在这里我们看到了很眼熟的”invalid native object”。我们首先获取到js中的this,然后通过映射找到c++中的对象,然后调用方法即可。如果绑定静态方法,因为不需要实例,所以非常简单,直接执行即可。获取当前的js对象,并以此获取绑定的c++对象,这些代码能看懂并使用就足够了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool js_MyClass_func(JSContext *cx, uint32_t argc, jsval *vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject jsobj(cx, args.thisv().toObjectOrNull());
js_proxy_t *proxy = jsb_get_js_proxy(jsobj);
MyClass* cobj = (MyClass *)(proxy ? proxy->ptr : NULL);
JSB_PRECONDITION2( cobj, cx, false, "Invalid Native Object");

if (cobj) {
cobj->func();
return true;
}else {
JS_ReportError(cx, "Error: SocketIO instance is invalid.");
return false;
}
}

C++调用js

前面代码能差不多明白的话,c++调用js的其实也很好写,这里贴一段例子代码,它就不再是从cocos引擎内拷贝的,而是我自己写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void MyClass::calljs() {
JSContext *cx = ScriptingCore::getInstance()->getGlobalContext();

js_proxy_t *proxy = jsb_get_native_proxy(this);
JSObject* jso = proxy->_jsobj;

std::string param = "test111";
jsval jsparam = std_string_to_jsval(cx, param);
JS::RootedValue retval(cx);
std::string ret = "";
std::string funcName = "calljs";

bool success = ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(jso), funcName.c_str(), 1, &jsparam, &retval);
if(success) {
if(retval.isString()) {
jsval_to_std_string(cx, retval, &ret);
CCLOG("return....%s " , ret.c_str());
}
}else {
CCLOG("excute failed ...");
}
}

我们在C++代码中调用这个函数,它会调用绑定的对象在js中的calljs函数,并可以传递参数和接受返回值。

tips

cocos封装了很多辅助函数,例如类型转换

1
2
3
4
5
jsval_to_std_string
std_string_to_jsval
OBJECT_TO_JSVAL
ccpoint_to_jsval
....

一般来说是够用了,需要的时候,多去引擎内部找找就可以了

另外,除了关注js-binding相关的代码外,cocos引擎在js层也有很多封装代码,位于js-bindings/bindings/script文件夹内。例如我们看tableView的jsbinding,它的构造方法是接受0个参数,但实际上我们可以这么写

1
var tableView = new cc.TableView(this, tsize);

原因就是在jsb_ext_create_apis.js内有这么一段

1
2
3
cc.TableView.prototype._ctor = function(dataSouurce, size, container) {
container == undefined ? this._init(dataSouurce, size) : this._init(dataSouurce, size, container);
};

它在js层实现了_ctor方法,而我们前面说到,在jsbinding的constructor函数里,如果发现js对象有_ctor方法,则会调用它,所以这个方法被调用,它执行了_init方法。

一个简单的联网棋牌游戏开发流程

最近负责一款新的牌类游戏的前端开发任务。这个项目从零开始到开发完成,稍微总结一下,棋牌类游戏属于比较简单的,项目使用MVC架构,一开始就可以规划好各个模块,我是划分成了

  • model MVC中的model
  • controller 兼具MVC中的controller和view
  • net 负责收和发网络消息
  • util 独立功能,例如constants, global, enum等
  • test 测试模块

MVC

controller兼具MVC中的controller和view的功能,每个controller基本对应了一个页面,并持有model作为成员变量,这样代码写起来简单,适用于这种不是很复杂的小型游戏。网络游戏的前端很多时候是由服务器消息驱动的,每次收到服务器消息,直接交给controller处理,controller将自身持有的model刷新,然后处理必要的逻辑,最后刷新UI。

项目流程

一个项目的开发流程,美术和后端先行,美术尽快出好素材后,前端就可以拼UI了,并且根据效果图和策划案,可以把各个功能模块都先写好,这样后端接口提供之后,前端在接入后端消息后,只需要一个个调用功能模块,给UI填入实际数据就可以了。至于前后端的通信协议,因为主要是由后端进行逻辑处理,所以我认为主动权在后端,除非前端认为不合理的时候,再进行沟通。Å

cocos源代码之GLProgram

GLProgram和GLProgramState

在cocos内操作shader,基本就是使用GLProgram和GLProgramState这两个类,他们的关系,在官方注释里写着

1
2
GLProgramState holds the 'state' (uniforms and attributes) of the GLProgram.
A GLProgram can be used by thousands of Nodes, but if different uniform values are going to be used, then each node will need its own GLProgramState

GLProgram是着色器的实现,GLProgramState是在Node上使用GLProgram的封装,所以我们在Node上应该操作GLProgramState,而不是GLProgram,例如设置uniform的值

GLProgram的创建

第一步是init,主要是完成顶点着色器和片段着色器编译

第二步是link

  • 调用bindPredefinedVertexAttribs,绑定一些默认的attribute,包括
1
2
3
4
5
6
7
const char* GLProgram::ATTRIBUTE_NAME_COLOR = "a_color";
const char* GLProgram::ATTRIBUTE_NAME_POSITION = "a_position";
const char* GLProgram::ATTRIBUTE_NAME_TEX_COORD = "a_texCoord";
const char* GLProgram::ATTRIBUTE_NAME_TEX_COORD1 = "a_texCoord1";
const char* GLProgram::ATTRIBUTE_NAME_TEX_COORD2 = "a_texCoord2";
const char* GLProgram::ATTRIBUTE_NAME_TEX_COORD3 = "a_texCoord3";
const char* GLProgram::ATTRIBUTE_NAME_NORMAL = "a_normal";

这样我们在顶点着色器内,就可以直接声明和使用这些变量名了,例如

1
2
attribute vec4 a_position;
attribute vec2 a_texCoord;

使用attribute修饰的变量,只能在顶点着色器中使用,且只能在顶点着色器中读取,不能赋值。

  • 调用glLinkProgram进行链接,如果链接成功,调用parseVertexAttribs保存实际用到了的attributes,以及parseUniforms保存自定义的uniforms
  • updateUniforms 将系统内置uniforms的使用情况存起来并更新

cocos之获取gzip压缩的http文本

当使用http请求文本内容时,如果使用gzip压缩,可以大大减少字符串长度,提高传输效率。要实现此功能,客户端需要写一些代码:

首先是请求时候,设定好http头

1
2
3
std::vector<std::string> headers;
headers.push_back("Accept-Encoding: gzip,deflate")
request->setHeaders(headers);

然后在处理收到的消息时解压缩一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::vector<char> *buffer = response->getResponseData();
std::ostringstream outBuff;
for (unsigned int i = 0; i < buffer->size(); i++){
outBuff << (*buffer)[i];
}
outBuff.flush();
std::string outstr = outBuff.str();
const unsigned char* tmp = (const unsigned char*)outstr.c_str();
size_t len = outstr.length();
unsigned char* outtmp = (unsigned char*)malloc(len);
ssize_t outlen = ZipUtils::inflateMemory((unsigned char*)tmp, len, &outtmp);
if(outlen > 0){
outstr = std::string((const char*)outtmp);
free(outtmp);
}else{
CCLOG("inflate gzip content failed....");
}

ZipUtils是cocos基于zlib的封装,还有一些别的接口,需要的时候可以去看看

【leetcode】Minimum Moves to Equal Array Elements

题目链接

看完题目之后,稍微想了下,觉得不能用递归或者动态规划,于是老老实实的按题目意思写,每次进行排序,除了最大的数字外都加1,直到所有数字相等。提交之后,直接在测试用例[1, 2147483647]超时挂了,于是开始思考怎么解决,因为这是个easy难度的题目,所以思路倒是很快就有了。

将数组排序后,要让所有数字都相同,第一步很容易想到,就是把除了最大的数字每次加1,直到最大的数字有两个,它需要的步数就是最大数字和第二大数字的差。然后顺着这个思路,再通过累加,把最大的数字变成有三个,这需要的步数如果一时无法总结出公式,可以写个例子很容易看出来,是最大数字和第二大数字之差的两倍,以此类推即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @param {number[]} nums
* @return {number}
*/
var minMoves = function(nums) {
var mul= 1;
nums.sort(function(left,right) {return left - right})
var len = nums.length;
if(len === 1) {
return 0;
}
var re = 0;
for(var i = len -1; i > 0; i--) {
re += (nums[i] - nums[i - 1]) * mul;
mul += 1;
}
return re;
};

cocos使用blendFunc实现擦除效果

效果图跟用clippingNode实现的那次差不多

img

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
bool HelloWorld::init() {
if ( !Layer::init()) {
return false;
}

auto winsize = Director::getInstance()->getWinSize();
auto width = winsize.width;
auto height = winsize.height;

auto s = Sprite::create("HelloWorld.png");
s->setPosition(width * 0.5, height * 0.5);
this->addChild(s);

mDrawNode = DrawNode::create();
this->addChild(mDrawNode);

auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = std::bind(&HelloWorld::onTouchBegan, this, std::placeholders::_1, std::placeholders::_2);
listener->onTouchMoved = std::bind(&HelloWorld::onTouchMoved, this, std::placeholders::_1, std::placeholders::_2);

EventDispatcher* ed = Director::getInstance()->getEventDispatcher();
ed->addEventListenerWithSceneGraphPriority(listener, this);

return true;
}

bool HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {
mTouchBeganPos = touch->getLocation();
return true;
}

void HelloWorld::onTouchMoved(cocos2d::Touch * touch, cocos2d::Event * event) {
Vec2 pos = touch->getLocation();
std::vector<cocos2d::Vec2> vec(2);
vec[0] = mTouchBeganPos;
vec[1] = pos;
mPoints.push_back(vec);
mTouchBeganPos = pos;
refresh();
}

void HelloWorld::refresh() {
mDrawNode->setBlendFunc(BlendFunc::DISABLE);
mDrawNode->clear();
mDrawNode->setLineWidth(10);
for(std::vector<std::vector<cocos2d::Vec2>>::iterator iter = mPoints.begin(); iter != mPoints.end(); iter++) {
std::vector<cocos2d::Vec2> vec = *iter;
mDrawNode->drawLine(vec[0], vec[1], Color4F(0,0,0,0));
}
}Render->end();
}

这次代码比上次的要简单一些,我们先加上背景图,然后再加上一个DrawNode来实现擦除,这个DrawNode设置BlendFunc为BlendFunc::DISABLE,也就是在它与背景混合的地方,只显示自己不显示背景,而他自己又是把运动轨迹绘制成透明的线段,所以就产生了背景图被擦除的效果。如果我们需要限制在局部范围内,只要处理好触摸区域或者添加到容器内即可

然后解释下blendFunc,它是用来处理颜色混合的,我们要将一个图片渲染到屏幕上时,需要将它的颜色(RGBA)与屏幕上已有的颜色混合,我们将要被渲染上去的称为源(src),屏幕上已有的称为目标(dst),最终颜色显示是它们和各自的因子相乘。如下所示

1
2
3
4
5
源颜色 Rs Gs Bs As
目标颜色 Rd Gd Bd Ad
源因子 Sr Sg Sb Sa
目标因子 Dr Dg Db Da
最终颜色 Rs * Sr + Rd * Dr Gs * Sg + Gd * Dg Bs * Sb + Bd * Db As * Sa + Ad * Da

cocos内置了一些值

1
2
3
4
5
6
7
8
9
10
11
12
13
GL_ZERO					//全部不用 (0,0,0,0)                           
GL_ONE //全部使用 (1,1,1,1)
GL_SRC_COLOR //使用源颜色(Rs, Gs, Bs, As)
GL_ONE_MINUS_SRC_COLOR //使用反源色(1-Rs, 1-Gs, 1-Bs, 1-As)
GL_SRC_ALPHA //使用源透明度 (As, As, As, As)
GL_ONE_MINUS_SRC_ALPHA //使用反源透明度 (1-As, 1-As, 1-As, 1-As)
GL_DST_ALPHA //使用目标透明度(Ad, Ad, Ad, Ad)
GL_ONE_MINUS_DST_ALPHA //使用反目标透明度(1-Ad, 1-Ad, 1-Ad, 1-Ad)

const BlendFunc BlendFunc::DISABLE = {GL_ONE, GL_ZERO};
const BlendFunc BlendFunc::ALPHA_PREMULTIPLIED = {GL_ONE, GL_ONE_MINUS_SRC_ALPHA};
const BlendFunc BlendFunc::ALPHA_NON_PREMULTIPLIED = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA};
const BlendFunc BlendFunc::ADDITIVE = {GL_SRC_ALPHA, GL_ONE};

这样就不难理解本例中我们使用的BlendFunc::DISABLE了,它意味着混合的时候,不取目标颜色,只显示源颜色。想一下sprite默认应该使用哪种呢?这取决于图片是否已经预乘了alpha,如果是则使用ALPHA_PREMULTIPLIED,否则使用ALPHA_NON_PREMULTIPLIED,很好理解。

cocos使用clippingNode实现擦除效果

先直接上模拟器上运行效果图
img

代码也不复杂,只要知道怎么用clippingNode即可,先放上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bool HelloWorld::init() {
if ( !Layer::init()) {
return false;
}

auto winsize = Director::getInstance()->getWinSize();
auto width = winsize.width;
auto height = winsize.height;

auto stencil = mDrawNode = DrawNode::create();

auto cnode = ClippingNode::create(stencil);
cnode->setContentSize(winsize);
cnode->setInverted(true);
auto s = Sprite::create("HelloWorld.png");
s->setPosition(width * 0.5,height * 0.5);
cnode->addChild(s);

this->addChild(cnode);

auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = std::bind(&HelloWorld::onTouchBegan, this, std::placeholders::_1, std::placeholders::_2);
listener->onTouchMoved = std::bind(&HelloWorld::onTouchMoved, this, std::placeholders::_1, std::placeholders::_2);

EventDispatcher* ed = Director::getInstance()->getEventDispatcher();
ed->addEventListenerWithSceneGraphPriority(listener, this);

return true;
}

bool HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {
mTouchBeganPos = touch->getLocation();
return true;
}

void HelloWorld::onTouchMoved(cocos2d::Touch * touch, cocos2d::Event * event) {
Vec2 pos = touch->getLocation();
std::vector<cocos2d::Vec2> vec(2);
vec[0] = mTouchBeganPos;
vec[1] = pos;
mPoints.push_back(vec);
mTouchBeganPos = pos;
refresh();
}

void HelloWorld::refresh() {
mDrawNode->clear();
mDrawNode->setLineWidth(10);
for(std::vector<std::vector<cocos2d::Vec2>>::iterator iter = mPoints.begin(); iter != mPoints.end(); iter++) {
std::vector<cocos2d::Vec2> vec = *iter;
mDrawNode->drawLine(vec[0], vec[1], Color4F::RED);
}
}

简单介绍一下clippingNode。它有一个stencil(可以是任何Node)用来决定切割区域,clippingNode的children都会被stencil切割,代码里我们的stencil是一个drawNode,需要擦除的图片HelloWorld.png添加为clippingNode的子节点。本来除了drawNode渲染的区域外都会被切割,我们通过setInverted接口设置后变成了相反,drawNode渲染的区域将被切割,被切割后我们就看到了图片下面的黑色背景,于是就呈现出擦除的效果。

使用setAlphaThreshold接口可以设置alpha阈值,当stencil中像素的alpha值大于这个阈值时,才会被绘制

svn之使用命令行做分支管理

使用svn不论是做分支(branch)还是标签(tag)都是使用svn cp命令,他们本质上没有区别,但用途上可能不同,这完全取决于开发者,例如一般trunk是作为开发目录,branch是为了作并行开发,而tag是作为milestone管理。

新建一个分支使用命令svn cp,他会增加一次提交

1
svn cp trunk-url tag-url -m "testtag"

然后就可以把这个分支co下来

1
svn checkout tag-url

svn使用的是全局版本号,分支之间是共享版本号的。例如我们在trunk下做一次修改并提交后版本是1063,此时我们到tag目录下执行svn up,会显示版本号也到了1063,但并不会把更新拉取下来。要将trunk下的更新拉取过来,需要使用svn merge命令

进入tag目录执行

1
svn merge trunk-url

就把trunk上的改动合并过来了,也可以使用-r参数指定将某两次提交之间的diff合并过来

从trunk上提交,然后到tag下去merge也是一样。

删除分支,使用

1
svn rm tag-url -m "remove tag"

最后稍微解释下svn cp和svn merge两个命令

svn cp

它的基本格式是:svn copy SRC DST,其中SRC和DST都可以是WC(working copy)或者URL

  • WC->WC 这个只是对本地文件的拷贝后执行了svn add
  • WC->URL 这个是拷贝后立即提交到了URL上,所以需要提供commit message
  • URL->WC 这个将URL上拷贝到本地后执行了svn add,可以用这个命令带上-r版本号来找回被删除的文件
  • URL->URL 这就是上面提到了创建分支了

svn merge

作用是应用两组源文件的差别到工作副本路径,基本格式为

1
2
3
4
5
svn merge sourceURL1[@N] sourceURL2[@M] [WCPATH]

svn merge sourceWCPATH1@N sourceWCPATH2@M [WCPATH]

svn merge [[-c M]... | [-r N:M]...] [SOURCE[@REV] [WCPATH]]

例如

1
2
svn merge http://xxxx/a.json -r 1063:1064
svn merge ../config/ -r 1063:1064

如果不指定初始和结束版本号,则默认为仓库起始和当前(HEAD)

最近刚刚发现一个问题,初始仓库trunk,使用svn cp生成release仓库,然后在trunk上增加一行代码,svn merge到release上去,在release上删除,然后svn merge到trunk上去。此时这行代码处不会提示有冲突,而是默认添加上了这行。我不太确定是否我的用法不对,所以暂时先记录下这个情况。在分支间来回merge时,要小心合并,尽量避免频繁merge吧,毕竟每次merge会有很多冲突