Design Patterns in Cocos2d-x

Hello, Habr! I present to you the translation of the article " Design Patterns in Cocos2d-x " by Aleksei Pinchuk.

This article will be interesting for Cocos2d-x developers and those who study patterns. It is made in the form of a brief summary, in which you can quickly see where a particular pattern is used in Cocos2d-x. The purpose of this article is not to fully describe each pattern.

Generative patterns


Prototype


Prototype defines the interface for copying an object. Creating a new object occurs by copying the state of the object. For example, if we call clone () on an Animation object, we will create an Animation object with exactly the same parameters.

image

Singleton


Yes laaaadno.

Structural patterns


Flyweight


Flyweight should be used for separate use of the same resources without creating a large number of instances of the same resource. This makes it possible to efficiently use memory. This pattern is good for displaying text.

To display text, the Label class is used. To display the text we need a font. To get a TTF font, you need to use the getFontAtlasTTF method of the FontAtlasCache class. FontAtlasCache is the pool of all fonts used in the application. The font is stored until at least one object uses it. If we have 10 Label objects with the same font, then all of them will use the same instance of the FontAtlas class.

image

FontAtlas contains data for displaying the symbol - textures and coordinates. The size of the texture is 512x512 and several such textures can be created if all the characters do not fit into one. Only those characters are created that are used by objects referencing this font. Label gets the FontLetterDefinition structure for each character from FontAtlas. Based on the data from this structure, Label creates a sprite and places it in its BatchNode. In general, we can see how carefully Label is optimized.

image

Another good example of sharing resources is using texture sprites. The texture for each downloaded file is stored in a single copy. Several sprites created from the same file, I will refer to the same texture instance.

image

We can find some regularity - if the class name ends in Cache, then it produces objects for separate use.

Bridge


Bridge separates abstraction and implementation. This pattern is most often used to implement cross-platform objects. Classic Bridge can be found in EditBox and Downloader.

image

image

Also, this pattern is well suited for GLView, AudioEngine, Controller, and WebView. However, in them we can see different ways of implementing the same task - the separation of abstraction from implementation.

Composite


The goal of Composite is to build trees and unify access to tree components. Typically, a Composite scheme looks like this:
Composite is a composite object consisting of several objects inherited from Component. Thus, we can perceive several objects as one. In Cocos2d-x, Node is both Composite and Component.

image

Thanks to Node, a scene graph is created for each node of which Node is a node.

Behavior patterns


Command


Command encapsulates the request as an object, thereby allowing queuing of requests. Other use cases will not be considered. In Cocos2d-x, this pattern is used to create a queue for Renderer. Thanks to this pattern, the use of the OpenGL API inside objects has been removed. The same code for OpenGL is output in one command and is not copied several times. It can also facilitate the transition to other graphical APIs, but it will not make it easy.

image

Observer


Defines a one-to-many relationship between objects. In Cocos2d-x, the subject is an EventDispatcher, and the observer is an EventListener. EventDispatcher is not a singleton, we can inherit our EventDispatcher from it. Director through EventDispatcher notifies observers about changes in the state of the accelerometer, mouse, keyboard, etc. You can also create custom EventCustom messages. These messages have a name and data that must be transmitted to the observer. This is an alternative for NotificationCenter which is already marked as deprecated. Director also defines several useful EventCustom, for example, EVENT_BEFORE_UPDATE, EVENT_AFTER_UPDATE and others. EVENT_BEFORE_UPDATE is convenient to use when working with Box2D. For example, before updating the physical world, change linearVelocity to some object.

image

Decoupling patterns


Component


I think every game developer knows about Component-Based Architecture. Unity3d programmers should know for sure. Such a system reduces the connectivity of components and allows you to add components to the entity in such a way that the entity will not even know about it. This system is in Cocos2d-x. Each Node has a container for components. You must inherit your components from the Component class. Each component has a link to the Node that it owns (owner), the update method, as well as the serialize method for processing messages between components and other objects.

image

Optimization patterns


Data locality


The purpose of this pattern is to speed up access to memory by using more convenient placement of data 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. Almost every member of ParticleData is an array. For example, the posx array stores the coordinates of the particles along the x axis.

class ParticleData
{
public:
    float* posx;
    float* posy;
	//...
}; 

Placing data in memory sequentially, in the order they are processed, allows you to quickly process it and avoid cache misses as much as possible.

Dirty flag


It is quite common in Sosos2d-x. This pattern delays the execution of slow work until its result is required. It can be found in the classes Scene, Camera, Node, Label, ParticleSystem, Sprite and many others.