Online game server design

Source: Internet
Author: User
Tags server memory

Original article: http://job.17173.com/content/2009-11-24/20091124170050513,1.shtml

 

Before talking about this topic, let everyone know what a server is. In the game, servers play the role of synchronization, broadcast and server-initiated behaviors, such as weather, npc ai, etc, the reason why many online game servers need to carry some game logic operations is to prevent client cheating. To understand this, the seriesArticleThere will be two parts to talk about the design of the online game server. One part is about how to set up the network connection, synchronization, broadcast, and NPC of the server, the other part focuses on which logic is suitable for the server and what structure is used to arrange these logic.

Server network connection

Most online game servers use a non-blocking select structure. Why? Because the network game servers need to process a large number of connections, and most of them will choose to run in Linux/Unix, it is not cost-effective to open a thread for each user, on the one hand, threads in Linux/Unix are simulated by the concept of processes, which consume system resources. Besides I/O, basically, each thread has no redundant tasks that require parallel processing, and online games are highly interactive. Therefore, inter-thread synchronization becomes very troublesome. As a result, blocking is obviously unrealistic for a Single-threaded server that contains a large number of network connections. For network connections, a structure is required for storage, which must contain a buffer for writing messages to the client, and a buffer for reading messages from the client, the specific size depends on the message structure. In addition, for synchronization, it takes some time to proofread The values. Different values are also required to record the current status. The following shows a preliminary connection structure:

Online Game typedef connection_s {

User_t * ob;/* point to the structure for processing the server logic */

Int FD;/* socket connection */

Struct sockaddr_in ADDR;/* connection address information */

Char text [max_text];/* Received message buffer */

Int text_end;/* tail pointer of the received message buffer */

Int text_start;/* Header pointer of the received message buffer */

Int last_time;/* when is the last message received */

Struct timeval latency;/* difference between the local time of the client and the local time of the server */

Struct timeval last_confirm_time;/* time of the last verification */

Short is_confirmed;/* Whether the connection has passed verification */

Int ping_num;/* The Ping value from the client to the server */

Int ping_ticker;/* how many I/O cycles are processed to update the ping value once */

Int message_length;/* length of the buffer message to be sent */

Char message_buf [max_text];/* Sending buffer */

Int iflags;/* The connection status */

} Connection_t;

The server processes all connections cyclically. It is an endless loop. Each loop uses select to check whether a new connection has arrived. Then, it loops through all connections to see which connection can be written or read, read and Write the connection. Since all the processing is non-blocking, All socket Io can be completed using one thread.

Due to the network transmission relationship, each Recv () may contain more than one message, or less than one message. How can this problem be solved? Therefore, two pointers are used for receiving message buffering. Each receipt starts from text_start, because the remaining half of the message received last time may exist, then text_end points to the end of the message buffer. In this way, two pointers can be used to easily handle this situation. In addition, it is worth noting that the process of parsing messages is a cyclical process, it is possible that more than two messages are received in the message buffer at a time. At this time, it should be executed until there is only one message that is not available in the message buffer. The general process is as follows:

While (text_end-text_start> A complete message length)

{

Start processing from text_start;

Text_start + = message length;

}

Memcpy (text, text + text_start, text_end-text_start );

For message processing, you first need to know the total messages in your game and all the messages to design a reasonable message header. Generally, messages can be divided into four parts: main message, scenario message, synchronous message, and interface message. The main message includes all the actions of the role controlled by the client, including walking, running, and fighting. Scenario messages include weather changes, and some things appear in the scenario for a certain period of time. The characteristics of such messages are that all message initiators are servers, broadcast objects are all players in the scenario. The synchronous message is intended for a player who initiates an object and is broadcast to all players who can see the object on the server. The message also includes all actions, different from the main message, this type of message is sent from the server to the client, and the main message is generally sent from the client to the server. Finally, the interface message includes the chat message sent from the server to the client and various attributes and status information.

The following describes the composition of a message. Generally, a message consists of a message header and a message body. The length of the message header remains unchanged, and the length of the message body is variable, the length of the message body to be saved in the message body. To clearly differentiate each message, you need to define a unique identifier of the message header and the type and ID of the message. The structure of the message header is as follows:

Type struct message_s {

Unsigned short message_sign;

Unsigned char message_type;

Unsigned short message_id

Unsigned char message_len

} Message_t;

