This paper mainly introduces the use of webGL, cocos2D family history, and analyzes cocos2D-JS source code about resource loading, rendering principle, collision and animation use

Background knowledge

Introduce WebGL

WebGL is a set of Javascirpt API for drawing 2D 3D graphics supported by browsers. The bottom layer is based on OpenGL, and OpenGL is a graphics interface at hardware driver level (similar to DirectX). Therefore, WebGL can achieve 2D 3D graphics rendering at hardware level. Performance is generally better than Canvas rendering

WebGL rendering process:

  1. Access to elements
let canvas = document.getElementById("canvas");
Copy the code
  1. Request a drawing context from the Canvas element
let gl = canvas.getContext("webgl");
Copy the code
  1. Call the corresponding drawing function in context

    1. Create shader program
// Vertex shader
let VSAHDER_SOURCE = 'void main() {gl_Position, gl_PointSize}'
// Chip shader
let FSAHDER_SOURCE = 'void main() {gl_FragColor}'
Copy the code
  1. Initialize the shader, compile, and check that it compiled successfully
  2. Binds the shader to the specified program to determine whether the association is successful
if(! initShaders(gl, VSAHDER_SOURCE, FSAHDER_SOURCE )){return}
Copy the code
  1. Execute shader program to process vertices and tiles
gl.drawSomething(gl.POINTS, 0.1);
Copy the code

The game engine

What is a game engine? Why do we need a game engine? To put it simply, it helps developers to achieve rapid development:

  • Not only about what the user sees, but also about rendering in real time with frequent user interactions
  • Provides a complete flow from data traversal, collection to rendering execution
  • cross-platform

Introduction to Cocos2D-JS

**** comparison of the Cocos family

cocos2d-js

  • Is the JS implementation of the Cocos2D-X game engine
  • Cocos2d-js is cross-platform via cocos2D HTML5 and cocos2dx jsbinding

Cocos2d – js architecture diagram

cocos2d cocos2dx cocos2d-js cocos creator
Development of language objective-c c++ JS, JSB, HTML5 (Is a development tool)
scaling Inherits the node Inherits the node Inherits the node To the node
Apply colours to a drawing OpenGL WebGL WebGL/Canvas WebGL/Canvas
Cross-platform no is is is

Environment configuration

  1. Install python2.x + JDK
  2. Download cocos2dx, unzip it, run the setup.py file in the directory (NDK ROOT is required if you want to create a mobile application), and then follow the instructions in the source configuration file for the cocos variable to take effect
  3. Use webstorm’s own server or Xampp to launch a local service to open index.html for game preview
  4. Initialize a new cocos project command
cocos new <project-name> -l js -d <path>
Copy the code
  1. Package web-side executable applications
cocos compile -p web -m release 
Copy the code

basis

The core code

Coco2dx-3.17.2 /web: Cocos2D: cocos2D: cocos2D: cocos2D: cocos2D: cocos2D: cocos2D: cocos2D: cocos2D: cocos2D: cocos2D

(PS: Most of the code related to game application in this article comes from the JS-test project of Cocos2D-JS)

It is important to have two JS files in the root directory

  • CCBoot: The main namespace where all of the engine’s core classes, methods, properties, and constants are defined
  • Jsb_apis: Provides API support for Native applications

hierarchy

Here’s an explanation

  1. Director: a Director who controls scene creation and switching and exists in singleton mode throughout the life cycle

  2. Scene

    1. Basic unit for scene switching, which can set different types of switching effects, titles and other basic rendering information
    2. Only one scene is allowed to render at the same stage. Here is a scene relationship chain. The appendChild method adds multiple layers to the scene

  1. Layer: Layer is the basic unit of scene composition, foreground background layered processing, accept events, Layer can add various elements, CCLayer defines several subclasses at the same time

    1. CCLayerColor: Implements the CCRGBAProtocol
    2. CCLayerGradient: Painting a gradient background
    3. CCLayerMultiplex: Manages multiple layers and switches between them
  2. You can use the Animation class to animate or pass in the action parameter to move or rotate. There are several ways to create a Sprite

    1. New cc.sprite (fileName) is generated from an image
    2. New cc.sprite (fileName, rect) is generated from an image and a rectangular area clipping
    3. New cc.sprite (srpiteFrame), created from frames in the global buffer class SpriteFrameCache

The life cycle

All of the elements mentioned above are subclasses of Node (Sprite, Layer, Menu, Label) and bind coordinates, scaling, rotation information, and other basic attributes needed for drawing

Node has its own life cycle to facilitate flow control

  • Ctor: constructor
  • OnEnter: calls into Node, where initialization is usually done
  • OnEnterDidTransitionFinish: into the Node and the transition animation at the end of the call
  • OnExit: Exits the Node call
  • OnExitDidTransitionStart: called when exiting Node and the transition animation starts
  • Cleanup: called when it is cleared

