Advanced Topics

Wow! You are on the last chapter. Good Job! By now you should feel comfortable creating your games with Cocos2d-x. However, please realize there is no limit to what you can create. This chapter covers advanced concepts. Note that this chapter gets more technical in its content and format.

File System Access

Even though you can use functions in stdio.h to access files it can be inconvenient for a few reasons: You need to invoke system specific API to get full path of a file. Resources are packed into .apk file on Android after installing. * You want to load a resource (such as a picture) based on resolution automatically.

The FileUtils class has been created to resolve these issues. FileUtils is a helper class to access files under the location of your Resources directory. This includes reading data from a file and checking file existence.

Functions to read file content

These functions will read different type of files and will return different data types:

function name return type support path type
getStringFromFile std::string relative path and absolute path
getDataFromFile cocos2d::Data relative path and absolute path
getFileDataFromZip unsigned char* absolute path
getValueMapFromFile cocos2d::ValueMap relative path and absolute path
getValueVectorFromFile std::string cocos2d::ValueVector

Functions to manage files or directories

These functions will manage a file or a directory:

function name support path type
isFileExist relative path and absolute path
isDirectoryExist relative path and absolute path
createDirectory absolute path
removeDirectory absolute path
removeFile absolute path
renameFile absolute path
getFileSize relative path and absolute path

Networking with HTTP

Sometimes it might be helpful to obtain resources or data from another source. One common way of doing this is by using an HTTP request.

HTTP networking has three steps: 1. Create an HttpRequest 2. Create a setResponseCallback() callback function for replying to requests. 3. Send HttpRequest by HttpClient

HttpRequest can have four types: POST, PUT, DELETE, UNKNOWN. Unless specified the default type is UNKNOWN. The HTTPClient object controls sending the request and receiving the data on a callback.

Working with an HTTPRequest is quite simple:

HttpRequest* request = new (std :: nothrow) HttpRequest();
request->setUrl("http://just-make-this-request-failed.com");
request->setRequestType(HttpRequest::Type::GET);
request->setResponseCallback(CC_CALLBACK_2 (HttpClientTest::onHttpRequestCompleted, this));

HttpClient::getInstance()->sendImmediate(request);

request->release();

Notice that we specified a setResponseCallback() method for when a response is received. By doing this we can look at the data returned and use it how we might need to. Again, this process is simple and we can do it with ease:

void HttpClientTest::onHttpRequestCompleted(HttpClient* sender, HttpResponse* response)
{
  if (!response)
  {
    return;
  }

  // Dump the data
  std::vector<char>* buffer = response->getResponseData();

  for (unsigned int i = 0; i <buffer-> size (); i ++)
  {
    log ("% c", (* buffer) [i]);
  }
}

Shaders and Materials

What is a Shader

From wikipedia:

In the field of computer graphics, a shader is a computer program that is used to do shading: the production of appropriate levels of color within an image, or, in the modern era, also to produce special effects or do video post-processing. A definition in layman's terms might be given as "a program that tells a computer how to draw something in a specific and unique way".

In other words, it is a piece of code that runs on the GPU (not CPU) to draw the different Cocos2d-x Nodes.

Cocos2d-x uses the OpenGL ES Shading Language v1.0 for the shaders. But describing the GLSL language is outside the scope of this document. In order to learn more about the language, please refer to: OpenGL ES Shading Language v1.0 Spec.

In Cocos2d-x, all Node objects that are renderable use shaders. As an example Sprite uses optimized shaders for 2d sprites, Sprite3D uses optimized shaders for 3d objects, and so on.

Customizing Shaders

Users can change the predefined shaders from any Cocos2d-x Node by calling:

sprite->setGLProgramState(programState);
sprite3d->setGLProgramState(programState);

The GLProgramState object contains two important things:

In case you are not familiar with the term uniform and why it is needed, please refer to the OpenGL Shading Language Specification

Setting uniforms to a GLProgramState is as easy as this:

glProgramState->setUniformFloat("u_progress", 0.9);
glProgramState->setUniformVec2("u_position", Vec2(x,y));
glProgramState->setUniformMat4("u_transform", matrix);

You can even set callbacks as a uniform value:

glProgramState->setUniformCallback("u_progress", [](GLProgram* glProgram, Uniform* uniform)
  {
      float random = CCRANDOM_0_1();
      glProgram->setUniformLocationWith1f(uniform->location, random);
  }
);

And although it is possible to set GLProgramState objects manually, an easier way to do it is by using Material objects.

What is a Material

Assume that you want to draw a sphere like this one:

The first thing that you have to do is to define its geometry, something like this:

...and then define the brick texture, like:

The answer is to use a Material instead of just a plain and simple texture. In fact, with Material you can have more than one texture, and much more features like multi-pass rendering.

Material objects are created from .material files, which contain the following information:

