Building A Full-Stack Online Game Framework

Server + Client + Frame Sync! Cocos Creator’s latest full-stack online game framework - free and open source!

Introduction

There have been more and more frame-synchronized online games in recent years, and there are many discussions and debates about the implementation of such games. The developer “One Brick Two Hundred” wrote a frame-synchronized online game framework in TypeScript and used the framework to create an online game demo, which is free to share with you.

Many small development groups want to make frame-synchronized online games. Now, I also made one. Just take a look at the game battle recording screen:

gameplay

image

You can scan the QR code above to experience the game online (currently in the same server global game mode, the game will be reset only after everyone exits).

After playing a match, you will find that this is not just a game of hand speed. The basic gameplay of the game is:

  • Each player entering the game will replace the computer country in it. Each country initially occupies two plots. If the plot is 0, it will fail, and if one team occupies the entire map, they will win.
  • Each plot will automatically grow until it has 50 troops.
  • Use your finger to drag your plot to the enemy plot to send troops. Any collision between the soldiers and any enemy unit will be counted as an attack. According to the remaining troops, you will decide to die or occupy and send troops to your own plot to increase your troops.

For testing, I just wanted to make a small battle game at the beginning, but it was both a server and a client. During the development process, I found that there was quite a few number of issues, and many concepts are generally used in every game, so I thought I share the answers to “Why not package a set of online games?” and “What about full-stack frameworks?” In this way, more people can quickly create their online games.

Thus, the full-stack online game development framework ts-gameframework was born (PS. The game demonstrated above has been included in the sample game of the framework).

TSGF localization

  • Benchmark MGOBE.
  • Open source online game full-stack solution: server + client, all written in TypeScript language.
  • “Black-box” implementation of online games: the client connects to the SDK and does not need to care about communication and synchronization logic.
  • Custom server logic: Extend your own synchronization logic and various interaction logic.

TSGF in detail

Server structure diagram

