Some time ago, in order to promote a new public number, developed a named “shooting talent” (offline) small game, released after the launch of a good effect, successfully attracted a lot of fans for the public number. Here is to share with you the development process of this game.

Demand analysis

The game itself is actually very simple, just a shooting game, basketball projection, basket movement, basketball and basket impact, after the end of the game leaderboard record and public identification. But because the game is 2D and has a 3D effect (the ball gets farther and farther away from us and through the net), while collision detection and a smooth, natural rebound effect are required, the holes need to be filled constantly.

Technology selection

Considering the speed of development, it is very important to find a mature and well-documented HTML5 game development engine. In China, most people use Cocos2D-X-JS or Egret, Phaser or Hilo, which are lack of convenient development tools. Cocos performance on the web is not ideal. So I chose Egret 2D

As for the physics engine, Egret recommends P2 engine because its performance is better than Box 2D or mats.js. However, the disadvantage is the lack of Chinese documentation, which I am currently translating. The address is 👉 P2 Chinese documentation

Implementation approach

  • Pseudo 3 d movement

    1. Near big far small display

A visual focus is established with the initial position (or bottom) of the basketball as the origin, and the scaling ratio is calculated according to the position of the basketball on the Y-axis during the basketball movement. The calculation formula is as follows:

``scale = (curY - startY) / ( endY - startY); ` `Copy the code

Also, in the physical world, any rigid body is a physical model of constant shape and size, which means that we can make the basketball’s map scale, but we can’t make the basketball’s rigid body scale. For basketball and the basket hit, the size of the basketball is accurate, we can according to the above formula of scaling calculation basketball in the net (net location to determine the proportion must be fixed) at the time of the proportion, in the initial position to the map and rigid body size to keep the proportion of basketball, when basketball basket size happens to be the real size of the rigid body.

  1. The interaction between the basketball and the basket

    Because the ball and the basket are actually on the same plane in the 2D physical world when it rises, the rigid bodies of the ball and the basket will collide by default, and the ball needs to be larger than the basket on the way up and smaller than the basket on the way down.

    To solve the first problem, you can take advantage of collision grouping provided by P2 to avoid collisions.

    In P2, the rigid body needs one or more shapes to determine the Shape of the rigid body, and Shape has two properties: collisionGroup and collisionMask. The former determines the collision groups of Shape, and the latter determines which collision groups the Shape will collide with. Note that collisionGroup values are from math.pow (2,0) to math.pow (2,32).

    // Create the shape of the basketball rigid body
    ballShape = new p2.Circle({radius: GlobalData.ballRadius / factor}); 
    // Set the collision group of the basketball
    ballShape.collisionGroup = this.FLYBALL;
    / / this setting collisionMask basketball can interact with the basket, pay attention to the multiple grouping is not | | |
    ballShape.collisionMask = this.BASKET | this.GROUND;Copy the code

    Therefore, when the basketball is going up, set its collision group to FLYBALL and its collisionMask to ground, so that the basketball will only collide with the ground, not the basket. When it is falling, set its collision group to DROPBALL and its collisionMask to ground and basket. At this point the basketball will collide with the basket.

    In order to solve the second problem, the idea is similar, when the rise of the basketball is set to the highest, the fall of the basket and exchange the depth value can be;

  • Qr code recognition

    In wechat web pages, it is often necessary to provide the recognition function of long-press TWO-DIMENSIONAL code, but the two-dimensional code needs to be an actual picture. The two-dimensional code embedded in the Canvas does not support long-press recognition. In order to enable wechat to recognize the picture, it only needs to cover a picture on the Canvas. The schematic is as follows:

Overlay hierarchy diagram

When setting the image, the size of the image needs to be scaled according to the browser size and the actual size of the Canvas, so that the image looks like it belongs to the Canvas.

  • Text copied

    In the requirements of the game, users need to click a button to copy a text and send it to the public account. However, due to the different permissions of browsers to copy text, the solution in the market is to use ZeroClipBoard, but the development did not take into account that the use of Document. ExecCommand (‘ copy ‘), Because of compatibility, fallback is implemented. When the browser does not support it, the user is prompted to hold down and select text for replication. At this time, as Canvas does not support text selection, the text needs to be processed like two-dimensional code and placed on the Canvas.

Performance optimization

  • Rendering optimization

    Egret performs four steps each time a frame is refreshed. Remember exactly what the four steps are doing when you’re making your game.

    1. We execute an EnterFrame once, at which point the engine executes the game logic. And throw the EnterFrame event.
    2. Execute a clear. Erase all of the previous frame.
    3. The Egret kernel iterates over all displayObjects in your game scene and recalculates the transform of all display objects
    4. If you’ve ever touched Canvas, you know that this step will draw all the images into the canvas.
Rendering schematic

Now that we know Egret’s rendering mechanism, we know where to start.

First, since invisible objects have to be cleared every time we walk through the DisplayObject, and there is a performance cost to creating objects, we can reduce the cost by creating a reclaim pool to reclaim invisible objects.

Secondly, Canvas detects Touch points by traversing all points to detect which point is clicked. When setting the Touch layer, we should move it up as much as possible instead of setting it on the object, so as to reduce the overhead caused by traversal.

In addition, during the development process of the game, I only had iOS devices at hand, but during the testing process, I found that frame dropping on Android devices was serious. After reducing the canvas size from 640, 1136 to 480, 800, the lag condition improved significantly. The guess is that too many canvases will cause a large rendering overhead, so the canvas can be set to a smaller acceptable range, and the browser can zoom the canvas to reduce the rendering overhead.

  • Dirty rectangle rendering

    In the case of complex UI interfaces, the full-screen refresh algorithm will refresh all UI objects 60 times per second continuously, and dirty Rectangle rendering can detect changed objects and render only changed objects. In the case of dirty rectangle rendering, the drawing changes as much as possible, and in extreme cases, the drawing is skipped. In the case of complex UI, it is very easy to reach full frame.

    Egret has dirty rectangle rendering on by default, but performance is poor when all display objects are constantly moving. At this point we need to render by turning off dirty rectangle

    this.stage.dirtyRegionPolicy = "off";

  • WebGL rendering

    WebGL provides hardware 3D accelerated rendering for HTML5 Canvas by adding a JavaScript binding to OpenGL ES 2.0. Egret Engine2D provides a WebGL rendering mode. You can get hardware acceleration just by turning on WebGL rendering.

    Egret When the WebGL rendering mode is enabled, if the browser does not support it, it will automatically switch to Canvas rendering mode.