A new game with an old atmosphere on Three.js

There are many fans of old games. And they are not averse to letting out a mean nostalgic tear and no, no, but playing “Arkanoid,” “Pakman,” or “Prince of Persia,” like twenty, thirty, forty, or — substitute the right number — years ago. DOS-box and emulators - to help them. Yes, there, I recently watched a stream of the very first 2D “Prince of Persia” on YouTube, where a rather young streamer, after passing another mortal obstacle, waving sweat from his forehead with his hand, said: “I have never been so scared in a computer game ". That is, even young people are able to appreciate the hardcore and coolness of old games.









I thought, why not create a new game in a similar style? Yes, there are various remakes and clones. Also, modern games in the style of pixel art delight. However, all of them, as a rule, repeat quests, mechanics and sometimes even completely level design of old games, based on which they were made. Well, or, conversely, they offer a completely new plot and locations, which is simply a visual stylization of "antique". But what if you imagine what the new part of the old game would be if it followed the last of the series? I decided to create one.



I took a 2D platformer and added 3D graphics there, while retaining the classic side view. He built new mazes, came up with new quests, introduced tasks and implemented inventory slots. Slightly diversified the feeling of space by adding 90-degree turns. Perhaps, in those ancient times, before the total transition of games in 3D with three degrees of freedom, something like this could well come out.



Since I am fond of writing games for browsers, I decided to make a game for the browser. Due to its specificity, it does not have any crazy number of polygons, at least that are displayed on the screen at the same time, there is no open world. Therefore, all this will not heavily load the browser. To display 3D graphics, I chose my favorite Three.js library (WebGL) with my own wrapper. No other libraries are used, and the code is written in pure javascript.



Three.js and the problems I encountered



Developers of graphic 3D engines periodically, about once every couple of years, release new versions of their creations. In the case of the Three.js script library, developers delight us with updates at a frantic frequency - about once a month. At the time of writing, the latest version had the number 106. It would seem that this is fine. (No.)



Why doesn't anyone even think about backward compatibility? When the Three.js developers renamed the material property ambient, I realized, “Houston, we have problems . Okay, let's say, it wasn’t difficult for me in all my code to change the word “ambient” to “color” with a search-replace. But when they ruled out the possibility of a light source creating a shadow without lighting as such, I realized that now the landing of the Eagle is in real danger, and the Calm base is slowly starting to turn into the Rabies base. However, as it turned out later, it was not so bad ...



The fact is that in my game, in addition to baked light, dynamic lighting is also used. The number of dynamic sources in a 3D scene greatly affects performance.









I made the choice of the number of sources in the game settings - from 1 to 7. And depending on the power of the video card, you can try different values.



There is also an array that contains the coordinates and characteristics of each source - intensity, color, and so on. It works like that. When moving around the game world in a certain square around the player, the number of light sources specified in the settings with the specified characteristics is lit. That is, the light sources seem to follow the player (around him) by the cloud.



So, Houston, what are our problems? I report.



Problem number 1: shadows



Light sources create shadows. I noticed that shadows from point light sources consume a lot more resources than directed shadows (spot light). And this is understandable: in the case of a point source, shadows are cast in all directions, and directional - only in a given cone. However, I suggested that when shadows in all directions are not needed, then two sources can be used at once: a point will create only lighting, and a directional one will only create shadows in a certain direction. This is perfect for my game and, as it turned out, really, significantly saves computing power.









The pyramid limits the area in which shadows can appear.



But here's the trouble, starting with version three.js r73, a directional light source can no longer only cast a shadow, it now always gives lighting too. And the light from it spreads within the area, as well as the shadow. It is impossible to remove a point source and leave only a directional one: I need lighting in all directions. And using both sources, adjusting them to the desired brightness, also does not work: then the objects inside the cone will be illuminated much brighter. The use of "honest" lighting with shadows in all directions affects the performance of the game just fatally.



