During February of 2024, I entered the annual Warwick Game Jam with a game I had created during my free time across a few days, following the set theme of ‘Actions and Consequences’. My game, The Butterfly Effect, had the player act as a butterfly trying to tirelessly flap its wings in the correct way to cause a storm (hence the name).
The project was built using the .NET version of the Godot engine.
I’d like to share a few brief points about the design and considerations of the project. Note: For context over the upcoming points, you may find it helpful to play the game here (requires a Desktop web browser).
For the player to ‘build-up the storm’, they are required to fly through ‘air currents’ at the correct speed and direction:
Although the player does have a speed in the classical sense, the speed with respect to passing through air currents is treated as being one of three discrete values.
public enum Speed { LOW, MED, HIGH }
Visually, each Speed
value maps to a particular color (green, yellow or red), which is used on the butterfly’s wings
and the air currents.
Each air current is instantiated to correspond to a random Speed
, so
determining if the player moves through an air current at the ‘correct’ speed requires just a
simple comparison between these discrete values.
For the method of determining if the player moves at a valid direction through an air current, we first remark that every
air current is spawned with a random rotation between -90
and 90
degrees. Note that the range of rotation is
only needed to be 180
degrees (rather than 360
) as the air current entity is functionally symmetrical.
So, the air current’s rotation directly indicates the ideal direction that the player must fly through it.
Hence, representing the air current’s rotation as vector \(\mathbf{r}\) and the player’s direction of movement as vector \(\mathbf{p}\), the geometric dot product can be used to ultimately determine the angle between them: \[ \begin{align*} \cos \theta = \frac{\mathbf{r} \cdot \mathbf{p}}{\lVert \mathbf{r} \rVert \lVert \mathbf{p} \rVert} \end{align*} \] If \(\mathbf{r}\) and \(\mathbf{p}\) are normalised (\(\mathbf{\hat{r}}\) and \(\mathbf{\hat{p}}\) respectively) then we have the more concise \[ \cos \theta = \mathbf{\hat{r}} \cdot \mathbf{\hat{p}} \] A threshold \(t \in [-1, 1]\) can then be chosen, such that the direction of flight is valid if and only \(\mathbf{\hat{r}} \cdot \mathbf{\hat{p}} > t\), i.e. \(\cos \theta > t\). This is visually depicted below, with the cosine graph shown in green, and the threshold line shown in orange — any part of the cosine graph above the threshold is treated as a valid direction.
As the intention was for the game to be at least somewhat challenging, \(t\) was chosen as \(0.85\).
Due to the aforementioned symmetry of the air current, the dot product of the two vectors, but with the direction of one them reversed, is also acceptable. Hence, overall, the player can pass through the air current from either end:
if (playerDirection.Dot(airCurrentRotation) > 0.85 || playerDirection.Dot(-1 * airCurrentRotation) > 0.85) { // Player is moving in the correct direction through the air current }
The game environment has the player affected by gravity, and intentionally allows them to fall indefinitely through the sky. From a game control standpoint, the player’s position is not important, as air currents and environmental effects are centered around the player’s world position anyway.
However, I was surprised to see that in-practice, human players were nervous about allowing the butterfly to keep falling through the sky, almost as if the uncertainty of whether there was a ground or not (and perhaps a resulting game-over screen) was distracting. So the design decision here could perhaps be improved upon.
Beyond that, from a game engine point-of-view, if the player continuously leaves the butterfly in freefall, then the butterfly’s \(y\) coordinate will continuously accumulate. In realistic playthroughs of the game, this should not be a problem given how slow this accumulation is, but the value could theoretically breach the finite limit of the data type used to store it. In my opinion, this is a code smell and should be accounted for, particularly in larger projects.
To create the majority of the visual environmental effects, I used Godot’s
CPUParticles2D
for the first time
as well as a CanvasLayer
over the screen,
in order to mimic leaves/rain and lighting respectively. It was fun to attempt to quickly model something aesthetic, and
to play
and tweak the configuration options, and I was pleased with the result.
It was a fun event to see so many games, as well as to receive feedback on my own entry. The game (as submitted) can be played via a web browser here.
As an aside, the idea for The Butterfly Effect also aligns with a personal belief that I try to be conscious about: no good action is too small. Like the small butterfly flapping its wings in the enormous sky, seemingly tiny actions can propagate towards massive impacts; even the smallest of good deeds could set-off a huge domino effect, culminating into something incredible. So, I encourage you to do more good if you can, even if it’s just a bit!