Design Patterns in Cocos2d-x
Introduction
The article will be interesting for Cocos2d-x developers and those who study patterns. It is a short review, in which you can quickly see where a particular pattern is applied in Cocos2d-x. The full description of each pattern is not the purpose of this article.
Creational Patterns
Prototype
Prototype defines the interface for copying an object. A new object is created by copying the state of the object. For example, if we call clone()
on an Animation object, we'll create an Animation object with the same parameters.
Singleton
Come on.
Structural Patterns
Flyweight
Flyweight should be used for the separate use of the same resources without creating a large number of instances of the same resource. This makes it possible to effectively use memory. This pattern is well suited for displaying text.
To display text, use the Label
class. To display the text we need a font. To get a TTF font, use the getFontAtlasTTF
method of the FontAtlasCache
class. FontAtlasCache
is a pool of all fonts used in the application. The font is stored as long as at least one object uses it. If we have 10 Label objects with the same font, they all use the same instance of the FontAtlas
class.
FontAtlas
contains the data to display the symbol - the texture and coordinates. The texture size is typically 512x512 and some textures may be more than that if all the symbols do not fit into one. The character is created if the same object used it. Label receives the FontLetterDefinition
structure for each character from FontAtlas
. Based on the data from this structure, Label
creates a sprite and puts it in its BatchNode
. In general, we can see how thoroughly the Label
is optimized.
Another good example of the separate use of resources is the use of textures by sprites. The texture for each downloaded file is stored in a single copy. Several sprites created from the same file will refer to a single instance of the texture.
We can find some regularity - if the class name ends with Cache, then it produces objects for separate use.
Bridge
Bridge decouples an abstraction from its implementation. This pattern is most often used to implement cross-platform objects. A Classic Bridge can be found in EditBox
and Downloader
.
Also, this pattern is well suited for GLView, AudioEngine, Controller, and WebView. However, these objects have different ways of implementing the same task - decoupling an abstraction from its implementation.
Composite
The task of Composite
is to build trees and unify access to tree components. Usually, the Composite
scheme looks like this:
Composite
is a compound object consisting of several objects inherited from the Component. Thus, we can perceive several objects as one. In Cocos2d-x, Node is both a Composite and a Component.
The graph-scene is created using Node in which each of its nodes is a Node.
Behavioral Patterns
Command
Command
encapsulates the request as an object, thereby letting request to be queued. Other use cases will not be considered. In Cocos2d-x, this pattern is used to create a queue for the Renderer. Thanks to this pattern, the use of the OpenGL API inside the objects was removed. The same code for OpenGL is placed in the same command and is not copied several times. It can also facilitate the transition to other graphical APIs, but it will not make it easy.
Observer
This pattern defines a one-to-many dependency between objects. In Cocos2d-x, the subject is the EventDispatcher
, and the observer is the EventListener
. EventDispatcher
is not a singleton, we can inherit from it our EventDispatcher
. A director via EventDispatcher
notifies observers about changes in the state of the accelerometer, mouse, keyboard, etc. You can also create custom events (EventCustom). These events have a name and data to be sent to the observer. This is an alternative to NotificationCenter which is already marked as deprecated. Director also defines several useful EventCustoms, for example, EVENT_BEFORE_UPDATE, EVENT_AFTER_UPDATE, and others. EVENT_BEFORE_UPDATE is useful for working with Box2D. For example, before updating the physical world, change the linearVelocity
to any object.
Decoupling Patterns
Component
I think every game developer knows about the Component-Based Architecture. Unity3d programmers should definitely know. This system reduces the coupling of components and allows you to add components to the entity so that the entity does not even know about it. Cocos2d-x has its system. Each Node has a container for components. You must inherit your components from the Component
class. Each component has a reference to the Node that it owns (owner), the update method, and the serialize method for processing messages between components and other objects.
Optimization Patterns
Data Locality
The task of this pattern is to speed up memory access by using more convenient data placement for caching by the processor. This pattern is often used to create particles. And in Cocos2d-x it is also used to create particles. In ParticleSystem
, all particle data is stored in ParticleData
. ParticleData
contains data for all particles. Every member of ParticleData
is an array. For example, the particle coordinates along the X-axis are stored in the posx
array.
class ParticleData
{
public:
float* posx;
float* posy;
//...
};
This is placing the data in memory sequentially, in the order of their processing, allowing them to be processed quickly and avoid cache misses as much as possible.
Dirty Flag
It is quite often found in Cocos2d-x. This pattern defers some slow work until the result is actually needed. It can be found in the classes Scene, Camera, Node, Label, ParticleSystem, Sprite, and many others.