Server Broadcast

The focus of server broadcast is how to calculate broadcast objects. Obviously, in a large map, a player's action on the eastmost side should not be visible to a player on the westmost side. So how can we calculate broadcast objects? The simplest way is to divide a map into small blocks and broadcast each time only a few blocks of players around it. So how much is appropriate? In general, the chunks are larger, the memory consumption increases, the chunks are smaller, and the CPU consumption increases (the reason will be mentioned later ). I personally think it is more appropriate to split a small block to the left and right of a screen. Each time nine small pieces of players around the broadcast, due to the frequent broadcast operations, the operations on the nine blocks around the bucket will become quite frequent. Therefore, if the block size is smaller, the range of the bucket will be expanded and the CPU resources will be quickly exhausted.

After cutting blocks, how can players move around between blocks? Let's think about what to do when switching a block. First, you need to figure out which of the nine players around the next block are not available in the current block, and broadcast your information to those players, at the same time, it is also necessary to determine which items in the nine blocks around the next block are not available now, and broadcast the information of those items to yourself, then, we can broadcast the information about the disappearance of the nine objects in the next block to ourselves, at the same time, it broadcasts the lost messages to those objects. This operation is not only cumbersome but also consumes a lot of CPU resources. How can we quickly calculate these items? Compare them one by one? Obviously, it seems that this is not a good solution. Here we can refer to some ideas of the Two-dimensional matrix collision detection to take nine blocks around ourselves as one matrix, and nine blocks around the target block as another matrix, check whether the two matrices collide. If the two matrices intersect, how can we calculate the blocks that do not overlap. Here, we can convert the coordinates of the intersection blocks into internal coordinates and then perform operations.

There is another solution for broadcasting. The implementation is not as simple as chunking. This method requires the client to assist in the computation. First, add a broadcast object queue in the connection structure of the server. When the client logs on to the server, the queue is sent to the client by the server, and the client maintains the queue by itself, when someone leaves the client's field of view, the client requests the server to send a message to the object that disappears. However, it is troublesome for someone to gain a general view.

First, each time the client sends an update position message to the server, the server calculates a field of view for the connection, and then loops the players on the entire map when the broadcast is required, find players whose coordinates are within their field of view. The advantage of using this method is that a large amount of messages need to be broadcast at one time when there is no conversion block. The disadvantage is that players on the whole map need to traverse when calculating the broadcast object, if there are too many players on a map, this operation will be slow.

 

Server Synchronization

Synchronization is very important in online games. It ensures that every player can see the same things on the screen. In fact, the simplest way to solve the synchronization problem is to broadcast the movements of each player to other players. There are actually two problems: 1. Broadcast to which players, which messages are broadcast. 2. What if network latency occurs. In fact, the first question is a very simple one. However, the reason why I raised this question is to remind everyone to consider this factor when designing their own message structure. The second problem is a very troublesome one. Let's take a look at this example:

For example, Player A sends a command to the server, saying that I am going to P2. The command is sent at T0, the server receives the command at T1, and then broadcasts the message to the players around it, the message content is "Player A from P1 to p2", there is a player B near a. The time for receiving this broadcast message from the server is T2. then, draw a picture on the client, A starts from P1 to P2. At this time there is a problem of non-synchronization, Players A and B on the screen shows a different picture of the T2-T1 time. What should we do at this time?

there is a solution. I name it "prediction pull". Although it is a little weird, it can be understood literally. To solve this problem, we must first define a value: prediction error. Then, you need to add an attribute in the connection class of each player on the server, called latency. Then, when the player logs in, the client time and server time are compared, the difference value is stored in latency. In the above example, when the server broadcasts a message, it calculates the currenttime of a client based on the latency of the object to be broadcast, and then contains the currenttime in the message header, then broadcast. In addition, a queue is created locally on the client of Player A to save the message. The message is deleted only in the message queue that has never been verified after server verification. If verification fails, it will be pulled back to Point P1. Then, when Player B receives the message "Player A from P1 to p2" sent from the server, it checks the server sending time in the message and compares it with the local time, if it is greater than the defined prediction error, even at the time of T2, Player A's screen goes to the location P3, and then the Player A on Player B's screen is pulled directly to P3, continue, so that synchronization can be ensured. Further, in order to ensure that the client runs more smooth, I do not recommend pulling the player directly, but calculate a bit of P4 after P3, and then use (P4-P1)/t (P4-P3) to calculate a very fast speed S, and then let player a quickly move to P4 with the speed S, this method is more reasonable, the prototype of this solution is known internationally as full plesiochronous. Of course, this prototype has been tampered with by me a lot to adapt to the synchronization of online games, so it becomes called prediction pull.