And they removed the property of the directed light source .onlyShadow that I needed simply because the author of the engine wanted it so.



Problem number 2: property names



I decided that I would rather stay on the r71 version, where the lighting property I need is still present. Why not r72 then? After all, the property disappeared only in version r73. Because I already wrote quite a lot of code for loading 3D models, animations and physics for the r71 version. And in the r72 version, a large number of property names have changed: type - shadowMap has become shadow.map, etc. In general, I also did not want to rename all this. And the difference between one version is small. So, we remain on the r71 version.



Problem number 3: roasting shadows









Baked light in different versions of the engine also looks different. I don’t know what they are doing with him there, but when applying a shadow map to the texture, the brightness and even color change dramatically. In general, I somehow solved this problem by sub-mocking the engine in GLSL code and setting automatic color correction in the loader of 3D models in those places where shadow maps are used. This works, of course, only for version r71. For other versions, you will have to use some other parameters. This is another reason to stay on the r71 version.









Actually, it makes no sense to switch to the latest versions, since the result of the work, in comparison with the old one, does not fundamentally differ in terms of performance.



Good. A directional light source creates only a shadow. I can bake and load shadows into the assembly on version r71. And now - the most important ambush. Characters.



Issue 4: skeletal animation



Here I fought for a very long time. In total, I spent a couple of weeks trying to find some kind of working algorithm for transferring a character with a set of animations to the game.









Recent versions of Three.js do not have this problem. There I successfully created an animated character model in a well-known online service, converted it to the popular gltf format and uploaded it to the test scene. However, if the new versions of Three.js use the gltf 2.0 format, then mine only supports 1.0. It would seem, what’s the problem, well, convert to 1.0 and download. It turned out not so simple. Apparently, there are several variations of the gltf 1.0 format. I needed one where, in addition to the main model file, two more files with the extension * .glsl should be present. But in this case, the format of the main file may vary ... In general, I could not find a converter that would satisfy all the parameters, especially since it also converted from the new format to the old one. I also could not finish the old version of three.js to gltf 2.0 support: this support is too deep in the code, rooted in the math, implemented differently in different versions of the engine ... In general, it somehow did not work out with gltf.



I tried using the .dae format for 3D models. As a result, the model itself was loading, but I could not get the character animations to work correctly. There were also problems with textures.



It turned out well with the .md2 format. The character was immediately displayed and all animations worked correctly. But, as I understand it, md2 is a model format for Quake 2 and someone realized its support in Three.js just for fun. And where to get models in this format, and even more so, to design and save my own character in it, I did not find.



I tried a few more formats. But under them, there was no bootloader for three.js, there were no programs for creating the preservation of their character in them, then they did not support skeletal animation at all.



The base "Fury" was just right to rename the base "Despair".



Already almost desperate and thinking about switching to a new version of the engine at the expense of performance (the story of a directional light source), I decided for the last time to torment the json format that I actually started with. But for the first time, I was not able to repeat the process of converting a character from the sample file that came in Three.js bundle from the original .blend format to .json in the Blender 3D editor. The animations spoiled and behaved somehow randomly, and with a dozen variations of the exporter from Blender to json, each and every one worked incorrectly. Moreover, I also tried them on several versions of Blender. The JSONLoader itself, that is, the model loader in json format, has now been removed from three.js. I decided to look at which version its support had stopped, to take it and a sample of the 3D model not from my version, but from the one where it was still. She turned out to be r88. And lo! I managed to reproduce the export of the test model in r71 and everything, including character animations, worked fine in the game!



"Eagle" safely landed.



Then I decided to edit one of the animations of the test character in Blender in order to understand if I could make my own. A bummer was waiting for me here. The animation I edited did not want to work in the game at all. The character froze in the initial position. Although other animations worked without problems. But this is something. That is, now the problem is my ignorance of some nuances of editing the animation.



