Road Crossing Simulator
Rytis Valiukas, Frederik Raud, Remi Sebastian Kits
Project repository
Download the game
Description
We created a game similar to Frogger (1997). We did not want to completely recreate it, thus we thought of our own theme. The main character is the epitome of an unpunctual student, who is running across a busy street to make it to university on time. We made the models, animations in Blender and we used Ogre3D as our graphics engine. The game was written in C++.
Controls
- ESC to quit
- WASD to move
- Mouse to look around with torch
Project Architecture
By Remi Sebastian Kits
I wrote the game loop, entity system for the game. For this I created a singleton CharacterHandler which is used for handling all entities in the game loop and the interactions between them. The class structure for the architecture looks like this:
The game loop calls the update method on CharacterHandler each tick, which calls the update method on all characters. CharacterHandler also allows safe character creation and deletion, so any character can delete itself or another character inside its update method. This is handled by queueing these actions that'll be executed after the update method call.
For collisions between the entities, the CollidableCharacter, which extends Character, allows collision checks between any character instances. It uses the CharacterHandler singleton that is also facilitated elsewhere.
Resource Management
Another issue I tackled was model, material and texture management. Ogre3D has its own mesh and skeleton file formats, which meant that our Blender models had to be converted into that. No other format was supported. This required multiple conversion steps as shown in this diagram:
First the blender file was exported into Ogre3D mesh.xml file format using the blender2ogre plugin. This plugin enforced quite a few limitations such as not being able to use any modifiers, scaling etc. So on the modelling side Rytis also had to account for that. The next step was to use OgreXMLConverter, a tool that was built when compiling Ogre3D, to convert from xml format to a faster binary format. Finally these files could be loaded into the graphics engine.
On the graphics engine part, I wrote a general loadModel function for characters which handles texture, material and model loading using Ogre3D's resource group system. This took some debugging, but worked out fine.
When we got to exporting more complex environment models that came with multiple materials, I had to change the initial manual material creation and model loading process to Ogre's automatic resource management system. What this required was to set up resource group management so that Ogre would automatically find all materials and associate them with the correct meshes. This was extra useful since there seemed to be no easy way to manually load multiple materials for a single mesh.
Shadows and normal mapping
Since we're doing a Computer Graphics project, we also wanted to have something more than simple 3D rendering and a game. To do so we decided to go for shadows and normal mapping on terrain. The documentation was not very useful for either use case, most examples suggested writing long shaders, material files and doing a lot of things manually. After some tinkering I got shadows working with the texture shadow technique. This required setting up which materials could receive shadows and which entities could cast them. Another issue we encountered here is that older OpenGL versions only supported Gouraud shading which meant that some of our larger flat terrain parts had very triangular shadows. To limit this issue we set Ogre3D to only allow d3d11 and GL3+. The first GL3.0 version was released in 2008, so that is a reasonable restriction.
After shadows we also wanted to have displacement or normal mapping. I tried to use the heightmaps on Rytis's model, but the examples I found on both bump and displacement mapping required extensive changes to our material files and a ton of shaders. Since we were time limited, I chose simpler normal mapping instead. For this I used Ogre3D RTShaderSystem's built in lighting stage settings, which allowed me to add normal mapping to the first pass in the material without having to write extra shaders for it:
texture_unit { texture ground.png tex_address_mode wrap colour_op modulate } rtshader_system { lighting_stage normal_map groundNormal.png tangent_space 0 bilinear 1 -0.5 lighting_stage per_pixel }
Dependency Management
As we were all somewhat unfamiliar with C++ dependency system, we initially had trouble getting Ogre3D to run properly. We first tried Nuget and CMake for getting Ogre, but these proved to have major issues. I finally solved this by compiling Ogre3D, adding libs, includes and dlls into the project repo itself. To also have them work out of the box with the Visual Studio project, I modified all dependency paths to be relative to the project directory. While this is not the best solution, it is one that works out of the box for everyone with no need for any more configuration and is okay for a smaller project like this.
Game Assets
By Rytis Valiukas
My task was to get familiar with modelling in Blender software by creating low poly models for the game. The overview of the final game models (car, jeep, lorry, oak, pine, spruce, student, university and environment) is presented in the image below.
Shading
I have chosen to apply solid color materials to most of the models. The reason is that this style of shading is computationally light, simple to implement and most important - fitted the low poly theme perfectly. However, to make environment more interesting, together with ground and asphalt textures, I have downloaded their corresponding height maps. Considering blender2ogre exporter cannot deal with height maps, I have manipulated the mesh itself by using them as displacement maps. I have achieved that by using the displacement maps as textures for displacement modifier and then used other modifier to subdivide the mesh. My method (centre) shows one of the three common uses of height maps in Blender.
Animation
To animate the main character model, first, I needed to create a simple armature. Also, I have implemented inverse kinematics for the legs in order to simplify the animation process. Then I have deformed the armature with automatic weights, which most of the them turned out to be correct and required only minor fixing. Finally, I have created walk and jump animations by simply moving bones in the animation editor according to the reference images that I have found online.
Game Implementation
By Frederik Raud
World Generation
The world is based on tile, as the character moves in discrete jumps. A TileHandler was necessary to spawn in the meshes created by Rytis and to scale as Entitys, so that their width along the z-axis (the forward and backward of this world) would be the same as a tile. I also created a LaneHandler which was responsible for creating Lanes along which cars would drive, these also needed to follow the discrete space of tiles. Each Lane has a speed, which is common for all cars, and needs to be the same across the Lane so as to avoid congestion. The value for this and the spawn rate of the Lane were assigned randomly by LaneHandler. It was also necessary to spawn road meshes where cars would drive and strips of grass, where they wouldn't. The start and end meshes also needed to be aligned with the rest of the world. As the player gets closer to the end, they will also find that the carless grass Lanes are further and further apart.
I automatized most of this, so that given the width and length of the world, and a the width of a lane, the world would be generated automatically.
Score HUD
The HUD for the game was going to be simple, so I decided that I wanted to do something special with it, I had recently played Post Void and was inspired by its presentation. I replicated the way the font of its UI glitch out when you hover over them. I created 6 different images of each of the 10 digits and used them to create an Overlay of numbers, consisting on OverlayElements, with Ogre. Overlays are rendered after the rest of the scene has finished rendering and are then blended over it. You can make several blending passes, but I only did one. The OverlayElements are textured just as Materials are. I decided not to mess around with UV coordinates and created one image for each number instead of an image with a set of numbers. They were loaded in once and assigned to an array. Every time the score was updated, or when a ticker counted down enough for it to start, the score started twitching, or changing rapidly. The previous Overlay is cleared and new digits are selected randomly from the array. The score itself is derived from the distance the player has moved. This is converted into an integer and then split into a stack of numbers, one for each place in the number. The OverlayElements then needed to be converted into the scene space, with a width and height of 1.0. They are also randomly shifted on the x and y dimensions just enough to make them look jittery. I had to choose the correct blending function to use the alpha value of the images and also had to turn of filtering, which didn't work with pixel art.
Lights and Camera
A core element of the game is that the longer the player takes, the darker it gets and the harder it is to move around. Any moment, a car might come out of the darkness and run over the student. The student does have two advantages though: the flashlight and the spotlight shining down on them from the sky. The player can move the flashlight with their mouse, spinning it around the y axis. The spotlight follows the player along with the camera. Both are at an offset. To direct them at the player, the lookAt method is used. Both the flashlight and the spotlight use Ogre's Spotlight. Unlike the more common point light and directional light, which we talked about on this course, the spotlight only shoots light out in a cone. In Ogre, it is defined by two angles, the inner, smaller angle, where the light is at its most intense, and the larger angle, where it is at its least intense. They also need to have their specular and diffuse colours set. The flashlight has a constant bluish-white tinge, while the spotlight switches linearly between green and pink, depending on how close the closest car is. This is a sign of danger for the player and serves both to create tension and to inform them that a car is getting close. The sky uses a directional light, which is slowly rotated as time passes. It also changes colours, using sinusoidal functions of different phases for each the rgb colours. This models a sunset more realistically. These lights also cast stencil shadows, implemented by Ogre. These shadows have a texture, which Remi figured out (not mentioned in the tutorial). These shadows get longer as day leads into night. For each mesh entity, you need to manually set whether it casts shadows. In place of bouncing light, there is ambient light which simulates the strong light of the sky. This also gets darker as the day goes on.
An additional feature is the projection of the Camera. By default, Ogre uses perspective projection, and I needed to manually create a projection matrix (I found one online) to get it to have orthographic projection, which fit this tile-based game better.