In another solution, I name it "verify synchronization" and I know the synchronization name. The general meaning is that each command is executed after it passes the server verification. The specific idea is as follows: first define a latency in each player connection type, and then the client will not move forward while responding to the player's mouse, instead, send a walking command to the server and wait for verification. After receiving the message, the server verifies the logic layer and calculates the range to be broadcast, including Player A. Different message headers are generated based on the latency of each client, start broadcasting. At this time, the gamer's walking information is completely synchronized. The advantage of this method is that it can ensure absolute synchronization between clients. The disadvantage is that when the network latency is large, the player's client behavior will become less smooth, it makes players feel uncomfortable. The prototype of this solution is known internationally as hierarchical master-slave synchronization. It has been widely used in various network fields since 1980s.

The last solution is an idealized solution, known internationally as mutual synchronization. It is a well-predicted solution for the future of the network. The reason for this solution is not to say that we have fully implemented this solution, but to apply some ideas of this solution to some aspects of the online game field. I named this solution semi-server synchronization. The general design concept is as follows:

First, the client needs to create many broadcast lists when logging on to the world. These lists are not synchronized in a timely manner on the backend of the client and the server. The reason for this is to create multiple lists, it is because there are more than one broadcast type, such as local message, remote message, and global message, these lists must be created based on the messages sent from the server during client login. When creating a list, you also need to obtain the latency of the broadcast object in each list, and maintain a complete user status list in the background, which is not synchronized with the server in time, according to the local user status table, some decisions can be decided by the client itself. When the client sends this decision, the final decision is directly sent to the client in each broadcast list, and the time is proofread to ensure that each client proofreads the received message based on the local time. Then, the method that uses the advance computing volume mentioned in the prediction pull to increase the speed of walking will make the synchronization very smooth. The advantage of this solution is that the synchronization between clients is not implemented through the server, which greatly reduces the errors caused by network latency, and most decisions can be made by the client, it also greatly reduces server resources. The drawback is that because both the message and decision-making power are placed on the client, the plug-in provides a great opportunity.

Next, I want to talk about the NPC design and NPC intelligence on the server. First, we need to know what the NPC is and what the NPC needs to do. The full name of NPC is (non-player character). Obviously, it is a character but not a player. From this point, we can know that some NPC behaviors are similar to those of players, he can walk, fight, and breathe (This point is mentioned in the later NPC intelligence). What's different from the player's object is that, the NPC can be resumed (that is, the NPC can be re-generated within a certain period of time after being killed ). In fact, the most important thing is that all the decisions on player objects are made by players, while the decisions on NPC are made by computers, therefore, when making a decision on the NPC, we need the so-called NPC intelligence to make a decision.

Next I will talk about the NPC in two parts: the first is the NPC intelligence, and the second is how the server organizes the NPC. The reason for talking about NPC intelligence is that we can design servers to organize the NPC only when we understand what we need to do.

NPC smart

There are two types of NPC intelligence: passive events and active events. For passively triggered events, the processing is relatively simple. The event itself can call functions on the NPC, such as the death of the NPC, in fact, when the HP of the NPC is smaller than a certain value, it actively calls the ondie () function on the NPC. This kind of NPC intelligence triggered by events is called passive triggering. There are usually two types of triggers:

One is the change of the attributes of the NPC caused by other objects, and the change of attributes will also lead to some behaviors of the NPC. As a result, the NPC object contains at least the following functions:

Class NPC {

Public:

// Who is causing my attributes to change.

Onchangeattribute (object_t * Who, int which, int how, int where );

PRIVATE:

Ondie ();

Onescape ();

Onfollow ();

Onsleep ();

// A series of events.

}

This is a basic NPC structure. This passive event that triggers the NPC, I call it the reflection of the NPC. However, such a structure can only allow the NPC to passively receive some information for decision-making. Such an NPC is stupid. So how can an NPC make some decisions? There is a way to breathe. So how can we let the NPC breathe?

