This is the first part of a series of posts diluting a complete undergrad thesis did by me and @pablodiegoss during a year and a half of research and development. You can find the full version of the thesis here and the proof of concept in this repo. Also, we are both available for opportunities in the game-dev/Rust industry, feel free to contact us! 😄
The game engine Amethyst has become a reference as a solution for game development in Rust, it does have a large and active community, and it is seeking to stand out as an alternative to current solutions. Amethyst brings parallelism and performance optimizations, but also the advantages of the Rust language. In this series of posts, we present the concepts of scripting, which allow the implementation of the game code in an external interpreted language. We developed a scripting module called
Legion Script, that was implemented for the entity component system (ECS) called Legion, which is part of the Amethyst organization. With
Legion Script, it is possible within a Rust project using Legion, to insert components and create entities from Python scripts.
In addition to the performance achieved with a system language, for games, one of the components that can be part and highlight a game engine is the scripting system. It provides access and extends its most common functionalities through an API and thus, allows you to customize and control the behavior of a game outside the main system.
The scripting system can be seen as an abstraction layer of the features of the engine, usually in a different language than the code that implements it, so that the specific logic of the game is separated from the complexities of the engine. Most of the competitive engines in the gaming market have a well-implemented scripting system with their own characteristics. This system allows a language with a smaller learning curve to be used for the implementation of games, facilitating that professionals from other areas can also have control over the construction of the project.
Two major aspects that must be taken into account in software development are the productivity of the developers and the efficiency of the final product, so when developing a digital game, an engine must be able to provide an adequate environment for productivity without compromising efficiency. For this, it is essential that the main features of a game are implemented in a system language and that for the use and customization of these features there is a communication layer exposed in one or more scripting languages.
In practical terms, the scripting system is of paramount importance in the dynamics of generating new versions of a game since, as the scripts extend functionality already compiled, there is no need to recompile all the source code of the engine to change, for example, a player's movement.
Typically, game engines have one or two scripting languages and do not allow an extension to other languages. In the case of an open-source game engine, it would be interesting to have a scripting system that would allow the coupling of any scripting language for customization and use of its features, since, when developing a game, if the development team has an affinity for a language, it would be more productive to use it.
In the development of a digital game, there are features that are common and necessary for most games, such as the rendering process and asset management. In addition to these common features, there are the specifics of each game that correspond to the gameplay, which are the actions that happen in the game and the rules that define the skills of the players, the objects, and the objective of the game.
The reuse of gameplay only makes sense in similar games, such as the movement of a character or the defeat rule of a chess game to be used to create another board game or even another chess. These reuse characteristics allow the gameplay to be separated from the main components, and can then be developed in the native language of the game engine, in another language with a higher level of abstraction, or in both simultaneously.
To make this separation possible, game engines generally implement a scripting system. It is a layer that abstracts the common functionality of low-level code components through an API. Therefore, it is the bridge necessary for a different language to have access to functionalities of the engine, allowing the gameplay to be separate from the common logic of the engine.
In addition to promoting code reuse and better maintainability, by the separation of engine modules from gameplay code, the scripting system encapsulates low-level concerns. An example of that can be memory management, where the scripting system could expose a layer with fewer concerns about the environment. This could be done by wrapping some memory responsibilities to increase the focus on gameplay building.
In general, a scripting system should allow the call of functions
defined in the context of the game engine that is already compiled and at the same time allow the functions defined in the scripts to be executed and interact with the game engine. Preserving the security of memory space between the engine and the scripts and also, between different scripts.
Conceptually when two programming languages interoperate it means that these languages are sharing their ways of interacting with the CPU so that an output is generated. For this interoperability to happen it is necessary to have an interface that allows the sharing of input and output data along with the instructions for the processor.
A common way of language interoperability is the Foreign Function
Interface (FFI), a term that has been used in the
Lisp language. This interface allows code written in one language to be called in a different language.
In this interface, we have the host that will make use of the functions defined in the guest language. This interface exists in several languages and generally uses
C as a guest, gaining performance from that low-level system code.
We can define the scripting module of an engine, as an FFI in the
application layer. In this scenario, it will be the bridge between the game engine and a virtual machine or compiler. However, in this case, the concept of host and guest is bidirectional, since the scripts use engine functions and the engine also performs functions defined in the scripts.