Creation of the 35MM game. Post Apocalypse in Russia
Good day to all, my name is Sergey Noskov. Today I would like to talk about the creation of my first full-fledged indie project called 35MM, released on Steam in 2016. The story is of course long, and since then several articles and interviews on the topic of the project have already been published, however, there was no detailed description of the development process. Also, the technical aspects of implementation were hardly touched. Actually, we’ll talk about this.
Let's start with a little background. 35MM is an adventure with a first-person view in the setting of a post-apocalypse in Russia. The people - a walking simulator. The game tells us the story of the journey of two wanderers through the deserted lands left by civilization. The bulk of the population died out after a terrible disease, and now nature wins back its points from humanity. Unfortunately, I don’t remember exactly how the idea of this project was born, but I definitely remember that at that time I was an ardent fan of the topic of stalker, Metro games and generally such atmospheric surroundings. The landscapes of abandoned cities, industrial zones and villages always aroused my trepidation and delight. I don’t know what kind of disease it is and how to explain such love, but there are many of us. In general, such a passion on this topic was enough to
How it all started
In some interviews, I already mentioned that usually I do not have a clear development plan, but only the big picture. Therefore, often, the creation of locations occurs spontaneously and the design is thought out on the go. This, of course, is more a minus than a plus, but there is a positive side - it is very entertaining and you never know exactly what will happen next. It turns out that the game to some extent lives its own life already at the initial stages. The development of 35MM began with the creation of the very first location - an abandoned forest house and a spacious plot with fields and coniferous forest.
To build the surface of the earth used terrane with 5-6 textures of grass and earth. The standard terrane shader paints the surface with different textures using the RGBA mask, which we create with a brush in the engine itself, and the result often looks very blurry, the transitions between the textures are too smooth and do not seem natural. To refine this point, the terrane shader was changed and an offset mask was added. The black-and-white texture “shifted” the mask drawn in the terrane and created torn and sharper edges, which visually slightly complicated the appearance of the surface.
The surface of our land was supplemented by several types of grass in the form of randomly scattered pleins. The unit also has a billboard mode (when grass plains always look at the player), but it is not very suitable for playing with a first-person view, because it is too noticeable how the grass “watches” our camera. You should always be careful with the grass and add it within reasonable limits, since this pleasure throws quite a lot of drawcalls, and this in turn affects performance. The more drawcalls, the higher the load on the computer system. But do not forget that not only challenges increase the load. There are tons of ways to kill in-game performance. In addition to grass on the ground, for a change, meshes with branches and stones were scattered, as well as a series of decals with dirt and traces of car wheels.
After creating the surface, go to the vegetation, trees and bushes. I preferred to plant the main part of the forest (coniferous trees) using the tools of the terrane itself - that is, with a brush. The advantage of this method is that it is done quickly and easily, moreover, at a great distance, such a forest is transformed from meshes to billboards, which affects optimization very well. However, near the load is greatly increased, because, as I understand it, such trees do not roll. Maybe I'm wrong, and someone will correct me. Butching is a very important tool for optimization. Roughly speaking, this is a combination of meshes with one material in one common mesh, which significantly reduces the number of drawcalls, respectively, reduces the load. Another minus of the seating on the terrain is the same position of the trees, that is, they are all created at the same angle and this sameness is striking. In this regard, I installed some pine, spruce and deciduous trees manually at different angles and with different sizes, which added variety to the landscape. Bushes were arranged in the same way.
For completeness, it remains to deal with the sky. For this task, an ordinary skybox material with six textures was used. There were attempts to modify the shader so that the sky looked dynamic, but the result did not justify itself and this idea had to be abandoned. An alternative was the use of a particle system with a cloud texture and horizontal billboards with motion tweens. As far as I remember, a similar option was used in the Stalker game, and indeed there are many more where else.
In the 4th version of the Unity engine, there was a very convenient mode for baking lightmaps - dual lightmapping. There is a similar option in current versions, but I have not studied it in detail yet. Dual mode allowed us to draw shadow and glare realtime at close range, but as we moved away from the camera, this whole thing smoothly turned into baked lightmaps, which greatly facilitated the task for our hardware. In general, I used this method on all locations of the game. As a result, for each middle location, a set of about 5-10 lightmaps for the near plan was baked, and a similar amount for the distant one (lightmaps in the near plan too, but only with ambient baked ambient).
In general, in many areas I tried to use completely baked light, with the exception of the light of the sun. Point lights were placed in places to emphasize accents and more light. This was mainly done in rooms where little external light penetrated. In a number of places, of course, rilttime lamps with shadows were also used: light from a fire, a table lamp, a ceiling or wall lamp. By the way, when working with lighting, I had to deal with a big problem related to Point lamps and realtime shadows. In some areas, the camera’s gaze on a point source of light with shadows gave eerie friezes and brakes. It is not entirely clear why the load was so high, but the profiler at that moment showed off-scale drawcalls for a split second. The use of two Spot lights helped to rectify the situation. directed opposite to each other. This option turned out to be less heavy.
Most of the 3D models for the game were created independently. Something was done quite carefully, with baking normal maps and other subtleties, and something was created in haste to save time. Most of the objects were created in groups and used a single atlas texture. That is, in one texture there were sections, for example, for a concrete block, a road sign, brick debris, a sewer hatch, etc. This allowed one material to be used for all of these objects, and, accordingly, allowed the objects to clatter. As we recall, this is pretty good. Some models were faithfully downloaded by me from the Internet, from free libraries. Basically, these are small props for filling the rooms, however, I tried to modify all these models a little so that the similarity was not very evident. Often I noticed identical assets in indie games, which somewhat influenced my perception not in the best way. The most problematic in terms of creation was transport. Modeling wheeled vehicles from scratch is very time consuming and takes a lot of time. Therefore, several copies of the cars were bought by me in the Asset Store.
A separate "song" was the creation of characters. This is still an experiment. For those who do not have a good idea of how much work is needed in order for a character to appear that is able to somehow exist in the game, I’ll explain. A high-poly model is created with all the details, buttons on the sleeves, wrinkles on the face, etc. A low-poly model of the same character is created with a texture scan (in my game the number of polygons per character was on average about 5-8 thousand). Further, a normal map, an ambient (soft shading) map is removed from the high-poly model for the low-poly one by cunning or uncomplicated manipulations. I usually from ambient then make a diffuse map in Photoshop. In the diffuse map in the alpha channel, create a specular map to create brilliance.
For 2019, of course, it is already too primitive, but for 16 years and for the indie project it was quite suitable.
Further, our Persian needs to be flirted - to place bones in it, due to which he can move his limbs, move his jaw, bend and unbend his fingers, etc. Well, in the end, the whole thing needs to be animated. Usually a set of animations with different states is created for the character: walking, running, standing or sitting position. But unique fragments are also required, for example, in my case, for the partner of our hero Petrovich, a large number of variations of actions were required: opening doors, examining a map, a fight with bandits, throwing a light bomb at the Bor level, etc. All this had to be animated by hand, which, of course, is very striking in its clumsiness. In general, manual animation of human movements is a very difficult task and it is extremely difficult to achieve a plausible result. Therefore, motion cap is the most suitable solution for this task. As far as I know, now this option is cheaper and faster than the work of the animator, although the received data needs to be processed and "cleaned up" manually.
I’ll clarify right away that I understood shader writing at that time very superficially. My training was mainly in the analysis of ready-made examples and their refinement. I took various options from the network, changed parameters, added new ones or removed old ones and checked how this affects the result. It turned out that this is an extremely exciting activity. Of particular interest to me was the handling of different texture channels as a mask. In some cases, I tried to fit the maximum amount of information into one texture and use it. At the beginning of the article, I mentioned the differences between the 4th and later versions of Unity, and specifically, the presence of physically correct shading in the latest ones. I tried to eliminate this shortcoming on my own, and the fresnel effect was added to the standard shader with a specular, cubmap and normal map. This is such a feature of reflective materials, in which surfaces at an angle relative to our view reflect the environment (or cubmap in this case) more strongly and usually look lighter and more contrasting. This is very noticeable on a glossy ball, the edges of which seem brighter than the center. I was able to repeat this effect, as well as add the ability to paint the cubmap in the material, which we can usually see on reflective but rough surfaces. This shader completely suited me and was applied to most materials in the game. I was able to repeat this effect, as well as add the ability to paint the cubmap in the material, which we can usually see on reflective but rough surfaces. This shader completely suited me and was applied to most materials in the game. I was able to repeat this effect, as well as add the ability to paint the cubmap in the material, which we can usually see on reflective but rough surfaces. This shader completely suited me and was applied to most materials in the game.
The second interesting experience was the creation of a shader for the skin of characters. The basis was a code found on the Internet that allows you to use a gradient texture, which is responsible for the strength and color of lighting that affects the model. A similar texture with a reddish tint in the middle made it possible to imitate human skin, which, as it were, is a little translucent, that is, has its own thickness, in which light is smoothly scattered. The effect is not perfect, but it looks better than the standard plastic Bumped Specular.
In addition to the above shaders, many secondary options with individual effects were created in the process. For example, a puddle shader with a cubmap and a diffuse map deformation. Since it’s too expensive to apply real reflections to puddles, and I didn’t want to use a render in the texture (this is when the frame is saved in the texture and used in the material, during which, for example, distortions of warm air can be made), I decided to make simple distortions of the earth’s texture, stretched over a puddle. The effect was quite nice and did not strain the iron at all. Incidentally, a shader with a texture render was used to distort the air of kerosene lamps and a bonfire. It seems to have been Heat distort from the Detonator asset. Also, To simulate volumetric light rays, a vertex shader was created with the Soft Particle effect and the rim light effect (when we look at polygons at an angle, the mesh goes into alpha). This is a classic and already bearded way of implementation. Now, for the new Unity there is a cool option that works on the basis of the post-effect and allows you to draw real rays of light even taking into account the shadows.
Another worth noting is the set of shaders that were made to simulate wet surfaces. The game has an episode in which at some point heavy rain begins and some of the materials smoothly acquire a characteristic luster. The main effect was applied to the terrain, on which, as in the case of puddles, the diffuse texture began to distort. Water leaks also appeared on the windows of the houses. Well, the most “wet” chip was drops falling on the lens. Here, in fact, I was tormented by doubts, because the hero had neither glasses nor a helmet, and how the drops so obsessively show off on the screen was not clear. However, visually I liked the effect so much that I just could not refuse it.
So we smoothly move on to post-effects. Speaking of drops on the screen, everything is simple: a few drops from the texture multiply and move down at different speeds. In parallel, waves (gradient textures) are moving down, which are multiplied, each by its own group of drops. Then the whole thing is summed up, slightly displayed in the “diffusion”, so to speak, but it is mainly used as a mask for shifting coordinates. As a result, our picture is distorted by the refraction of water drops. The main set of post-effects that were always or optional on the camera (if the player turned them on or off) is antialiasing, SSAO, Bloom, Aberration, Vignette, Sun Shafts. These are all standard Unity effects, but SSAO has been modified so that the rendering of shadows at a distance is reduced to zero, because in the distance in the fog the dark spots of the shadows looked strange. The aberration effect was also changed (this is the color distortion of the image when using lenses, something like color contours at the edges of objects). The standard effect from Unity painted the burgundy-green edges of objects (a rather strange solution in my opinion). In fact, most often the colors are closer to yellow-red-blue, which I realized. Another permanent effect was the self-made color correction. The standard Unity effect seemed to me too resource-intensive, so its own, simplified one was realized. Basically, he created the effect of tonmapping and slightly changed the color scheme to a colder one. Choosing a color palette for an image is always a difficult task, which is difficult to determine. It happens that you may like completely opposite options and make a decision is extremely difficult. In this project, I settled on dull and cold colors.
What about the code?
I repeatedly mentioned in an interview that I was always far from the topic of programming and focused more on the visual component. I started writing the first full-fledged code while working on the “Train” game, so by the time of developing 35MM I already had some skills. In general, the quest genre seemed to me very suitable for understanding programming at my initial level. Most of the actions in the game are based on triggers. An object enters the trigger (a cube with a collider) and something starts to happen, for example, a cut scene starts. In the script, as in the script for the theatrical performance, it is described line by line when and what happens - now the player’s camera turns off, the cut-scene camera turns on, the character appears in the frame, the conversation animation starts, etc. I believe that there are tools that make this whole process easier (I believe
The movement of our partner in the game was implemented using triggers, which were checkpoints of his route. If you hit the trigger, some new animation might turn on, or the character could say something.
This method was used at all levels except the last. At the final location in the city, if we got to her with a partner, he no longer led us along the route, but rather ran after us. There, a controller based on NavMesh (a system that allows an object to search for a path to a target and move to it) has already been used.
Things were more difficult with the bear in the second location of the game. There was used a controller that works only with a rigid body (physical body), so the beast was stupid and often crashed into trees and other objects. Physical material with zero friction allowed us to avoid serious jams, and the bear eventually slipped and continued to pursue us. Here, and in general in areas where you can die, I ran into the most serious problem for me - the launch of death and restart. At the time of death, it was necessary to take into account all the current states of the character: is the flashlight turned on, is the card open, is the knife activated, etc. It was also required to preserve the values of health and all resources and then, everything that was activated, it was required to deactivate and start the animation of the camera falling. After darkening the screen, it was necessary to return everything and read the stored values. In fact, there are no big difficulties with the proper approach, but in my case a lot of bugs popped up: either the knife remained in my hands before my eyes when the bear attacked, then the card was not removed - everything like that. In addition, you never know how a player can lead himself at this moment, where he will run and in what conditions a bear can lead, which can get stuck somewhere or, for example, attack us through the wall. In general, there are many nuances that you will not immediately foresee. attack us through the wall. In general, there are many nuances that you will not immediately foresee. attack us through the wall. In general, there are many nuances that you will not immediately foresee.
The interaction of our character with objects was realized using the Raycast ray. All interactive objects were tagged with the Subject tag, and when the beam hits them, it activates the backlight (the mesh is an indicator with highlighted edges) and includes a script that is already responsible for what action we can take with this object, for example, pick up an object, read a note or open the door.
For interaction, initially there were plans to make full-fledged hands that would reach for objects, this would create a more obvious effect of presence. But this option represented for me the great difficulty of implementation and the prospect of having a whole “bundle” of bugs in the future, so there were only hands that carry already selected items. There is a prefab with small handles in front of the camera, in which all the objects are already present (camera, knife, ax, etc.). When you select an object during the game, the desired one turns on, and the unnecessary ones turn off.
An interesting point was related to the animation of the characters talking. The technique is primitive, but I thought of it myself, proud, yeah. At first, I thought that when communicating the characters, I would have to start the animation of the opening of the jaw in a random order with each phrase. But then it occurred to me that a script could read the volume level of the soundtrack at the time of playback and transfer this level to a float value that is already responsible for the position of the jaw of the hero. Ultimately, the jaw automatically opened when pronouncing words to the beat of the sound file. This greatly simplified the task, although it looked too "machine".
Optimization is a very important part of development, on which how smoothly the game will work on different hardware depends on. I will touch upon the optimization of the visual component of the project. There are several useful methods for this: groups of lods, culling is occluded, clipping objects at a distance. LOD Group should be used in the case of “heavy” high-poly objects. To do this, create several meshes with a different number of polygons. The farther the camera is from the subject, the more simplified the model is drawn in the frame. For example, for 35MM, lods were used in models of cars, characters, and some trees. Usually 2-3 lodes were made, among which each subsequent mesh had almost 2 times less polygons. For clarity: the original car model consists of 15 thousand polygons, the first LOD already has about 9 tons. (the number of ribs decreases, small parts such as hinges, interior parts are removed), the second LOD reaches 5 tons already (door handles, mirrors inside the cabin are removed, the geometry becomes even easier). Further in the same vein. For lods, by the way, one interesting trick was used. When we bake lightmaps for an object with lods, we have to bake for both objects. In order to reduce the baking time and save system memory, I used a script that automatically transferred the assigned lightmap with all the coordinates from the parent object (zero LOD) to all other lods. one interesting trick was used. When we bake lightmaps for an object with lods, we have to bake for both objects. In order to reduce the time for baking and save system memory, I used a script that automatically transferred the assigned lightmap with all coordinates from the parent object (zero LOD) to all other lods. one interesting trick was used. When we bake lightmaps for an object with lods, we have to bake for both objects. In order to reduce the time for baking and save system memory, I used a script that automatically transferred the assigned lightmap with all coordinates from the parent object (zero LOD) to all other lods.
The second optimization method is Occlusion Culling. This is a mechanism in which everything that is not in the camera’s field of view or is closed by another object is cut off. For example, when we go into the room, behind the wall we no longer see many objects on the street, and therefore there is no need to spend resources on rendering them.
Another useful way to simplify rendering is by clipping objects at a distance. This is the first option that I met since the time of the project “Light”. A script is hung on the camera that adjusts its rendering distance for each layer. In my case, three categories of layers were specially created, with small objects (household items, hammers, bricks and small debris), with medium and slightly higher than average sizes (teapots, bushes, flower pots, small lampposts, etc.). Three categories were assigned distances: 40, 80 and 120 meters. Once beyond the specified distance, the camera stopped rendering the corresponding object. The option is very convenient and effective, since small props can no longer be seen from afar, and therefore, rendering them makes no sense.
Most of the sounds for the game were taken from free libraries from the Internet. Usually I downloaded the necessary options, and then combined and mixed them in Adobe Audition. In general, there is nothing much to be said about this part of the work, because it is a rather routine, boring process and is not particularly attractive to me. By the way, the work of introducing sounds, scoring cut scenes, adjusting sound files so that the desired sound plays at the right time - all this probably took one fourth of the total time working on the game. The only pleasant moment was the introduction of music, on which the cool and extremely talented composer Dmitry Nikolaev worked. I am very pleased with what he did, because by and large, I did not know exactly what I wanted to hear. But Dmitry felt the mood very well, which was laid down in the project and implemented it in the form of atmospheric ambient. It turned out something fantastic, mysterious and melodic.
Another interesting step was working with voice acting characters. Despite criticism from the outside, I am still pleased with the result and I believe that the actors did their job very well. By the way, the main characters voiced by Vsevolod Petrykin and Alexander Bragi, for which many thanks to them.
In general, there were no serious problems with the sound, although, after the release, a rare bug was discovered that I still could not overcome, because I still did not understand its nature. Sometimes part of the sounds ceased to play, or sounded with some strong echo effect. When talking to a hero, a voice could suddenly disappear and, in the same way, suddenly recover. There were conjectures associated with a large load and a large number of sounds playing at the same time. There were also assumptions about the connection of the bug with the reverb zones, but this is not accurate.
That’s probably all. A lot of time has passed since the development, some things have been forgotten, and some have become completely irrelevant, but I hope that the article will be useful for someone and may provide answers to some questions. Thank you all and good luck!