A very simple method is to use a timer to regularly trigger the breathing of all the NPCs so that a single NPCs can breathe. In this case, there will be a problem. When there are too many NPCs, the last NPC breathing has not been completed, and the next breathing has come again. How can this problem be solved. There is a way for the NPC to breathe asynchronously, that is, the breathing cycle of each NPC is determined based on the birth time of the NPC, at this time, the timer needs to perform a period of time to check which NPCs should breathe at the time to trigger the breathing of these NPCs.

As mentioned above, how does the system trigger the breathing rate of the NPC itself? This is like a real person. The breathing frequency is different between sleeping and intense exercise. Similarly, the breathing frequency of the NPC is different from that of the normal one during combat. A breath_ticker is required to set the current breathing frequency of the NPC.

In the NPC breathing event, how do we set the NPC intelligence? It can be summarized into two parts: checking the environment and making decisions. First, we need to make statistics on the current environment, such as whether there are several enemies in the battle, how much HP is left in the battle, and whether there are any enemies nearby. The statistical data is passed into the decision-making module. The decision-making module makes some decisions based on the NPC's own personality orientation. For example, the attention-type NPC will continue to soar when the number of HP is relatively small, for example, a smart NPC will choose to escape when there are few HP nodes. And so on.

Now, the structure of a breathing and reflecting NPC has basically been made up. Let's talk about how the system organizes an NPC to appear in the world.

NPC Organization

There are two schemes to choose from. One is to save the location information of the NPC in the scenario and load the NPC when loading the scenario. Second, the location information of the NPC is stored on the NPC. There are special events for all NPC login scenarios. What are the differences between the two methods? What are their differences?

The advantage of the previous method is that when the scenario is loaded at the same time, the scenario can manage the NPC without additional processing, the disadvantage is that the refresh is synchronized during the refresh process, that is, the NPC in a scenario may be extended within the same time. However, the design of the second method may be a little effort-consuming. A unified mechanism is required to allow the NPC to log on to the scenario, and some troublesome designs are required, however, this scheme can achieve asynchronous refresh of the NPC, which is currently widely used in online games. Next we will focus on the implementation of this method:

First, we need to introduce the concept of a "soul", that is, after an NPC dies, what disappears is his physical body, and his soul still exists in the world without breathing, floating near death, waiting for the time to reincarnation, when the reincarnation, all the previous attributes are cleared, re-on the scene to build its physical body. So how can we design such a structure? First, create a ing table for the NPC that will appear in a scenario, and give each NPC a unique identifier. After loading the scene, load the NPC that belongs to the scenario according to the graph scale. In the ondie () event of the NPC, instead of directly dropping the object destroy, the NPC's breathing is disabled, a rebirth timer is opened, and the object is set to invisable. This design can achieve asynchronous refresh of the NPC, saving server resources while making players feel more real.

(This chapter involves some server script-related things, so the next chapter will talk about some designs related to server script)

In addition, we will talk about the application of heuristic searching in NPC intelligence.

The main idea is to filter all the nodes in the next layer through an inspiration function while giving priority to search in breadth, so as to narrow the search scope within a certain range. A *AlgorithmIt is a typical heuristic search application. Its principle is to design a judge (point_t * point) function at the beginning to get the price of the point, then, during each search, all the vertices that may arrive at the next step are evaluated by the judge () function to get two or three low-cost points and continue the search, those points that have not been selected won't be searched any more. The consequence is that they may not be the optimal path, this is also why the * algorithm goes before the obstacle while seeking the path, instead of going through the diagonal lines in advance to bypass the obstacle. If you want to find the optimal path, you cannot use the * algorithm. Instead, you must use the dynamic planning method, which consumes much more than.

In addition to finding the path, what other aspects can be applied to heuristic search? In fact, a bigger point is that any decision-making of the NPC can be done through heuristic search, for example, escape. If it is a 2D online game, there are eight directions. Which direction does the NPC choose to escape? You can set a judge (INT direction) to give the price of each vertex, calculate the enemy strength of the vertex in the judge, or how agile the enemy is, finally, choose the least expensive place to escape. Next, let's talk about the design of several smart heuristic search methods common to NPC:

Target select (select target ):

First, obtain the list of enemies near the NPC on the map. Design the judge () function to calculate the cost based on the enemy's strength and distance. Then, select the enemy with the minimum cost to initiate an active attack.

