Spherical Practices in Godot
The game Relight created as part of the recent Go Godot Jam 2, in about 8 days. The gameplay consists in the fact that you need to switch dimensions in order to pass the levels. In one world you need to catch sparks, in another to accumulate a charge from the spheres, and in the third to collect hard-to-reach resources or find teleportation points. Below you can watch a video with fragments of gameplay:
Now let’s see how the project works inside:
Sphere and basic elements
Conceptually, all movement in this game is actually rotation. That is, the planet is in the center of coordinates, and various objects moving along it rotate around the same center. These objects are removed from the center by a certain radius (in order to be above the surface of the planet).
Thus, the character moves forward, rotating locally along the X-axis, or turns to the sides, rotating already along the Z-axis. Various collected entities and enemies are also placed on the planet through turning at different angles.
For some optimization, I decided to combine two levels on one model of the planet. At least for beginners. Thus, there were two planets in total (one for levels 1-2, the other for 3-4), and levels 5 and 6 reuse each of them entirely.
The planets were modeled in Blender with subsequent conversion to .obj for uploading to Godot. I exported the collision meshes separately (without unwrapping, which they do not need) and already in Godot made a collision mesh out of these meshes.
To detect a collision with obstacles, the character ultimately has three raycasts (straight ahead and two almost straight, on the sides), which are enough to avoid situations of leakage through the walls.
Basically, the game uses the seventh iteration of the idea of combining several simple mechanics in one general concept tied to a single scale, the meaning of which changes in different dimensions. The first prototype was written in Blitz3d. Then a more polished flash version, already in 2D. You can find video clips of them here: Blitz3d , Flash
After some time, a slightly different variation appeared, already on Unity, where, along with the dimension, the growth of the character also changed, or rather the height with which he interacts – in the lower dimension, the shape of a wolf, on average, human, and in the upper dimension, a bird. That made it possible to make low passages, where you can only pass in the shape of a wolf, as well as hang something high so that only a bird could pick it up.
Another variation I wrote on Godot, where there was no scale, and had to be controlled by a flying joystick inside some volume-levels. Here the mechanics were based on tricks with engine CGS objects (which support boolean operations). In one world, matter existed only near the player, in the other, on the contrary, it was absent in a certain area from him. And in the third world it was simply quite dark and it was there that it was necessary to find a way out with a flashlight.
Further in the project on Unigine, a certain more simplified concept was used – there are two dimensions, not quite connected by a scale, but to pass one way or another, you need to switch.
In the PlayCanvas framework, I also wrote prototype browser 3d game, this time again with a scale, but now the character was physical – the lower part rolled and jumped like a ball. In the upper dimension, with the passing of time, new paths were simply added there, along which one could go to new areas.
In the current game, Reight, the concept is roughly back to the original version, but with a couple of little things from other iterations, to which the idea of moving around the sphere and related game design solutions were also added.
The lower dimension is considered the immaterial world. If you gain mass from catching sparks, then you can materialize in local reality and the scale will reflect the standard of living. And if you gain even more mass, then the character flies out of the body to the astral plane, in which you can only be for a certain time and the movement here is not real – when disconnected, the character will return to the body, in the place where he left him. But if you find special beacons in the astral plane, you can teleport your material shell to them.
Intelligence of enemies
In order for the opponents to be able to chase the character on the sphere, I made them some kind of “fishing rods” from raycasts. They also have several states – in one, the enemy simply revolves around its axis, trying to hook something with its “fishing rod”. If a character is found, then the enemy moves in that direction.
Initially, the opponents moved on the surface, but then I visually made them flying. That is, in fact, they remained ground-based, but in order not to prescribe behavior for tracking collisions with walls, the ground part became invisible, and instead a model with a propeller hanging from above is drawn. At the same time, enemy raikasts do not react to walls (although they touch them), tracking only the player.
An additional short raycast revolves around the enemy and deals damage to the player when it is detected.
The main interface screen is always visible in the game – the scale and other indicators are displayed there, including the settings button. There are also several additional screens – the start of the game, the interlevel transition, the end of the game and the pause screen (aka the settings screen).
The settings button (ButtonEsc) is located outside the individual screens so that it does not overlap during pause. In addition, it was necessary to add a little bit to it processing the signals of pointing and moving the mouse, in addition to the click signal, so that by clicking on it the user does not switch to the upper dimension (because the left and right mouse buttons can also change the dimension).
Inside the main screen (GameUI) there is also an animated toast notification block, which displays some tips at the beginning of each level and explanations for some not too frequent game moments. For example, a message that the battery is charged and you need to look for a portal to the next level.
Textures and materials
Since there are three dimensions, I had to model three characters. Although one is made quite simple – two rotating rings. Others are created on the basis of an old sketch of a light bulb hero, for which I have already made both a color picture and a sculpt. And now here’s a low poly based on two variations.
To reduce the amount of material, I have combined various elements with a transparent background into a couple of atlases. One contains some interface elements, the other contains everything else for use in 3d space. The atlas for 3d elements, in turn, was included in two seemingly identical materials – the difference is only in some settings. One material is for particles, so it unfolds to the camera, and the other is for those elements that do not need to unfold (for example, grass).
We also needed to make our own planes, which would contain unique sweeps that refer to the desired area of the atlas. An important point – for particles, these planes had to be rotated in a special way before exporting from Blender so that the game engine would display them correctly in billboard mode. In the Cull Mode engine itself, the material for the particles should be set to Disabled (as if the material is double-sided), since if you leave Back, then there is a possibility that on some hardware the particles will be billboards on the wrong side and will not be visible. Most likely this will not happen, but on a laptop with a slightly crooked driver, I caught such a nuance during tests.
The grass is placed through an intra-slide tool multimesh… To do this, I opened planets in Blender and cut off those areas from them that should have been covered with grass. Then I exported it to .obj, threw it into Godot and indicated it as a target surface for multimesh.
Since there are three different dimensions in the game, everyone needed their own lightmap (HDRI). Godot supports the .exr format, so to reduce the size, I rendered the hdri images in Blender and saved them in .exr. In general, they still weighed a lot compared to other game content, so for the web version I re-rendered the saved scenes again, with a lower quality (setting the size to 1024 by 512 instead of 2048 by 1024).
I did the sweeps for the planets in a compact way – I marked the hexagons with seams, but since the whole sphere cannot be assembled from hexagons alone, triangular and other pieces also formed. I collected all this in groups and thus packed most of the space into one hexagon and several smaller fragments.
After a crust of a higher landscape was squeezed out in the planets, another line of walls was formed. I unrolled them onto ribbons and then packed them like an “accordion” on top of each other, so that in the end I got one square. Some individual parts of the top of the planet were completely deployed.
As a result, both planets have only one material and there is still room on the uv-surface. There is more than one texture in this material, since I also made the base of the normal map based on the base color texture using the Materialyse program, correcting it further manually. And also a homemade roughness map so that it glares a little in certain places.
All the main logic revolves in the main script of the scene (MainScript), which does not go anywhere between levels. The levels themselves are loaded and unloaded along the way.
What MainScript can easily reach through the hierarchy – it processes itself. For example, update the numbers and scales in the interface. It, in turn, receives signals from something fixed – like interface buttons. The main script also contains methods that are called externally from objects unknown to it. For example, from the next spark collected by the player.
Other objects refer to the main script through a global link to it, which it puts at the very start in a singleton with global variables. Thus, the collected spark calls the “Collect spark” method in the MainScript body, and it will itself update the scale, play the desired sound, and so on.
Collected spheres contain a Bonus script that handles player collisions. The sfxBonus script hangs on the prefab, which remains after collecting the sphere, and when the animator launched in this prefab stops, it sends a signal to this script, processing which the prefab will self-destruct. The BonusTime script controls the orbs that the player must collect in the third dimension (since this is handled slightly differently).
The LevelOperator script is attached to the prefabs of the levels and manages their internal affairs, switching dimensions within the level, if instructed from the main script. For example, when the player switched from the middle dimension to the lower one, then you need to hide the layer with spheres. And not just hide it, but also temporarily move it some great distance. This is done in a simple way to temporarily remove the sphere colliders from the player’s reach, because in Godot, hiding an object (or a whole branch) does not mean turning off the colliders inside it.
Some scripts simply handle various specific collisions with the player, for example, Portal – ends the level, including the non-game mode and showing the “continue” screen. Or FlyEnd, which teleports the material body of the character to the point at which it is installed.
Well, that’s all. Thank you for the attention.