I wanted to add a feature to the game whereby when an enemy or player ship gets hit and takes damage, there’s a visual cue for the player. I had a look around how some other 2D shooters do it and decided that making the sprite flash white briefly, and showing some pieces getting “blasted off”, would look good.
To cut a long story short, I ended up using a custom OpenGL shader with Cocos2d-x to make it work. The finished effect looks like this:
Which Shader Do I Need?
Custom shaders are implemented in their own code files in Cocos2d-x (not sure if that’s true for OpenGL shaders in general). There are two breeds of shader:
- Vertex Shaders: Transform shapes into their on-screen coordinates
- Fragment Shaders: These dictate what the actual pixels look like, transformed from the input texture into what appears on screen
For my purposes, I simply need to make every pixel of a given sprite appear white, I’m not altering their positions. As such, I only needed to tinker with the fragment shader.
In Cocos2d-x you can assign a shader to a particular node, so the process should be as simple as:
- Write the shader
- Assign it to the node
- Restore the default shader a fraction of a second later
Writing The Fragment Shader
Now I knew I needed a fragment shader, I searched the web until I found a suitably simple example that I could alter. Here’s the finished version:
#ifdef GL_ES
precision mediump float;
#endif
// v_texCoord is the texture coord position, u_texture is our original texture
varying vec2 v_texCoord;
uniform sampler2D u_texture;
void main()
{ // get the "usual" colour - although we don't actually use it, could be useful for future dev
vec3 normalColor = texture2D(u_texture, v_texCoord).rgb;
// set the output pixel colour to white, but keep alpha value from original
gl_FragColor = vec4(1, 1, 1, texture2D(u_texture, v_texCoord).a);
}
Code language: PHP (php)
The code is straightforward; we simply set the output colour (named gl_FragColor as standard) to maximum RGB float values of 1.0, and keep the original texture’s alpha value (texture2D(u_texture, v_texCoord).a).
Make sure the shader file is saved into your resources directory. I named mine “flash.fsh”.
Using The Shader in Cocos2d-x
To use the shader, we need to create the shader object and assign to a node (the enemy being hit in this case).
Here’s the code I used to create the shader object, which I do just once in the main layer:
// initialise the damage effect shader
std::string contents = FileUtils::getInstance()->getStringFromFile("flash.fsh");
flashShader = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, contents.c_str());
flashShader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
flashShader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
flashShader->link();
CHECK_GL_ERROR_DEBUG();
flashShader->updateUniforms();
CHECK_GL_ERROR_DEBUG();
flashShader->retain();
Code language: PHP (php)
The shader is then passed as a parameter to the Ship class which will use it for the flash effect. The shader is applied, then a callback function is scheduled to revert to the original shader a fraction of a second later. Notice that we set the current node’s texture as the “u_texture” object referenced in the shader code itself:
void Ship::doDamageFlash(GLProgram* p)
{
// Here we set up which textures are associated with which shader variables
auto glProgramState = GLProgramState::getOrCreateWithGLProgram(p);
this->setGLProgramState(glProgramState);
glProgramState->setUniformTexture("u_texture", this->getTexture());
this->getGLProgram()->use();
// schedule function to reset shaders
this->scheduleOnce(schedule_selector(Ship::resetShaders), 0.05);
}
void Ship::resetShaders(float dt)
{
this->setGLProgram(ShaderCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP));
this->getGLProgram()->use();
}
Code language: PHP (php)
Using the shader we created named “flashShader” above, it is passed to the Ship class like so:
Code language: PHP (php)ship->doDamageFlash(flashShader);
Simple!