Article posted on: 2021-04-30 21:06
Last edited on: 2021-05-23 12:17
Written by: Sylvain Gauthier
fxc and myself participated for the first time in the Ludum Dare, 48th edition, as jammers, from April 24th to April 27th.
The theme was deeper and deeper. After 2 hours of debate, considering themes such as deep sea, mining and such, we settled on what we thought would be both a fun and original game: an oil drilling game.
While intended to be fun, light and straight-forward, as game jam entries tend to be, it ended up being more of a remarkably complex and intense little simulation game, or as one of the commentators puts it, “1/3 game, 2/3 simulation of hard business problems, risk management and timing”.
The game is available here.
The point of participating in the jam for us was to test our 3dmr 3D rendering engine so of course we used it and made the whole thing in ANSI C.
We used Blender for assets and Gimp for icons.
We didn’t have time for any sort of audio.
You are navigating on a 3D grassy, hilly map with a few sparse trees, and you have to place oil rigs to extract as much oil as possible. Only very shallow fields are visually hinted by small black patches, you can start with those as your small initial budget doesn’t really allow for any expensive prospecting.
Then, as you pump the oil, you can decide to sell it to an extremely volatile market, that can jump from 1 to several dollars in a matter of seconds, so you have to keep an eye on it.
You can then use the money to build better equipment and use radar scans and digging probes to find new, deeper fields. And such is the risk/reward loop. As of writing this article, there is no way to “win” the game, just try to extract as much oil as possible. A score system might be added in the future, that tracks the total amount of oil sold.
The map itself is generated using a simple 1-level Perlin noise that we extracted from a previous project. First a heightmap is generated in the form of a simple array of floats. Then we iterate through the heightmap and generate “cells”, actual renderable nodes, whose vertexArrays are generated from a subsection of the map. We add all those cells as children of the empty map root node.
The reason for splitting the map into cells is for leveraging 3dmr’s frustrum culling capabilities, that is, not rendering nodes outside the field of view. If we weren’t splitting the map, all 240000 vertices would be rendered at all times. Furthermore, adding trees as children of the tile they are on allows the algorithm to skip checking whether they should be rendered or not if their parent tile is not rendered.
Now the oil fields where an interesting problem. We wanted to have relatively realistic-looking deposits, that is, some sort of flat, round volumes, like a doughnut you stepped on, but at the same time have an organic, unpredictable shape. We also wanted large areas where the concentration of oil fields would be larger than elsewhere.
To do this, we generate a 3D Perlin noise that we used as a density distribution for seeds (simple points). We generate random gaussian parameters centered on each of those seeds. Using those gaussians, we can now merge neighboring fields depending on a threshold. At this stage, fields are by construction a set of gaussians with random parameters and location, and we can determine whether a point is in a field by summing the influence of all the gaussians in a given field at the given point. If it exceeds the threshold, the point is in the field.
From there, we can relatively easily determine the intersection of a drilling trajectory with a field, in particular the entry and exit coordinates of the well into the field.
Radar probing works by getting a set of points at the surface of the map from the player. From those points, we compute a probability map of the presence of a field at the vertical slice under the path.
With this gaussian model of oil fields, the radar imaging becomes both relatively simple and really cool. With the coordinates of the points on the map and the depth capability of the radar, you can deduce the map coordinates of each pixel in the resulting image you are generating. So for each pixel, you get the world coordinates, and from those coordinates you can compute the influence of nearby oilfields. You sum it, add some random noise because it wouldn’t be a radar without the noise, you give it a green color and there you have it, a pretty cool looking oil field detector.
To simulate oil extraction speed in a manner that’s not too overwhelming and obscure for players, we reduced the number of parameters to a “minimum” (don’t laugh when you see what’s coming…).
Oil fields have an initial volume Vi, a current volume V of oil left, an initial pressure P0 and a current pressure Pf. The volumes are computed from the gaussian parameters, and the pressure is, as of writing this article, a random number between 1 and 10.
Oil rigs have a depth, an oil debit, a pressure (at the well), a position of course, an energy input and an energy efficiency.
First, the pressure at the well is computed as
P = Pf - h * rho * g, where
Pf is the internal pressure of the field,
h the height of the rig relative to
rho the density of oil (we took 900kg/m3) and
gravitational acceleration (9.81 N/kg).
Then, the oil debit at the field is
D = f * P where
f is a constant relative
to the type of rig. Physically speaking, it would be proportional to the surface
area of the pipe the oil comes from and some other stuff I have no idea about.
So at every interval
dt, we compute
D(t) = f * P(t), we update the current
volume of the oil field:
V(t) = V(t - dt) - dt * D(t), and we update the
pressure of the field:
Pf(t) = P0 * V(t) / Vi.
Hence, the internal pressure of the field decreases as we extract from it, and so does the pressure at the well, and so does the debit, etc. Consequently, you have a differential equation (or feedback loop) that leads to a debit being exponentially decreasing.
On top of that, we throttle the rig’s debit to
maxDebit, so in effect the
graph of the debit would look like a plateau at
maxDebit followed by a
decreasing exponential when the field’s internal pressure is not enough to
maxDebit at the rig.
Finally, we introduce the
energyInput which effectively allows a rig to
artificially increase the pressure by inputting some energy. The pressure (at
the rig) computation becomes:
P(t) = Pf - h * rho * g + eff * Ei / f
eff is the energy efficiency of the rig,
Ei is the energy input and
f is the constant described above.
So in practice, when the pressure of a rig falls below nominal pressure, for
every unit of energy you input in the field, you’ll increase the debit by
This is viable since all rigs have, logically, an
eff greater than 1
(otherwise why bother wasting 1 energy when you get less than that back).
So now, note that when
h, the height between the field and the rig, is very
big, and/or if the field internal pressure was randomly generated as pretty low,
the pressure at the rig can get quite negative. In that case, you’ll need to
input a lot of energy to prop it back up to net positive, and it’s not
guaranteed that you’ll reach a viable debit.
This is a decent simulation of real-life “EROEI” (Energy Return on Energy Invested) factors that determine whether an oil installation is viable or not.
The market simulation is remarkably simple and yet looks pretty realistic to
some extent. I was very surprised to find that my 3-liner got such a decent
result, in fact. Essentially, at each time step
dt, we compute:
M(t) = M(t - dt) * (r + S / 100000) ^ (dt / 1000) M(t) = M(t) + dt * r' M(t) = min(1, M(t))
ris a random number between 0,6 and 1.
Sis the total amount of oil sold since the beginning of the game.
r'is a random number between -3 and 3.
The idea behind the first line is that we wanted the market price to increase
“by a given factor” every 1000 seconds, so every
dt interval, we have to
multiply by the factor to the power of
dt / 1000 so that the accumulative
multiplication will work.
But then hey, it’s a market, it should be random, so let’s regenerate this factor randomly every time we do the multiplication. But we still wanted to be player friendly and have a market that overall grew kind of exponentially overtime, so to this random number we add the total amount of oil sold since the beginning. Since the random factor is minimum 0.6, once you sold 40k units of oil, the overall factor will always be greater than 1 and the price will start seriously increasing, which should mitigate the difficulty you’ll have extracting the deeper oil fields.
The second line just adds a random factor just because, for high frequency variations in the market.
With the third line, we make sure that the market is at least $1, as a necessary balance to make it not too hard.
Finally, it wouldn’t be a market if it didn’t drop when you sold stuff. So
every time you sell an amount
X of oil:
M(t) = M(t) * (1 - X / (S + 100))
S is the total amount of oil sold since the beginning of the game.
The idea is that there is a certain amount of oil floating in the market, and when you dump your oil in it, the price decrease by an inverse-proportional amount of the oil increase you create.
The interface was the most painful and time-consuming part, mostly because
3dmr has no easy way of instancing/managing UI elements. We made a small lib,
lib3dmenu beforehand, in
anticipation to the jam but even then we encountered many excruciatingly painful
Furthermore, our text-rendering stack is still quite experimental and very
inefficient for modifying text in real time. This will be the focus of our
This jam was a lot of fun, not much sleep, the first proper game ever made using our now 3 years old rendering engine and it revealed a lot of caveats, especially on the UI side.
I do not have high expectation on our score in the jam, as the game is quite complex, not very well documented, it doesn’t have any sort of tutorial (yet) and is quite different from the stuff people would normally expect from a game jam (light, aesthetic games with catchy music). We have low-poly assets, no sound and the gameplay is basically a business simulation. A good and funny one, but the target audience isn’t probably that big.
Still would recommend the Ludum Dare, will try to participate in 6 months or next year.