Login process

  • gate is the entry server (or scheduling server) + login server + cluster management server (simple three-in-one), which can be separated if necessary.
  • backend is the game logic server that can be designed like a cost game, one server corresponds to one global game, and all connected players are regarded as joining the game. The concept of a room can also be added.
  • frontend is the Game client (including two demos).
  • You need to deploy the redis server and configure the connection information in the gate and backend configuration files.
  • TSRPC ( https://tsrpc.cn/ ) is used as the communication framework, so the code generation/synchronization module that comes with this framework is used to fix the directory name and relative path of each project (unfamiliarity with messing around can lead to errors).

Note: Each project root directory needs to execute npm i separately, and the frontend needs to be opened once with Cocos Creator to eliminate the error.

  1. gate
  • During development, the dev script will start the service, monitor the TypeScript code, and generate and synchronize related APIs.
  • If only the communication protocol of TSRPC is modified and not started dev, it can be executed separately proto and sync.
  • All unit tests are written for jest and executed by test.
  • If deployment is required, execute buildTS, then package the following files:
./deploy
./dist
./node_modules
./gf.*.config.json    (The configuration is modified according to the actual situation)
  • Windows deployment provides a quick deployment service mode, right deploy/install_runasAdmin.cmdclick.
  • Linux deployment, you can use pm2start : pm2 start dist/index.js.
  1. backend
  • Executing the dev's script at development time will start the service, monitor TypeScript code, and generate and synchronize the associated API.
  • Two examples are included, both of which are single server game instances (i.e., one game server with one game instance).
  • It can be expanded to one game in one room, multiple games in one room, etc.
  • Deployment reference gate.
  1. frontend
  • Imported as a Cocos Creator 3.4.2 project.
  • The scene is assets/occupationTheWar/occupationScene.scene.
  • assets/scripts/occupationTheWar/ The client code directory for this game.
  • assets/occupationTheWar/The client resource file directory of this game.

Note: In preview mode, because the disconnection and reconnection information is written to the web storage, if you want to open the second client, you need to use the Private window to open the preview address, or use two browsers instead.

Skills

Frame synchronization implementation, including state synchronization

This function is the core package of this framework, and the idea comes from the “Tencent Senior Engineer Bao Ye: Implementation Details of Frame Synchronized Games at the Technical Level” talk.

  1. Schematic diagram of frame synchronization

  • Definition of input operation: All inputs that will affect the game logic, such as player joining and leaving operations and attack operations caused by dragging and dropping, rather than data changes caused by operations such as movement. (This is very important!)
  • The input operation of the client is sent to the server and inserted into the next frame of the current frame.
  • The server broadcasts each frame of data according to the frame rate, and each frame contains all the input operations of the client.
  • After the client receives the frame, it joins the frame queue and executes at a given frequency.
  • To perform multiple input operations in each frame, the client needs to implement the logic of various input operations. After the input operation is completed, the client triggers the update of the logical frame (used to calculate coordinates, physics, etc.). (Note that this is to be distinguished from the rendered frame!)
  1. Add status synchronization information
  • The client periodically synchronizes a copy of the current state data to the server for storage.
  • The server saves the last state data and the corresponding frame index.
  • When a player disconnects or a new player joins, the last state data + subsequent frame tracking list will be sent, greatly reducing the frame tracking time.
  • This feature has higher requirements for game design: data separation, but you can choose to turn off this feature.
  1. Core code

Server: src\FrameSyncExecutor.ts

export class FrameSyncExecutor {
    /**
     * Processing of a logical frame, usually called by the internal timer, can only be tested by having external calls made during unit testing
     */
    public onSyncOneFrameHandler(): void {
    }
    /**
     * Synchronize game status data
     * @param stateData
     * @param stateFrameIndex
     */
    public syncStateData(stateData: any,stateFrameIndex: number): void {
    }
    /**
     * Add connected input operations to the next frame
     * @param connectionId
     * @param inpFrame
     */
    public addConnectionInpFrame(connectionId: string,inpFrame: MsgInpFrame): void {
    }
    /**Get the frame chase data to send to the connection (last state data + frame chase packet) */
    public buildAfterFramesMsg(): MsgAfterFrames {
    }
}

Client: assets\scripts\common\FrameSyncExecutor.ts

export class FrameSyncExecutor {
    /**Triggered when the server requests a frame chase, updating the relevant data*/
    onMsgAfterFrames(msg: MsgAfterFrames) {
    }
    /** A frame is received from the server and is pushed into the frame queue, and some data checks are done */
    onSyncFrame(frame: MsgSyncFrame) {
    }
    /** Truly executes the next frame (an implementation that triggers multiple input operations in sequence) and finally triggers a logical frame event that returns whether all frames have been executed (either before or after execution)*/
    executeOneFrame(dt: number): boolean {
    }
    /** Starts executing the next frame (or catching up to a lagging frame) and automatically executes the subsequent frames as appropriate, stopping automatically when it's done, setting executeFrameStop=true */
    executeNextFrame() {
    }
}

Logical frame and rendering frame separation

What are logical frames and rendered frames?

  • Logical frame: The frame frequency may not be as high as the rendering frame in the frame broadcast by the server.
  • Rendering frame: The frame in FPS.

Generally, in single-player games, it is the update to calculate the move coordinates and so on. However, because the frequency of frame synchronization and the rendering frequency are highly likely to be out of sync, such as the mobile function, the input operation is generally 0 frames to start moving up and three frames to stop moving. If it is placed in the rendering frame, it cannot guarantee that all clients moving distance is the same. Therefore a distinction must be made between logical and rendered frames!

Demonstrate the design principles of the game

I think that no matter what framework or implementation scheme, the design principles should be standardized, which is very important in team collaboration. Otherwise, each has its habits, and when interaction occurs, the problem will be big.

All designs are iterated step by step according to the usage scenarios. There is no universal framework, only the most suitable design (weighing time cost + collaboration cost). The design principles of this game are as follows for reference:

  • Scene/resource reference, stored in a separate class, such as OTWGameResource put the reference of the game prefab, OTWSceneManager put the reference of the scene node (now there is some simple logic in it if it is complicated, it needs to be separated according to the design principle), and then give other business logic implements component references to avoid circular dependencies.
  • Modules encapsulate the game business logic to avoid circular dependencies, such as: OTWGameManager for the overall game logic (entering/exiting the game, logic frame execution, input operation implementation, etc. If there are many types of input operations, you need to separate a “Mgr” further), OTWGameTouchController is the interaction of user operation scene nodes, and it OTWTroopManager is encapsulated for soldier management and interaction logic.
  • The root node of all game objects must have a fundamental component, OTWObjectComponent (to facilitate unified access to information). All types of game objects use subclasses to implement their own information storage and basic logic (such as object type attributes objType, logical frame rendering methods updateData).
  • Because the physics uses the built-in Cocos Creator, the collision detection is implemented with Collider-related components, so the game objects with spatial transformation need to be divided into physical nodes and rendering nodes, and the corresponding physical components are added to the physical nodes, such as soldiers have points, Plots do not need to be subdivided (no movement).
  • data is defined as the model that wraps the data or node reference to all and is uniformly maintained (created and destroyed) by the corresponding Mgr for each business logic reference.
  • All inputs cannot directly manipulate the data by themselves and must be operated in the input operation implementation (OTWGameManager → define the ‘execInput_’ method) to ensure that the final calculation data of all clients can be consistent.

Unit test

I use jest+ chai as a unit test module and recommend the plugin for vscode called Jest Test Explorer (It is more convenient to perform unit tests separately):

Whenever a logic or module becomes complex, it is bound to be encapsulated to reduce various dependencies and be as “independent” as possible. Otherwise, the more complex the logic, the stronger the dependencies, and the more expensive the troubleshooting after an error. I think encapsulation + unit testing is the antidote to any complex logic.

To use in your own project, execute:

npm i -D jest ts-jest chai @types/jest @types/chai

Configuration and code can refer to the source code.

Download

Please go to the gitee address below to download ts-gameframework for free. The specific usage and usage points are attached here:

One Brick Two Hundred/ts-gameframework

typescript game online framework

At present, the TSGF framework is only suitable for frame synchronization of mini-games and is used to verify the core gameplay of online games quickly. Subsequent versions are planned to implement more functions:

V1.1.0 improves room and matchmaking support

V1.2.0 is designed to be used as a module reference

V1.3.0 supports state synchronization

Next, I also plan to use ts-gameframework to make an online game of my own. After all, whether something works or not, only those who really use it know! Later, I may try to operate this business so that developers who do not want to deploy the server can use it directly! It is also a closed-loop.