In this article I will tell you about interesting and slightly unobvious moments of video game development in a short time: according to the competition regulations, a workable demo must be submitted within a week, and a release within two weeks. The article is intended for those who have already played with Unity3D, but have not yet done any projects on this game engine more complicated than HelloWorld.
Picture to attract attention - a screenshot of the game.

The idea of the game, the concept
I love games. As a phenomenon. And the development of video games is a very interesting spike of the art of programming, mathematics and art. Unfortunately, I still can not afford enough time and resources to engage in game development in full, commercially-successful measure: despite the substantial simplification of the process of creating games, experience has shown that a more or less holistic game on mobile phones receives less attention than the two-day hello -Port three-four years ago.
')
However, with a two-week competition of games and the demand is small, then you can try.
At the very first stage of development, you need to choose the concept of the game. Genre, gameplay, similar games, than your game will be better than existing ones. And the most important thing at this moment is not to overestimate your strength, because the rules are very tough in terms of time, and apart from the competition it is very important to adequately assess your capabilities and capabilities of your team - otherwise the development may be delayed, and the enthusiasm will be lost.
In this case, I chose a rather simple genre - an arcade from the top, with elements of hack & slash. I was inspired by the following games (I think many of them played, saw, are somehow familiar):
Sword of the stars: The pit
A turn-based game in the Rogue-like genre, one of my favorite bagels: extraordinarily simple, but at the same time exciting. From this game, I borrowed the idea of generated mazes and random spawning of enemies.
Crimsonland
Arcade with top view. From this game I borrowed the number of opponents and the dynamics.
As a result, I came to the following gameplay: the player, controlling the character, runs through the maze, shoots enemies, collects power-ups, interacts with the environment of the level, searches for keys and goes to the next level, where the adversaries are more evil, and the mazes are more intricate. Choosing a development environment, tools, platform.
What can we choose from? There is a huge number of gaming and graphics engines, but the most popular at the moment are Unity3D, UnrealEngine4, cocos2d, libGDX. The first two are used mainly for desktop "complex" games, the last two - for simple two-dimensional games for mobile phones. Also for simple games there are mouse-oriented designers.

Despite the fact that I am a C ++ zealot and endlessly in love with this language, I am aware that this language is not what a game should be written in two weeks. Unity3D provides the ability to write scripts in C #, JS and in its own Python-like Boo-script language. Most programmers use C # for obvious reasons. Yes, and I liked Unity, at the level of sensations.
Development, architecture, interesting moments
Levels
The game involves running through the maze, and the maze must be generated. At first, I decided to come up with an algorithm myself, at first glance, the business seemed simple: I split the field into two unequal parts horizontally or vertically, creating a passage from one part to another. Then I recursively pass through the left and right "island" and also process them: if the dimensions allow, I divide them into two parts, if they do not, then I leave the room as it is.
It turns out such an unbalanced binary tree with all the consequences - from any room to any other you can go through a single path. This is not very correct from the point of view of interest in the study of this labyrinth, and the labyrinths themselves had an obvious square-nested appearance with a tendency to elongated, narrow rooms, resembling something like the construction of residential areas in Russia, no matter how I am adjusted to the constants. Unfortunately, the original version of the algorithm has not been preserved, and I cannot show its results.
Illustration of the original maze generation algorithmThen, I decided to look for an generation algorithm on the Internet, found it on gamedev.ru, written in C ++.
The essence of the algorithm is as follows: the desired number of n rooms is placed on an empty field. For each new room, its size is specified randomly within the specified limits, then the room is placed on random coordinates of the playing field. If the created room crosses the previously created rooms, then it should be recreated in a random place with random sizes. If the room cannot find a place through the main m = 100 iterations, then the field is considered filled, and proceed to the next room.
void Generate(int roomsCount) { for (int i = 0; i < roomsCount; i++) { for (int j = 0; j < 100; j++) { int w = Random.Range(3, 10); int h = Random.Range(3, 10); Room room = new Room(); room.x = Random.Range(3, m_width - w - 3); room.y = Random.Range(3, m_height - h - 3); room.w = w; room.h = h; List<Room> inter = rooms.FindAll(room.Intersect); if (inter.Count == 0) { rooms.Add(room); break; } } return; } }

