Technical Overview of Douyin National Day Mini‑Game Development Using Lynx and Cocos Creator
This article details the end‑to‑end development of Douyin's National Day mini‑game, covering the Lynx + Cocos tech stack, core Cocos concepts such as ECS, node hierarchy, lifecycle, a step‑by‑step walkthrough of the star‑catcher demo code, background looping strategies, checkpoint transitions, character Spine animation, resource loading pipelines, custom bitmap fonts, and a series of performance optimizations for mobile deployment.
Introduction
The author, a member of the Douyin Interactive Technology team, participated in the development of the National Day activity mini‑game, a high‑visibility project that showcases travel guides within Douyin. The article shares the technical experience gained during the project.
Technology Stack
The game is built on a combination of Lynx (ByteDance's cross‑platform framework) and Cocos Creator . Lynx provides a fast first‑screen rendering canvas, while Cocos handles the game logic and UI components.
Cocos Basics
Entity‑Component System (ECS)
Cocos adopts a component‑based architecture (ECS). Instead of deep inheritance hierarchies, entities (nodes) gain functionality by attaching reusable components. The article illustrates this with a vehicle‑bus example, showing how composition replaces inheritance.
Node, Component, and Anchor Point
In Cocos, a Node is the container for components. Adding a Label component gives a node the ability to display text, while a Widget component provides CSS‑like positioning (e.g., bottom alignment). The anchor point determines the node’s origin for positioning, scaling, and rotation.
Coordinate System
Cocos uses a Cartesian coordinate system (right‑handed) similar to WebGL, which differs from the screen‑coordinate system used in typical web development. Conversions are required when synchronising Lynx UI elements with Cocos objects.
Lifecycle Callbacks
Cocos component scripts expose several lifecycle methods. The most frequently used are onLoad , start , and update . Callbacks are invoked in a depth‑first order: parent nodes before children, earlier siblings before later ones.
Star‑Catcher Demo Walkthrough
The following code snippets demonstrate how a simple star‑catching game is assembled.
const { ccclass, property } = cc._decorator;
@ccclass
export default class LabelDemo extends cc.Component {
start() {
// Get the Label component of the node
const labelComponent = this.node.getComponent(cc.Label);
// Update the label text every second
this.schedule(() => {
labelComponent.string = `当前时间:${Date()}`;
}, 1);
}
}Game logic resides in Game.js attached to the Canvas node.
onLoad: function () {
// Ground Y coordinate
this.groundY = this.ground.y + this.ground.height / 2;
this.timer = 0;
this.starDuration = 0;
this.spawnNewStar();
this.score = 0;
},
spawnNewStar: function () {
var newStar = cc.instantiate(this.starPrefab);
this.node.addChild(newStar);
newStar.setPosition(this.getNewStarPosition());
newStar.getComponent('Star').game = this;
this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration);
this.timer = 0;
},
getNewStarPosition: function () {
var randX = 0;
var randY = this.groundY + Math.random() * this.player.getComponent('Player').jumpHeight + 50;
var maxX = this.node.width / 2;
randX = (Math.random() - 0.5) * 2 * maxX;
return cc.v2(randX, randY);
},Scoring and game‑over handling:
gainScore: function () {
this.score += 1;
this.scoreDisplay.string = 'Score: ' + this.score;
cc.audioEngine.playEffect(this.scoreAudio, false);
},
gameOver: function () {
this.player.stopAllActions();
cc.director.loadScene('game');
},Player movement and input handling ( Player.js ):
onLoad: function () {
var jumpAction = this.runJumpAction();
cc.tween(this.node).then(jumpAction).start();
this.accLeft = false;
this.accRight = false;
this.xSpeed = 0;
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
runJumpAction: function () {
var jumpUp = cc.tween().by(this.jumpDuration, { y: this.jumpHeight }, { easing: 'sineOut' });
var jumpDown = cc.tween().by(this.jumpDuration, { y: -this.jumpHeight }, { easing: 'sineIn' });
var tween = cc.tween().sequence(jumpUp, jumpDown).call(this.playJumpSound, this);
return cc.tween().repeatForever(tween);
},
onKeyDown(event) {
switch (event.keyCode) {
case cc.macro.KEY.a:
this.accLeft = true;
break;
case cc.macro.KEY.d:
this.accRight = true;
break;
}
},
onKeyUp(event) {
switch (event.keyCode) {
case cc.macro.KEY.a:
this.accLeft = false;
break;
case cc.macro.KEY.d:
this.accRight = false;
break;
}
},
update: function (dt) {
if (this.accLeft) {
this.xSpeed -= this.accel * dt;
} else if (this.accRight) {
this.xSpeed += this.accel * dt;
}
if (Math.abs(this.xSpeed) > this.maxMoveSpeed) {
this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
}
this.node.x += this.xSpeed * dt;
},Star behaviour ( Star.js ):
getPlayerDistance: function () {
var playerPos = this.game.player.getPosition();
var dist = this.node.position.sub(playerPos).mag();
return dist;
},
onPicked: function () {
this.game.spawnNewStar();
this.game.gainScore();
this.node.destroy();
},
update: function (dt) {
if (this.getPlayerDistance() < this.pickRadius) {
this.onPicked();
return;
}
var opacityRatio = 1 - this.game.timer / this.game.starDuration;
var minOpacity = 50;
this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
},Background Implementation
The game world consists of three layers: road (foreground), middle ground (named frontground ), and sky (far background). Two looping strategies are discussed:
Full‑image loop: duplicate the whole background node and stitch them together, moving the camera and resetting positions when one copy leaves the view.
Half‑image loop: split a background image into two halves and alternate them. This reduces memory pressure because the off‑screen half can be disabled, freeing GPU texture memory.
Checkpoint backgrounds require a state machine (normal → arriveScenery → leaveScenery) that synchronises foreground and middle‑ground positions. When a player reaches a checkpoint, the system may adjust the middle‑ground speed so that both layers align with the checkpoint visual.
if (playerDistance * 0.8 > sceneryDistance) {
// Middle ground lags too far; let player run an extra loop and speed up middle ground
newMiddleSpeed = sceneryDistance / (playerDistance + 4);
} else {
// Middle ground ahead; slow it down
newMiddleSpeed = sceneryDistance / playerDistance;
}Character Animation
Characters are animated with Spine skeletal animation. The engine loads Spine assets and switches animations based on game state, using mix‑in transitions for smooth blending.
Resource Loading Pipeline
After Lynx finishes the first screen, the game scene is loaded. All heavy assets are placed in a dedicated resource bundle and loaded via cc.resources.load . A custom wrapper adds onProgress and onFinish callbacks to track overall progress, retry failures, and log timing.
Custom Bitmap Fonts
Because WebGL text rendering is expensive, the team converts designer‑provided PNGs into bitmap fonts. Cocos’s built‑in bitmap font supports only fixed‑width digits, so a custom solution is used to render variable‑width numbers and symbols.
Performance Optimizations
Draw Call Reduction
Sprites are packed into atlases to enable batch rendering, dramatically lowering draw calls.
Texture Memory
PNG textures are decoded to RGBA (4 bytes per pixel). Reducing image dimensions to 70 % saves ~51 % memory; to 50 % saves ~75 %.
Engine Trimming & Custom Engine
Unused engine modules are unchecked in the project settings, shrinking the final engine binary. A custom‑built Cocos engine further removes dead code.
Code‑Level Optimizations
Release textures promptly after use.
Lower refresh rates for distant sky layers and the character.
Disable background blur except when a flash‑card is active, avoiding heavy GPU convolution.
References
Star‑catcher demo: http://fbdemos.leanapp.cn/star-catcher/
Cocos Creator documentation: https://docs.cocos.com/creator/manual/zh/getting-started/quick-start.html
Premultiplied alpha discussion: https://www.zhihu.com/question/264223719
TikTok Frontend Technology Team
We are the TikTok Frontend Technology Team, serving TikTok and multiple ByteDance product lines, focused on building frontend infrastructure and exploring community technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.