Escape (escape ):

Check your hp in the breathing event. If HP is lower than a certain value, or if you are a remote soldier and the enemy is near, the escape function is triggered, in the escape function, it also organizes a list of all the enemies around you, then designs the judge () function, first selects the biggest enemy that threatens you, the judge () the function needs to determine the speed and combat strength of the enemy, and finally obtain a major enemy. Then, it designs the path's judge () function for the main enemy, the search range may only be in the opposite direction of the main enemy, and then calculate the price based on the strength of the enemy in these directions to make the final choice.

Random Walk (Random Walk ):

I do not recommend the * Algorithm for this, because once the number of NPCS increases, the CPU consumption is terrible, and most NPCs do not need long-distance routing, you only need to walk around, so randomly give a few points nearby, and then let the NPC walk over. If it encounters an obstacle, it will stop, so there is almost no burden.

Follow target (follow the target ):

There are two methods here. One is that the NPC looks stupid, and the other is that the NPC looks smarter. The first method is to let the NPC follow the path of the target, almost no resource consumption. The other is to let the NPC determine the current position of the other party in the breathing event while following, and then go straight, and then bypass a * when encountering an obstacle, this design will consume a certain amount of system resources, so it is not recommended that many NPCs follow the goal. If a large number of NPCS follow the goal, there is a relatively simple method: let the NPC and the Target move synchronously, that is, let them speed up and move the same path. Of course, this design is only suitable for the relationship between the NPC and the target, not the target, it's just a move with the players.

In this section, I want to talk about the design of scripts on the server. In the previous chapter, some script-related things have been introduced when talking about NPC intelligence. Let's talk about the role of the script first.

On the compilation-based server sideProgramIt is impossible to build something during the running process of the program. In this case, the script language is required. Because the script language involves logical judgment, therefore, it is useless to provide some function interfaces. You also need to provide some simple syntax and Syntax Parsing functions. In fact, in the end, any event can be regarded as two parts: the first is to change the value of itself or other objects, and the other is to broadcast the event in the form of text or graphics. A very important topic is addressing an object. Well, when talking about this, I want to divide this chapter into three parts: first, how the server manages dynamically created objects (Server Memory Management ), the second is how to address an object, and the third is how to organize and explain the script language. In fact, the reason for talking about Server Memory Management in Chapter 4 is that we don't have a perceptual knowledge of the server memory management in the previous chapters, and we may not know what the server memory management actually uses.

 

4.1 Server Memory Management

For server memory management, we will adopt the memory pool method, also known as static memory management. The concept is to apply for a very large memory pool called a memory pool during server initialization, and also apply for a small memory space, it is called the garbage recollecting station ). The general idea is as follows: when the program needs to apply for memory, first check whether the recycle bin is empty. If not, find an available memory address from the recycle bin, find the corresponding space in the memory pool based on the address and allocate it to the program. If the spam recycle bin is empty, apply for a memory directly from the current pointer position of the memory pool; when the program releases space, it marks the memory that has been released, and then places the memory address in the garbage bin.
The following describes the detailed design of this method. First, we will use a segment-based system similar to the operating system to manage the memory. The advantage is that the memory pool can be fully utilized, the disadvantage is that it is difficult to manage. Well, let's take a look at how we can define the structure of pages and segments:

Typedef struct m_segment_s
{
Struct m_segment_s * Next;/* double-line linked list + static memory can achieve random access and sequential access,
If you really want to access it, you can access it. */
Struct m_segment_s * pre; int flags; // some marks of the segment.
Int start; // relative to the first address of the page.
Int size; // length.
Struct m_page_s * my_owner; // The page on which I belong.
Char * data; // content pointer.
} M_segment_t;

Typedef struct m_page_s
{
Unsigned int flags;/* Indicates whether to use the flag completely or not */
Int size;/* the size of the page, which is generally unified, except for the last page */
Int end;/* Where is it used */
Int my_index;/* provides random access indexes */
M_segment_t * segments; // the header pointer of the segment in the page.
} M_page_t;

How can we build a memory pool and a garbage collection bin? The following also provides some build-related pseudoCode:

