Level design is one of the final steps of developing a game. It consists in creating maps/terrains and filling them with objects, NPCs, obstacles, enemies etc. Most of such elements is just decorative or are placed to be an easy challenge. They are merely graphical objects equipped with colliders or defined by a specific volume of a map.
However, the remaining ones require a bit more designer's attention (and effort). The opponents must be AI-driven, the NPCs have to have their dialogue lines configured and interactive objects must be properly scripted. We do not hard-code such things. Tinkering with them should be easier than writing the game code and possible for those who do not have access to the code.
Simple dialogue systems allow for organizing texts in tree-like structures with capability to add a predefined function in actual game code to launch. For example, it can unlock a certain feature, give the player a reward (item), grant experience or activate a subquest.
The AI usually concerns more or less complicated algorithms that analyze the situation on the map and decide what action to pursue next. It is often the case, when it is configurable with merely a few parameters such as aggresion level, assignment to a particular group or... ability to cheat (yep, AI often takes an easy route and do not follow the human player's rules). More advanced systems give the option to precisely adapt a particular behavior to a given situation and selecting the parameters of the game environment that influence the bot behavior. A perfect example of such an advanced solution is our Polish system called Grail.
The whole process of making a map and placing objects can be tackled in many ways. If we use Unity engine, then the simplest one is to use the built-in scene editor. It enables to place objects in the world space made from prefabs prepared earlier. Oftentimes, however, the programmers implement their own editor which can immensely boost designers work. This was our approach in Skullstone.
The underground scenes were possible to be created very fast thanks to their specific nature. The map is a grid in which we mark which cells are corridors. The walls, the floor and the roof are generated automatically. In addition, each cell may contain an object (whether it is interactive or not) on the floor and on each of its edges. We click on the cell to select one of the predefined elements from a list, e.g. lever, button, door, spider's web... The structure (representation) of such a map is simple as well. It contains identifiers of object types and their placement - which is - x,y and orientation. What you see on the screen is dynamically created based on such a representation.
During a game, the player may place an object on the floor and we took advantage of this fact when making levels for the game. There was no need to copy an already existing mechanics in the game, so one of the aspects of creating a level is flying over the map in the 'god mode' and placing objects which later can be found by the player. Placing enemies is even simpler - we only have to choose the enemy type from a list and click the respective button in the editor. The mob appears in the cell directly in front of the player character. We can select the mob on the list of all mobs in the current level and set its additional properties such as the group it belongs to or initial behavior (sleep, guard).
Okay, but what should I do to make everything feel "alive" - how to implement the game logic? How to connect the lock and the door? How to make the lever close the trapdoor? The most obvious answer seems to be - write the script for each such behavior. In Unity, we would create and add a dedicated component that will detect whether the switch is toggled on and perform the action on the scene object that is operated by it. Languages such as Lua or Python allow scripting object in a similar fashion. They require their respective interpreters to be loaded by the game engine and properly configured to work with it.
We have done it in much simpler way in Skullstone. Each object has two boolean states: open/closed, on/off, alive/dead etc. We deal with them as we dealt with a two-state machine. The most expected thing is then to transfer the state of an object to another one. As we mentioned earlier, each object is represented as class+x+y+direction, which allows addressing them easily. In order to control a trapdoor by a lever we only need to transfer a state from one object to the other. With this purpose in mind, we have designed our own language to represent this. The basic instruction consists of the adress of the object the action operates on, the type of action (state transfer, lock, timer and so on) as well as parameters. In the case of state transfer, one of the parameters is the adress of the destination object and the value that denotes whether it should be negated or not.
At one point, it became apparent that a script can also be used to set a particular property of an object and not only for operating on states. In this way, we attach texts to the signs on the wall and we configure altars (you will see what they are in the game).
What about if we wanted to define a more sophisticated behavior? What if we neeed to store some data and run a sequence of events? Memory cells are needed and we have them. Each of the two-state objects can be such a memory unit that can accept 0 or 1 value. We hide them in some inaccessible place so the player does not need to see them. Those strikethrough cells are such "memory objects". They have no special logic to them nor interaction, they cannot even be seen! They simply remember the state and can be addressed. A suitable sequence of state-transfer instructions that conditionally lock the state (change of the state is locked if a particular state occurs in another object) has given us the door, which we can open and close only a few times before they are lock for good. Nice trap, isn't it? Especially, if there is a pack of bloodthirsty creatures waiting behind the door.
Some time ago we recorded a video in which we presented our solutions. I think it will be a nice addition to the above article.
I really hope that you will like our unconventional solution and maybe it will inspire someone to create something interesting too. We are eager to read about it.