This time I will bring you a JS imitation of the Legend of Blood game (with code). What are the precautions for the JS imitation of the Legend of Blood game? Here is a practical case, let’s take a look.
The first version of the game was developed in 2014. The browser uses html css js, the server uses asp php, communication uses ajax, and data storage uses access mySql. However, due to some problems (I didn’t know how to use node at that time, writing complex logic in asp was really difficult to write; there was little writing on canvas at that time, and dom rendering could easily reach performance bottlenecks), it has been abandoned. Later, a version was remade using canvas. This article was written in 2018.
1. Preparation before development
Why use Javascript to implement a more complex PC game
1. It is feasible to implement PC-side online games with js. With the upgrade of PC and mobile phone hardware configurations, the updating of browsers, and the development of various H5 libraries, it is becoming increasingly difficult to implement an online game in js. The difficulty here mainly lies in two aspects: the performance of the browser; whether the js code is easy enough to expand to satisfy the iteration of a game with extremely complex logic.
2. Among the current js games, there are few large-scale ones for reference. Most (almost all) games involving multiplayer connections, server-side data storage, and complex interactions are developed using Flash. But flash is declining after all, while js is developing rapidly and can run as long as there is a browser.
Why did you choose a 2001 legendary game
The first reason is my feelings for old games; of course, the other more important reason is that I either don’t know how to play other games, or I know how to play them but don’t have the materials (pictures, sound effects, etc.). I think it is a waste of time to spend a lot of effort to collect a game's map, character and monster models, items and equipment diagrams, and then process and parse them before using them for js development.
Since I have collected some materials for Legend games before, and luckily found a way to extract the Legend of Legend client resource files (github address), I can start writing code directly, saving some preparation time.
Possible difficulties
1. Browser performance: This should be the most difficult point. If the game wants to maintain 40 frames, then each frame only has 25ms left for js to calculate. And since rendering usually consumes more performance than calculation, the actual time left for js is only about 10 milliseconds.
2. Anti-cheating: How to prevent users from directly calling interfaces or tampering with network request data? Since the goal is to use js to implement more complex games, and any online game needs to consider this, there must be a relatively mature solution. This is not the focus of this article.
2. Overall design
Browser side
Screen rendering uses canvas.
Compared with dom(p) css, canvas can handle more complex scene rendering and event management. For example, the following scene involves four pictures: players, animals, items on the ground, and the lowest map picture. (Actually, there are also shadows on the ground, the corresponding names that appear when the mouse points to characters, animals, and objects, as well as shadows on the ground. For the convenience of reading, we will not consider so much content.)
At this time, if you want to achieve the effect of "click on the animal, attack the animal; click on the item, pick up the item", then you need to monitor the events for animals and items. If you use the dom method, there will be several difficult problems to deal with:
a. The order of rendering is different from the order of event processing (sometimes the event needs to be processed first if the z-index is small), and additional processing is required. For example, in the above example: when you click on monsters or items, it is easy to click on characters, so you need to perform "click event penetration" processing for the characters. Moreover, the order of event processing is not fixed: if I have a skill (such as treatment in the game) that requires a character to be released, then the character needs to have event monitoring at this time. Therefore, whether an element needs to process events and the order in which events are processed vary with the game state, and DOM's event binding can no longer meet the needs.
b. Related elements are difficult to put in the same dom node: such as the player's model, the player's name and the player's skill effects. Ideally, they should be placed in a
or
c.Performance issues. Even if the effect is sacrificed, using DOM for rendering will inevitably lead to many nested relationships, and the styles of all elements will change frequently, continuously triggering the browser's repaint or even reflow.
Separation of canvas rendering logic and project logic
If various rendering operations of canvas (such as drawImage, fillText etc.) are placed together with the project code, which will inevitably lead to the inability to maintain the project later. After looking through several existing canvas libraries, combined with vue's data binding debugging tool, I created a new canvas library Easycanvas (github address), and like vue, it supports a plug-in. Debug elements in canvas.
In this way, the rendering part of the entire game is much easier. You only need to manage the current state of the game and update the data based on the data returned from the socket by the server. "Changes in data cause changes in views" is the responsibility of Easycanvas . For example, in the implementation of the player wrapping items in the picture below, we only need to give the location of the wrapping container and the arrangement rules of each element in the backpack, and then bind each wrapped item to an array, and then manage this array. Yes (Easycanvas is responsible for the process of mapping data to the screen).
For example, the style of a total of 40 items in 5 rows and 8 columns can be passed to Easycanvas in the following form (index is the item index, the item x-direction spacing is 36, and the y-direction spacing is 32). And this logic is immutable. No matter how the array of items changes or where the package is dragged, the relative position of each item is fixed. As for rendering on canvas, there is no need to consider the project itself, so maintainability is better.
style: { tw: 30, th: 30, tx: function () { return 40 + index % 8 * 36; }, ty: function () { return 31 + Math.floor(index / 8) * 32; } }
canvas layered rendering
Assumption: The game needs to maintain 40 frames, the browser is 800 wide and 600 high, with an area of 480,000 (hereinafter referred to as 480,000 as a screen area).
If the same canvas is used to render, then the frame number of this canvas is 40, and at least 40 screen areas need to be drawn per second. However, it is likely that multiple elements overlap at the same coordinate point. For example, the UI, health bar, and buttons at the bottom are overlapped, and they jointly block the scene map. So adding these together, the browser's drawing amount per second can easily reach more than 100 screen areas.
This drawing is difficult to optimize, because the view is being updated everywhere on the entire canvas: it may be the movement of players and animals, it may be the special effects of buttons, it may be changes in the effects of a certain skill. In this case, even if the player does not move, the entire canvas will be redrawn due to the effect of the clothes "fluttering in the wind" (actually the sprite animation plays to the next picture), or a bottle of potion appearing on the ground. because It is almost impossible for a certain frame of the game to be indistinguishable from the previous frame. Even a part of the game screen is difficult to remain unchanged. The entire game screen is always updated.
Because it is almost impossible for a certain frame of the game to be indistinguishable from the previous frame, and the screen is always updated.
Therefore, this time I adopted the overlapping arrangement of three canvases. Since Easycanvas' event processing supports delivery, even if the top canvas is clicked, if no element ends a click, the subsequent canvas can also receive the event. The three canvases are responsible for UI, ground (map), elves (characters, animals, skill special effects, etc.):
The advantage of this layering is that the maximum number of frames per layer can be adjusted as needed:
For example, the UI layer, because many UIs usually do not move, and even if they move, they do not require too precise drawing, so the number of frames can be appropriately reduced, for example, to 20. In this way, if the player's physical strength decreases from 100 to 20, the view can be updated within 50ms, and the 50ms switch cannot be felt by the player. Because things like physical strength It is difficult for UI layer data to change multiple times continuously in a short period of time, and the 50ms delay is difficult for humans to perceive, so frequent drawing is not required. . If we save 20 frames per second, we can probably save 10 screen areas of drawing.
Another example is the ground. The map will only change when the player moves. In this way, if the player is not moving, 1 screen area can be saved per frame. Due to the need to ensure smoothness when players move, the maximum frame rate on the ground should not be too low. If the ground frame is 30 frames, then when the player is stationary, 30 screen areas can be saved per second (in this project, the map is almost drawn to fill the screen). Moreover, the movement of other players and animals will not change the ground, and there is no need to redraw the ground layer.
The maximum frame rate of the sprite layer cannot be reduced. This layer will display the core parts of the game such as character movements, so the maximum frame rate is set to 40.
In this way, the area drawn per second may be 80 to 100 screen areas when the player is moving, but may only be 50 screen areas when the player is not moving. In the game, players stop to fight monsters, type, organize items, and release skills while standing still. Therefore, the drawing of the ground will not be triggered for a large amount of time, which saves performance greatly.
Service-Terminal
Since the goal is to implement a multiplayer online game using js, the server uses Node and sockets to communicate with the browser. Another advantage of this is that some common logic can be reused at both ends, such as determining whether there is an obstacle at a certain coordinate point on the map.
Game-related data such as players and scenes on the Node side are all stored in memory and synchronized to files regularly. Every time the Node service starts, data is read from the file into memory. In this way, when there are more players, the frequency of file reading and writing increases exponentially, causing performance problems. (Later, in order to improve stability, a buffer was added for file reading and writing, using the "memory-file-backup" method to avoid file damage caused by server restarting during the reading and writing process).
The Node side is divided into multiple layers such as interface, data, and instance. The "interface" is responsible for interacting with the browser. "Data" is some static data, such as the name and effect of a certain medicine, the speed and physical strength of a certain monster, and is part of the game rules. "Instance" is the current state in the game. For example, a medicine on a certain player is an instance of "drug data". For another example, "deer instance" has the attribute "current blood volume". Deer A may be 10, deer B may be 14, and "deer" itself only has "initial blood volume".
3. Implementation of scene map
Map scene
Let's start with the map scene part, which still relies on Easycanvas for rendering.
think
Since the player is always fixed at the center of the screen, the player's movement is actually the movement of the map. For example, if the player runs to the left, the map will move to the right. As mentioned just now, the player is in the middle layer of the three canvases, and the map belongs to the bottom layer, so the player must block the map.
This seems reasonable, but if there is a tree in the map, then "the player's level is always higher than the tree" is wrong. At this time, there are 2 big solutions:
The map is layered, and the "ground" is separated from the "above ground". Place the player between two layers, for example, in the picture below, the left side is on the ground and the right side is on the ground, and then overlap and draw to sandwich the character in the middle:
This seems to solve the problem, but it actually introduces two new problems: The first is that players may sometimes be blocked by things "on the ground" (such as a tree), and sometimes they need to be able to block things "on the ground" ( For example, if you stand under this tree, your head will block the tree). Another problem is that the performance cost of rendering will increase. Since players are changing all the time, the "ground" layer needs to be redrawn frequently. This also breaks the original design - to save the rendering of the large ground map as much as possible, which makes the layering of the canvas more complicated.
The map is not layered, "ground" and "above ground" are drawn together. When the player is behind a tree, set the player's transparency to 0.5, for example, as shown below:
There is only one disadvantage to this: the player's body is either opaque or translucent (monsters walking on the map will also have this effect), which will not be completely realistic. Because the ideal effect is to have a scene where part of the player's body is obscured. But this is performance-friendly and the code is easy to maintain. I currently use this solution.
So how to determine which parts of the "map" picture are trees? Games usually have a large map description file (actually an Array), which uses numbers such as 0, 1, and 2 to identify which places can be passed, where there are obstacles, which places are transfer points, etc. The "description file" in Legend of Hot Blood is described in 48x32 as the smallest unit, so the player's actions in Legend will have a "chessboard" feel. The smaller the unit, the smoother it is, but the larger the volume it takes up, and the more time-consuming the process of generating this description is.
Now let’s get down to business.
accomplish
I asked a friend to help me export the map of "Beech Province" in the Legend of Legend client. It is 33600 wide and 22400 high, which is hundreds of times the size of my computer. To prevent the computer from exploding, it needs to be split into multiple chunks to load. Since the smallest unit of a legend is 48x32, we split the map into 4900 (70x70) image files at 480x320.
I believe you have mastered the method after reading the case in this article. For more exciting information, please pay attention to other related articles on the php Chinese website!
Recommended reading:
FileReader implements local preview before uploading pictures
detailed explanation of the steps of vue-dplayer to implement hls playback
The above is the detailed content of JS imitation of the legendary game (with code). For more information, please follow other related articles on the PHP Chinese website!