Realistic shadow for roguelike
Good time, Habr community.
Many years ago, I ran into a post (1) . Then I was puzzled by the opportunity to create interesting elements for gameplay in roguelike (2) . Suppose the enemy can be behind the wall, we do not see him until we encounter him in the line of sight. But more I like the situation when, traveling through the corridors of the dungeon, we reveal the features of the location of objects gradually based on the area of visibility.
Later in the posts: (3) , (4) and (5) the issues of shadow overlay in 2D games were considered. As noted both by the authors themselves and in the comments, the calculation of shadows is a rather voluminous and not an easy task, both for the calculator and for the design.
Somehow I had a few free days, and I decided to return to the issue of more promising shadows. It is clear that the video card copes with shadows successfully and quickly, but in this case, I wanted to process the shadows for a 2D game, and it seemed to me odd to transfer the calculations to the video card. Yes, and the processor power in recent years as a whole has grown, the actual post about what happened in the end.
The program was written in Pascal, simply because I know it quite well, and Lazarus is an open IDE with a wide range of components.
The initial idea was to draw straight lines from the observer through each of the corners of the tile, and then darken the resulting figure.
However, such a shadow looks somewhat unnatural when the angle of view changes. Shadows are becoming wider, now.
The shadow of a round object looks much better. In order to build such a shadow, you need to hold two tangents from the observation point to the circle, and to the edges of the screen. The diameter of the circle will correspond to the size of the tile.
In my program I used the following function:
//Нахождение координат точек пересечения прямой с окружностьюfunctiontangent_to_circle(x1,y1,x2,y2,r:Single; var x3,y3,x4,y4:Single):Boolean; var l,dx,dy,i,ii,ij:Single; begin dx := x1 - x2; dy := y1 - y2; l := Sqrt(dx*dx + dy*dy); if l<=r thenbegin tangent_to_circle:=false; exit; end; i := r/l; ii := i*i; ij := i * Sqrt(1 - ii); x3 := x2 + dx*ii - dy*ij; y3 := y2 + dx*ij + dy*ii; x4 := x2 + dx*ii + dy*ij; y4 := y2 - dx*ij + dy*ii; tangent_to_circle:=true; end;
Where (x1, y1) is the observation point, (x2, y2) is the center of the circle, ® is its radius, and (x3, y3) and (x4, y4) are the points of intersection of the lines and the circle. The function returns truth only when the observer is outside the circle.
Since the processor is not very friendly with trigonometry, I tried to use it to a minimum. Actually relied on a simple rule (rough model), experts will tell why.
(Bad) SIN, COS ..> '/', SQRT> 'DIV', 'MOD'> 'SHR', 'SHL'> '*'> ': =', '+', '-', 'AND ',' XOR '.. (Good)
Implement the graphic part of primitives on the canvas, something else is fun, there are many libraries and engines that facilitate the work. When developing on Delphi, I had to use the Agg2D library, on Lazarus its port exists (6), on it and decided to translate the idea. Actually, the gain from the library is that the alpha channel is added to the RGB colors, and the primitives are smoothed out, and due to direct access to the pixels and various tricks, the processing is much faster than the canvas.
When drawing the shadow of the tile, it was originally intended to fill the sector with a shadow, but then the image inside the tile was poorly distinguishable (the sector in Fig. 3 is filled with green). Having experimented with various options, I stopped at selecting a sector from the shadow area.
To draw a sector, we need an angle in radians; nevertheless, it did not go without trigonometry. (arctan2 - math module library function)
// Получаем угол в радианахfunctionalpha_angle(x1,y1,x2,y2:Single):Single; begin alpha_angle := arctan2(y1 - y2, x1 - x2); end;
Actually, everything is ready to assemble the image. We take a tile map and apply shadows on a separate layer, one by one. For trees, the shadows are darker, for other objects the shadows are more transparent.
The finished image is applied on top of the main tile layer. A little background design and pick up tilesets more fun. Actually, it took me two days to search for suitable tilesets, those that are publicly available or of very poor quality or cost money. As a result, the trees were painted by himself, and he borrowed other elements from the user Joe Williamson (7) (a great style).
This would all have ended, but there was some sediment about the performance. When the number of objects about half a thousand begins drawdown. Different methods of optimization were considered, and splitting into cores and limiting the drawing area to a certain radius, changing the shape of the shadow (to less costly than arcs), even thought of transferring the calculation to video.
As a result, I came to the conclusion that the best option would be to reduce the image discretization of the employee by the shadow mask. Since the number of calculations is significantly reduced, and the unexpected effect of pixelization of the shadow contours appears, which gives a certain old school charm.
I liked the effect so much that I had to make scaling a dynamic process through a given multiplicity parameter.
It only remained to create opaque walls and present the result to the community.
Waiting for new games that use this effect or its development.
Demo version where you can feel the pens (exe for windows).