Based on using

Just like Tencent document defines different types of renderers in Canvas rendering, Cocos2DX also creates corresponding rendering objects according to the requirements of the scene. For example, the following Sprite objects are used to form 2D game characters, buildings and other units. You can set colors and play animations. Or we can build a lot of complex blending modes where the user just needs to pass in a few simple parameters (image, coordinates, action) to render a node element the way we want.

var LogicTest = ActionManagerTest.extend({
    title:function () {
        return "Logic test";
    },
    onEnter:function () {
        this._super();

        var grossini = new cc.Sprite(s_pathGrossini);
        this.addChild(grossini, 0.2);
        grossini.x = 200;
        grossini.y = 200;

        grossini.runAction(cc.sequence(
            cc.moveBy(1, cc.p(150.0)),
            cc.callFunc(this.onBugMe, this)));if ( autoTestEnabled ) {
            this._grossini = grossini; }},onBugMe:function (node) {
        node.stopAllActions(); 
        node.runAction(cc.scaleTo(2.2));
    },

    testDuration: 4.0.getExpectedResult:function() {
        var ret = [ {"scaleX":2."scaleY":2}];return JSON.stringify(ret);
    },
    
    getCurrentResult:function() {
        var ret = [ {"scaleX":this._grossini.scaleX, "scaleY":this._grossini.scaleY} ];
        return JSON.stringify(ret); }});Copy the code

Initialize the

Start the

First, ccboot. js and main.js files (program entry) are loaded by the Web through index.html. If it is Native, JSB. Js will be loaded through the Native code. Main.js overrides cc.game.onstart () to do view initialization

// Set the game to a fixed orientation
cc.view.setOrientation(cc.ORIENTATION_LANDSCAPE);
// Set the resolution policy size
cc.view.setDesignResolutionSize(800.450, cc.ResolutionPolicy.SHOW_ALL);
// Recalculate the size of the game interface after the browser size changes
cc.view.resizeWithBrowserSize(true);
Copy the code

Load the scene

cc.LoaderScene.preload(g_resources, function () {
        if(window.sideIndexBar && typeof sideIndexBar.start === 'function'){
            sideIndexBar.start();
        }else{
            var scene = new cc.Scene();
            //TestController is a Layer controller implemented manually
            // Is essentially a Layer
            scene.addChild(newTestController()); cc.director.runScene(scene); }},this);
Copy the code

Finally launch the game

cc.game.run();
Copy the code

Resource to load

Run () is implemented in ccboot.js. If you start the TestController Layer, you will find that the TestController Layer is undefined. This is because the project script file has not been loaded. CCBoot has two loadConfig and loadText methods to load and parse the project.json text, respectively.

{
    "debugMode"     : 1."showFPS"       : true."frameRate"     : 60."noCache"       : false."id"            : "gameCanvas"."renderMode"    : 0."engineDir"     : ".. /.. /web/"."modules"       : ["cocos2d"."extensions"."external"]."plugin": {
        "facebook": {
            "appId" : "1426774790893461"."xfbml" : true."version" : "v2.0"}},"jsList"        : [
        "src/BaseTestLayer/BaseTestLayer.js"."src/tests_resources.js"]},Copy the code

During CCBoot, the jsList file was loaded in the final prepare method

// Load game scripts
var jsList = config[CONFIG_KEY.jsList];
if (jsList) {
    cc.loader.loadJsWithImg(jsList, function (err) {
        if (err) throw new Error(err);
        self._prepared = true;
        if (cb) cb();
    });
}
else {
    if (cb) cb();
}
Copy the code

Add “SRC/testtests-main. js” to jsList to load the TestController. At the same time, it is observed that cc.loader is a singleton. All types of external resources can be registered to cc.loader through the mode of plug-in mechanism

  • Load Entry API for loading resources
  • LoadJS /loadJsWithImg Loads js files.
  • LoadXXX (Txt, Img, Binary, BinarySync) loads the XXX file
  • The register register loader
  • GetRes retrieves cached data
  • Release Releases cached data

CCLoader registered

cc.loader.register(["font"."eot"."ttf"."woff"."svg"."ttc"], cc._fontLoader);
Copy the code

The SRC directory contains the ressource.js resource configuration file. In cocos2dx V3, the configuration format is as follows (font is used as an example), and the loader type is font, and the resource cache key is Thonburi

var g_fonts = [
    //@face-font for WebFonts
    {
        type:"font".name:"Thonburi".srcs: [".. /cpp-tests/Resources/fonts/Thonburi.eot".".. /cpp-tests/Resources/fonts/Thonburi.ttf"]]},Copy the code

