/Kingdom

Probably a tempoary name. A simple game where the world is top down and made out of tiles.

Primary LanguageC++MIT LicenseMIT

Kingdom

Created using C++ and SFML, this is a game where the world is viewed from a top down perspective.

The earliest screenshot of the game is this, showing grass tiles and random water tiles. It was created during the testing of tile collisions.

The tile collisions were somewhat awkward to do as unlike other tile games, the player is not restricted to be in the square, meaning they can go in any direction they like and move anywhere in a square.

alt text

Building

Building can be done on linux with the makefile. In order to reduce compilation time, you should use multiple make jobs. This is done with the -jX argument, with X being the amount of processing cores you have. An example for 4 cores:

make -j4

Prerequisites

In order to build the game you need the sfml library, sfml's dependencies and OpenGL headers.

On ubuntu:

sudo apt-get install libpthread-stubs0-dev libgl1-mesa-dev libx11-dev libxrandr-dev libfreetype6-dev libglew1.5-dev libjpeg8-dev libsndfile1-dev libopenal-dev libsfml-dev libglu1-mesa-dev mesa-common-dev freeglut3-dev

Entities

All entities (excluding tiles) are based of a component system. This means that entities must inherit from "Mob" (or Enemy_Mob). The "Mob" class has an std::vector of components, of which get cycled through every frame in the update method of the mob, after doing any unique logic of a derived mob class first.

Components are in the "Component" folder, and inherit from the "Component::Component_Base" class, and implement the logic method from the base.

It is then very easy to add logic to the Entities as you can simply do it in a header include and a couple lines of code in the Entities constructor. For example, to make an derived mob collide with tiles:

addComponent( std::make_unique<Component::Tile_Collidable>
            ( this, m_tileMap ) );

Tiles

Tiles are bit odd, as I decided to only have 3 variants of tile textures and then colour them in the code. That texture looks like this:

alt text

The jigsaw style edges allows for the tiles to easily interlock with eachother and sort of "blend" with tiles of not the same type without looking too square.

All tiles must inherit from the base "Tiles::Tile" class, and pass along some basic information such as location and colour.

Each tile type also has a respective .tile (custom file type) in Res/Data/Tiles/ which allows much easier changes for tile types, eg their friction or viscosity values. The format is as follows, as shown by the Grass.tile file as an example:

...........

Viscosity

0

Friction

0.76

Swimmable

false

Walkable

true

Colour

0 255 50

..................

The information can go in any order, except the RGBA colour values must go at the bottom due to a possible alpha value at the end, and the current way of detection for that is checking for the EOF.

Thanks to the "Component::Steps_On_Tiles" class and the "steppedOn()" method of tiles, it is also possible to trigger events when walking on tiles. For example, here a screenshot of water turning into ice when stepped on, simply by adding that logic to the Tiles::Water_Tile class, and then adding the "Steps_On_Tiles" component to the Player class.

alt text

Level generation

Much room for improvement.

As of now, the whole map is filled with water and then randomly place islands are "generated". Here is a couple of zoomed out views of that:

alt text alt text

Rendering

Only tiles within the view are updated and drawn, allowing for 1 million tiles and still run at 120 FPS. The tile map has a vector of tiles, and then selects which ones to draw

void
Tile_Map :: draw ( sf::RenderWindow& window, const sf::Vector2i& playerTilePos )
{
    const int tilesX = (Win_Info::WIDTH / Tiles::Info::SIZE) + 3; //Width of the screen in tiles
    const int tilesY = (Win_Info::WIDTH / Tiles::Info::SIZE) + 3;

    int xStart = playerTilePos.x - tilesX; //Start of the render bounds
    int xEnd = playerTilePos.x + tilesX;  //End of the render bounds

    if ( xStart < 0 ) xStart = 0; //Make sure the tile range is actually within the maps bounds
    if ( xEnd > Tiles::Info::MAP_SIZE - 1 ) xEnd = Tiles::Info::MAP_SIZE - 1;

    int yStart = playerTilePos.y - tilesY;
    int yEnd = playerTilePos.y + tilesY;

    if ( yStart < 0 ) yStart = 0;
    if ( yEnd > Tiles::Info::MAP_SIZE - 1 ) yEnd = Tiles::Info::MAP_SIZE - 1;

    //Only drawing those tiles
    for ( int y = yStart ; y < yEnd ; y++ )
    {
        for ( int x = xStart ; x < xEnd ; x++ )
        {
            this->at( x, y )->draw ( window );
        }
    }
}

Lighting

Lighting is as simple as each tile checking lights and multiplying colours together.

void
Tile :: updateLight ( const std::vector<Light>& lights )
{
    //Start the lights as being dark
    m_light = { 0, 0, 0 };

    for ( const Light& light : lights)
    {
        if ( m_light.r < 255 &&
             m_light.g < 255 &&
             m_light.b < 255 )
        {
            //Increase the light of the tile based on distance from the light
            m_light += light.getLightFromIntensity ( m_tilePos );
        }
    }
    applyLight(); //aka "M_sprite.setColor( m_info.colour * m_light );"
}

Unfournatly, this caused the game's performance to suffer. Luckily, I made a few optimisations.

One optimisations to add a static boolean into the light class that says whether or not there has been a "light update". If there has, then the light is recalculated.

The boolean is set to true if

  1. A light is moved.
  2. A light is added.
  3. A light is removed.

Mobs are lit up by adding the "Effected_By_Light" component to them, which simply checks the light value of the tile below them, and then applies the light colours to the mob.

The lights look like this:

alt text alt text

Compared to no lights, it is a huge improvement:

alt text