Static m_page_t * all_pages;
// Total_size indicates the total number of memories to be applied for, and num_pages indicates the total number of pages to be created.
Void initialize_memory_pool (INT total_size, int num_pages)
{
Int I, page_size, last_size; // calculate the size of each page.
Page_size = total_size/num_pages; // allocate enough pages.
All_pages = (m_page_t *) calloc (num_pages, sizeof (m_page_t *));
For (I = 0; I <num_pages; I ++)
{
// Initialize the segment pointer of each page.
All_pages [I]. m_segment_t = (m_segment_t *) malloc (page_size );
// Initialize the tag of the page.
All_pages [I]. Flags | = never_used;
// Except the last page, all the other pages are page_size.
All_pages [I]. size = page_size;
// Initialize the random access index.
All_pages [I]. my_index = I;
// Since it has not been used, the size is 0.
All_pages [I]. End = 0;
}

// Set the size of the last page.
If (last_size = total_size % num_pages )! = 0)
All_pages [I]. size = last_size;
}

The following describes how to design the recycle bin:

Int ** garbage_station;
Void init_garbage_station (INT num_pages, int page_size)
{
Int I;
Garbage_station = (INT **) calloc (num_pages, sizeof (int *));
For (I = 0; I <num_pages; I ++)
{
// Here, the unsigned short's 8-bit high is used to store the Prime Minister's address, and the 8-bit low is used to store the length.
Garbage_station [I] = (int *) calloc (page_size, sizeof (unsigned short ));
Memset (garbage_station [I], 0, sizeof (garbage_station [I]);
}
}

Maybe this post-code will make you feel quite confused. Well, my code level is really not very good, so let's talk about the general concept in text. For segment-page memory management, it is first divided into N pages, which is fixed, while the segments in each page are dynamic. The major and minor sections are unknown, we need to reclaim not only the page memory, but also the segment memory, so we need a two-dimensional array to save the address of the segment of which the page is released. Then, when applying for memory, first check the size of the memory to be applied. If there is not enough page size, find available segment space allocation in the recycle bin. If not, apply for a new page space.
In this way, using a memory pool to manage the memory of the entire game world can effectively reduce memory fragments and improve the stability and efficiency of game operation to a certain extent.

4.2 addressing of objects in the game

The first question is, why are we addressing? After the concept of scripting language is added, some logical objects in the game, such as the NPC and an item, are dynamically generated by the scripting language during the game's operation, so how can we index these objects? To put it simply, how can we find them? There is a very simple method to traverse all at once. Of course, this is a simple and effective method, but the cost of efficiency is too high for any server, especially after the game scale is relatively large.

So how can we quickly find these objects in the game world? Before talking about this, let me talk about the hash table data structure. It is also called a hash table or a hash table. The working principle of hash table is sequential access or random access, instead, a hash function is used to calculate the key, calculate the address of the value corresponding to the key in the memory, and access it. The advantage is that no matter how big the data is, you only need to calculate it once to find its address, which is very fast. What is the drawback? When the addresses calculated by the two keys through the hash function are the same address, the problem arises and a collision occurs. The solution is very troublesome, I will not discuss the solution in detail here. Otherwise, I would like to write another four or five chapters. However, if you are interested in the solution, you are welcome to discuss it.

Well, we will use a hash to index the objects in the game. What should we do? First, why is it twice as large as applying for a piece of memory in the memory pool that is more than the total number of objects in the game? Prevents hash collision. Then we select the object name as the index key of the hash table, and then we can start designing the hash function. Here is an example:

Static int T [] =
{
1, 87, 49, 12,176,178,102,166,121,193, 6, 84,249,230, 44,163,
14,197,213,181,161, 85,218, 80, 64,239, 24,226,236,142, 38,200,
110,177,104,103,141,253,255, 50, 77,101, 81, 18, 45, 96, 31,222,
25,107,190, 70, 86,237,240, 34, 72,242, 20,214,244,227,149,235,
97,234, 57, 22, 60,250, 82,175,208, 5,127,199,111, 62,135,248,
174,169,211, 58, 66,154,106,195,245,171, 17,187,182,179, 0,243,
132, 56,148, 75,128,133,158,100,130,126, 91, 13,153,246,216,219,
119, 68,223, 78, 83, 88,201, 99,122, 11, 92, 32,136,114, 52, 10,
138, 30, 48,183,156, 35, 61, 26,143, 74,251, 94,129,162, 63,152,
170, 7,115,167,241,206, 3,150, 55, 59,151,220, 90, 53, 23,131,
125,173, 15,238, 79, 95, 89, 16,105,137,225,224,217,160, 37,123,
118, 73, 2,157, 46,116, 9,145,134,228,207,212,202,215, 69,229,
27,188, 67,124,168,252, 42, 4, 29,108, 21,247, 19,205, 39,203,
233, 40,186,147,198,192,155, 33,164,191, 98,204,165,180,117, 76,
140, 36,210,172, 41, 54,159, 8,185,232,113,196,231, 47,146,120,
51, 65, 28,144,254,221, 93,189,194,139,112, 43, 71,109,184,209,
};

// S is the string pointer to be indexed, maxn is the maximum possible length of a string, and the return value is the relative address.
Inline int whashstr (char * s, int maxn)
{
Register unsigned char Oh, h;
Register unsigned char * P;
Register int I;

If (! * S)
Return 0;
P = (unsigned char *) S;
Oh = T [* p]; H = (* (p ++) + 1) & 0xff;
For (I = maxn-1; * P & -- I> = 0 ;)
{
Oh = T [OH ^ * p]; H = T [H ^ * (p ++)];
}
Return (Oh <8) + h;
}

The specific algorithm is not mentioned. Don't ask me why in the above section. The source of this algorithm is Peter K in CACM 33-6. the algorithms introduced in Pearson's ghost paper are said to be very fast. With this hash function, we can use it to quickly address any object in the world.

4.3 script language explanation

Before designing a script language, we must first understand what features should we implement in our script language? Otherwise, you may want to write a C interpreter or something like that. The functions we need to implement are simple logical judgment and loops. All other functions can be completed by providing functions in advance. Well, we can list a workload form: design the storage structure of objects at the underlying layer, provide the access interface between scripts and the underlying layer, and design the interpreter supporting logical judgment and loop.

Next we will talk about the storage structure of objects at the underlying layer. Objects with different properties must have different structures. Of course, if you want to, you can use the same structure for all objects, then, design a hash in the structure to save different attributes. However, this is not a good method. relying too much on the scattered list will complicate the logic of your game. Therefore, try to distinguish between different objects and adopt different structures for design. But it is worth noting that no matter what structure, there are some things that are unified, that is, what we call object headers. How can we design such an object header?

Typedef struct object_head_s
{
Char * Name;
Char * prog;
} Object_head_t;

Here, name is the index number of the object in the hash list, and prog is the program content to be explained by the script interpreter. The following uses the NPC as an example to design a structure:

Typedef struct npc_s
{
Object_head_t header; // Object Header
Int HP; // The hpvalue of the NPC.
Int level; // The level of the NPC.
Struct position_s position; // The current position information.
Unsigned int personality; // NPC personality. An unsigned int can save 24 types of personality.
} Npc_t;

OK. The structure design is complete. How can we design the script interpreter? There are two methods: one is to use the virtual machine mode to parse the script language, and the other is to use a structure similar to the assembly language for design, you can set conditional jumps and loops to implement logical judgment and loops, for example:

Set Name, "Lu Ren jia ";
Choose: random_choose_personality; // randomly select the NPC personality
Compare HP, 100; // compare qi and blood. The value can be placed in a fixed variable.
Ifless less; // if HP is less than 100, return.
Jump choose; // otherwise, continue to select only one HP <100.
Less: Return success;

This script structure is similar to the CPU instruction structure. A single instruction can be executed in sequence. For script programmers (script. programmer), they can also develop their assembly capabilities.

So how can we imitate this structure? Let's take the CPU command as a reference. First, we need to set some registers. The size and quantity of the CPU registers are affected by hardware, but we use memory to simulate the registers, that's how big it can be. Then some commands are provided, including four operations, addressing, judgment, and loop. Next we will use different parsing methods for different scripts. For example, we will use a fixed script for the NPC and a fixed script for the item, after parsing, the result is used to generate the structure of the underlying object.

If you use a virtual machine to implement the scripting language, the project will become very huge and is strongly not recommended. However, if you want to build a general underlying network game, you can consider designing a virtual machine. The general interpretation process of the virtual machine is to compile the keywords twice. The first compilation is to generate the assembly language for the second generation. Then, the virtual machine will explain the compilation language line by line, if you are interested in this, you can download a mudos source code from www.mudos.org for research.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.