Godot, 1000 little things
Godot as a whole has a reputation rather as a 2D engine, which was worked out pretty well, but not so long ago the 3D capabilities that have appeared allow you to make three-dimensional games. Especially if you are able to optimize some things yourself, don’t make the game too heavy and complex, and you are also happy with the current rendering options. Well, or you can wait for future optimizations and the appearance of vulkan.
“Out of the box” in the engine there is some physics, including joints and wheeled vehicles. There is no built-in terrain editor, but you can use the plug-in, import from specialized programs, or just like a mesh from a 3D package. In the latter case, for the sake of performance, you will have to cut the landscape yourself into chunk fragments, and most likely make them separate meshes for collisions. As for meshes under the form of a landscape collision - in order to avoid visual inconsistencies, you need to triangulate the model during export from a 3D package. For example, one of the simplest ways to do this in Blender is to export the mesh in collada format (.dae), there is a triangulate checkmark by default.
Sometimes a model from Blender will have to be rotated 180 degrees inside Godot, so don’t be surprised if your terrain is not visible - most likely it will be turned by normals in the wrong direction.
Godot practices the “everything is a scene” approach and is geared to the tree structure of elements. In practice, this means that new objects are added to the level as branches to nodes already existing on it, they can be turned into prefabs and opened separately, as if it were their separate small local world. Thus, it’s very convenient to edit all kinds of saved composite objects, the only moment is that if light was set at your level, then, say, by going inside the character into his local scene, you will not see this lighting and setting up his materials will not understand how they look at the light. This problem can be solved in different ways, for example, by switching to the level window and evaluating the ongoing changes with the character, after saving his scene there. Or just temporarily throw the light source inside the prefab with the character.
Scene with theEnergy object that will be placed on the level for player to collect. As a basis, the Area node is used, to which the collision form is attached, as well as a spherical primitive, inside which another one is embedded.
As for languages, if you do not consider the low-level method, the most common options are the GDScript and C # scripting languages, as well as visual scripting for something simple. GDScript is better integrated into the engine, has more examples, does not require starting an external environment, and in terms of the general structure, everything will be the same as in the C # variant - declaration of variables, call of the initialization function, process cycle, cycle of physical processes and so on. So choosing GDScript as your primary development scripting language makes sense. Changing to local C #, we can only get more accuracy and detail of the recording, losing its conciseness, but increasing intelligibility and control over the situation - curly braces instead of using tabs, line breaks, more formal typing.
Code attached to the aforementioned object (in GDScript). An identifier variable is set up, the value of which can be set in the editor. Next, a one-time initialization function in which nothing special happens (and it was possible to erase it). The following describes a signal handler method that will remove an object.
As for global variables, in order to use them, both in GDScript and in C # it will be necessary to add a global script/scripts to autoload in the project parameters, the variables of which can be accessed globally.
There is also a restriction in Godot - each object cannot have more than one script. But this point can be circumvented, for example, by hanging the second script on a child object.
You can also use a hierarchy to exchange data between nodes in Godot, because objects are attached to each other, like branches to a tree. True, this approach has its pitfalls, because in our game the hierarchy can be dynamically changeable. In addition, referring to the hierarchy within the same scene is one thing, but when you have a scene inside a scene inside a scene, there are some difficulties with this, even if it’s purely in understanding what is happening.
One way to manage all of this and not get too attached to a particular current hierarchy is through signals. A certain number of common signals is already pre-installed - you can look into the object's signal panel to attach a line with the processing of receiving one of them into a script of the same object or another object with a script within the scene. If you need to make your own signal, then this is done like this:
Start the signal
Radiating it in the same script when you click a button or other conditions
Everything with this is great, until you needed to transmit signals from one scene to another. For example, because you code the level from the scenes and you need to know when to destroy the current level and, say, collect the next one.
In this case, right at the time of constructing the level with a code, you can attach a signal handler to the object that will send the signal to the root scene. Thus, we, so to speak, start a spy agent in this generated branch and will listen to what he tells us.
At the time of level assembly, we find a spaceship on it and cling to the signal listener to it, indicating the method in which we will process messages of this signal.
You can also attach some variables to the signal, which can be quite useful. For example, instead of starting different signals on the object, we can do one, but we will send it with different parameters and additionally process it upon receipt.
And here is the description of the method itself, which we started above. Receiving the signal, it processes the variable sent with it.
Another useful thing that reduces the number of unnecessary signals is that instead of sending a personal signal, one object can knock on another so that it sends the signal already wound up in it. For example, an explosion receives a signal that it has touched a player, and in the handler of this signal it finds out whether the player has a self-destruction method, starting it if it is detected. The player, in this called method, sends a signal to the root scene that he is dead. In the script of the root scene, the player’s death signal handler erases the world and collects the game menu.
An event occurs and we look to see if the object that entered the zone has the right method.
There is a method, and it is activated by sending a signal.The owner of the method itself, by the way, does not have to be able to call this function on its own.
One of the useful 3D tools in Godot is the constructive solid geometry primitives. Simply put, these are objects that support Boolean operations — intersection, exclusion, union. In addition to the set of primitives, there is a universal CSG Mesh, for which you can set an arbitrary mesh as a form.
The CSG Combiner dummy is required for use in the hierarchy to control the priority of operations when it is necessary to assemble some complex structure.
A pair of spheres from which child spheres are cut.
The main application of CSG is the convenience and simplicity of prototyping the level statics and some of its elements. In fact, you can make a mesh in a 3D package, and repeat the same resulting forms in it, unless you need to do this outside the game editor.
The next use of static CSG is to simulate destructibility and damage. this requires placing CSG primitives in exclusion mode, like holes, stubs and dents, and then temporarily hiding them, including visibility at the right time. The limitation here is that we can “damage” only the surface of the CSG object, and besides, the “damage” must be initially attached to it as a child or attached via code as a descendant. But this option in terms of configuration flexibility already wins significantly in comparison with the destructible object prepared in the 3D package.
3 cylinders in exception mode are integrated in the bridge. While they have hidden the whole bridge.
If you enable them, the region they exclude is cut.
Next we have CSG moving. Through code, or recorded animation. In general, through this method you can implement some effects, but it is very desirable that such animations do not spin on the stage in a loop. Several animated CSGs can significantly impact performance, and Godot doesn’t optimize all things, and if you don’t cut out animated CSGs yourself, they will continue to consume performance out of camera visibility.
Nevertheless, the effects of animated CSGs are already difficult to replace with 3D package solutions, so you may be interested in using them (at least if you are not going to draw advanced 3D through the code). The main thing is to find them the correct application, it is best to use them pointwise, in certain places, including trigger animation. As the effect of opening the passage or other one-time special effect. Naturally, the simpler the form of CSG objects - the better. And they exert the main computational load precisely in the process of contact in motion, moreover, there isn’t much difference which object to move relative to another.
There’s a moment in the video where a car falls through a hole in a bridge when an animated CSG capsule passes through a CSG Mesh with a model of a bridge.
If you need to replicate a mesh in huge quantities, for example, scattering stones or trees by level, then the MultimeshInstance node is useful here.
After adding the multimedia to the scene, you need to specify on which object to scatter copies and what to take as a model for clones.Along the way, you can choose the size of the clones and their number. When the multimesh is “baked”, it represents a lot of clones, and the goals that he used for his generation can be deleted if they are no longer needed.
A button for selecting multimedia targets appears in the editor window at the top right when it is selected. If you click it, then this window will drop out with the settings for the generation.
Here we have two objects added to the scene as MeshInstance nodes. Next, we’ll clone the left object to the right.
We get multimesh. In this situation, 10,000 clones were set in the settings. By the way, if there were 3-4 thousand of them, then visually the result would not differ much.
The “baked” multimesh can be moved anywhere, and besides, it has the Visible Instance parameter, which initially displays the number of clones specified during generation. By changing this value, you can control how many clones are currently displayed. By default there is -1, but this is a special value for displaying the maximum of clones. If you put 10, there will be 10 clones. If 100, then 100. If the maximum was 50, then at 100, and at 1000, and at -1 they will remain 50.
This multimash feature can be used to create special effects by animating the Visible Instance parameter through code or recorded animation. For finer control over the density of clones, you can use the 3D package to make a separate mesh with a solid mesh exactly in those places where the maximum density of clones is needed, and those places where they should not be, do not cover with polygons, or make breaks in them.
Set Visible Instance to 500 and the fill density drops dramatically.
Management in Godot can be configured by opening the Project item in the top editor pane. Next Project Settings , the List of Actions tab.
You can see what the default buttons are called to know by what names they are accessed through the code. And add your own.
For example, for the PgUP button, the default name is “ui_page_up”, and you can handle it by clicking on the code in the following line in GDScript - if Input.is_action_pressed (“ui_page_up”):
If an action is required for a single click, then is_action_pressed must be replaced by is_action_Just_pressed. End of pressing - pressed changes to released.
If you need to animate the flickering of a material, you can do this through a self-written shader.To do this, select New ShaderMaterial
as the material of the object.
Next, in the empty field, select VisualShader
We click on it, a visual editor opens below the editor
Add a couple of nodes, first Input - All - Time , then Scalar - Common - ScalarFunc , setting, for example, Sin in its drop-down list. In principle, such a design will already give something like a flicker between black and white, but for better, add another Scalar node - Common - ScalarFunc and select Abs there. We connect the nodes together (note that by clicking on the closed eye of each node you can open it and see how the picture changes at each stage) and connect to the Alpha, Emission or Albedo channel. That's it, our object is now flickering.
Naturally, this is far from the only way, but one of the very simple options. And if you want the flicker to be colored, then you will need to make a couple of nodes with color, mix them in a special node (facing Albedo) and attach to it the output of that chain of nodes that we collected above. Thus, this chain will act as a mixing factor for these colors.
To turn the selected node into a separate scene, right-click on it and select the option “Save branch as a scene”. Then set a name for this scene. After that, the “Open in editor” icon will appear next to the node name, from where you can proceed to editing the resulting scene in a new window.
In order to perform the reverse manipulation, if, for example, we need to get the objects of the saved branch to the current scene in order to somehow rebuild or modify them, click on the node that hides the scene in it, right-click and select “Make local”. The saved scene, which he was, is not destroyed at the same time.
To use global variables, you need to create a script and drop it into startup: Project - Project Settings - Startup . A singleton must be checked.
The contents of the SaveTheWorld script whose variables we want to use as global. At the moment, a certain indicator of health and an array of enemy conditions are announced here, which is filled in at the initialization step.
To access these global variables from the code, you first need to get a link to the singleton, and then use its variables:
We find our SaveTheWorld, after which we check whether the state of this particular enemy is zero. If so, the enemy is removed.
UPD. You can access the singleton’s variables immediately through its name, that is, in the example above, we would not have to take a link, and instead of main.enemy_arr [myID], we would turn to SaveTheWorld.enemy_arr [myID]. But you must be sure that this node has a tick in the “singleton” column at startup.
To record and play animations, an AnimationPlayer node is added to the scene. It can be in any place in the hierarchy. In order to give him the opportunity to record animation, you need to click on the "Animation" button, above the opened timeline and create a new animation.
After that, on each selectable node in the current scene, key icons will appear on the right in the inspector. If you click on the key, the animator will offer to add a new animation track.
Next, we move the timeline slider to the desired points, and in each we change the animated value in the object inspector, pressing the key again so that this point appears on the timeline. You can expand the available timelane in the field with the clock, on the right, by default there is 1 second. To the right of the watch is a looping animation button. Below, the first in the line is one of the frequently-used options that sets the twitchy discrete or continuous smooth character of the animation.
To the right of the central panel with the name of the animation is the autorun button, if you press it, the selected animation will play when the application starts. If you need several animated objects that play continuously in a loop, you can make their tracks in one AnimationPlayer, creating one common animation for them. If you need to switch animations for a particular object, you can create a separate AnimationPlayer for it and create several animations for different states there, while only one of them can be marked as starting automatically.
For various nodes, such as MeshInstance or CollisionShape, in the Mesh (Shape) field, you can choose from a prepared list of primitives (including loading your custom model, in the case of MeshInstance).
For MeshInstance, one of the most expensive primitives will be a sphere and a capsule, since they will likely have many polygons. Therefore, for 3D particles, it is better to choose what is like a polygon, cube or prism. For collisions, on the contrary, the shape of the sphere will be most quickly readable, since of the parameters it has only one radius. Actually, the above applies not only to Godot, but also to other game engines.
Godot can also make a collision form for MeshInstance automatically, for this you need to click on the "Array" button, which appears in the upper right in the editor when MeshInstance is selected. Next, choose from the proposed options, but as a rule, for the same terrain, this will be the first item - “Create a concave static body” (after which the StaticBody node will be attached to the object with the exact collision nested into it). In all other cases, when especially precise collisions are not required, you can dispense with the independently exposed CollisionShape primitives, or collect optimal meshes for collisions in the 3D package, then add them through MeshInstance and also create a collision in their form via the Array button.
If in some of the numerical fields in the inspector it is required, for example, to add a certain number to the parameter, multiply the value, and so on, then you can knock it out like this: “315-180”, “20 + 40”, “64 * 5”... the editor will calculate and substitute the final result of the operation in the field.