About architectural failures, planning errors and other jambs in the development of the game.
For me Bender started with topics on the gamedev.ru forum about finding a programmer for a puzzle. I was going through ideas for mini-games to add to my rpg, and this puzzle seemed very familiar to me because of the similarities with Sokoban, but I could not find a copy anywhere.
I liked that the game designer immediately recorded a gameplay video with animations and music, so I decided that with such a high-quality approach, I could take on the toy. Moreover, it is not so complicated – all the action takes place on one screen, the video demonstrates all the functionality – in fact, this is the TK. The only mechanic that seemed “difficult” was turning the level with a wrench, but I figured with a bit of math I could handle it
At that time, I was looking for a second programmer as a partner, so that I could jointly cut rpgshka in my free time, so another plus of this project was the opportunity to attract a programmer to the team, first on a small project, and if everything goes well, then continue working on a big one.
Development took almost two years. Although at the beginning I planned to do the puzzle in a few free weekends.
You can, of course, attribute the final difference to the fact that free weekends did not happen so often, but the main problem was the initial lack of clear planning. By this, I mean not just a list of tasks in the tracker, but estimates of the time to implement each feature.
For example, the task “level selection window” – time estimate – 4 hours. Having already calculated only the amount of time to complete all such tasks, one could understand that working at best 6 hours a week, the game will have to be cut for at least several months. And after it becomes clear that there are not 6 hours a week, but 4, and not every one, and the time estimates for tasks are on average 2 times lower, then it will take a year to cut the game.
So we should not have neglected the more careful design of the list of tasks, the correct assessment of the timing essential developer skill and you need to train it as often as possible, especially over long distances.
And it would also be cool to structure the tasks into monthly sprints – so that there are some deadlines for the functionality.
It is naive to think that a task tracker is enough for management. It is known that when there are not enough people, then the artist has to be an animator, a game designer is also a manager, but these roles should not be combined, but “alternate”, allocating days for programming and days for management – in this case, the work will not be done to hell, to move as quickly as possible to the “main role” and completely.
As I wrote at the beginning – I had a desire to find a second programmer. At the beginning of development, for those who responded to the topic with the search for a second programmer, I recorded small videos with a review of the already written code, which, unfortunately, were not saved, and gave a task for a small refactoring, thinking that this would just allow you to get acquainted with the project.
Later, when the functionality was largely ready, I gave the task of implementing a wrench, which I didn’t want to do at all because I was too lazy to deal with mathematics.
In both cases, people disappeared without a trace.
What was the problem? A year later, it seems that the choice of the first task was unsuccessful. The wrench was not worth giving at all, because this is the most difficult part of the project, refactoring also does not seem like an adequate first task. Perhaps it was necessary to ask to collect some small window like “Sound Settings”.
Or maybe people are just unlucky. It is unlikely that the problem was in the terrible quality of the code – I think it could just be reported before disappearing 🙂
With the potential goal of finding a partner, I even started a programming blog, but after a short time he concluded that if he himself is not a student or a junior, then it is useless to look for a partner, alas. Pair programming will not work anyway, because the periods of free time, as a rule, do not intersect, and matching skills is completely fantastic.
Now, actually, about the code. He’s truly terrible.
In pet projects, I always experiment, this time I decided to completely abandon the coherence of the code. I thought – what for singletons, what for DI, what for everything in general – let the classes communicate with signals. So what? The game is small, signals allow you to quickly prototype, they can flexibly transfer data between any entities – some pluses. I relied on typed signals, such as MessageBroker from UniRxso or signals in Zenject
Moreover, I had a positive experience with them – the implementation of the quest controller through them. The code threw out events of some key events, such as killing mobs, or crafting items, and the quest controller subscribed to them and increased the internal counters of active quests. Thus, the quest manager, instead of depending on a million controllers, subscribed only to the necessary events. It was nice.
But if you use signals for communications between the UI and controllers, as well as between the controllers themselves, signals multiply incredibly quickly. And it is not clear where to store the types of signals – what kind of separate folders should be made for them? Or store next to the controllers that “produce” them? Or maybe vice versa next to the views for which these signals are intended? In any case, it quickly becomes frustrating to open files with tons of signals in order to insert another one.
Another problem is reading code with signals. When I wrote the code for moving the chip, it was obviously clear that first the SwipeSignal swipe was detected, then the MoveSignal movement direction was determined, then the StartMovingSequence movement animation itself began, then the FinishMovingSequence animation ended, and finally the end of the NewTurnSignal move. But refactoring this code, you can easily forget that you need to fire FinishMovingSequence. And if this movement was started by a hint, then both StartHintMove and FinishHintMove need to be put somewhere …
The problem is that these signals are difficult to document. To connect classes, you can at least draw a diagram, and in such an architecture, instead of connections, there will be signals and, most importantly, their call sequence, cannot be reflected in the diagram.
In addition, the call stack worsens, debugging becomes more complicated, it becomes more and more difficult to invent signal names with each new one … So the good old dependency dropping in Inits or in constructors and simple method calls, or standard subscriptions to events like public event Action are better <> OnSomethingHappend.
As the implementation of the main game controller, I chose the state machine. A lot of good things have been said about her., and in gamedev it is often recommended to use it for the states of the game itself, and I have never used it like that. Usually only in the UI for a couple of states with animated transitions between them. Decided to catch up.
In the puzzle, based on the initial video, there seem to be only a few states – the actual expectation of the player’s move, the animation of the move, and three states for each instrument. Total 5 states.
I didn’t want to make classes for states, because in fact I only needed switching between them, so I chose Stateless.
In the process, it became clear that the pause state was forgotten, and the state with the wrench was divided into two separate ones. At the same time, there are several ways to get from one state to another, for example, through the cancellation of the move, which I also did not think about at the beginning.
resulting the file with the state machine turned out like this. From it it is completely incomprehensible how the states are interconnected and from which to which one can go and, most importantly, how. Horror.
And all because the state machine is an incredibly viscous pattern, to which it is good to rewrite existing code, knowing that it will not change anymore, but here is code that can be expanded, i.e. new states are added and the transitions between them become more complicated – writing on a state machine is extremely difficult. And this applies not only to such a stateless state machine, but also to a state machine, where each state has its own class.
Or, if the state machine still cannot be avoided, as, for example, in the controller of some platformer character with different types of jumps, sticking to surfaces, slides, etc. – have to do super documentation. It is a pity that no one has yet written a plug-in for the rider or visual studio to add “drawings” to the fields, and not just text comments.
It was, of course, good. All complex code – be it turning a part of the level with a key, or triggering a few special objects such as a magnet or a fan – was covered by unit tests.
And although in all sorts of books about TDD they write that only public methods need to be tested, but in my opinion this can only be applied in the backend, where there is a public api. And in a unit, public methods often contain some other animations and other not “pure” functionality, which requires a lot of mocks.
So I took out all the bug-intensive methods of controllers for transforming models into separate pure static functions, which you can test without mocks – passing the entire level state through a parameter and getting a new state at the output.
The second positive the game is still out. But am I happy with the game? Unfortunately no.
Because making a game is only the first step, and the second step is polishing it. Therefore, we decided to turn off the sounds, since we couldn’t find normal ones in a short time, we didn’t do the second iteration of the graphics, because we were just tired, we spent only a little time balancing the difficulty of the levels and didn’t add any visual effects and animations.
And in a good way, these “little things” need to be laid as much time as for the development of the whole game. The managerial technique of multiplying time estimates, named by programmers, by π to get a realistic development time is the best fit here.
But the gameplay itself, in our opinion, was a success – a hardcore puzzle, to solve which you need to really think, and not just go through the options.