
Shortcuts:
github ,
tiles.js tiles.groovy tiles.rubyIt is no secret that objects in games are an order of magnitude larger than their possible behaviors. When prototyping, descriptions of objects can be made directly in the code in Java, C ++ or C #, but everything will get confused pretty quickly. Then the objects are transferred to the database, either in XML or JSON config. This helps a lot, because after editing the configuration, it is not necessary to recompile the code, and not only programmers can do this, but also specialists in the subject (for games, these are game designers and content managers). When a team grows, or the number of objects goes beyond a certain line, programmers write a convenient editor that allows you to visually edit this JSON-config. As a result, some difficultly maintained monster is obtained at the exit.
')
If you are not going to hire a lot of people who do not know how to code at all, then you can try to go the other way: describe the metadata using Domain Specific Language.
What gives the visual editor, which is not when editing the database / json / xml?
- Tracking links between objects
- Groupings of objects are visible
- There are no numeric IDs, the search is done by names.
What can give a rejection of the full visual editing in favor of a more advanced description language?
- Readable diff in the version system
- Faster incorporation of new designs, new fields for objects
- Simplification of description due to Convention over configuration
Goal: get all these buns.
Speaking of numeric IDs. If in your project the numeric IDs of objects are written in the code as constants - it is better to change it, otherwise when they become dozens - there will be problems. The owners of minecraft servers have shed whole lakes of tears due to conflicts of block IDs and entities from different mods. Even if this list of constants is stored in one place, then this is also bad, because when you start writing additions that change this list, you will get a big hemorrhoids. And if you have a multiplayer game and these IDs are sewn into the protocol, then get ready to burn in hell.
Result: code torn from production
github , on it is Java code with a model and a test that loads data from two scripts:
tiles.js tiles.groovy tiles.rubyThe way of thinking
There is a model of tiles, for example, a couple of classes from it:
And TileSet on the client:

There is also a code that acts like an automaton, when according to the data from these objects it determines which tile to put in place of the exploded one and which prize to create.
To edit this as tables in SQL or as a JSON file with a list of entities would be very inconvenient, you need something more powerful.
Let's try to describe the configuration not as a list of objects, but as a tree. Vertices will be of different types - somewhere a new tile is declared, somewhere a new group, and somewhere it is described what the tile turns into if it is touched by an explosion. In this case, we need the following:
- Parser creating this tree from text
- A piece that bypasses a tree and, for a vertex, calls the appropriate method for its type, which does something in the model.
- Logic for Convention over configuration: filling in the empty fields according to the agreed logic, for example, you can take the value from the corresponding field from the group
If the parser can take the usual - JSON, XML, then that in the second paragraph will have to write themselves. Or not? Here is the secret: you can immediately use the script engine, you just need to create bindings so that it calls methods to construct the model.
The result is a language that is more suitable for this area, Domain Specific Language.
Here is a description of the most frequent tile - a brick wall, from which prizes can fall out with different probability.
It should be noted that the groovy or coffeescript brackets will be smaller.
newTile("brick", { type: "solid", image: 44,
Implementation
Functions like newXXX and getXXX create and search for entities, returning a pair (object id, object type). newXXX also manages to check whether the types of the fields of the object. All get-s are carried out by name, and return the object even if it has not yet been created - this ensures modularity, that is, the config can be split into different files corresponding to different aspects of the game. At the same time, it will not matter what the exact order of execution of these scripts will be.
As a result of the configuration, ScriptClassTable tables are created with objects inherited from ScriptClass. In this case, object IDs are automatically generated during the execution of the script. Only IDs are transmitted over the network, without the names of the objects, and they are the same on the client and on the server, since one script was executed there and there.
At first, I used JavaScript, as the browser client builds under JS. The server used the Rhino engine. The abstraction was carried out at the expense of different implementations of the ScriptUtils interface, which does not know about the game model at all, but knows that there are Object, Line, List and Number types in the script, and that the script can call methods.
Later, I began to transfer tables over the network, but the script was executed only on the server. Now I transfer the configuration to groovy so that it can describe not only the data, but also set the behavior of some special entities with closures. Of course, I could do it through JS, but calling the java code transferred from the groovy script is much simpler, because it is assembled into binary code right at runtime. Also, the Groovy syntax is sweeter, in many cases brackets can be omitted. Although, for this it was possible and go to the coffee.
Because of the chosen abstraction, the translation from Rhino to Groovy took 20 minutes: it was enough to register another implementation of the ScriptUtils class. The script itself has changed little: braces have changed to square brackets in the description of objects, and syntax when describing functions. After the translation, it immediately went into production.
Bootstrap on server:
- The server finds out which configuration to use, maybe even takes it over http
- Script is executed, object type tables are generated.
- A map generator is created; it extracts from the table the tiles it needs by their name.
- The rest of the controllers start, they all request and remember the types of objects they need.
- The game begins
- Each user who is logged receives those tables and only those fields of objects that the game client needs.
Fortunately, loading the game takes a fairly short time, which allows you to quickly debug the configuration. Visual tools are still present in the game itself, but with their help, it is not yet possible to edit the config.
It is worth noting that some objects from this configuration describe the format of analytics that will be collected for players: if there is a slot “money”, then the items collected for the round, which increase this “money”, affect what goes to the statistics database in the “money” column ".
Look like that's it.
Of course, this DSL does not yet use the delights of the language on which it is based. You can make the configuration not only build the model, but also describe the generation of the map and the structures on it, making the code injection into the appropriate controllers. Also, a few constants of the type BLOCK_XXX and EFFECT_XXX can be passed to the script via bindings.
Those who ask good questions will give access to the skins in the game, if you write your login :) And do not hesitate to criticize.
PS While writing this article, I had to
pause to go to the
oncite Topcoder OpenPS Thank you
akzhan , now there is JRuby