Big news! We just finished designing and implementing one of the most critical and distinguishing feature of Dual: the capability to script any construct that you make. A construct is anything that you build in the game, it can be a ship, a building, a space station, or whatever. It is a physical object, it can move, collide with stuff and potentially include hundreds of “Elements”, which are operational units that you can craft or buy, and freely position inside your construct to add functionalities to it. Examples of Elements are: propulsion engines, control units (computers), doors, weapons, batteries, containers, accelerometers, radars, targeters, drone bay, elevator, and many more. The way the construct orchestrates all these Elements is through scripts, and what we call “Distributed Processing Units”. Let’s dive into it, and see what it can do.
Warning: The following Blog Post implies you already have some basic programming knowledge.
If you don’t, some parts may seem a bit obscure or difficult to understand.
This is going to be a slightly more technical and longer post than usual, but I hope people interested in the topic will find it entertaining. In any case, remember an important point: you don’t need to understand anything about scripting or ship building to actually buy a ship and fly it. You actually don’t even need to use a ship at all if you don’t want, there are plenty of activities to do besides this in the game. It’s a bit like in real life: you don’t need to be a mechanics to drive a car, and some people just never drive anyway, they take a taxi. It all depends on your playing style, and what you expect from the game. But if you are interested in creating your constructs and scripting their behavior, I hope you won’t be disappointed by the deepness of the gameplay experience we propose.
So, let’s summarize again what a “construct” is: in Dual, you can assemble chunks of matter (stone, wood, iron, kevlar, etc…) in any way you like, a bit like in Minecraft, but instead of simple cubes, you can forge almost any shape you like. The technology behind this is called “Dual Contouring”. This can be used to build the hull of a spaceship, the walls of a castle or a gigantic statue. Up to you. Attached to this inert structural skeleton, you can add some components called Elements, which are actual gameplay components that you can deploy in your construct to make it functional. In a spaceship, you need propulsion engines, fuel tanks, navigation instruments, one or several cockpits, etc. All of these are Elements. The collection of all Shapes and Elements are what constitutes a construct, which is a whole that can be moved once created and is subject to the laws of physics (for example, it falls if dropped in a gravitational field). A construct can be owned, traded, copied, etc, but this is another story we will discuss in another post. Right now we will concentrate on one particular aspect: how do you orchestrate all the Elements so that they can work together? We will take the particular viewpoint of a spaceship, which is a good way to illustrate the concepts involved, but this can apply to anything you like, including a giant spaghetti monster robot or whatnot.
In terms of game design, we could opt for an easy strategy here. If you have the required number of engines in the right direction (no matter where they are), and you check the list of instruments needed, it would “magically” fly. With this approach, all ships would fly the same. Trying to put more engines, or optimizing their position would be more or less useless. Hoping to have an AI helping with automatic navigation would be up to the engineers of Novaquark only. Fancy a new way to drive your ship? Impossible. How about the weapons system? How about drones? All this would be predefined and more or less rigidly identical for all players. That’s not what we have in mind for Dual Universe. While we will provide basic templates to start with, you’ll be able to engineer your construct the way you want. Engines are real (they physically push your ship where they are, with the power they have), gravity is real, weapons have to turn and target (which also requires a targeter). If you are smarter than others, you can get the job done in a better way, get an edge in battle, or in trade by launching the new Falcon X-42 superfighter and change the balance of game combat with new tactics and possibilities. It’s not only about how you can use the predefined capabilities of ships within a predefined classical game setting, but it’s also about how you can redefine these capabilities. We call it: emergent gameplay.
Each Element is an active unit. It can do basically two things: emit “events” and execute “functions”. Let’s give some examples: a radar unit can emit events like “new enemy detected at (x,y,z)”, a jet engine can execute a function like “set thrusting power to 45%”, a weapon can execute the function “fire”, etc. Technically events and functions are described a bit the same way: a name followed by parameters written between parentheses. The above examples would become something like:
Events are emitted spontaneously by the Element when something happens for which it has been designed to react to, and functions are services that the element can fulfill when it is asked to by some external agent. When considering a particular Element, you want to know the list of events it can emit and the list of functions it can fulfill. This is entirely defining what this Element is, from the point of view of the gameplay. We call it its “Type”.
Now, how do you orchestrate the interactions between several Elements, each emitting events and reacting to them, fulfilling functions in return, etc? The central notion we introduce here is what we call a “Distributed Processing Unit”, or DPU in short. A DPU is a bit like a computer program, an orchestrator. It has several slots in which it is possible to plug Elements, and it contains a list of event handlers that can react to events emitted by the Elements plugged inside its slots. The schema below illustrate this:
Events handlers are conceptually quite simple: they are “condition => action” managers. On one side, there is a conditional filter, which is a generalization of an event from a given slot, with certain parameters set to given expected values, while other parameters can take a variable “free” value. A particular event emitted by an Element in a slot will be examined by all event handlers, and it will trigger the event handlers if the event signature matches the event handler filter.
To give an example: suppose that we have a filter like ‘enemyAt(x, 42, y)’, associated to the ‘radar’ slot. Now the radar slot emits the event ‘enemyAt(11,42,66)’. This event matched the filter and so will trigger the event handler. If the event ‘enemyAt(12,13,66)’ is emitted however, it will not match the filter (because 13 is not equal to 42). The schema below illustrate this point:
On the other side of an event handler is the action that the handler can trigger when the filter is matched. This is where LUA really enters the scene. The action is simply a piece of LUA code. LUA is a very simple and efficient scripting language, for which you can get many tutorials online. You can try this for a start.
Now what kind of code will you write on the LUA side? Basically anything you want, but, as you guessed, it will ultimately contain some calls to functions among those provided by the Elements plugged in the DPU slots. The syntax will be something like: “self.engine2.setPower(42)”. The “self” prefix is a requirement of LUA, then comes the name of the slot, and then the name of a function available from the Element in that slot, together with its parameters between parenthesis.
That’s it. That’s the basics of what a DPU is and how scripting works. Let me summarize: a DPU is an orchestrator, it has several slots in which you can plug Elements. You can then define event handlers to catch events emitted by these plugged Elements, and react by executing LUA code, which includes calls to functions taken amongst the set of available functions of your plugged Elements.
To be more precise, I have to refine this picture a little bit: I talked about customizing a DPU, but exactly what DPU are we talking about? Where is it? In fact, the DPU you want to customize is stored inside a special Element called a “Control Unit”. The DPU is started when you activate the Control Unit (go next to it and press the activation key). Notice that there is no problem with having several Control Units (hence, DPUs) inside the same construct, potentially all activated at the same time.
Now, the DPU inside a Control Unit is special because you’ll have a GUI to freely customize it (plug stuff into the slots, define event handlers, etc). Actually, you can do even more than what I just described: you can define a set of events that your DPU is capable of emitting (event emission is done via a special LUA syntax inside your scripts). You can also define a set of functions associated to your DPU, and the corresponding LUA code that should be executed when this function is requested. If you remember, I presented the type of an Element as the set of events and functions exposed by this Element when plugged inside a DPU. Actually, the truth is that there is in fact a DPU inside each Element (albeit not a customizable one), and the events and functions of this Element are actually the events and functions of its DPU. Now to the important conclusion: it is not really Elements that you plug inside the slots of your Control Unit’s customizable DPU. It is in fact DPUs. It can be the DPU of Elements, when you plug Elements, but it can be more generic or abstract DPUs. More about this now.
One particularly important “abstract” DPU is the System DPU. This DPU is capable of emitting events when keys are pressed. You almost always want to plug the System DPU, because you want to control your scripts with keystrokes bound to actions. So, typically, you will have several event handlers in your custom DPU to catch action events emitted by the System DPU. The System DPU is also capable of emitting “timeout” events when a timer is due, or at regular intervals, and many other useful functionalities.
You know enough now to script a simple spaceship controller. You need to put a least one Control Unit in the ship, and customize its DPU. Inside this DPU, you will plug the System DPU (typically in the “system” slot), plus at least 3 engines pointing upwards to lift the ship, and 2 more engines at the back of the ship to make it move forward. You need also to plug a gyro (more exactly: an inclinometer), which is an Element that provides the getPitch and getRoll functions, to know about the pitch/roll angles of your ship. And that’s it. Now you set a timer with the system DPU (call the system.setTimer(0.1) function to set the update every 0.1 second) and catch the corresponding timeout event. When caught, you can then associate the LUA script that will query the gyro about the pitch/roll and use these values to correct the intensity of thrust power in your motors (using the setPower function) to stabilize the ship tilting. And of course, you will also catch the action events from the System DPU to inform your script about the direction in which the player wants to stir the ship. The exact details of how this is done in terms of control and dynamics are beyond the range of this post, but it involves simple Newtonian physics about torques and forces.
This above custom made Control Unit DPU could be seen as a black box, a “Stabilizer”, with 7 slots (5 engines, 1 gyro and the System DPU). You can move this black box in what we call a Component DPU, so that you can sell/exchange it. Now players possessing your “Stabilizer” can plug it in their own Control Unit DPU along with their own Elements, and then connect these Elements in the Component slots, with a simple drag and drop interface, or a smart “autoconfigure” system, making the whole process of scripting a ship very simple for those who don’t want to put their hand on code:
The DPU system goes ways beyond this simple ship stabilization example. Weapon control, whether in FPS direct view on a jet fighter, or in tactical overall view in a battleship, will be handled with DPUs. Scripting a drone can be done with DPUs. Setting the automatic defense mechanism of your castle can be made with DPUs. Factories can be automated with DPUs, etc. It’s all about orchestrating Elements and Component DPUs via scripts that react to events and execute LUA code. The user interface is also scriptable to decide what happens when the Control Unit is activated, how Elements can display their status and give access to parameters (we will probably talk about this in another blog post).
Novaquark will be providing several useful “starter” Component DPUs to start with, as well as smart autoconfigure options to handle the most basic cases. You can get a ship to fly without knowing a thing about DPUs. But, if you are interested in this aspect of the game, it will be up to you to build from there and create the most amazing control system and contraptions, win the markets or the wars with them and leave your mark in the Dual Universe history of innovation and engineering!