The scene and resource file bindings are bound in a handler

{
        title:"Font Test",
        resource:g_fonts,  
        platforms: PLATFORM_ALL,
        linksrc:"src/FontTest/FontTest.js", testScene:function () { return new FontTestScene(); }},Copy the code

Finally, load the related scene resources in the callback function of scene switch

cc.LoaderScene.preload(res, function () {
            var scene = testCase.testScene();
            if(scene) { scene.runThisTest(); }},this);
Copy the code

The image processing

Cocos mainly uses texture cache to process image resources. Generally, texture objects are cached in TextCache during the preload stage of scene switching. When Sprite objects are created, the program will request data from TextCache by default. The texture object calls the underlying drawing interface to complete the filling of pixel information

var tex = cc.textureCache.getTextureForKey(filename);
        if(! tex) { tex = cc.textureCache.addImage(filename); }Copy the code

Voice processing

  • Music: long playing time, usually used as background music
  • Sound effects: Temporary sound effects

Cocos2djs’ AudioEngine encapsulates music and sound effects and provides a variety of API call methods

An AudioId is returned when a sound file is played, and the sound can be processed each time using this ID

aId = audioEngine.playEffect("music/sound.mp3", _loop);
Copy the code

AudioEngine plays sound resources in two ways

  • WebAudio: Good compatibility, but too much cache usage
cc.Audio.WebAudio = function (buffer) {
    this.buffer = buffer;
}
Copy the code
  • DomAudio (auto-play-audio) : caches only elements, but has poor compatibility

Initialize the renderer process

Apply colours to a drawing

Rendering process

  1. Activation:

Execute _runMainLoop to start the main loop, and director to draw the scene. Before drawing the scene, all nodes will be looped through before rendering.

  1. Traversal and rearrangement

The rendering order of each node depends on three orders: priority globalZOrder>localZOrder>arrivalOrder. The higher the order value of the same priority, the later the rendering

  • GlobalZOrder: is set via Node::setGlobalZorder
  • LocalZOrder :set with Node::setLocalZorder
  • OrderOfArrival: Set when a node is added to a parent node, resulting from a global quantity summation

Before drawing, cocos will traverse all children nodes starting from ccScene, and the visit function is implemented as follows

Cocos2dx Cocos3.0 does not immediately render the calculated rendering data after executing visit() to collect data of each node. Instead, it first encapsulates the data with RenderCommand as the base class to generate the drawing command of openGL. Push into a CommandQuere after waiting after performing the render sequentially

rej::RenderCommand()
: _type(RenderCommand::Type::UNKNOWN_COMMAND)
, _globalOrder(0)
{
}
Copy the code

Each CMD saves the data required by webGL rendering. LabelTTFWebGLRenderCmd is shown as follows

The queue holds webGL drawing function commands for different types of base elements

  • ReorderChildDirty is set to true every time a new chLID or child is reordered
  • Nodes with localZOrder less than 0 are traversed first each time, followed by push of the current node, and finally nodes greater than 0 (middle order).

Cocos2dx-js actually uses the Node localZOrder to adjust the structure of the node tree. Nodes with localZOrder less than 0 are placed in the left subtree, and those greater than 0 are placed in the right subtree. The recursive method is adopted to conduct middle-order traversal of the nodes in the UI tree. First, the localZorder of the node is compared, and the arrivalOrder of the node is compared. In the same case, the arrivalOrder of the node is compared, and the node of the top is rendered first

  1. draw

Renderer. Render (cc._renderContext) starts loop rendering of all elements, passing the calculated node data to opeGL’s buffer object, and then performing batch rendering

The collision

Anchor points and coordinates

Cocos2dx uses the OpenGL coordinate system. The starting position (0, 0) is located at the lower left corner of the screen. The archorPoint anchor point is a two-dimensional coordinate, and the value is taken from 0 to 1

The node position is determined by position and Archor point. The default anchor point of Layer is (0,0), namely the lower left corner, and the default anchor point of Sprite is (0.5, 0.5), namely the center. The anchor point is actually the origin of the image. It can be understood that there is a picture anchor point (0.5,0.5), which is the center of the picture. We regard this point as the origin of the picture, and the picture starts from this point, and then combines it with position

Physics engine

Cocos2d integrates the open source physics engine with PhysicsSprite, a subclass of Sprite bound to a physical Body, Multiple physical engines cannot be started at the same time (only one of the preprocessing macros CC_ENABLE_CHIPMUNK_INTEGRATION, CC_ENABLE_CHIPMUNK_INTEGRATION, and CC_ENABLE_BOX2D_INTEGRATION can be defined) :

  • Chipmunk
  • Objective-Chipmunk
  • Box2d

When the cocos2D Sprite object is bound to the physics engine, parameters such as mass density and friction coefficient can be assigned to make the Sprite behave more like the real physical world