Then, when the required number of rooms is created, it is necessary to connect them with aisles: We go around all the created rooms and calculate the path according to the algorithm for finding the path A * from the center of the processed room to the center of each other room. For the algorithm A *, we set the weight for the cell of the room unit to 1, and for the empty cell to k. The algorithm will give us a set of cells - the path from room to room, in this lies the magic: increasing k, we get a more confusing maze with a smaller number of corridors, increasing - we get a labyrinth “stitched” by the corridors. This effect is achieved due to the fact that with a larger weight of an empty cage, the path finding algorithm tries to pave the way around, rather than “punch” the road.
void GenerateAllPassages() { foreach (Room r in rooms) { APoint center1 = new APoint(); center1.x = rx + (rw/2); center1.y = ry + (rh/2); center1.cost = 1; foreach (Room r2 in rooms) { APoint center2 = new APoint(); center2.x = r2.x + (r2.w/2); center2.y = r2.y + (r2.h/2); center2.cost = 1; GeneratePassage(center1, center2);
Green tiles indicate the result of finding a path from each room to each.

Then comes the “decoration” of the labyrinth, the search for walls.

The algorithm is extremely non-optimized; on the i7 notebook, the generation of the maze 50 * 50 (but with the creation of game objects representing the level) takes about 10 seconds. Unfortunately, I can’t even approximately estimate the complexity of the level generation algorithm, because the complexity of the wave path finding algorithm depends on the coefficient k - the more confusing the labyrinth, the more A * is prone to exponential complexity. However, he creates exactly such mazes as I would like to see in my game.
Now, we have a labyrinth, in the form of a two-dimensional array. But the game we have is three-dimensional, and it is necessary to somehow create this maze in three-dimensional space. The most obvious and wrong option is to create a game object - a cube or a plane - for each element of the array. The disadvantage of this approach will be the fact that each of these objects will be integrated into the event processing system, which already works in one CPU flow, significantly slowing it down. The scene from the screenshot with a large number of rooms on my computer seriously lags. It may seem that this approach will load the video card with draw-calls — but no, Unity perfectly batches various objects with one material. And yet, this is not worth doing.
The correct approach is to create a single object, with a mesh based on a two-dimensional array of level tiles. The algorithm is simple: for each tile, we draw two geometry triangles for the render, two geometry triangles for the collider, and set texture coordinates to the points. There is one subtlety.
public void GenerateMesh(Map.Tile [,] m_data, Map.Tile type, float height = 0.0f, float scale = 1.0f) { mesh = GetComponent<MeshFilter> ().mesh; collider = GetComponent<MeshCollider>(); squareCount = 0; int m_width = m_data.GetLength(0); int m_height = m_data.GetLength(1); for (int x = 0; x < m_width; x++) for (int y = 0; y < m_height; y++) { if (m_data[x,y] == type) { newVertices.Add( new Vector3 (x*scale - 0.5f*scale , height , y*scale - 0.5f*scale )); newVertices.Add( new Vector3 (x*scale + 0.5f*scale , height , y*scale - 0.5f*scale)); newVertices.Add( new Vector3 (x*scale + 0.5f*scale , height, y*scale + 0.5f*scale)); newVertices.Add( new Vector3 (x*scale - 0.5f*scale , height, y*scale + 0.5f*scale)); newTriangles.Add(squareCount*4); newTriangles.Add((squareCount*4)+3); newTriangles.Add((squareCount*4)+1); newTriangles.Add((squareCount*4)+1); newTriangles.Add((squareCount*4)+3); newTriangles.Add((squareCount*4)+2); int tileIndex = 0; const int numTiles = 4; tileIndex = Mathf.RoundToInt(Mathf.Sqrt(Random.Range(0, numTiles * numTiles)*1.0f)); int squareSize = Mathf.FloorToInt(Mathf.Sqrt(numTiles)); newUV.Add(new Vector2((tileIndex % squareSize)/squareSize, (tileIndex/squareSize)/squareSize)); newUV.Add(new Vector2(((tileIndex + 1) % squareSize)/squareSize, (tileIndex/squareSize)/squareSize)); newUV.Add(new Vector2(((tileIndex + 1) % squareSize)/ squareSize, (tileIndex / squareSize + 1)/squareSize)); newUV.Add(new Vector2((tileIndex % squareSize)/squareSize, (tileIndex/squareSize + 1)/squareSize)); squareCount++; } } mesh.Clear (); mesh.vertices = newVertices.ToArray(); mesh.triangles = newTriangles.ToArray(); mesh.uv = newUV.ToArray(); mesh.Optimize (); mesh.RecalculateNormals (); Mesh phMesh = mesh; phMesh.RecalculateBounds(); collider.sharedMesh = phMesh; squareCount=0; newVertices.Clear(); newTriangles.Clear(); newUV.Clear(); }
For the texture of the floor, I use altas of four different tiles:

And I noticed that a distribution looks more natural when there are more tiles and less of them. In fact, there is no such subgame, where, on average, on every second square meter of the floor there is scattered exactly one skeleton of fish.
To create such an uneven distribution, I use the following line:
tileIndex = Mathf.RoundToInt(Mathf.Sqrt(Random.Range(0, numTiles * numTiles)*1.0f));
It provides a fundamental dependence of the number of tiles.
Then, the texture coordinates of the selected tile are set in the list of texture coordinates. This block of code works on tile sets with the number of tiles equal to the power of two.
The result is visible on the KDPV - quite naturally and unaffected, despite the fact that the tiles still have joints, and there are only four of them.
At this stage, we have the geometry of the level, but we need to “revive” it by adding enemies, power-ups, cutscenes, and some other game logic, if necessary.
To do this, create a Singleton Manager responsible for levels. You can make the “right” singleton, but I just added an object manager object to the scene where the gameplay directly takes place and made the manager fields global. Such a decision may be academically incorrect (and has the name Hindu Singleton), but with his help I achieved that Unity serializes the manager's fields and allows you to edit them in the real-time object inspector. It is much easier and faster than changing the settings of the ka; logo level in the code, allows you to debug the balance without turning off the player. A very useful feature, I used it more than once, for example, to create objects of a class responsible for dialogs:

A set of game levels is an array of objects that describe each level. The class that describes the level is:

The level is created as follows: the level manager receives an Awake () message, reads the value of a global variable — the level number to be loaded (initially it is zero, increases as the protagonist moves deeper into the maze), selects the desired level from an array of Level objects. Then, if the level is procedurally generated (generated == true), then the construction and decoration of the maze is launched. The construction of the maze is discussed earlier, and the decoration is due to the performance of delegates decorations and decorationsSize. In the first delegate, I add procedures that take no arguments, for example, adding to the level of a ladder, and in the second, with an argument, for example, adding powerings to the level (to maintain power density equal to the density of power per square meter of the maze, the amount of bonuses should be proportional to square maze-sized maze or enemies.
The following function places the enemies using an array of structures from two fields - the enemy's prabab, which should be created, and the weight of this opponent. For example, if the array is:
{{Mob1, 5.0f}, {Mob2, 1.0f}}
That mob like Mob1 will spawn with a probability five times more than Mob2. The number of types of mobs can be arbitrary.
With this feature, you can randomly with a certain probability to place the same artifacts in the chests, the effects of unknown potions and anything!

Then, as for the generated one, as well as for the hard-coded one, we instantiate the prefab level, if any. For a hard-coded level, this is the level itself, with its geometry, characters. And for generated it can be a dialogue or a cut-scene.
Characters Scripts Interactions
Despite the fact that Unity3D uses C #, it is supposed to work not with the usual OOP, but with its own pattern based on objects, components and events.
If you have ever launched Unity, then you need to understand these entities - game objects have several components, components are responsible for certain functions of the object, which may include user scripts. Messages (user or service, such as Awake (), Start (), Update ()) are sent to the game object and reach each component, each component processes them at its own discretion. The pattern familiar to Unity comes out of this: instead of inheritance, you should use a composition of components, for example, as shown in the picture.

There is an object of the type “Mob”, with components that perform special tasks, called characteristic names. The EnemyScript component is responsible for the AI, sends Shoot (Vector3 target) messages to its game objec- tion to fire, and MoveTo messages (Vector3 target) to send the mob to this point.
The ShooterScript component accepts “Shoot” messages and fires, MoveScript accepts MoveTo messages. Now, let's say we want to make a mob that is similar to this one, but with a modified behavior logic, AI. To do this, you can simply replace the component with the AI script with some other one, for example, like this:

In the usual C ++ C ++ OOP, this could look like this (in Unity, this is not the case):
class FooEnemy : public ShooterScript, public MoveScript, public MonoBehaviour { void Update() { MoveScript::Update(); ShooterScript::Update(); Shoot({ 0.0f, 0.0f, 0.0f}); MoveTo({ 0.0f, 0.0f, 0.0f}); } }; class BarEnemy : public ShooterScript, public MoveScript, public MonoBehaviour { void Update() { MoveScript::Update(); ShooterScript::Update(); Shoot{ 1.0f, 1.0f, 1.0f}); MoveTo({ 1.0f, 1.0f, 1.0f}); } };
In Unity, a very user friendly has been done and is muscularly oriented, in my opinion, this is good, since the variety of enemies is more content of the game than the “programmer” part.
The communication between different objects occurs in a similar way: when the bullet object processes the collision overhead message, it sends a “Damage (float)” message to the object it collides with. Then, the “Damage Receiver” component accepts this message, reduces the number of hit points, and if the hit points are below zero, it sends itself the “OnDeath ()” message.
Thus, we can get several prefabs of opponents with diverse behavior.
Lifebar
The project uses a lot of interesting and non-obvious for the novice developer techniques, everything in the framework of the article will not work out, so in this article I will consider a simple implementation of the life bar.


In all games, there are opponents who have to be full of HP, and in almost all games there is a hero who spends his energy or mana to get rid of HP. For my solution, you will need a child game object (say, you can call it Healthbar) with a quad, which will display the desired strip. This object should have a MeshRenderer component with material that can be seen on the thumbnail (a square with the left side green and the right red side).
Also, for the correct display of the conceived it is necessary that the texture of the life bar has a point (POINT) filtering.

The idea is this: we set the tiling on the horizontal axis to 0.5, and the quad will display half of the image. When the offset on the same axis is set to 0, the left half of the square (green) is displayed, when in 1 it is the right, in intermediate values the life bar behaves the way the life bar should behave. In my case, the texture of the lifebar consists of two pixels, but this menthod allows you to display beautiful, hand-made lifebars.
The component responsible for processing and displaying damage, in the case when it needs to update the state of the lifebar, executes the following code:

The method for displaying mana is “smarter”, because it also operates with an offset along the vertical axis - in this case, a four-pixel texture is used in which the upper part corresponds to the active strip and the lower part to the passive one (for example, during reloading, overheating of the weapon or overworked sorcerer) .


Results

Unfortunately, I lost the contest, taking a shameful place in the middle of the list of participants.
The error, in my opinion, is this: very little content. Having spent a lot of time designing spherical scalability in vacuum, on the penultimate day of the competition I realized that there is no content in the game at all. Levels, plot, sprites, enemies, bonuses and all that. As a protagonist in the game runs a placeholder, inserted for the sake of laughter. As a result, the game turned out to be ten minutes of interesting, vigorous gameplay, with various enemies, with the possibility of game development, but without history and without graphics. A zero level with a square platform suspended in a vacuum immediately informs the player that the game is raw and unfinished. It was not what was required in the competition. The jury wanted to read the story, look at pixel art and listen to music, not play games.
Also, UnityD is about assets. Of course, it was more interesting for me to develop bicycles, which I did - I didn't use any assets, all the scripts and resources were my own. But you don’t have to do that if the work goes to the result, not to the interest.
In general, the defeat somewhat affected my drive impulse. We need to get some rest, and then look at the project with a new look: if I find the enthusiasm and prospects for the project, then maybe I’ll finish it and put it in the greenlight. Otherwise, I will lay out the source code in open access, and I will draw up interesting moments, like the same creation of mazes, in the form of a paid asset in Unity.
I hope the article was interesting and informative.
PS: You can familiarize yourself with the game by downloading it
from this link .
Or, in the Yandex browser
here .