Skip to content

Commit

Permalink
Component factories now have an extra level parameter to vary the gam…
Browse files Browse the repository at this point in the history
…e object's component data based on the game object's level. This level is normally distributed around the game level with a small deviation to allow for game objects to be at higher or lower levels.

`MapGenerator` can now be default initialised simplifying `GameEngine`. Various other improvements were also made to the `GameEngine`.

Various improvements to `PhysicsEngine::add_bullet()` were made to improve the overall code quality.
  • Loading branch information
JackAshwell11 committed Dec 30, 2024
1 parent a46250e commit fad6c9b
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 35 deletions.
4 changes: 2 additions & 2 deletions src/hades_extensions/include/ecs/systems/physics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ struct PhysicsSystem final : SystemBase {
///
/// @param bullet - The bullet's position and velocity.
/// @param damage - The damage the bullet inflicts.
/// @param source - The game object type that fired the bullet.
void add_bullet(const std::pair<cpVect, cpVect> &bullet, double damage, GameObjectType source) const;
/// @param source_type - The game object type that fired the bullet.
void add_bullet(const std::pair<cpVect, cpVect> &bullet, double damage, GameObjectType source_type) const;

/// Get the nearest item to a game object.
///
Expand Down
4 changes: 2 additions & 2 deletions src/hades_extensions/include/factories.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
// Avoid having to include headers for this
struct cpVect;

/// Alias for a factory function that creates components for a game object.
using ComponentFactory = std::function<std::vector<std::shared_ptr<ComponentBase>>()>;
/// Alias for a factory function that creates components for a game object with a given level.
using ComponentFactory = std::function<std::vector<std::shared_ptr<ComponentBase>>(int)>;

/// Load a hitbox for a given game object type.
///
Expand Down
16 changes: 16 additions & 0 deletions src/hades_extensions/include/game_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ class GameEngine {
void generate_enemy(double delta_time = 1.0 / 60.0);

private:
/// Get the components for a game object type.
///
/// @param game_object_type - The game object type.
/// @return The components for the game object type.
[[nodiscard]] auto get_game_object_components(GameObjectType game_object_type)
-> std::vector<std::shared_ptr<ComponentBase>>;

/// The current level of the game.
int level_;

/// The random generator for the game.
std::mt19937 random_generator_;

/// The normal distribution to determine the level of the game objects.
std::normal_distribution<> level_distribution_;

/// Manages game objects, components, and systems that are registered.
std::shared_ptr<Registry> registry_;

Expand Down
3 changes: 3 additions & 0 deletions src/hades_extensions/include/generation/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
/// Manages the generation of the map.
class MapGenerator {
public:
/// Initialise the object.
MapGenerator();

/// Initialise the object.
///
/// @param level - The game level to generate a map for.
Expand Down
13 changes: 7 additions & 6 deletions src/hades_extensions/src/ecs/systems/physics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ void PhysicsSystem::add_force(const GameObjectID game_object_id, const cpVect &f
}

void PhysicsSystem::add_bullet(const std::pair<cpVect, cpVect> &bullet, const double damage,
const GameObjectType source) const {
const GameObjectType source_type) const {
const auto bullet_id{get_registry()->create_game_object(GameObjectType::Bullet, get<0>(bullet),
get_factories().at(GameObjectType::Bullet)())};
get_factories().at(GameObjectType::Bullet)(0))};
const auto damage_component{get_registry()->get_component<Damage>(bullet_id)};
const auto kinematic_component{get_registry()->get_component<KinematicComponent>(bullet_id)};
damage_component->add_to_max_value(damage - damage_component->get_value());
damage_component->set_value(damage);
cpShapeSetFilter(
*get_registry()->get_component<KinematicComponent>(bullet_id)->shape,
{static_cast<cpGroup>(source), static_cast<cpBitmask>(GameObjectType::Bullet), ~static_cast<cpBitmask>(source)});
cpBodySetVelocity(*get_registry()->get_component<KinematicComponent>(bullet_id)->body, get<1>(bullet));
cpShapeSetFilter(*kinematic_component->shape,
{static_cast<cpGroup>(source_type), static_cast<cpBitmask>(GameObjectType::Bullet),
~static_cast<cpBitmask>(source_type)});
cpBodySetVelocity(*kinematic_component->body, get<1>(bullet));
}

auto PhysicsSystem::get_nearest_item(const GameObjectID game_object_id) const -> GameObjectID {
Expand Down
16 changes: 8 additions & 8 deletions src/hades_extensions/src/factories.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ auto get_hitboxes() -> auto & {
/// The bullet factory.
///
/// @return The components for the bullet.
const auto bullet_factory{[] {
const auto bullet_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<Damage>(10, -1),
std::make_shared<KinematicComponent>(),
Expand All @@ -38,7 +38,7 @@ const auto bullet_factory{[] {
/// The enemy factory.
///
/// @return The components for the enemy.
const auto enemy_factory{[] {
const auto enemy_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<Armour>(50, 5),
std::make_shared<Attack>(std::vector{AttackAlgorithm::Ranged}),
Expand All @@ -61,7 +61,7 @@ const auto enemy_factory{[] {
/// The wall factory.
///
/// @return The components for the wall.
const auto wall_factory{[] {
const auto wall_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<KinematicComponent>(true),

Expand All @@ -71,7 +71,7 @@ const auto wall_factory{[] {
/// The floor factory.
///
/// @return The components for the floor.
const auto floor_factory{[] {
const auto floor_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<KinematicComponent>(),
};
Expand All @@ -80,7 +80,7 @@ const auto floor_factory{[] {
/// The goal factory.
///
/// @return The components for the goal.
const auto goal_factory{[] {
const auto goal_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<KinematicComponent>(),
std::make_shared<PythonSprite>(),
Expand All @@ -90,7 +90,7 @@ const auto goal_factory{[] {
/// The player factory.
///
/// @return The components for the player.
const auto player_factory{[] {
const auto player_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<Armour>(100, 5),
std::make_shared<Attack>(
Expand Down Expand Up @@ -120,7 +120,7 @@ const auto player_factory{[] {
/// The health potion factory.
///
/// @return The components for the health potion.
const auto health_potion_factory{[] {
const auto health_potion_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<EffectLevel>(1, 3),
std::make_shared<PythonSprite>(),
Expand All @@ -131,7 +131,7 @@ const auto health_potion_factory{[] {
/// The chest factory.
///
/// @return The components for the chest.
const auto chest_factory{[] {
const auto chest_factory{[](const int /*level*/) {
return std::vector<std::shared_ptr<ComponentBase>>{
std::make_shared<PythonSprite>(),
std::make_shared<KinematicComponent>(),
Expand Down
37 changes: 23 additions & 14 deletions src/hades_extensions/src/game_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include "factories.hpp"

namespace {
// The deviation of the level distribution.
constexpr int LEVEL_DISTRIBUTION_DEVIATION{2};

// The number of cellular automata runs to perform.
constexpr int CELLULAR_AUTOMATA_SIMULATIONS{3};

Expand All @@ -26,12 +29,15 @@ constexpr int ENEMY_RETRY_ATTEMPTS{3};
} // namespace

GameEngine::GameEngine(const int level, const std::optional<unsigned int> seed)
: registry_(std::make_shared<Registry>()), generator_(0, std::mt19937{std::random_device{}()}), player_id_(-1) {
: level_(level),
random_generator_(seed.has_value() ? seed.value() : std::random_device{}()),
level_distribution_(level, LEVEL_DISTRIBUTION_DEVIATION),
registry_(std::make_shared<Registry>()),
player_id_(-1) {
if (level < 0) {
throw std::length_error("Level must be bigger than or equal to 0.");
}
const std::mt19937 random_generator{seed.has_value() ? seed.value() : std::random_device{}()};
generator_ = MapGenerator{level, random_generator};
generator_ = MapGenerator{level, random_generator_};
generator_.generate_rooms()
.place_obstacles()
.create_connections()
Expand All @@ -56,11 +62,8 @@ GameEngine::GameEngine(const int level, const std::optional<unsigned int> seed)
}

void GameEngine::create_game_objects() {
// Create the registry and get the grid and factories
const auto &grid{*generator_.get_grid().grid};
const auto &factories{get_factories()};

// Create the game objects ignoring empty and obstacle tiles
const auto &grid{*generator_.get_grid().grid};
for (auto i{0}; i < static_cast<int>(grid.size()); i++) {
const auto tile_type{grid[i]};
if (tile_type == TileType::Empty || tile_type == TileType::Obstacle) {
Expand All @@ -78,11 +81,12 @@ void GameEngine::create_game_objects() {
};
const auto game_object_type{tile_to_game_object_type.at(tile_type)};
const auto [x, y]{generator_.get_grid().convert_position(i)};
if (tile_type != TileType::Wall) {
registry_->create_game_object(GameObjectType::Floor, cpv(x, y), factories.at(GameObjectType::Floor)());
if (tile_type != TileType::Wall && tile_type != TileType::Floor) {
registry_->create_game_object(GameObjectType::Floor, cpv(x, y),
get_game_object_components(GameObjectType::Floor));
}
const auto game_object_id{
registry_->create_game_object(game_object_type, cpv(x, y), factories.at(game_object_type)())};
registry_->create_game_object(game_object_type, cpv(x, y), get_game_object_components(game_object_type))};
if (tile_type == TileType::Player) {
player_id_ = game_object_id;
}
Expand All @@ -103,10 +107,9 @@ void GameEngine::generate_enemy(const double /*delta_time*/) {
floor_positions.push_back(cpv(x, y));
}
}
std::ranges::shuffle(floor_positions, std::mt19937{std::random_device{}()});
std::ranges::shuffle(floor_positions, random_generator_);

// Determine which floor to place the enemy on only trying ENEMY_RETRY_ATTEMPTS times
const auto &factories{get_factories()};
for (auto attempt{0}; attempt < std::min(static_cast<int>(floor_positions.size()), ENEMY_RETRY_ATTEMPTS); attempt++) {
const auto position{floor_positions[attempt]};
if (const auto player_position{cpBodyGetPosition(*registry_->get_component<KinematicComponent>(player_id_)->body)};
Expand All @@ -118,9 +121,15 @@ void GameEngine::generate_enemy(const double /*delta_time*/) {
}

// Create the enemy and set its required data
const auto enemy_id{
registry_->create_game_object(GameObjectType::Enemy, position, factories.at(GameObjectType::Enemy)())};
const auto enemy_id{registry_->create_game_object(GameObjectType::Enemy, position,
get_game_object_components(GameObjectType::Enemy))};
registry_->get_component<SteeringMovement>(enemy_id)->target_id = player_id_;
return;
}
}

auto GameEngine::get_game_object_components(const GameObjectType game_object_type)
-> std::vector<std::shared_ptr<ComponentBase>> {
const auto &factories{get_factories()};
return factories.at(game_object_type)(std::max(0, static_cast<int>(level_distribution_(random_generator_))));
}
2 changes: 2 additions & 0 deletions src/hades_extensions/src/generation/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ void place_tiles(const Grid &grid, std::mt19937 &random_generator, const TileTyp
}
} // namespace

MapGenerator::MapGenerator() : level_{0}, grid_{0, 0}, random_generator_{std::random_device{}()} {}

MapGenerator::MapGenerator(const int level, const std::mt19937 random_generator)
: level_(level),
grid_{WIDTH.generate_value(level), HEIGHT.generate_value(level)},
Expand Down
6 changes: 3 additions & 3 deletions src/hades_extensions/tests/test_factories.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ TEST_F(FactoriesFixture, TestLoadHitboxTwice) {

/// Test that loading a factory that doesn't require a hitbox works.
TEST_F(FactoriesFixture, TestGetFactoryNoHitboxRequired) {
ASSERT_NO_THROW(get_factories().at(GameObjectType::Floor)());
ASSERT_NO_THROW(get_factories().at(GameObjectType::Floor)(0));
}

/// Test that loading a factory that requires a hitbox works when the hitbox is loaded.
TEST_F(FactoriesFixture, TestGetFactoryHitboxLoaded) {
load_hitbox(GameObjectType::Player, {{0.0, 0.0}});
ASSERT_NO_THROW(get_factories().at(GameObjectType::Player)());
ASSERT_NO_THROW(get_factories().at(GameObjectType::Player)(0));
}

/// Test that loading a factory that requires a hitbox which isn't loaded throws an exception.
TEST_F(FactoriesFixture, TestGetFactoriesHitboxNotLoaded) {
ASSERT_THROW(get_factories().at(GameObjectType::Player)(), std::out_of_range);
ASSERT_THROW(get_factories().at(GameObjectType::Player)(0), std::out_of_range);
}

0 comments on commit fad6c9b

Please sign in to comment.