Then I thought - what if I ask the author of this example how he did it. But getting to the author was not easy. He has no personal contacts at Github. A search for a nickname and a couple more parameters brought to his Twitter, however, private messages there were closed. But from there I learned that he was a professor at some American university, and went to the website of this educational institution. It turned out that the professor was engaged in 3D graphics with his students, using his example as a methodological material. Then I returned to Github and looked through all its repositories. Here I was waiting for success. As I expected, his example was stored in a separate repository. And not just a copy of what I have already seen in the examples of three.js, but an example carefully furnished with instructions (apparently, for students). I downloaded the archive and, following the instructions, repeated everything. Hurrah!



This is a small step for humanity, but a huge leap for one person and his game!



No problem, Houston!









Now I understand that if I stick to this format, these instructions and these versions of Three.js, JSONLoader, and Blender and do everything in the same way, I can create and load any of my characters into the browser game. The good news was that, despite using the old version of the engine, you can use the latest version of the 3D Blender editor and create any characters with animation. Just then you need to export them according to a strictly defined scheme using this specific toolkit.



Yes, I noticed one more problem of the new version of Three.js: during the game, when scrolling the screen, for some reason, constant friezes are observed. And this is not due to increased resource consumption - the processor and video card do not load 100%. And in the old r71 there is no such disgrace.



Now it remains only to do for the game in the 3D character editor with animation. And, of course, the geometry of the levels. I don’t know how much time it will take me. But so far I have just collected a free demo version on Webkit and uploaded it to popular app stores.



A little bit about the game



Title. I called the game "Percy Lancaster." Everything is obvious here: "I am an artist, and I see so."



How graphics were created . I created two planes in the 3D editor, arranged them at a distance from one another so that the far one was hidden behind the nearest one, pulled a stone texture on them, cut holes in the nearest one, and then removed the unnecessary, that is, invisible, parts. Then he modeled the planes for floors and ceilings. So it turned out to be a corridor for the player to walk. I diversified locations with various textures.









In the game itself there is no such kind, there is only a side view, and no black spaces are noticeable. This is just a general view of the corridor.



I decided to create intersecting corridors. During the turn, one wall of the other corridor is completed, the whole structure is turned, and then the wall of the first corridor is removed. Based on this mechanics, I also plan to create towers inside which you can climb the spiral staircase.









With static graphics, in fact, there are no problems, it is easily exported to json from any 3D editor. Difficulties, as I already mentioned, arose only with baking shadows and character animation.



Performance. In the HD screen resolution, that is, 1280x720, my not-so-powerful GT-730 graphics card is loaded by about 35-40%, and the Xeon E5440 processor is loaded by about 30%. I think this is more than an acceptable result.



OS So far, the demo is only available under Windows as an assembly on Webkit. In the future I plan to launch a browser version. I came across the fact that not all browsers scroll the screen smoothly. I still need to work on managing and calling the graphics output function. In the meantime, I settled on Webkit version 26.0. She weighs little and everything works fine on her.



Sound. Sounds are partly taken from free libraries, partly generated. In general, they have been made so far, “to be”. While I did not bother much about them.



Video. Full passage demo level.





Plans



I plan to go to crowdfunding, and hire a 3D modeler with the money raised, which will create a normal character and animations for him. Still, the graphics are not mine. But now I know how to introduce a character to my game.



Also, I plan to launch a browser version for the latest versions of Chrome and Firefox. Let me tell you a secret, I even managed to run the game on MS Edge, but for some reason there were no objects on which the textures of baked shadows overlapped, I still did not figure it out. Next, I will debug for browsers for Linux and Android, and if I get apple devices for someone to test, then also for browsers on iOS and MacOS.



In the long run - writing your own library for working with WebGL, like Three.js: I do not like that they abruptly rename properties and remove the functionality I need. Conversely, most of the huge number of opportunities Three.js offers I do not need.



I think later, when I figure out the correct operation of the game for all browsers on different OSs, I will write an article about this with some technical details.










All Articles