A new day, a new engine – Jokes beside; the GoDot engine has been released around 2015 and is, by time of this article, at version 3.1.1 stable. I do like engines, frameworks and developing video games, so I simply had to evaluate this engine for my personal needs. Before we get right into the topic: some facts about GoDot
- Open Source (GitHub)
- Platform independent (PC, Mac, Linux, Android, iOS)
- Free to use with no hidden costs (commercial/none-commercial)
- Coding with GDScript, C++ or C#
Since the engine throws their own scripting language into the game, I started right away with GDScript. Therefore, this article only covers the usage of GoDot’s proprietary scripting language.
The biggest plus point while using GoDot: The Editor. It has by far the best design and the IDE is build right into it. Nevertheless, it’s stability leaves a lot to be desired. A 10 hour development sitting required several restarts, in order to get some core functionalities up and running again. The documentation is mostly available, but isn’t helpful in some cases. E.g.: The description of a property mostly repeats the name of the property definition, but doesn’t explain the mechanics behind.
The Editor features Hot-Keys, but they aren’t well documented nor visible within the IDE.
GDScript, as the name already indicates, feels and programms like a scripting language. GDScirpt is a phyton similar programming language, reduced to a bare minimum. Developers experienced with a managed programming language (E.g.: C#, Java, C++,…) are used to several different patterns, object orientation, inheritance, generics and much more; which will be mostly missed while scripting with GDScript.
Scope and Visibility
Everything is public per default. There is no private, protected or any other visibility scope available. This behavior forces a programmer to use special code conventions to differentiate between public and private variables beforehand. Getters and setters are available, but optional. And since the variable behind a setter can be accessed nevertheless, object fields may be modified accidentally. There are no namespaces or scope definitions for classes as well.
Workaround: Use prefixes to expose the classes scope reasoning. E.g.:
- gClassName: Global class (or singleton)
- xyzClassName: A class within the virtual xyz namespace
# Export Variables export var _name : String setget set_name, get_name export var _sprite : Texture export var _enableRegion : bool export var _region : Rect2 export var _enableFloating : bool = true # Components var _spriteNode : Sprite var _transform : Transform2D var _viewport : Viewport var _collisionShape : CollisionShape2D setget set_collision_shape, get_collision_shape var _area : Area2D setget set_area, get_area # Variables var _spriteSize : Vector2 var _onDestroy : bool = false var _shaderMaterial : Material
It is available and possible to use inheritance within a GDScript, but the base-class has to inherit from a GoDot type in the end. Hence, a script attached to a Node2D needs to inherit from Node2D. A script attached to a AudioStream2D, needs to inherit from AudioStream2D. In other words, a script can inherit from other GDScripts, but won’t be attachable to each and every node, making inheritance mostly unusable.
Other useful features like abstraction, interfaces and generics aren’t available at all.
Type safety isn’t forced within a GDScript. If needed, a type can be specified which will be evaluated before it’s interpretation. Hence, the freedom of choice will lead to inconsistency and several runtime failures. Not only requires a type safe definition more code, but isn’t simply possible in all cases. (E.g.: Dictionary)
While interacting between GDScripts, the methods often can only be guessed, due to an unusable inheritance behavior and missing type definitions. To check whether a method is available or not, an extra call is required. And due to possible object type combinations, those checks could get out of hand very quickly.
Workaround: Use as many type definitions as possible for classes (class_name TestClass), variables (var test : int = 0) and methods (func testMethod(var param1 : String) -> String).
AutoLoad allows the programmer to define global scripts which will be available even after unloading the scene. However, besides the AutoLoad feature singletons or static variables aren’t available.
Workaround: Add a GDScript to a node and access the script with “get_node(…)”
Some pre-defined core methods like _ready, _process,… are starting with an underscore, while any other method lacks this definition. That may be handy to differentiate between build in routines and methods, but conflicts with the code style definition for private variables which do also start with an underscore. Thus, it is quite easy to select a variable instead of a method while the intellisense triggers.
Workaround: Stick to a code convention of your choice from the begining on. E.g.:
- Variable: _nameOfVariable
- Method: name_of_method
- Parameter: _param
- Getter/Setter: get_name_of_variable
Even thought GDScript is build into the engine, it’s performance is the worst. The scripts do get interpreted during runtime. Hence, no AOT or JIT compilation.
In a nutshell
GDScript is a nice way to get things quickly up and running. However, as the complexity of a game evolves, GDScript feels more like a burden. Due to the inheritance, missing abstraction and interfaces, scripts need to be copied over and over again. GoDot has a component and entity similar system, but allows only one script to be attached, leading to spaghetti code or several unused child nodes. GDScript is recommendable for prototypes or a proof of concept project. For everything beyond that point I can only recommend using C# or C++.
GDScript aside, the engine itself deserves an article by its own. Till the next article arrives, the following taxonomie represents my personal impression after using GoDot for several weeks.