Doomenstein
The purpose of this project was to create a unique gaming experience by combining the elements of Doom and Wolfenstein. The main focus was on developing a robust rendering pipeline using DirectX11 and implementing an Entity-based game driven by XML. The game utilizes advanced Blinn-Phong illumination techniques, which are combined with billboarded sprites to create an immersive visual experience. Players can enjoy a variety of game modes and encounter a diverse range of enemies and weapons, each with their own unique characteristics
Doomenstein Trailer
Currently Implemented
-
Entity-based actors
-
Billboarded actors
-
3D animated model
-
Cylinder based collision scheme
-
Hit-scan weapons (Raycasting cylinder)
-
Data-driven actors, map and level creation
-
Blinn-Phong lighting
-
Multiple weapons
Role
Gameplay and Graphics Programmer
Engine
Custom C++ Engine
Tools and Languages
C++, DirectX11, Visual Studio, RenderDoc
Development Time
3 Months
Actors
In this game, there are two distinct types of actors with unique appearances and behaviors. The first are demons, which are six-sided billboarded actors that rotate to face the player depending on their viewing angle. Once a demon spots the player, it will immediately give chase. However, due to their limited intelligence, demons will stop pursuing the player if they lose sight of them.
Billboarded Demons
The second enemy in the game is a spider that is more advanced than the previous enemy. It is loaded from a 3D model and has some additional intelligence. When it spots the player, the spider will attack just like the first enemy. However, it also patrols the map, actively searching for players to attack. This means that players must be extra careful when navigating the game world, as the spider can appear at any moment and launch a surprise attack.
3D Spider Enemy
Here we can see the enemy intelligently patrolling a map, by using heatmaps.
Spider Enemy Wandering
Data-driven Game
An objective for the game was to make it easy to create maps and enemies by just modifying a few lines or adding them to an XML file. This way, the behavior of an actor is easily adjustable, or the way they look.
Here is a shortened version of the XML code that defines the marine actor:
The same principle applies to the maps. With this tool, designers can rapidly iterate on created levels for a better development process. There are many other XML files, where included are one that defines the projectiles of the game, the maps, animations, tile definitions and weapon definitions.
Collision
In the game, all actors are surrounded by a collision cylinder. This design choice enables quick collision detection when using hit-scan weapons. By using a cylinder shape, the game engine can perform fast mathematical calculations to detect whether a projectile intersects with an actor's collision cylinder.
​
From different angles, the cylinder appears as a circle when viewed from the top. This circular shape enables fast collision detection with walls, as the game engine only needs to check if the disc intersects with the wall.
​
When dealing with hit-scan type of weapons, this can be a fast way of eliminating raycast function calls.
Player's Collision Cylinder
Hit-scan vs Actors (Raycasting the cylinder)
Raycasting against the cylinder uses the same principle as before - checking whether the ray intersects with the circle. If a collision with the circle is detected, further steps are taken to determine the exact point of collision. All the steps needed to check the raycast are:​
1. Raycast vs disc:
1. Check whether the start or end of the ray is within the disc, if so, the return an impact.​
2. Calculate the displacement to the disc (dispToDisc) and project the displacement onto the ray's forward (relative x).
3. If the distance on the x axis towards the disc is less than the disc's radius, that means the disc is behind the ray.
4. If the distance on the x axis towards the disc is beyond the ray's length + the disc's radius, it means the disc is beyond.
5. Apply steps 2-4 to the y axis.
6. After ruling out all misses, this means the ray clearly hits the disc. By checking the right triangle formed with the disc's radius and dispToDiscY, the distance to impact can be found.
Note: Keep in mind that a parametric result (from 0 to 1 in ray's length) is desired so when coming back to 3D, distances are not distorted, so further steps are required.​
Raycast vs Disc
2. Check if the ray start or end is inside the cylinder, otherwise move to the next step.
3. Check if the ray hits the top or bottom of the cylinder:
1. The impact position that resulted from step 1 is "distorted" as only the distance in 2D is considered. By converting the distance into a parametric value, the z position can be found.
2. Check if the z impact position is above the cylinder with the ray pointing down (is hitting the top) or the impact position is below and the ray pointing up (is hitting the bottom).
Ray hits top or bottom of cylinder
The usage of similar triangles is needed then at the last step to find out the parametric point where the cylinder is hit.​
4. The ray hits the side of the cylinder. In this case the Z impact point will be between the MinZ and MaxZ. The complete impact point would then be the impact point from the first step + the calculated impact point on z.
​