图片/按钮变灰变暗变黑白是游戏UI常用的操作
之前项目使用多套资源实现
如一个按钮对应3张图,普通/选中/禁用状态
然后使用下列方法切换
setEnabled/setBright
selected/unselected
然后如果对象不是按钮而是一般的sprite
或包含sprite的node
并没有以上的方法可以调用
同时也出于减少资源的考虑
新项目依靠shader实现显示效果切换
程序原理如下
CCNode有以下3个接口
(1,2)setGLProgram/setShaderProgram
(3)setGLProgramState
接口本质都是设置CCNode的成员变量_glProgramState
从而实现显示效果的切换(包括黑白/彩色切换等等)
前两个接口一样,通过GLProgram获取state,是设置shader的主要方法
第三个接口更直接一些直接设置state
通过GLProgram设置shader更方便
因为cocos预设了许多可以直接用的效果
这些效果定义在CCGLProgram.h
通过CCGLProgramCache可以使用这些效果
比如我们代码里用到的(变为正常彩色)
auto programNormal = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP);
若需要自行设置shader(变为黑白灰色)
auto programGray = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert,
ccUIGrayScale_frag);
实际上从CCGLProgramCache获取的GLProgram就是通过
createWithByteArrays/initWithByteArrays预先加载好的
再进一步看其中的两个参数
ccPositionTextureColor_noMVP_vert
ccUIGrayScale_frag
在目录cocos\renderer中我们可以看到
ccShader_PositionTextureColor_noMVP.vert
打开文件可以看到定义如下
const char* ccPositionTextureColor_noMVP_vert = STRINGIFY( attribute vec4 a_position; attribute vec2 a_texCoord; attribute vec4 a_color; \n#ifdef GL_ES\n varying lowp vec4 v_fragmentColor; varying mediump vec2 v_texCoord; \n#else\n varying vec4 v_fragmentColor; varying vec2 v_texCoord; \n#endif\n void main() { gl_Position = CC_PMatrix * a_position; v_fragmentColor = a_color; v_texCoord = a_texCoord; } );
另外,在目录cocos\ui\shaders中可以看到
ccShader_grayscale.frag
const char* ccUIGrayScale_frag = STRINGIFY( \n#ifdef GL_ES\n \nprecision mediump float;\n \n#endif\n \n\n \nvarying vec4 v_fragmentColor;\n \nvarying vec2 v_texCoord;\n \n\n \nvoid main(void)\n \n{\n \nvec4 c = texture2D(CC_Texture0, v_texCoord);\n \ngl_FragColor.xyz = vec3(0.2126*c.r + 0.7152*c.g + 0.0722*c.b);\n \ngl_FragColor.w = c.w;\n \n}\n );
具体的代码由glsg编写在外部
也可以自己手写在游戏内部
原理就是这样
具体的变灰操作在cocos2d-x3.7中更加方便
新加入了GLProgram ShaderUIGrayScale
可以直接从Cache中get
非常有趣的是
UIScale9Sprite定义了setState方法切换元素的彩色/灰色
我们的代码基本都是从那里学的
但不知道为什么cocos只在UIScale9Sprite中写了这个接口
CCNode中却没有这个接口
不过看过本文,现在变灰已经不是问题了
shader非常强大,变灰只是其中的一个功能
更多的还需要继续不断学习!
以下代码使用cocos2d-x3.5
点击按钮调用clickCallback切换 彩色/灰色
#include "HelloWorldScene.h" #include "../cocos/ui/shaders/UIShaders.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; } HelloWorld::HelloWorld() :m_pTitle(nullptr), m_pGround(nullptr), m_pTest(nullptr), m_pButton(nullptr) {} // on "init" you need to initialize your instance bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ) { return false; } Size visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); ///////////////////////////// // 2. add a menu item with "X" image, which is clicked to quit the program // you may modify it. // add a "close" icon to exit the progress. it's an autorelease object auto closeItem = MenuItemImage::create( "CloseNormal.png", "CloseSelected.png", CC_CALLBACK_1(HelloWorld::menuCloseCallback, this)); closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 , origin.y + closeItem->getContentSize().height/2)); m_pButton = MenuItemImage::create( "normal.png", "selected.png", CC_CALLBACK_1(HelloWorld::clickCallback, this)); m_pButton->setPosition(Vec2(origin.x + m_pButton->getContentSize().width / 2, origin.y + m_pButton->getContentSize().height / 2)); // create menu, it's an autorelease object auto menu = Menu::create(closeItem, m_pButton, NULL); menu->setPosition(Vec2::ZERO); this->addChild(menu, 1); ///////////////////////////// // 3. add your codes below... // add a label shows "Hello World" // create and initialize a label m_pTitle = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24); // position the label on the center of the screen m_pTitle->setPosition(Vec2(origin.x + visibleSize.width / 2, visibleSize.height / 2 + origin.y)); // add the label as a child to this layer this->addChild(m_pTitle); // add "HelloWorld" splash screen" m_pGround = Sprite::create("Test.jpg"); // position the sprite on the center of the screen m_pGround->setPosition(Vec2(visibleSize.width / 3 + origin.x, visibleSize.height / 2 + origin.y)); // add the sprite as a child to this layer this->addChild(m_pGround); m_pTest = Sprite::create("Test.jpg"); m_pTest->setPosition(Vec2(visibleSize.width*2 / 3 + origin.x, visibleSize.height / 2 + origin.y)); this->addChild(m_pTest); return true; } void HelloWorld::clickCallback(Ref* pSender) { static bool isGray = false; isGray = !isGray; auto programGray = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, ccUIGrayScale_frag); auto programNormal = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP); m_pTest->setGLProgram(isGray ? programGray : programNormal); } void HelloWorld::menuCloseCallback(Ref* pSender) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert"); return; #endif Director::getInstance()->end(); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) exit(0); #endif }