As an example, this is how a material file looks like:

// A "Material" file can contain one or more materials
material spaceship
{
    // A Material contains one or more Techniques.
    // In case more than one Technique is present, the first one will be the default one
    // A "Technique" describes how the material is going to be renderer
    // Techniques could:
    //  - define the render quality of the model: high quality, low quality, etc.
    //  - lit or unlit an object
    // etc...
    technique normal
    {
        // A technique can contain one or more passes
        // A "Pass" describes the "draws" that will be needed
        //   in order to achieve the desired technique
        // The 3 properties of the Passes are shader, renderState and sampler
        pass 0
        {
            // shader: responsible for the vertex and frag shaders, and its uniforms
            shader
            {
                vertexShader = Shaders3D/3d_position_tex.vert
                fragmentShader = Shaders3D/3d_color_tex.frag

                // uniforms, including samplers go here
                u_color = 0.9,0.8,0.7
                // sampler: the id is the uniform name
                sampler u_sampler0
                {
                    path = Sprite3DTest/boss.png
                    mipmap = true
                    wrapS = CLAMP
                    wrapT = CLAMP
                    minFilter = NEAREST_MIPMAP_LINEAR
                    magFilter = LINEAR
                }
            }
            // renderState: responsible for depth buffer, cullface, stencil, blending, etc.
            renderState
            {
                cullFace = true
                cullFaceSide = FRONT
                depthTest = true
            }
        }
    }
}

And this is how to set a Material to a Sprite3D:

Material* material = Material::createWithFilename("Materials/3d_effects.material");
sprite3d->setMaterial(material);

And if you want to change between different Techniques, you have to do:

material->setTechnique("normal");

Techniques

Since you can bind only one Material per Sprite3D, an additional feature is supported that's designed to make it quick and easy to change the way you render the parts at runtime. You can define multiple techniques by giving them different names. Each one can have a completely different rendering technique, and you can even change the technique being applied at runtime by using Material::setTechnique(const std::string& name). When a material is loaded, all the techniques are loaded ahead too. This is a practical way of handling different light combinations or having lower-quality rendering techniques, such as disabling bump mapping, when the object being rendered is far away from the camera.

Passes

A Technique can have one or more passes That is, multi-pass rendering. And each Pass has two main objects:

Material file format in detail

Material uses a file format optimized to create Material files. This file format is very similar to other existing Material file formats, like GamePlay3D's and OGRE3D's.

Notes:

// When the .material file contains one material
sprite3D->setMaterial("Materials/box.material");
// When the .material file contains multiple materials
sprite3D->setMaterial("Materials/circle.material#wood");
material material_id : parent_material_id    
{    
  renderState {} [0..1] block
  technique id {} [0..*] block
}    
technique technique_id    
{    
  renderState {} [0..1] block
  pass id {} [0..*] block
}    
pass pass_id    
{    
  renderState {} [0..1] block
  shader {} [0..1] block
}    
renderState    
{    
  blend = false [0..1] bool
  blendSrc = BLEND_ENUM [0..1] enum
  blendDst = BLEND_ENUM [0..1] enum
  cullFace = false [0..1] bool
  depthTest = false [0..1] bool
  depthWrite = false [0..1] bool
}    
  frontFace = CW | CCW [0..1] enum
  depthTest = false [0..1] bool
  depthWrite = false [0..1] bool
  depthFunc = FUNC_ENUM [0..1] enum
  stencilTest = false [0..1] bool
  stencilWrite = 4294967295 [0..1] uint
  stencilFunc = FUNC_ENUM [0..1] enum
  stencilFuncRef = 0 [0..1] int
  stencilFuncMask = 4294967295 [0..1] uint
  stencilOpSfail = STENCIL_OPERATION_ENUM [0..1] enum
  stencilOpDpfail = STENCIL_OPERATION_ENUM [0..1] enum
  stencilOpDppass = STENCIL_OPERATION_ENUM [0..1] enum
shadershader_id    
{    
  vertexShader = res/colored.vert [0..1] file path
  fragmentShader = res/colored.frag [0..1] file path
  defines = semicolon separated list [0..1] string
     
  uniform_name = scalar | vector [0..*] uniform
  uniform_name = AUTO_BIND_ENUM [0..*] enum
  sampler uniform_name {} [0..*] block
}    
sampler uniform_name    
{    
  path = res/wood.png | @wood [0..1] image path
  mipmap = bool [0..1] bool
  wrapS = REPEAT | CLAMP [0..1] enum
  wrapT = REPEAT | CLAMP [0..1] enum
 minFilter = TEXTURE_MIN_FILTER_ENUM [0..1] enum
  magFilter = TEXTURE_MAG_FILTER_ENUM [0..1] enum
}    

Enums:

TEXTURE_MIN_FILTER_ENUM  
NEAREST Lowest quality non-mipmapped
LINEAR Better quality non-mipmapped
NEAREST_MIPMAP_NEAREST Fast but low quality mipmapping
LINEAR_MIPMAP_NEAREST  
NEAREST_MIPMAP_LINEAR  
LINEAR_MIPMAP_LINEAR Best quality mipmapping
TEXTURE_MAG_FILTER_ENUM  
NEAREST Lowest quality
LINEAR Better quality
BLEND_ENUM  
ZERO ONE_MINUS_DST_ALPHA
ONE CONSTANT_ALPHA
SRC_ALPHA ONE_MINUS_CONSTANT_ALPHA
ONE_MINUS_SRC_ALPHA SRC_ALPHA_SATURATE
DST_ALPHA  
CULL_FACE_SIDE_ENUM
BACK Cull back-facing polygons.
FRONT Cull front-facing polygons.
FRONT_AND_BACK Cull front and back-facing polygons.
FUNC_ENUM
NEVER ALWAYS
LESS GREATER
EQUAL NOTEQUAL
LEQUAL GEQUAL
STENCIL_OPERATION_ENUM
KEEP REPLACE
ZERO INVERT
INCR DECR
INCR_WRAP DECR_WRAP

Types:

Predefined uniforms

The following are predefined uniforms used by Cocos2d-x that can be used in your shaders:

How to optimize the graphics performance of your Cocos2d-x games

Golden rules

Know the bottlenecks and optimize the bottlenecks.

When doing optimization, you should always stick to the _Pareto principle__ (also known as the 80–20 rule). In this case 80% of the performance issues will come from only 20% of the code.

Always use tools to profile the bottleneck, don't guess randomly.

There are many tools available for profiling the graphics performance. Even though you might be developing a game for Android, if you have a Mac with OS X, XCode could also be helpful to debugging. Debugging OpenGL ES With Xcode Profile Tools and the official Apple documentation are both good articles to read.

There are three major mobile GPU vendors now-a-days and they provide decent graphics profiling tools:

Use these tools when you suffer from graphics performance issues. Knowing the CPU/GPU family of your target device is important. Sometimes the performance issues only occur on certain kind of devices. You might find they share the same kind of GPU. It is important to to consider all aspects of the system when tracking down performance issues. Remember, graphic performance issues may also be caused by the __CPU__ and not the __GPU__.

CPU

The CPU is often limited by the number of draw calls and the heavy compute operations in your game loop Try to minimize the total draw calls of your game. We should use batch draw as much as possible. Cocos2d-x 3.x has auto batch support, but it needs some effort to make it work.

Also try avoid IO operations when players are playing your game. Try to preload your spritesheets, audios, TTF fonts etc.

Also don't do heavy compute operations in your game loop which means don't let the heavy operations called 60 times per frame.

Never!

The GPU is often limited by the overdraw(fillrate) and bandwidth.

If you are creating a 2D game and you don't write complex shaders, you might won't suffer GPU issues. But the overdraw problem still has trouble and it will slow your graphics performance with too much bandwidth consumption.

Though modern mobile GPU have TBDR(Tiled-based Defered Rendering) architecture, but only PowerVR's HSR(Hidden Surface Removal) could reduce the overdraw problem significantly. Other GPU vendors only implement a TBDR + early-z testing, it only reduce the overdraw problem when you submit your opaque geometry with the order(font to back). And Cocos2d-x always submit rendering commands ordered from back to front. Because in 2D, we might have many transparency images and only in this order the blending effect is correct.

Note: By using poly triangles, we could improve the fillrate. Please refer to this article for more information: https://www.codeandweb.com/texturepacker/tutorials/cocos2d-x-performance-optimization

But don't worry too much of this issue, it doesn't perform too bad in practice.

Simple checklist to make your Cocos2d-x game faster

  1. Always use batch drawing. Package sprite images in the same layer into a large atlas(Texture packer could help).
  2. As rule of thumb, try to keep your draw call below 50. In other words, try to minimize your draw call number.
  3. Prefer 16bit(RGBA4444+dithering) over raw 32bit(RGBA8888) textures.
  4. Use compressed textures: In iOS use PVRTC texture. In Android platform, use ETC1. but ETC1 doesn't has alpha, you might need to write a custom shader and provide a separate ETC1 image for the alpha channel.
  5. Don't use system font as your game score counter. It's slow. Try to use TTF or BMFont, BMfont is better.
  6. Try to preload audio and other game objects before usage.
  7. Use armeabi-v7a to build Android native code and it will enable neon instructors which is very fast.
  8. Bake the lighting rather than using the dynamic light.
  9. Avoid using complex pixel shaders.
  10. Avoid using discard and alpha test in your pixel shader, it will break the HSR(Hidden surface removal). Only use it when necessary.