Hello,
I'm doing some experiments, the idea is to make a
simple top down roguelike game. I'm new to game development, but I'm feeling lucky
I've also read the SFML Game Development book, which is great, so I will be using many of it's ideas.
First things first, I wanted to create a Dungeon with Floor and Wall tiles and a movable Character.
So I created my Character and Tile entities and built the Dungeon Scene.
I've found some cool assets
http://opengameart.org/content/dawnlike-16x16-universal-rogue-like-tileset-v17 that I will be using for the time being.
Tiles are 16x16, and I'm using a 12x16 TextureRect for the Character.
Right now, the character moves freely in the Dungeon, so I thought it would be better to center the origin of the Character and the Tile to their Sprite centers. Due to this, I had to add an offset to the Tile position for generating a 10x10 Dungeon, like in the first image. I've set the visible area to a 5x5 cells, that's why you are not seeing the entire room. The View zoom is being applied.
Related code so far:
// the following snippet in buildScene() generates a square room with no door and a wall tile at map[3][3]
const auto tilemapSize = 10u; // 10x10 cells
mDungeonBounds = sf::FloatRect(0.f, 0.f, Tile::Size * tilemapSize, Tile::Size * tilemapSize); //160x160 pixels
for (auto x = 1u; x < tilemapSize - 1u; ++x)
for (auto y = 1u; y < tilemapSize - 1u; ++y)
{
if (x != 3u || y != 3u)
{
Tile::TileID id(x, y);
addTile(id, Tile::Type::Floor);
}
}
addTile(Tile::TileID(3u, 3u), Tile::Type::Wall);
for (auto i = 0u; i < tilemapSize; ++i)
{
Tile::TileID firstRow(i, 0u);
Tile::TileID lastRow(i, tilemapSize - 1);
Tile::TileID firstColumn(0u, i);
Tile::TileID lastColumn(tilemapSize - 1, i);
addTile(firstRow, Tile::Type::Wall);
addTile(lastRow, Tile::Type::Wall);
addTile(firstColumn, Tile::Type::Wall);
addTile(lastColumn, Tile::Type::Wall);
}
// spawns player at map[5][5]
Tile::TileID tileId(5u, 5u);
mSpawnPosition = sf::Vector2f(tileId.first * Tile::Size + Tile::Size / 2, tileId.second * Tile::Size + Tile::Size / 2);
/*---------------*/
void Dungeon::addTile(Tile::TileID id, Tile::Type type)
{
std::unique_ptr<Tile> tilePtr(new Tile(type, mTextures, mFonts, id));
auto tile = tilePtr.get();
// Tile has centered origin
tile->setPosition(id.first * Tile::Size + Tile::Size / 2, id.second * Tile::Size + Tile::Size / 2);
mSceneLayers[Main]->attachChild(std::move(tilePtr));
}
void Dungeon::setupView()
{
auto visibleArea = Tile::Size * 5u; // 5x5 cells
auto zoom = visibleArea / std::min(mView.getSize().x, mView.getSize().y);
mView.setCenter(mSpawnPosition);
mView.zoom(zoom);
}
1) Drawing the Dungeon
As you may have noticed, I'm attaching all the Tiles as SceneNodes.
Also, each Tile is calling it's own draw method.
At this point you may be thinking that I'm completely lost, but I did read some stuff and I am aware of these problems
But I still have doubts and need help on how to solve them.
First, the drawing problem.
The Tiles share the same Texture, and their TextureRects are initialized in the DataTable.
Is it okay to draw them with the draw calls since they are sharing the same Texture, or should I build a VertexArray?
If i use the VertexArray, what happens if a Tile is destroyed? Can I change the data of the VertexArray at runtime? Or do I need to update the TileMap and rebuild the VertexArray?
I think it is interesting to make a Tile an Entity.
One direct optimization that could be done is creating a BaseTile that is walkable and has no effects, not even destroyable. If using the VertexArray, this Tile does not even has to be attached to the SceneNode, reducing the number of SceneNodes, which raises my second question:
2) Space Partitioning
Right now, in a 10x10 Dungeon there are 101 SceneNodes, including the Character.
Still runs at 60fps in a 11x11 Dungeon, but when it's 12x12, it's totally unplayable with 1fps.
I'm still reading about Grid and Quadtrees and this topic
http://en.sfml-dev.org/forums/index.php?topic=13766.0.
But I do have a design question.
Each SceneNode has a vector with pointers to their children and a pointer to it's parent.
Suppose I want to adapt this to a 4x4 Grid structure, four 5x5 Cells that maps the 10x10 dungeon.
Is it simple as creating 4 SceneGraphs for the 4 Cells of the room and attach nodes to the graphs depending on their positions?
Then i just update the current Cell.
Data Structures are underestimated, I must be missing something
. Haven't tried this yet though.
3) Wall Collisions
Before diving into problems 1 and 2, I've decided to implemented wall collisions, just to fool around the map and see if I could do it.
After struggling many hours I've came to a working solution
I've tried to keep in mind the axis of least penetration idea, as explained here:
http://gamedevelopment.tutsplus.com/tutorials/create-custom-2d-physics-engine-aabb-circle-impulse-resolution--gamedev-6331 (AABB vs AABB)
Here's the collision code:
if (matchesCategories(pair, Category::Character, Category::UnwalkableTile))
{
auto& character = static_cast<Character&>(*pair.first);
auto& tile = static_cast<Tile&>(*pair.second);
auto characterBounds = character.getBoundingRect();
auto characterPosition = character.getPosition();
auto tileBounds = tile.getBoundingRect();
auto tilePosition = tile.getPosition();
// check X axis penetration through left or right
auto penetrationX = std::min(std::abs(tileBounds.left + tileBounds.width - characterBounds.left)
, std::abs(characterBounds.left + characterBounds.width - tileBounds.left));
// check Y axis penetration through up or down
auto penetrationY = std::min(std::abs(tileBounds.top + tileBounds.height - characterBounds.top)
, std::abs(characterBounds.top + characterBounds.height - tileBounds.top));
auto penetratingAxis = std::min(penetrationX, penetrationY);
auto collideX = penetratingAxis < penetrationY;
if (collideX)
{
// Colliding Left
if (characterPosition.x > tilePosition.x)
character.setPosition(characterPosition.x + penetrationX, characterPosition.y);
// Colliding Right
else
character.setPosition(characterPosition.x - penetrationX, characterPosition.y);
}
else
{
// Colliding Top
if (characterPosition.y > tilePosition.y)
character.setPosition(characterPosition.x, characterPosition.y + penetrationY);
// Colliding Bottom
else
character.setPosition(characterPosition.x, characterPosition.y - penetrationY);
}
}
Works quite well. Screenshot 2 show character at the corner with LEFT and UP being pressed, and screenshot 3 just after both keys are released.
Character slides smoothely throught when both keys are pressed,
however it will get stuck sometimes when near the corners (haven't discovered why yet). Screenshot 4 shows this case.
Character never gets stuck if only one key is pressed, and no bizarre repositionings are happening. Collision with that single block near the corner works perfectly in all directions and with all movement combinations. I'm satisfied with the results for now, but I do want to get rid of that corner stuck situation
Well.... this is it, for now.
Sorry for the long post, and
thank you very much for your attention.
Thank you all SFML collaborators, I'm happy learning game development without sticking with those fancy engines full of options. I may put them to good use one day, but I don't think they are adequate for beginners at game development.
I have a CS background (not that it means much xD), and SFML Game Development Book really cleared many things out for me
The coding style and patterns made me give C++ a second chance, and I'm enjoying it a lot! (spent my last years working with IT).
And here is the code so far, you may build the project with CMake.
https://github.com/gabrieljt/Dungeons- Gabriel