Ruins development
written by Hevey
Perspective effect
Ruins looks like a 3D project, but it was entirely made on a 2D canvas (like Dencity) in order to achieve a rendering closer to that of a drawing and more organic textures.
To create such a perspective effect in 2D, it is necessary to start by creating the functions useful for this perspective effect.
I started by drawing lines to serve as markers and then placed 2 rectangles taking into account the perspective:
I then moved these rectangles and added a top face:
Then I created loops to have more previews:
I did the same for the lower and upper layers:
And I made some adjustments:
Until I achieved a satisfactory perspective effect to start the project:
First attempts
Before starting to work on the main structure, I created a function to randomly place an object (here a circle) on the ground:
And taking into account its distance to adjust its size:
Development of the tower
Although I didn't have a precise idea of the final result yet, the goal was to create a tower with a variable number of sides, bricks per side, and height to have a lot of diversity.
And to create ruins, it implies that some bricks must be missing, so there must be a depth effect for each brick:
But to achieve something as complex in 2D, it is necessary to proceed step by step and gradually add new elements of complexity.
My first step was to create a circular tower by starting by calculating positions to create a circle:
I then used the same positions but at a different height to obtain the upper part of the bricks and have all the necessary positions to draw the faces of my bricks:
I repeated the process for the other layers of bricks by shifting the bricks at each layer:
And to avoid drawing the bricks at the back in front of the bricks at the front, I created a first function to sort the bricks:
I then replaced the simple lines with more textured lines (composed of multiple simple lines) to resemble more of a drawing:
Hexagonal tower
The next step was to replace the circular tower with a tower with a variable number of sides (6 for this example).
To create the 6 sides, I used the code from my circular tower and defined a number of bricks to be 6:
Then I divided these faces into multiple bricks:
Several layers were then added:
And the bricks of one layer out of two have been shifted:
Then solutions had to be found to sort the layers and bricks of each layer:
Sometimes using tricks to visualize the algorithm's behavior, because with so many bricks, debugging can be challenging.
In this case, for example, I had applied a shade of gray to the layers based on the order in which they are drawn... Here, we can see that the correct method is to first draw the layers furthest from the horizon line:
Depth
So far, the bricks were only made up of a simple rectangle, now we need to add all the other faces of the bricks.
So, I started by removing all but one layer:
And added new faces to the inside:
To better visualize the pairs, I added a random shade of gray to each brick:
Then I modified my sorting functions to take into account all these new faces:
Thinking it was all good, I then redisplayed the other layers and got this:
So, I had to modify the sorting functions again until I got the expected result:
It was already very complicated to draw everything in the right order, but it was still nothing compared to what awaited me in a few steps...
Missing faces
To add the missing faces, I first hid the unnecessary layers and added the bottom faces:
Then I added the final faces and updated the sorting functions:
To also check the sides of the bricks, I randomly removed some bricks:
And tested with different brick sizes:
Weathered texture
To create a weathered effect, I started by adding random vertical lines in the center of the bricks:
Then instead of placing these lines in the center, I placed a random number of lines at random positions:
Grass
Before continuing, I reused the function I created before starting the tower development, which displayed circles at random positions:
And used it to make the ground slightly less uniform:
To create the grass, I started by drawing vertical lines similar to those of the bricks:
Then I randomly curved them:
But I had to come up with a solution to avoid drawing the tower in front of or behind the grass:
Knowing that there may be multiple towers and many other objects later, I anticipated and made sure that all objects to be drawn could be sorted using a new sorting function to draw all objects in the correct order.
I also made sure that each new object created takes into account the already created objects (to avoid, for example, drawing grass in a wall).
By respecting the sorting order, the tower is properly drawn:
Simple plant
For more variety, I then wanted to create a simple plant made up of several strands that curve outward:
After displaying the grass again, we get:
And the worst is yet to come
Before adding new types of objects, I wanted to be able to add several towers.
So the first step was to be able to place the tower somewhere other than the center of the image:
And then disaster struck!
The sorting functions that I had spent so much time perfecting no longer worked when the tower was not perfectly centered.
So it took a long time to break down everything to try to find solutions to this sorting problem:
And very often, when I thought I had finally found the solution (left image), by changing the angle of the tower (right image), I realized that I had not actually found the solution:
The search process was particularly long and complex because as long as the solution found is not absolutely perfect, there will always be a special case where faces will be drawn in the wrong order.
And the slightest error can greatly worsen the situation:
So it was necessary to test and analyze the data to try to understand how to improve the sorting function:
And again:
And again:
And again:
When I develop a project and encounter a difficulty, I am an optimistic person, I do not worry and I tell myself that I will inevitably find a solution... But this time, I did have some doubt.
After a seemingly endless search for a solution, I finally found the solution:
The sorting function is now able to draw each face of each brick of each layer of each tower in a perfect order, regardless of the position of the tower, its starting angle, its number of sides, its number of bricks, its number of layers, its height, etc.
Lighting
At this stage of development, I added the first color palettes, then darkened the colors of certain faces depending on their orientation:
By restoring texture effects to the bricks and choosing a less colorful palette, it is easier to see what the darkening of certain faces brings:
Colored plants
From there, I decided to change the project format as it was too narrow for everything I still wanted to add.
To create the colored plants, my starting point was the simple plants created previously that I enlarged:
I then duplicated each strand to create a long leaf:
My idea was to fill these leaves with 2 different colors to achieve a more organic effect, so I started by filling them with 2 bright colors to visualize how the algorithm filled the leaves:
Then I replaced them with colors from the palette:
And I displayed the other objects again:
Ruins
To add more diversity, I decided to create ruined towers, which involved removing a large number of bricks in a targeted manner:
I then had to improve the algorithm that determines which bricks to remove to achieve a more aesthetic result:
And for more variety, the state of ruin for each tower will be randomly defined:
Tests were then performed with different types of towers, such as a tower with 4 sides and 2 bricks per side:
When a tower has only one brick per side, it has a quite distinctive appearance:
Rocks
To create the rocks, I started with shapes with 5 or more sides:
I then added 2 additional layers and created the faces based on these positions:
But even without trying to sort these faces, I didn't like the shape and decided to remove the intermediate layer:
And I sorted the faces:
I then darkened the faces of the rocks based on their orientation (like the faces of the bricks):
Then, the simple lines were replaced by the same lines as those of the towers and the color of each rock was determined based on the palette:
Finally, lines similar to those of the faces of the bricks were added to the rocks:
This results in:
Or with another palette:
Sky
To fill the sky, I chose to take inspiration from star trails (which can be captured at night with long exposure photography or by combining photos taken at regular intervals).
I started by creating arcs of circles by placing points at calculated positions to visualize them:
Then I replaced the circles with lines similar to those of the other objects already drawn:
Next, I used the colors from the palette:
Impossible tower
For more variety, I created a new type of tower that is a bit more rare, a tower with floating rings:
More variety
For even more variety, I tested and defined a wide range of possibilities for the towers, including:
- 3 to 16 sides
- 1 to 6 bricks per side
- 1 to 35 brick layers
- 7 different sizes
- 4 different heights
This allows for very varied results:
And even triangular towers:
This was also the point at which I did the most work on color palettes, testing and selecting the 51 palettes for this project:
Most of the selected palettes are similar to those of my previous projects, so it will be easy to create pairs for collectors who want them.
White and black outlines
For even more variety, I have also added a probability (for certain palettes only) of getting white and black outlines:
Animation
Until now, I hadn't planned to create an animation... And then I had an idea... What if I rotated the star trails around their center, slowly, to create a soft and mesmerizing animation?
And to make it more interesting, I also thought it would be cool if not all stars rotated at the same speed...
But that poses some problems because if the algorithm has to generate a new sky 30 times per second, your computer won't like it and the result probably won't be smooth.
So the solution to obtain a smooth and low-demanding animation is to generate all elements only once at the beginning, which means 3 distinct images:
- 2 large images for the sky, each containing half of the stars
- 1 image containing the remaining elements (ground, towers, and other objects)
The idea was then to combine all these images by applying a rotation to the sky images to create the animation:
The white parts in these images are actually transparent, which allows for simply overlaying images to obtain the final image.
The algorithm starts by applying a background color, then it places the 2 sky images with a different rotation, and finally, it places the last image on top.
Anything that extends beyond the background area (in dark blue in this example) will be ignored because it's outside of the canvas, so we get:
Preview of this animation: click here
And for more variety, I have created 3 types of star animations (same direction, opposite directions, sliding rings).
It is also possible to pause the animation by clicking on it and to modify its speed using the arrow keys (by pressing them several times).
Ground variations
To bring even more diversity to the outputs, the ground can sometimes be composed of large rocks:
Or a desert ground composed of small rocks and rare plants:
Or a ground full of tall grass:
Sky variations
The sky has also been given more variety, sometimes by removing the stars at the center:
Or by displaying it in the form of rings:
The star trails can also sometimes be much more irregular:
Bugged tower
By doing experiments (which I often do) and randomly modifying certain dimensions of the bricks in this case, it resulted in a tower whose brick stacking is quite difficult to interpret.
I found this result interesting and finally integrated it into the project with a fairly low probability:
Landscape format
When working on a project, I experiment with a lot of ideas... At that point, I thought I was almost done with the project, but I wondered what it would look like in landscape format...
So I quickly did a test just to see, and I quickly realized that it was much better, especially for animation:
However, changing the format requires recalculating and redefining many parameters, starting with the ground:
And since I had to recalculate everything anyway, I also tested different horizon heights and settled on this one:
This height and the curve at the horizon give the impression of being on a small hill, with a bit of emptiness behind, then the sky:
Testing and final improvements
The final step was to carry out many tests and checks, including with less common parameters, to make sure everything is ready:
Code
The entire development of this project fits within this block of code of 20,062 characters :
Release
The release of my Ruins project will be on Monday, March 6th, 2023, at 8pm CET.