Here are some of the methods used in collision detection

Axisymmetric bounding box collision detection

The familiar collision detection method is rectangular collision detection

Cc.rect () to create a regular rectangle

cc.rect = function (x, y, w, h) {
  return {x: x, y: y, width: w, height: h };
};
Copy the code

The x and y here are the bottom left corner of the rectangle, which is origin, or minX, minY.

getBoundingBox(); Methods The range of collision objects to be detected was obtained, and then rectIntersectsRect() was selected. Method to judge whether there is overlap between the range of touch points and detection points, if there is collision

// Determine whether to intersect
cc.rectIntersectsRect = function (rect1, rect2) {
    var max1x = rect1.x + rect1.width,
        max1y = rect1.y + rect1.height,
        max2x = rect2.x + rect2.width,
        max2y = rect2.y + rect2.height;
    return! (max1x < rect2.x || max2x < rect1.x || max1y < rect2.y || max2y < rect1.y); };Copy the code

In the same principle, we can also judge whether a certain rectangular element is hit by obtaining position of a click. However, we can only detect regular graphics or objects here, if complex graphics are unfriendly

SAT collision detection

In addition to the irregular graphics outer package once again after boundingRect axisymmetric case detection, in order to improve accuracy, sometimes we need to test the irregular graphics, general testing convex polygon collision can use separation axis theorem, the two convex polygons, when there is a straight line, make two polygons projection don’t want to pay on the line, So these two polygons do not want to cross, this line is called the separation axis, so writing code generally consider: determine the projection axis, projection to the projection axis, determine the overlap

To find the projection axis is to find the axis on which the normal vectors of each side of the polygon are parallel

A simple way to calculate the projection of a polygon on a line is to calculate the projection of all vertices on a line and obtain coordinates with a minimum maximum value

// let p; / / p to the original point = k * x + y/b/projection axis x1 = (k * (p) [1] - b + p [0])/(k + 1) * k; y1=k*x1+b; M=[x1 y1] // Projection point coordinatesCopy the code

Finally, we compare the difference values of the corresponding coordinate values and judge whether there is projection intersection to obtain the collision detection results

Ball collision detection

function isCollision(a, b) {
    // To find the distance between the point and the center of the circle, we use the Pythagorean theorem
    var dis = Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    return dis < (a.r + b.r)
}
Copy the code

Spherical and rectangular collision detection

function isCollision(a, closePoint) {
    // To find the distance between the point and the center of the circle, we use the Pythagorean theorem
    var dis = Math.sqrt((a.x -closePoint.x) * (a.x -closePoint.x) + (a.y - closePoint.y) * (a.y - closePoint.y));
    return dis < a.r
}
Copy the code

quadtree

According to the above method, if every two objects need to calculate a collision, usually the performance overhead is very high. Therefore, the method of space partition is generally used to group different graphics to ensure that only the graphics objects in the group may have collisions, which greatly reduces the calculation times

This structure is called a quadtree, quadtree can along with the increase in number of image objects and deeper level, this recursive constructed tree will set up a hierarchy number or the number of architectural elements as the threshold value, to provide the termination conditions, if lead to three-dimensional space, and octree structure is constructed, in principle is similar

BVH surrounded by body

Sometimes what we need to calculate is not the collision between objects, but whether a line intersects a graph object at a certain moment, which is usually used in the case of bounding body (BVH). If you are familiar with clustering algorithm, you should know that if we have a two-dimensional space, first we randomly specify a center point, And then we compute the distance from the center of the object set to the center, and then we recalculate the center and after repeated iterations, we get n clusters.

In BVH, each cluster is a rectangular bounding body, which is regarded as a complete tree structure constructed by a single node according to certain rules

It has the following characteristics:

  • Leaf nodes represent graphic objects, and non-leaf nodes represent graphic objects
  • Number of leaf nodes = number of non-leaf nodes -1
  • The order of leaf nodes is optional

animation

Cocos2d-js animation is divided into frame by frame play and PLIST play (cut map frame play) two ways

Frame by frame to play

Cocos2d-js implements frame-by-frame playback by Animation. First, instantiate an Animation instance and a Sprite object, set the playback time, and call addSpriteFrameWithFile to add pictures of all frames and set the interval of each frame. RepeatForever encapsulates the action and assigns it to sprite.runAction().

Plist play

Plist playback is a large image that contains all the frames of the animation process. It is generally divided into different arrays according to the direction of the large image, and added to an AnimationCache instance to cache the animation. Take the corresponding frame animation in different directions and wrap it as an Action like a frame by frame animation and pass it to sprite.runAction()

reference

  1. Cocos2d-x Api documentation docs.cocos2d-x.org/api-ref/cpl…